diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 4f0a3ce6..e30a687a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -43,6 +43,8 @@ register_component() spiffs_create_partition_image(user ../spiffs/user FLASH_IN_PROJECT) +string(APPEND CMAKE_C_FLAGS -Werror) + # sdkconfig sanity checks if($ENV{WILLOW_SDKCONFIG_SANITY_CHECKS}) if (NOT CONFIG_WILLOW_ETHERNET) diff --git a/main/audio.c b/main/audio.c index 99e3856a..2a3fdf2a 100644 --- a/main/audio.c +++ b/main/audio.c @@ -59,9 +59,15 @@ #define DEFAULT_WIS_TTS_URL "https://infer.tovera.io/api/tts" #define DEFAULT_WIS_URL "https://infer.tovera.io/api/willow" -#define MULTINET_TWDT 30 -#define STR_WAKE_LEN 25 -#define WIS_URL_TTS_ARG "?format=WAV&speaker=CLB&text=" +#define HTTP_STREAM_TIMEOUT_MS 2 * 1000 +#define MULTINET_TWDT 30 +#define STR_WAKE_LEN 25 +#define WIS_URL_TTS_ARG "?format=WAV&speaker=CLB&text=" + +typedef enum willow_http_stream { + WILLOW_HS_ESP_AUDIO, + WILLOW_HS_STT, +} willow_http_stream_t; QueueHandle_t q_ea, q_rec; audio_hal_handle_t hdl_aha = NULL, hdl_ahc = NULL; @@ -146,6 +152,52 @@ static void init_audio_response(void) free(audio_response_type); } +static esp_err_t cb_ae_hs(audio_element_handle_t el, audio_event_iface_msg_t *ev, void *data) +{ + if (ev->cmd == AEL_MSG_CMD_REPORT_STATUS) { + // if we get a AEL_MSG_CMD_REPORT_STATUS command we can check for errors in audio_element_status_t + // 1-7 is error + int ae_status = (int)ev->data; + ESP_LOGI(TAG, "event_cb_func(): AEL_MSG_CMD_REPORT_STATUS: %d", ae_status); + + if (ae_status == 0 || ae_status > 7) { + return ESP_OK; + } + + willow_http_stream_t type_hs = (willow_http_stream_t)data; + if (type_hs == WILLOW_HS_STT) { + audio_recorder_trigger_stop(hdl_ar); + war.fn_err("Cannot Reach WIS"); + ESP_LOGE(TAG, "Error opening STT endpoint (%d)", ae_status); + ui_pr_err("Cannot Reach WIS", "Check Server & Settings"); + } else if (type_hs == WILLOW_HS_ESP_AUDIO) { + play_audio_err(NULL); + ESP_LOGE(TAG, "Error opening ESP Audio endpoint (%d)", ae_status); + ui_pr_err("Cannot Reach WIS", "Check Server & Settings"); + } + } + return ESP_OK; +} + +static esp_err_t hdl_ev_hs_esp_audio(http_stream_event_msg_t *msg) +{ + if (msg == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_http_client_handle_t http = (esp_http_client_handle_t)msg->http_client; + + switch (msg->event_id) { + case HTTP_STREAM_PRE_REQUEST: + esp_http_client_set_authtype(http, HTTP_AUTH_TYPE_BASIC); + esp_http_client_set_timeout_ms(http, HTTP_STREAM_TIMEOUT_MS); + break; + default: + break; + } + return ESP_OK; +} + static void init_esp_audio(void) { audio_err_t ret = ESP_OK; @@ -169,8 +221,12 @@ static void init_esp_audio(void) hdl_ea = esp_audio_create(&cfg_ea); http_stream_cfg_t cfg_hs = HTTP_STREAM_CFG_DEFAULT(); + cfg_hs.event_handle = hdl_ev_hs_esp_audio; - ret = esp_audio_input_stream_add(hdl_ea, http_stream_init(&cfg_hs)); + audio_element_handle_t hdl_ae_hs = http_stream_init(&cfg_hs); + audio_element_set_event_callback(hdl_ea, cb_ae_hs, (void *)WILLOW_HS_ESP_AUDIO); + + ret = esp_audio_input_stream_add(hdl_ea, hdl_ae_hs); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to add HTTP input stream to ESP Audio"); } @@ -408,7 +464,7 @@ static int feed_afe(int16_t *buf, int len, void *ctx, TickType_t ticks) return raw_stream_read(hdl_ae_rs_from_i2s, (char *)buf, len); } -static esp_err_t hdl_ev_hs(http_stream_event_msg_t *msg) +static esp_err_t hdl_ev_hs_to_api(http_stream_event_msg_t *msg) { if (msg == NULL) { return ESP_ERR_INVALID_ARG; @@ -421,7 +477,9 @@ static esp_err_t hdl_ev_hs(http_stream_event_msg_t *msg) switch (msg->event_id) { case HTTP_STREAM_PRE_REQUEST: ESP_LOGI(TAG, "WIS HTTP client starting stream, waiting for end of speech"); + esp_http_client_set_authtype(http, HTTP_AUTH_TYPE_BASIC); esp_http_client_set_method(http, HTTP_METHOD_POST); + esp_http_client_set_timeout_ms(http, HTTP_STREAM_TIMEOUT_MS); char *audio_codec = config_get_char("audio_codec", DEFAULT_AUDIO_CODEC); char dat[10] = {0}; snprintf(dat, sizeof(dat), "%d", 16000); @@ -474,16 +532,20 @@ static esp_err_t hdl_ev_hs(http_stream_event_msg_t *msg) // Check status code int http_status = esp_http_client_get_status_code(http); if (http_status != 200) { - if (http_status == 406) { + if (http_status == 401) { + ESP_LOGE(TAG, "WIS returned Unauthorized Access (HTTP 401)"); + ui_pr_err("WIS auth failed", "Check server & settings"); + } else if (http_status == 406) { ESP_LOGE(TAG, "WIS returned Unauthorized Speaker"); - if (lvgl_port_lock(lvgl_lock_timeout)) { - lv_obj_clear_flag(lbl_ln3, LV_OBJ_FLAG_HIDDEN); - lv_label_set_text_static(lbl_ln3, "Unauthorized Speaker"); - lvgl_port_unlock(); - } + ui_pr_err("Unauthorized Speaker", NULL); war.fn_err("Unauthorized Speaker"); + } else { + ESP_LOGE(TAG, "WIS returned HTTP error: %d", http_status); + char str_http_err[14]; + snprintf(str_http_err, 14, "WIS HTTP %d", http_status); + ui_pr_err(str_http_err, NULL); + war.fn_err(str_http_err); } - ESP_LOGE(TAG, "WIS returned HTTP error: %d", http_status); return ESP_FAIL; } // Allocate memory for response. Should be enough? @@ -556,12 +618,14 @@ static esp_err_t init_ap_to_api(void) hdl_ap_to_api = audio_pipeline_init(&cfg_ap); http_stream_cfg_t cfg_hs = HTTP_STREAM_CFG_DEFAULT(); - cfg_hs.event_handle = hdl_ev_hs; + cfg_hs.event_handle = hdl_ev_hs_to_api; cfg_hs.task_stack = 8 * 1024; // default 6 * 1024 cfg_hs.type = AUDIO_STREAM_WRITER; cfg_hs.user_agent = WILLOW_USER_AGENT; hdl_ae_hs = http_stream_init(&cfg_hs); + audio_element_set_event_callback(hdl_ae_hs, cb_ae_hs, (void *)WILLOW_HS_STT); + raw_stream_cfg_t cfg_rs = RAW_STREAM_CFG_DEFAULT(); cfg_rs.out_rb_size = 64 * 1024; // default is 8 * 1024 cfg_rs.type = AUDIO_STREAM_WRITER; diff --git a/main/was.c b/main/was.c index d699dbc2..d6ce9ee0 100644 --- a/main/was.c +++ b/main/was.c @@ -19,8 +19,11 @@ #include "slvgl.h" #include "system.h" #include "timer.h" +#include "ui.h" #include "was.h" +#define WAS_RECONNECT_TIMEOUT_MS 10 * 1000 + static const char *TAG = "WILLOW/WAS"; static esp_websocket_client_handle_t hdl_wc = NULL; static volatile struct notify_data *notify_active; @@ -352,14 +355,16 @@ void was_deinit_task(void *data) ESP_LOGW(TAG, "failed to enable destroy on exit"); } - ret = esp_websocket_client_close(hdl_wc, portMAX_DELAY); + ret = esp_websocket_client_close(hdl_wc, 5000 / portTICK_PERIOD_MS); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to cleanly close WebSocket client"); } + ret = esp_websocket_client_stop(hdl_wc); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to stop WebSocket client: %s", esp_err_to_name(ret)); } + vTaskDelete(NULL); } @@ -382,6 +387,7 @@ esp_err_t init_was(void) const esp_websocket_client_config_t cfg_wc = { .buffer_size = 4096, + .reconnect_timeout_ms = WAS_RECONNECT_TIMEOUT_MS, .uri = was_url, .user_agent = WILLOW_USER_AGENT, }; @@ -407,18 +413,37 @@ esp_err_t init_was(void) return err; } +static bool was_is_connected(const bool wait) +{ + if (esp_websocket_client_is_connected(hdl_wc)) { + return true; + } + + if (wait) { + int max = WAS_RECONNECT_TIMEOUT_MS / 1000; + for (int i = 0; i < max; i++) { + if (esp_websocket_client_is_connected(hdl_wc)) { + return true; + } + i++; + } + ui_pr_err("WAS disconnected", NULL); + return false; + } else { + return false; + } +} + esp_err_t was_send_endpoint(const char *data, bool nc_skip) { cJSON *in = NULL, *out = NULL; char *json = NULL; esp_err_t ret = ESP_OK; - if (!esp_websocket_client_is_connected(hdl_wc)) { + if (!was_is_connected(true)) { if (nc_skip) { return ENOTCONN; } - esp_websocket_client_destroy(hdl_wc); - init_was(); } in = cJSON_Parse(data); @@ -457,9 +482,9 @@ void request_config(void) char *json = NULL; esp_err_t ret; - if (!esp_websocket_client_is_connected(hdl_wc)) { - esp_websocket_client_destroy(hdl_wc); - init_was(); + // not sure if we should wait here as we call request_config on WEBSOCKET_EVENT_CONNECTED + if (!was_is_connected(true)) { + return; } cjson = cJSON_CreateObject(); @@ -488,9 +513,8 @@ static void send_hello_goodbye(const char *type) ESP_LOGI(TAG, "sending WAS %s", type); - if (!esp_websocket_client_is_connected(hdl_wc)) { - esp_websocket_client_destroy(hdl_wc); - init_was(); + if (!was_is_connected(true)) { + return; } ret = esp_netif_get_hostname(hdl_netif, &hostname); @@ -548,7 +572,7 @@ void IRAM_ATTR send_wake_start(float wake_volume) return; } - if (!esp_websocket_client_is_connected(hdl_wc)) { + if (!was_is_connected(false)) { ESP_LOGW(TAG, "Websocket not connected - skipping wake start"); return; } @@ -585,7 +609,7 @@ void send_wake_end(void) return; } - if (!esp_websocket_client_is_connected(hdl_wc)) { + if (!was_is_connected(false)) { ESP_LOGW(TAG, "Websocket not connected - skipping wake end"); return; } @@ -698,9 +722,8 @@ static void notify_task(void *data) goto skip_notify_done; } - if (!esp_websocket_client_is_connected(hdl_wc)) { - esp_websocket_client_destroy(hdl_wc); - init_was(); + if (!was_is_connected(true)) { + goto skip_notify_done; } cjson = cJSON_CreateObject();