/* DeskLock — living-room face and voice for the Tatlock butler. * * Boot: display + face -> audio (gong) -> Wi-Fi (C6/ESP-Hosted) -> gateway WS. * Interaction (phase 2): touch-to-talk — tap to speak, tap again to finish. * * Touch is DECOUPLED from the LVGL thread: the touch callback only signals a * semaphore; all the real work (blocking WebSocket sends, audio, face changes) * runs in app_task. A blocking WS send on the LVGL render thread stalls the * MIPI-DSI flush -> blue/garbage frame + task-watchdog reboot. */ #include "esp_log.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "bsp/esp-bsp.h" #include "desklock.h" /* 1 = L1 wifi driver diagnosis (SoftAP, no STA/gateway). 0 = normal app. */ #define WIFI_DIAG_MODE 0 static const char *TAG = "desklock"; static SemaphoreHandle_t s_touch_sem; static bool s_talking; /* Called from the LVGL touch callback — MUST be fast and non-blocking. */ void app_on_touch(void) { if (s_touch_sem != NULL) { xSemaphoreGive(s_touch_sem); } } /* Called from the WS event task when the link drops: forget any half-utterance * so the next tap starts a fresh one instead of hitting the "stop" branch. */ void app_on_disconnect(void) { s_talking = false; } /* Runs in app_task (normal context): blocking sends here don't stall rendering. */ static void handle_touch(void) { face_activity(); if (!gw_connected()) { s_talking = false; /* link gone; nothing to talk to */ return; } if (!s_talking) { if (audio_is_playing() || face_get() == FACE_EFFORT) { return; /* barge-in is phase 4 */ } s_talking = true; gw_send_text("{\"type\":\"utterance_start\"}"); audio_capture_start(); face_set(FACE_LISTENING); ESP_LOGI(TAG, "listening…"); } else { s_talking = false; audio_capture_stop(); gw_send_text("{\"type\":\"utterance_end\"}"); face_set(FACE_PENSIVE); ESP_LOGI(TAG, "utterance sent"); } } static void app_task(void *arg) { (void)arg; for (;;) { if (xSemaphoreTake(s_touch_sem, portMAX_DELAY) == pdTRUE) { vTaskDelay(pdMS_TO_TICKS(60)); /* debounce */ xSemaphoreTake(s_touch_sem, 0); /* coalesce repeats during debounce */ handle_touch(); } } } void app_on_playback_done(void) { face_set(FACE_IDLE); } void app_main(void) { ESP_LOGI(TAG, "DeskLock starting"); s_touch_sem = xSemaphoreCreateBinary(); bsp_display_start(); bsp_display_backlight_on(); face_init(); audio_init(); if (esp_reset_reason() == ESP_RST_POWERON) { audio_play_gong(); /* cold boot only — recovery reboots stay silent */ } xTaskCreate(app_task, "app", 4096, NULL, 5, NULL); #if WIFI_DIAG_MODE wifi_diag_start(); #else net_start(); c6_ota_start(); /* one-shot C6 radio firmware update — remove once proven */ #endif ESP_LOGI(TAG, "DeskLock up"); }