/* * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "control_plane.h" #include "control_plane_internal.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "sdkconfig.h" #ifndef CONFIG_MULTIMODAL_CONNECT_TIMEOUT_MS #define CONFIG_MULTIMODAL_CONNECT_TIMEOUT_MS 10000 #endif #ifndef CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS #define CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS 3000 #endif #ifndef CONFIG_TQ_BOOT_SOUND_FILE #define CONFIG_TQ_BOOT_SOUND_FILE "" #endif #define CONTROL_PLANE_WIFI_SUCCESS_PROMPT_FILE "/littlefs/audio_prompts/network_connect_success.opus" #define CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS 3 #define CONTROL_PLANE_STA_RETRY_BASE_DELAY_MS 300 static const char *TAG = "control_plane_rt"; static bool s_wifi_success_prompt_played; static int elapsed_ms_since(TickType_t start_tick) { TickType_t elapsed_ticks = xTaskGetTickCount() - start_tick; return (int)(elapsed_ticks * portTICK_PERIOD_MS); } static int remaining_timeout_ms(TickType_t start_tick, int timeout_ms) { int elapsed_ms = elapsed_ms_since(start_tick); if (elapsed_ms >= timeout_ms) { return 0; } return timeout_ms - elapsed_ms; } esp_err_t control_plane_prepare_runtime_storage(void) { return domain_runtime_prepare_storage(); } static void control_plane_wifi_set_keep_awake(bool keep_awake) { domain_runtime_behavior_set_keep_awake(keep_awake); } static esp_err_t control_plane_wifi_play_prompt(domain_wifi_prompt_id_t prompt) { return domain_runtime_behavior_play_prompt(prompt); } static esp_err_t control_plane_wifi_play_prompt_file(const char *path) { return domain_runtime_behavior_play_prompt_file(path); } static int control_plane_normalize_connect_timeout_ms(int timeout_ms, int min_timeout_ms) { int effective_timeout = timeout_ms; if (effective_timeout <= 0) { effective_timeout = 10000; } if (min_timeout_ms > 0 && effective_timeout < min_timeout_ms) { effective_timeout = min_timeout_ms; } return effective_timeout; } static void control_plane_play_wifi_success_prompt(void) { if (s_wifi_success_prompt_played) { return; } s_wifi_success_prompt_played = true; bool played = false; esp_err_t audio_err = domain_runtime_multimodal_audio_init(); if (audio_err != ESP_OK && audio_err != ESP_ERR_INVALID_STATE) { ESP_LOGW(TAG, "Prepare audio for Wi-Fi success prompt failed: %s", esp_err_to_name(audio_err)); } played = (control_plane_wifi_play_prompt_file(CONTROL_PLANE_WIFI_SUCCESS_PROMPT_FILE) == ESP_OK); #ifdef CONFIG_VOICE_PROMPT_WIFI_SUCCESS_FILE if (!played && CONFIG_VOICE_PROMPT_WIFI_SUCCESS_FILE[0] != '\0') { played = (control_plane_wifi_play_prompt_file(CONFIG_VOICE_PROMPT_WIFI_SUCCESS_FILE) == ESP_OK); } #endif if (!played) { (void)control_plane_wifi_play_prompt(DOMAIN_WIFI_PROMPT_WIFI_SUCCESS); } } static esp_err_t control_plane_connect_sta_with_feedback(int timeout_ms, bool play_connecting_prompt) { esp_err_t err = ESP_ERR_TIMEOUT; TickType_t start_tick = xTaskGetTickCount(); if (play_connecting_prompt) { (void)control_plane_wifi_play_prompt(DOMAIN_WIFI_PROMPT_WIFI_CONNECTING); } control_plane_wifi_set_keep_awake(true); for (int attempt = 1; attempt <= CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS; ++attempt) { int attempt_timeout_ms = remaining_timeout_ms(start_tick, timeout_ms); if (attempt_timeout_ms <= 0) { err = ESP_ERR_TIMEOUT; break; } err = domain_runtime_wifi_portal_connect_sta(attempt_timeout_ms); if (err == ESP_OK) { break; } if (attempt >= CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS) { break; } if (err != ESP_ERR_TIMEOUT && err != ESP_FAIL) { break; } int retry_delay_ms = CONTROL_PLANE_STA_RETRY_BASE_DELAY_MS * attempt; int remaining_ms = remaining_timeout_ms(start_tick, timeout_ms); if (remaining_ms <= 0) { err = ESP_ERR_TIMEOUT; break; } if (retry_delay_ms > remaining_ms) { retry_delay_ms = remaining_ms; } ESP_LOGW(TAG, "STA connect attempt %d/%d failed (%s), retry in %d ms", attempt, CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS, esp_err_to_name(err), retry_delay_ms); vTaskDelay(pdMS_TO_TICKS(retry_delay_ms)); } control_plane_wifi_set_keep_awake(false); if (err != ESP_OK) { return err; } control_plane_play_wifi_success_prompt(); return ESP_OK; } static bool control_plane_wifi_connect_error_keeps_credentials(esp_err_t err) { return domain_runtime_wifi_connect_error_keeps_credentials(err); } static void control_plane_register_wifi_behavior_hooks(void) { domain_wifi_behavior_hooks_t hooks = { .set_keep_awake = control_plane_wifi_set_keep_awake, .play_prompt = control_plane_wifi_play_prompt, .play_prompt_file = control_plane_wifi_play_prompt_file, }; domain_runtime_wifi_portal_set_behavior_hooks(&hooks); } esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid, const char *seed_password, int connect_timeout_ms, const char *config_ap_ssid_prefix) { ESP_LOGI(TAG, "Boot Wi-Fi config SSID=%s timeout_ms=%d", (seed_ssid != NULL && seed_ssid[0] != '\0') ? seed_ssid : "", connect_timeout_ms); esp_err_t err = control_plane_prepare_runtime_storage(); if (err != ESP_OK) { return err; } control_plane_register_wifi_behavior_hooks(); err = domain_runtime_wifi_portal_init_defaults(seed_ssid, seed_password); if (err != ESP_OK) { return err; } if (domain_runtime_wifi_portal_has_saved_credentials()) { int timeout_ms = control_plane_normalize_connect_timeout_ms(connect_timeout_ms, 0); err = control_plane_connect_sta_with_feedback(timeout_ms, true); if (err == ESP_OK) { return ESP_OK; } if (control_plane_wifi_connect_error_keeps_credentials(err)) { ESP_LOGW(TAG, "STA connect failed, keep credentials for runtime retry: %s", esp_err_to_name(err)); } else { ESP_LOGW(TAG, "STA connect failed, clear credentials and fallback: %s", esp_err_to_name(err)); (void)domain_runtime_wifi_portal_clear_saved_credentials(); } } control_plane_wifi_set_keep_awake(true); err = domain_runtime_wifi_portal_start_config_ap( (config_ap_ssid_prefix != NULL && config_ap_ssid_prefix[0] != '\0') ? config_ap_ssid_prefix : "TQ-CAT"); if (err != ESP_OK) { control_plane_wifi_set_keep_awake(false); } return err; } bool control_plane_is_sta_connected(void) { return domain_runtime_wifi_portal_sta_is_connected(); } esp_err_t control_plane_play_boot_sound(int timeout_ms) { int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS; ESP_LOGI(TAG, "Boot sound policy start timeout=%dms", effective_timeout_ms); if (CONFIG_TQ_BOOT_SOUND_FILE[0] == '\0') { ESP_LOGW(TAG, "Boot sound file is not configured"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t audio_err = domain_runtime_multimodal_audio_init(); if (audio_err != ESP_OK) { ESP_LOGW(TAG, "Boot sound audio init failed: %s", esp_err_to_name(audio_err)); return audio_err; } esp_err_t play_err = domain_runtime_behavior_play_prompt_file(CONFIG_TQ_BOOT_SOUND_FILE); if (play_err != ESP_OK) { ESP_LOGW(TAG, "Boot sound playback failed: %s", esp_err_to_name(play_err)); } ESP_LOGI(TAG, "Boot sound audio resources kept for runtime"); return play_err; } esp_err_t control_plane_init_lcd_device(void) { return domain_runtime_init_lcd_device(); } void control_plane_start_lcd_render_loop(void) { domain_runtime_start_lcd_render_loop(); } esp_err_t control_plane_request_boot_expression(void) { domain_expression_request_t request = { .id = DOMAIN_EXPRESSION_IDLE, .trigger = DOMAIN_EXPRESSION_TRIGGER_UNSPECIFIED, .direction_index = 0, }; return domain_runtime_request_expression(&request); } esp_err_t control_plane_stop_lcd_render_loop(int timeout_ms) { return domain_runtime_stop_lcd_render_loop(timeout_ms); }