Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve server failure handling #338

Merged
merged 14 commits into from
Nov 29, 2023
Merged
2 changes: 2 additions & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
90 changes: 77 additions & 13 deletions main/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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");
}
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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;
Expand Down
53 changes: 38 additions & 15 deletions main/was.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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,
};
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down
Loading