E-switch UI Development / FSM

I made a UI toolkit for e-switch lights. It makes new UIs relatively easy to create, makes porting UIs to new hardware relatively easy, and makes bugs relatively easy to avoid. But that still leaves a big question:

What makes a good e-switch UI?

So I’m hoping to get some discussion going about what people want in a UI. What’s good, BLF?

The UI toolkit is called Spaghetti Monster, or FSM for short.

(pictured: the [insert your preferred F-word] Spaghetti Monster)

FSM implements an event-driven finite state machine for cooperative multitasking, and attempts to abstract out most of the hardware-specific details so the main code can focus entirely in the interface.

The basic idea is that you #define some options, #include a single file, and then start writing a UI. The UI consists of one or more State objects for the finite state machine, a setup() function, a loop() function, and whatever else you need for the particular UI.

State objects are actually just callback functions. They take an Event type and a 16-bit integer parameter, and they are expected to return "EVENT_HANDLED" or "EVENT_NOT_HANDLED" depending on whether they do anything with the Event they're given. States can be stacked, so if the top-most State doesn't handle an Event, an underlying State gets a chance to handle it instead. The stacking also means it's possible to have utility States which do common tasks like "ask the user to enter a number, then return to the previous state". It can just pop() itself off the stack and return to the previous State without having to care what that State was.

The setup() function runs once every time power is connected. It should do things like loading eeprom and then pushing a default State onto the stack.

The loop() function runs repeatedly, whenever the MCU is otherwise idle. It's for doing long-running tasks, like blinking out a SOS pattern... or for tasks which shouldn't happen until the system is idle, like going into standby mode.

It can also have a low_voltage() function which runs whenever LVP activates, or this can be implemented by handling the EV_voltage_low Event on a per-state basis. Similar story for thermal regulation.

Otherwise most of the UI code ends up fairly similar to how I usually describe UIs. It's a list of States, and each State has a list of Events it accepts and the corresponding actions to take for each Event.

For example, let's say we wanted the following UI, similar to an Olight Baton:

  • From off:
    • 1 click: Go to lowest level.
  • While on:
    • 1 click: Turn off.
    • Hold: Ramp up one step. At highest level, loop back to lowest level.
    • When voltage is low, ramp down in steps then turn off.

The code would look something like this:

