Files
lucky-cat/docs/knowledge vault/project-runtime-workflow.md
T

706 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TQ Cat 固件整体架构与运行工作流
本文档基于 `feature/st7789p3-new-board` 分支的代码,面向项目接手、问题定位和后续重构。内容以实际函数调用关系为准。
## 1. 系统定位
项目运行在 ESP32-S3 上,主要负责:
- ST7789P3 LCD 表情播放;
- ES8311 音频采集与播放;
- Opus 编解码与 WebSocket 多模态对话;
- TTP223 PTT、SW1、SW2 按键处理;
- ULN2003 步进电机和转盘校准;
- Wi-Fi 连接、重连和配置热点;
- 设备身份、JWT 和会话令牌;
- 用户出生资料、每日运势和财富方向;
- NVS、应用内嵌素材、模型分区和工厂 CLI。
## 2. 分层架构
```mermaid
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. 启动总流程
入口:
```text
app_main()
└── app_composition_start_default_runtime()
└── app_composition_run_plan_a_boot_sequence()
```
主要文件:
- `main/main.c`
- `components/app_composition/src/runtime/app_composition_default_runtime.c`
启动阶段:
```mermaid
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 存储初始化
```text
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 音频与开机音
```text
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()` 的主要步骤:
```text
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 初始化
```text
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()
```
渲染任务:
```text
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 初始化
```text
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. 转盘校准与旋转
启动校准调用链:
```text
control_plane_calibrate()
└── control_plane_calibrate_with_timeout()
└── control_plane_direction_calibrate_internal()
└── domain_calibrate_with_timeout()
```
`domain_calibrate_with_timeout()`:
```text
锁定方向状态
→ 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()
```
底层旋转:
```text
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()
```
绝对角度旋转:
```text
control_plane_rotate_to_angle()
└── domain_rotate_to_angle_with_timeout()
├── domain_angle_normalize()
├── domain_angle_shortest_rotation()
├── 执行电机旋转
└── 更新 s_current_angle
```
## 6. Wi-Fi 启动与恢复
启动入口:
```text
control_plane_bootstrap_connectivity(seed_ssid, seed_password, timeout, "TQ-CAT")
```
调用链:
```text
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. 运行时任务拓扑
入口:
```text
control_plane_start_runtime_workers()
```
```mermaid
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. 按键和系统事件
按键中断链路:
```text
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 状态机
状态:
```text
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` 中周期驱动:
```text
无 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
启动链:
```text
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()`:
```text
control_plane_prepare_for_multimodal_start()
→ 注册 event callback
→ 注册 downstream audio callback
→ 创建 mm_watchdog
→ control_plane_multimodal_start()
```
认证链:
```text
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 链:
```text
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. 一次完整语音会话
```mermaid
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
```text
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 开始采集和上行
```text
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()
```
实时音频:
```text
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 下行播放
```text
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`。
回复文本还会进入:
```text
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 表情工作流
```text
domain_runtime_request_expression()
├── domain_resolve_expression_platform_request()
└── platform_lcd_request_presentation()
└── lcd_direct_request_presentation()
```
domain 负责把业务语义映射成 platform 播放请求:
```text
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。
渲染循环:
```text
lcd_direct_take_pending()
→ lcd_direct_play_asset_sequence() 或 lcd_direct_play_step_sequence()
→ lcd_direct_signal_completion()
→ 无 pending 时播放 basic idle face
```
## 14. 财富方向与运势
财富方向动作:
```text
control_plane_apply_wealth_direction()
├── domain_runtime_preheat_expression(reveal)
├── 阻塞播放 WEALTH_SPIN
├── 请求 WEALTH_DIRECTION reveal
├── 记录 start_angle
├── 旋转固定角度
├── 延时
└── 返回 start_angle
```
当前 `direction_index` 主要选择 LCD 方向资源,电机仍执行固定角度转出再返回,并非直接旋转到方向目录的绝对角度。
每日运势 worker:
```text
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:
```text
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()` |
资料更新后:
```text
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 秒。
处理:
```text
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. 停止流程
```text
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. 调试索引
遇到问题时先判断属于哪个状态机:
1. `control_plane_wifi_state_task()`:Wi-Fi、WebSocket 启动、PTT;
2. multimodal session:task、Listening、Speech、Responding;
3. 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.c`
- `components/app_composition/src/runtime/app_composition_default_runtime.c`
- `components/control_plane/src/runtime/control_plane_runtime_workers.c`
- `components/control_plane/src/runtime/control_plane_connectivity_runtime.c`
- `components/control_plane/src/runtime/control_plane_key_input.c`
- `components/control_plane/src/runtime/control_plane_system_events.c`
- `components/control_plane/src/multimodal/control_plane_multimodal_runtime.c`
- `components/control_plane/src/multimodal/control_plane_multimodal_session_service.c`
- `components/control_plane/src/multimodal/control_plane_multimodal_auth_service.c`
- `components/control_plane/src/multimodal/control_plane_multimodal_device_auth.c`
- `components/domain/src/direction/domain_direction_service.c`
- `components/domain/src/calibration/domain_calibration_service.c`
- `components/domain/src/runtime/domain_runtime_adapters.c`
- `components/platform/src/multimodal/audio_pipeline/audio_pipeline_runtime_impl.c`
- `components/platform/src/multimodal/transport_ws/transport_ws_runtime_impl.c`
- `components/platform/src/lcd_direct.c`
- `components/platform/src/platform_stepper_driver.c`