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;
}
return EVENT_NOT_HANDLED;
}
uint8_t ramp_state(Event event, uint16_t arg) {
// turn LED on when we first enter the mode
if (event == EV_enter_state) {
set_level(arg);
return EVENT_HANDLED;
}
// 1 click: off
else if (event == EV_1click) {
set_state(off_state, 0);
return EVENT_HANDLED;
}
// 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));
}
return EVENT_HANDLED;
}
return EVENT_NOT_HANDLED;
}
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:
http://bazaar.launchpad.net/~toykeeper/flashlight-firmware/fsm/files/head:/ToyKeeper/spaghetti-monster/