uint8_t off_state(Event event, uint16_t arg) {
    if (event == EV_enter_state) {
        go_to_standby = 1; // sleep while off
        return EVENT_HANDLED;
    // 1 click: go to lowest level
    else if (event == EV_1click) {
        set_state(ramp_state, 1);
        return EVENT_HANDLED;

uint8_t ramp_state(Event event, uint16_t arg) {
// turn LED on when we first enter the mode
if (event == EV_enter_state) {
// 1 click: off
else if (event == EV_1click) {
set_state(off_state, 0);
// hold: change brightness every HOLD_TIMEOUT ticks
else if (event == EV_click1_hold) {
if ((arg % HOLD_TIMEOUT) == 0) {
set_level((actual_level+1) % (MAX_LEVEL));

void low_voltage() {
// step down by one level or turn off
if (actual_level > 1) {
set_level(actual_level - 1);
else {
set_state(off_state, 0);

void setup() {
// turn on at lowest level when battery is connected
push_state(ramp_state, 1);

void loop() {

That's pretty much all it takes to implement a very basic Baton-like stepped ramping UI.

This example is only missing a couple small things to make it actually compile and run, like the config #defines and #include at the top, some declarations, and a ramp table for PWM values. It's intended as a simple introduction to how FSM works. Or for a more complete version which has more features, here is one which actually compiles.

I hope it's fairly easy to read, as far as C code goes, and easy to modify. The point of all this is to take the "spaghetti code" normally involved in writing firmware... and lock it in a box where you don't have to see it or care about it. All the spaghetti bits are hidden away under the hood.

The code is available in two branches... "trunk" for the stable version, or the "fsm" branch for more up-to-date development:


Here’s a summary of FSM-based interfaces created so far.


A simple on-off interface.

  • Hold the button: Light is on.
  • Release the button: Light is off.

Mostly added as an example.


Similar to the Olight Baton series.

From off:

  • 1 click: Turn on at last-ramped level.
  • Hold: Lowest level, then ramp up in steps.
  • 2 clicks: Highest level.
  • 4 clicks: Soft lockout.

While on:

  • 1 click: Off.
  • Hold: Ramp up in steps. Loops back from highest level to lowest.
  • 2 clicks: Go to/from highest level.

Soft lockout:

  • 4 clicks: Turn on at “low” level.

Includes LVP and thermal regulation.


Nearly exact clone of Zebralight’s UI from 2016.

From off:

  • 1 click: High mode.
  • 2 clicks: Med mode.
  • 3 clicks: Blinky mode.
  • 4 clicks: Battcheck mode.
  • Hold: Low mode, then ramp up.

Low/med/high modes:

  • 1 click: Off.
  • Hold: Ramp through low, med, and high… always starting at low.
  • 2 clicks: Toggle between H1/H2, M1/M2, or L1/L2 levels.
  • Click, hold: Change the brightness of current mode’s secondary (H2/M2/L2) level.

Battcheck mode:

  • Blinks 1 to 4 times then turns itself off. (0 blinks for less than 3.0V, 5 blinks for more than 4.2V)

Blinky mode:

  • 1 click: Off.
  • 2 clicks: Go to next blinky mode. The sequence is:
    • 0.2 Hz beacon at L1
    • 0.2 Hz beacon at H1
    • 4 Hz tactical strobe at H1
    • 19 Hz tactical strobe at H1

Remembers a few things after battery change:

  • Primary or secondary toggle for high/med/low.
  • H2/M2/L2 levels.
  • Which blinky was last used.

Includes LVP and thermal regulation.


Fancy all-in-one UI inspired by Tom E’s fantastic Narsil. It’s still a work in progress, so it’s expected to change.

I should probably mention that the smooth and discrete ramps have independent floor/ceiling values. You can have one with the ceiling all the way up, and one with the ceiling at a comfortable safe level. Or you can have one with moon and one without.

Discrete ramp mode has anywhere from 2 to 150 steps, spaced evenly (or as close to “even” as possible with integers).

A “hold” always ramps up, and a “click-release-hold” always ramps down.

Sorry about the 5-click action for momentary mode. Too many states available from “off”.

Beacon and momentary mode use the last-ramped brightness level.

Momentary mode is permanent until power is disconnected.

“Good night” mode starts at low and slowly ramps down for an hour, then shuts itself off. Intended for tail-standing next to a bed at night.

I set the PWM levels to start at 1/255. Depending on the hardware, this may be too low to make any light. However, the ramp floor can be set to whatever level you think looks good.

Config modes are similar to bistro — they blink out a number, pause, then “buzz” for a while. Click during the “buzz” to set a new value. It’ll keep buzzing as long as you keep clicking. If you want to skip an option, just wait without clicking.

Settings which are remembered after a battery change:

  • Smooth ramp floor / ceiling.
  • Discrete ramp floor / ceiling / steps.
  • Beacon timing.
  • Temperature limit.
  • Which strobe mode was last used.
  • Bike flasher brightness.
  • Party strobe speed.
  • Tactical strobe speed.

Includes LVP and smooth thermal regulation.

Werner Dual-Switch

Since it’s a thing people might want, and since I think Lexel may have been requesting it, I took a moment to make a Werner-style momentary UI, side e-switch plus tail clicky-switch.


The short version is:

While completely off (power disconnected):

  • Click tail to turn on at memorized level.
  • Hold e-switch and click tail to enter utility mode.

While on (regular “on” mode):

  • Click tail switch to turn off.
  • Click side switch to go brighter.
  • Hold side switch to go dimmer.

In utility mode:

  • Click side switch to go to regular “on” mode.
  • Hold side switch to go turn on at lowest level.
  • Double click side switch to turn on at highest level.
  • 3 clicks: Battcheck.
  • 4 clicks: Ramp config.

Battcheck mode: Blinks out battery voltage.

  • Click to go back to utility mode.
  • 2 clicks for tempcheck mode.

Tempcheck mode: Blinks out current temperature in C.

  • Click to go back to utility mode.
  • 2 clicks for battcheck mode.
  • 4 clicks for thermal config mode.

Ramp config:

  • During first “buzz”, click N times to set floor level to N out of 150 total levels.
  • During second “buzz”, click N times to set ceiling level to 151 minus N.
  • During third “buzz”, click N times to set number of total brightness steps.
  • Each “buzz” can be skipped to leave the value as-is.

Thermal config:

  • During first “buzz”, click N times to calibrate current temperature to N degrees C.
  • During second “buzz”, click N times to set thermal ceiling to 30 C + N.
  • Each “buzz” can be skipped to leave the value as-is.

Includes LVP and smooth thermal regulation, copied directly from Anduril.

I think there’s 2 good types of UI:

1) one that is so simple that it works for everyone without thinking, on-off is one of those glorious UI’s, but ramp up-ramp down is another one. There’s no objection against safety measures going on in the background, like reverse polarity protection, LVP and thermal management.

2) a UI that can be configured to anyone’s preferred simple or difficult UI, so also the ones in 1) , but any variation should be possible. Key is that once it is configured, you just notice your simple UI and no surprises happen like unintended strobes or blasts in your face. I was very charmed by led4power’s LD3 user interface, although it is a clicky UI.

(If the BLF-A6 driver was available in just ramp up/ramp down with background safety stuff, I would swap all my drivers.)

I think NarsilM is close to be perfect,
if you like more options to play with and understand how to change the settings
also in modes its quite simple to operate

I filled in the “reserved” posts #2 and #3 with a summary of the toolkit and a summary of what has been made with it so far.

I agree that NarsilM is pretty dang spiffy, to use some awkwardly mild words for it. I always wanted to change a few things though, which is how I ended up with the unnamed UI in comment #3.

I don’t know what to call it though. Maybe I should call it “Supafly”. :stuck_out_tongue:

I’m not sure if it would interest you, but have you looked at Crescendo? It runs on a BLF-A6 driver and does smooth ramp up/down with background safety stuff like LVP and thermal regulation.

Also a bunch of blinkies, but those can be compiled out or avoided.

:laughing: Nice thread TK! Love them flowcharts! :wink:

that deathstar moon… :exclamation: :smiley:

May I ask, what is the Good Night blinkie mode?

May you be touched by its noodly appendages.

Keep up the great work

The “good night” mode starts at “low” and slowly ramps down for an hour, then shuts itself off.

It’s not really a blinky, but there wasn’t a more appropriate place to put it. And I like to do battcheck on the way to goodnight, so that puts them right next to each other. Perhaps I should call that group “specials” or something instead of “blinkies”.

Thumbs up to “unnamed” and crescendo. Both perfect

I really would like to see something like an HDS clicky UI.
I’m still using my HDS with SSC P4 (100 lumens) despite its dim LED just because of the excellent tint + CRI and very particular UI.

I didn’t play with most recent UI then I don’t know if HDS UI is still worth it.

The term ‘goddess’ is well-deserved. Wish i knew you in the everyday.

I hope “unnamed” makes it’s way into some drivers. I have some nice lights that have uninspired UIs, I would gut them all in a heartbeat for this.
It looks like a fast click always leads to OFF, that is great in case a user has a finger spasm or a non-flashoholic gets a hold of it and starts spazzing with the button.
Just turn it off.
I used to love the programming options of HDS and Liteflux but they have gotten way too expensive for me.

Beacons, strobes, the brilliant Good Night, they’ll all get used.
Some O-lights have a little 1 or 2 minute timer, I’ve always wondered why they didn’t make it longer. It’s pretty cool to put next to the bed at night…well… cool for 2 minutes. One that ramps down over the course of an hour would get used every night.
Maybe this will show up in the D1?
Great work TK!!

I decided to try cloning the Meteor M43 UI tonight.

First I cloned UI1 — no problem.

Cloned UI2 — no problem.

Started on UI3 and then I noticed… it has different actions defined for “short click”, “long click”, and “hold”. And different actions for “two short clicks”, “short click then long click”, and “short click, then hold”. :person_facepalming:

No wonder I could never get some of the actions to work reliably.

Otherwise it’s going well though. I’ll continue later when I get my palm unstuck from my forehead.

In SM: Unnamed

what is the difference between
from OFF: 2 fast clicks
from OFF: click, then hold ?
Are the tips of the two green arrows pointing right?

My guess:
from OFF: 2 fast clicks
go to a temperature secure level below TURBO.
If I want then TURBO I must double click.

from OFF: click, then hold
goes to the same line. o_O
maybe this should point directly to TURBO?


I’m really liking the looks of the unnamed UI. The user configurable floor and ceiling within the ramp range is a very nice feature. I also like the ability to switch between smooth and discrete ramping on the fly without going into configuration settings.

Awesome work as always.

If you double-click from off, it goes to the ramp ceiling level and stays there.

If you click, release, then hold from off, it goes to the ramp ceiling level and ramps down. It’s like a double click, except you hold the second click and it ramps until you let go.

Basically, it lets you navigate from the top or bottom from off. So, there isn’t really any need to configure whether modes are in low-to-high vs high-to-low order, because you get both.

Great work as usual TK!

Regarding the question “what is the best single side switch UI”, i share djozz comments.

Apart from my personal preferences the one thing that worries me the most is whether a non flashaolic can use a light without any explanation. We enthusiasts tend to forget about it, but most people only think about flashlights as something you turn on and off, providing a decent brightness level - not moon/firefly or blindingly bright turbo either. A mid level usually provides more then what they would expect anyway.

Some UIs offer different mode groups, including a ‘simple’ one, but you have to switch group and you may not be around to do so if someone needs the light on his own or in an emergency. So i think the above described behavior should be the default in any UI.

As a side note, i’ve witnessed that ‘people’ can easily resort to a long press if things don’t act has they expect. It seems natural to press a switch harder and longer when in doubt. That’s something to consider while designing a UI.

One of my favorite UI is the DQG Tiny 26650 3rd. Single click goes off/mid/off - providing 8 hours at 600 lumen. Long press from off goes to turbo then single click off. While on, long press cycles through modes.

I did also like the good old SRK style UI. off/high/mid/off all single clicks. Anyone can figure it out. I’d rather have off/low/mid/high/off but you get the idea. Double click goes disco on some.

These are awesome!

SupaFly is just that!

I would love to get Crescendo on a couple of my A6 drivers, but would never be able to accomplish this myself.

Since this is the proper place for it, my suggestion was to turn the double blink when installing a new battery to moonlight instead of something bright like now. Reason: If you unscrew the cap for lockout and use the light in the middle of the night, you have to see 2 bright flashes first.

Second was no memorized moon or turbo since shortcuts already exist (this was someone else’s suggestion which I liked)

Thank you!

My rookie input...

Make a simple from off, hold button to ramp ui, 1 click to memory and devote the rest of resources to a better Thermal regulation sampling system and smooth hiccup free ramp.

Going on the premise of heat is the worst enemy of durability/longevity/lumens,,,

Then maybe create an advanced ui/hardware that allows individual heat sensors for each led to show/control its individual performance, maybe helping troubleshoot a problem with that led circuit/dtp/etc, that makes it run hotter, lower lumen, etc, With a hardware/ui system that lets one turn on/off each individual led and regulate power level of each led...? this would be a flashoholic only Ui, or maybe just a pipe dream...lol

My only fear is on improving the heat reg is that the on chip heat regulation/sensor without remote sensor is not ideal for the fastest most precise, consistent, efficient heat regulation. So until a dedicated circuit/chip is used, maybe with a remote sensor soldered on the dtp, then all the software in the world won't help..?