Compare commits

..

3 Commits

Author SHA1 Message Date
379f8e105b fix? 2026-02-16 22:14:55 +09:00
3b4d61c56d process order fix 2026-02-16 22:00:23 +09:00
5ae0c64ba9 new client connection logic
- esp32 requests for image when ready to receive
- server serves initial image on request
2026-02-16 21:56:28 +09:00
4 changed files with 67 additions and 11 deletions

View File

@@ -35,6 +35,7 @@ static uint8_t s_img_buf[STATUS_IMG_BYTES];
static lv_img_dsc_t s_img_dsc; 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_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_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 */ static TaskHandle_t s_img_notify_task = NULL; /* task to wake on new image */
/* Forward declarations */ /* Forward declarations */
@@ -157,11 +158,17 @@ static void ws_event_handler(void *arg, esp_event_base_t event_base,
case WEBSOCKET_EVENT_CONNECTED: case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "Audio WS connected"); ESP_LOGI(TAG, "Audio WS connected");
s_state = AUDIO_CONNECTED; s_state = AUDIO_CONNECTED;
s_img_pending = false;
s_need_request_image = true;
if (s_img_notify_task) {
xTaskNotifyGive(s_img_notify_task);
}
break; break;
case WEBSOCKET_EVENT_DISCONNECTED: case WEBSOCKET_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "Audio WS disconnected"); ESP_LOGW(TAG, "Audio WS disconnected");
s_playing = false; s_playing = false;
s_img_pending = false;
flush_queue(); flush_queue();
s_state = AUDIO_IDLE; s_state = AUDIO_IDLE;
break; break;
@@ -275,9 +282,25 @@ const lv_img_dsc_t *audio_client_get_status_image(bool *updated)
{ {
if (updated) { if (updated) {
*updated = s_img_updated; *updated = s_img_updated;
if (s_img_updated) {
s_img_updated = false;
}
} }
return &s_img_dsc; return &s_img_dsc;
} }
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;
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;
}
ESP_LOGI(TAG, "Sent image request to server");
return true;
}

View File

@@ -40,11 +40,24 @@ void audio_client_set_image_notify_task(TaskHandle_t task);
/** /**
* Get the latest status image descriptor. * 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). * @return Pointer to the static image descriptor (always valid).
*/ */
const lv_img_dsc_t *audio_client_get_status_image(bool *updated); 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);
/**
* 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -194,12 +194,16 @@ static void sensor_task(void *arg)
*/ */
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)); 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 */ /* Check for status image updates immediately */
bool img_updated = false; bool img_updated = false;
const lv_img_dsc_t *img = audio_client_get_status_image(&img_updated); const lv_img_dsc_t *img = audio_client_get_status_image(&img_updated);
if (img_updated && Lvgl_lock(100)) { if (img_updated && Lvgl_lock(100)) {
dashboard_ui_update_status_image(img); dashboard_ui_update_status_image(img);
Lvgl_unlock(); Lvgl_unlock();
audio_client_ack_status_image();
} }
/* Sensor + clock updates at ~1s cadence (skip if woken early) */ /* Sensor + clock updates at ~1s cadence (skip if woken early) */

View File

@@ -17,6 +17,7 @@ Protocol:
import argparse import argparse
import asyncio import asyncio
import json
import logging import logging
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -68,17 +69,17 @@ async def handler(ws):
configs = load_config(_config_path) configs = load_config(_config_path)
img_idle = load_status_image(IMG_DIR / "idle.png") img_idle = load_status_image(IMG_DIR / "idle.png")
current_img = img_idle
try: alarms = [_prepare_alarm(entry) for entry in configs] if configs else []
await send_status_image(ws, img_idle)
if not configs: async def alarm_ticker():
nonlocal current_img
if not alarms:
log.info("No alarms configured — idling forever") log.info("No alarms configured — idling forever")
await asyncio.Future() await asyncio.Future()
return return
alarms = [_prepare_alarm(entry) for entry in configs]
while True: while True:
for alarm in alarms: for alarm in alarms:
if should_fire(alarm["config"]): if should_fire(alarm["config"]):
@@ -88,13 +89,28 @@ async def handler(ws):
alarm["last_fired"] = current_minute alarm["last_fired"] = current_minute
log.info("Alarm firing: %s at %s", log.info("Alarm firing: %s at %s",
alarm["config"]["alarm_time"], current_minute) 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"], await stream_alarm(ws, alarm["pcm"], alarm["sr"],
alarm["ch"], alarm["bits"]) 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) 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: except websockets.exceptions.ConnectionClosed:
log.info("Client disconnected: %s:%d", remote[0], remote[1]) log.info("Client disconnected: %s:%d", remote[0], remote[1])