Button Input Handling
Button events from the driver queue are dispatched to the state machine, which determines the appropriate response based on the current application state.
Button → State Machine Pipeline
Button Action Map
| State | Button | Event type | Action |
|---|---|---|---|
| IDLE | BTN_SELECT | LONG_PRESS | → MENU |
| IDLE | any | PRESS | (ignored) |
| ALARM_FIRING | BTN_SNOOZE | PRESS | → SNOOZED; stop audio; set snooze timer |
| ALARM_FIRING | BTN_DISMISS | PRESS | → IDLE; stop audio |
| ALARM_FIRING | BTN_UP / BTN_DOWN | PRESS | (ignored during alarm) |
| SNOOZED | BTN_DISMISS | PRESS | → IDLE; clear snooze timer |
| MENU | BTN_UP | PRESS | scroll menu up |
| MENU | BTN_DOWN | PRESS | scroll menu down |
| MENU | BTN_SELECT | PRESS | enter selected menu item |
| MENU | BTN_BACK | PRESS | → IDLE |
| ALARM_SET | BTN_UP | PRESS | increment selected field |
| ALARM_SET | BTN_DOWN | PRESS | decrement selected field |
| ALARM_SET | BTN_SELECT | PRESS | advance to next field; save on last field |
| ALARM_SET | BTN_BACK | PRESS | cancel; → MENU |
Long-Press Detection
BTN_SELECT held for >2 seconds from IDLE opens the menu. The button driver emits
BTN_EVENT_LONG_PRESS when the hold duration exceeds the threshold. The main task
treats it the same as a regular event:
case BTN_EVENT_LONG_PRESS:
if (evt.id == BTN_SELECT && current_state == STATE_IDLE) {
transition_to(STATE_MENU);
}
break;
Menu Navigation
The menu is a simple list of items with a cursor position:
typedef struct {
const char *label;
screen_t screen; // screen to enter on SELECT
} menu_item_t;
static const menu_item_t menu_items[] = {
{ "Set alarm", SCREEN_ALARM_SET },
{ "Volume", SCREEN_VOLUME_SET },
{ "Timezone", SCREEN_TZ_SET },
{ "WiFi setup", SCREEN_WIFI_SETUP },
};
static int menu_cursor = 0;
void menu_handle(button_event_t evt) {
switch (evt.id) {
case BTN_UP:
menu_cursor = (menu_cursor - 1 + ARRAY_LEN(menu_items)) % ARRAY_LEN(menu_items);
request_display_update(SCREEN_MENU);
break;
case BTN_DOWN:
menu_cursor = (menu_cursor + 1) % ARRAY_LEN(menu_items);
request_display_update(SCREEN_MENU);
break;
case BTN_SELECT:
transition_to_screen(menu_items[menu_cursor].screen);
break;
case BTN_BACK:
transition_to(STATE_IDLE);
break;
default: break;
}
}
Inactivity Timeout
If no button is pressed for 30 seconds while in MENU or ALARM_SET, return to IDLE automatically:
static TimerHandle_t inactivity_timer = NULL;
static void inactivity_callback(TimerHandle_t t) {
if (current_state == STATE_MENU || current_state == STATE_ALARM_SET) {
transition_to(STATE_IDLE);
}
}
// Reset the timer on every button event:
xTimerReset(inactivity_timer, 0);