diff options
| author | Luke Hoersten <[email protected]> | 2026-06-13 22:52:41 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-06-13 22:52:41 -0500 |
| commit | 57c93fd2978ed5b5e64f4620803b7a2cd7767ed2 (patch) | |
| tree | 545d3a4ed3ee42cc87d4bb78ba9321eeadec19b2 /main/state_machine.c | |
| parent | 6cbdd4ed7466da28e524c9e0804722428a4b9698 (diff) | |
M6: state machine — POST /state, idle timer, /frame 409 guard
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 <scrypted>/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.
Diffstat (limited to 'main/state_machine.c')
| -rw-r--r-- | main/state_machine.c | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/main/state_machine.c b/main/state_machine.c new file mode 100644 index 0000000..22f3f56 --- /dev/null +++ b/main/state_machine.c @@ -0,0 +1,89 @@ +#include "state_machine.h" + +#include "esp_check.h" +#include "esp_log.h" +#include "esp_timer.h" + +#include "display.h" + +static const char *TAG = "state"; + +static esp_timer_handle_t s_idle_timer; + +static void arm_idle_timer_unlocked(void) +{ + viewport_state_lock(); + uint32_t ms = viewport_state_get()->idle_timeout_ms; + viewport_state_unlock(); + if (ms == 0) return; + esp_timer_stop(s_idle_timer); + esp_timer_start_once(s_idle_timer, (uint64_t)ms * 1000ULL); +} + +static void disarm_idle_timer(void) +{ + esp_timer_stop(s_idle_timer); +} + +static void idle_timer_fired(void *arg) +{ + ESP_LOGI(TAG, "idle timer expired — sleeping"); + state_machine_set(VIEWPORT_STATE_ASLEEP); + // TODO M7: state_client_post(VIEWPORT_STATE_ASLEEP); // tell Scrypted +} + +esp_err_t state_machine_init(void) +{ + esp_timer_create_args_t args = { + .callback = &idle_timer_fired, + .name = "viewport_idle", + }; + return esp_timer_create(&args, &s_idle_timer); +} + +esp_err_t state_machine_set(viewport_run_state_t target) +{ + if (target != VIEWPORT_STATE_AWAKE && target != VIEWPORT_STATE_ASLEEP) + return ESP_ERR_INVALID_ARG; + + viewport_state_lock(); + viewport_state_t *st = viewport_state_get(); + + if (!st->configured) { + viewport_state_unlock(); + return ESP_ERR_INVALID_STATE; + } + if (st->state == target) { + viewport_state_unlock(); + return ESP_OK; // idempotent no-op + } + st->state = target; + viewport_state_unlock(); + + if (target == VIEWPORT_STATE_AWAKE) { + if (display_is_up()) display_wake(); + arm_idle_timer_unlocked(); + ESP_LOGI(TAG, "AWAKE"); + } else { + disarm_idle_timer(); + if (display_is_up()) display_sleep(); + ESP_LOGI(TAG, "ASLEEP"); + } + return ESP_OK; +} + +void state_machine_frame_painted(void) +{ + viewport_state_lock(); + bool awake = (viewport_state_get()->state == VIEWPORT_STATE_AWAKE); + viewport_state_unlock(); + if (awake) arm_idle_timer_unlocked(); +} + +viewport_run_state_t state_machine_current(void) +{ + viewport_state_lock(); + viewport_run_state_t s = viewport_state_get()->state; + viewport_state_unlock(); + return s; +} |
