Files
ESP32S3_vt/README.md
T
sloray 896b21fe44
构建客户端 / 检查(类型 + 单元测试) (push) Canceled after 0s
构建客户端 / Linux x64(deb + AppImage) (push) Canceled after 0s
构建客户端 / Windows x64(NSIS 安装包) (push) Canceled after 0s
构建客户端 / macOS 通用(.app + .dmg) (push) Canceled after 0s
feat(client): RMIC::2026 界面重做、可计算的版本机制与固件版本联动
- feat(client): 界面换成墨黑蓝底 + 单支红的矩形语言,切角全部去掉,仅装甲条保留平行四边形
- feat(client): 自托管 HarmonyOS Sans SC 与 JetBrains Mono,中文与拉丁数字逐字回退
- feat(client): 新增启动序列、首启门、用户端 HUD、裁判端控制台、设置抽屉与固件维护页
- feat(client): 裁判端中缝与下方控制台可拖,鼠标落在哪一路下方就看哪一路
- feat(client): 设置抽屉常驻实时预览,与画面共用同一份准星绘制代码
- feat(client): 动效改用 Material 3 的缓动曲线与时长,逐条替换掉自造的贝塞尔
- feat(version): 版本号与 CHANGELOG 由 scripts/version.mjs 从 git 历史算出,不再手写
- feat(firmware): Hello 报文携带语义化版本、构建号与自报协议号,负载向后兼容
- feat(client): 固件版本显示在绑定列表与裁判端每一格;协议不匹配时给出具体原因
- feat(firmware): 固件包版本与客户端同源,都从 version.json 读
- fix(client): getCurrentWindow() 在没有 Tauri 注入时同步抛异常,浏览器预览整棵树被卸载
- fix(client): 启动序列的淡出 effect 清掉了自己刚设的 setTimeout,界面永远停在启动页
- fix(client): 设置抽屉左侧导航的锚点没挂到 section 上,且用错了坐标系
- fix(protocol): Hello 改用宽松帧头解析,让协议不匹配的板子能被解释而不是静默消失
- fix(build): Windows 上 bindgen 加载不了 libclang 的同目录依赖,构建脚本补上搜索路径
- fix(build): esp-idf-sys 在长路径下拒绝构建,固件构建改用短的目标目录
- build(client): 队标图标,含光学尺寸分级与手工拼的多尺寸 ICO
- build(ci): 守门 job 增加版本一致性检查,产物名改为 rmic-2026-*
- docs: 补充界面语言、版本机制与协议通告布局的说明
2026-10-19 02:40:08 +08:00

