Files
lucky-cat/components/control_plane/src/runtime/control_plane_connectivity_runtime.c
T

276 lines
8.5 KiB
C

/*
* 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 : "<empty>",
connect_timeout_ms);
control_plane_register_wifi_behavior_hooks();
esp_err_t 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);
}