20 KiB
TQ Cat 固件整体架构与运行工作流
本文档基于 feature/st7789p3-new-board 分支的代码,面向项目接手、问题定位和后续重构。内容以实际函数调用关系为准。
1. 系统定位
项目运行在 ESP32-S3 上,主要负责:
- ST7789P3 LCD 表情播放;
- ES8311 音频采集与播放;
- Opus 编解码与 WebSocket 多模态对话;
- TTP223 PTT、SW1、SW2 按键处理;
- ULN2003 步进电机和转盘校准;
- Wi-Fi 连接、重连和配置热点;
- 设备身份、JWT 和会话令牌;
- 用户出生资料、每日运势和财富方向;
- NVS、LittleFS 和工厂 CLI。
2. 分层架构
flowchart TB
Main["main / app_main"]
App["app_composition<br/>启动顺序与应用装配"]
CP["control_plane<br/>策略、状态机、任务、重试"]
Domain["domain<br/>方向、校准、运势、表情语义"]
Platform["platform<br/>GPIO、LCD、音频、网络、存储"]
HW["ESP32-S3 新板硬件"]
Main --> App --> CP --> Domain --> Platform --> HW
各层职责:
| 层 | 主要职责 | 典型内容 |
|---|---|---|
main |
固件入口 | 只调用 composition 入口 |
app_composition |
决定启动顺序 | 存储、音频、LCD、校准、联网、workers |
control_plane |
产品策略和生命周期 | Wi-Fi、PTT、多模态、任务、重试、异常恢复 |
domain |
业务语义和状态 | 方向、角度、校准、运势、LCD 表情映射 |
platform |
硬件与传输原语 | GPIO、I2S、I2C、SPI、WebSocket、NVS、HTTP |
3. 启动总流程
入口:
app_main()
└── app_composition_start_default_runtime()
└── app_composition_run_plan_a_boot_sequence()
主要文件:
main/main.ccomponents/app_composition/src/runtime/app_composition_default_runtime.c
启动阶段:
flowchart TD
Entry["app_main"]
Storage["准备 NVS / 文件系统"]
Audio["初始化音频并播放开机音"]
LCD["初始化 LCD 和渲染任务"]
Core["初始化 platform / domain"]
Expression["请求默认 LCD 表情"]
Calibration["执行转盘原点校准"]
WiFi["连接 Wi-Fi 或启动配置 AP"]
Workers["启动运行时 workers"]
Entry --> Storage --> Audio --> LCD --> Core --> Expression --> Calibration --> WiFi --> Workers
3.1 存储初始化
control_plane_prepare_runtime_storage()
└── domain_runtime_prepare_storage()
└── platform_prepare_storage()
├── nvs_flash_init()
├── 必要时 nvs_flash_erase()
├── 再次 nvs_flash_init()
└── platform_log_nvs_stats()
默认 NVS namespace 包括 wifi、ota、auth、auth_key 和 fortune。
3.2 音频与开机音
control_plane_play_boot_sound(timeout)
├── domain_runtime_multimodal_audio_init()
│ └── platform_multimodal_audio_init()
│ └── multimodal_audio_init()
└── domain_runtime_behavior_play_prompt_file(path)
multimodal_audio_init() 的主要步骤:
fill_codec_sample_info()
→ audio_init_heap_guard_passed()
→ init_opus_codec()
→ init_i2c_bus()
→ init_i2s_bus()
→ create_codec_path()
→ open_codec_device()
→ apply_codec_default_controls()
→ ensure_uplink_worker_tasks()
→ ensure_downstream_playback_task()
开机音失败只记录 warning,不会终止启动。
3.3 LCD 初始化
control_plane_init_lcd_device()
└── domain_runtime_init_lcd_device()
└── platform_init_lcd_device()
└── lcd_direct_init_device()
├── lcd_direct_gpio_init()
└── lcd_direct_panel_init()
渲染任务:
control_plane_start_lcd_render_worker()
└── control_plane_lcd_render_loop_task()
└── control_plane_start_lcd_render_loop()
└── domain_runtime_start_lcd_render_loop()
└── platform_start_lcd_render_loop()
└── lcd_direct_start_render_loop()
3.4 platform/domain 初始化
control_plane_init()
├── domain_direction_default_axis_config()
├── domain_direction_default_calibration()
└── domain_init(axis_config, calibration)
├── domain_runtime_platform_init()
│ └── platform_init()
│ ├── platform_power_hold_init()
│ ├── platform_io_effects_init()
│ ├── platform_stepper_driver_init()
│ └── platform_stepper_driver_power_off()
├── 初始化方向状态
└── domain_calibration_store_set()
注意:启动日志将 power hold 描述为已提前拉起,但实际 platform_power_hold_init() 位于 platform_init() 中。新板应真机验证 GPIO9 拉高时机是否足够早。
4. 新板关键 GPIO
| 功能 | GPIO | 电气语义 |
|---|---|---|
| Power Hold | 9 | 高有效 |
| LCD CLK | 8 | SPI clock |
| LCD MOSI | 18 | SPI data |
| LCD CS | 15 | 低有效 |
| LCD DC | 16 | 数据/命令 |
| LCD RESET | 17 | 低有效 reset |
| LCD Backlight | 7 | 高有效 |
| TTP223 / PTT | 3 | 高有效,无内部上下拉 |
| SW1 校准开关 | 12 | 低有效,上拉 |
| SW2 功能开关 | 13 | 低有效,上拉 |
| Stepper IN1/2/3/4 | 45/48/47/21 | ULN2003 相线 |
| Audio PA | 11 | 功放使能 |
| ES8311 I2C SDA/SCL | 1/2 | codec 控制 |
| I2S MCLK/BCLK/DOUT/WS/DIN | 38/39/40/41/42 | 音频数据 |
5. 转盘校准与旋转
启动校准调用链:
control_plane_calibrate()
└── control_plane_calibrate_with_timeout()
└── control_plane_direction_calibrate_internal()
└── domain_calibrate_with_timeout()
domain_calibrate_with_timeout():
锁定方向状态
→ prepare_calibration_switch()
→ 检查 SW1 是否已有效
→ 循环执行 calibration_rotate_and_track()
→ calibration_settle()
→ 再次读取 SW1
→ 触发后 calibration_return_from_limit()
→ domain_set_current_angle(home_angle)
→ domain_runtime_stepper_power_off()
底层旋转:
domain_runtime_stepper_rotate_angle_with_accel_timeout()
└── platform_stepper_rotate_angle_with_accel_timeout()
└── platform_stepper_driver_rotate_angle_with_accel()
└── rotate_with_accel()
├── calculate_motion_segments()
├── stepper_advance_phase()
└── precise_delay_us()
绝对角度旋转:
control_plane_rotate_to_angle()
└── domain_rotate_to_angle_with_timeout()
├── domain_angle_normalize()
├── domain_angle_shortest_rotation()
├── 执行电机旋转
└── 更新 s_current_angle
6. Wi-Fi 启动与恢复
启动入口:
control_plane_bootstrap_connectivity(seed_ssid, seed_password, timeout, "TQ-CAT")
调用链:
control_plane_register_wifi_behavior_hooks()
→ domain_runtime_wifi_portal_init_defaults()
→ wifi_portal_init_defaults()
→ 检查已保存凭据
→ control_plane_connect_sta_with_feedback()
→ 最多三次 STA 连接
→ 成功则播放提示音
→ 失败则按错误类型保留或清除凭据
→ 必要时启动 TQ-CAT 配置 AP
运行时由 control_plane_wifi_state_task() 负责:
- 检测 Wi-Fi 断开;
- 等待短暂 disconnect grace;
- 停止多模态 client 和用户配置 server;
- 请求断网表情;
- 按指数退避重连;
- 重连后恢复 idle 表情、WebSocket 和配置 server;
- 同时驱动 PTT 状态机。
7. 运行时任务拓扑
入口:
control_plane_start_runtime_workers()
flowchart TB
Start["control_plane_start_runtime_workers"]
Model["cp_model_cmd<br/>模型设备命令"]
WiFi["cp_wifi_state<br/>Wi-Fi、WS、PTT"]
LCD["cp_lcd_render<br/>LCD 启动"]
Events["cp_system_event<br/>事件分发"]
Keys["cp_key_input<br/>GPIO 与消抖"]
Fortune["cp_fortune<br/>每日运势"]
Monitor["cp_task_monitor<br/>可选诊断"]
Start --> Model
Start --> WiFi
Start --> LCD
Start --> Events
Start --> Keys
Start --> Fortune
Start --> Monitor
control_plane_schedule_websocket_wait_task() 当前只设置 s_ws_start_requested = true,实际 WebSocket 启动由 cp_wifi_state 执行。
8. 按键和系统事件
按键中断链路:
GPIO edge
→ control_plane_key_gpio_isr_handler()
→ xQueueSendFromISR(s_gpio_irq_queue)
→ control_plane_key_input_task()
→ control_plane_process_key_timers()
→ control_plane_handle_debounced_transition()
→ control_plane_emit_key_event()
→ control_plane_post_system_event()
→ control_plane_system_event_task()
→ control_plane_dispatch_system_event()
→ control_plane_dispatch_key_event()
事件包括:
PRESSED;RELEASED;SHORT_PRESS;LONG_PRESS_START;LONG_PRESS_RELEASE;DOUBLE_CLICK。
行为映射:
| 输入 | 行为 |
|---|---|
| PTT 短按 | control_plane_set_ptt_active(true) |
| PTT 在服务端回复时按下 | control_plane_multimodal_send_request_to_speak() |
| PTT 双击 | 请求 DOMAIN_EXPRESSION_MUYU |
| SW2 单击 | 请求 DOMAIN_EXPRESSION_CHOUQIAN |
| SW2 双击 | 请求 DOMAIN_EXPRESSION_WUXING |
9. PTT 状态机
状态:
IDLE
WAIT_WIFI
WAIT_WS
WAIT_START_SENT
WAIT_LISTENING
WAIT_SPEECH_STARTED
STREAMING
TEARDOWN_LOCAL
TEARDOWN_REMOTE
DENIED
状态机由 control_plane_drive_ptt_state_machine() 在 cp_wifi_state 中周期驱动:
无 Wi-Fi → WAIT_WIFI
无 WebSocket → WAIT_WS
无 active task → WAIT_START_SENT
服务端未 Listening → WAIT_LISTENING
未发送 SendSpeech → WAIT_SPEECH_STARTED
uplink 已运行 → STREAMING
释放/超时 → TEARDOWN_LOCAL → TEARDOWN_REMOTE → IDLE
每次推进调用 control_plane_multimodal_handle_ptt_press_step(),根据当前会话状态选择:
control_plane_multimodal_send_start();control_plane_multimodal_send_request_to_speak();control_plane_multimodal_send_speech_start();control_plane_multimodal_start_uplink()。
10. 设备认证和 WebSocket
启动链:
cp_wifi_state
→ control_plane_try_start_multimodal_from_state_machine()
→ control_plane_start_multimodal_client()
→ control_plane_multimodal_start()
control_plane_start_multimodal_client():
control_plane_prepare_for_multimodal_start()
→ 注册 event callback
→ 注册 downstream audio callback
→ 创建 mm_watchdog
→ control_plane_multimodal_start()
认证链:
control_plane_multimodal_auth_fetch()
├── resolve_device_identity()
├── control_plane_multimodal_device_auth_ensure()
│ ├── control_plane_multimodal_device_auth_init()
│ ├── 检查已有 JWT 和 scope
│ └── refresh_jwt_internal()
├── control_plane_multimodal_device_auth_copy_jwt()
├── HTTP POST token endpoint
├── 401/403 时 invalidate JWT 并重试
└── parse_token_response()
WebSocket 链:
domain_runtime_multimodal_transport_start()
└── platform_multimodal_transport_start()
└── multimodal_ws_start()
├── 停止旧 client
├── 构造 Authorization 和 Workspace headers
├── 创建 send mutex
├── create_worker_pipeline()
└── start_websocket_client_with_fallback()
11. 一次完整语音会话
sequenceDiagram
participant User as 用户/PTT
participant CP as Control Plane
participant Session as Session Service
participant Audio as Audio Pipeline
participant WS as WebSocket
participant Server as 服务端
User->>CP: 短按
CP->>Session: send_start()
Session->>WS: run-task / Start
Server-->>Session: DialogStateChanged(Listening)
CP->>Session: send_speech_start()
CP->>Audio: start_uplink()
Audio->>WS: Opus binary frames
Server-->>Session: SpeechEnded / RespondingStarted
Session->>Audio: stop uplink / begin response
Server-->>Audio: Opus binary frames
Audio-->>User: 扬声器播放
Server-->>Session: RespondingEnded
Audio-->>Session: playback completed
Session->>WS: LocalRespondingEnded
11.1 Start
control_plane_multimodal_send_start()
→ 生成 task UUID
→ 构造 run-task / Start JSON
→ 设置 upstream/downstream Opus 参数
→ domain_runtime_multimodal_transport_send_text()
→ 保存 s_task_id
11.2 开始采集和上行
control_plane_multimodal_send_speech_start()
→ send_directive("continue-task", "SendSpeech")
→ control_plane_multimodal_start_uplink()
→ domain_runtime_multimodal_audio_start_uplink()
→ multimodal_audio_start_uplink()
实时音频:
I2S capture task
→ PCM 整理
→ Opus encode
→ uplink send queue
→ uplink_send_task
→ control_plane_audio_uplink_send_cb()
→ control_plane_multimodal_send_audio_realtime()
→ multimodal_ws_send_binary_realtime()
11.3 下行播放
WebSocket binary frame
→ transport_event_bridge()
→ control_plane_downstream_audio_bridge()
→ control_plane_multimodal_play_downstream_audio()
→ multimodal_audio_handle_downstream_opus()
→ downstream playback queue
→ Opus decode
→ I2S / ES8311 播放
服务端 RespondingEnded 和本地播放完成都满足后,才发送 LocalRespondingEnded。
12. 服务端事件解析
所有文本帧进入 process_text_json(),主要处理:
task-failed;task-started;DialogStateChanged;RequestAccepted;RespondingStarted;RespondingContent;RespondingEnded;SpeechStarted;SpeechContent;SpeechEnded;Stopped;Error。
回复文本还会进入:
notify_responding_text()
→ control_plane_responding_text_bridge()
→ control_plane_extract_model_command()
→ control_plane_on_model_command()
→ model command queue
→ control_plane_model_command_task()
设备命令包括方向、校准动作和抽签。
13. LCD 表情工作流
domain_runtime_request_expression()
├── domain_resolve_expression_platform_request()
└── platform_lcd_request_presentation()
└── lcd_direct_request_presentation()
domain 负责把业务语义映射成 platform 播放请求:
IDLE
SERVER_ERROR
WIFI_DISCONNECTED
WEALTH_SPIN
WEALTH_DIRECTION
MUYU
CHOUQIAN
WUXING
platform 只处理:
- asset directory;
- frame index;
- frame interval;
- priority;
- interrupt policy;
- repeat count;
- return-to-idle。
渲染循环:
lcd_direct_take_pending()
→ lcd_direct_play_asset_sequence() 或 lcd_direct_play_step_sequence()
→ lcd_direct_signal_completion()
→ 无 pending 时播放 basic idle face
14. 财富方向与运势
财富方向动作:
control_plane_apply_wealth_direction()
├── domain_runtime_preheat_expression(reveal)
├── 阻塞播放 WEALTH_SPIN
├── 请求 WEALTH_DIRECTION reveal
├── 记录 start_angle
├── 旋转固定角度
├── 延时
└── 返回 start_angle
当前 direction_index 主要选择 LCD 方向资源,电机仍执行固定角度转出再返回,并非直接旋转到方向目录的绝对角度。
每日运势 worker:
control_plane_daily_fortune_task()
├── 等待 birth profile
├── 等待 Wi-Fi
├── control_plane_wait_for_synced_time()
├── 等待 WebSocket TLS 握手稳定
├── 计算本地日期
├── control_plane_fortune_fetch()
├── domain_fortune_result_store_set()
└── control_plane_apply_wealth_direction()
运势 HTTPS 与 WebSocket TLS 握手被有意串行化,以避免内部 DMA RAM 不足。
15. 局域网用户配置
联网后 control_plane_wifi_state_task() 启动用户配置 server:
control_plane_ensure_user_config_server()
└── domain_user_config_server_start()
└── platform_lan_http_server_start()
路由:
| 方法 | 路径 | 处理函数 |
|---|---|---|
| GET | / |
handle_index() |
| GET | /api/profile |
handle_get_profile() |
| POST | /api/profile |
handle_post_profile() |
| GET | /api/fortune |
handle_get_fortune() |
资料更新后:
domain_birth_profile_store_set()
→ birth profile observer
→ control_plane_request_fortune_refresh()
→ 唤醒 cp_fortune
16. Watchdog 和异常恢复
multimodal_watchdog_task() 每秒检查:
- WebSocket 是否连接;
- 是否存在 active task;
- heartbeat 是否到期;
- 是否长时间没有下行;
- transport 是否空闲超过 30 秒。
处理:
heartbeat 到期
→ control_plane_multimodal_send_heartbeat()
无下行超时
→ control_plane_multimodal_send_stop("NoResponseTimeout")
→ stop uplink
transport 空闲 30 秒
→ control_plane_multimodal_stop()
WebSocket 断开或错误时,会停止 uplink,并由 cp_wifi_state 安排后续重连。
17. 停止流程
control_plane_stop_runtime_workers()
├── 设置 runtime stop flag
├── 停止用户配置 server
├── 注销模型命令和 profile observer
├── 停止 key task 和 system event task
├── 停止 LCD render loop
├── bounded wait 各 worker
├── 必要时强制删除 task
├── 删除 model command queue
└── control_plane_stop_multimodal_client()
├── stop_watchdog_task()
└── control_plane_multimodal_stop()
├── stop uplink
├── 停止本地 responding worker
├── 注销 callbacks
├── transport_stop(2000)
└── reset_session_state_locked()
18. 调试索引
遇到问题时先判断属于哪个状态机:
control_plane_wifi_state_task():Wi-Fi、WebSocket 启动、PTT;- multimodal session:task、Listening、Speech、Responding;
- audio pipeline:capture、send queue、decode、playback。
推荐排查路径:
| 现象 | 优先检查 |
|---|---|
| 上电后掉电 | GPIO9、platform_power_hold_init() 调用时机 |
| LCD 无显示 | GPIO、lcd_direct_init_device()、背光和 SPI |
| 电机不动 | platform_stepper_driver_init()、相序、限位状态 |
| 开机校准超时 | GPIO12、有效电平、最大 sweep、旋转方向 |
| PTT 无反应 | ISR queue、消抖、system event、PTT state |
| 有按键但没录音 | Listening、SendSpeech、audio init、uplink task |
| 有录音但服务端收不到 | Opus queue、uplink callback、WS binary send |
| 服务端回复但没声音 | binary callback、decode queue、I2S、PA GPIO11 |
| 第二轮对话失败 | s_task_id、Listening、speech flags、playback completion |
| 断网不恢复 | cp_wifi_state、saved credentials、backoff、WS start request |
19. 关键源码入口
main/main.ccomponents/app_composition/src/runtime/app_composition_default_runtime.ccomponents/control_plane/src/runtime/control_plane_runtime_workers.ccomponents/control_plane/src/runtime/control_plane_connectivity_runtime.ccomponents/control_plane/src/runtime/control_plane_key_input.ccomponents/control_plane/src/runtime/control_plane_system_events.ccomponents/control_plane/src/multimodal/control_plane_multimodal_runtime.ccomponents/control_plane/src/multimodal/control_plane_multimodal_session_service.ccomponents/control_plane/src/multimodal/control_plane_multimodal_auth_service.ccomponents/control_plane/src/multimodal/control_plane_multimodal_device_auth.ccomponents/domain/src/direction/domain_direction_service.ccomponents/domain/src/calibration/domain_calibration_service.ccomponents/domain/src/runtime/domain_runtime_adapters.ccomponents/platform/src/multimodal/audio_pipeline/audio_pipeline_runtime_impl.ccomponents/platform/src/multimodal/transport_ws/transport_ws_runtime_impl.ccomponents/platform/src/lcd_direct.ccomponents/platform/src/platform_stepper_driver.c