fix: MQTT协议 V1.02 同步 — 字段名缩短 + 时间单位修正

- docs/DLD960_IoT_MQTT协议.md: 字段缩写 + 时间单位 5ms→50ms
- vd960DBN/iot_mqtt_srv.c: 6字段缩短对齐协议 (level/iscar/freq/diff/sens/cndtn)
- vd960DBN/tcp_json_srv.c: gap_ms5/passtime_ms → gap/passtime
- vd960DBN/loop_uart_proto.h: 注释 5ms→50ms
- DBNMQTTool/main.py: 模拟样本数据字段同步
This commit is contained in:
wangfq
2026-07-09 10:18:03 +08:00
parent 466ce00680
commit c515638032
7 changed files with 275 additions and 150 deletions
+32
View File
@@ -107,3 +107,35 @@ DBNMQTTool/
| 协议Topic订阅/发布 | ✅ c19e465 | | 协议Topic订阅/发布 | ✅ c19e465 |
| 自定义Topic订阅/发布 | ✅ c19e465 | | 自定义Topic订阅/发布 | ✅ c19e465 |
| 固件联调 | ✅ vd960DBN IoT 模式稳定上报 | | 固件联调 | ✅ vd960DBN IoT 模式稳定上报 |
---
## 2026-07-07 — MQTT V1.01 适配 + 稳定性修复
### 1. MQTT 协议 V1.01 适配
- Topic 从多主题(`/loop_data`, `/event_report`, `/heartbeat`, `/cmd`…)压缩为双主题 `dld960/{sn}/dev`(上报)、`dld960/{sn}/srv`(下发)
- `manage_mqtt_recv_message()` 实现 V1.01 协议命令分发,支持 `cmd`/`Method` 双格式
- MQTT PUBLISH 接收修复:topic 过滤 + 通配符路由修正
### 2. MqttClient 重构:tkinter 回调 → QObject 信号/槽
**问题**: `paho.mqtt` C 层回调直接操作 Qt UI 控件 → `RuntimeError: wrapped C/C++ object has been deleted` 崩溃。
**修复**:
- `MqttClient` 继承 `QObject`,使用 Qt 信号/槽机制
- `on_message``message_received` 信号 → 主线程 slot 安全更新 UI
- 所有 publish 路径增加通用异常捕获,防止未捕获异常导致静默崩溃
- 回退 Qt 信号排队方案,改用 `QMetaObject.invokeMethod` + 日志追踪定位阻塞点
### 3. DeviceManager 死锁修复
`threading.Lock` 在单线程 Qt 事件循环中自锁 → `RLock``add_device()``remove_device()` 同线程互调时 `Lock` 阻塞自身。
### 4. UI/UX 改进
- **日志增强**: 收发打印 topic + payload 详情,调试信息更直观
- **查询响应自动回填**: 设备信息查询后自动填充 SN 等输入框
- **主动上报配置**: `report_config` 面板支持完整 7 参数独立开关
- `right` 局部变量 → `self._notebook`,修复 tab 引用丢失
+4 -4
View File
@@ -458,11 +458,11 @@ class MainWindow(QMainWindow):
"ts": 1719000100, "ts": 1719000100,
"data": { "data": {
"channels": [ "channels": [
{"ch": 1, "freq_level": "high", "has_car": False, "loop_ok": True, {"ch": 1, "level": "high", "iscar": False, "loop_ok": True,
"freq_current": 105280, "freq_diff": 20, "sensitivity": 7, "condition": 0, "freq": 105280, "diff": 20, "sens": 7, "cndtn": 0,
"misc": {"type": "time", "value": 0}}, "misc": {"type": "time", "value": 0}},
{"ch": 2, "freq_level": "mid_high", "has_car": True, "loop_ok": True, {"ch": 2, "level": "mid_high", "iscar": True, "loop_ok": True,
"freq_current": 98700, "freq_diff": 1500, "sensitivity": 7, "condition": 2, "freq": 98700, "diff": 1500, "sens": 7, "cndtn": 2,
"misc": {"type": "time", "value": 350}}, "misc": {"type": "time", "value": 350}},
] ]
} }
+89 -87
View File
@@ -462,47 +462,47 @@ dld960/{dev_serial}/{direction}
"channels": [ "channels": [
{ {
"ch": 1, "ch": 1,
"sensitivity": 7, "sens": 7,
"freq_level": "high", "level": "high",
"loop_delay": 0, "delay": 0,
"output_mode": "exist", "output": "exist",
"exist_mode": 0, "exist": 0,
"direction_mode": 0, "dir": 0,
"safe_mode": 0, "safe": 0,
"function_mode": 0 "fun_mode": 0
}, },
{ {
"ch": 2, "ch": 2,
"sensitivity": 7, "sens": 7,
"freq_level": "mid_high", "level": "mid_high",
"loop_delay": 5, "delay": 5,
"output_mode": "enter_pulse", "output": "enter_pulse",
"exist_mode": 10, "exist": 10,
"direction_mode": 0, "dir": 0,
"safe_mode": 0, "safe": 0,
"function_mode": 0 "fun_mode": 0
}, },
{ {
"ch": 3, "ch": 3,
"sensitivity": 5, "sens": 5,
"freq_level": "mid_low", "level": "mid_low",
"loop_delay": 0, "delay": 0,
"output_mode": "exist", "output": "exist",
"exist_mode": 0, "exist": 0,
"direction_mode": 1, "dir": 1,
"safe_mode": 5, "safe": 5,
"function_mode": 0 "fun_mode": 0
}, },
{ {
"ch": 4, "ch": 4,
"sensitivity": 8, "sens": 8,
"freq_level": "low", "level": "low",
"loop_delay": 10, "delay": 10,
"output_mode": "leave_pulse", "output": "leave_pulse",
"exist_mode": 15, "exist": 15,
"direction_mode": 0, "dir": 0,
"safe_mode": 0, "safe": 0,
"function_mode": 0 "fun_mode": 0
} }
] ]
} }
@@ -514,14 +514,14 @@ dld960/{dev_serial}/{direction}
| `auto_mode` | bool | 自动调频模式,默认 false | | `auto_mode` | bool | 自动调频模式,默认 false |
| `channels` | array | 各路通道参数,1~4 路 | | `channels` | array | 各路通道参数,1~4 路 |
| `channels[].ch` | uint8 | 通道号 1~4 | | `channels[].ch` | uint8 | 通道号 1~4 |
| `channels[].sensitivity` | uint8 | 灵敏度 0~9,默认 7 | | `channels[].sens` | uint8 | sensitivity, 灵敏度 0~9,默认 7 |
| `channels[].freq_level` | string | `"high"`(33nF) / `"mid_high"`(43nF) / `"mid_low"`(66nF) / `"low"`(76nF) | | `channels[].level` | string | freq_level, `"high"`(33nF) / `"mid_high"`(43nF) / `"mid_low"`(66nF) / `"low"`(76nF) |
| `channels[].loop_delay` | uint8 | 延时时间,0~200(×0.1s),最大 20s | | `channels[].delay` | uint8 | loop_delay, 延时时间,0~200(×0.1s),最大 20s |
| `channels[].output_mode` | string | `"exist"` / `"enter_pulse"` / `"leave_pulse"` / `"direction"` | | `channels[].output` | string | exist_mode, `"exist"` / `"enter_pulse"` / `"leave_pulse"` / `"direction"` |
| `channels[].exist_mode` | uint8 | 存在方式,0=永久,非0=分钟数 | | `channels[].exist` | uint8 | output_mode, 存在方式,0=永久,非0=分钟数 |
| `channels[].direction_mode` | uint8 | 方向判别模式,0=触发,1~6=方向输出 | | `channels[].dir` | uint8 | direction_mode, 方向判别模式,0=触发,1~6=方向输出 |
| `channels[].safe_mode` | uint8 | 安全模式,0=关闭,非0=分钟数 | | `channels[].safe` | uint8 | safe_mode, 安全模式,0=关闭,非0=分钟数 |
| `channels[].function_mode` | uint8 | 功能模式 | | `channels[].fun_mode` | uint8 | function_mode, 功能模式 |
**响应:** **响应:**
@@ -563,17 +563,17 @@ dld960/{dev_serial}/{direction}
"channels": [ "channels": [
{ {
"ch": 1, "ch": 1,
"sensitivity": 7, "sens": 7,
"freq_level": "high", "level": "high",
"loop_delay": 0, "delay": 0,
"output_mode": "exist", "output": "exist",
"exist_mode": 0, "exist": 0,
"direction_mode": 0, "dir": 0,
"safe_mode": 0, "safe": 0,
"function_mode": 0, "fun_mode": 0,
"freq_initial": 105300, "f_initial": 105300,
"freq_current": 105280, "f_current": 105280,
"freq_diff": 20 "diff": 20
} }
] ]
} }
@@ -582,9 +582,9 @@ dld960/{dev_serial}/{direction}
| 额外字段 | 类型 | 单位 | 说明 | | 额外字段 | 类型 | 单位 | 说明 |
|----------|------|------|------| |----------|------|------|------|
| `freq_initial` | uint32 | Hz | 初始频率 | | `f_initial` | uint32 | Hz | freq_initial, 初始频率 |
| `freq_current` | uint32 | Hz | 当前实时频率 | | `f_current` | uint32 | Hz | freq_current, 当前实时频率 |
| `freq_diff` | uint32 | Hz | 频率变化量(绝对值) | | `diff` | uint32 | Hz | 变化量(绝对值) |
## 4.15 设置主动上报 `report_config` ## 4.15 设置主动上报 `report_config`
@@ -639,13 +639,13 @@ dld960/{dev_serial}/{direction}
"channels": [ "channels": [
{ {
"ch": 1, "ch": 1,
"freq_level": "high", "level": "high",
"has_car": false, "iscar": false,
"loop_ok": true, "loop_ok": true,
"freq_current": 105280, "freq": 105280,
"freq_diff": 20, "diff": 20,
"sensitivity": 7, "sens": 7,
"condition": 0, "cndtn": 0,
"misc": { "misc": {
"type": "time", "type": "time",
"value": 0 "value": 0
@@ -653,13 +653,13 @@ dld960/{dev_serial}/{direction}
}, },
{ {
"ch": 2, "ch": 2,
"freq_level": "mid_high", "level": "mid_high",
"has_car": true, "iscar": true,
"loop_ok": true, "loop_ok": true,
"freq_current": 98700, "freq": 98700,
"freq_diff": 1500, "diff": 1500,
"sensitivity": 7, "sens": 7,
"condition": 2, "cndtn": 2,
"misc": { "misc": {
"type": "time", "type": "time",
"value": 350 "value": 350
@@ -667,13 +667,13 @@ dld960/{dev_serial}/{direction}
}, },
{ {
"ch": 3, "ch": 3,
"freq_level": "mid_low", "level": "mid_low",
"has_car": false, "iscar": false,
"loop_ok": false, "loop_ok": false,
"freq_current": 0, "freq": 0,
"freq_diff": 0, "diff": 0,
"sensitivity": 5, "sens": 5,
"condition": 0, "cndtn": 0,
"misc": { "misc": {
"type": "cut_count", "type": "cut_count",
"value": 3 "value": 3
@@ -681,13 +681,13 @@ dld960/{dev_serial}/{direction}
}, },
{ {
"ch": 4, "ch": 4,
"freq_level": "low", "level": "low",
"has_car": false, "iscar": false,
"loop_ok": true, "loop_ok": true,
"freq_current": 62100, "freq": 62100,
"freq_diff": 5, "diff": 5,
"sensitivity": 8, "sens": 8,
"condition": 0, "cndtn": 0,
"misc": { "misc": {
"type": "flow_count", "type": "flow_count",
"value": 128 "value": 128
@@ -701,15 +701,15 @@ dld960/{dev_serial}/{direction}
| 通道字段 | 类型 | 说明 | | 通道字段 | 类型 | 说明 |
|----------|------|------| |----------|------|------|
| `ch` | uint8 | 通道号 1~4 | | `ch` | uint8 | 通道号 1~4 |
| `freq_level` | string | 线圈高低频档位 | | `level` | string | freq_level 线圈高低频档位 |
| `has_car` | bool | 是否有车 | | `iscar` | bool | 是否有车 |
| `loop_ok` | bool | 线圈是否正常(false=断开) | | `loop_ok` | bool | 线圈是否正常(false=断开) |
| `freq_current` | uint32 | 当前频率 (Hz) | | `freq` | uint32 | 当前频率 (Hz) |
| `freq_diff` | uint32 | 频率变化量 (Hz) | | `diff` | uint32 | 变化量 |
| `sensitivity` | uint8 | 当前灵敏度等级 | | `sens` | uint8 | sensitivity, 当前灵敏度等级 |
| `condition` | uint8 | 环境状态评估值(越大干扰越大) | | `cndtn` | uint8 | condition, 环境状态评估值(越大干扰越大) |
| `misc.type` | string | `"time"`(时间量) / `"cut_count"`(断开次数) / `"flow_count"`(车流量) | | `misc.type` | string | `"time"`(时间量) / `"cut_count"`(断开次数) / `"flow_count"`(车流量) |
| `misc.value` | uint32 | 杂项数值(时间量单位 5ms) | | `misc.value` | uint32 | 杂项数值(时间量单位 50ms |
## 5.2 事件上报 `event_report` ## 5.2 事件上报 `event_report`
@@ -747,9 +747,9 @@ dld960/{dev_serial}/{direction}
| 事件类型 `type` | 说明 | `value` 含义 | | 事件类型 `type` | 说明 | `value` 含义 |
|----------------|------|-------------| |----------------|------|-------------|
| `car_enter` | 车辆进入 | 0 | | `car_enter` | 车辆进入 | 0 |
| `car_leave` | 车辆离开 | 通过时间 (×5ms) | | `car_leave` | 车辆离开 | 通过时间 (×50ms) |
| `loop_cut` | 线圈断开 | 0 | | `loop_cut` | 线圈断开 | 0 |
| `loop_restore` | 线圈恢复 | 断开持续时长 (×5ms) | | `loop_restore` | 线圈恢复 | 断开持续时长 (×50ms) |
## 5.3 设备心跳 `heartbeat` ## 5.3 设备心跳 `heartbeat`
@@ -785,3 +785,5 @@ dld960/{dev_serial}/{direction}
|------|----------|----------|--------| |------|----------|----------|--------|
| V1.00 | 2026-06-22 | 初始版本,基于串口协议 V1.01 | wangfq | | V1.00 | 2026-06-22 | 初始版本,基于串口协议 V1.01 | wangfq |
| V1.01 | 2026-07-07 | Topic 压缩为双主题(`{sn}/srv` + `{sn}/dev`),消息类型由 `cmd` 字段区分 | wangfq | | V1.01 | 2026-07-07 | Topic 压缩为双主题(`{sn}/srv` + `{sn}/dev`),消息类型由 `cmd` 字段区分 | wangfq |
| V1.02 | 2026-07-09 | 缩写相关字段 | wangfq |
@@ -100,7 +100,7 @@ typedef struct {
uint32_t freq; // 频率 (3 bytes, little-endian) uint32_t freq; // 频率 (3 bytes, little-endian)
uint16_t variation; // 变化量 (2 bytes, little-endian) uint16_t variation; // 变化量 (2 bytes, little-endian)
union { union {
uint32_t passtime_ms; // 通过时间/车间距, 5ms单位 (misc_type=0) uint32_t passtime_ms; // 通过时间/车间距, 50ms单位 (misc_type=0)
uint32_t cut_amount; // 线圈断开次数 (misc_type=1) uint32_t cut_amount; // 线圈断开次数 (misc_type=1)
uint32_t flow_amount; // 车流量数 (misc_type=2) uint32_t flow_amount; // 车流量数 (misc_type=2)
uint32_t relay_count; // 继电器输出次数 (misc_type=3) uint32_t relay_count; // 继电器输出次数 (misc_type=3)
@@ -386,72 +386,59 @@ void iot_mqtt_publish_sensor(void) {
return; return;
} }
/* Batch-publish: 2 channels per message to keep each under 512 bytes. /* 全通道一次性上报,字段按 DLD960_TCP_JSON协议.md §5.1:
Full-field JSON (freq_current, freq_diff, condition, misc) per the protocol doc. */ ch, freq_level, has_car, loop_ok, freq_current, freq_diff,
static char data_json[1024]; // single buffer for 2-channel JSON sensitivity, condition, misc.type, misc.value
static char payload[1024]; 注意: mqttBuf 需 ≥ 1024 才能容纳 4 通道全字段封包 (~846 字节) */
char *pub_topic = (char *)g_iot_topic.topic_pub; static char data_json[1024];
uint8_t batch_start = 0; static char payload[1400];
char *p = data_json;
int remaining = sizeof(data_json);
int written;
const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"};
uint8_t i; uint8_t i;
while (batch_start < sr.coil_count) { written = snprintf(p, remaining, "{\"channels\":[");
char *p = data_json; if (written < 0 || written >= remaining) goto done;
int remaining = sizeof(data_json); p += written; remaining -= written;
int written;
uint8_t batch_end = batch_start + 2; for (i = 0; i < sr.coil_count; i++) {
if (batch_end > sr.coil_count) batch_end = sr.coil_count; const LUP_CoilSensor *cs = &sr.coils[i];
const char *misc_type_str = "time";
uint32_t misc_val = 0;
if (cs->misc_type == 0) { misc_type_str = "time"; misc_val = cs->misc.passtime_ms; }
else if (cs->misc_type == 1) { misc_type_str = "cut_count"; misc_val = cs->misc.cut_amount; }
else if (cs->misc_type == 2) { misc_type_str = "flow_count"; misc_val = cs->misc.flow_amount; }
else if (cs->misc_type == 3) { misc_type_str = "relay_count"; misc_val = cs->misc.relay_count; }
written = snprintf(p, remaining, written = snprintf(p, remaining,
"{\"channels\":["); "%s{\"ch\":%d,\"level\":\"%s\",\"iscar\":%s,"
"\"loop_ok\":%s,\"freq\":%lu,\"diff\":%d,"
"\"sens\":%d,\"cndtn\":%d,"
"\"misc\":{\"type\":\"%s\",\"value\":%lu}}",
(i > 0) ? "," : "",
i + 1,
freq_level_names[cs->freq_level],
cs->car_state ? "true" : "false",
cs->loop_state ? "false" : "true",
cs->freq, cs->variation,
cs->sensitivity, cs->condition,
misc_type_str, misc_val);
if (written < 0 || written >= remaining) goto done; if (written < 0 || written >= remaining) goto done;
p += written; remaining -= written; p += written; remaining -= written;
for (i = batch_start; i < batch_end; i++) {
const LUP_CoilSensor *cs = &sr.coils[i];
const char *misc_type_str = "time";
uint32_t misc_val = 0;
if (cs->misc_type == 0) { misc_type_str = "time"; misc_val = cs->misc.passtime_ms; }
else if (cs->misc_type == 1) { misc_type_str = "cut_count"; misc_val = cs->misc.cut_amount; }
else if (cs->misc_type == 2) { misc_type_str = "flow_count"; misc_val = cs->misc.flow_amount; }
else if (cs->misc_type == 3) { misc_type_str = "relay_count"; misc_val = cs->misc.relay_count; }
const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"};
written = snprintf(p, remaining,
"%s{\"ch\":%d,\"freq_level\":\"%s\",\"has_car\":%s,"
"\"loop_ok\":%s,\"freq_current\":%lu,\"freq_diff\":%d,"
"\"sensitivity\":%d,\"condition\":%d,"
"\"misc\":{\"type\":\"%s\",\"value\":%lu}}",
(i > batch_start) ? "," : "",
i + 1,
freq_level_names[cs->freq_level],
cs->car_state ? "true" : "false",
cs->loop_state ? "false" : "true",
cs->freq, cs->variation,
cs->sensitivity, cs->condition,
misc_type_str, misc_val);
if (written < 0 || written >= remaining) goto done;
p += written; remaining -= written;
}
snprintf(p, remaining, "]}");
/* Wrap in outer message and publish */
snprintf(payload, sizeof(payload),
"{\"msg_id\":%d,\"cmd\":\"loop_data\",\"ts\":%lu,\"data\":%s}",
++g_iot_msg_id, mstick() / 1000, data_json);
uint16_t plen = (uint16_t)strlen(payload);
mqtt_publish(pub_topic, payload, 0);
batch_start = batch_end;
} }
done: snprintf(p, remaining, "]}");
InitPkgUart(&g_pkg_uart_2);
}
/* Wrap and publish */
snprintf(payload, sizeof(payload),
"{\"msg_id\":%d,\"cmd\":\"loop_data\",\"ts\":%lu,\"data\":%s}",
++g_iot_msg_id, mstick() / 1000, data_json);
mqtt_publish((char *)g_iot_topic.topic_pub, payload, 0);
done:
InitPkgUart(&g_pkg_uart_2); InitPkgUart(&g_pkg_uart_2);
} }
@@ -1044,7 +1044,7 @@ static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorRepo
switch (cs->misc_type) { switch (cs->misc_type) {
case 0: case 0:
// car_state=0(无车)→通过时间, car_state=1(有车)→车间距 // car_state=0(无车)→通过时间, car_state=1(有车)→车间距
misc_field = cs->car_state ? "\"gap_ms5\"" : "\"passtime_ms\""; misc_field = cs->car_state ? "\"gap\"" : "\"passtime\"";
misc_val = cs->misc.passtime_ms; misc_val = cs->misc.passtime_ms;
break; break;
case 1: misc_field = "\"cut_amount\""; misc_val = cs->misc.cut_amount; break; case 1: misc_field = "\"cut_amount\""; misc_val = cs->misc.cut_amount; break;
+104
View File
@@ -107,10 +107,114 @@
--- ---
## 2026-07-06 — MQTT IoT 基础打通
### 背景
在 CH32V208RISC-V, 栈仅 2KB)上实现 MQTT IoT 协议栈,对标 DBN101GA 参考项目。WCHNET TCP MSS=576, Publish 须 ≤500B。
### 1. MQTT 重构对标 DBN101GA
- `iot_mqtt_srv.c` 对标 DBN101GA 参考实现重写
- MQTT socket 复用 `SocketId_TCP`(而非独立 socket
- `iot_connect_broker``SourPort` 参数
- 出厂默认 topic 使用真实设备序列号
### 2. 栈溢出系列修复
| 问题 | 修复 |
|------|------|
| MQTT buffer 在栈上 → 溢出 | 改为 `static` 全局分配 |
| `iot_mqtt_publish_sensor`: `data_json[2KB] + payload[2KB] = 4KB` 远超 2KB 栈 | 减小缓冲区 + static 分配 |
| 缓冲区 2048 → 1024 | 单次交互不超 1KB |
### 3. MQTT CONNECT 延迟发送
**根因**: `MQTT_connect()` 在中断上下文调用 `WCHNET_SocketSend` → 崩溃。
**修复**: CONNECT 包延到 poll 轮询内发送,避免中断内调用网络 API。
### 4. MQTTPacket 初始化修复
`MQTTPacket_connectData_initializer` 是复合字面量,不能用于赋值。改用临时变量中转。
### 5. IoT socket 重复创建 + broker IP 解析修复
- IoT socket 重复创建 → 连接失败,改为复用已有 socket
- broker IP 字符串解析修正
---
## 2026-07-07 — MQTT 协议 V1.01 + 命令分发
### 1. MQTT 主题压缩:多主题 → 双主题
协议从 V1.00 的多 topic`dld960/{sn}/loop_data`, `/event_report`, `/heartbeat`, `/cmd`, `/cfg`…)压缩为 V1.01 双主题:
| 方向 | V1.00 | V1.01 |
|------|-------|-------|
| 设备→平台 | 5+ topics | `dld960/{sn}/dev` |
| 平台→设备 | 5+ topics | `dld960/{sn}/srv` |
### 2. MQTT 命令分发
`manage_mqtt_recv_message()` 实现 V1.01 协议命令分发,支持 `cmd` / `Method` 双格式:`pwd_verify`, `dev_info_query`, `loop_param_set/query`, `loop_version_query`, `loop_factory_init`, `device_reset`, `factory_reset` 等。
### 3. report_config 完整 7 参数
MQTT/TCP `report_config` 支持完整 7 参数配置:`period`, `loop_data`, `event_report`, `heartbeat`, `passtime`, `cut_amount`, `flow_amount`。与 TCP JSON 协议统一。
### 4. MQTT 网络配置
`ssc_net_set` / `iot_net_set` / `iot_topic_set` 三条命令实现,支持通过 MQTT 远程配置设备网络参数和主题。
---
## 2026-07-08 — MQTT 稳定性修复系列
### 1. g_iot_socket 未初始化 → 硬故障重启
`g_iot_socket` 声明为全局变量但未初始化,值为 `0xFF``WCHNET_SocketSend(0xFF)` 直接触发硬件故障重启。
**修复**: 初始化为 `INVALID_SOCKET`
### 2. loop_data JSON 超 MSS 导致 SocketSend 溢出
WCHNET TCP MSS=576,原 `loop_data` JSON 四通道全字段 ~800B → `WCHNET_SocketSend` 溢出崩溃重启。
**修复**: 精简 JSON 字段至 ~400B(单包达标),再**分批发送**(2通道/包 × 2包)。
### 3. iot_mqtt_send() hex dump 循环 → 栈溢出
调试用的 hex dump 循环(65次 `PRINT`)在仅 2KB 栈的 CH32V208 上直接栈溢出重启。
**修复**: 移除 hex dump 循环。
### 4. MQTT 主动上报无数据 + PINGREQ 缺失
`poll_mqtt()` 未正确触发 `MQTT_Yield()``MQTT_Live()`,导致主动上报无数据、PINGREQ 缺失断连。
**修复**: 改用 `net_srv.c` 统一的 `mqtt_publish()` + 修正 `poll_mqtt()` 调度逻辑。
### 5. 传感数据 topic 修正
topic 修正为 V1.01 双主题协议 `dld960/{sn}/dev`(此前遗留旧 topic 格式)。
### 6. 恢复 loop_data 完整字段
精简 JSON 后缺失部分字段(`freq`, `passtime_ms` 等),恢复完整字段并在分批框架内发送。
---
## 修订记录 ## 修订记录
| 版本 | 时间 | 说明 | | 版本 | 时间 | 说明 |
|------|------|------| |------|------|------|
| V3.0 | 2026-07-08 | MQTT 稳定性修复: socket初始化/buffer溢出/分批发送/hex dump/PINGREQ |
| V2.9 | 2026-07-07 | MQTT V1.01 双主题协议 + 命令分发 + report_config 7参数 |
| V2.8 | 2026-07-07 | MQTT 网络配置: ssc_net_set / iot_net_set / iot_topic_set |
| V2.7 | 2026-07-06 | MQTT IoT 基础打通: 对标DBN101GA + 栈溢出修复 + CONNECT延迟发送 |
| V2.6 | 2026-07-06 | IoT 模式配置 bug 修复 |
| V2.5 | 2026-07-03 | tcp_json_restart 只关数据socket, listen保持不动 (WCHNET限制) | | V2.5 | 2026-07-03 | tcp_json_restart 只关数据socket, listen保持不动 (WCHNET限制) |
| V2.4 | 2026-07-03 | 去掉 Auth 60s timeout, 改为3次失败即重启 | | V2.4 | 2026-07-03 | 去掉 Auth 60s timeout, 改为3次失败即重启 |
| V2.3 | 2026-07-03 | Auth timeout 不计入重启限额, 修复冷却期阻断连接 | | V2.3 | 2026-07-03 | Auth timeout 不计入重启限额, 修复冷却期阻断连接 |