时间单位: - 串口协议 V1.06 / TCP JSON 协议: 杂项时间量、事件 value 由 5ms 修正为 50ms, 对齐 Loop 固件 50ms tick 实现 (TMR15 5ms×10) event_report (iot_mqtt_srv.c + net_srv.c): - 沿检测双路汇聚: Step1 消费路径 + lup 回调覆盖 uart_srv 路径, 不漏帧 - 16 深环形队列, 事件仅 ACK(code=0) 后出队; 溢出丢最旧 - 5s 超时重发同 msg_id/原始 ts ×3 次, 耗尽挂起, 新事件/重连沿合并补报 - 帧消费提前至 READY/enable 之前: 断网期间事件入队, 重连补报 - 线圈断开期间屏蔽 car 沿; loop_restore value=本地计时/50 - msg_id 独立 uint32 计数 (g_iot_msg_id 为 uint8 混用会破坏去重窗口) - net_srv.c ACK 路由: cmd=event_report 回显帧确认出队, 不回 unsupported - gcc 隔离单测 8 组全过 (tests/test_event_report.c), 单包 6 条实测 317B
18 KiB
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 回绕会破坏平台去重窗口) |
关键决策
- 帧消费提前到 READY/enable 之前: 断网/未使能期间事件照样检测入队, 重连后补报。loop_data 周期上报仍受 READY+enable 门控。
- event_report 不受 report_config.enable 限制(事件为关键不可再生数据; 若需要开关另加字段)— ⚠️ 待老大确认。
- 线圈断开期间屏蔽 car 沿: 断开时 car_state 不可信, 仅前后两帧 loop 均正常才判进出车; loop_restore 的 value = DBN 本地计时(mstick 差)/50。
- car_leave 的 value 直接取离开帧 misc(时间量) = 通过时间(50ms 单位)。
- 首帧只建快照: 上电线圈上已有车不算进入沿。
已知边界
- 队列溢出且恰有未决包时: 作废未决包重发新 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 快速上报。
实现要点(两个坑,首版都踩了)
if (fast_mode = 0)单等号 → 赋值恒假,沿检测死代码。修正为直接判断(循环内走到该行 fast_mode 必为 0,无需再判)。- 快照刷新必须在间隔门控之后。若在门控前无条件刷新
_last_car_state,沿被门控吞掉(如距上次发布 <300ms)时快照已同步,下一轮检测不到翻转 → 事件退化为空闲间隔上报。修正:return(未到间隔)时不刷快照,沿保持"待发"持续顶住 fast_mode,直到真正发布才同步。
/* 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 — 结构体字段类型
// 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 符号扩展:
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 阈值判断(隐藏坑)
// 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.cJSON 字段同步
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.cheartbeat:iot_make_topic(..., "dev", "status", ...)→snprintf(..., "dld960/%s/dev", ...)iot_mqtt_srv.ciot_mqtt_publish_sensor:g_iot_topic.topic_pub→dld960/{sn}/devnet_srv.cdev_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。
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条命令) |