Files
simba-rmic-2026/docs/maintenance/firmware.md
T
sloray 6a0eb14b28
构建客户端 / 检查(类型 + 单元测试) (push) Waiting to run
构建客户端 / Linux x64(deb + AppImage) (push) Blocked by required conditions
构建客户端 / Windows x64(NSIS 安装包) (push) Blocked by required conditions
构建客户端 / macOS 通用(.app + .dmg) (push) Blocked by required conditions
docs: 整理项目文档并统一界面表述
将项目说明按主题集中到 docs,补齐根目录 README 和 AGENTS,移除弃用方案并保留有效测试记录与未实现提案。同步文档索引、引用及改动日志生成路径,README 增加 GitHub Markdown 导航和 Mermaid 图。

- fix(client): 统一状态与操作文案,录制和截图成功后再显示完成提示
- test(client): 按文件名核对媒体类型,避免依赖时间戳排序

验证:23 份项目文档、144 处本地引用、27 条外链、2 张 Mermaid 图;前端构建和 12 项测试;截图与录制的 4 条异步反馈路径;Windows Rust 编译检查和后端测试。后端 40 项返回成功,其中 3 项 ffmpeg 集成用例因本机缺少工具而未实际执行。
2026-10-19 11:26:26 +08:00

10 KiB
Raw Blame History

固件维护页:实现说明

范围

维护页通过 USB 串口更新设备,不支持设备联网自升级。以下命令均从仓库根目录执行,另有 cd 的示例除外。

  • App 镜像逐字节原样写入,不支持动态修改 SSID;
  • 当前固件没有 OTA 分区、esp_ota 接收端或控制通道;
  • 使用已构建的官方 bundle,无需修改固件源码或重新编译;客户端只修改设备当前分区表的 factory 偏移和大小;
  • 目标芯片以 ESP32-S3 为当前实现,chip 字段与 espflash 的芯片探测已经为 C3 预留, 但 C3 的 bundle/布局尚未验收。

为什么坏块规避不需要重编译

App 镜像不包含自己的分区偏移;偏移只在 0x8000 的分区表里。所以同一份 App 可以 写到旧 App 之后的空闲槽位,只需要改分区表里的 factory 偏移,不需要重新构建。

旧 scripts/flash.sh 之所以重编,是因为它把新偏移写回 partitions.csv 再重建。 客户端直接修改从设备读取的当前分区表;包内的 partition-table.bin 只作参考模板与校验:

  1. 从设备读当前分区表(0xC00 字节);
  2. 解析条目,找到 factory;
  3. 把 factory 的 offset/size 改成目标槽位;
  4. 重算分区表 MD5(本仓库实测在表内 0x70,不是 0xC00 末尾);
  5. 写回并整段回读。

烧录顺序与坏块检测

探测设备 → 读当前分区表/App 描述 → 选候选槽位(旧 App 之后)
→ 备份旧分区表 + 旧 App
→ 对每个候选槽位:
     写 App → 整段回读逐字节比较
     通过 → 选中;失败 → 记录坏字节/坏扇区,换下一个
→ 只有选中槽位通过,才写 patch 后的分区表
→ 回读分区表 → 复位 → 抓启动日志
  • 候选槽位按 64 KiB 对齐,优先 >= 0x200000,并跳过与旧 App 重叠的区间;
  • 当前布局(factory 0x70000,size 0x120000)下,第一个候选是 0x200000, 新分区 size 0x140000;旧 App 保留在 0x70000,可回滚;
  • 实际镜像写读校验是最终判据:图案扫描会误报“当前镜像恰好可写”的槽位;主流程 写真实镜像再回读;
  • 不使用 merged image:填充区的坏字节也会让整包失败;只写 App 和分区表;
  • 分区表只在 App 回读通过后才切换;切换失败/启动失败可用备份恢复旧表,旧 App 未覆盖。

已知坏点

  • 0xFADE7:bit 5–7 无法正确编程(已确认);
  • 0x10863:bit 6 读回为 1,尚未确认是否为永久故障,见硬件说明。

