现场: 长车离开后 event_report 正确, 但 loop_data 连续 34s/100+ 条 iscar:true 陈旧快照, 且冻结 diff=517 锁死 fast_mode 300ms 刷屏。 根因: 0xC0 帧两条竞争消费路径数据源分裂 — uart_srv→lup回调只喂事件不更新 _cached_sr; Step1 直读才更新缓存, 但赢得竞争概率实测 ~2% → 事件每帧必达而快照冻结, 出现'事件说车走了、快照说车还在'。 修复: - 新增 iot_sensor_ingest(): 事件沿 + _cached_sr 刷新单一入口, 回调与 Step1 两路汇入, 帧竞争不再影响数据新鲜度 - Step1 直读路径补 lup_verify_checksum (此前裸解析未校验)
511 lines
26 KiB
Markdown
511 lines
26 KiB
Markdown
# vd960DBN 开发日志
|
||
|
||
> MCU: CH32V208 (RISC-V, WCH) | 通信: BLE + ETH (WCHNET) + UART2→Loop MCU | TCP JSON 端口: 5960
|
||
>
|
||
> 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接
|
||
|
||
---
|
||
|
||
## 2026-07-15 — 🔴 loop_data 陈旧快照: 事件与缓存不同源 (帧竞争)
|
||
|
||
### 现象 (现场测试: 长车过双线圈)
|
||
|
||
车离开线圈1后, event_report(car_leave ch1 val=97) 正确上报并 ACK; 但随后 loop_data 连续 **34 秒 100+ 条** ch1/ch2 `iscar:true`, 直到 msg_id 141 才恢复正常。
|
||
|
||
### 根因: 两条帧消费路径的数据源分裂
|
||
|
||
0xC0 帧有两条竞争消费路径 (谁先看到 `g_pkg_uart_2.flag` 谁消费):
|
||
- **uart_srv → lup_process_frame → lup 回调**: 只喂事件检测 `iot_evt_feed`, **不更新 `_cached_sr`**
|
||
- **iot_mqtt_publish_sensor Step1 直读**: 事件 + 缓存都更新
|
||
|
||
主循环里 uart_srv 先于 publish_sensor 执行, Step1 只能吃到"帧恰好在两调用之间完成"的窄窗口 — **实测命中率 ~1/56 ≈ 2%**。结果:
|
||
1. 事件路径 (回调) 每帧必达 → car_leave 正确 ✅
|
||
2. `_cached_sr` 冻结在车压线圈时的旧帧 (freq=61518/diff=517/relay_count, msg 28~140 逐字节一致) ❌
|
||
3. **陈旧数据自激**: 冻结的 diff=517 > 阈值 → fast_mode 锁死 → 以 300ms 最高频率刷屏错误快照
|
||
4. 34s 后 Step1 偶然赢一次 → 缓存刷新 → car_edge(陈旧true→新false) 立即档发出 msg 141 恢复
|
||
|
||
> 证据: 同期真实 LUP 帧 eval 字节 car_state=0、variation=±个位数、misc_type 0→3 轮转, 与冻结快照完全对不上。
|
||
|
||
### 修复
|
||
|
||
- 新增 `iot_sensor_ingest()`: 事件沿检测 + 刷新 `_cached_sr`, **单一数据源**;
|
||
lup 回调与 Step1 直读两路全部汇入 → 帧竞争从此无关紧要
|
||
- Step1 直读路径补 `lup_verify_checksum` (此前只有 uart_srv 路径过校验, 直读裸解析)
|
||
|
||
### 教训 (通用)
|
||
|
||
**同一物理量的多个消费者必须同源**。事件说"车走了"、快照说"车还在", 这种自相矛盾一定是数据源分裂 — 排查时先问: 这两个字段是从同一帧解析的吗?
|
||
|
||
### 板上验证
|
||
|
||
长车复测同场景: car_leave 事件后**下一条** loop_data 的 iscar 必须已翻 false (两者同帧同源); 车走后 diff 回落 → 300ms 快速档应在数秒内退回空闲档, 不再刷屏。
|
||
|
||
---
|
||
|
||
## 2026-07-15 — loop_data 上报调度升级三档: car_state 沿立即上报
|
||
|
||
### 背景
|
||
|
||
原双速率 (空闲 interval / 变化态 300ms) 下, 进/出车沿最坏也要等 300ms 门控。车辆进出是最关键时刻 (道闸联动/计数), 王工要求: **car_state 翻转沿立即上报, 不等间隔**。
|
||
|
||
### 实现 (iot_mqtt_publish_sensor Step2/3)
|
||
|
||
三档调度:
|
||
|
||
| 档位 | 触发 | 间隔 |
|
||
|------|------|------|
|
||
| **立即档** | 任一通道 car_state 翻转沿 | **0 (直通门控)** |
|
||
| 快速档 | 任一通道 \|variation\| > 阈值(10) | 300ms |
|
||
| 空闲档 | 平稳 | 配置 interval (默认 60s, 最小 1s) |
|
||
|
||
- car_edge 优先级最高, 命中即定档; variation 记录但不 break (后续通道可能有沿)
|
||
- 门控: `if (!car_edge && 未到间隔) return;` — 沿直通, 其余照旧
|
||
- 快照仍在门控通过后刷新 → 同一个沿只触发一次立即发布, 下一轮回归常规档
|
||
|
||
### 防洪泛分析
|
||
|
||
立即档天然有界: Loop MCU 事件帧最快 150ms 一帧, car_state 是 Loop 侧防抖后的状态 (进入确认 3 次 + 离开检测), 不存在毛刺沿; 且发布后快照即同步, 同沿不重复。最坏情况 = 帧率上限 150ms/次, 可接受。
|
||
|
||
### 验证
|
||
|
||
`tests/test_report_cadence.c` 8 组全过: 空闲60s到点 / 进车沿距上次50ms立即发 / 同沿不重复 / 快速档仍受300ms门控 / 出车沿20ms立即 / 回落空闲 / 双通道同帧沿单包覆盖。
|
||
|
||
---
|
||
|
||
## 2026-07-15 — 设备时钟同步 (方案B): 平台经 report_config 下发 Unix ts
|
||
|
||
### 背景
|
||
|
||
原上行 `ts` = `mstick()/1000` (上电秒数), 非 Unix 时间戳 (现场事故报告实锤)。设备无 RTC/SNTP。王工定方案B: **initialize 上线 → 平台经 report_config 下发真 Unix ts → 设备校准**。
|
||
|
||
### 实现
|
||
|
||
- `net_srv.c` 新增时钟模块: `dev_time_sync(unix_ts)` (合法性门槛 ≥1600000000 挡掉上电秒数/0) + `dev_time_now()` (已校准→真 Unix; 未校准→退回上电秒数)。基准用 `base_unix + (mstick()-base_tick)/1000`, 无符号相减天然处理 49.7 天回绕。
|
||
- `manage_mqtt_recv_message` 解析 msg_id 后, 从任意下行命令信封 `ts` 校准 (report_config 为主同步点)。
|
||
- 全部上行 `ts` 由 `mstick()/1000` 改 `dev_time_now()`: net_srv.c 14 处命令响应 + initialize; iot_mqtt_srv.c loop_data + event_report(首发时刻, 重发不刷新) + 遗留响应。**heartbeat 的 `uptime` 字段保留 `mstick()/1000`** (那本就是运行时长, 非时间戳)。
|
||
|
||
### 关键点
|
||
|
||
- 校准前 (含首个 initialize) ts=上电秒数, 平台按量级识别未校准。
|
||
- 设备重启无掉电保持 → 每次 initialize 后平台都须重下发 report_config 带 ts。
|
||
- 门槛 1600000000 (2020-09) 挡掉上电秒数(几百)/0/异常, 防污染基准。
|
||
|
||
### 验证
|
||
|
||
`tests/test_dev_time_sync.c` 6 组全过: 未同步退回上电秒数 / 非法ts被拒 / 校准瞬间对齐 / 校准后随时钟递增 / 门槛边界 / mstick 49.7天回绕无符号相减正确。
|
||
|
||
⚠️ **平台侧须配合**: 收到 initialize 后, 下发 report_config 时信封 `ts` 填当前 Unix 时间 (协议 V1.05 §2.3)。
|
||
|
||
---
|
||
|
||
## 2026-07-15 — 🔴 现场事故: MQTT protocol error 重连风暴 (根因/修复)
|
||
|
||
### 现象
|
||
|
||
设备 DC045A49718F (DLD960GA) 上电后每 ~1s 被 mosquitto 以 `disconnected due to protocol error` 踢下线, 15 分钟 80+ 次重连。平台抓包: 设备发出 520B 垃圾包 = 前 512B 全 0x00 + 尾部 8B ASCII "report_c"。
|
||
|
||
### 根因 (静态分析 + 报文长度量化坐实, 与平台"环形缓冲/event_report队列"推测不同)
|
||
|
||
**`mqtt_publish` 的 `mqttBuf` 只有 512B, 但 `loop_data`(4通道)≈604B → 溢出。**
|
||
|
||
链路:
|
||
1. `loop_data` 4 通道 JSON = 576B payload / 604B 整包 MQTT > `MAX_MQTTBUF_LEN=512`
|
||
2. `MQTTSerialize_publish` 检测缓冲不足 → 返回 `MQTTPACKET_BUFFER_TOO_SHORT(-2)`, **且一字节不写 buf**(仍是 clear_mqtt_buf 的全零)
|
||
3. `mqtt_publish` 里 `len` 是 **uint32_t** → -2 变 4294967294
|
||
4. `WCHNET_SocketSend(mqttBuf, &len)` 把清零的 512B + **越界相邻全局 `temp_guide`("report_config"→"report_c")** 当一包发出 = 520B 垃圾
|
||
5. broker 见非法报文类型 0x00 → RST → TCP Timeout(0x40) → 全量重连 → 风暴
|
||
|
||
> ⚠️ 与 event_report 无关: 事故日志中设备从未发过 event_report; `mqtt_publish` 是扁平缓冲非环形。此为**先前就存在**的隐患, 4 通道 loop_data 必触发。report_config 响应仅 216B 装得下, 故看着正常。
|
||
|
||
### 修复 (net_srv.c)
|
||
|
||
1. `MAX_MQTTBUF_LEN` 512 → **1024**: 容纳 604B loop_data + 余量 (TCP 自动分段, MQTT 不关心段边界)
|
||
2. `mqtt_publish` 的 `len` 改 **int** + **守卫**: `if (len <= 0) return;` 序列化失败绝不发残缓冲 —— 这是根本防线, 即便未来任何 payload 溢出也不再吐垃圾包
|
||
3. keepalive `MQTT_KEEPALIVE_INTERVAL` 9 → **60** (报告建议; 9s 过激易误断)
|
||
|
||
### 协议违规修复 (event_report, 报告实锤)
|
||
|
||
- **断线重连后重发用了新 msg_id** → 平台 (sn,msg_id) 去重失效重复入库。修复 `iot_evt_process` 重连沿: 有未决包时保持**原 msg_id/原 ts** 立即重发 (V1.04 §5.3-2), 不再作废换号。
|
||
|
||
### 待决 (需老大拍板)
|
||
|
||
- **ts 字段是上电秒数 (mstick()/1000) 而非 Unix 时间戳**: MQTT 模式设备无 RTC/SNTP 时间源。选项: (a) 平台以服务端收包时间为准忽略设备 ts (最省, 推荐); (b) 平台下发时间同步命令; (c) 设备加 SNTP。**暂未改**, 待定方案。
|
||
|
||
### 验证
|
||
|
||
- `tests/test_mqtt_publish_overflow.c`: 复现旧版 512 缓冲发 520B 垃圾包(首字节0x00) + 验证守卫拦截 + 1024 缓冲 641B 正常 + report_config 回归。全过。
|
||
- `tests/test_event_report.c` T8 改为断言重连保持同 msg_id/ts。8 组全过。
|
||
|
||
⚠️ 板上验证: 编译查 .map 确认 RAM 余量(mqttBuf +512B); 烧录后观察不再有 `TCP Timeout` 风暴, loop_data 正常周期上报, 压线圈看 event_report 断线重连去重。
|
||
|
||
---
|
||
|
||
## 2026-07-15 — event_report 实现: 平台必答 + 设备重发 (协议 V1.04)
|
||
|
||
### 实现 (iot_mqtt_srv.c 事件模块 + net_srv.c ACK 路由)
|
||
|
||
| 组件 | 说明 |
|
||
|------|------|
|
||
| 沿检测 `iot_evt_feed` | 每帧比对 car_state/loop_state 快照。**双路汇聚**: Step1 消费路径直接喂 + `lup_set_sensor_callback(iot_evt_sensor_cb)` 覆盖 uart_srv 消费路径(两路都存在帧竞争, 单独任一路都会漏帧漏沿) |
|
||
| 事件队列 | 16 深环形, 溢出丢最旧; **事件仅在 ACK(code=0) 后出队** |
|
||
| 发送状态机 `iot_evt_process` | 每轮主循环调用(挂在 iot_mqtt_publish_sensor 内, 置于 READY/enable 检查之前)。5s 超时重发, 同 msg_id/原始 ts, 最多 3 次; 耗尽→挂起, 新事件或重连沿解除并以**新 msg_id** 合并重报 |
|
||
| ACK 入口 `iot_evt_handle_ack` | net_srv.c `manage_mqtt_recv_message` 收到 `cmd=event_report` 的回显帧时调用, **必须 return 不回 unsupported**(否则与平台互打乒乓) |
|
||
| msg_id | 事件独立 uint32 计数器(`g_iot_msg_id` 是 uint8 且与 MQTT packet id 混用, 255 回绕会破坏平台去重窗口) |
|
||
|
||
### 关键决策
|
||
|
||
1. **帧消费提前到 READY/enable 之前**: 断网/未使能期间事件照样检测入队, 重连后补报。loop_data 周期上报仍受 READY+enable 门控。
|
||
2. **event_report 不受 report_config.enable 门控**(王工 2026-07-15 拍板): 事件为关键不可再生数据, 上电即检测上报, 与 loop_data 周期上报的开关解耦。`report_config.enable` 只管 loop_data。
|
||
3. **线圈断开期间屏蔽 car 沿**: 断开时 car_state 不可信, 仅前后两帧 loop 均正常才判进出车; loop_restore 的 value = DBN 本地计时(mstick 差)/50。
|
||
4. car_leave 的 value 直接取离开帧 misc(时间量) = 通过时间(50ms 单位)。
|
||
5. 首帧只建快照: 上电线圈上已有车不算进入沿。
|
||
|
||
### 已知边界
|
||
|
||
- 队列溢出且恰有未决包时: 作废未决包重发新 msg_id → 平台 (sn,msg_id) 去重失效, **可能重复入库一包**。触发条件: broker 不应答的 20s 窗口内涌入 >16 条事件, 现场概率极低, 平台可按事件内容+ts 二次去重兜底。
|
||
- BSS 增量 ~700B(队列128B + 发送缓冲 512B + 状态), **编译后查 .map 确认 RAM 余量**。
|
||
|
||
### 验证
|
||
|
||
gcc 隔离单测 (/tmp/test_event_report.c) 8 组全过: 首帧快照/进出车沿/5s×3 重发同 id 同 ts/挂起与新事件解除/错 msg_id 及 code≠0 不出队/断开恢复时长+断开期屏蔽 car 沿/单包 6 条上限(实测 317B<500B)/重连沿补报。
|
||
|
||
⚠️ 平台端注意: 收到 event_report **先落库后应答**, 按 (dev_serial,msg_id) 10 分钟窗口去重, 重复包直接答 code=0。
|
||
|
||
---
|
||
|
||
## 2026-07-15 — MQTT 快速上报增加 car_state 翻转沿触发
|
||
|
||
### 背景
|
||
|
||
原 fast_mode 仅由 `|variation| >= 10` 触发。灵敏度设高档时小车信号弱,variation 可能不过阈值,但 Loop MCU 已判定 car_state 翻转——进/出车事件仍按空闲间隔(≥1s)慢发,后台看到的过车时刻误差大。故增加:**任一通道 car_state 翻转沿也触发 300ms 快速上报**。
|
||
|
||
### 实现要点(两个坑,首版都踩了)
|
||
|
||
1. **`if (fast_mode = 0)` 单等号** → 赋值恒假,沿检测死代码。修正为直接判断(循环内走到该行 fast_mode 必为 0,无需再判)。
|
||
2. **快照刷新必须在间隔门控之后**。若在门控前无条件刷新 `_last_car_state`,沿被门控吞掉(如距上次发布 <300ms)时快照已同步,下一轮检测不到翻转 → 事件退化为空闲间隔上报。修正:`return`(未到间隔)时不刷快照,沿保持"待发"持续顶住 fast_mode,直到真正发布才同步。
|
||
|
||
```c
|
||
/* Step 2 循环内: variation 阈值 与 car_state 沿, 任一命中即 fast */
|
||
if (av >= IOT_MQTT_VARIATION_THRESHOLD) { fast_mode = 1; break; }
|
||
if (_last_car_state[i] != coils[i].car_state) { fast_mode = 1; break; }
|
||
|
||
/* Step 3 门控通过后才刷新快照 */
|
||
for (i = 0; i < coil_count; i++) _last_car_state[i] = coils[i].car_state;
|
||
```
|
||
|
||
### 验证
|
||
|
||
本地 gcc 隔离单测(buggy vs fixed 对照):进车沿后 buggy 延迟 950ms(退化空闲间隔),fixed 250ms(≤300ms 快速档);出车沿、负向 variation 回归均通过。
|
||
|
||
⚠️ 上电首帧若已有车(`_last_car_state` 初值 0 vs car_state=1),会触发一次 fast_mode——属良性,首帧本就该尽快发。
|
||
|
||
---
|
||
|
||
## 2026-07-14 — Loop 上报 variation 解析升级 2B→3B 有符号 (协议 V1.05)
|
||
|
||
### 背景
|
||
|
||
Loop MCU 上报的 `SENS_MULTI_LOOP_DYNAMIC (0xC0/0x0C)` 中变化量 `variation` 由 2B 无符号扩展为 **3B 有符号补码**(详见 Loop 侧 devlog 与协议 V1.05)。DBN 作为接收/转发端,解析逻辑须同步升级,否则每通道单元 12B 会被按旧的 11B 步长错位解析,**4 通道数据全错**。
|
||
|
||
### 改动 (4 处)
|
||
|
||
**1. `loop_uart_proto.h` — 结构体字段类型**
|
||
```c
|
||
// uint16_t variation; -> int32_t variation; // 3B有符号, = Origin-CAPVD
|
||
```
|
||
|
||
**2. `loop_uart_proto.c` — 解析函数 `lup_parse_sensor_report`**
|
||
- 每通道单元步长 `i*11` → `i*12`
|
||
- 通道数计算 `data_len/11` → `data_len/12`
|
||
- 频率仍 3B 无符号;**变化量 3B 解码 + bit23 符号扩展**:
|
||
```c
|
||
int32_t v = c[5] | ((int32_t)c[6] << 8) | ((int32_t)c[7] << 16);
|
||
if (v & 0x800000) v |= (int32_t)0xFF000000; // 符号扩展, 漏了负值会变~1600万大正数
|
||
cs->variation = v;
|
||
```
|
||
- 杂项偏移 `c[7..10]` → `c[8..11]`(整体右移 1B)
|
||
|
||
**3. `iot_mqtt_srv.c` — fast_mode 阈值判断(隐藏坑)**
|
||
```c
|
||
// variation 变有符号后, 车进入(正)/反向漂移(负) 都应触发加速上报
|
||
int32_t av = (v >= 0) ? v : -v;
|
||
if (av >= IOT_MQTT_VARIATION_THRESHOLD) fast_mode = 1; // 原 variation>=10 会漏掉负向
|
||
```
|
||
|
||
**4. JSON 输出(`iot_mqtt_srv.c` / `tcp_json_srv.c`)**
|
||
- `"diff":%d` / `"variation":%d` 格式符不变——CH32V208 上 `int32_t == int`,`%d` 天然正确输出负号,无需改动。
|
||
|
||
### 缓冲核查
|
||
|
||
整帧 52B→56B,DBN 侧 `g_pkg_uart_2.pkg[BUFF_STACK_SIZE=512]`、`LUP_MAX_PKG_LEN=70` 均远大于 56B,接收无截断;转发 MSS=576 不分片。
|
||
|
||
⚠️ **必须与 Loop 固件同版本发布**(步长死绑定)。
|
||
|
||
---
|
||
|
||
## 2026-06-26 — TCP JSON 协议框架搭建
|
||
|
||
### 1. TCP JSON Server 基础实现
|
||
|
||
- WCHNET TCP listen socket 创建(端口 5960,PROTO_TYPE_TCP)
|
||
- 参考 WCH 官方 EVT/EXAM/ETH/TCPServer 例程
|
||
- `g_net_state.flag` 状态机: `ETH_LibInit→1, WCHNET_CreateUdpSocket→2, WCHNET_CreateTcpSocket→3`
|
||
- SSC 禁用时 (`NET_SSC_ENABLE=0`) 手动设 `flag=2` 跳过 UDP 初始化
|
||
|
||
### 2. 鉴权 + 15 条命令
|
||
|
||
- `pwd_verify` 鉴权,3次错误 → 60s 锁定
|
||
- 命令表驱动分发: `dev_info_query`, `ssc_net_set/query`, `iot_net_set/query`, `iot_topic_set/query`, `pwd_set`, `factory_reset`, `device_reset`, `loop_param_set/query`, `loop_version_query`, `loop_factory_init`, `loop_sens_read/write`
|
||
- Deferred 响应模式: Loop MCU 命令异步 → `TcpJsonPending` 挂起 → `json_check_pending()` 轮询
|
||
|
||
### 3. simple_json 解析器修复
|
||
|
||
- **6 个 bug 修复**: NULL 解引用崩溃(plain values)、缺少 null terminator、buffer unsafe clear、数组支持缺失
|
||
- 字符串值提取时**不带引号**(调用方无需手动 strip quotes)
|
||
|
||
### 4. WCHNET TCP 踩坑
|
||
|
||
| 问题 | 修复 |
|
||
|------|------|
|
||
| listen socket 与数据 socket 混用 → 收不到数据 | CONNECT 发到 N, RECV/DISCONNECT 发到 N+1 |
|
||
| `WCHNET_SocketSend` 在 listen socket 静默失败 | 改用 `g_json_socket_listen + 1` |
|
||
| 接收缓冲区与帧缓冲区重叠 → 数据损坏 | 分离 WCHNET 内部 buf + 帧累加 buf |
|
||
| `#if NET_SSC_ENABLE` 误包共享函数 | 移出 `mStopIfError`/`GetMacAddr`/`get_ipstr_to_array` |
|
||
|
||
---
|
||
|
||
## 2026-06-30 — Loop MCU 串口协议 (0x7F) + 传感器上报
|
||
|
||
### 1. USART2 Loop MCU 通信
|
||
|
||
- 波特率 **192000**(文档写 115200,实际硬件 192000)
|
||
- 0x7F 协议帧解析器: `lup_feed_byte()` 逐字节状态机 + LEN-based 帧边界
|
||
- 校验字节站位: `total_len = 5 + LEN`(CMD 已在 LEN 中,勿重复计)
|
||
- 命令: `lup_cmd_send()` 发送 → `lup_cmd_check_timeout()` 轮询超时
|
||
|
||
### 2. 0xC0 传感器数据上报
|
||
|
||
- Loop MCU 主动推送 0xC0 帧 → `lup_process_frame()` 校验 → 回调 `json_sensor_callback`
|
||
- TCP JSON 输出格式: `{"sens_type":"multi_coil","coils":[...]}`
|
||
- `g_report_active` 开关控制上报启停
|
||
- 0xC0 帧通过回调直接驱动网络上报,不经 `uart_srv` 阻塞
|
||
|
||
### 3. 0xC0 帧时间量字段
|
||
|
||
- `misc_type=0` → `passtime_ms`(通过时间 / 车间距,根据 `car_state` 区分)
|
||
- `misc_type=1` → `cut_amount`(线圈断开次数)
|
||
- `misc_type=2` → `flow_amount`(车流量)
|
||
- `misc_type=3` → `relay_count`(继电器动作次数)
|
||
|
||
### 4. 协议文档
|
||
|
||
- `docs/vd960_loop_protocol_v1.0x.md` — 0x7F 帧格式、校验算法、命令参考
|
||
- 波特率修正: 115200 → 192000
|
||
|
||
---
|
||
|
||
## 2026-07-01 — passtime_ms5 字段改名
|
||
|
||
`vd960Loop` 侧时间戳从 5ms 改 50ms 后,DBN 侧同步:
|
||
- `passtime_ms5` → `passtime_ms`(字段名去 5 后缀)
|
||
- `loop_uart_proto.h/c` 结构体 + 解析 + `tcp_json_srv.c` JSON 字段同步
|
||
|
||
---
|
||
|
||
## 2026-07-02~03 — TCP Server 超时自动重启机制
|
||
|
||
### 1. 三项超时触发条件
|
||
|
||
| 条件 | 时间 | 处理 |
|
||
|------|------|------|
|
||
| 无任何连接 | 5min | `do_restart` → 计数重启 |
|
||
| 无数据交互 | 5min | `do_restart` → 计数重启 |
|
||
| Auth 密码错 3 次 | 立即 | `tcp_json_restart()` 直接重启 |
|
||
|
||
### 2. 重启计数保护
|
||
|
||
- 最多连续重启 **3 次**,超过进入 **10 分钟冷却期**
|
||
- `CONNECT` 成功时 `restart_count` 清零(正常连接重置计数,不算异常)
|
||
- Auth timeout 不消耗计数器(正常运维行为)
|
||
|
||
### 3. WCHNET 限制 - listen socket 不可关闭重建
|
||
|
||
**根因**: `WCHNET_SocketClose(listen)` 后端口 5960 仍标记"已占用",`SocketCreat` 返回 `ERR_ISCONN(0x1D)`。
|
||
|
||
**最终方案**: `tcp_json_restart()` 只关数据 socket(N+1) 断开客户端,listen socket 保持不动,仅重置应用层状态。WCHNET 自动处理下一个 CONNECT。
|
||
|
||
### 4. Auth timeout 死循环修复
|
||
|
||
原 Auth timeout 手工 `WCHNET_SocketClose` 后未设 `g_json_socket_listen = 0xFF`,下次 poll 条件仍满足 → 无限打印。改用统一的 `do_restart` 路径后修复。
|
||
|
||
### 5. Auth 3次失败 → 直接重启
|
||
|
||
去掉 60s Auth timeout 倒计时。改为连接后 3 次鉴权失败(密码错/格式错)即 `tcp_json_restart()`。连接后无交互由 5min 空闲检查覆盖。
|
||
|
||
---
|
||
|
||
## 2026-07-06 — MQTT IoT 基础打通
|
||
|
||
### 背景
|
||
|
||
在 CH32V208(RISC-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` 等),恢复完整字段并在分批框架内发送。
|
||
|
||
---
|
||
|
||
## 2026-07-10 — 双主题发布统一 + initialize 对齐 + packet_id 断连修复
|
||
|
||
### 1. 双主题发布统一 (dld960/{sn}/dev)
|
||
|
||
此前 heartbeat 和传感器上报仍使用旧 topic 路径(`dev/status`、`g_iot_topic.topic_pub`),与 V1.01 双主题模型不一致。
|
||
|
||
**修复**: 所有 MQTT 发布统一为 `dld960/{sn}/dev`:
|
||
- `iot_mqtt_srv.c` heartbeat: `iot_make_topic(..., "dev", "status", ...)` → `snprintf(..., "dld960/%s/dev", ...)`
|
||
- `iot_mqtt_srv.c` iot_mqtt_publish_sensor: `g_iot_topic.topic_pub` → `dld960/{sn}/dev`
|
||
- `net_srv.c` dev_initialize_pub: `g_iot_topic.topic_pub` → `dld960/{sn}/dev`
|
||
|
||
### 2. initialize 消息格式对齐 V1.03
|
||
|
||
`dev_initialize_pub()` 原先缺少顶层 `model`/`hard_ver`/`soft_ver` 字段,且在 `extra_info` 中有已废弃的 `version` 字段。
|
||
|
||
**修复**: 对齐协议文档 §5.1:
|
||
- 补充 `model`(PRODUCT_MODEL)、`hard_ver`(HARDWARE_VER)、`soft_ver`(FIRMWARE_VER) 到 `data` 顶层
|
||
- 移除 `extra_info.version`
|
||
- 缓冲区扩容 256→512 字节
|
||
|
||
### 3. mqtt_publish() packet_id=0 导致 broker 断连 🔴
|
||
|
||
**现象**: 设备收到查询指令后正确发布响应 JSON,10ms 后 broker 主动断开 TCP 连接(SockInt stat=0x10)。
|
||
|
||
**根因**: `net_srv.c` 的 `mqtt_publish()` 调用 `MQTTSerialize_publish()` 时 `packet_id` 硬编码为 `0`。当 `req_qos=1`(命令响应使用 QoS 1)时,MQTT 规范要求 `packet_id ≠ 0`,`0` 属于协议违规,broker 检测后直接踢掉连接。
|
||
|
||
**修复**: 新增 `static uint16_t s_mqtt_pkt_id` 计数器,QoS>0 时自增作为 packet_id,QoS=0 保持 0。
|
||
|
||
```c
|
||
static uint16_t s_mqtt_pkt_id = 0;
|
||
uint16_t pkt_id = (req_qos > 0) ? ++s_mqtt_pkt_id : 0;
|
||
```
|
||
|
||
### 4. DBNMQTTool 同步更新
|
||
|
||
- 协议版本注释 V1.01→V1.03
|
||
- 协议 Topic 树 + 模拟页增加 `initialize` 支持
|
||
- `device_manager.mark_online()` 扩展 `model`/`hard_ver`/`soft_ver` 参数,设备列表正确显示型号
|
||
- 增加 `dld960/+/dev/#` 通配订阅兼容旧固件多级 topic
|
||
|
||
---
|
||
|
||
## 修订记录
|
||
|
||
| 版本 | 时间 | 说明 |
|
||
|------|------|------|
|
||
| V3.2 | 2026-07-10 | 双主题发布统一 + initialize 数据格式对齐 V1.03 + mqtt_publish packet_id=0 断连修复 |
|
||
| V3.1 | 2026-07-09 | V1.03: 订阅后发 initialize 上线消息; iot_mqtt_srv 订阅改双主题 |
|
||
| 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.4 | 2026-07-03 | 去掉 Auth 60s timeout, 改为3次失败即重启 |
|
||
| V2.3 | 2026-07-03 | Auth timeout 不计入重启限额, 修复冷却期阻断连接 |
|
||
| V2.2 | 2026-07-03 | Auth timeout 死循环打印修复 → goto do_restart |
|
||
| V2.1 | 2026-07-03 | TCP Server 自动重启机制 (3条件 + 3次限额 + 10min冷却) |
|
||
| V2.0 | 2026-07-01 | passtime_ms5 → passtime_ms 字段改名 |
|
||
| V1.5 | 2026-06-30 | 0xC0 传感器上报 + 时间量字段完善 + 协议文档 |
|
||
| V1.4 | 2026-06-30 | USART2 Loop MCU 0x7F 协议实现 |
|
||
| V1.3 | 2026-06-30 | simple_json 6 bug 修复 |
|
||
| V1.2 | 2026-06-30 | WCHNET buffer 分离 + SocketSend listen→data 修正 |
|
||
| V1.1 | 2026-06-30 | NET_SSC_ENABLE 隔离 + #if guard 共享函数修复 |
|
||
| V1.0 | 2026-06-26 | TCP JSON 协议框架 (鉴权 + 15条命令) |
|