317 lines
19 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.
# ESP32-S3 摄像头图传系统
基于 **Rust + ESP-IDF** 的局域网实时图传系统。ESP32-S3 采集摄像头输出的 JPEG,通过 UDP 向桌面客户端推流;桌面端提供队伍端与裁判端界面,并接收 ESP32-C3 装甲板的血量广播。
## 功能与架构
- 摄像头自动探测、JPEG 采集、UDP 分片发送与完整帧重组。
- 局域网广播发现与订阅,单块图传板最多向 4 个客户端发送画面。
- 运行中 Wi-Fi 断开后自动重连,重新获得 IP 后重建 UDP socket 并恢复订阅。
- 使用 MAC 派生的稳定设备 ID;两块图传板可烧录同一固件,在客户端绑定队伍。
- Tauri v2 + React 桌面端:视频显示、双方血量、设备绑定与录制。
- 独立 ESP32-C3 装甲板固件:四路受击检测、WS2812 灯带及血量上报。
```text
ESP32-S3 图传板 A ─┐ ┌─ 队伍客户端
├─ 同一局域网 / UDP ─┼─ 裁判客户端
ESP32-S3 图传板 B ─┤ └─ 队伍客户端
ESP32-C3 装甲主板 ─┘
```
图传使用 UDP **5600**,装甲板血量使用 UDP **5601**。板卡与客户端需在可互相通信、允许广播的网络中;关闭 AP 客户端隔离,并允许客户端程序通过防火墙。
## 项目结构
| 路径 | 用途 |
| --- | --- |
| `src/` | ESP32-S3 图传固件,根目录 Cargo 项目 |
| `components/` | 本地相机驱动及 C/Rust 接口封装 |
| `protocol/` | 共享 UDP 协议与帧重组,支持主机测试 |
| `armor/` | 独立 ESP32-C3 装甲板 workspace |
| `desktop/` | Tauri + React 客户端及独立网络核心 `core/` |
| `scripts/` | 构建、烧录与探测脚本 |
| `reference/armor/` | 早期 C 版装甲板工程 |
各子项目的构建目标不同。以下编译、烧录流程针对**根目录的 ESP32-S3 图传固件**。
## 硬件要求
| 项目 | 当前配置 |
| --- | --- |
| 开发板 | Freenove ESP32-S3-WROOM CAM |
| 模组 | ESP32-S3-WROOM-1-N16R8,16 MB Flash + 8 MB Octal PSRAM |
| 摄像头 | OV5640;驱动包含 OV2640 / OV3660 支持,接线需核对 |
| 网络 | 2.4 GHz Wi-Fi |
| 连接 | 支持数据传输的 USB 线、可识别的串口及稳定供电 |
默认采集 VGA(640 × 480)、彩色 JPEG quality 12,按传感器节拍发送。相机引脚、Flash 分区和 PSRAM 配置见 [HARDWARE.md](HARDWARE.md)。换板型前必须核对接线,避免占用 Flash / PSRAM 引脚。
## ESP Rust 工具链入门
本项目使用 Rust `std` 和 ESP-IDF。ESP32-S3 属于 **Xtensa** 架构,需要通过 `espup` 安装 Espressif Rust 工具链;普通 `rustup target add` 不能替代这一步。根目录固定选择名为 `esp-1.97.0.0` 的工具链,避免浮动版本影响构建。
### 1. 安装基础依赖与 Rust 工具
先安装 [Rust / rustup](https://rustup.rs/)、[Git](https://git-scm.com/downloads)、[Python 3](https://www.python.org/downloads/)(建议 3.11)、[CMake](https://cmake.org/download/) 与 [Ninja](https://ninja-build.org/),确保命令在 `PATH` 中。
- **Windows**:使用 PowerShell 7.5 或更新版本,安装 Visual Studio Build Tools 的“使用 C++ 的桌面开发”和 Windows SDK,使用 MSVC Rust 主机工具链。仓库和工具链尽量使用较短、无空格和中文的路径。
- **Linux(Debian / Ubuntu)**:安装 `build-essential pkg-config libudev-dev libssl-dev python3 python3-venv cmake ninja-build git curl`。
- **macOS**:安装 Xcode Command Line Tools(`xcode-select --install`),以及 Python、CMake 和 Ninja。
以下命令显式使用 `+stable`,即使在仓库内执行,也不会要求事先已有 `esp`:
```text
rustup toolchain install stable
cargo +stable install espup --locked
cargo +stable install ldproxy --locked
cargo +stable install espflash --version 4.4.0 --locked
espup install --name esp-1.97.0.0 --toolchain-version 1.97.0.0 --targets esp32s3 --std
```
也可以从各工具的官方 releases 下载预编译程序。`espup --std` 安装本项目所需的 Rust / LLVM 工具,ESP-IDF 的 C 工具链由后续构建准备。
Linux / macOS 每次新开终端后执行:
```bash
. "$HOME/export-esp.sh"
```
Windows 安装后重新打开 PowerShell,让环境变量生效;若当前终端仍找不到 LLVM,可执行 `. "$HOME/export-esp.ps1"`。检查结果:
```text
rustup toolchain list
rustc +esp-1.97.0.0 --version
cargo +esp-1.97.0.0 --version
espflash --version
```
官方入门资料:[The Rust on ESP Book](https://docs.espressif.com/projects/rust/book/getting-started/toolchain.html)、[espup](https://github.com/esp-rs/espup)、[espflash](https://github.com/esp-rs/espflash/tree/v4.4.0/espflash)。
### 2. 准备 ESP-IDF
默认使用 **ESP-IDF v5.5.5**。首次构建时,`esp-idf-sys` 下载 SDK、对应 C 工具链及 Python 依赖,默认缓存到用户目录的 `.espressif/`;需要网络、磁盘空间及缓存目录写入权限。
已有匹配版本 SDK 时可在终端设置 `IDF_PATH`,不要提交个人绝对路径:
```powershell
# Windows:替换为实际 SDK 源码目录。
$env:IDF_PATH = 'C:\Espressif\frameworks\esp-idf-v5.5.5'
```
```bash
# Linux / macOS:替换为实际 SDK 源码目录。
export IDF_PATH="$HOME/esp/esp-idf-v5.5.5"
```
使用自动下载时,清除指向其他 SDK 的旧 `IDF_PATH`。缓存位置等高级设置见 [esp-idf-sys 构建选项](https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md)。
## 配置 Wi-Fi 与摄像头
在根目录复制 `.env.example` 为 `.env`,再用编辑器填写;已有 `.env` 时不要覆盖。
```powershell
# Windows PowerShell
Copy-Item .env.example .env
```
```bash
# Linux / macOS
cp .env.example .env
```
```dotenv
WIFI_SSID='你的2.4G网络名称'
WIFI_PASS='你的Wi-Fi密码'
# 可选,省略时使用固件默认值。
#CAM_RES=vga
#CAM_QUALITY=12
#CAM_QUALITY_MAX=0
#CAM_MAX_FPS=0
#CAM_CLOCK_PROFILE=default
#CAM_FB_COUNT=3
#CAM_GRAYSCALE=0
#CAM_EXPOSURE_EV=0
#CAM_AE_LEVEL=-1
#CAM_SHUTTER_LINES=0
#CAM_EXPOSURE_MAX_LINES=0
#CAM_GAIN_CEILING=0
#CAM_HTS=0
#UDP_PACKET_GAP_US=0
#UDP_RETRY_BUDGET_US=8000
#CAM_THERMAL_LIMIT_C=75
#CAM_VSYNC_INVERT=1
#WIFI_AUTH=wpa2
```
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `WIFI_SSID` | 无,必填 | 2.4 GHz 网络名称,最多 32 字节 |
| `WIFI_PASS` | 空字符串 | Wi-Fi 密码;开放网络可留空 |
| `CAM_RES` | `vga` | `qqvga` / `qvga` / `cif` / `hvga` / `vga` / `svga` / `xga` / `sxga` / `uxga` |
| `CAM_QUALITY` | `12` | 0–63,越小通常画质越好、JPEG 越大 |
| `CAM_QUALITY_MAX` | `0` | `0` 固定画质;设为 `CAM_QUALITY`–63 时按发送回压在两者之间调节压缩,保留彩色和分辨率。拥塞时每 5 秒增大最多 3 档,连续稳定 15 秒才恢复 1 档 |
| `CAM_MAX_FPS` | `0` | 取帧与发送的最高节拍,1–60;`0` 不限帧。仅限制消费节拍,不修改传感器时钟 |
| `CAM_CLOCK_PROFILE` | `default` | `default` 保留驱动时钟;`vga30` 使用 OV5640 VGA 约 30.09 fps PLL 配置,保持 HTS/VTS 和读出窗口,SYSCLK 61 MHz / PCLK 15.25 MHz。切换后需重新核对曝光行数 |
| `CAM_FB_COUNT` | `3` | PSRAM 帧缓冲数,允许 2 或 3。当前交付选三缓冲,保留发送突发时的采集余量;双缓冲曾在短测降低部分回显延迟,但用户长程运行反馈峰值丢帧明显,保留为试验选项 |
| `CAM_GRAYSCALE` | `0` | `1` 使用传感器灰度效果,保留硬件 JPEG;`0` 彩色 |
| `CAM_EXPOSURE_EV` | `0` | OV5640 自动曝光目标偏置:`-1` / `-2` 约对应 −1.1 / −1.9 EV;不是标定后的绝对 EV,也不保证快门缩短 |
| `CAM_AE_LEVEL` | 未设置 | 可选 OV5640 驱动 AE 档位,−5–5;仅与 `CAM_EXPOSURE_EV=0` 同用。`-1` 将目标从 55 小幅降至 45,目标比值约 −0.29 EV,保留自动曝光/增益;未设置时按 EV 参数处理。实际快门和亮度变化取决于场景 |
| `CAM_SHUTTER_LINES` | `0` | OV5640 固定快门,单位为传感器曝光行;`0` 自动曝光,非零保持自动增益。默认 VGA / HTS 2060 下 220 行估算约 10 ms;数值不能超过当前 VTS |
| `CAM_EXPOSURE_MAX_LINES` | `0` | OV5640 自动曝光上限,`0` 保留驱动默认。非零保持 AEC/AGC 联动并禁用夜景延帧,不能与固定快门同时使用。默认 VGA 下 440 行约 20 ms;`vga30` 下 592 行约 20 ms。实际曝光由场景和抗频闪步长决定 |
| `CAM_GAIN_CEILING` | `0` | OV5640 自动增益上限,`0` 保留驱动默认;1–63,单位为倍,实际上限略低于整数档。短曝光遇到室内弱光时可提高,但暗部噪点和 JPEG 负载可能增加 |
| `CAM_HTS` | `0` | OV5640 水平总长度试验参数,`0` 使用驱动默认。缩短消隐会影响彩色稳定性,本机 1500 / 1800 出现绿紫闪烁,已撤回;几何检查通过不代表可用 |
| `UDP_PACKET_GAP_US` | `0` | 成功提交视频数据报之间的最小间隔;0–5000 μs,须为调度 tick 的整数倍(当前为 1000 μs)。配合有界帧等待,避免集中突发写入队列 |
| `UDP_RETRY_BUDGET_US` | `8000` | 每帧、所有分片及订阅者共用的让出调度重试预算,0–25000 μs,须为 tick 整数倍;`0` 为非阻塞失败即弃。总发送检查期限为 25 ms,采集帧龄期限为 100 ms;分片间隔也计入期限,不会额外延长 |
| `CAM_THERMAL_LIMIT_C` | `75` | 芯片内部温度达到该值时减半帧率(向上取整),下降 5°C 后恢复;40–95,`0` 禁用。原先不限帧时降至 8 fps |
| `CAM_VSYNC_INVERT` | `1` | 驱动反相 VSYNC;硬件已反相时填 `0` |
| `WIFI_AUTH` | 自动选择 | `none` / `wep` / `wpa` / `wpa2` / `wpawpa2` / `wpa3` / `wpa2wpa3` / `wapi` |
| `WIFI_TX_POWER_DBM` | SDK 默认上限 | 可选整数 2–20 dBm;当前交付请求 15 dBm,本板驱动回读 15.00 dBm;启动及重连时应用,拥塞和温控逻辑不降低此上限 |
`.env` 使用简单的 `KEY=value`;值含空格、`#` 或 `$` 时用单引号包围,不使用变量展开或命令替换。脚本会加载 `.env`,直接 `cargo build` 不会自动读取它。上述参数在编译时写入固件,修改后需要重新编译和烧录。`.env` 已被 Git 忽略;固件也包含凭据,分享前应重新配置。
运行时断线恢复不重置摄像头、曝光或增益。首次尝试立即发起,失败后按 1、2、4、8、16、30 秒退避;关联与 DHCP 分别有 15 秒期限。恢复 IP 后丢弃旧采集帧、重建 socket 并重新广播,由客户端自动续订。启动时首次联网仍使用扫描与认证模式探测流程。实现依据 [ESP-IDF 断线事件处理说明](https://docs.espressif.com/projects/esp-idf/en/v5.5.5/esp32s3/api-guides/wifi.html#wifi-event-sta-disconnected)。
## 编译固件
在仓库根目录执行:
```powershell
# Windows PowerShell
$env:CARGO_TARGET_DIR = 'C:\s3vt'
.\scripts\build.ps1
```
```bash
# Linux / macOS
./scripts/build.sh
```
脚本执行 `cargo build --release --locked`,目标为 `xtensa-esp32s3-espidf`。首次编译包含 Rust 标准库、ESP-IDF 和相机组件,耗时较长。
Windows 的 ESP-IDF 构建输出路径需不超过 88 字符,因此示例使用短目录 `C:\s3vt`(需可写,且不与其他项目共用)。`subst` 不能绕过该检查。下表的 `<TARGET_DIR>` 在 Windows 示例中为 `C:/s3vt`,Linux / macOS 默认是仓库的 `target`。每次新开 Windows 终端都要设置相同的 `CARGO_TARGET_DIR`。
| 产物 | 位置 |
| --- | --- |
| 固件 ELF | `<TARGET_DIR>/xtensa-esp32s3-espidf/release/esp32`(没有 `.exe` 后缀) |
| Bootloader | `<TARGET_DIR>/xtensa-esp32s3-espidf/release/bootloader.bin` |
| 分区表 BIN | `<TARGET_DIR>/xtensa-esp32s3-espidf/release/partition-table.bin` |
| 分区表源文件 | 根目录 `partitions.csv` |
ELF 供 `espflash` 烧录与符号解析,不能当作 BIN 直接写入 Flash。Bootloader、分区表与 App 应来自同一次构建。
## 烧录与串口验证
### 1. 查找串口
```text
espflash list-ports
```
Windows 常见 `COM5`;Linux 为 `/dev/ttyACM0` 或 `/dev/ttyUSB0`;macOS 为 `/dev/cu.usbmodem...`。关闭其他串口监视器,Linux 需具备串口访问权限。
### 2. 烧录 ELF
本项目使用自定义分区表,显式传入 `partitions.csv` 和本次构建的 Bootloader。将示例端口替换为实际端口:
```powershell
$targetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { 'target' }
espflash flash --chip esp32s3 --port COM5 --flash-size 16mb --flash-mode dio --flash-freq 40mhz --partition-table partitions.csv --bootloader "$targetDir/xtensa-esp32s3-espidf/release/bootloader.bin" --monitor "$targetDir/xtensa-esp32s3-espidf/release/esp32"
```
```bash
target_dir="${CARGO_TARGET_DIR:-target}"
espflash flash --chip esp32s3 --port /dev/ttyACM0 --flash-size 16mb --flash-mode dio --flash-freq 40mhz --partition-table partitions.csv --bootloader "$target_dir/xtensa-esp32s3-espidf/release/bootloader.bin" --monitor "$target_dir/xtensa-esp32s3-espidf/release/esp32"
```
连接失败时按住 **BOOT**,点按 **RESET / EN**,松开 BOOT 后重试。正常启动应看到相机识别、Wi-Fi 获取 IP 和 `ESP32 camera is live` 日志。没有客户端时,`0 clients subscribed` 属于正常状态。
仅查看日志:
```text
espflash monitor --port COM5
```
### 3. 导出 BIN
`espflash save-image --merge` 可导出从 `0x0` 写入的合并镜像。完整命令、独立镜像偏移与测试方法见 [DEVELOPMENT.md](DEVELOPMENT.md)。
保留的 `scripts/flash.sh` 针对**特定坏 Flash 模组**,依赖 macOS 环境,会修改分区并重编译。普通板卡使用上述标准流程;坏板限制见 [HARDWARE.md](HARDWARE.md#损坏-flash-模组的兼容处理)。
## 客户端与装甲板
桌面端入口如下。安装依赖、主机 target 设置与功能说明见 [desktop/README.md](desktop/README.md):
```text
cd desktop
pnpm install
pnpm app
```
装甲板是独立的 **ESP32-C3 / RISC-V** 工程,不能使用 S3 的 ELF。`ARMOR_TEAM=0/1` 在编译期决定红 / 蓝队伍。接线、规则与构建烧录见 [armor/README.md](armor/README.md)。
## 常见问题
| 现象 | 检查方法 |
| --- | --- |
| 提示缺少 `esp-1.97.0.0` | 按上述命令安装指定名称的工具链,重新打开终端后检查版本 |
| `espup` 查询 GitHub 版本返回 403 | 对上述固定版本安装命令追加 `--skip-version-parse`,或稍后重试 |
| 找不到 `ldproxy` / `libclang` | 安装链接工具;Unix 加载 `export-esp.sh`,Windows 重开终端 |
| ESP-IDF 路径或版本错误 | 清除旧 `IDF_PATH` 或指向 v5.5.5 SDK |
| Windows 路径过长 | 缩短仓库和工具链路径,见开发说明 |
| Wi-Fi 连不上 | 核对 2.4 GHz、凭据和认证方式,再重新编译烧录 |
| 相机无输出 / 初始化失败 | 核对板型接线、供电与 Octal PSRAM |
| `NO-EOI`、几帧后停止 | 确认 `CONFIG_CAMERA_PSRAM_DMA=n` |
| 在线但没有画面 | 检查客户端订阅、UDP 5600、防火墙及 AP 隔离 |
| 开启 VPN / 虚拟网卡后设备在线但无画面 | 使用支持按已发现设备 IP 定向续订的客户端;仅发送 `255.255.255.255` 广播可能走错网卡 |
| 改 C 组件后无变化 | 清理 `esp-idf-sys` 构建缓存后重编,见开发说明 |
| 主机项目被编为 Xtensa | 显式指定宿主 target,见各子项目说明 |
用 `python scripts/udp_probe.py 10` 可独立检查视频协议;装甲板用 `python scripts/armor_probe.py 15`。这些脚本需要板卡在线,构建通过不代表已完成硬件验证。
测量帧率和丢帧可运行以下命令,将 IP 换成板卡实际地址。对照测试时先退出桌面客户端,避免额外订阅者增加无线负载:
```text
python scripts/perf_probe.py 192.168.1.139 --seconds 30 --json perf.json
```
脚本定向续订,支持保留 VPN / TUN 的环境。输出包含完整帧率、帧损失率、接收间隔和组装耗时;这些时间不等于摄像头到屏幕的端到端延迟。
运动图传保持 `CAM_GRAYSCALE=0`,优先使用自动曝光上限和自动增益协同控制拖影。室内强制 −1 EV / 固定短快门在本机过暗,已撤回;提高增益上限也会增加噪点。需要限流时可设置 `CAM_MAX_FPS=15`,追求帧率时使用 `0`;可启用有界 JPEG 压缩调节应对发送回压,分片间隔按网络实测选择。灰度效果仍保留硬件 JPEG,不是改为 RGB565 或降低 JPEG 采样位数。75°C 是主动减载阈值,温度来自 ESP 芯片内部,不能代表摄像头或环境温度,也不是芯片损坏温度规格。五分钟彩色对照与此前中止的灰度长测见 [STABILITY.md](STABILITY.md)。
## 版本号与改动日志
版本号和 `CHANGELOG.md` 不手写,从 git 历史算:
```sh
node scripts/version.mjs --write # 计算并写回 version.json / CHANGELOG / 客户端版本
node scripts/version.mjs --check # 只校验(CI 与发布前用)
```
- 版本号由提交类型决定:`BREAKING CHANGE:` → major,`feat` → minor,`fix`/`perf` → patch。
- 构建号 = `git rev-list --count HEAD`,单调、不撞车。
- 同一次计算还产出 `version.json`(固件 `build.rs` 读它)、`CHANGELOG.json`、
`desktop/src/lib/version.ts`。
**固件与客户端的版本是联动过的**:板子每 2 秒广播的 `Hello` 里带着语义化版本、构建号与
它编译进去的线路协议号;客户端读出来显示在设置与裁判端每一格下面,协议对不上时直接说明
原因("板子是协议 2,客户端是协议 1,画面会被丢弃,请重新烧录"),而不是只表现为没有画面。
细节见 [`desktop/README.md`](desktop/README.md#版本与改动日志算出来的不是写出来的) 与
[`protocol/README.md`](protocol/README.md#通告里的版本信息)。
## 文档索引
- [CHANGELOG.md](CHANGELOG.md):改动记录与历史修复。
- [HARDWARE.md](HARDWARE.md):接线、内存、Flash 与驱动限制。
- [DEVELOPMENT.md](DEVELOPMENT.md):镜像导出、测试、协议与性能测量。
- [PERFORMANCE.md](PERFORMANCE.md):保留 TUN 的图传排查、参数取舍与实际测试结果。
- [STABILITY.md](STABILITY.md):限帧、灰度与温度减载,长时间运行测试。
- [REALTIME.md](REALTIME.md):阻塞时取最新帧、超期丢弃和客户端积压处理。
- [POWER.md](POWER.md):15 dBm 回退、功率与距离的关系,以及发送拥塞证据。
- [LATENCY.md](LATENCY.md):当前约 30 fps 配置、Wi-Fi 重连及实际画面链路延迟对照。
- [protocol/README.md](protocol/README.md):UDP 线路格式及帧重组。
- [armor/README.md](armor/README.md):装甲板固件与规则。
- [desktop/README.md](desktop/README.md):桌面端配置、录制与排查。
- [docs/firmware-provisioning-evaluation.md](docs/firmware-provisioning-evaluation.md):自动烧录、坏区规避与 SSID 配置 patch 的工具评估、MVE 与测试矩阵。
- [docs/firmware-maintenance.md](docs/firmware-maintenance.md):已实现的固件维护页、官方 bundle 格式、坏块规避流程与实机验证记录。