两个地址都在 0x100000 以下;0x200000 槽位天然避开它们。客户端仍会对写入的 App 整段回读,不依赖“已知坏点表”。

官方 .zip 固件包

编译完成后由 scripts/package_firmware.py 打成单文件 .zip,客户端直接读取,不需要先解压。旧目录包仍兼容。

zip 根目录:

bundle.json
esp32s3-app.bin
partition-table.bin   # 0xC00 字节
bootloader.bin        # 可选;客户端默认不写
BUILD-INFO.json       # 可选;展示版本/构建信息

客户端读取 zip 时的硬限制:

  • 条目数 ≤ 32;只接受根目录单层文件名,拒绝 ..、绝对路径、盘符、/、\;
  • bundle.json ≤ 64 KiB;分区表必须正好 0xC00;App 不得超过 appPartitionSize;
  • zip CRC + appSha256 / partitionTableSha256 双重校验;
  • 只解压到 app cache 下的临时目录,读完立即删除;不把 zip 内容写进用户目录;
  • BundleInfo 会显示 sourceKind=zip、整包 SHA256 与条目数。

bundle.json 示例(由 scripts/make_bundle.py 生成):

{
  "format": 1,
  "name": "ESP32-S3 图传固件",
  "version": "vga30-fb3-realtime-tx15+4794bed",
  "chip": "esp32s3",
  "app": "esp32s3-app.bin",
  "appSha256": "…",
  "partitionTable": "partition-table.bin",
  "partitionTableSha256": "…",
  "bootloader": "bootloader.bin",
  "bootloaderSha256": "…",
  "appPartitionSize": "0x140000",
  "flashSize": "16mb",
  "flashMode": "dio",
  "flashFrequency": "40mhz",
  "notes": "…"
}

客户端会校验 format、chip、flashSize、appSha256、partitionTableSha256; 不匹配就拒绝,不接受任意 .bin。本地构建生成的示例位于 target/firmware/bundle.json,不随源码提交。

编译后打包:

# 只打包(会先调用 make_bundle.py 刷新 bundle.json)
python scripts/package_firmware.py --firmware target/firmware

# 构建完成后自动打包
scripts/build.ps1 -Package
scripts/build.sh --package

产物:

target/firmware/camfw-<chip>-<variant>-<rev>-<yyyymmdd>.zip
target/firmware/camfw-...zip.sha256

zip 使用固定条目顺序、固定 mtime 与 deflate level 9,同一个输入应得到同一个整包 SHA256,便于核对分发的固件包是否一致。package_firmware.py 会复用 make_bundle.py,不维护第二套 manifest 逻辑。

注意:bundle 里的 partition-table.bin 是参考模板;客户端实际写的是设备当前 分区表的 patch 版本,以保留设备的 NVS/PHY 布局。bundle 的分区表 SHA 仍会校验。

界面

入口有三处,不要求先选身份或绑定摄像头:

  • 首次启动的身份选择页:打开固件维护;
  • 用户端设置密码页:同上;
  • 主界面右上角:固件。

页面流程:

  1. 自动列出串口并选中 Espressif 设备;
  2. 读取设备 显示芯片、MAC、Flash、安全启动/加密状态、当前 App、分区表、空闲区间;
  3. 选择 .zip 固件包 选择编译后打包的单文件;选目录(兼容旧包) 仍可用;
  4. 生成计划 显示目标槽位、候选槽位、切换后的 factory、警告;
  5. 勾选是否备份旧 App;开始烧录 二次确认后执行;
  6. 进度条 + 运行日志 + 结果;失败时显示坏字节/坏扇区、备份目录,并可 恢复旧分区表。

读取设备和烧录都会复位一次;开始前应停止录制,视频流会短暂中断。

代码位置

层 文件
后端:探测/分区表/候选槽位/写入/回读/备份/恢复 desktop/src-tauri/src/maintenance.rs
Tauri 命令注册、--maintenance-probe/--maintenance-bundle/--maintenance-plan desktop/src-tauri/src/main.rs
前端 IPC 与 DTO desktop/src/lib/maintenance.ts
维护页 desktop/src/pages/MaintenancePage.tsx
样式 desktop/src/styles/maintenance.css
manifest 生成 scripts/make_bundle.py
zip 打包 scripts/package_firmware.py

