From 5ae0c64ba9446bc6c6e2ee3320efc29d98c8a4bc Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 16 Feb 2026 21:56:28 +0900 Subject: [PATCH 1/3] new client connection logic - esp32 requests for image when ready to receive - server serves initial image on request --- components/audio_client/audio_client.cpp | 11 ++++++--- components/audio_client/audio_client.h | 8 ++++++- components/user_app/user_app.cpp | 1 + pi/contents_server.py | 30 ++++++++++++++++++------ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/components/audio_client/audio_client.cpp b/components/audio_client/audio_client.cpp index 13f5823..edfa601 100644 --- a/components/audio_client/audio_client.cpp +++ b/components/audio_client/audio_client.cpp @@ -157,11 +157,14 @@ static void ws_event_handler(void *arg, esp_event_base_t event_base, case WEBSOCKET_EVENT_CONNECTED: ESP_LOGI(TAG, "Audio WS connected"); s_state = AUDIO_CONNECTED; + s_img_pending = false; + esp_websocket_client_send_text(s_client, "{\"type\":\"request_image\"}", 23, pdMS_TO_TICKS(1000)); break; case WEBSOCKET_EVENT_DISCONNECTED: ESP_LOGW(TAG, "Audio WS disconnected"); s_playing = false; + s_img_pending = false; flush_queue(); s_state = AUDIO_IDLE; break; @@ -275,9 +278,11 @@ const lv_img_dsc_t *audio_client_get_status_image(bool *updated) { if (updated) { *updated = s_img_updated; - if (s_img_updated) { - s_img_updated = false; - } } return &s_img_dsc; } + +void audio_client_ack_status_image(void) +{ + s_img_updated = false; +} diff --git a/components/audio_client/audio_client.h b/components/audio_client/audio_client.h index a061f90..a4cd3d3 100644 --- a/components/audio_client/audio_client.h +++ b/components/audio_client/audio_client.h @@ -40,11 +40,17 @@ void audio_client_set_image_notify_task(TaskHandle_t task); /** * Get the latest status image descriptor. - * @param updated Set to true if a new image arrived since last call, then reset. + * @param updated Set to true if a new image arrived since last call. * @return Pointer to the static image descriptor (always valid). */ const lv_img_dsc_t *audio_client_get_status_image(bool *updated); +/** + * Acknowledge that the status image was successfully rendered. + * Clears the updated flag so subsequent get_status_image calls return false. + */ +void audio_client_ack_status_image(void); + #ifdef __cplusplus } #endif diff --git a/components/user_app/user_app.cpp b/components/user_app/user_app.cpp index ffd0ba0..0d257a0 100644 --- a/components/user_app/user_app.cpp +++ b/components/user_app/user_app.cpp @@ -200,6 +200,7 @@ static void sensor_task(void *arg) if (img_updated && Lvgl_lock(100)) { dashboard_ui_update_status_image(img); Lvgl_unlock(); + audio_client_ack_status_image(); } /* Sensor + clock updates at ~1s cadence (skip if woken early) */ diff --git a/pi/contents_server.py b/pi/contents_server.py index 40546c5..3445fd6 100644 --- a/pi/contents_server.py +++ b/pi/contents_server.py @@ -17,6 +17,7 @@ Protocol: import argparse import asyncio +import json import logging from datetime import datetime from pathlib import Path @@ -68,17 +69,17 @@ async def handler(ws): configs = load_config(_config_path) img_idle = load_status_image(IMG_DIR / "idle.png") + current_img = img_idle - try: - await send_status_image(ws, img_idle) + alarms = [_prepare_alarm(entry) for entry in configs] if configs else [] - if not configs: + async def alarm_ticker(): + nonlocal current_img + if not alarms: log.info("No alarms configured — idling forever") await asyncio.Future() return - alarms = [_prepare_alarm(entry) for entry in configs] - while True: for alarm in alarms: if should_fire(alarm["config"]): @@ -88,13 +89,28 @@ async def handler(ws): alarm["last_fired"] = current_minute log.info("Alarm firing: %s at %s", alarm["config"]["alarm_time"], current_minute) - await send_status_image(ws, alarm["img"]) + current_img = alarm["img"] + await send_status_image(ws, current_img) await stream_alarm(ws, alarm["pcm"], alarm["sr"], alarm["ch"], alarm["bits"]) - await send_status_image(ws, img_idle) + current_img = img_idle + await send_status_image(ws, current_img) await asyncio.sleep(TICK_INTERVAL) + async def receiver(): + async for msg in ws: + try: + data = json.loads(msg) + except (json.JSONDecodeError, TypeError): + continue + if data.get("type") == "request_image": + log.info("Client requested image — sending current (%d bytes)", + len(current_img)) + await send_status_image(ws, current_img) + + try: + await asyncio.gather(alarm_ticker(), receiver()) except websockets.exceptions.ConnectionClosed: log.info("Client disconnected: %s:%d", remote[0], remote[1]) From 3b4d61c56df1c90fe8b4ec092e3ff0d227fc237a Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 16 Feb 2026 22:00:23 +0900 Subject: [PATCH 2/3] process order fix --- components/audio_client/audio_client.cpp | 19 ++++++++++++++++++- components/audio_client/audio_client.h | 7 +++++++ components/user_app/user_app.cpp | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/components/audio_client/audio_client.cpp b/components/audio_client/audio_client.cpp index edfa601..3e2f523 100644 --- a/components/audio_client/audio_client.cpp +++ b/components/audio_client/audio_client.cpp @@ -35,6 +35,7 @@ static uint8_t s_img_buf[STATUS_IMG_BYTES]; static lv_img_dsc_t s_img_dsc; static volatile bool s_img_pending = false; /* expecting binary frame with image data */ static volatile bool s_img_updated = false; /* new image ready for UI consumption */ +static volatile bool s_need_request_image = false; /* deferred image request on connect */ static TaskHandle_t s_img_notify_task = NULL; /* task to wake on new image */ /* Forward declarations */ @@ -158,7 +159,10 @@ static void ws_event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Audio WS connected"); s_state = AUDIO_CONNECTED; s_img_pending = false; - esp_websocket_client_send_text(s_client, "{\"type\":\"request_image\"}", 23, pdMS_TO_TICKS(1000)); + s_need_request_image = true; + if (s_img_notify_task) { + xTaskNotifyGive(s_img_notify_task); + } break; case WEBSOCKET_EVENT_DISCONNECTED: @@ -286,3 +290,16 @@ void audio_client_ack_status_image(void) { s_img_updated = false; } + +bool audio_client_send_pending_request(void) +{ + if (!s_need_request_image || !s_client) return false; + s_need_request_image = false; + int ret = esp_websocket_client_send_text(s_client, "{\"type\":\"request_image\"}", 23, pdMS_TO_TICKS(1000)); + if (ret < 0) { + ESP_LOGE(TAG, "Failed to send image request: %d", ret); + return false; + } + ESP_LOGI(TAG, "Sent image request to server"); + return true; +} diff --git a/components/audio_client/audio_client.h b/components/audio_client/audio_client.h index a4cd3d3..526de09 100644 --- a/components/audio_client/audio_client.h +++ b/components/audio_client/audio_client.h @@ -51,6 +51,13 @@ const lv_img_dsc_t *audio_client_get_status_image(bool *updated); */ void audio_client_ack_status_image(void); +/** + * Send any pending image request to the server. + * Call from a task context (not from an event handler). + * @return true if a request was sent, false if none pending or send failed. + */ +bool audio_client_send_pending_request(void); + #ifdef __cplusplus } #endif diff --git a/components/user_app/user_app.cpp b/components/user_app/user_app.cpp index 0d257a0..d3eaeed 100644 --- a/components/user_app/user_app.cpp +++ b/components/user_app/user_app.cpp @@ -194,6 +194,9 @@ static void sensor_task(void *arg) */ ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)); + /* Send deferred image request if connect just happened */ + audio_client_send_pending_request(); + /* Check for status image updates immediately */ bool img_updated = false; const lv_img_dsc_t *img = audio_client_get_status_image(&img_updated); From 379f8e105b4e8c2c0e4f13dea8f0411d13730dd7 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 16 Feb 2026 22:14:55 +0900 Subject: [PATCH 3/3] fix? --- components/audio_client/audio_client.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/audio_client/audio_client.cpp b/components/audio_client/audio_client.cpp index 3e2f523..bd725f4 100644 --- a/components/audio_client/audio_client.cpp +++ b/components/audio_client/audio_client.cpp @@ -295,7 +295,8 @@ bool audio_client_send_pending_request(void) { if (!s_need_request_image || !s_client) return false; s_need_request_image = false; - int ret = esp_websocket_client_send_text(s_client, "{\"type\":\"request_image\"}", 23, pdMS_TO_TICKS(1000)); + static const char REQUEST_IMG_JSON[] = "{\"type\":\"request_image\"}"; + int ret = esp_websocket_client_send_text(s_client, REQUEST_IMG_JSON, strlen(REQUEST_IMG_JSON), pdMS_TO_TICKS(1000)); if (ret < 0) { ESP_LOGE(TAG, "Failed to send image request: %d", ret); return false;