fix(vd960DBN): OTA 下载失败 — hex 提取兼容 json.dumps 空格

现象: DBNMQTTool 第一片 ota_data code=1 crc/gap, received=0, 重试全败

根因: 工具 json.dumps 默认带空格 ("data": "hex"), 设备端 strstr 找
"data":" 无空格格式 → 提取失败 → hexbuf 空 → 解码失败 code=1
(simple_parse_json 容错空格所以其他命令正常, 只有手工 strstr 踩坑)

修复:
- ota_srv.c/h: 新增 ota_json_extract_hex() 兼容有无空格 (扫描 data 键跳过空白)
- iot_mqtt_srv.c: ota_data 改用该函数, 删 strstr 手工提取 + hp 残留
- 单测 +test_json_extract_hex: 无空格/带空格(事故场景)/512hex/找不到 4 场景

验证: 单测 11/11 PASS; 语法 0 错误; 待板级重测下载
This commit is contained in:
wangfq
2026-08-20 14:13:06 +08:00
parent 2484a03326
commit c305ca651f
5 changed files with 547 additions and 459 deletions
@@ -124,6 +124,9 @@ int ota_cmd_flash(uint8_t slot, uint8_t force);
/* 分片落盘用: hex → bin (n 字节), 成功 0 */
int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n);
/* 从 MQTT JSON 中提取 ota_data 的 data 字段 hex 值 (兼容 "data":"..." 与 "data": "...")
返回 hex 字符串长度, 0=未找到/为空 */
int ota_json_extract_hex(const char *json, char *out, int out_len);
/* CRC-32/ISO-HDLC (逐位实现, 零查表 RAM) */
uint32_t ota_crc32(const uint8_t *data, uint32_t len);
@@ -755,7 +755,6 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_
} else if (strcmp(cmd_str, "ota_data") == 0) {
/* V1.08: 分片下发 (256B/片, 单片 CRC32; 重复片幂等, 乱序拒收指示续传) */
const char *hp;
uint32_t offset = 0, crc32 = 0;
int rc;
memset(tmp, 0, sizeof(tmp));
@@ -764,14 +763,8 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_
memset(tmp, 0, sizeof(tmp));
simple_parse_json(json, "\"crc32\"", tmp);
if (strlen(tmp) > 0) crc32 = (uint32_t)strtoul(tmp, NULL, 10);
/* hex 值提取: data 对象内字段是 "data":"...", 顶层 data 对象是 "data":{ — 前者唯一 */
hp = strstr(json, "\"data\":\"");
memset(_ota_io.hexbuf, 0, sizeof(_ota_io.hexbuf));
if (hp) {
hp += 8; /* 跳过 "data":" */
strncpy(_ota_io.hexbuf, hp, sizeof(_ota_io.hexbuf) - 1);
{ char *q = strchr(_ota_io.hexbuf, '"'); if (q) *q = '\0'; }
}
/* hex 值提取: ota_json_extract_hex 兼容 "data":"..." 与 "data": "..." (json.dumps 带空格) */
ota_json_extract_hex(json, _ota_io.hexbuf, (int)sizeof(_ota_io.hexbuf));
rc = ota_cmd_data(offset, crc32, _ota_io.hexbuf);
if (rc == 0) {
const OtaMeta *m = ota_meta();
@@ -99,6 +99,39 @@ int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n)
return 0;
}
/* 从 MQTT JSON 提取 ota_data 的 data 字段 hex 值。
⚠ 2026-08-20 事故: json.dumps 默认带空格 ("data": "hex"),
strstr("\"data\":\"") 无空格匹配 → 提取失败 → 单片 CRC 全错。
本函数扫描 "data" 键后跳过任意空格/冒号, 兼容两种格式。 */
int ota_json_extract_hex(const char *json, char *out, int out_len)
{
const char *p = json;
int n = 0;
if (!json || !out || out_len <= 0) return 0;
out[0] = '\0';
while (*p) {
if (strncmp(p, "\"data\"", 6) == 0) {
const char *q = p + 6;
while (*q == ' ' || *q == '\t') q++;
if (*q == ':') {
q++;
while (*q == ' ' || *q == '\t') q++;
if (*q == '"') {
q++; /* 跳过起始引号 */
while (*q && *q != '"' && n < out_len - 1) {
out[n++] = *q;
q++;
}
out[n] = '\0';
return n;
}
}
}
p++;
}
return 0;
}
/*===========================================================================
* 元数据读写 (双备份, 同扇区 0x010000~0x0103FF)
* 写: 显式擦扇区 (45ms) + NoCheck 写主 + 写备份
+22
View File
@@ -4,6 +4,28 @@
>
> 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接
## 2026-08-20 — OTA 下载失败修复:hex 提取不兼容 json.dumps 空格(单片 CRC 全错)
### 现象(板级)
DBNMQTTool 下载第一片即失败:`ota_data code=1 crc/gap error`received=0,重试 4 次全败。
### 根因
工具 `json.dumps` 默认**带空格**`"data": "hex..."`),设备端手工 `strstr(json, "\"data\":\"")` 找**无空格**格式 → 匹配不到 → hexbuf 空 → `ota_hex_decode` 失败 → code=1。其他命令走 `simple_parse_json`(容错空格)不受影响,只有 OTA 的 hex 提取踩坑。
### 修复
- `ota_srv.c/h` 新增 `ota_json_extract_hex()`:扫描 `"data"` 键后跳过任意空格/冒号/引号,兼容 `"data":"..."``"data": "..."` 两种格式
- `iot_mqtt_srv.c` ota_data 分支改用该函数(删除 strstr 手工提取 + 残留 hp 变量)
- 单测新增 `test_json_extract_hex`:无空格/带空格(事故场景)/512 hex 满片/找不到,4 场景全过
### 教训
嵌入式 JSON 协议字段提取**统一用 simple_parse_json 或兼容空格的扫描**,不要 strstr 精确匹配——上位机 JSON 序列化器(json.dumps/不同语言库)的空格行为不可控。
---
## 2026-08-20 — 联网复位事故修复:OTA 命令缓冲合并 union(RAM 90% 栈余量不足)
### 现象(板级)
+37
View File
@@ -148,6 +148,42 @@ static int test_hex_decode(void)
return 0;
}
static int test_json_extract_hex(void)
{
char out[520];
/* 无空格 (紧凑格式) */
CHK(ota_json_extract_hex(
"{\"msg_id\":402,\"cmd\":\"ota_data\",\"ts\":1,\"data\":{\"target\":\"loop\",\"offset\":0,\"crc32\":1,\"data\":\"0102aBff\"}}",
out, sizeof(out)) == 8);
CHK(strcmp(out, "0102aBff") == 0);
/* 带空格 (json.dumps 默认) — 2026-08-20 事故场景 */
CHK(ota_json_extract_hex(
"{\"msg_id\": 402, \"cmd\": \"ota_data\", \"ts\": 1, \"data\": {\"target\": \"loop\", \"offset\": 0, \"crc32\": 1, \"data\": \"0102aBff\"}}",
out, sizeof(out)) == 8);
CHK(strcmp(out, "0102aBff") == 0);
/* 512 hex 满片 */
{
char json[700];
char hex[513];
int i, n;
for (i = 0; i < 256; i++) sprintf(hex + i * 2, "%02x", i);
snprintf(json, sizeof(json),
"{\"msg_id\":1,\"cmd\":\"ota_data\",\"data\":{\"data\":\"%s\"}}", hex);
n = ota_json_extract_hex(json, out, sizeof(out));
CHK(n == 512);
CHK(strcmp(out, hex) == 0);
}
/* 找不到 data 字段 */
CHK(ota_json_extract_hex("{\"cmd\":\"ota_status\"}", out, sizeof(out)) == 0);
CHK(out[0] == '\0');
printf("PASS test_json_extract_hex\n");
return 0;
}
static int test_meta_restore(void)
{
flash_init();
@@ -434,6 +470,7 @@ int main(void)
int fails = 0;
fails += test_crc32();
fails += test_hex_decode();
fails += test_json_extract_hex();
fails += test_meta_restore();
fails += test_session_flow();
fails += test_begin_resume();