1600 lines
53 KiB
C
1600 lines
53 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "domain.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "domain_internal.h"
|
|
#include "platform.h"
|
|
#include "esp_random.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/portmacro.h"
|
|
#include "sdkconfig.h"
|
|
|
|
#ifndef CONFIG_MULTIMODAL_PTT_BUTTON_GPIO
|
|
#error "CONFIG_MULTIMODAL_PTT_BUTTON_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_PTT_BUTTON_ACTIVE_LEVEL
|
|
#error "CONFIG_MULTIMODAL_PTT_BUTTON_ACTIVE_LEVEL must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_PTT_BUTTON_PULLUP
|
|
#define CONFIG_MULTIMODAL_PTT_BUTTON_PULLUP 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_PTT_BUTTON_PULLDOWN
|
|
#define CONFIG_MULTIMODAL_PTT_BUTTON_PULLDOWN 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_SLOT_SWITCH_GPIO
|
|
#error "CONFIG_TQ_SLOT_SWITCH_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_SLOT_SWITCH_ACTIVE_LEVEL
|
|
#error "CONFIG_TQ_SLOT_SWITCH_ACTIVE_LEVEL must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_SLOT_SWITCH_PULLUP
|
|
#define CONFIG_TQ_SLOT_SWITCH_PULLUP 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_SLOT_SWITCH_PULLDOWN
|
|
#define CONFIG_TQ_SLOT_SWITCH_PULLDOWN 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_CALIBRATION_SWITCH_GPIO
|
|
#error "CONFIG_TQ_CALIBRATION_SWITCH_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_CALIBRATION_SWITCH_ACTIVE_LEVEL
|
|
#error "CONFIG_TQ_CALIBRATION_SWITCH_ACTIVE_LEVEL must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_CALIBRATION_SWITCH_PULLUP
|
|
#define CONFIG_TQ_CALIBRATION_SWITCH_PULLUP 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_CALIBRATION_SWITCH_PULLDOWN
|
|
#define CONFIG_TQ_CALIBRATION_SWITCH_PULLDOWN 0
|
|
#endif
|
|
|
|
static domain_multimodal_transport_event_callback_t s_domain_transport_event_cb;
|
|
static void *s_domain_transport_event_user_data;
|
|
static domain_multimodal_downstream_playback_callback_t s_domain_downstream_playback_cb;
|
|
static void *s_domain_downstream_playback_user_data;
|
|
static domain_wifi_behavior_hooks_t s_domain_wifi_behavior_hooks;
|
|
static portMUX_TYPE s_domain_runtime_adapter_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
static const char *TAG = "domain_rt_adapters";
|
|
|
|
static const uint8_t s_server_error_frame_order[] = {0, 1, 2, 1, 0};
|
|
static const uint8_t s_wifi_disconnected_frame_order[] = {0, 1, 2, 3, 2, 1, 0};
|
|
static const uint8_t s_wifi_connecting_frame_order[] = {0};
|
|
static const uint8_t s_basic_face_frame_order[] = {0, 1, 2};
|
|
static const uint8_t s_muyu_frame_order[] = {0, 1, 2, 1};
|
|
static const uint8_t s_listening_frame_order[] = {0, 1, 2};
|
|
static const uint8_t s_ota_update_frame_order[] = {0, 1, 2};
|
|
static const char *const s_chouqian_result_asset_dirs[] = {
|
|
"chouqian_buyun",
|
|
"chouqian_heping",
|
|
"chouqian_shangshang",
|
|
"chouqian_xiaoji",
|
|
"chouqian_zhongshang",
|
|
};
|
|
|
|
typedef struct {
|
|
const char *asset_dir;
|
|
uint16_t icon_id;
|
|
uint16_t icon_x;
|
|
uint16_t icon_y;
|
|
} domain_wuxing_result_t;
|
|
|
|
/* Keep this table ordered by domain_wuxing_element_t. */
|
|
static const domain_wuxing_result_t s_wuxing_results[] = {
|
|
{.asset_dir = "shui", .icon_id = 0, .icon_x = 124, .icon_y = 8},
|
|
{.asset_dir = "mu", .icon_id = 1, .icon_x = 217, .icon_y = 92},
|
|
{.asset_dir = "huo", .icon_id = 2, .icon_x = 198, .icon_y = 171},
|
|
{.asset_dir = "tu", .icon_id = 3, .icon_x = 46, .icon_y = 178},
|
|
{.asset_dir = "jin", .icon_id = 4, .icon_x = 18, .icon_y = 102},
|
|
};
|
|
|
|
_Static_assert(sizeof(s_wuxing_results) / sizeof(s_wuxing_results[0]) ==
|
|
DOMAIN_WUXING_ELEMENT_COUNT,
|
|
"Wuxing result assets must match domain_wuxing_element_t");
|
|
|
|
/*
|
|
* Wealth-direction reveal asset directories, indexed parallel to the direction
|
|
* catalog order in components/domain/src/direction/domain_direction_catalog.c
|
|
* (s_default_directions). Keep these in sync if the catalog order changes.
|
|
*/
|
|
static const char *const s_wealth_direction_asset_dirs[] = {
|
|
"south", /* 0 南/S */
|
|
"southwest", /* 1 西南/SW */
|
|
"west", /* 2 西/W */
|
|
"northwest", /* 3 西北/NW */
|
|
"north", /* 4 北/N */
|
|
"northeast", /* 5 东北/NE */
|
|
"east", /* 6 东/E */
|
|
"southeast", /* 7 东南/SE */
|
|
};
|
|
|
|
#define DOMAIN_WEALTH_SPIN_FRAME_MS 50U
|
|
#define DOMAIN_WEALTH_SPIN_REPEAT_COUNT 1U
|
|
#define DOMAIN_WEALTH_DIRECTION_FRAME_MS 1200U
|
|
#define DOMAIN_CHOUQIAN_FRAME_MS 500U
|
|
#define DOMAIN_CHOUQIAN_FINAL_FRAME_MS 1500U
|
|
#define DOMAIN_WUXING_QIANZHI_FRAME_MS 100U
|
|
#define DOMAIN_WUXING_REVEAL_FRAME_MS 100U
|
|
#define DOMAIN_WUXING_HOLD_FRAME_MS 500U
|
|
|
|
#define DOMAIN_AUTH_KEY_STORE_NAMESPACE "auth_key"
|
|
#define DOMAIN_AUTH_STATE_STORE_NAMESPACE "auth"
|
|
#define DOMAIN_AUTH_KEY_STORE_BLOB_KEY "keymat"
|
|
#define DOMAIN_AUTH_STATE_STORE_BLOB_KEY "state"
|
|
#define DOMAIN_AUTH_LEGACY_KEY_PRIV_KEY "sk"
|
|
#define DOMAIN_AUTH_LEGACY_KEY_PUB_KEY "pk"
|
|
#define DOMAIN_AUTH_LEGACY_KEY_PUB_JKT "jkt"
|
|
#define DOMAIN_AUTH_LEGACY_KEY_VERSION_KEY "kv"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_JWT_KEY "jwt"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_EXP_KEY "exp"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_SCOPE_KEY "scope"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_KEY_VERSION_KEY "kv"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_WAIT_FACTORY_KEY "waitf"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_FAIL_COUNT_KEY "failc"
|
|
#define DOMAIN_AUTH_LEGACY_STATE_NEXT_RETRY_KEY "nextr"
|
|
|
|
typedef struct {
|
|
domain_expression_id_t id;
|
|
platform_lcd_presentation_request_t platform_request;
|
|
} domain_expression_catalog_entry_t;
|
|
|
|
static const domain_expression_catalog_entry_t s_expression_catalog[] = {
|
|
{
|
|
.id = DOMAIN_EXPRESSION_IDLE,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 120,
|
|
.interrupt_current = true,
|
|
.return_to_idle = false,
|
|
.asset_dir = "jichubiaoqing",
|
|
.frame_order = s_basic_face_frame_order,
|
|
.frame_order_count = sizeof(s_basic_face_frame_order) / sizeof(s_basic_face_frame_order[0]),
|
|
.repeat_count = 0,
|
|
.frame_ms = 100,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_SERVER_ERROR,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 100,
|
|
.interrupt_current = true,
|
|
.return_to_idle = true,
|
|
.asset_dir = "jichubiaoqing",
|
|
.frame_order = s_server_error_frame_order,
|
|
.frame_order_count = sizeof(s_server_error_frame_order) / sizeof(s_server_error_frame_order[0]),
|
|
.repeat_count = 5,
|
|
.frame_ms = 150,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_SLOT_REELS,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_SLOT_REELS,
|
|
.priority = 20,
|
|
.interrupt_current = false,
|
|
.return_to_idle = true,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_WIFI_CONNECTING,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 120,
|
|
.interrupt_current = true,
|
|
.return_to_idle = false,
|
|
.asset_dir = "wifi_connecting_qr",
|
|
.frame_order = s_wifi_connecting_frame_order,
|
|
.frame_order_count = sizeof(s_wifi_connecting_frame_order) /
|
|
sizeof(s_wifi_connecting_frame_order[0]),
|
|
.repeat_count = 0,
|
|
.frame_ms = 1000,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 120,
|
|
.interrupt_current = false,
|
|
.return_to_idle = false,
|
|
.asset_dir = "wifi_disconnected",
|
|
.frame_order = s_wifi_disconnected_frame_order,
|
|
.frame_order_count = sizeof(s_wifi_disconnected_frame_order) /
|
|
sizeof(s_wifi_disconnected_frame_order[0]),
|
|
.repeat_count = 0,
|
|
.frame_ms = 150,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_WEALTH_SPIN,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 110,
|
|
.interrupt_current = true,
|
|
.return_to_idle = false,
|
|
.asset_dir = "spin",
|
|
.repeat_count = DOMAIN_WEALTH_SPIN_REPEAT_COUNT,
|
|
.frame_ms = DOMAIN_WEALTH_SPIN_FRAME_MS,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_MUYU,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 125,
|
|
.interrupt_current = true,
|
|
.return_to_idle = true,
|
|
.asset_dir = "muyu",
|
|
.frame_order = s_muyu_frame_order,
|
|
.frame_order_count = sizeof(s_muyu_frame_order) / sizeof(s_muyu_frame_order[0]),
|
|
.repeat_count = 1,
|
|
.frame_ms = 50,
|
|
.duration_ms = 0,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_LISTENING,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 130,
|
|
.interrupt_current = true,
|
|
.return_to_idle = false,
|
|
.asset_dir = "listening",
|
|
.frame_order = s_listening_frame_order,
|
|
.frame_order_count = sizeof(s_listening_frame_order) /
|
|
sizeof(s_listening_frame_order[0]),
|
|
.repeat_count = 0,
|
|
.frame_ms = 500,
|
|
},
|
|
},
|
|
{
|
|
.id = DOMAIN_EXPRESSION_OTA_UPDATE,
|
|
.platform_request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 200,
|
|
.interrupt_current = true,
|
|
.return_to_idle = false,
|
|
.asset_dir = "ota",
|
|
.frame_order = s_ota_update_frame_order,
|
|
.frame_order_count = sizeof(s_ota_update_frame_order) /
|
|
sizeof(s_ota_update_frame_order[0]),
|
|
.repeat_count = 0,
|
|
.frame_ms = 200,
|
|
},
|
|
},
|
|
};
|
|
|
|
static void snapshot_domain_wifi_behavior_hooks(domain_wifi_behavior_hooks_t *out_hooks)
|
|
{
|
|
if (out_hooks == NULL) {
|
|
return;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
*out_hooks = s_domain_wifi_behavior_hooks;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
}
|
|
|
|
static void snapshot_domain_transport_callback(domain_multimodal_transport_event_callback_t *out_cb,
|
|
void **out_user_data)
|
|
{
|
|
domain_multimodal_transport_event_callback_t cb = NULL;
|
|
void *user_data = NULL;
|
|
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
cb = s_domain_transport_event_cb;
|
|
user_data = s_domain_transport_event_user_data;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
|
|
if (out_cb != NULL) {
|
|
*out_cb = cb;
|
|
}
|
|
if (out_user_data != NULL) {
|
|
*out_user_data = user_data;
|
|
}
|
|
}
|
|
|
|
static void snapshot_domain_downstream_playback_callback(
|
|
domain_multimodal_downstream_playback_callback_t *out_cb,
|
|
void **out_user_data)
|
|
{
|
|
domain_multimodal_downstream_playback_callback_t cb = NULL;
|
|
void *user_data = NULL;
|
|
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
cb = s_domain_downstream_playback_cb;
|
|
user_data = s_domain_downstream_playback_user_data;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
|
|
if (out_cb != NULL) {
|
|
*out_cb = cb;
|
|
}
|
|
if (out_user_data != NULL) {
|
|
*out_user_data = user_data;
|
|
}
|
|
}
|
|
|
|
static esp_err_t storage_get_optional_u8(const char *ns, const char *key, uint8_t *out, bool *found_any)
|
|
{
|
|
esp_err_t err = platform_storage_get_u8(ns, key, out);
|
|
if (err == ESP_OK) {
|
|
if (found_any != NULL) {
|
|
*found_any = true;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
return (err == ESP_ERR_NOT_FOUND) ? ESP_OK : err;
|
|
}
|
|
|
|
static esp_err_t storage_get_optional_u32(const char *ns, const char *key, uint32_t *out, bool *found_any)
|
|
{
|
|
esp_err_t err = platform_storage_get_u32(ns, key, out);
|
|
if (err == ESP_OK) {
|
|
if (found_any != NULL) {
|
|
*found_any = true;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
return (err == ESP_ERR_NOT_FOUND) ? ESP_OK : err;
|
|
}
|
|
|
|
static esp_err_t storage_get_optional_u64(const char *ns, const char *key, uint64_t *out, bool *found_any)
|
|
{
|
|
esp_err_t err = platform_storage_get_u64(ns, key, out);
|
|
if (err == ESP_OK) {
|
|
if (found_any != NULL) {
|
|
*found_any = true;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
return (err == ESP_ERR_NOT_FOUND) ? ESP_OK : err;
|
|
}
|
|
|
|
static void dispatch_domain_transport_event(domain_multimodal_transport_event_type_t type,
|
|
const uint8_t *data,
|
|
size_t data_len)
|
|
{
|
|
domain_multimodal_transport_event_callback_t cb = NULL;
|
|
void *cb_user_data = NULL;
|
|
snapshot_domain_transport_callback(&cb, &cb_user_data);
|
|
|
|
if (cb == NULL) {
|
|
return;
|
|
}
|
|
|
|
domain_multimodal_transport_event_t event = {
|
|
.type = type,
|
|
.data = data,
|
|
.data_len = data_len,
|
|
};
|
|
cb(&event, cb_user_data);
|
|
}
|
|
|
|
static void domain_transport_connected_adapter(void *user_data)
|
|
{
|
|
(void)user_data;
|
|
dispatch_domain_transport_event(DOMAIN_MULTIMODAL_TRANSPORT_EVENT_CONNECTED, NULL, 0U);
|
|
}
|
|
|
|
static void domain_transport_disconnected_adapter(void *user_data)
|
|
{
|
|
(void)user_data;
|
|
dispatch_domain_transport_event(DOMAIN_MULTIMODAL_TRANSPORT_EVENT_DISCONNECTED, NULL, 0U);
|
|
}
|
|
|
|
static void domain_transport_error_adapter(void *user_data)
|
|
{
|
|
(void)user_data;
|
|
dispatch_domain_transport_event(DOMAIN_MULTIMODAL_TRANSPORT_EVENT_ERROR, NULL, 0U);
|
|
}
|
|
|
|
static void domain_transport_text_adapter(const uint8_t *data, size_t data_len, void *user_data)
|
|
{
|
|
(void)user_data;
|
|
dispatch_domain_transport_event(DOMAIN_MULTIMODAL_TRANSPORT_EVENT_TEXT_MESSAGE, data, data_len);
|
|
}
|
|
|
|
static void domain_transport_binary_adapter(const uint8_t *data, size_t data_len, void *user_data)
|
|
{
|
|
(void)user_data;
|
|
dispatch_domain_transport_event(DOMAIN_MULTIMODAL_TRANSPORT_EVENT_BINARY_MESSAGE, data, data_len);
|
|
}
|
|
|
|
static const char *domain_wifi_prompt_file_path(domain_wifi_prompt_id_t prompt)
|
|
{
|
|
switch (prompt) {
|
|
case DOMAIN_WIFI_PROMPT_WIFI_SUCCESS:
|
|
#ifdef CONFIG_VOICE_PROMPT_WIFI_SUCCESS_FILE
|
|
return CONFIG_VOICE_PROMPT_WIFI_SUCCESS_FILE;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
case DOMAIN_WIFI_PROMPT_AFTER_WIFI_SUCCESS:
|
|
#ifdef CONFIG_VOICE_PROMPT_AFTER_WIFI_SUCCESS_FILE
|
|
return CONFIG_VOICE_PROMPT_AFTER_WIFI_SUCCESS_FILE;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
case DOMAIN_WIFI_PROMPT_WIFI_CONNECTING:
|
|
#ifdef CONFIG_VOICE_PROMPT_WIFI_CONNECTING_FILE
|
|
return CONFIG_VOICE_PROMPT_WIFI_CONNECTING_FILE;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
case DOMAIN_WIFI_PROMPT_WIFI_FAILED:
|
|
#ifdef CONFIG_VOICE_PROMPT_WIFI_FAILED_FILE
|
|
return CONFIG_VOICE_PROMPT_WIFI_FAILED_FILE;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
case DOMAIN_WIFI_PROMPT_WIFI_CONFIG:
|
|
#ifdef CONFIG_VOICE_PROMPT_WIFI_CONFIG_FILE
|
|
return CONFIG_VOICE_PROMPT_WIFI_CONFIG_FILE;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void domain_downstream_playback_dispatch(
|
|
domain_multimodal_downstream_playback_event_type_t type,
|
|
uint32_t playback_generation)
|
|
{
|
|
domain_multimodal_downstream_playback_callback_t cb = NULL;
|
|
void *cb_user_data = NULL;
|
|
snapshot_domain_downstream_playback_callback(&cb, &cb_user_data);
|
|
if (cb == NULL) {
|
|
return;
|
|
}
|
|
|
|
const domain_multimodal_downstream_playback_event_t event = {
|
|
.type = type,
|
|
.playback_generation = playback_generation,
|
|
};
|
|
cb(&event, cb_user_data);
|
|
}
|
|
|
|
static void domain_downstream_playback_started_adapter(uint32_t playback_generation,
|
|
void *user_data)
|
|
{
|
|
(void)user_data;
|
|
domain_downstream_playback_dispatch(DOMAIN_MULTIMODAL_DOWNSTREAM_PLAYBACK_STARTED,
|
|
playback_generation);
|
|
}
|
|
|
|
static void domain_downstream_playback_completed_adapter(uint32_t playback_generation,
|
|
void *user_data)
|
|
{
|
|
(void)user_data;
|
|
domain_downstream_playback_dispatch(DOMAIN_MULTIMODAL_DOWNSTREAM_PLAYBACK_COMPLETED,
|
|
playback_generation);
|
|
}
|
|
|
|
static void domain_downstream_playback_stopped_adapter(uint32_t playback_generation,
|
|
void *user_data)
|
|
{
|
|
(void)user_data;
|
|
domain_downstream_playback_dispatch(DOMAIN_MULTIMODAL_DOWNSTREAM_PLAYBACK_STOPPED,
|
|
playback_generation);
|
|
}
|
|
|
|
static void domain_downstream_playback_stalled_adapter(uint32_t playback_generation,
|
|
void *user_data)
|
|
{
|
|
(void)user_data;
|
|
domain_downstream_playback_dispatch(DOMAIN_MULTIMODAL_DOWNSTREAM_PLAYBACK_STALLED,
|
|
playback_generation);
|
|
}
|
|
|
|
static void domain_wifi_set_keep_awake_adapter(bool keep_awake, void *user_data)
|
|
{
|
|
(void)user_data;
|
|
|
|
domain_wifi_behavior_hooks_t hooks = {0};
|
|
snapshot_domain_wifi_behavior_hooks(&hooks);
|
|
if (hooks.set_keep_awake != NULL) {
|
|
hooks.set_keep_awake(keep_awake);
|
|
}
|
|
}
|
|
|
|
static esp_err_t domain_wifi_play_prompt_file_adapter(const char *path, void *user_data)
|
|
{
|
|
(void)user_data;
|
|
|
|
domain_wifi_behavior_hooks_t hooks = {0};
|
|
snapshot_domain_wifi_behavior_hooks(&hooks);
|
|
if (hooks.play_prompt_file == NULL) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
return hooks.play_prompt_file(path);
|
|
}
|
|
|
|
esp_err_t domain_runtime_time_set_timezone(const char *posix_tz)
|
|
{
|
|
return platform_time_set_timezone(posix_tz);
|
|
}
|
|
|
|
esp_err_t domain_runtime_time_sync_start(const char *sntp_server, const char *posix_tz)
|
|
{
|
|
return platform_time_sync_start(sntp_server, posix_tz);
|
|
}
|
|
|
|
bool domain_runtime_time_is_synced(void)
|
|
{
|
|
return platform_time_is_synced();
|
|
}
|
|
|
|
esp_err_t domain_runtime_post_json(const char *url,
|
|
const char *payload,
|
|
const char *device_id,
|
|
const char *bearer_token,
|
|
int timeout_ms,
|
|
int tx_buffer_size,
|
|
int rx_buffer_size,
|
|
char *response_body,
|
|
size_t response_body_capacity,
|
|
size_t *out_response_body_len,
|
|
int *out_status)
|
|
{
|
|
if (out_response_body_len != NULL) {
|
|
*out_response_body_len = 0U;
|
|
}
|
|
if (out_status != NULL) {
|
|
*out_status = -1;
|
|
}
|
|
|
|
if (url == NULL || url[0] == '\0' ||
|
|
payload == NULL ||
|
|
response_body == NULL ||
|
|
response_body_capacity < 2U) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
platform_https_header_t headers[2] = {0};
|
|
size_t header_count = 0U;
|
|
if (device_id != NULL && device_id[0] != '\0') {
|
|
headers[header_count++] = (platform_https_header_t) {
|
|
.name = "X-Device-Id",
|
|
.value = device_id,
|
|
};
|
|
}
|
|
|
|
char *auth_header = NULL;
|
|
if (bearer_token != NULL && bearer_token[0] != '\0') {
|
|
int needed = snprintf(NULL, 0, "Bearer %s", bearer_token);
|
|
if (needed <= 0) {
|
|
return ESP_FAIL;
|
|
}
|
|
auth_header = (char *)malloc((size_t)needed + 1U);
|
|
if (auth_header == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
snprintf(auth_header, (size_t)needed + 1U, "Bearer %s", bearer_token);
|
|
headers[header_count++] = (platform_https_header_t) {
|
|
.name = "Authorization",
|
|
.value = auth_header,
|
|
};
|
|
}
|
|
|
|
platform_https_post_json_request_t platform_request = {
|
|
.url = url,
|
|
.payload = payload,
|
|
.headers = headers,
|
|
.header_count = header_count,
|
|
.timeout_ms = timeout_ms,
|
|
.tx_buffer_size = tx_buffer_size,
|
|
.rx_buffer_size = rx_buffer_size,
|
|
};
|
|
platform_https_response_t platform_response = {
|
|
.body = response_body,
|
|
.body_capacity = response_body_capacity,
|
|
.body_len = 0U,
|
|
.status_code = -1,
|
|
};
|
|
|
|
esp_err_t err = platform_https_post_json(&platform_request, &platform_response);
|
|
free(auth_header);
|
|
|
|
if (out_response_body_len != NULL) {
|
|
*out_response_body_len = platform_response.body_len;
|
|
}
|
|
if (out_status != NULL) {
|
|
*out_status = platform_response.status_code;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_transport_start(
|
|
const char *bearer_token,
|
|
const char *workspace_id,
|
|
domain_multimodal_transport_event_callback_t cb,
|
|
void *user_data)
|
|
{
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
s_domain_transport_event_cb = cb;
|
|
s_domain_transport_event_user_data = user_data;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
|
|
const platform_multimodal_transport_callbacks_t callbacks = {
|
|
.on_connected = domain_transport_connected_adapter,
|
|
.on_disconnected = domain_transport_disconnected_adapter,
|
|
.on_error = domain_transport_error_adapter,
|
|
.on_text_payload = domain_transport_text_adapter,
|
|
.on_binary_payload = domain_transport_binary_adapter,
|
|
.user_data = NULL,
|
|
};
|
|
|
|
esp_err_t err = platform_multimodal_transport_start(
|
|
bearer_token,
|
|
workspace_id,
|
|
(cb != NULL) ? &callbacks : NULL);
|
|
if (err != ESP_OK) {
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
s_domain_transport_event_cb = NULL;
|
|
s_domain_transport_event_user_data = NULL;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_transport_stop(int timeout_ms)
|
|
{
|
|
esp_err_t err = platform_multimodal_transport_stop(timeout_ms);
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
s_domain_transport_event_cb = NULL;
|
|
s_domain_transport_event_user_data = NULL;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
return err;
|
|
}
|
|
|
|
bool domain_runtime_multimodal_transport_is_connected(void)
|
|
{
|
|
return platform_multimodal_transport_is_connected();
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_transport_send_text(const char *label, const char *text)
|
|
{
|
|
return platform_multimodal_transport_send_text(label, text);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_transport_send_text_realtime(const char *label, const char *text)
|
|
{
|
|
return platform_multimodal_transport_send_text_realtime(label, text);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_transport_send_binary_realtime(const uint8_t *data, size_t len)
|
|
{
|
|
return platform_multimodal_transport_send_binary_realtime(data, len);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_init(void)
|
|
{
|
|
return platform_multimodal_audio_init();
|
|
}
|
|
|
|
void domain_runtime_multimodal_audio_deinit(void)
|
|
{
|
|
platform_multimodal_audio_deinit();
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_start_uplink(
|
|
domain_multimodal_audio_uplink_cb_t cb,
|
|
void *user_data)
|
|
{
|
|
return platform_multimodal_audio_start_uplink(cb, user_data);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_stop_uplink(int timeout_ms)
|
|
{
|
|
return platform_multimodal_audio_stop_uplink(timeout_ms);
|
|
}
|
|
|
|
bool domain_runtime_multimodal_audio_is_uplink_running(void)
|
|
{
|
|
return platform_multimodal_audio_is_uplink_running();
|
|
}
|
|
|
|
bool domain_runtime_multimodal_audio_is_initialized(void)
|
|
{
|
|
return platform_multimodal_audio_is_initialized();
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_set_speaker_output_enabled(bool enabled)
|
|
{
|
|
return platform_multimodal_audio_set_speaker_output_enabled(enabled);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_play_downstream_opus(const uint8_t *data, size_t len)
|
|
{
|
|
return platform_multimodal_audio_play_downstream_opus(data, len);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_begin_downstream_response(
|
|
uint32_t *out_playback_generation)
|
|
{
|
|
return platform_multimodal_audio_begin_downstream_response(out_playback_generation);
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_finish_downstream_response(void)
|
|
{
|
|
return platform_multimodal_audio_finish_downstream_response();
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_stop_downstream_playback(void)
|
|
{
|
|
return platform_multimodal_audio_stop_downstream_playback();
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_audio_register_downstream_playback_callback(
|
|
domain_multimodal_downstream_playback_callback_t cb,
|
|
void *user_data)
|
|
{
|
|
const platform_multimodal_downstream_playback_callbacks_t callbacks = {
|
|
.on_started = domain_downstream_playback_started_adapter,
|
|
.on_completed = domain_downstream_playback_completed_adapter,
|
|
.on_stopped = domain_downstream_playback_stopped_adapter,
|
|
.on_stalled = domain_downstream_playback_stalled_adapter,
|
|
.user_data = NULL,
|
|
};
|
|
|
|
esp_err_t err = platform_multimodal_audio_register_downstream_playback_callbacks(
|
|
cb != NULL ? &callbacks : NULL);
|
|
if (err == ESP_OK) {
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
s_domain_downstream_playback_cb = cb;
|
|
s_domain_downstream_playback_user_data = user_data;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static domain_digital_input_config_t domain_runtime_multimodal_ptt_button_config(void)
|
|
{
|
|
domain_digital_input_config_t config = {
|
|
.gpio_num = CONFIG_MULTIMODAL_PTT_BUTTON_GPIO,
|
|
.active_level = CONFIG_MULTIMODAL_PTT_BUTTON_ACTIVE_LEVEL,
|
|
.pullup_enabled = CONFIG_MULTIMODAL_PTT_BUTTON_PULLUP,
|
|
.pulldown_enabled = CONFIG_MULTIMODAL_PTT_BUTTON_PULLDOWN,
|
|
};
|
|
return config;
|
|
}
|
|
|
|
esp_err_t domain_runtime_multimodal_ptt_button_init(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_multimodal_ptt_button_config();
|
|
ESP_LOGI(TAG,
|
|
"PTT button config gpio=%d active_level=%d pullup=%d pulldown=%d",
|
|
config.gpio_num,
|
|
config.active_level,
|
|
config.pullup_enabled,
|
|
config.pulldown_enabled);
|
|
return domain_runtime_digital_input_configure(&config);
|
|
}
|
|
|
|
bool domain_runtime_multimodal_ptt_button_is_pressed(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_multimodal_ptt_button_config();
|
|
return domain_runtime_digital_input_is_active(&config);
|
|
}
|
|
|
|
static domain_digital_input_config_t domain_runtime_slot_feature_switch_config(void)
|
|
{
|
|
domain_digital_input_config_t config = {
|
|
.gpio_num = CONFIG_TQ_SLOT_SWITCH_GPIO,
|
|
.active_level = CONFIG_TQ_SLOT_SWITCH_ACTIVE_LEVEL,
|
|
.pullup_enabled = CONFIG_TQ_SLOT_SWITCH_PULLUP,
|
|
.pulldown_enabled = CONFIG_TQ_SLOT_SWITCH_PULLDOWN,
|
|
};
|
|
return config;
|
|
}
|
|
|
|
esp_err_t domain_runtime_slot_feature_switch_init(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_slot_feature_switch_config();
|
|
ESP_LOGI(TAG,
|
|
"Slot feature switch config gpio=%d active_level=%d pullup=%d pulldown=%d",
|
|
config.gpio_num,
|
|
config.active_level,
|
|
config.pullup_enabled,
|
|
config.pulldown_enabled);
|
|
return domain_runtime_digital_input_configure(&config);
|
|
}
|
|
|
|
bool domain_runtime_slot_feature_switch_is_active(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_slot_feature_switch_config();
|
|
return domain_runtime_digital_input_is_active(&config);
|
|
}
|
|
|
|
static domain_digital_input_config_t domain_runtime_calibration_switch_config(void)
|
|
{
|
|
domain_digital_input_config_t config = {
|
|
.gpio_num = CONFIG_TQ_CALIBRATION_SWITCH_GPIO,
|
|
.active_level = CONFIG_TQ_CALIBRATION_SWITCH_ACTIVE_LEVEL,
|
|
.pullup_enabled = CONFIG_TQ_CALIBRATION_SWITCH_PULLUP,
|
|
.pulldown_enabled = CONFIG_TQ_CALIBRATION_SWITCH_PULLDOWN,
|
|
};
|
|
return config;
|
|
}
|
|
|
|
esp_err_t domain_runtime_calibration_switch_init(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_calibration_switch_config();
|
|
ESP_LOGI(TAG,
|
|
"Calibration switch config gpio=%d active_level=%d pullup=%d pulldown=%d",
|
|
config.gpio_num,
|
|
config.active_level,
|
|
config.pullup_enabled,
|
|
config.pulldown_enabled);
|
|
esp_err_t err = domain_runtime_digital_input_configure(&config);
|
|
if (err == ESP_OK) {
|
|
ESP_LOGI(TAG,
|
|
"KEY1/SW1 ready gpio=%d initial_state=%s",
|
|
config.gpio_num,
|
|
domain_runtime_digital_input_is_active(&config) ? "pressed" : "released");
|
|
}
|
|
return err;
|
|
}
|
|
|
|
bool domain_runtime_calibration_switch_is_active(void)
|
|
{
|
|
domain_digital_input_config_t config = domain_runtime_calibration_switch_config();
|
|
return domain_runtime_digital_input_is_active(&config);
|
|
}
|
|
|
|
esp_err_t domain_runtime_platform_init(void)
|
|
{
|
|
return platform_init();
|
|
}
|
|
|
|
esp_err_t domain_runtime_prepare_storage(void)
|
|
{
|
|
return platform_prepare_storage();
|
|
}
|
|
|
|
esp_err_t domain_runtime_mount_littlefs(void)
|
|
{
|
|
return platform_mount_littlefs();
|
|
}
|
|
|
|
esp_err_t domain_runtime_mount_logfs(void)
|
|
{
|
|
return platform_mount_logfs();
|
|
}
|
|
|
|
void domain_runtime_read_device_identity(char *device_id,
|
|
size_t device_id_len,
|
|
char *serial_number,
|
|
size_t serial_number_len)
|
|
{
|
|
platform_read_device_identity(device_id, device_id_len, serial_number, serial_number_len);
|
|
}
|
|
|
|
esp_err_t domain_runtime_read_firmware_version(char *out, size_t out_len)
|
|
{
|
|
return platform_read_firmware_version(out, out_len);
|
|
}
|
|
|
|
esp_err_t domain_runtime_write_device_identity(const char *device_id, const char *serial_number)
|
|
{
|
|
return platform_write_device_identity(device_id, serial_number);
|
|
}
|
|
|
|
typedef struct {
|
|
uint8_t priv_key[32];
|
|
uint8_t pub_key[65];
|
|
uint8_t pub_key_jkt[32];
|
|
uint32_t key_version;
|
|
bool valid;
|
|
} domain_auth_key_material_legacy_blob_t;
|
|
|
|
typedef struct {
|
|
char jwt[1536];
|
|
uint64_t exp_epoch_s;
|
|
uint32_t scope_mask;
|
|
uint32_t key_version;
|
|
bool jwt_valid;
|
|
bool wait_factory_persisted;
|
|
uint32_t fail_count;
|
|
uint64_t next_retry_epoch_s;
|
|
} domain_auth_state_legacy_blob_t;
|
|
|
|
static bool auth_store_record_target(domain_runtime_auth_record_t record,
|
|
const char **out_ns,
|
|
const char **out_key)
|
|
{
|
|
if (out_ns == NULL || out_key == NULL) {
|
|
return false;
|
|
}
|
|
|
|
switch (record) {
|
|
case DOMAIN_RUNTIME_AUTH_RECORD_KEY_MATERIAL:
|
|
*out_ns = DOMAIN_AUTH_KEY_STORE_NAMESPACE;
|
|
*out_key = DOMAIN_AUTH_KEY_STORE_BLOB_KEY;
|
|
return true;
|
|
case DOMAIN_RUNTIME_AUTH_RECORD_STATE:
|
|
*out_ns = DOMAIN_AUTH_STATE_STORE_NAMESPACE;
|
|
*out_key = DOMAIN_AUTH_STATE_STORE_BLOB_KEY;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static esp_err_t load_legacy_auth_key_material(void *out, size_t out_len, size_t *out_len_actual)
|
|
{
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = 0U;
|
|
}
|
|
if (out == NULL || out_len < sizeof(domain_auth_key_material_legacy_blob_t)) {
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
domain_auth_key_material_legacy_blob_t key = {0};
|
|
size_t priv_len = 0U;
|
|
size_t pub_len = 0U;
|
|
size_t jkt_len = 0U;
|
|
esp_err_t priv_err = platform_storage_get_blob(DOMAIN_AUTH_KEY_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_KEY_PRIV_KEY,
|
|
key.priv_key,
|
|
sizeof(key.priv_key),
|
|
&priv_len);
|
|
esp_err_t pub_err = platform_storage_get_blob(DOMAIN_AUTH_KEY_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_KEY_PUB_KEY,
|
|
key.pub_key,
|
|
sizeof(key.pub_key),
|
|
&pub_len);
|
|
esp_err_t jkt_err = platform_storage_get_blob(DOMAIN_AUTH_KEY_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_KEY_PUB_JKT,
|
|
key.pub_key_jkt,
|
|
sizeof(key.pub_key_jkt),
|
|
&jkt_len);
|
|
if (priv_err != ESP_OK || pub_err != ESP_OK || jkt_err != ESP_OK) {
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
if (priv_len != sizeof(key.priv_key) ||
|
|
pub_len != sizeof(key.pub_key) ||
|
|
jkt_len != sizeof(key.pub_key_jkt)) {
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
uint32_t key_version = 0U;
|
|
esp_err_t kv_err = platform_storage_get_u32(DOMAIN_AUTH_KEY_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_KEY_VERSION_KEY,
|
|
&key_version);
|
|
key.key_version = (kv_err == ESP_OK && key_version > 0U) ? key_version : 1U;
|
|
key.valid = true;
|
|
|
|
memcpy(out, &key, sizeof(key));
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = sizeof(key);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t load_legacy_auth_state(void *out, size_t out_len, size_t *out_len_actual)
|
|
{
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = 0U;
|
|
}
|
|
if (out == NULL || out_len < sizeof(domain_auth_state_legacy_blob_t)) {
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
domain_auth_state_legacy_blob_t *state = calloc(1, sizeof(*state));
|
|
if (state == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
bool found_any = false;
|
|
|
|
size_t jwt_len = 0U;
|
|
esp_err_t err = platform_storage_get_string(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_JWT_KEY,
|
|
state->jwt,
|
|
sizeof(state->jwt),
|
|
&jwt_len);
|
|
if (err == ESP_OK) {
|
|
found_any = true;
|
|
state->jwt_valid = jwt_len > 1U && state->jwt[0] != '\0';
|
|
} else if (err != ESP_ERR_NOT_FOUND) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
err = storage_get_optional_u64(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_EXP_KEY,
|
|
&state->exp_epoch_s,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
err = storage_get_optional_u32(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_SCOPE_KEY,
|
|
&state->scope_mask,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
err = storage_get_optional_u32(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_KEY_VERSION_KEY,
|
|
&state->key_version,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
uint8_t wait_factory = 0U;
|
|
err = storage_get_optional_u8(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_WAIT_FACTORY_KEY,
|
|
&wait_factory,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
state->wait_factory_persisted = wait_factory != 0U;
|
|
|
|
err = storage_get_optional_u32(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_FAIL_COUNT_KEY,
|
|
&state->fail_count,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
err = storage_get_optional_u64(DOMAIN_AUTH_STATE_STORE_NAMESPACE,
|
|
DOMAIN_AUTH_LEGACY_STATE_NEXT_RETRY_KEY,
|
|
&state->next_retry_epoch_s,
|
|
&found_any);
|
|
if (err != ESP_OK) {
|
|
free(state);
|
|
return err;
|
|
}
|
|
|
|
if (!found_any) {
|
|
free(state);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
memcpy(out, state, sizeof(*state));
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = sizeof(*state);
|
|
}
|
|
free(state);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t load_legacy_auth_record(domain_runtime_auth_record_t record,
|
|
void *out,
|
|
size_t out_len,
|
|
size_t *out_len_actual)
|
|
{
|
|
switch (record) {
|
|
case DOMAIN_RUNTIME_AUTH_RECORD_KEY_MATERIAL:
|
|
return load_legacy_auth_key_material(out, out_len, out_len_actual);
|
|
case DOMAIN_RUNTIME_AUTH_RECORD_STATE:
|
|
return load_legacy_auth_state(out, out_len, out_len_actual);
|
|
default:
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
}
|
|
|
|
esp_err_t domain_runtime_auth_store_load_record(domain_runtime_auth_record_t record,
|
|
void *out,
|
|
size_t out_len,
|
|
size_t *out_len_actual)
|
|
{
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = 0U;
|
|
}
|
|
if (out == NULL || out_len == 0U) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
const char *ns = NULL;
|
|
const char *key = NULL;
|
|
if (!auth_store_record_target(record, &ns, &key)) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
size_t actual = 0U;
|
|
esp_err_t err = platform_storage_get_blob(ns, key, out, out_len, &actual);
|
|
if (err == ESP_OK) {
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = actual;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
if (err != ESP_ERR_NOT_FOUND) {
|
|
return err;
|
|
}
|
|
|
|
err = load_legacy_auth_record(record, out, out_len, &actual);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
esp_err_t save_err = platform_storage_set_blob(ns, key, out, actual);
|
|
if (save_err != ESP_OK) {
|
|
ESP_LOGW(TAG,
|
|
"Persist migrated auth record %d failed: %s",
|
|
(int)record,
|
|
esp_err_to_name(save_err));
|
|
} else {
|
|
ESP_LOGI(TAG, "Migrated legacy auth record %d", (int)record);
|
|
}
|
|
if (out_len_actual != NULL) {
|
|
*out_len_actual = actual;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t domain_runtime_auth_store_save_record(domain_runtime_auth_record_t record,
|
|
const void *data,
|
|
size_t len)
|
|
{
|
|
if (data == NULL || len == 0U) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
const char *ns = NULL;
|
|
const char *key = NULL;
|
|
if (!auth_store_record_target(record, &ns, &key)) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
return platform_storage_set_blob(ns, key, data, len);
|
|
}
|
|
|
|
esp_err_t domain_runtime_console_install(void)
|
|
{
|
|
return platform_console_install();
|
|
}
|
|
|
|
int domain_runtime_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms)
|
|
{
|
|
return platform_console_read_bytes(buf, len, timeout_ms);
|
|
}
|
|
|
|
void domain_runtime_console_write_line(const char *line)
|
|
{
|
|
platform_console_write_line(line);
|
|
}
|
|
|
|
esp_err_t domain_runtime_stepper_rotate_angle_with_accel_timeout(float angle,
|
|
int target_delay_us,
|
|
int timeout_ms)
|
|
{
|
|
return platform_stepper_rotate_angle_with_accel_timeout(angle, target_delay_us, timeout_ms);
|
|
}
|
|
|
|
void domain_runtime_stepper_power_off(void)
|
|
{
|
|
platform_stepper_power_off();
|
|
}
|
|
|
|
esp_err_t domain_runtime_stepper_release_hold(int timeout_ms)
|
|
{
|
|
return platform_stepper_release_hold(timeout_ms);
|
|
}
|
|
|
|
esp_err_t domain_runtime_digital_input_configure(const domain_digital_input_config_t *config)
|
|
{
|
|
if (config == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
platform_digital_input_config_t platform_config = {
|
|
.gpio_num = config->gpio_num,
|
|
.active_level = config->active_level,
|
|
.pullup_enabled = config->pullup_enabled,
|
|
.pulldown_enabled = config->pulldown_enabled,
|
|
};
|
|
return platform_configure_digital_input(&platform_config);
|
|
}
|
|
|
|
bool domain_runtime_digital_input_is_active(const domain_digital_input_config_t *config)
|
|
{
|
|
if (config == NULL) {
|
|
return false;
|
|
}
|
|
|
|
platform_digital_input_config_t platform_config = {
|
|
.gpio_num = config->gpio_num,
|
|
.active_level = config->active_level,
|
|
.pullup_enabled = config->pullup_enabled,
|
|
.pulldown_enabled = config->pulldown_enabled,
|
|
};
|
|
return platform_is_digital_input_active(&platform_config);
|
|
}
|
|
|
|
void domain_runtime_behavior_set_keep_awake(bool keep_awake)
|
|
{
|
|
platform_behavior_set_keep_awake(keep_awake);
|
|
}
|
|
|
|
esp_err_t domain_runtime_behavior_play_prompt(domain_wifi_prompt_id_t prompt)
|
|
{
|
|
const char *path = domain_wifi_prompt_file_path(prompt);
|
|
if (path == NULL || path[0] == '\0') {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
return platform_behavior_play_prompt_file(path);
|
|
}
|
|
|
|
esp_err_t domain_runtime_behavior_play_prompt_file(const char *path)
|
|
{
|
|
return platform_behavior_play_prompt_file(path);
|
|
}
|
|
|
|
void domain_runtime_wifi_portal_set_behavior_hooks(const domain_wifi_behavior_hooks_t *hooks)
|
|
{
|
|
if (hooks == NULL) {
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
memset(&s_domain_wifi_behavior_hooks, 0, sizeof(s_domain_wifi_behavior_hooks));
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
platform_wifi_portal_set_behavior_hooks(NULL);
|
|
return;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
s_domain_wifi_behavior_hooks = *hooks;
|
|
portEXIT_CRITICAL(&s_domain_runtime_adapter_lock);
|
|
|
|
platform_wifi_behavior_hooks_t platform_hooks = {
|
|
.set_keep_awake = domain_wifi_set_keep_awake_adapter,
|
|
.play_prompt_file = domain_wifi_play_prompt_file_adapter,
|
|
.user_data = NULL,
|
|
};
|
|
platform_wifi_portal_set_behavior_hooks(&platform_hooks);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_init_defaults(const char *seed_ssid, const char *seed_password)
|
|
{
|
|
return platform_wifi_portal_init_defaults(seed_ssid, seed_password);
|
|
}
|
|
|
|
bool domain_runtime_wifi_portal_has_saved_credentials(void)
|
|
{
|
|
return platform_wifi_portal_has_saved_credentials();
|
|
}
|
|
|
|
bool domain_runtime_wifi_portal_sta_is_connected(void)
|
|
{
|
|
return platform_wifi_portal_sta_is_connected();
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_start_config_ap(const char *ssid_prefix)
|
|
{
|
|
return platform_wifi_portal_start_config_ap(ssid_prefix);
|
|
}
|
|
|
|
bool domain_runtime_wifi_connect_error_keeps_credentials(esp_err_t err)
|
|
{
|
|
return platform_wifi_connect_error_keeps_credentials(err);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_connect_sta(int timeout_ms)
|
|
{
|
|
return platform_wifi_portal_connect_sta(timeout_ms);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_connect_sta_with_credentials(
|
|
const char *ssid,
|
|
const char *password,
|
|
int timeout_ms)
|
|
{
|
|
return platform_wifi_portal_connect_sta_with_credentials(ssid, password, timeout_ms);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_reconnect_sta(int timeout_ms)
|
|
{
|
|
return platform_wifi_portal_reconnect_sta(timeout_ms);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_stop_sta(void)
|
|
{
|
|
return platform_wifi_portal_stop_sta();
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_set_light_standby(bool standby)
|
|
{
|
|
return platform_wifi_portal_set_light_standby(standby);
|
|
}
|
|
|
|
bool domain_runtime_is_network_connect_error(esp_err_t err)
|
|
{
|
|
return platform_https_is_connect_error(err);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_save_credentials(const char *ssid, const char *password)
|
|
{
|
|
return platform_wifi_portal_save_credentials(ssid, password);
|
|
}
|
|
|
|
esp_err_t domain_runtime_wifi_portal_clear_saved_credentials(void)
|
|
{
|
|
return platform_wifi_portal_clear_saved_credentials();
|
|
}
|
|
|
|
esp_err_t domain_runtime_init_lcd_device(void)
|
|
{
|
|
return platform_init_lcd_device();
|
|
}
|
|
|
|
esp_err_t domain_runtime_set_lcd_backlight_brightness(uint8_t brightness_percent)
|
|
{
|
|
return platform_lcd_set_backlight_brightness(brightness_percent);
|
|
}
|
|
|
|
void domain_runtime_start_lcd_render_loop(void)
|
|
{
|
|
platform_start_lcd_render_loop();
|
|
}
|
|
|
|
esp_err_t domain_runtime_stop_lcd_render_loop(int timeout_ms)
|
|
{
|
|
return platform_stop_lcd_render_loop(timeout_ms);
|
|
}
|
|
|
|
esp_err_t domain_runtime_update_battery_overlay(const domain_battery_status_t *status)
|
|
{
|
|
if (status == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
const platform_lcd_status_overlay_t overlay = {
|
|
.low_battery_visible = status->valid &&
|
|
(status->level_state == DOMAIN_BATTERY_LEVEL_LOW ||
|
|
status->level_state == DOMAIN_BATTERY_LEVEL_CRITICAL),
|
|
.charging_visible =
|
|
status->charge_state == DOMAIN_BATTERY_CHARGE_CHARGING,
|
|
};
|
|
return platform_lcd_set_status_overlay(&overlay);
|
|
}
|
|
|
|
static const domain_expression_catalog_entry_t *domain_find_expression_entry(domain_expression_id_t id)
|
|
{
|
|
for (size_t i = 0; i < sizeof(s_expression_catalog) / sizeof(s_expression_catalog[0]); ++i) {
|
|
if (s_expression_catalog[i].id == id) {
|
|
return &s_expression_catalog[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static esp_err_t domain_resolve_expression_platform_request(
|
|
const domain_expression_request_t *request,
|
|
platform_lcd_presentation_request_t *out_platform_request)
|
|
{
|
|
if (request == NULL || out_platform_request == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (request->id == DOMAIN_EXPRESSION_WEALTH_DIRECTION) {
|
|
size_t direction_count = sizeof(s_wealth_direction_asset_dirs) /
|
|
sizeof(s_wealth_direction_asset_dirs[0]);
|
|
if (request->direction_index >= direction_count) {
|
|
ESP_LOGW(TAG,
|
|
"Wealth-direction reveal index out of range: %u (max %u)",
|
|
(unsigned int)request->direction_index,
|
|
(unsigned int)direction_count);
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
*out_platform_request = (platform_lcd_presentation_request_t) {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.priority = 110,
|
|
.interrupt_current = true, /* equal priority + interrupt replaces the spin loop */
|
|
.return_to_idle = true,
|
|
.asset_dir = s_wealth_direction_asset_dirs[request->direction_index],
|
|
.repeat_count = 1, /* play the reveal once, then return to idle */
|
|
.frame_ms = DOMAIN_WEALTH_DIRECTION_FRAME_MS,
|
|
};
|
|
return ESP_OK;
|
|
}
|
|
|
|
if (request->id == DOMAIN_EXPRESSION_CHOUQIAN) {
|
|
static platform_lcd_presentation_step_t s_chouqian_steps[6];
|
|
const size_t result_count = sizeof(s_chouqian_result_asset_dirs) /
|
|
sizeof(s_chouqian_result_asset_dirs[0]);
|
|
size_t result_index = esp_random() % result_count;
|
|
if (request->chouqian_result_index_valid) {
|
|
if (request->chouqian_result_index >= result_count) {
|
|
ESP_LOGW(TAG,
|
|
"Chouqian result index out of range: %u (max %u)",
|
|
(unsigned int)request->chouqian_result_index,
|
|
(unsigned int)result_count);
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
result_index = request->chouqian_result_index;
|
|
}
|
|
const char *result_asset = s_chouqian_result_asset_dirs[result_index];
|
|
|
|
s_chouqian_steps[0] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = "chouqian_qianzhi",
|
|
.frame_index = 0,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FRAME_MS,
|
|
};
|
|
s_chouqian_steps[1] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = "chouqian_qianzhi",
|
|
.frame_index = 1,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FRAME_MS,
|
|
};
|
|
s_chouqian_steps[2] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = "chouqian_qianzhi",
|
|
.frame_index = 2,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FRAME_MS,
|
|
};
|
|
s_chouqian_steps[3] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = result_asset,
|
|
.frame_index = 0,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FRAME_MS,
|
|
};
|
|
s_chouqian_steps[4] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = result_asset,
|
|
.frame_index = 1,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FRAME_MS,
|
|
};
|
|
s_chouqian_steps[5] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = result_asset,
|
|
.frame_index = 2,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_CHOUQIAN_FINAL_FRAME_MS,
|
|
};
|
|
|
|
*out_platform_request = (platform_lcd_presentation_request_t) {
|
|
.kind = PLATFORM_LCD_PRESENTATION_STEP_SEQUENCE,
|
|
.priority = 125,
|
|
.interrupt_current = true,
|
|
.return_to_idle = true,
|
|
.steps = s_chouqian_steps,
|
|
.step_count = sizeof(s_chouqian_steps) / sizeof(s_chouqian_steps[0]),
|
|
};
|
|
ESP_LOGI(TAG, "Chouqian result asset=%s", result_asset);
|
|
return ESP_OK;
|
|
}
|
|
|
|
if (request->id == DOMAIN_EXPRESSION_WUXING) {
|
|
static platform_lcd_presentation_step_t s_wuxing_steps[3];
|
|
const size_t result_count = sizeof(s_wuxing_results) / sizeof(s_wuxing_results[0]);
|
|
size_t result_index = esp_random() % result_count;
|
|
if (request->wuxing_element_valid) {
|
|
if ((size_t)request->wuxing_element >= result_count) {
|
|
ESP_LOGW(TAG,
|
|
"Wuxing element out of range: %u (max %u)",
|
|
(unsigned int)request->wuxing_element,
|
|
(unsigned int)result_count);
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
result_index = (size_t)request->wuxing_element;
|
|
}
|
|
const domain_wuxing_result_t *result = &s_wuxing_results[result_index];
|
|
|
|
s_wuxing_steps[0] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = "qianzhiwuxing",
|
|
.frame_index = 0,
|
|
.icon_id = result->icon_id,
|
|
.icon_x = result->icon_x,
|
|
.icon_y = result->icon_y,
|
|
.delay_ms = DOMAIN_WUXING_QIANZHI_FRAME_MS,
|
|
};
|
|
s_wuxing_steps[1] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = result->asset_dir,
|
|
.frame_index = 0,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_WUXING_REVEAL_FRAME_MS,
|
|
};
|
|
s_wuxing_steps[2] = (platform_lcd_presentation_step_t) {
|
|
.asset_dir = result->asset_dir,
|
|
.frame_index = 1,
|
|
.icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON,
|
|
.delay_ms = DOMAIN_WUXING_HOLD_FRAME_MS,
|
|
};
|
|
|
|
*out_platform_request = (platform_lcd_presentation_request_t) {
|
|
.kind = PLATFORM_LCD_PRESENTATION_STEP_SEQUENCE,
|
|
.priority = 125,
|
|
.interrupt_current = true,
|
|
.return_to_idle = true,
|
|
.steps = s_wuxing_steps,
|
|
.step_count = sizeof(s_wuxing_steps) / sizeof(s_wuxing_steps[0]),
|
|
};
|
|
ESP_LOGI(TAG, "Wuxing result asset=%s", result->asset_dir);
|
|
return ESP_OK;
|
|
}
|
|
|
|
const domain_expression_catalog_entry_t *entry = domain_find_expression_entry(request->id);
|
|
if (entry == NULL) {
|
|
ESP_LOGW(TAG, "Unknown expression request id=%d trigger=%d",
|
|
(int)request->id,
|
|
(int)request->trigger);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
*out_platform_request = entry->platform_request;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t domain_runtime_request_expression(const domain_expression_request_t *request)
|
|
{
|
|
platform_lcd_presentation_request_t platform_request = {0};
|
|
esp_err_t resolve_err = domain_resolve_expression_platform_request(request, &platform_request);
|
|
if (resolve_err != ESP_OK) {
|
|
return resolve_err;
|
|
}
|
|
|
|
ESP_LOGI(TAG,
|
|
"Expression request id=%d trigger=%d",
|
|
(int)request->id,
|
|
(int)request->trigger);
|
|
return platform_lcd_request_presentation(&platform_request);
|
|
}
|
|
|
|
esp_err_t domain_runtime_preheat_expression(const domain_expression_request_t *request)
|
|
{
|
|
platform_lcd_presentation_request_t platform_request = {0};
|
|
esp_err_t resolve_err = domain_resolve_expression_platform_request(request, &platform_request);
|
|
if (resolve_err != ESP_OK) {
|
|
return resolve_err;
|
|
}
|
|
|
|
if (platform_request.kind != PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
ESP_LOGI(TAG,
|
|
"Expression preheat id=%d trigger=%d",
|
|
(int)request->id,
|
|
(int)request->trigger);
|
|
return platform_lcd_preheat_presentation(&platform_request);
|
|
}
|
|
|
|
esp_err_t domain_runtime_request_expression_blocking(const domain_expression_request_t *request,
|
|
uint32_t timeout_ms)
|
|
{
|
|
/* Snapshot the playback completion counter before queuing so the wait stays
|
|
* robust to the asynchronous request not having started yet. */
|
|
uint32_t since_count = platform_lcd_presentation_completion_count();
|
|
|
|
esp_err_t err = domain_runtime_request_expression(request);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
return platform_lcd_wait_presentation_completion(since_count, timeout_ms);
|
|
}
|