src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/state_machine.c
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-07-17 18:56:03 -0500
committerLuke Hoersten <[email protected]>2026-07-17 18:56:03 -0500
commit9d94f710f7b00d109bf6f6ee57996f43bce5c67f (patch)
treeb86320353884536d2d1b342efa27600b19c845de /main/state_machine.c
parent866015678918560a0d0519be8a2549e4fc23b2cd (diff)
main: code-review fixes — brightness units, OTA stall cap, stats + locking
- display: s_last_pwm cached the raw 0-100 percentage at init but display_wake writes it straight to REG_PWM (0-255 duty) — first wake ran visibly dim until a /config brightness change. Shared pct_to_duty helper now converts in both paths. - http_api: cap consecutive OTA recv timeouts (a stalled client spun forever holding s_ota_in_progress, wedging OTA until reboot); cap viewport name at 54 chars so viewport-<name> fits the 63-byte mDNS label; log mdns_service_refresh failures; /state builds JSON from a snapshot instead of holding the state lock across ~25 cJSON allocs; respond_400 delegates to respond_status. - stream_server: bytes_in_window now counts painted frames only (frames discarded while asleep inflated the first post-wake window's MB/s, avg-jpeg and chunk/wire averages; recv_bytes folded in); so_rcvbuf carried into the stats snapshot under the mux; drop dead HEADER_BYTES; merge read_body_instrumented into read_n. - state_machine: transition mutex serializes concurrent wake/sleep (display side-effects ran after the state lock dropped, so racing callers could leave the backlight contradicting st->state); wake-path placeholder paint now takes the decoder lock like the stream path (concurrent esp_lcd_panel_draw_bitmap from two tasks isn't safe). - local_screens: overlay paint takes the decoder lock too; check the overlay timer create; panel dims from viewport_state.h. - jpeg_decoder: try_lock before init returns busy instead of passing a NULL semaphore to xSemaphoreTake. - net_eth: clear cached IP string on link down (stale /state + info). - dead code: display_present_bgr888, TOUCH_FT5426_ADDR, touch s_task, JPEG_DECODER_MAX_OUTPUT_BYTES; doc drift in nvs_config.h / jpeg_decoder.h / app_main flag legend.
Diffstat (limited to 'main/state_machine.c')
-rw-r--r--main/state_machine.c25
1 files changed, 23 insertions, 2 deletions
diff --git a/main/state_machine.c b/main/state_machine.c
index 5e688bc..eb2548c 100644
--- a/main/state_machine.c
+++ b/main/state_machine.c
@@ -4,15 +4,22 @@
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
#include "freertos/task.h"
#include "display.h"
+#include "jpeg_decoder.h"
#include "local_screens.h"
#include "state_client.h"
static const char *TAG = "state";
static esp_timer_handle_t s_idle_timer;
+// Serializes whole transitions (state write + display side-effects).
+// Without it, concurrent wake+sleep callers (HTTP task, touch task, idle
+// timer) can interleave display_wake/display_sleep after the state lock
+// is dropped and leave the backlight not matching st->state.
+static SemaphoreHandle_t s_transition_mutex;
// ms = idle_timeout_ms snapshotted under the caller's state lock.
// ms == 0 disables the timer (caller's idle_timeout_ms feature).
@@ -31,6 +38,8 @@ static void idle_timer_fired(void *arg)
esp_err_t state_machine_init(void)
{
+ s_transition_mutex = xSemaphoreCreateMutex();
+ if (!s_transition_mutex) return ESP_ERR_NO_MEM;
esp_timer_create_args_t args = {
.callback = &idle_timer_fired,
.name = "viewport_idle",
@@ -43,11 +52,14 @@ 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;
+ xSemaphoreTake(s_transition_mutex, portMAX_DELAY);
+
viewport_state_lock();
viewport_state_t *st = viewport_state_get();
if (st->state == target) {
viewport_state_unlock();
+ xSemaphoreGive(s_transition_mutex);
return ESP_OK; // idempotent no-op
}
st->state = target;
@@ -63,8 +75,16 @@ esp_err_t state_machine_set(viewport_run_state_t target)
// backlight comes up. Without the delay, esp_lcd_panel_draw_bitmap
// returns before the panel has been refreshed and the user sees
// a flash of the previous /frame's contents.
- if (configured) local_screens_show_loading();
- else local_screens_show_info();
+ //
+ // Decoder lock: the stream decode-task paints under this lock,
+ // and concurrent esp_lcd_panel_draw_bitmap calls from two tasks
+ // aren't safe. If the decoder is busy >500 ms a live frame is
+ // painting anyway — skip the placeholder and just light up.
+ if (jpeg_decoder_try_lock(500)) {
+ if (configured) local_screens_show_loading();
+ else local_screens_show_info();
+ jpeg_decoder_unlock();
+ }
vTaskDelay(pdMS_TO_TICKS(33));
display_wake();
}
@@ -75,6 +95,7 @@ esp_err_t state_machine_set(viewport_run_state_t target)
if (display_is_up()) display_sleep();
ESP_LOGI(TAG, "ASLEEP");
}
+ xSemaphoreGive(s_transition_mutex);
return ESP_OK;
}