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
@@ -17,6 +17,11 @@
#if CONFIG_TQ_BATTERY_ENABLE
/*
* 电量等级使用迟滞阈值并连续确认3次,避免临界点抖动。单次电压跳变超过300mV时,
* 必须在100mV窗口内再次出现才接受;滤波采用旧值80%、新值20%。这些规则只生成
* domain 状态,不决定关机、提示或 LCD 策略。
*/
#if CONFIG_TQ_BATTERY_LOW_EXIT_PERCENT <= CONFIG_TQ_BATTERY_LOW_ENTER_PERCENT
#error "Battery low recovery threshold must exceed the entry threshold"
#endif
@@ -110,6 +115,7 @@ static domain_battery_charge_state_t map_charge_state(
static uint8_t voltage_to_percentage(int voltage_mv)
{
/* 在实测放电曲线相邻点之间线性插值,而不是用单一线性电压公式。 */
if (voltage_mv <= s_discharge_curve[0].voltage_mv) {
return s_discharge_curve[0].percentage;
}
@@ -131,6 +137,7 @@ static uint8_t voltage_to_percentage(int voltage_mv)
static bool accept_voltage_step(int voltage_mv)
{
/* 大跳变需要连续候选确认,用于过滤电机/扬声器负载造成的瞬时压降。 */
if (!s_filter_initialized || abs(voltage_mv - s_filtered_mv) <= BATTERY_MAX_ACCEPTED_STEP_MV) {
s_jump_candidate_count = 0U;
return true;
@@ -230,6 +237,7 @@ esp_err_t domain_battery_init(void)
esp_err_t domain_battery_sample(void)
{
/* platform 样本无效或高负载抑制时保留最后有效状态,只更新质量和失败计数。 */
esp_err_t err = domain_battery_init();
if (err != ESP_OK) {
return err;
@@ -14,6 +14,10 @@
static const char *TAG = "domain_cal";
/*
* 20秒是完整校准预算,单步最多1.5秒。扫描不超过330度;命中限位后分最多90度
* 的块回到180度机械中心,再把逻辑角设为0。所有阶段共享同一绝对截止时间。
*/
#define DOMAIN_CALIBRATION_TIMEOUT_MS 20000
#define DOMAIN_CALIBRATION_STEP_TIMEOUT_MS 1500
#define DOMAIN_TURNTABLE_LIMIT_SEARCH_MAX_DEG 330.0f
@@ -96,6 +100,7 @@ static bool calibration_settle(const domain_direction_calibration_t *calibration
static esp_err_t calibration_rotate_and_track(float delta, int rotate_speed_us, int timeout_ms)
{
/* 只有物理运动成功才同步更新 domain 角度,避免软件/机械位置分叉。 */
if (timeout_ms <= 0) {
return ESP_ERR_TIMEOUT;
}
@@ -139,6 +144,7 @@ static esp_err_t calibration_return_from_limit(float search_direction,
esp_err_t domain_calibrate_with_timeout(const domain_direction_calibration_t *config, int timeout_ms)
{
/* 完整扫描期间持有 rotation mutex,普通方向命令不能插入校准序列。 */
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : DOMAIN_CALIBRATION_TIMEOUT_MS;
TickType_t timeout_ticks = pdMS_TO_TICKS(effective_timeout_ms);
if (timeout_ticks == 0) {
@@ -14,6 +14,7 @@ static domain_direction_calibration_t s_calibration = {0};
static bool s_calibration_configured = false;
static portMUX_TYPE s_calibration_lock = portMUX_INITIALIZER_UNLOCKED;
/* 校准配置是受自旋锁保护的内存 SSOT;读写按值复制,不暴露内部指针。 */
void domain_calibration_store_set(const domain_direction_calibration_t *config)
{
portENTER_CRITICAL(&s_calibration_lock);
@@ -6,6 +6,7 @@
#include "domain_internal.h"
/* 角度统一归一化到 (-180, 180],为目录匹配和最短旋转提供唯一表示。 */
float domain_angle_normalize(float angle)
{
while (angle > 180.0f) {
@@ -19,6 +20,7 @@ float domain_angle_normalize(float angle)
float domain_angle_shortest_rotation(float current, float target)
{
/* 结果限制到 [-180, 180],正负号决定方向,避免不必要的整圈运动。 */
float diff = target - current;
while (diff > 180.0f) {
@@ -9,6 +9,7 @@
#include <ctype.h>
#include <string.h>
/* 方向目录是名称、别名和标准角度的唯一事实来源;解析不修改当前转盘状态。 */
static const domain_direction_entry_t s_default_directions[] = {
{"南", "S", 0.0f},
{"西南", "SW", 45.0f},
@@ -61,6 +62,7 @@ static void trimmed_span(const char *input, const char **start_out, size_t *len_
static bool equals_trimmed(const char *input, const char *token, bool case_insensitive)
{
/* 忽略输入首尾空白;英文别名大小写不敏感,中文按 UTF-8 字节精确比较。 */
const char *start = NULL;
size_t len = 0;
size_t token_len = 0;
@@ -140,6 +142,7 @@ bool domain_direction_catalog_find_by_name(const char *input, size_t *out_index)
bool domain_direction_catalog_find_by_angle(float angle, size_t *out_index)
{
/* 先归一化角度,再在浮点容差内匹配目录标准方向。 */
for (size_t i = 0; i < domain_direction_catalog_count(); i++) {
const domain_direction_entry_t *entry = domain_direction_catalog_get(i);
if (entry == NULL) {
@@ -15,6 +15,10 @@
static const char *TAG = "domain";
/*
* 默认速度和超时是一次方向动作的产品基线;校准扫描以5度步进、最多330度搜索,
* 避免机械结构在找不到限位时持续整圈运动。修改需同步验证电机和限位开关。
*/
#define DOMAIN_DEFAULT_ROTATE_SPEED_US 1500
#define DOMAIN_ROTATE_TIMEOUT_MS 8000
#define DOMAIN_STATE_LOCK_TIMEOUT_MS 2000
@@ -26,6 +30,7 @@ static const char *TAG = "domain";
#define DOMAIN_DEFAULT_CALIBRATION_SETTLE_DELAY_MS 10
static float s_current_angle = 0.0f;
/* 状态锁保护快照,rotation mutex 串行化整个物理运动;下层电机调用不持有状态锁。 */
static int s_rotate_speed_us = DOMAIN_DEFAULT_ROTATE_SPEED_US;
static bool s_initialized;
static SemaphoreHandle_t s_state_mutex;
@@ -169,6 +174,7 @@ void domain_direction_unlock_state(void)
esp_err_t domain_init(const domain_direction_axis_config_t *config,
const domain_direction_calibration_t *calibration)
{
/* platform 初始化成功后才发布 initialized,避免查询者看到半初始化配置。 */
esp_err_t runtime_err = domain_runtime_platform_init();
if (runtime_err != ESP_OK) {
return runtime_err;
@@ -301,6 +307,7 @@ int domain_get_rotate_speed_us(void)
esp_err_t domain_rotate_to_angle_with_timeout(float target_angle, int timeout_ms)
{
/* 计算最短角差并使用剩余总预算驱动电机;成功后才更新角度 SSOT。 */
TickType_t start_tick = xTaskGetTickCount();
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : DOMAIN_ROTATE_TIMEOUT_MS;
@@ -365,6 +372,7 @@ esp_err_t domain_rotate_to_angle_with_timeout(float target_angle, int timeout_ms
esp_err_t domain_apply_rotation_delta(float delta)
{
/* 相对运动仍走同一互斥路径,完成后把累计角度归一化。 */
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
return ESP_ERR_TIMEOUT;
}
@@ -15,8 +15,7 @@
static const char *TAG = "domain_fortune_store";
/* NVS namespace + keys (<=15 chars). The platform layer owns the raw storage;
* the domain serializes its typed records as opaque struct blobs. */
/* NVS 命名空间/键不超过15字符。platform 只存原始 blob,domain 拥有结构 schema。 */
#define DOMAIN_FORTUNE_NVS_NAMESPACE "fortune"
#define DOMAIN_FORTUNE_KEY_PROFILE "profile"
#define DOMAIN_FORTUNE_KEY_RESULT "result"
@@ -33,6 +32,7 @@ static bool s_fortune_result_loaded;
static bool s_fortune_result_valid;
static portMUX_TYPE s_fortune_result_lock = portMUX_INITIALIZER_UNLOCKED;
/* 内存快照采用惰性加载;loaded 与 valid 分开,避免每次查询重复访问 NVS。 */
static bool domain_birth_profile_is_valid(const domain_birth_profile_t *profile)
{
return profile != NULL &&
@@ -67,6 +67,7 @@ void domain_runtime_register_birth_profile_observer(domain_birth_profile_observe
esp_err_t domain_birth_profile_store_set(const domain_birth_profile_t *profile)
{
/* 先完成持久化,再发布内存快照和锁外观察者通知,确保通知代表已落盘状态。 */
if (!domain_birth_profile_is_valid(profile)) {
return ESP_ERR_INVALID_ARG;
}
@@ -105,6 +106,7 @@ esp_err_t domain_birth_profile_store_set(const domain_birth_profile_t *profile)
bool domain_birth_profile_store_get(domain_birth_profile_t *out_profile)
{
/* 首次查询从 NVS 加载,缺失或旧记录无效时缓存“无有效资料”结果。 */
if (out_profile == NULL) {
return false;
}
@@ -18,6 +18,10 @@
static const char *TAG = "domain_user_web";
static const char *kFortuneTimezone = "Asia/Shanghai";
/*
* 本服务拥有路由、JSON schema、日期校验和嵌入页面;platform 只提供有界 HTTP
* request/response 缓冲。handler 不保存 request body 指针,返回前完成全部解析。
*/
extern const uint8_t user_config_html_start[]
asm("_binary_user_config_html_start");
extern const uint8_t user_config_html_end[]
@@ -30,6 +34,7 @@ static bool is_leap_year(int year)
static bool is_valid_date(int year, int month, int day)
{
/* 接受1900-2100年并执行完整闰年/月份天数校验,拒绝只满足数值范围的无效日期。 */
static const uint8_t days_per_month[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
};
@@ -200,6 +205,7 @@ static bool parse_profile(const uint8_t *body,
domain_birth_profile_t *out_profile,
const char **out_error)
{
/* 所有必填字段先解析到局部快照,完整校验通过后才交给 store,避免部分更新。 */
if (body == NULL || body_len == 0U) {
*out_error = "Request body is empty";
return false;
@@ -333,6 +339,7 @@ static esp_err_t user_config_http_handler(
platform_lan_http_response_t *response,
void *user_data)
{
/* 路由匹配与 method 检查集中在此处,未知路径和错误方法返回明确 HTTP 状态。 */
(void)user_data;
if (strcmp(request->path, "/") == 0) {
@@ -19,6 +19,10 @@
#include "platform.h"
#include "sdkconfig.h"
/*
* 本地命令链路:platform PCM observer -> 有界 PCM 队列 -> domain MultiNet 推理任务
* -> generation 标记的检测事件。观察者从不阻塞采集;队列满时计数并丢帧。
*/
#ifndef CONFIG_TQ_LOCAL_COMMAND_TASK_STACK_BYTES
#define CONFIG_TQ_LOCAL_COMMAND_TASK_STACK_BYTES 8192
#endif
@@ -47,6 +51,10 @@
#define DOMAIN_LOCAL_COMMAND_MODEL_NAME "mn7_cn"
#define DOMAIN_LOCAL_COMMAND_TEXT "喵掌柜"
#define DOMAIN_LOCAL_COMMAND_GRAPH_TEXT "miao zhang gui"
/*
* 8帧队列吸收短期调度抖动;每帧最多512样本。控制/锁等待有界,统计每2秒汇总,
* 任务事件位区分请求停止、已停止、运行和暂停,避免只靠 task handle 猜测状态。
*/
#define DOMAIN_LOCAL_COMMAND_PCM_QUEUE_LEN 8U
#define DOMAIN_LOCAL_COMMAND_PCM_MAX_SAMPLES 512U
#define DOMAIN_LOCAL_COMMAND_QUEUE_WAIT_MS 100U
@@ -203,6 +211,7 @@ static void local_command_pcm_observer(const int16_t *pcm,
uint32_t sample_rate_hz,
void *user_data)
{
/* 只复制可容纳的数据并立即返回;推理、日志和回调都留给 domain 任务。 */
(void)user_data;
bool admitted = false;
portENTER_CRITICAL(&s_pcm_ingress_lock);
@@ -425,6 +434,7 @@ static bool local_command_apply_control(size_t *fill_samples)
static void local_command_publish_detection(float probability)
{
/* confidence 转为万分比整数,generation 防止暂停/恢复前的迟到结果被误用。 */
domain_local_command_event_callback_t callback = NULL;
void *callback_user_data = NULL;
domain_local_command_event_t event = {
@@ -692,6 +702,7 @@ esp_err_t domain_runtime_local_command_start(
domain_local_command_event_callback_t callback,
void *user_data)
{
/* 模型、队列、静态任务栈全部准备成功后才注册 PCM observer 并发布 started。 */
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -14,6 +14,10 @@
#include "freertos/FreeRTOS.h"
#include "platform.h"
/*
* 设备信息使用固定缓冲构造 JSON;写特征分片必须在3秒内完成,否则清空旧片段,
* 防止两次手机写入拼接。名称前缀和 UUID 属于固定 TalkingQ 小程序协议。
*/
#define DOMAIN_PROVISIONING_ID_MAX_LEN 64
#define DOMAIN_PROVISIONING_SERIAL_MAX_LEN 64
#define DOMAIN_PROVISIONING_VERSION_MAX_LEN 32
@@ -63,6 +67,7 @@ static const platform_ble_uuid128_t s_device_info_uuid = {
static void domain_provisioning_dispatch_event(
const domain_wifi_provisioning_event_t *event)
{
/* 回调对在锁内取快照、锁外调用,允许上层在事件中停止或重启配网。 */
domain_wifi_provisioning_event_callback_t callback;
void *user_data;
@@ -91,6 +96,7 @@ domain_provisioning_append_write_fragment(const uint8_t *data,
size_t data_len,
size_t *out_buffered_len)
{
/* 以完整 JSON 结束符判断写入完成;越界、超时或格式异常会重置整个缓冲。 */
if (data == NULL || data_len == 0U) {
return DOMAIN_PROVISIONING_WRITE_INVALID;
}
@@ -19,6 +19,10 @@
#include "mbedtls/sha256.h"
#include "platform.h"
/*
* 4KB 响应上限约束版本查询 JSON;36字符 nonce 提供请求唯一性;内部认证值包含
* 时间戳/nonce/HMAC 编码。系统时间早于合理 epoch 时拒绝生成时效认证。
*/
#define DOMAIN_OTA_RESPONSE_CAPACITY 4096
#define DOMAIN_OTA_NONCE_LEN 36
#define DOMAIN_OTA_AUTH_VALUE_MAX_LEN 128
@@ -117,6 +121,7 @@ static esp_err_t domain_ota_build_internal_auth(const char *url,
char *signature,
size_t signature_len)
{
/* 签名输入绑定 HTTP 路径、时间戳和随机 nonce,避免认证值跨接口或重放复用。 */
time_t now = time(NULL);
if ((int64_t)now < DOMAIN_OTA_MIN_VALID_EPOCH) {
return ESP_ERR_INVALID_STATE;
@@ -216,6 +221,7 @@ static esp_err_t domain_ota_build_report_payload(const domain_ota_report_request
static esp_err_t domain_ota_parse_report_response(const char *body,
domain_ota_report_response_t *response)
{
/* 完整验证业务码、版本、URL、SHA-256、大小和强制/确认标志后才发布固件快照。 */
cJSON *root = cJSON_Parse(body);
if (root == NULL) {
return ESP_ERR_INVALID_RESPONSE;
@@ -427,6 +433,7 @@ esp_err_t domain_runtime_ota_install(const domain_ota_firmware_t *firmware,
domain_ota_cancel_callback_t cancel_cb,
void *user_data)
{
/* domain 将十六进制哈希转为32字节并桥接进度/取消回调,下载写入仍由 platform 拥有。 */
if (firmware == NULL || strncmp(firmware->url, "https://", 8) != 0 ||
firmware->size_bytes == 0U || timeout_ms <= 0) {
return ESP_ERR_INVALID_ARG;
@@ -16,6 +16,12 @@
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
/*
* Domain 运行时适配器:把领域层端口翻译成 platform 调用,并把平台事件映射回领域语义。
* 本文件不应引入重试、状态机或产品策略;这些属于 control_plane。静态回调及 hook 是注册
* 信息的唯一副本,分发时先在临界区内取得快照,再在锁外调用,防止回调重入造成死锁。
*/
#include "sdkconfig.h"
#ifndef CONFIG_MULTIMODAL_PTT_BUTTON_GPIO
@@ -138,8 +144,12 @@ static const char *const s_wealth_direction_asset_dirs[] = {
#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"
@@ -304,6 +314,7 @@ static const domain_expression_catalog_entry_t s_expression_catalog[] = {
static void snapshot_domain_wifi_behavior_hooks(domain_wifi_behavior_hooks_t *out_hooks)
{
/* 只在临界区复制函数指针和上下文;实际 hook 在锁外调用,允许 hook 反向更新注册。 */
if (out_hooks == NULL) {
return;
}
@@ -392,6 +403,7 @@ static void dispatch_domain_transport_event(domain_multimodal_transport_event_ty
const uint8_t *data,
size_t data_len)
{
/* data 只在本次 platform 回调期间有效;domain 不缓存,跨任务保留由上层自行复制。 */
domain_multimodal_transport_event_callback_t cb = NULL;
void *cb_user_data = NULL;
snapshot_domain_transport_callback(&cb, &cb_user_data);
@@ -576,6 +588,7 @@ esp_err_t domain_runtime_post_json(const char *url,
size_t *out_response_body_len,
int *out_status)
{
/* 本适配只映射 header/缓冲区所有权,不解释业务 HTTP 状态,也不在 domain 内隐式重试。 */
if (out_response_body_len != NULL) {
*out_response_body_len = 0U;
}
@@ -1116,6 +1129,7 @@ static esp_err_t load_legacy_auth_record(domain_runtime_auth_record_t record,
size_t out_len,
size_t *out_len_actual)
{
/* 旧键只用于一次兼容读取;成功后由新格式保存路径接管,迁移不会改变调用方序列化 schema。 */
switch (record) {
case DOMAIN_RUNTIME_AUTH_RECORD_KEY_MATERIAL:
return load_legacy_auth_key_material(out, out_len, out_len_actual);
@@ -1180,6 +1194,7 @@ esp_err_t domain_runtime_auth_store_save_record(domain_runtime_auth_record_t rec
const void *data,
size_t len)
{
/* platform blob 写入成功才更新持久状态;输入缓冲区始终由调用方拥有。 */
if (data == NULL || len == 0U) {
return ESP_ERR_INVALID_ARG;
}
@@ -1430,6 +1445,7 @@ static esp_err_t domain_resolve_expression_platform_request(
const domain_expression_request_t *request,
platform_lcd_presentation_request_t *out_platform_request)
{
/* 这是领域表情 ID 到平台资源序列的唯一映射,避免 control_plane 拼接资源目录和帧参数。 */
if (request == NULL || out_platform_request == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -1621,6 +1637,7 @@ esp_err_t domain_runtime_preheat_expression(const domain_expression_request_t *r
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();