docs: 补充核心分层中文注释

This commit is contained in:
chenjintao
2026-09-29 18:18:44 +08:00
parent a77471a7b1
commit 9fd01bb61b
81 changed files with 1189 additions and 112 deletions
@@ -16,6 +16,12 @@
#if CONFIG_TQ_BATTERY_ENABLE
/*
* 电池后台策略层:domain 负责把原始电压稳定为状态,本任务负责提示音、充满确认和受控关机。
* GPIO ISR 只唤醒/通知,不读取 ADC 或播放音频;所有慢操作都在任务上下文完成。
* 关机一旦进入独立任务即视为不可逆流程,以免普通采样任务重复触发并争用电机/音频资源。
*/
#define BATTERY_WORKER_STACK_BYTES 4096
#define BATTERY_WORKER_PRIORITY 2
#define BATTERY_SUPPRESSED_RETRY_MS 1000
@@ -31,6 +37,11 @@
#define BATTERY_SHUTDOWN_TASK_STACK_BYTES 6144
#define BATTERY_SHUTDOWN_TASK_PRIORITY 5
#define BATTERY_SHUTDOWN_MOTOR_STOP_MS 2000
/*
* 多次确认用于排除负载突变导致的瞬时低压;紧急阈值只需两次以缩短保护延迟。
* 轻待机下采样间隔显著拉长,充电 GPIO 中断仍可及时唤醒状态更新。
*/
#define BATTERY_LOW_PROMPT_FILE "/littlefs/audio_prompts/battery_low_20.opus"
#define BATTERY_FULL_PROMPT_FILE "/littlefs/audio_prompts/charge_complete.opus"
@@ -17,6 +17,12 @@
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* 联网与配网用例编排:先尝试已保存凭据,失败后按配置切换门户或小程序 BLE 配网。
* 连接周期使用同一个递减超时预算,防止每次重试重置计时;成功提示只播放一次。
* BLE worker 通过单元素队列串行化请求,恢复重启标记放在 RTC 内存以跨软重启传递一次。
*/
#ifndef CONFIG_MULTIMODAL_CONNECT_TIMEOUT_MS
#define CONFIG_MULTIMODAL_CONNECT_TIMEOUT_MS 10000
#endif
@@ -66,6 +72,11 @@
#define CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS 100
#define CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC 0x5451424cU
/*
* 重试基准 300 ms 兼顾快速恢复与射频稳定;配网切换前先停传输并等待运行时静默,
* 是为了释放内部 RAM/DMA 连续块,而不只是检查总剩余堆。
*/
static const char *TAG = "control_plane_rt";
static bool s_wifi_success_prompt_played;
static bool s_provisioning_recovery_boot;
@@ -108,6 +119,7 @@ static void control_plane_request_provisioning_recovery_restart(
static esp_err_t control_plane_check_provisioning_memory_headroom(void)
{
/* BLE 初始化需要连续内部/DMA 块;总空闲堆达标但最大块不足时仍必须拒绝并走恢复启动。 */
domain_ota_memory_headroom_t headroom = {0};
esp_err_t err = domain_runtime_ota_get_memory_headroom(&headroom);
if (err != ESP_OK) {
@@ -216,6 +228,7 @@ static void control_plane_play_wifi_success_prompt(void)
static esp_err_t control_plane_connect_sta_with_feedback(int timeout_ms,
bool play_connecting_prompt)
{
/* 所有候选网络共享调用方预算,并把失败分类反馈给 UI;此函数不保存试连失败的凭据。 */
esp_err_t err = ESP_ERR_TIMEOUT;
TickType_t start_tick = xTaskGetTickCount();
@@ -320,6 +333,7 @@ static void control_plane_provisioning_event_callback(
const domain_wifi_provisioning_event_t *event,
void *user_data)
{
/* BLE 回调只复制凭据到单元素队列;联网验证与 NVS 提交在 worker 中串行完成。 */
(void)user_data;
if (event == NULL) {
return;
@@ -343,6 +357,7 @@ static bool control_plane_provisioning_stop_requested(void)
static void control_plane_provisioning_worker(void *arg)
{
/* 状态通知顺序也是小程序协议的一部分:connecting→got_ip→saving→ready,失败则给明确原因。 */
(void)arg;
bool provisioning_succeeded = false;
TickType_t last_wait_log_tick = xTaskGetTickCount();
@@ -543,6 +558,7 @@ esp_err_t control_plane_stop_mini_program_provisioning(int timeout_ms)
esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason)
{
/* 先静默语音/网络运行时释放内存;仍不足时写 RTC 标记并软重启进入最小资源配网路径。 */
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
(void)reason;
return ESP_ERR_NOT_SUPPORTED;
@@ -580,6 +596,7 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
const char *seed_password,
int connect_timeout_ms)
{
/* 启动只做一轮有界策略:已存凭据优先,失败再配网;持续重连归 runtime worker 所有。 */
ESP_LOGI(TAG,
"Boot Wi-Fi config SSID=%s timeout_ms=%d",
(seed_ssid != NULL && seed_ssid[0] != '\0') ? seed_ssid : "<empty>",
@@ -7,6 +7,10 @@
* command handlers, and UART replies in the control-plane layer. Reads
* `\r\n`-delimited commands from the console UART through the domain console
* bridge and replies with `OK`, `ERROR <reason>`, or one JSON line.
*
* 工厂串口 CLI 拥有行缓冲、命令解析和应答格式,但设备身份/Wi-Fi 的持久化仍经 domain 完成。
* worker 单实例运行;停止通过标志协作完成,不能在别的任务中直接删除它,否则可能留下半条
* UART 应答或未提交的配置。所有输入都有固定上限,超长行会整体拒绝而不是截断后误执行。
*/
#include "control_plane.h"
@@ -46,6 +50,8 @@
#define FACTORY_CLI_START_WAIT_STEP_MS 20
#define FACTORY_CLI_START_GATE_WAIT_MS 5000
/* 512 字节覆盖普通命令,独立 3 KiB Wi-Fi JSON 上限用于凭据列表;二者不可互相替代。 */
#ifndef CONFIG_TQ_LOCAL_TASK_CORE
#define CONFIG_TQ_LOCAL_TASK_CORE 1
#endif
@@ -18,6 +18,12 @@
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "sdkconfig.h"
/*
* 每日运势远端用例:读取 domain 中的出生档案,构造请求并把验证后的结果交回 domain 持久化。
* 网络响应属于临时缓冲区,解析后全部复制到定长领域结构;服务端日期必须与请求目标一致,
* 防止缓存或跨时区响应污染“当天结果”。启用 Device-JWT 时还会校验 fortune 所需 scope。
*/
#include "soc/soc_caps.h"
#ifndef CONFIG_FORTUNE_API_URL
@@ -43,6 +49,8 @@
static const char *TAG = "cp_fortune";
static const int kMaxHttpBody = 4096;
/* 返回字段规模固定,4 KiB 是协议护栏;超限视为服务端异常而不是尝试部分解析。 */
static void *fortune_psram_first_calloc(size_t count, size_t size)
{
if (count == 0U || size == 0U || count > (SIZE_MAX / size)) {
@@ -20,6 +20,12 @@
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* 物理按键输入前端:ISR 只把 GPIO 编号投入队列,任务随后完成去抖、单双击和长按判定,
* 最终发布统一 system event。这样所有产品动作都离开 ISR,且多个按键共享同一时间语义。
* 配置表是 GPIO、有效电平和事件类型的唯一映射;新增按键应先检查引脚冲突与上下拉组合。
*/
#ifndef CONFIG_TQ_KEY_INPUT_ENABLE
#define CONFIG_TQ_KEY_INPUT_ENABLE 1
#endif
@@ -50,6 +56,11 @@
#ifndef CONFIG_TQ_KEY_INPUT_POLL_MS
#define CONFIG_TQ_KEY_INPUT_POLL_MS 20
/*
* 20 ms 与去抖周期一致;800 ms 长按避免普通点击误触,300 ms 双击窗口兼顾可操作性,
* 双击后再抑制 450 ms 是为了不把第二次释放沿解释成新的单击序列。
*/
#endif
#if !defined(CONFIG_TQ_PTT_MODE_HOLD_TO_TALK) && !defined(CONFIG_TQ_PTT_MODE_CLICK_TO_TALK)
@@ -237,6 +248,7 @@ static bool control_plane_key_level_is_pressed(const control_plane_key_config_t
static void IRAM_ATTR control_plane_key_gpio_isr_handler(void *arg)
{
/* ISR 只能使用 ISR-safe 队列 API,不能记录日志、读取复杂状态或直接调用业务服务。 */
const gpio_num_t gpio_num = (gpio_num_t)(intptr_t)arg;
QueueHandle_t queue = s_gpio_irq_queue;
if (queue == NULL) {
@@ -292,6 +304,7 @@ static esp_err_t control_plane_configure_key_gpio(const control_plane_key_config
static esp_err_t control_plane_validate_key_configs(void)
{
/* 启动前一次性拒绝重复 GPIO 和互斥的上下拉设置,避免运行期出现不可解释的幽灵按键。 */
for (size_t i = 0; i < sizeof(s_key_configs) / sizeof(s_key_configs[0]); ++i) {
const control_plane_key_config_t *config = &s_key_configs[i];
if (config->pullup_enabled && config->pulldown_enabled) {
@@ -356,6 +369,7 @@ static void control_plane_handle_debounced_transition(const control_plane_key_co
int level,
int64_t now_ms)
{
/* 物理边沿只更新按键状态机;单击要等双击窗口关闭后才最终发布。 */
if (state->wait_inactive_before_press) {
if (pressed) {
state->debounced_pressed = false;
@@ -435,6 +449,7 @@ static void control_plane_handle_debounced_transition(const control_plane_key_co
static void control_plane_process_key_timers(void)
{
/* 定时扫描补足没有新 GPIO 中断时的长按触发和延迟单击确认。 */
const int64_t now_ms = control_plane_event_timestamp_ms();
for (size_t i = 0; i < sizeof(s_key_configs) / sizeof(s_key_configs[0]); ++i) {
@@ -17,6 +17,12 @@
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* 轻待机协调器:根据最后活动时间和系统忙闲状态进入低功耗策略,但不执行深睡或重启。
* transition_lock 串行化进入/退出,防止按键唤醒与定时任务同时改背光、音频、电机和 Wi-Fi。
* 每项节能动作都可配置,唤醒按相反顺序恢复;活动时间只由明确的用户/业务事件刷新。
*/
#ifndef CONFIG_TQ_LIGHT_STANDBY_ENABLE
#define CONFIG_TQ_LIGHT_STANDBY_ENABLE 0
#endif
@@ -86,6 +92,8 @@
#define CONTROL_PLANE_LIGHT_STANDBY_STOP_POLL_MS 20U
#define CONTROL_PLANE_LIGHT_STANDBY_TRANSITION_WAIT_MS 1000U
/* 每秒复查资格即可满足分钟级空闲阈值,同时避免忙时高频查询多模态和 OTA 状态。 */
#if CONFIG_TQ_LOCAL_TASK_CORE < 0
#define CONTROL_PLANE_LIGHT_STANDBY_TASK_CORE tskNO_AFFINITY
#else
@@ -171,6 +179,7 @@ static void control_plane_light_standby_log_transition(
static bool control_plane_light_standby_runtime_is_eligible(void)
{
/* 有语音轮次、OTA、配网或其他前台交互时禁止进入,空闲时长只是必要条件而非充分条件。 */
if (!control_plane_voice_turn_is_idle() ||
!control_plane_ota_voice_services_allowed() ||
control_plane_is_mini_program_provisioning_running()) {
@@ -243,6 +252,7 @@ static esp_err_t control_plane_light_standby_apply_runtime_policy(bool standby)
static esp_err_t control_plane_light_standby_apply_wake_policy(void)
{
/* 先恢复业务所需资源再宣布 ACTIVE,调用者返回时即可安全发起屏幕、音频或网络动作。 */
esp_err_t first_error = control_plane_light_standby_apply_runtime_policy(false);
#if CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT
@@ -274,6 +284,7 @@ static esp_err_t control_plane_light_standby_apply_wake_policy(void)
static esp_err_t control_plane_light_standby_apply_entry_policy(void)
{
/* 进入顺序尽量先停止高层活动再降低硬件能力;中途失败会由唤醒策略回滚已应用项。 */
#if CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD
esp_err_t motor_err = domain_direction_release_motor_hold(
CONTROL_PLANE_LIGHT_STANDBY_MOTOR_RELEASE_WAIT_MS);
@@ -365,6 +376,7 @@ static bool control_plane_light_standby_entry_is_current(
static void control_plane_light_standby_enter(uint64_t now_ms)
{
/* 二次核对 activity generation,防止准备待机期间刚发生的用户活动被旧定时结果覆盖。 */
if (!control_plane_light_standby_lock_transition()) {
ESP_LOGW(TAG, "Enter transition lock timed out");
return;
@@ -672,6 +684,7 @@ esp_err_t control_plane_light_standby_stop(int timeout_ms)
esp_err_t control_plane_light_standby_record_activity(
control_plane_light_standby_activity_t activity)
{
/* 更新时间与唤醒共用转换锁,确保新的 deadline 不会和正在提交的 STANDBY 状态交叉。 */
if (activity <= CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_UNSPECIFIED ||
activity > CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_EXTERNAL) {
return ESP_ERR_INVALID_ARG;
@@ -14,9 +14,16 @@
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
/*
* 网络反馈是一个小型聚合状态机:分别记录 Wi-Fi 与服务端可用性,只在持续故障或恢复边界提示。
* 状态在临界区更新,播放提示音必须在锁外进行;否则音频回调重入会阻塞其他网络事件。
* require_ready() 同时承担动作门禁,让“无法执行”的原因和播报保持一致。
*/
#define CONTROL_PLANE_SERVER_DISCONNECT_GRACE_MS 3000U
#define CONTROL_PLANE_NETWORK_PROMPT_COOLDOWN_MS 5000U
/* 3 秒宽限过滤 WebSocket 短重连,5 秒冷却防止 Wi-Fi 与服务端事件交替造成提示音轰炸。 */
#define CONTROL_PLANE_WIFI_INTERRUPTED_PROMPT_FILE \
"/littlefs/audio_prompts/network_interrupted_repairing.opus"
#define CONTROL_PLANE_WIFI_ACTION_BLOCKED_PROMPT_FILE \
@@ -20,6 +20,12 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* OTA 策略状态机:周期报告版本、等待服务端指令、检查电量/内存/业务空闲门禁,再独占语音资源
* 执行安装。下载与分区写入由 domain/platform 完成;本层负责“现在是否适合升级”及失败退避。
* snapshot 是面向诊断的值拷贝,任务停止采用协作标志,安装关键区不能被外部强删任务打断。
*/
#include "soc/soc_caps.h"
#ifndef CONFIG_TQ_OTA_ENABLE
@@ -80,6 +86,11 @@
#define CONTROL_PLANE_OTA_TASK_STACK_WORDS \
((CONFIG_TQ_OTA_TASK_STACK_BYTES + sizeof(StackType_t) - 1U) / sizeof(StackType_t))
/*
* 常规轮询与确认均为 6 小时,避免设备群频繁访问服务;低电量以 60 秒重查,因为充电状态可能
* 很快变化。除总内存外还检查 DMA 最大连续块,防止“剩余很多但无法分配音频/网络缓冲”。
*/
typedef struct {
bool acquired;
bool restore_multimodal;
@@ -155,6 +166,7 @@ static void control_plane_ota_set_state(control_plane_ota_state_t state,
esp_err_t error,
int http_status)
{
/* snapshot 更新集中在同一临界区,诊断读取不会观察到 state 与 error/http 状态跨代混合。 */
portENTER_CRITICAL(&s_ota_lock);
s_ota_snapshot.state = state;
s_ota_snapshot.last_error = error;
@@ -203,6 +215,7 @@ static bool control_plane_ota_wait_interruptible(uint32_t wait_ms)
static uint32_t control_plane_ota_retry_delay(uint32_t failures)
{
/* 采用饱和倍增而非直接移位,防止连续失败次数过大产生溢出后的短延迟。 */
uint64_t delay_ms = CONFIG_TQ_OTA_RETRY_BASE_MS;
for (uint32_t i = 1U; i < failures && delay_ms < CONFIG_TQ_OTA_RETRY_MAX_MS; ++i) {
delay_ms *= 2U;
@@ -320,6 +333,7 @@ static bool control_plane_ota_cancel(void *user_data)
static bool control_plane_ota_runtime_is_idle(void)
{
/* “空闲”要求语音轮次和关键业务都可让渡,单看 WebSocket 是否连接不足以判断。 */
if (!control_plane_voice_turn_is_idle()) {
return false;
}
@@ -420,6 +434,7 @@ static esp_err_t control_plane_ota_acquire_voice_window(
bool allow_active_teardown,
control_plane_ota_voice_window_t *window)
{
/* 先设置全局门禁阻止新会话,再停止已存在的语音服务;失败时由 window 记录内容精确回滚。 */
if (window == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -505,6 +520,7 @@ static esp_err_t control_plane_ota_acquire_voice_window(
static void control_plane_ota_release_voice_window(control_plane_ota_voice_window_t *window)
{
/* 只恢复本函数实际停止过的服务,避免把调用前就关闭的功能意外启动。 */
if (window == NULL || !window->acquired) {
return;
}
@@ -591,6 +607,7 @@ static esp_err_t control_plane_ota_prepare_report(domain_ota_report_request_t *r
static esp_err_t control_plane_ota_check_once(domain_ota_report_response_t *response,
bool force_initial_jwt_refresh)
{
/* 一次检查包含上报、候选校验和确认判定,但不在资源/电量门禁之前开始下载。 */
if (response == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -646,6 +663,7 @@ static esp_err_t control_plane_ota_check_once(domain_ota_report_response_t *resp
static void control_plane_ota_worker(void *arg)
{
/* 所有等待均切片为可中断睡眠,使 stop 不必等完整 6 小时轮询周期。 */
(void)arg;
if (ulTaskNotifyTake(pdTRUE,
pdMS_TO_TICKS(CONTROL_PLANE_OTA_START_GATE_MS)) == 0U) {
@@ -929,6 +947,7 @@ esp_err_t control_plane_start_ota_service(void)
esp_err_t control_plane_stop_ota_service(int timeout_ms)
{
/* 安装临界区优先协作取消;只有超出有界等待后才走确定性清理,避免写分区中途强删任务。 */
if (timeout_ms < 0) {
return ESP_ERR_INVALID_ARG;
}
@@ -26,6 +26,12 @@
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* 远程日志采用“日志 hook -> 内存环形队列 -> JSONL 文件 -> 批量上传”的四段式所有权链。
* hook 只复制有限长度文本,绝不联网或写文件;worker 独占文件轮转和上传,成功后才删除文件。
* 本地控制台输出可保留,递归保护防止本模块自己的上传日志再次进入 hook。
*/
#ifndef CONFIG_TQ_REMOTE_LOG_ENABLE
#define CONFIG_TQ_REMOTE_LOG_ENABLE 0
#endif
@@ -94,6 +100,11 @@
#define REMOTE_LOG_POLL_MS 200
#define REMOTE_LOG_VOICE_STARTUP_GRACE_MS 60000
/*
* 队列和单条长度均有界,压力过大时记录 dropped_count 而非阻塞产生日志的实时任务。
* 语音启动后留 60 秒宽限,避免日志 TLS/上传与首个会话争夺内部 RAM 和网络带宽。
*/
#if CONFIG_TQ_REMOTE_LOG_ENABLE
typedef struct {
@@ -240,6 +251,7 @@ static bool should_capture_level(char level)
static void enqueue_line(const char *line, bool truncated)
{
/* hook 上下文不等待空间:队列满时累计丢弃数,稍后作为合成记录写入,保留压力证据。 */
if (line == NULL || line[0] == '\0' || s_queue == NULL) {
return;
}
@@ -384,6 +396,7 @@ static void sanitize_value(char *text, const char *key)
static void sanitize_log_line(char *text)
{
/* 落盘前脱敏常见认证字段;这是最后一道保护,不替代调用方避免打印秘密。 */
sanitize_value(text, "password");
sanitize_value(text, "Password");
sanitize_value(text, "pwd");
@@ -557,6 +570,7 @@ static size_t count_pending_files(void)
static void trim_old_files(void)
{
/* 超过文件上限时只删最旧已封存批次,当前追加文件和内存队列不受影响。 */
uint32_t sequences[CONFIG_TQ_REMOTE_LOG_MAX_FILES + 8] = {0};
size_t count = collect_files(sequences, sizeof(sequences) / sizeof(sequences[0]));
while (count > CONFIG_TQ_REMOTE_LOG_MAX_FILES) {
@@ -618,6 +632,7 @@ static esp_err_t append_entry_to_file(const remote_log_entry_t *entry)
static void drain_memory_queue_to_flash(void)
{
/* 先从队列取得值拷贝再写 flash,文件 I/O 期间不占用队列临界区。 */
remote_log_entry_t entry = {0};
while (dequeue_entry(&entry)) {
if (append_entry_to_file(&entry) != ESP_OK) {
@@ -631,6 +646,7 @@ static void drain_memory_queue_to_flash(void)
static bool realtime_path_busy(void)
{
/* 音频/OTA 等实时路径繁忙时仍允许落盘,但推迟 TLS 上传以减少带宽和堆竞争。 */
#if CONFIG_TQ_LOCAL_WAKE_ENABLE
if (!control_plane_ota_voice_services_allowed() ||
!control_plane_voice_turn_is_idle()) {
@@ -861,6 +877,7 @@ static esp_err_t build_upload_body(uint32_t sequence,
static bool upload_one_file(uint32_t sequence)
{
/* 只有服务端明确接受后才删除源文件;传输或状态码失败均保留供下次重试。 */
uint32_t dropped = snapshot_and_clear_dropped_count();
char *body = (char *)remote_log_psram_malloc(REMOTE_LOG_UPLOAD_BODY_MAX);
char *response = (char *)remote_log_psram_malloc(REMOTE_LOG_RESPONSE_MAX);
@@ -1106,6 +1123,7 @@ static void uninstall_hook(void)
static void remote_log_task(void *arg)
{
/* worker 独占 flash 文件和上传顺序:先排空内存,再按序上传,停止前尽力完成最后一次落盘。 */
(void)arg;
esp_err_t mount_err = domain_runtime_mount_logfs();
@@ -1215,6 +1233,7 @@ esp_err_t control_plane_remote_log_flush_now(void)
esp_err_t control_plane_remote_log_clear(void)
{
/* 清理仅作用于远程日志目录和内存队列,不触碰系统日志分区或 coredump 本体。 */
portENTER_CRITICAL(&s_lock);
s_queue_read = 0U;
s_queue_count = 0U;
@@ -10,6 +10,10 @@
static const char *TAG = "cp_reset";
/*
* 恢复出厂联网状态的单一路径:先经 domain 清除持久化凭据,再请求平台重启。
* 即使清除失败也会记录错误并继续重启,使设备下次启动仍能进入现有的恢复/配网判定流程。
*/
void control_plane_reset_wifi_provisioning_and_restart(void)
{
ESP_LOGW(TAG, "Wi-Fi reset requested; clearing saved credentials");
@@ -20,6 +20,12 @@
#include "freertos/task.h"
#include "sdkconfig.h"
/*
* 控制面长期任务的集中装配与监督点。它拥有 LCD 渲染、Wi-Fi 状态、模型命令和任务监控 worker,
* 并按依赖顺序启动/逆序停止外围服务。队列只传递值类型命令;任何指针载荷都必须明确复制。
* 每个 task handle 同时承担“已启动”标志,创建/退出更新受生命周期门禁保护,避免重复 worker。
*/
#ifndef CONFIG_TQ_LCD_RENDER_TASK_STACK_BYTES
#define CONFIG_TQ_LCD_RENDER_TASK_STACK_BYTES (8U * 1024U)
#endif
@@ -58,6 +64,12 @@
#define CONTROL_PLANE_MEMORY_INTERNAL_DROP_BYTES 512U
#define CONTROL_PLANE_MEMORY_DMA_LARGEST_DROP_BYTES 256U
/*
* Wi-Fi 重连使用 5~60 秒指数退避,既快速处理偶发断链又避免离线时持续占用射频。
* PTT 的 80 ms 轮询追求按键响应;普通 Wi-Fi 状态每秒采样即可。内存告警看变化量而不仅是
* 绝对值,便于在任务监控日志中发现缓慢泄漏和最大连续块碎片化。
*/
#if (configSUPPORT_STATIC_ALLOCATION == 1) && \
((defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) || \
(defined(CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY) && CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY))
@@ -325,6 +337,7 @@ static unsigned control_plane_heap_contiguous_percent(const multi_heap_info_t *i
static void control_plane_memory_monitor_sample(TickType_t now_tick, bool force)
{
/* 同时采样内部堆、PSRAM 和最大 DMA 块;后者下降往往比总空闲量更早暴露音频分配风险。 */
bool light_standby;
portENTER_CRITICAL(&s_runtime_lock);
light_standby = s_runtime_diagnostics_light_standby;
@@ -534,6 +547,7 @@ static const char *ptt_state_name(ptt_state_t state)
static void control_plane_ptt_transition_locked(ptt_state_t next_state, const char *reason)
{
/* 所有 PTT 状态迁移集中记录,便于把按键、网络和会话异步事件还原为一条时间线。 */
if (s_ptt_runtime.state == next_state) {
return;
}
@@ -876,6 +890,7 @@ static void control_plane_delete_model_cmd_queue(QueueHandle_t queue, bool queue
static esp_err_t control_plane_ensure_model_cmd_queue(void)
{
/* 先完整创建队列再发布句柄;其他任务永远看不到半初始化对象。 */
QueueHandle_t created_queue = NULL;
bool created_queue_with_caps = false;
@@ -1123,6 +1138,7 @@ static void control_plane_on_model_command(control_plane_model_command_type_t co
bool value_index_valid,
void *user_data)
{
/* 模型回调只复制白名单命令并入队,电机/LCD 等慢操作由 model_command_task 串行执行。 */
(void)user_data;
QueueHandle_t queue = NULL;
@@ -1269,6 +1285,7 @@ static esp_err_t control_plane_show_chouqian_expression(size_t result_index, boo
static void control_plane_model_command_task(void *arg)
{
/* 串行消费保证两个模型动作不会同时争用方向、表情和提示音资源。 */
(void)arg;
QueueHandle_t queue = control_plane_get_model_cmd_queue();
@@ -1633,6 +1650,7 @@ static void control_plane_request_wifi_state_expression(domain_expression_id_t i
static uint32_t control_plane_next_wifi_reconnect_delay_ms(uint32_t current_delay_ms)
{
/* 成功路径在调用方重置为基准;失败按倍增并封顶,避免整数溢出和永久过长等待。 */
if (current_delay_ms == 0U) {
return CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
}
@@ -1897,6 +1915,7 @@ static bool control_plane_service_ptt_lifecycle(void)
static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_attempt_tick)
{
/* 这是 PTT 唯一推进器:按键只改变意图,网络/会话条件满足后才真正建链和开麦。 */
if (control_plane_service_ptt_lifecycle()) {
return;
}
@@ -2057,6 +2076,7 @@ static void control_plane_start_wall_clock_sync(void)
static void control_plane_wifi_state_task(void *arg)
{
/* 汇总 STA、服务端、配网和 PTT 状态,负责重连与服务启停;不把策略分散到平台事件回调。 */
(void)arg;
ESP_LOGI(TAG, "Wi-Fi state monitor task started");
@@ -2451,6 +2471,7 @@ static time_t control_plane_next_local_midnight(time_t now)
static esp_err_t control_plane_refresh_daily_fortune(const char *target_date)
{
/* 远端成功后先持久化领域结果,再更新方向/表情,使重启后看到的状态与已呈现结果一致。 */
domain_fortune_result_t result = {0};
esp_err_t err = control_plane_fortune_fetch(target_date, &result);
if (err != ESP_OK) {
@@ -2617,6 +2638,7 @@ esp_err_t control_plane_start_lcd_render_worker(void)
esp_err_t control_plane_start_runtime_workers(void)
{
/* 启动顺序遵循生产者/消费者依赖;失败路径调用统一 stop,避免留下孤儿任务。 */
control_plane_set_runtime_stop_requested(false);
control_plane_network_feedback_reset();
portENTER_CRITICAL(&s_runtime_lock);
@@ -2792,6 +2814,7 @@ void control_plane_stop_event_observer_runtime(void)
void control_plane_stop_runtime_workers(void)
{
/* 先发布全局停止意图,再逆序唤醒并回收任务;最后才销毁队列和回调注册。 */
control_plane_set_runtime_stop_requested(true);
(void)control_plane_light_standby_stop(CONTROL_PLANE_TASK_STOP_WAIT_MS);
(void)control_plane_battery_service_stop(CONTROL_PLANE_TASK_STOP_WAIT_MS);
@@ -16,6 +16,12 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
/*
* 系统事件任务是按键等离散输入的串行化决策点。生产者只投递值拷贝事件,消费者按 family/type
* 调度 PTT、校准、木鱼等用例;因此耗时动作不会出现在 GPIO ISR 中,也不会并发解释同一次输入。
* 队列满代表用户输入已过时,调用方得到明确错误而不是无限阻塞实时任务。
*/
#include "sdkconfig.h"
#ifndef CONFIG_TQ_SYSTEM_EVENT_ENABLE
@@ -35,6 +41,8 @@
#endif
#define CONTROL_PLANE_PTT_HOLD_STEP_MS 80
/* 长按 PTT 以 80 ms 小步等待,既能及时感知释放,也能周期检查会话已失败或被取消。 */
#define CONTROL_PLANE_MUYU_PROMPT_FILE "muyu.opus"
#ifndef CONFIG_TQ_LOCAL_TASK_CORE