# ESP32 example — button/LED machine with timers A minimal but complete ESP-IDF application showing the library's intended embedded usage: **the application is C**, the state machine is C++ behind an `extern "C"` facade, and all timing flows through the machine's own deadlines. ## What it does - BOOT button (GPIO0, active low), LED on GPIO2. - Short press toggles the LED. - Hold ≥ 1.5 s: the LED blinks at 4 Hz until released. - Presses are debounced with a 30 ms qualification window. All three timings — debounce, long-press, blink — are `fsm::after(...)` columns in the state table ([main/button_fsm.cpp](main/button_fsm.cpp)); no `esp_timer` callbacks, no timer tasks, no cancellation races. ## The event-loop pattern [main/main.c](main/main.c) is the part worth copying: 1. The GPIO ISR posts edge events into a FreeRTOS queue — it never touches the machine. 2. The task asks `btn_next_deadline()` and blocks on `xQueueReceive` for exactly that long (the machine owns the nearest-deadline computation, so the loop cannot forget a timeout source). 3. Wake-ups deliver queued events via `btn_dispatch()`; `btn_service()` fires whatever came due. Arm, fire and transit all happen on this one task, which removes timer/cancel races *by construction*. ## Build ```sh . $IDF_PATH/export.sh idf.py set-target esp32 # or esp32s3/c3/... idf.py build flash monitor ``` The `fsm` dependency is declared in [main/idf_component.yml](main/idf_component.yml): building inside this repository resolves it from the source tree via `override_path`, while a standalone copy of this example fetches `fishwaldo/fsm` from the component registry. In your own project, depend on it the same way: ```yaml dependencies: fishwaldo/fsm: "^0.4.0" ``` Kconfig options live under *Component config → FSM library* (`CONFIG_FSM_OBSERVER`, `CONFIG_FSM_INTROSPECTION`).
To create a project from this example, run:
idf.py create-project-from-example "fishwaldo/fsm=0.4.0:esp32"