Skip to main content

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

StateButtonEvent typeAction
IDLEBTN_SELECTLONG_PRESS→ MENU
IDLEanyPRESS(ignored)
ALARM_FIRINGBTN_SNOOZEPRESS→ SNOOZED; stop audio; set snooze timer
ALARM_FIRINGBTN_DISMISSPRESS→ IDLE; stop audio
ALARM_FIRINGBTN_UP / BTN_DOWNPRESS(ignored during alarm)
SNOOZEDBTN_DISMISSPRESS→ IDLE; clear snooze timer
MENUBTN_UPPRESSscroll menu up
MENUBTN_DOWNPRESSscroll menu down
MENUBTN_SELECTPRESSenter selected menu item
MENUBTN_BACKPRESS→ IDLE
ALARM_SETBTN_UPPRESSincrement selected field
ALARM_SETBTN_DOWNPRESSdecrement selected field
ALARM_SETBTN_SELECTPRESSadvance to next field; save on last field
ALARM_SETBTN_BACKPRESScancel; → 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;

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);