src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/state_machine.c
blob: c9e4367e5e2526e3a7c47572e8cc763fef2b781d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include "state_machine.h"

#include "esp_check.h"
#include "esp_log.h"
#include "esp_timer.h"

#include "display.h"
#include "local_screens.h"
#include "state_client.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_local(VIEWPORT_STATE_ASLEEP);
}

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->state == target) {
        viewport_state_unlock();
        return ESP_OK;  // idempotent no-op
    }
    st->state = target;
    bool configured = st->configured;
    viewport_state_unlock();

    if (target == VIEWPORT_STATE_AWAKE) {
        if (display_is_up()) {
            display_wake();
            // Content choice: configured devices show "Loading…" until
            // Scrypted pushes a /frame; unconfigured devices show the
            // identity screen since there's no Scrypted to push anything.
            if (configured) local_screens_show_loading();
            else            local_screens_show_ip();
        }
        arm_idle_timer_unlocked();
        ESP_LOGI(TAG, "AWAKE (%s)", configured ? "configured" : "unconfigured");
    } 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();
}

void state_machine_set_local(viewport_run_state_t target)
{
    esp_err_t err = state_machine_set(target);
    if (err != ESP_OK) return;
    // Only POST when there's a Scrypted to talk to. Unconfigured tap toggles
    // change the local display state without notifying anyone.
    viewport_state_lock();
    bool configured = viewport_state_get()->configured;
    viewport_state_unlock();
    if (configured) state_client_post(target);
}

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