Files
vd_960/vd960DBN/docs/devlog.md
T
wangfq e54334e761 docs: event_report 不受 report_config.enable 门控 (王工拍板)
- devlog 决策落定, 移除待确认标记
- MQTT/TCP 两协议 §5.x 补充: enable/interval 仅管 loop_data, 事件上报解耦
2026-07-15 13:45:51 +08:00

377 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 门控**(王工 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→56BDBN 侧 `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 创建(端口 5960PROTO_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 基础打通
### 背景
在 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` 等),恢复完整字段并在分批框架内发送。
---
## 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_idQoS=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条命令) |