feat(spi_link): 添加SPI Link v2 PC自测框架并优化协议稳定性
SPI Link v2 Self-Test / selftest (push) Has been cancelled
SPI Link v2 Self-Test / selftest (push) Has been cancelled
添加SPI Link v2的PC端自测功能,包含交叉校验基准测试,覆盖HELLO/OTA/CAN/PING等 多种协议场景。同时重构了health monitor中的多字段读取逻辑,消除了volatile变量 的撕裂读问题,并增强了BLE OTA的数据传输可靠性。 refactor(health_monitor): 修复heap hook多字段撕裂读问题 采用portMUX_CRITICAL临界区实现多字段一致快照读取,消除任务上下文与heap hook 之间的数据竞争。修改了HookSnap结构体设计,通过一次性拷贝volatile变量避免 跨字段组合时的数据不一致。JSON输出中明确标注estimated值的置信度。 fix(ble_ota): 实现DATA_IN丢包负反馈机制 当malloc失败或队列满时发送缺口ACK通知前端按completed_bytes续传,避免 静默丢包导致的8秒超时重传风暴。增加节流机制防止负ACK在重传叠加时打爆 NimBLE mbuf池,提升低内存环境下的OTA传输稳定性。 refactor(spi_master): 从堆分配改为固定Job池管理 将SPI worker的动态堆分配Job改为固定大小的静态池管理,避免频繁的 calloc/free操作影响实时性。新增acquireJobSlot/releaseJobSlot机制 管理池槽生命周期,提升SPI通信的可靠性和内存使用效率。
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
## 目标
|
||||
重新生成 `G:/Robot_v2/DCU/SPEC/code_optimization_audit_2026-07-19.html`,让它反映当前工作树的代码状态(已修改的 `health_monitor.*`、`spi_master.*`、`ble_ota_data.cpp`、`version.h`、`tools/js/common/connection.js` 等)。
|
||||
|
||||
## 步骤
|
||||
1. 在 `G:/Robot_v2/DCU` 下运行生成器(不带 `--verify`,按你刚才的选择):
|
||||
```bash
|
||||
python tools/gen_code_optimization_audit.py
|
||||
```
|
||||
- 默认输出到 `SPEC/code_optimization_audit_<今天日期>.html`,即 `code_optimization_audit_2026-07-20.html`(今天 2026-07-20)。
|
||||
- 注意:现有文件名是 `..._2026-07-19.html`,新生成的会是 `..._2026-07-20.html`,不会覆盖旧文件。
|
||||
|
||||
2. 检查脚本输出:
|
||||
- 看 `[audit] files=... lines=...`、findings 数量、是否出现 `重复 finding ID` 或锚点漂移错误。
|
||||
- 脚本内置 HTML 结构与内部链接自校验,失败会返回非 0。
|
||||
|
||||
3. 如有锚点漂移(某个 finding 引用的行号在改动后对不上原证据),脚本会报错并指出哪个 finding;此时需要我先核对对应源码再决定是调整脚本里的锚点还是接受新行号——会先汇报给你,不会私自改 finding 逻辑。
|
||||
|
||||
4. 生成成功后,简要汇报:
|
||||
- 新报告路径
|
||||
- findings 数量与 P0/P1/P2 分布是否变化
|
||||
- 与旧报告的关键差异(例如新增/消失的 finding、行号变化)
|
||||
- 旧报告 `2026-07-19.html` 是否保留(默认保留)
|
||||
|
||||
## 不做的事
|
||||
- 不带 `--verify`(不跑 ESP32/GD32 构建、SPI 自测、前端语法检查)
|
||||
- 不删除旧报告 `2026-07-19.html`
|
||||
- 不改生成器脚本本身
|
||||
- 不做 git 提交
|
||||
@@ -0,0 +1,116 @@
|
||||
## 目标
|
||||
修复 heap alloc hook 与 health_mon 任务之间共享诊断状态的「无原子采样协议」问题:hook 写多个 volatile 字段组成的「最后一击」快照,reader(`maybeRecordHeapLowWater`)在 FreeRTOS mutex 下读取——但 hook 不获取该 mutex,因此 mutex 不提供与 hook 写的互斥,多字段快照可被撕裂(`hook_fired=1` 配上上一次的 `hook_alloc_size`/`caller`/`free_after`)。同时 `g_est_free` 是只减不增的近似值,命名上未与精确实测区分。
|
||||
|
||||
## 现状(已确认)
|
||||
- ESP32-C3 **单核** RISC-V(`CONFIG_FREERTOS_UNICORE=y`),无 SPIRAM,`CONFIG_HEAP_USE_HOOKS=y`。
|
||||
- hook (`esp_heap_trace_alloc_hook`, `health_monitor.cpp:77-115`) 在 ESP-IDF heap 分配器**自带的临界区内**被调用(heap spinlock 已禁中断),且可能从 IRAM/cache-off 上下文进入 → **禁止 FreeRTOS/flash 调用**,禁止在 hook 内再加锁。
|
||||
- hook 写入的共享状态(`health_monitor.cpp:50-68`):
|
||||
- `g_est_free`(近似空闲,只减不增,周期 resync)
|
||||
- 「最后一击」快照:`g_hook_min_est` / `g_hook_alloc_size` / `g_hook_caller_addr` / `g_hook_free_after` / `g_hook_fired`
|
||||
- 环形 `g_recent_allocs[4]` + `g_recent_alloc_w` / `g_recent_alloc_n`
|
||||
- reader 路径:`maybeRecordHeapLowWater()` 在 `LockGuard lock(m_mutex)`(FreeRTOS mutex)内读取上述所有字段(`689-704`)。**`m_mutex` 不能阻断 hook**(hook 不获取它,且 hook 可能在中断禁用上下文)→ 撕裂读。
|
||||
- reader 还有 `dumpHeapLowWaterJson()`(httpd 任务)读 `m_heapLowWater`:这条路径已由 `m_mutex` 正确保护(writer 也是 `maybeRecordHeapLowWater` 在同一 `m_mutex` 下写 `m_heapLowWater`),无撕裂问题,**不需改**。
|
||||
- 现有同类模式:`CpuStats::getSnapshot()`(`cpu_stats.cpp:67-69`)用 `portENTER_CRITICAL(&m_mux)` 做多字段快照,**reader-only 临界区**,与 writer(也是任务上下文)互斥。本库无 seqlock / std::atomic / memory fence,统一依赖单核字宽原子性 + 临界区。
|
||||
|
||||
## 设计:reader-only `portENTER_CRITICAL` 快照(与 CpuStats 一致)
|
||||
|
||||
### 关键约束分析
|
||||
- 单核 + hook 已在 heap 临界区内(禁中断)→ reader 用 `portENTER_CRITICAL` 禁中断即可与 hook 互斥:reader 持有临界区期间,任何 `malloc`(从而 hook)无法打断(因为 malloc 需取 heap spinlock,而 heap spinlock 也禁中断;但更直接:reader 禁中断后,hook 所在的 malloc 调用路径在 reader 释放前无法进入——单核上禁中断即阻止所有任务/ISR 抢占)。
|
||||
- reader 在临界区内**只做 volatile 拷贝,不分配、不调任何 FreeRTOS/flash 函数**,保持极短热路径(与 hook 约束对称)。
|
||||
- hook 端**不动**:不加锁、不改逻辑,保持 IRAM 安全。hook 写多个字段天然在 heap 临界区内 = 对 reader 而言是「原子写」(reader 禁中断期间 hook 跑不了;hook 跑时 reader 也进不来)。
|
||||
|
||||
### 实施步骤
|
||||
|
||||
#### 1. `health_monitor.h` — 新增 portMUX 与 reader 快照结构
|
||||
- 在 `HealthMonitor` private 区新增静态 spinlock:
|
||||
```cpp
|
||||
static portMUX_TYPE s_hookSnapMux; ///< 保护 hook 共享状态的多字段读快照(单核 = 禁中断)
|
||||
```
|
||||
- 声明一个私有 reader 快照结构(把 hook 端多字段一次性拷出来),供 `maybeRecordHeapLowWater` 使用:
|
||||
```cpp
|
||||
struct HookLastHitSnap {
|
||||
uint32_t alloc_size;
|
||||
uint32_t caller_addr;
|
||||
uint32_t free_after;
|
||||
uint32_t fired;
|
||||
uint32_t est_free;
|
||||
uint32_t min_est;
|
||||
uint32_t recent_n;
|
||||
RecentAllocSnap recent[kRecentAllocCap];
|
||||
};
|
||||
```
|
||||
(`RecentAllocSnap` / `kRecentAllocCap` 当前在 .cpp 匿名 namespace,需提到头文件或在 .cpp 内做快照——**选择后者**:快照结构与 `s_hookSnapMux` 的定义都留在 .cpp 匿名 namespace,reader 在 .cpp 内取快照,不污染头文件。`portMUX_TYPE` 是 FreeRTOS 类型,头文件已 include `freertos/FreeRTOS.h`,但 spinlock 实例可放 .cpp 匿名 namespace。)
|
||||
|
||||
**修正**:`s_hookSnapMux` 放 .cpp 匿名 namespace(与现有 `g_hook_*` 同作用域),头文件零改动。快照结构也放 .cpp 匿名 namespace。
|
||||
|
||||
#### 2. `health_monitor.cpp` — reader 临界区快照
|
||||
- 匿名 namespace 新增:
|
||||
```cpp
|
||||
static portMUX_TYPE s_hookMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
```
|
||||
- 新增 reader 辅助函数(**任务上下文专用**,禁中断 + 极短):
|
||||
```cpp
|
||||
HookSnap captureHookSnapshot()
|
||||
{
|
||||
HookSnap s = {};
|
||||
portENTER_CRITICAL(&s_hookMux);
|
||||
s.alloc_size = g_hook_alloc_size;
|
||||
s.caller_addr = g_hook_caller_addr;
|
||||
s.free_after = g_hook_free_after;
|
||||
s.fired = g_hook_fired;
|
||||
s.est_free = g_est_free;
|
||||
s.min_est = g_hook_min_est;
|
||||
s.recent_n = g_recent_alloc_n;
|
||||
for (uint32_t i = 0; i < s.recent_n && i < kRecentAllocCap; ++i) {
|
||||
s.recent[i] = g_recent_allocs[i];
|
||||
}
|
||||
portEXIT_CRITICAL(&s_hookMux);
|
||||
return s;
|
||||
}
|
||||
```
|
||||
- 新增 writer 辅助(在 reader 临界区内做 hook 状态的「确认消费」):
|
||||
```cpp
|
||||
void consumeHookFired(uint32_t new_min_est)
|
||||
{
|
||||
// 必须在 portENTER_CRITICAL(&s_hookMux) 内调用
|
||||
g_hook_min_est = new_min_est;
|
||||
g_hook_fired = 0;
|
||||
}
|
||||
```
|
||||
- 修改 `maybeRecordHeapLowWater` 的 hook 块(`688-730`):把 `LockGuard lock(m_mutex)` 内的裸 volatile 读 + `g_hook_min_est`/`g_hook_fired` 写,替换为:
|
||||
1. 在 `m_mutex` 外、进入快照写入前,调用 `captureHookSnapshot()` 取一份一致快照(`HookSnap hs`)。
|
||||
2. 用 `hs.*` 字段填充 `rec.hook_*` 与 `big_*` 搜索(不再裸读 `g_hook_*` / `g_recent_allocs`)。
|
||||
3. 在 `m_mutex` 内写完 `rec` 后,再用一个 `portENTER_CRITICAL(&s_hookMux)` 块调用 `consumeHookFired(g_est_free)` 重置 `g_hook_min_est` / `g_hook_fired`(保持原语义:消费后把 min_est 抬到当前 est,fired 清零,避免同一窗口重复记)。
|
||||
- 注意 `g_est_free` 在 reader 临界区外已被 `hookResyncEstFree(free_heap)` 更新过(`336`),`consumeHookFired` 读它时需在 `s_hookMux` 内读以保证与 hook 写一致——因此 `consumeHookFired` 内读 `g_est_free`。
|
||||
|
||||
#### 3. 命名区分 estimated vs measured + 置信度标注
|
||||
- `HeapLowWaterRecord.hook_free_after`(`health_monitor.h:84`)注释已说明「est,非精确实测」。增强为在 JSON 输出与字段名上显式区分:
|
||||
- JSON 字段 `hook_free_after` → 改名为 `hook_est_free_after`(明确 est);前端 schema 同步说明。
|
||||
- 在 `dumpHeapLowWaterJson` 的字段输出中,紧随其后加一个 `hook_confidence` 字段:值为字符串 `"est_low_watermark"`(hook 命中时)或 `"none"`(未命中),明确告知前端这是近似值、非精确实测。
|
||||
- `health_monitor.h` 注释补充:`hook_free_after`(保留成员名以减小 diff)注释行加「**置信度:est 低水位近似值**,误差 = resync 周期内 free 路径未记账量;前端应标注 estimated」。
|
||||
- `g_est_free` 注释(`56` 行)补充:「**estimated**,非 measured;health_mon 周期用 `esp_get_free_heap_size()` 实测纠偏。误差上界 ≈ resync 周期(5s)内总 alloc 字节」。
|
||||
- 在文件头 `46-48` 的「记录策略」注释块补一段「**采样协议**:reader(`maybeRecordHeapLowWater`)用 `portENTER_CRITICAL(&s_hookMux)` 禁中断取一致快照,与 hook(在 heap 临界区内写)互斥;hook 端不加锁、保持 IRAM 安全」。
|
||||
|
||||
#### 4. 文档化 hook 可调用 API / IRAM 约束
|
||||
- 文件头 `36-48` 注释块已说明「禁止 FreeRTOS/flash」。补充明确契约条目:
|
||||
```
|
||||
// ★ hook 可调用约束(IRAM 安全契约):
|
||||
// - 禁止:FreeRTOS API、flash/rodata 访问、mutex/semaphore、长函数。
|
||||
// - 允许:DRAM volatile 标量读写、__builtin_return_address、纯算术。
|
||||
// - 写共享状态天然原子:hook 已在 heap 临界区内(禁中断),reader 用 s_hookMux 互斥。
|
||||
// - 嵌套分配:hook 内禁止任何可能触发 malloc 的调用(否则递归进 hook,armed 标志不防递归——
|
||||
// 当前 hook 体内无分配调用,保持之)。
|
||||
```
|
||||
|
||||
## 验收
|
||||
- 编译通过(ESP-IDF build),无新 warning。
|
||||
- 并发压力下(WiFi + OTA + CAN 并发)多次抓取 `/system/heap_low_log`:`hook_fired=1` 的记录中 `hook_alloc_size`/`hook_caller_addr`/`hook_est_free_after` 三者一致(来自同一次 alloc),不再出现「fired=1 但 size=上一次值」的撕裂。
|
||||
- JSON 输出含 `hook_est_free_after` 与 `hook_confidence` 字段,前端可明确区分 estimated vs measured。
|
||||
- hook 热路径长度不变(未加锁、未加函数调用),IRAM 安全契约保持。
|
||||
- reader 临界区极短(仅 volatile 拷贝,~10 个字段 + ≤4 条 recent),不持 `m_mutex` 同时持 `s_hookMux`(`captureHookSnapshot` 在 `m_mutex` 外调用),避免锁序问题。
|
||||
|
||||
## 影响范围
|
||||
- 仅改 `main/monitor/health_monitor.cpp`(hook 块、`maybeRecordHeapLowWater`、注释、新增匿名 namespace 辅助)与 `main/monitor/health_monitor.h`(`hook_free_after` 注释增强、JSON 字段名说明)。
|
||||
- `dumpHeapLowWaterJson`:JSON 字段 `hook_free_after` → `hook_est_free_after`,新增 `hook_confidence`。前端(IndexedDB 累积端)需同步字段名——属可观测性 schema 变更,需告知前端。
|
||||
- 不动 hook 函数体逻辑、不动 `m_mutex` 保护的 `m_heapLowWater` 写入路径、不动 ESP-IDF 构建配置。
|
||||
- 不引入 seqlock / std::atomic(与库现有单核 volatile + 临界区惯例一致)。
|
||||
@@ -0,0 +1,84 @@
|
||||
## 目标
|
||||
把 BLE OTA DATA_IN 路径的 OOM/队列满从“静默丢包”变为“可恢复协议反馈”,让前端在一个窗口内收到缺口反馈并按 `completed_bytes` 续传退避,避免整窗口 8s 超时重发风暴。仅做方案 A(负 ACK 反馈),不上固定缓冲池;节流复用现有窗口节流思路。
|
||||
|
||||
## 现状核对(已核实)
|
||||
- 平台 ESP32-C3,无 PSRAM,堆紧张;稳态堆水位需实测后才能评估是否上池,故本计划不涉及池。
|
||||
- `ble_ota_data.cpp:597` `malloc(recv_len)` 失败仅 `DEBUG_ERR`,回调仍返回 0(ATT 成功)。
|
||||
- `ble_ota_data.cpp:602` `xQueueSend` 失败同样静默:`free(heap_buf)` + `DEBUG_WARN("...丢...等重传")`,无协议反馈。两条路径都需修,不只 malloc 路径。
|
||||
- 客户端窗口内 seq≥1 用 `writeValueWithoutResponse`(即发即忘),**收不到 ATT Error Response**;只有 seq=0 用 `writeValueWithResponse`。因此“返回 ATT 错误码”对窗口内丢包无效,必须靠 DATA_OUT 通知通道发窗口 ACK。原报告“返回 ATT 错误”建议在窗口路径不成立,本计划以通知 ACK 为主。
|
||||
- 客户端 `transfer.js` 已支持按 `completed_bytes` 续传(`resumeSeq = ceil(lastAckBytes / payloadSize)`,最多 3 次重试,窗口 64 包),所以设备端多发一个缺口 ACK 不会破坏客户端语义,只会让续传更早开始。
|
||||
- 现有 `sendWindowAck(cmd, seq, completed_bytes)` 在 `ble_ota_data.cpp:440` 已封装好,seq 取 `s_reorder.expected - 1`(最近连续落盘序号),负 ACK 直接复用即可,无需新协议字段、无需改客户端。
|
||||
- 现有节流是“进度触发 + 每 `BLE_OTA_ACK_WINDOW`(64) 包边界”(见 process_esp32_packet:691-712),不是时间间隔。丢包路径发生在写入前、无进度,无法复用边界判定,需为负 ACK 引入轻量节流(避免重传叠加时 ACK 通知打爆 NimBLE mbuf 池)。
|
||||
|
||||
## 改动点(集中在 `main/server/ble_ota_data.cpp`)
|
||||
|
||||
### 1. 新增丢包计数(可观测性,满足“池耗尽/丢包有计数”验收)
|
||||
- 在文件静态区新增:
|
||||
- `static uint32_t s_ota_drop_malloc_fail = 0;` // malloc 失败累计
|
||||
- `static uint32_t s_ota_drop_queue_full = 0;` // 队列满累计
|
||||
- `static uint32_t s_neg_ack_sent = 0;` // 负 ACK 发送累计
|
||||
- 在 `ble_ota_session_reset()`(已有复位点,约 line 414)中清零,与 reorder 状态同生命周期。
|
||||
- 通过现有 health monitor 或 DEBUG_DBG 暴露(按现有日志风格,不新增上报通道)。
|
||||
|
||||
### 2. 新增负 ACK 节流状态
|
||||
- 复用“每 64 包/最小间隔”思路,但丢包路径无 `completedPackets`,故用包计数 + 最小时间间隔双约束:
|
||||
- `static uint32_t s_neg_ack_count = 0;` // 自上次发负 ACK 以来累计丢包数
|
||||
- `static int64_t s_last_neg_ack_us = 0;` // 上次负 ACK 时刻(`esp_timer_get_time()`)
|
||||
- 节流规则(在丢包路径触发):
|
||||
- 累计丢包 ≥ `BLE_OTA_ACK_WINDOW`(64) **或** 距上次 ≥ 200ms 时,发一次负 ACK 并复位计数/时间。
|
||||
- 否则只计数、不发,避免重传风暴期间 ACK 通知打爆 mbuf 池。
|
||||
- 常量定义在文件内 `static constexpr`(与 `BLE_OTA_BUF_SIZE` 同位置,约 line 580),不污染 app_config.h。
|
||||
- 时间用 `esp_timer_get_time()`(已是项目内惯用 API,见 reorder/health 用法)。
|
||||
|
||||
### 3. 新增负 ACK 发送辅助函数
|
||||
- 在 `sendWindowAck`(line 460 后)下方新增:
|
||||
```cpp
|
||||
// 丢包路径专用:发缺口 ACK 让前端按 completed_bytes 续传退避,而非等 8s 超时。
|
||||
// 节流:每 BLE_OTA_ACK_WINDOW 个丢包或 200ms 最多一次,防止 ACK 通知打爆 mbuf 池。
|
||||
static void sendNegativeAckIfDue(uint8_t cmd);
|
||||
```
|
||||
- 实现:
|
||||
- 读取 `s_reorder.expected`:>0 时 `ackSeq = expected - 1`,`completed = Esp32OtaCore::getReceived()`;expected==0(首包都没落盘)时 ackSeq=0、completed=0(等价于让前端从头续发,客户端已支持此语义)。
|
||||
- 节流判定通过后调用 `sendWindowAck(cmd, ackSeq, completed)` 并 `s_neg_ack_sent++`。
|
||||
- GD32 路径同样适用(`sendWindowAck` 已按 cmd 区分),但 GD32 的 expected 状态在 `s_ble_gd32_meta`,需在函数内按 cmd 分支取 expected/received;为简单起见,先实现 ESP32 路径,GD32 路径仅计数不发 ACK(GD32 是中继转发,缺口由 GD32 侧自行反馈),并在注释中标注。
|
||||
|
||||
### 4. 改造 `ble_ota_data_in_access` 丢包路径(line 596-605)
|
||||
- malloc 失败分支:`s_ota_drop_malloc_fail++` → `sendNegativeAckIfDue(cmd)`(cmd 从 batch 头解析,见下)→ 保留 `DEBUG_ERR`。
|
||||
- 队列满分支:`s_ota_drop_queue_full++` → `sendNegativeAckIfDue(cmd)` → 保留 `DEBUG_WARN`。
|
||||
- **cmd 解析问题**:当前 line 566-596 的回调只把 mbuf 拷到 `buf`,未解析 cmd。负 ACK 需要 cmd 字节。两种做法:
|
||||
- (a) 回调里解析前 7 字节头取 cmd(与 worker 中 `ble_ota_process_data_batch` 解析逻辑一致,可抽一个小 helper `parse_cmd_from_batch(buf, recv_len)`)。
|
||||
- (b) 不解析,统一用 `BLE_OTA_CMD_ESP32_DATA`(0x01)(C3 自身 OTA 是主路径,GD32 经 BLE 中继较少)。
|
||||
- **选 (a)**:抽 helper,正确性优先,避免误报 cmd 让前端续传走错分支。helper 放在 `ble_ota_data.cpp` 内 static,不暴露到头文件。
|
||||
|
||||
### 5. 不改动的部分
|
||||
- 回调返回值仍为 0(窗口内 write-without-response 返回错误对端也收不到,且会污染 ATT 层;负 ACK 走通知通道才是正反馈)。
|
||||
- 不引入固定缓冲池(C3 无 PSRAM,池规模需实测,本计划范围内不做)。
|
||||
- 不改客户端 `tools/js/ble/*`(现有续传语义已兼容)。
|
||||
- 不改协议字段/UUID/通知格式。
|
||||
|
||||
## 风险与权衡
|
||||
- **负 ACK 抖动**:极端低堆时丢包密集,节流会让部分丢包延迟反馈,客户端可能仍等部分超时。权衡已用 200ms 兜底,可接受;若实测仍风暴,再调常量。
|
||||
- **首包未落盘时负 ACK**:expected==0 时发 `ackSeq=0, completed=0`,等价“一切重来”。客户端 seq=0 用 write-with-response,本就等首包 ACK;此时负 ACK 不会误导。
|
||||
- **GD32 路径**:本计划 GD32 仅计数不发负 ACK,避免改动 GD32 中继反馈语义;如需对称可后续扩展。
|
||||
- **节流状态并发**:`s_neg_ack_*` 仅在 NimBLE host task 回调中读写(单线程),worker task 不写,无需锁。
|
||||
|
||||
## 验收
|
||||
1. 构造低堆注入:临时在 malloc 前插 `if (s_inject_fail) { heap_buf=nullptr; }` 开关(测试桩,提交前移除),或用 `heap_caps_set_minimum_size` 压低堆水位。
|
||||
2. 触发 OTA 上传,观察:
|
||||
- 日志出现 `DATA_IN 堆内存不足` / `DATA_IN满` + `sendNegativeAckIfDue` 发送记录。
|
||||
- 客户端在窗口内(≤200ms~8s)收到缺口 ACK,`lastAckBytes` 推进,从 `resumeSeq` 续传,**不出现整窗口 8s 超时**。
|
||||
- `s_ota_drop_*` 计数器增长,负 ACK 后传输恢复,无永久卡死。
|
||||
3. 正常路径回归:不注入失败时 ACK 行为、传输耗时、收尾 SHA256 不变。
|
||||
4. 池耗尽有计数:`s_ota_drop_queue_full` / `s_ota_drop_malloc_fail` 在 health monitor/日志可查。
|
||||
|
||||
## 实施顺序
|
||||
1. 加丢包计数 + `ble_ota_session_reset` 清零。
|
||||
2. 加负 ACK 节流状态 + `sendNegativeAckIfDue`(ESP32 路径)。
|
||||
3. 抽 `parse_cmd_from_batch` helper。
|
||||
4. 改造 `ble_ota_data_in_access` 两条丢包路径接入负 ACK。
|
||||
5. 低堆注入测试验收,移除注入桩。
|
||||
6. (可选)GD32 路径对称负 ACK,视后续需要。
|
||||
|
||||
## 涉及文件
|
||||
- `main/server/ble_ota_data.cpp`(主要改动:line 414 reset、line 460 后新增函数、line 596-605 丢包路径、line 580 附近常量)
|
||||
- 不改:`ble_ota_priv.h`、`ble_ota.cpp`、`app_config.h`、客户端 JS。
|
||||
Reference in New Issue
Block a user