From 57c93fd2978ed5b5e64f4620803b7a2cd7767ed2 Mon Sep 17 00:00:00 2001 From: Luke Hoersten Date: Sat, 13 Jun 2026 22:52:41 -0500 Subject: M6: state machine — POST /state, idle timer, /frame 409 guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit state_machine.{h,c} — central wake/sleep transitions: - state_machine_init() creates the esp_timer one-shot for the idle timer. - state_machine_set(target) is idempotent and atomic. On AWAKE: backlight on, idle timer (re)armed. On ASLEEP: idle timer cancelled, backlight off. Rejects with INVALID_STATE when the device is unconfigured. - state_machine_frame_painted() restarts the idle timer if awake; called by /frame after each successful paint. - Idle-timer callback transitions to ASLEEP. TODO M7 hook: outbound POST {viewport, state:sleep} to /state. http_api.c: - POST /state: parse {state}, accept "wake"/"sleep", reject others 400. Unconfigured device → 409 "device unconfigured". Already-in-state → 204 (idempotent no-op). Successful transition → 204. - POST /frame: 409 Conflict when state != AWAKE. After successful paint, call state_machine_frame_painted() so the idle clock keeps resetting while frames stream. app_main: - Initialize state_machine before http_api so the route handler can drive it from request 0. - After display_init(), reconcile the panel with the boot state: UNCONFIGURED → test pattern (placeholder until M8 IP screen) ASLEEP → display_sleep() so a configured device boots dark AWAKE → leave on (not reached on fresh boot) Disable path: idle_timeout_ms=0 in /config means the timer is never armed and a wake state persists until /state {sleep} or a power cycle. Build clean against ESP-IDF 5.4 (binary ~645 KB). TESTING.md M6 expands with idempotency checks, 409-when-asleep, 409-when- unconfigured, idle-timer firing within idle_timeout_ms+slack, /frame restarting the idle timer, idle-timer disable via idle_timeout_ms=0, and the M7 dependency note about the missing outbound sleep POST. --- main/app_main.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'main/app_main.c') diff --git a/main/app_main.c b/main/app_main.c index 4ac925d..126bf15 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -4,6 +4,7 @@ #include "mdns_service.h" #include "net_eth.h" #include "nvs_config.h" +#include "state_machine.h" #include "viewport_state.h" #include "esp_event.h" @@ -30,25 +31,37 @@ void app_main(void) ESP_LOGW(TAG, "no DHCP lease after 30s, will keep retrying in the background"); } + ESP_ERROR_CHECK(state_machine_init()); ESP_ERROR_CHECK(mdns_service_start()); ESP_ERROR_CHECK(http_api_start()); // Display is best-effort — a missing/miswired panel must not kill - // networking + /state. M3 acceptance: show a test pattern. + // networking + /state. if (display_init() == ESP_OK) { - display_test_pattern(); - ESP_LOGI(TAG, "display up — test pattern on screen"); + // Reconcile panel with current run-state: + // UNCONFIGURED -> placeholder test pattern (M8 replaces with IP screen) + // ASLEEP -> backlight off (configured device booted asleep) + // AWAKE -> leave on (shouldn't happen on a fresh boot) + viewport_state_lock(); + viewport_run_state_t s = viewport_state_get()->state; + viewport_state_unlock(); + + if (s == VIEWPORT_STATE_ASLEEP) { + display_sleep(); + ESP_LOGI(TAG, "display up — configured, backlight off (asleep)"); + } else { + display_test_pattern(); + ESP_LOGI(TAG, "display up — test pattern (unconfigured)"); + } } else { ESP_LOGW(TAG, "display init failed — continuing without panel"); } - // JPEG decoder is the M5 dependency for POST /frame. Best-effort: - // if it fails (e.g. hardware not present in sim) /frame just returns 500. + // JPEG decoder is the M5 dependency for POST /frame. Best-effort. if (jpeg_decoder_init() != ESP_OK) { ESP_LOGW(TAG, "jpeg decoder init failed — /frame will be unavailable"); } - // TODO M6: POST /state + idle timer - // TODO M7: Capacitive touch -> outbound /state POST + // TODO M7: Capacitive touch -> outbound /state POST to /state // TODO M8: Local screens (IP, loading) + BOOT button } -- cgit v1.2.3