Files
story/docs/device-ota-performance-20261001.md
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

62 lines
8.3 KiB
Markdown
Raw Permalink 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.
# EchoEar OTA 下载性能优化
日期:2026-10-01。设备:`KP01-020026091601`,型号 `ECHOEAR-S3`。
## 实现
- 已确认清单型号、版本、有效期、大小、SHA256 和同源 HTTPS,HTTP 200 与 Content-Length 匹配后,使用 `esp_ota_begin(target, state.size)` 按已知镜像大小预擦备用应用分区。SDK 可合并对齐的 64KiB 擦除;运行应用分区另有地址检查,禁止作为目标。
- 写入缓冲使用 `MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT`,8KiB 优先、4KiB 降级,两个分配均失败即保留旧应用并报错。SPI1 的 SDK 非 DRAM 写入分支每次通过 32 字节临时缓冲拷贝,显式内部内存避免落入这条路径。未记录旧固件缓冲的实际位置,不能据此单独证明此前慢速的主因。
- HTTP 接收缓冲由 4KiB 增至 16KiB,写入缓冲保持内部内存。第二轮将 TCP 接收邮箱从 6 增至 24,运行时 Wi-Fi 动态接收缓存从 16 增至 32;TCP 窗口仍为 32KiB,静态接收、发送缓存与 BA 窗口保持原值。8KiB 写入块对齐 SDK 常规写入块大小,保留内部内存余量。
- 下载预算从分配和连接前开始,包含预擦、读取、写入、SHA 校验及 OTA 结束。每次 HTTP 等待按剩余预算裁剪,只有 EAGAIN 在同一连接与偏移上有限重试。SDK HTTP 的超时是每次 transport 等待的上限;生产代码在每次应用读取前后检查总预算,不宣称能在底层一次阻塞读取的中途主动取消。
- 累计并记录预擦、读取、写入、SHA、OTA 结束、最长阶段调用及完整用时;每 30 秒最多一条进度日志。成功摘要写入原有 100 字节持久化消息,经过重启确认再发云端成功回执。后续日志提交或启动选择失败明确覆盖摘要,保留真实失败原因。
- 不改变 OTA 日志结构、型号/签名/摘要/证书校验、应用槽、启动健康确认、A/B 回退及绑定/配网规则。SDK begin 可能先分配句柄再遇到擦除失败,非零句柄会被 abort 回收。
方案参考 [ESP-IDF 5.5.1 官方 OTA 性能建议](https://github.com/espressif/esp-idf/blob/v5.5.1/docs/en/api-reference/system/ota.rst);实现依据本机同版本 SDK 的 `app_update/esp_ota_ops.c`、`spi_flash/esp_flash_api.c`、`hal/spi_flash_hal.c` 和 `esp_http_client/esp_http_client.c`。
## 验证
完整生产 C 与真实 SHA256 的 22 组故障测试通过 GCC 13.3、AddressSanitizer、UndefinedBehaviorSanitizer 和 float-cast-overflow。覆盖已知大小与非运行槽预擦、预擦错误句柄清理、预擦/连接/校验期限、8KiB/4KiB 降级与尾包、全部分配失败、读写/摘要失败、成功计时跨重启保存及后续失败原因。
`controls.17` 启动包和 `controls.18` OTA 包均通过 ESP-IDF 5.5.1 编译,大小 3,760,896 字节。前一版 16 的 38 项应用/配置来源中,仅 `main/device_ota.c` 和 `main/firmware_version.h` 变化;两新包之间仅版本字符串变化。SDK 配置、分区表、模型、音频和屏幕资源均未修改。
优化逻辑已应用程序方式安装到同一设备的 `ota_0 / 0x10000`:先核对原 16MiB 恢复备份、MAC、有效 OTA 选择、原 15 应用摘要与分区,再只写 17 应用;完整读回 SHA256 为 `43178315a21126608023cc18fba3ca6352e83096d46707cf0bcbc6a3440d9fd1`。分区表和 OTA 数据写前写后相同,原绑定数 1、配置 19/19、音量 56 均保留,云端确认 17 在线。
真实云端 17 → 18 已失败:任务 `2a3ca992-c246-46ab-98ee-dc5606ade2c4` 在接收 1,372,872 / 3,760,896 字节后出现 HTTP 读取错误,耗时约 355 秒,设备保留 17 并继续在线。这次 USB 只采集非敏感日志,18 镜像由设备通过 Wi-Fi/HTTPS 接收,没有通过 USB 写入。断开 USB 后的独立供电验证尚未完成。
设备实测预擦耗时 4.69 秒;前 605,685 字节的写入仅 1.149 秒,读取累计 206.511 秒,说明剩余慢速主要发生在网络读取。匿名候选下载连接累计重传 464 次、拥塞窗口降至 1;接收窗口非零,不能只凭这一连接快照确定丢包位置。源码核对未发现服务端 3 分钟硬期限,暂不修改生产 API 超时。
第二轮针对接收容量调整,增加慢速读取、短读取和 EAGAIN 的纯数字日志。完整生产 C 的 22 组故障测试及原有网络重连、BLE 配网生命周期、超时恢复、配网端点回归均通过。
真实 19 → 20 任务 `aebecc4c-a07a-4fc9-a37a-5afbc4fb2703` 已在约 31.3 秒接收完 3,760,896 字节并通过完整 SHA256。30.036 秒时已收 3,612,672 字节,累计读取 16.637 秒、写入 6.939 秒,预擦 3.065 秒。匿名候选 TCP 流的 ACK 在 10 秒内增加 957,138 字节,队列清零,拥塞窗口 18 → 25,无新增重传;这支持接收端调整的效果,但不能替代任务成功回执。
这轮随后在 SDK 的 `esp_ota_end` 镜像验证阶段触发内部栈断言:`esp_image_verify → bootloader_mmap → esp_mmu_map → esp_cache_freeze_caches_disable_interrupts`,控制任务原有 12KiB PSRAM 栈不满足要求。因此任务失败并保留 19,不能将下载完成称为升级成功。
最终修复将 END、启动分区选择及失败回退的镜像验证放在临时 8KiB `MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT` 任务中。先释放 HTTP/TLS、SHA 和内部写入缓冲,再创建验证任务,完成后由原任务调用 `vTaskDeleteWithCaps` 回收任务栈和 TCB;分配失败仍 abort 活跃句柄。已调用 `esp_ota_end` 的错误返回不再重复 abort。回退任务使用无重启 API,回收后由原任务按原有成功条件重启,保留健康时限、日志和启动选择顺序。编译后的 21/22 应用均为 3,761,200 字节,两包只差版本字符串;精确生产 C 的 24 组 OTA 及 4 套联网/BLE 回归通过三个 sanitizer。主机调度模拟不证明 DRAM/MMU 行为,最终安装路径另经真实硬件验收。
## 最终实物验收
2026-10-01 09:43:24 北京时间,从已登录家庭端发起真实 21 → 22 云端任务 `18216193-939f-47a4-b49b-96e9002eb05e`。09:44:31 成功回执确认运行 `1.7.0-controls.22 / code 6`,备用 `ota_1 / 0x410000` 镜像经过完整摘要、SDK 安装校验与重启健康确认。镜像大小 3,761,200 字节,SHA256 `c13ae3b2aab18431466d40b5d60b101a8c3345e438be87ef474f85ac8653616f`。
| 实测项目 | 用时 |
| --- | --- |
| 下载、写入、SHA 和 SDK 安装校验 | 41.003 秒 |
| 备用分区预擦 | 6.087 秒 |
| 累计网络读取 | 22.656 秒 |
| 累计 Flash 写入 | 7.267 秒 |
| 累计 SHA 校验 | 1.409 秒 |
| 用户发起至重启后的成功回执 | 67.394 秒 |
整包处理吞吐约 91.7KB/s,约为旧失败尝试 6.32KB/s 的 14.5 倍。比较基线是旧版本的未完成传输,不能描述为同一完整镜像的成功 A/B 速度测试;以上是单次实测,网络状况会影响时间。21/22 与 19/20 的接收设置相同。
成功后 MQTT 在线、支持 OTA,配置 19/19、音量 56/56、原绑定数 1 均保留。网页显示当前版本 22 和“升级完成”,成功消息持久化原始计时。USB 当时接着提供供电和非敏感日志,22 镜像由设备通过 Wi-Fi/HTTPS 下载,没有通过 USB 写入。**尚未完成断开 USB 的独立供电验收、受控实物启动失败回退和手机热点切换**;不将软件故障测试冒称为这些实物验收。
最终可上传应用包及已脱敏验收记录在 [固件 22 目录](../artifacts/firmware/1.7.0-controls.22/manifest.json)。发布使用真实 SSH 维护服务身份,四条 service/null 成功审计及完整公网回读均通过,原 11 个生产容器不变,临时 helper 和凭据文件均已清理。
## 对照与解释
- 旧固件失败尝试:430.039 秒传送 2,719,221 字节,约 6.3KB/s;7 分钟期限内只完成约 72%。这是包含设备反压的端到端数据,未分开测出网络和 Flash 耗时。
- 同路线电脑对照:16 固件 3,758,224 字节,5.812 秒,646.6KB/s,完整 SHA256 匹配。对照缩小排查范围,但不能替代实物优化测速。
- 最终以完整镜像校验、启动确认、云端成功回执及新版本上线为验收条件。速度使用设备实际完成时间;总时间包含连接、预擦和校验,不能只选取某次最快读取作为整包速度。
受限验证资料保存在 `E:/Codex/story/.local-evidence/ota-speed-20261001`;含设备恢复数据的备份不可作为发布固件分发。