# vd960DBN 开发日志 > MCU: CH32V208 (RISC-V, WCH) | 通信: BLE + ETH (WCHNET) + UART2→Loop MCU | TCP JSON 端口: 5960 > > 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接 --- ## 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 限制**(事件为关键不可再生数据; 若需要开关另加字段)— ⚠️ 待老大确认。 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条命令) |