# 构建与烧录 > 相关:[02-进展与下一步.md](02-进展与下一步.md) | [01-方案设计.md](01-方案设计.md) 本文只讲**怎么把这套东西跑起来**:环境、构建、烧录、恢复、以及插件怎么装进 DSH。 --- ## 一、环境 ### 一键自检 ```bash ./tools/setup-env.sh --check ``` 它只读不写,逐项报告构建工具、ESP-IDF 版本(真正执行 `idf.py --version`)、 以及上游要求的 5 个 skill 是否存在。 ### 当前这台机器的实际状态 | 项 | 值 | | --- | --- | | ESP-IDF | `v5.5.3` @ `~/esp/esp-idf-v5.5.3` | | 工具链 | `~/.espressif`(`xtensa-esp32c3-elf` + esptool 4.12.0) | | cmake / ninja / ccache | 4.4.3 / 1.13.2 / 4.14.1(Homebrew) | | 激活方式 | `source ~/esp/esp-idf-v5.5.3/export.sh` | ### 从零安装(换一台机器时) ```bash # 1) 主机工具 brew install cmake ninja ccache # 2) ESP-IDF 5.5.3(必须装在仓库之外、路径不含空格) mkdir -p ~/esp git clone --depth 1 --branch v5.5.3 --recurse-submodules --shallow-submodules \ https://github.com/espressif/esp-idf.git ~/esp/esp-idf-v5.5.3 # 3) 工具链 ~/esp/esp-idf-v5.5.3/install.sh esp32c3 ``` > **不要用 `sudo xcode-select --install` 之外的方式改系统配置**,也不要修改 shell 启动文件。 > 上游规范明确要求这些属于主机级改动,须先取得授权。 --- ## 二、构建固件 ```bash source ~/esp/esp-idf-v5.5.3/export.sh cd vendor/ai-passport ./tools/validate.sh --static # 仓库检查 + host tests,秒级 ./tools/validate.sh --firmware # ESP-IDF 构建 + 合并镜像校验,分钟级 ``` 产物固定为单个合并镜像: ``` vendor/ai-passport/build/FoloToy-AI-Passport-full.bin ``` 这个镜像可以**从 `0x0` 一次性写入**,是上游要求的交付格式。 `validate.sh --firmware` 还会校验分区表 MD5、边界与不重叠、以及应用是否落在配置的 app 分区内。 ### 加速重复编译 ```bash export IDF_CCACHE_ENABLE=1 # 只影响当前 shell 及其子进程 ./tools/validate.sh --firmware ``` ### 常见偏差处理 | 现象 | 原因与处理 | | --- | --- | | `idf.py --version` 不是 v5.5.3 | 不要复用,另行并行安装;不要覆盖已有安装 | | 改了 `sdkconfig.defaults` 但没生效 | 已有 `sdkconfig` 不会自动同步 defaults。保留有意的本地设置后执行 `idf.py set-target esp32c3` | | `dependencies.lock` 出现未提交差异 | 改了 `idf_component.yml` 就必须用 5.5.3 重新生成锁文件并一起评审提交 | --- ## 三、烧录 ### 前置条件(缺一不可) 1. **用户明确授权**。检测到设备 ≠ 获得烧录许可,这是上游规范里写死的一条。 2. 设备开机,用**支持数据传输**的 USB 线连到电脑(有些线只能充电)。 3. macOS 上确认串口出现: ```bash ls /dev/cu.usb* # 期望看到 /dev/cu.usbserial-* 或 /dev/cu.wchusbserial* ``` ### 烧录命令 ```bash # 全片刷写(新板初始化或需要完整刷新时) esptool.py -p /dev/cu.usbserial-XXXX write_flash 0x0 \ vendor/ai-passport/build/FoloToy-AI-Passport-full.bin # 或者用 idf.py(增量、方便看日志) cd vendor/ai-passport && idf.py -p /dev/cu.usbserial-XXXX flash monitor ``` > **数据说明**:烧录会覆盖设备上的原有固件,**不会**自动恢复。 > 也不保证保留已有设置与用户数据(合并镜像可能重置 NVS)。 > 需要保留 NVS 时改用分段 `idf.py flash`。 ### ★ 在 macOS 上打开串口会把设备复位(别用它做"只看不改"的取证) ESP32-C3 走的是原生 USB-Serial-JTAG。**只要打开串口端口,芯片就会复位**: ``` rst:0x15 (USB_UART_CHIP_RESET),boot:0xa (SPI_FAST_FLASH_BOOT) ``` 表现为 BLE 断链、握手失败、挂件报 `适配器未初始化` / `not-available`。 **这跟固件、插件都无关**,很容易被误判成"设备挂了"或"刚刷的固件有问题"。 试过且**都绕不过去**的写法: | 写法 | 结果 | | --- | --- | | `Serial(port); s.dtr = False; s.rts = False` | 构造时已拉高,事后改太晚 → 仍复位 | | `Serial(port=None)` 先设 `dtr/rts=False` 再 `open()` | 仍复位(macOS 驱动自己会发 SET_CONTROL_LINE_STATE) | **因此**: - 只看日志、**不想打断正在跑的链路**时,别开串口。改用与串口无关的观测点: - 录音时长 / 停顿是否保留 → 看归档 WAV `<插件包>/audio/*.wav`(`wave` 模块读帧数即可) - 识别结果 / 丢片 → 看挂件 `state.json` 的 `voiceLog`(含 `text`、`deviceMaxRms`、`seqGaps`) - 确实要开串口时,预期设备会重启;先让链路自己恢复(`autoReconnect`),必要时手动 `device.disconnect` → `device.connect` 各触发一次(首次 `device.connect` 常因适配器尚未 初始化而停在 `scanning`,**断开再连一次**就正常)。 - 真机验证的结论要基于 WAV / `voiceLog` 这类持久化证据,串口日志只作辅助佐证。 ### 恢复出厂固件 从 [AI Passport 刷机工具](https://ai-passport.folotoy.cn/tools/web-flasher/) 下载官方固件, 或从玩法社区选一个已发布玩法安装回去。**刷机前先记下当前固件版本**,方便回退对照。 --- ## 四、把插件装进 DSH ### macOS / Windows 通用安装(推荐) ```bash npm run install-plugin # 默认 desktop profile npm run install-plugin -- web desktop # 可指定多个 profile ``` Windows(PowerShell)等价入口: ```powershell powershell -ExecutionPolicy Bypass -File tools\install-plugin-to-dsh.ps1 ``` 链接策略按平台自动选择,用户无需改命令: | 平台 | 方式 | 权限要求 | | --- | --- | --- | | macOS / Linux | 符号链接 | 无 | | Windows | 目录 junction | **无需**管理员或开发者模式 | | Windows(junction 被机器策略禁止) | 自动降级为复制,并写 `.dsh-passport-install.json` 标记 | 无;此后改源码需重跑安装命令 | profile 目录 = `~/.dsh/profiles/`(macOS)/ `%USERPROFILE%\.dsh\profiles\`(Windows), 与 DSH 宿主自身的解析规则一致,均可用环境变量 `DSH_HOME` 覆盖。 安装器还会检查 profile 下有没有 `@stoprocent/noble`(原生模块装在 profile 层被 hoist)。 缺了就在 profile 目录执行 `pnpm install`:Windows x64 直接用 win32-x64 预编译, 不需要 Visual Studio;Windows on ARM64 没有预编译,需要 VS Build Tools 源码编译。 ### 本地开发安装(Web profile 的 CLI 方式) ```bash # 让 DSH 认识这个本地包(link 方式,改动即时生效) dsh plugin --profile web add link:/绝对路径/dsh-ai-passport-pulg/packages/dsh-ai-passport ``` 装好后 `cordis.patch.yml` 会把插件插入 Web profile 的配置树, 默认配置为 `transport: bridge` + `autoConnect: true`。 ### 打开控制面板 ``` http://127.0.0.1:19387/dsh-passport ``` 面板接口一览: | 路径 | 方法 | 用途 | | --- | --- | --- | | `/dsh-passport` | GET | 控制面板页面 | | `/dsh-passport/state.json` | GET | 完整状态快照(只读,局域网亦可访问) | | `/dsh-passport/events` | GET | SSE 实时事件流 | | `/dsh-passport/action` | POST | 唯一写入口,**仅限回环地址** | ### 配置项 在 DSH 的设置界面里改本插件的配置(schema 由 `lib/config.js` 导出), 或直接改 profile 的 `cordis.patch.yml`。常用项: | 配置 | 默认 | 说明 | | --- | --- | --- | | `transport` | `bridge` | `bridge`(子进程,推荐)/ `mock`(无硬件联调)/ `noble`(进程内) | | `targetDeviceId` | 空 | 只连指定设备;空则连第一台匹配的 | | `autoConnect` | `true` | 插件加载后自动扫描连接 | | `tasks.maskTitles` | `false` | 设备上只显示"任务 xxxx",适合公共场合 | | `approval.readOnlyToolsOnDevice` | `true` | 只允许在设备上批只读工具 | | `approval.timeoutMs` | `120000` | 设备审批超时(超时按"交回电脑端"处理,不默认放行) | | `voice.directSend` | `true` | 设备识别结果卡「双击确定」= 整段替换草稿并发送(取代已删除的 `voice.autoSend`) | | `voice.maxSeconds` | `30` | 单次转写长度上限(秒)。固件端自律 30s,这里是主机侧兜底:超出的尾部截断后转写,不丢弃整段录音。调小它=进一步压短单次识别长度;调大不会让设备录更久 | ### 无硬件联调 把 `transport` 改成 `mock`,整套业务逻辑都能跑(虚拟设备会应答握手、心跳、任务列表、余额): ```bash cd packages/dsh-ai-passport node --test "lib/**/*.test.js" "test/**/*.test.js" ``` ### 单独调试 BLE 桥进程 ```bash # 不碰硬件,验证进程通信与协议 node packages/dsh-ai-passport/lib/ble/bridge-worker.js --mock # 真实扫描(需要装有 noble,且系统蓝牙已开启) node packages/dsh-ai-passport/lib/ble/bridge-worker.js ``` 桥进程用 stdin/stdout 上的 JSON Lines 通信,可以手工喂命令: ```bash printf '{"id":1,"cmd":"init"}\n{"id":2,"cmd":"scan"}\n' | node lib/ble/bridge-worker.js --mock ``` --- ## 五、语音识别 插件只调用 `ctx.speechToText`,**不关心**识别是谁做的。因此先要有一个提供者。 ### 启用 DSH 内置的本地识别(推荐:离线、免费) DSH 桌面版已内置完整栈,只是默认没启用。在 `~/.dsh/profiles/web/cordis.patch.yml` 里加入: ```yaml - insert: - id: speech-to-text name: '@deepseek-ai/dsh-experimental-speech-to-text' config: defaultProvider: sensevoice-local - id: speech-to-text-sensevoice name: '@deepseek-ai/dsh-experimental-speech-to-text-sensevoice' config: dataRoot: !!js dshHomePath('speech-to-text', 'sensevoice') - id: api-speech-to-text name: '@deepseek-ai/dsh-experimental-api-speech-to-text' ``` 首次使用会下载约 240 MB 的 int8 模型(SenseVoiceSmall,支持中/英/日/韩/粤), 面板的「语音识别」卡片会显示下载进度,也可以点「准备模型」提前触发。 原生依赖 `sherpa-onnx-darwin-arm64` 已随 DSH 发布,Apple Silicon 上**无需编译、无需 Python**。 ### 没有提供者时的表现 面板会显示"没有可用的识别提供者",设备端录音后会提示"语音未就绪"。 这是**设计好的降级**,不是故障:语音以外的全部功能不受影响。 ## 固件版本号怎么定 版本号**不写死在代码里**(写死必然忘记同步——真机踩过:挂件上一直显示旧版本 `0.2.0`)。 | 想要的效果 | 做法 | | --- | --- | | 日常发版 | 改 `vendor/ai-passport/version.txt` 一行(如 `1.0.0`),再 `./tools/build-firmware.sh --firmware`;显示成 `1.0.0+g[-dirty]` | | 一次性覆盖整串(发候选版) | 构建时加 `-DAP_VERSION=1.0.0-rc1`(如 `idf.py -DAP_VERSION=1.0.0-rc1 build`),不再追加 git 描述 | | 源码离开 git 仓库 | 只留基底版本(`version.txt` 的值),构建不失败 | - 注入点在 `main/CMakeLists.txt`(`target_compile_definitions(... AP_FIRMWARE_VERSION=...)`), 头文件里用 `#ifndef AP_FIRMWARE_VERSION` 兜底。 - 这个串会随 `hello` 上报给主机 → **挂件信息格的「固件」显示的就是设备真实版本**。 - 插件(挂件)自身版本在 `package.json`(根 + `packages/dsh-ai-passport`)。