Files
lucky-cat/components/domain/src/runtime/domain_runtime_adapters.c
T

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);
}