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:
@@ -124,6 +124,9 @@ int ota_cmd_flash(uint8_t slot, uint8_t force);
|
|||||||
|
|
||||||
/* 分片落盘用: hex → bin (n 字节), 成功 0 */
|
/* 分片落盘用: hex → bin (n 字节), 成功 0 */
|
||||||
int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n);
|
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) */
|
/* CRC-32/ISO-HDLC (逐位实现, 零查表 RAM) */
|
||||||
uint32_t ota_crc32(const uint8_t *data, uint32_t len);
|
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) {
|
} else if (strcmp(cmd_str, "ota_data") == 0) {
|
||||||
/* V1.08: 分片下发 (256B/片, 单片 CRC32; 重复片幂等, 乱序拒收指示续传) */
|
/* V1.08: 分片下发 (256B/片, 单片 CRC32; 重复片幂等, 乱序拒收指示续传) */
|
||||||
const char *hp;
|
|
||||||
uint32_t offset = 0, crc32 = 0;
|
uint32_t offset = 0, crc32 = 0;
|
||||||
int rc;
|
int rc;
|
||||||
memset(tmp, 0, sizeof(tmp));
|
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));
|
memset(tmp, 0, sizeof(tmp));
|
||||||
simple_parse_json(json, "\"crc32\"", tmp);
|
simple_parse_json(json, "\"crc32\"", tmp);
|
||||||
if (strlen(tmp) > 0) crc32 = (uint32_t)strtoul(tmp, NULL, 10);
|
if (strlen(tmp) > 0) crc32 = (uint32_t)strtoul(tmp, NULL, 10);
|
||||||
/* hex 值提取: data 对象内字段是 "data":"...", 顶层 data 对象是 "data":{ — 前者唯一 */
|
/* hex 值提取: ota_json_extract_hex 兼容 "data":"..." 与 "data": "..." (json.dumps 带空格) */
|
||||||
hp = strstr(json, "\"data\":\"");
|
ota_json_extract_hex(json, _ota_io.hexbuf, (int)sizeof(_ota_io.hexbuf));
|
||||||
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'; }
|
|
||||||
}
|
|
||||||
rc = ota_cmd_data(offset, crc32, _ota_io.hexbuf);
|
rc = ota_cmd_data(offset, crc32, _ota_io.hexbuf);
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
const OtaMeta *m = ota_meta();
|
const OtaMeta *m = ota_meta();
|
||||||
|
|||||||
@@ -99,6 +99,39 @@ int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n)
|
|||||||
return 0;
|
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)
|
* 元数据读写 (双备份, 同扇区 0x010000~0x0103FF)
|
||||||
* 写: 显式擦扇区 (45ms) + NoCheck 写主 + 写备份
|
* 写: 显式擦扇区 (45ms) + NoCheck 写主 + 写备份
|
||||||
|
|||||||
@@ -4,6 +4,28 @@
|
|||||||
>
|
>
|
||||||
> 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接
|
> 项目定位: 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% 栈余量不足)
|
## 2026-08-20 — 联网复位事故修复:OTA 命令缓冲合并 union(RAM 90% 栈余量不足)
|
||||||
|
|
||||||
### 现象(板级)
|
### 现象(板级)
|
||||||
|
|||||||
@@ -148,6 +148,42 @@ static int test_hex_decode(void)
|
|||||||
return 0;
|
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)
|
static int test_meta_restore(void)
|
||||||
{
|
{
|
||||||
flash_init();
|
flash_init();
|
||||||
@@ -434,6 +470,7 @@ int main(void)
|
|||||||
int fails = 0;
|
int fails = 0;
|
||||||
fails += test_crc32();
|
fails += test_crc32();
|
||||||
fails += test_hex_decode();
|
fails += test_hex_decode();
|
||||||
|
fails += test_json_extract_hex();
|
||||||
fails += test_meta_restore();
|
fails += test_meta_restore();
|
||||||
fails += test_session_flow();
|
fails += test_session_flow();
|
||||||
fails += test_begin_resume();
|
fails += test_begin_resume();
|
||||||
|
|||||||
Reference in New Issue
Block a user