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

20 KiB
Raw Blame History

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. 分层架构

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.c
  • components/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. 调试索引

遇到问题时先判断属于哪个状态机:

  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