后端使用 espflash 4.6 库(default-features = false, features = ["serialport"])、 serialport 4.10 和 zip 2.6(只开 deflate);Tauri 用 tauri-plugin-dialog 选文件/目录。

构建与验证

Windows x64:

cd desktop
pnpm install
pnpm check
pnpm build
cd src-tauri
cargo check --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
cargo test  --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]' maintenance::tests

固件侧打包:

python scripts/package_firmware.py --firmware target/firmware
# 或:scripts/build.ps1 -Package / scripts/build.sh --package

运行 GUI:

cd desktop
pnpm tauri dev --target x86_64-pc-windows-msvc -- --config 'unstable.build-std=[]'

desktop/.cargo/config.toml 里的 build.target 是 macOS ARM 的默认值;Windows 需要上面的 --target 覆盖。tauri.conf.json 的 bundle targets 是 "all", 各平台打包时使用自己的默认 bundle 类型。

无窗口诊断(仍会复位设备):

cd desktop/src-tauri
.\target\x86_64-pc-windows-msvc\debug\cam-desktop.exe --maintenance-probe COM8
# 只校验 zip/目录包,不连设备
.\target\x86_64-pc-windows-msvc\debug\cam-desktop.exe --maintenance-bundle C:\path\to\camfw-*.zip
.\target\x86_64-pc-windows-msvc\debug\cam-desktop.exe --maintenance-plan COM8 C:\path\to\camfw-*.zip
# 只写 App + 回读,不切分区表;设备仍从旧 App 启动
.\target\x86_64-pc-windows-msvc\debug\cam-desktop.exe --maintenance-write-test COM8 C:\path\to\camfw-*.zip 0x200000

已验证 / 未验证

原记录已在测试板(COM8,ESP32-S3 v0.2,MAC 90:70:69:f8:f2:14,16 MB, Secure Boot / Flash Encryption 均未启用)上验证:

  • --maintenance-probe:读取芯片、MAC、分区表、App 描述;
  • --maintenance-plan:选中 0x200000 / 0x140000,patch 后 factory 为 0x200000,旧 App 保留;
  • --maintenance-write-test COM8 … 0x200000:实机写入 App 并整段回读, writeOk=true、verifyOk=true、0 处不一致;分区表未动,设备仍从旧 App 启动;
  • .zip 打包与读取:camfw-…zip 5 项、758,149 字节,--maintenance-bundle 校验通过, sourceKind=zip、整包 SHA256 与条目数正确;
  • 离线单元测试:分区表解析、MD5 重算、候选槽位、尺寸解析、zip 路径白名单,共 7 个;
  • 前端 tsc --noEmit、vite build,后端 cargo check 通过。

尚未执行完整实机切换(写 patch 后分区表 → 复位 → 启动日志)。App 写入/回读 已经在同一块板上验证;切换步骤的风险集中在 0x8000 的 3 KiB 分区表写入,写入后会 回读,失败会尝试恢复旧表。如果新 App 启动失败,页面会给出 备份目录和 恢复旧分区表 按钮。

边界

  • 不做运行时坏块重映射/FTL;ESP32 ROM bootloader 按分区线性读取,不能在 App 镜像 中间跳过坏扇区。这里的“屏蔽”= 选一个实际镜像写读校验通过的整槽位。
  • 不写 bootloader、不碰 NVS/PHY;bundle 里的 bootloader 只做展示/校验。
  • 分区表写入是单点风险:写入后会回读;失败会尝试恢复旧表;极端断电情况下可用 ROM 下载模式重新写入,备份在 %APPDATA%/com.simbarobotics.rmic2026/backups/(Windows)或 对应平台的 app data 目录。
  • 网络 OTA 仍需要一次性改固件(OTA 分区 + esp_ota 接收端 + 控制通道),不在本次范围。