Commit Graph
126 Commits
Author SHA1 Message Date
wangfq 92887a3d92 feat(vd960DBN): 传感快照区落地 snapshot.c + BLE SNAP_STAT/QUERY/CLEAR
- snapshot.h/c: 64B 定长 SnapRec (头部16B + 4x12B 0xC0线圈数据原样)
  环形分区独立于事件日志, 快照区 = 总容量 - 固定区576KB - 事件区
- 线程模型: USART2 ISR 只打包+RAM暂存(8深满丢新), 主循环 snap_flush 落盘
  (iot_sensor_ingest 在中断上下文, SPI 45ms 擦除严禁进中断)
- dbn_ble_srv: SNAP_STAT(0x28)/SNAP_QUERY(0x29)/SNAP_CLEAR(0x2A)
  QUERY 上限 2 条 (64x2+2=130B, ODR 教训防御)
- iot_mqtt_srv: ingest 挂 snap_enqueue, publish_sensor 挂 snap_flush
  (放在 READY 检查前, 断网照常落盘)
- peripheral_main: offlog_init 后加 snap_init
- 清空审计: 写事件流 log_clear payload[0]=2
- 测试: test_snapshot.c 8 例全过, offlog/ble_offlog 回归全过
- 文档: DLD960_BLE协议 V1.02 + ROADMAP P1.2 状态 + devlog
2026-08-12 17:40:14 +08:00
wangfq a8120cc852 feat(vd960DBN): 事件日志区动态分区 — JEDEC ID 选档 (ROADMAP 落地)
- offlog.h: OfflogPart 运行时分区表 + g_offlog_part; AREA_BASE 改 0x090000
  (参数64KB+OTA512KB); 容量宏展开为运行时值(主体零改动)
- offlog.c: offlog_part_detect() 读 JEDEC ID → 事件区容量
  W25Q32=512KB/16256条, Q64=1MB/32640, Q128=2MB/65408, Q256=4MB/130944
- storage.c/h: SPI_Flash_ReadJEDEC_ID() (0x9F, 校验 EF 40)
- 旧固件事件区 0x010000 自动废弃(新地址无 magic → 重建), 预期行为
- 测试: mock JEDEC=0x16 + g_offlog_part, 15 例全过
- 文档: BLE/MQTT/TCP 协议 capacity 8064→动态, 交互示例, devlog
2026-08-12 16:46:19 +08:00
wangfq 193455e629 docs(vd960DBN): devlog 记录第二分包根因 — 响应缓冲越界踩踏 g_notify_buftemp 2026-08-12 14:51:47 +08:00
wangfq e8b7c6f70c fix(vd960DBN): BLE 响应缓冲越界防御 — 根因: MAX_BLE_DAT_BUF_LEN 宏不一致(ODR)
根因链 (14:46 日志 + .map 铁证):
- .map: g_buf_ble_response = 107B (7 + dat[100]) -> 编译时 MAX_BLE_DAT_BUF_LEN=100
- 但 QUERY 响应 130B 写入 dat[0..129] -> 越界 30B
- 越界踩中相邻 g_notify_buftemp.flag/len (0x20007BDC)
- 第二分包填充后被物理抹除 (无 CLR_NTF = 非 clear_ble_notify_buf 干的)

修复 (兼容新旧宏, 永不越界):
- QUERY: _req_count 按 MAX_BLE_TMP_BUF_LEN 动态限制 (100->3条/132->4条)
- set_response_buf: dat_len 截断到 MAX_BLE_DAT_BUF_LEN
- 验证: 旧宏100 下 3条=98B 分包[93,17] 全合规
2026-08-12 14:51:21 +08:00
wangfq 67734d3b9a debug(vd960DBN): clear_ble_notify_buf 抓现行 — buf指针 + 调用者返回地址
- 沿监控证实: 第二包填充成功 (flag=1 len=49 temp=1) 后被 clear_ble_notify_buf 清空
  (flag=0 len=0), 但无 BLE send 打印 = 不是 performPeriodicTask 发送路径
- clear_ble_notify_buf 入口: 有货被清时打印 buf 指针 + __builtin_return_address(0)
- 若 buf 指针 != g_notify_buftemp → 指针/内存布局问题; ret 对 .map 定位调用者
2026-08-12 14:41:44 +08:00
wangfq 6582b93938 debug(vd960DBN): notify flag / ntf_temp 变化沿监控 — 抓第二包消失瞬间
- ret=00008122 对 .map = set_response_to_notify 内部 (0x7FEC+0x136)
  → CLEAR busy 是第二包填充完成的正常清空, 队列清空不是问题!
- 真正问题: 第二包填充后 notify flag 从 1 被清 0, g_flag_notify_temp=0
  → 下一个 TMOS 周期不发送 (无 BLE send len:49)
- 加 BLE ntf flag x->y (len= temp=) 沿监控, 只在变化时打印
2026-08-12 14:28:41 +08:00
wangfq 7a08e083d6 debug(vd960DBN): clear_buf_dbn_ble 抓现行 — 有货被清时打印调用者返回地址
- 已排除 simpleProfile_Notify (发送后 resp=1 队列完好)
- 队列在 hex 打印后 ~20ms 被清空 (amt/seq/off 全0 = clear_buf_dbn_ble 效果)
- clear_buf_dbn_ble 入口: 仅当 buf->flag=1 (有货被清) 时打印 + __builtin_return_address(0)
- 烧录后 ret=地址 对照 .map 文件直接定位调用者
2026-08-12 14:14:39 +08:00
wangfq 66f156105c debug(vd960DBN): peripheralChar4Notify 内 resp 队列状态实验打印
- 上一轮日志: BLE send 时 resp=1 amt=2 seq=1 off=87 (队列正常),
  但 peripheralChar4Notify 执行后下一周期队列已空
- 锁定嫌疑: simpleProfile_Notify 库函数内部是否清了 g_buf_ble_response
- N4 enter / after simpleProfile_Notify 各打印一次 resp 状态
2026-08-12 14:08:34 +08:00
wangfq 60576b2b33 debug(vd960DBN): 抓现行打印 — 发送前队列状态 + resp flag 变化沿
- 心跳证实: 第一包发出后 resp=0 amt=0, 队列已被意外清空 (第二包不存在)
- poll_dbn_ble 尾部: resp flag 变化沿打印 (0->1/1->0 各一次, 不刷屏)
- performPeriodicTask: 发送前打印 len + resp 队列状态
- 烧录后一次看清 谁在何时清了 g_buf_ble_response
2026-08-12 14:02:23 +08:00
wangfq ef3316f2b3 docs(vd960DBN): BLE 日志查询交互过程文档 + 协议 V1.01 动态分包 + 周期心跳
- 新增 docs/DLD960_BLE日志查询交互.md: 0x25/0x26/0x27 完整交互示例
  (真实日志逐字节解析, 分包规则, 第二分包未发问题记录)
- DLD960_BLE协议.md V1.01: 分包上限改为随协商 MTU 动态 (min(MTU-9,94))
- peripheral.c: performPeriodicTask 加 500ms 心跳打印 (确认 TMOS 周期存活
  + resp 队列状态, 定位第二分包为何不续传)
2026-08-12 13:51:32 +08:00
wangfq 070db9f383 debug(vd960DBN): performPeriodicTask 拉包失败打印 resp 状态 + 注释闭合
- 第二分包未进发送函数, 加条件打印 (仅当队列有货但拉包返回0时)
  BLE pull FAIL: resp flag/amt/seq/off 一烧录即知第二包断在哪
- 补 ble_notify_chunk_max 注释块闭合 */
2026-08-12 11:55:44 +08:00
wangfq 5eef0756f2 debug(vd960DBN): BLE notify 发送数据 hex 打印 — 排查第二分包
- peripheralChar4Notify OK 分支: 打印 len/MTU + 整包 hex (对齐收包打印风格)
- 烧录后设备日志可直接比对发送帧与小程序收到帧
2026-08-12 11:45:06 +08:00
wangfq bcf797285b fix(vd960DBN): BLE 第二分包丢失 — performPeriodicTask 主动拉包 + 发送日志
- 现场: 第一包 93B 正常收到, 第二包 49B 永远收不到 (两包间隔 50ms)
- 根因: 分包续传依赖 poll_dbn_ble 下一轮收包/主循环轮转填充, 与 TMOS 周期交错
- peripheral.c: performPeriodicTask 发完当前包后主动 set_response_to_notify 拉下一分片,
  与收包事件解耦; peripheralChar4Notify 增加 BLE notify OK/FAIL 打印 (发送失败不再静默)
- 隔离测试: 仅靠周期事件驱动, MTU=96 3 周期发出 93B+49B, 重组 130B 一致
2026-08-12 11:35:21 +08:00
wangfq 62f7de0ca2 fix(vd960DBN): BLE 分包粒度动态化 — 修复 offlog_query Too large noti 丢包
- 根因: MAX_BLE_DAT_RESPONSE_LEN=96 写死, MTU 协商 96 后整包 102B > MTU-3=93
  peripheralChar4Notify 直接 return 丢第一包, 小程序重组不完整
- peripheral.c: 新增 peripheral_get_mtu() getter
- dbn_ble_srv.c: ble_notify_chunk_max() 按 min(MTU-9,94) 动态分包
  4 处 MAX_BLE_DAT_RESPONSE_LEN 统一替换 (set_response_buf/to_notify/iot_net/iot_topic)
- 消除 BLE_Notify_Buf.buf[100] 写 102B 越界 2B 隐患
- 隔离 C 测试 MTU=23/96/185/517 四组全过, 重组 130B 逐字节一致
2026-08-12 08:30:44 +08:00
wangfq d6174b9d3f feat(vd960DBN): BLE 读取脱机日志 — OFFLOG_STAT/QUERY/CLEAR (0x25/0x26/0x27)
蓝牙通道补齐脱机日志读取 (此前仅 MQTT V1.06 / TCP JSON V1.02 有 log_* 命令):
- 3 条 BLE 命令, 语义对齐 MQTT log_stat/log_query/log_clear
- QUERY 直接传 32B OfflogEvt 原始结构 (二进制协议, 无需 JSON), 复用
  idx = start_seq - seq_first 定位, 不新增 offlog API
- 缓冲扩容: MAX_BLE_TMP_BUF_LEN 100→132, 新增 MAX_BLE_DAT_BUF_LEN=132
  (QUERY 响应 1+4x32B=129B), clear_buf_dbn_ble_all memset 同步
- 新协议文档 docs/DLD960_BLE协议.md V1.00 (帧格式+分包+命令+记录结构)
- 隔离测试 test_ble_offlog.c 嵌入源文件 3 case 真实文本, 7 例全过;
  offlog 回归 8 例 ALL PASS
- devlog V4.0; README 协议矩阵补 BLE 行
2026-08-10 14:24:20 +08:00
wangfq 1a01316c7f feat(vd960DBN): offlog 协议导出命令分发 — log_stat/log_query/log_clear 落地
协议先行 (08893d5, MQTT V1.06 + TCP JSON V1.02) 的固件侧实现:
- offlog 新增导出 API: enabled/seq_last/type_str/evt_to_json
  (seq_first=seq_last-count+1, idx=start_seq-seq_first, count≤4)
- MQTT iot_handle_publish 三分支: log_stat/log_query/log_clear
  (log_query 响应 static buf 防大栈; log_clear 阻塞~2.8s 主循环可接受)
- TCP JSON 三 handler + cmd 表 (需鉴权)
- 单测新增 test_export_json (8 例全过): boot rst 大端/coil sub/evt_retry/data:null/seq 公式
- devlog V3.9
2026-08-05 08:53:18 +08:00
wangfq 90755bf465 feat(vd960DBN): offlog TIME_ANCHOR 严格用平台下发 unix_ts
offlog_time_anchor 原丢弃传参内部重取 dev_time_now() (毫秒级误差),
改为 offlog_evt_ts() 直接写入平台下发值, 锚点事件与平台 ts 严格一致。
新增单测 test_time_anchor_exact, 7 例 ALL PASS
2026-08-04 18:10:18 +08:00
wangfq 2b1e888a73 refactor(vd960DBN): tcp_json_srv data_json[2048] 宏化 → TCP_JSON_DATA_BUF_LEN(1024)
三处 char data_json[2048] (sensor_report×2 + loop_param_query×1)
改用头文件宏 TCP_JSON_DATA_BUF_LEN, 默认 1024。

容量核验: format_sensor_json 4通道~660B / format_loop_param_json~920B
均 < 1024, format 函数有 snprintf 溢出保护; 最终帧受 MAX_FRAME=800 截断。
附带: 栈上缓冲 2048→1024 每处省 1KB
2026-08-04 17:06:29 +08:00
wangfq 5155219c92 fix(vd960DBN): 修复 offlog 编译错误 — 声明补齐 + RCC 宏移入公共头
MRS 编译报错:
1. offlog.c: SPI_Flash_Erase_Sector 隐式声明 → storage.h 补声明
2. peripheral_main.c: OFFLOG_RCC_RSTSCKR 未声明 → RCC 宏从 offlog.c
   移到 offlog.h (peripheral_main.c 只 include offlog.h)
3. offlog.c 删除本地重复 RCC 宏定义

单测重跑 ALL PASS
2026-08-04 16:51:53 +08:00
wangfq ac3fb512d5 feat(vd960DBN): 脱机事件日志系统 (W25Q32 环形) — 解决重复上线取证
背景: 设备挂平台测试出现重复上线(现场未断电), 无本地日志无法区分
设备真复位 vs MQTT 断连重连。iot_handle_suback 每次重连都发
initialize 是重复上线的直接证据, 日志需能区分二者。

实现 (V3.6, ROADMAP P1.2 事件流先行):
- offlog.c/h: W25Q32 256KB 环形事件日志 (头扇区+63数据扇区, 8064条)
  - 32B 定长记录: magic/type/len/flags/seq/ts_ms/unix_ts/boot_seq/payload[12]
  - 8 类事件: BOOT(复位原因)/IOT_CONNECT/READY/DISCONN/RECONN/
    EVT_RETRY/GIVEUP/COIL/TIME_ANCHOR/LOG_CLEAR
  - 掉电恢复: 头扇区写指针锚点 + 上电 seq 连续性扫描
  - 编译期断言防 32B padding 回归
- 插桩 (全部主循环上下文, socket 中断内不写 SPI):
  - main(): offlog_init + RCC_RSTSCKR 复位原因采集/清除
  - iot_mqtt_poll(): MQTT 状态沿检测 (CONNECT/READY/DISCONN)
  - 重连退避/TCP超时/CONNACK拒绝: RECONN/DISCONN(4)/(3)
  - iot_evt_process/enqueue: EVT_RETRY/GIVEUP/COIL
  - net_srv.c dev_time_sync: TIME_ANCHOR 时钟同步锚点
- tests/test_offlog.c: gcc 隔离单测 6 例 (mock W25Q32 NOR 语义)

关键坑: ①中断内写SPI阻塞 ②结构体36B padding致环形错乱
③扇区级覆盖粒度count扣减语义 (均单测抓出)

待办: P1.3 导出命令(log_query/stat/clear) + 快照流 + 复位原因板上验证
2026-08-04 15:04:12 +08:00
wangfq a5ac54a2bf docs(vd960DBN): 开发日志 V3.5 — 保活精简 + SocketSend 故障恢复 2026-07-23 18:36:40 +08:00
wangfq 98d9b99cf6 fix(vd960DBN): 断连重连时重建 TCP socket (不是复用已 close 的)
root cause 1: 3次失败断连后, iot_connect_broker() 只设
  g_iot_socket=SocketId_TCP, 但 WCHNET_SocketClose 后
  socket 已销毁, 没有重建 → TCP connect 一直超时

root cause 2: old socket close 未完成就立即重连,
  WCHNET_SocketCreat 可能失败

fix:
  1. iot_connect_broker() 先调 WCHNET_CreateTcpMqttSocket()
     真正 create + connect
  2. 3次失败后加 3s 延迟再重连, 给 WCHNET 清理时间
2026-07-23 17:41:41 +08:00
wangfq 7c0de6c566 fix(vd960DBN): SocketSend 连续3次失败 → 主动断连重连
root cause: WCHNET_SocketSend 返回 ret=0x11(sent=0) 后
  TCP超时要等~2分钟才触发 SINT_STAT_TIM_OUT, 期间所有
  loop_data/event_report 全丢, 平台收不到任何数据

fix: iot_mqtt_send 追踪连续失败计数, 3次→强制 close socket
  + g_iot_state=DISCONNECTED, 触发 iot_mqtt_poll 重连
  (成功一次就清零, 新连接也清零)
2026-07-23 17:14:49 +08:00
wangfq 29d1f6a783 refactor(vd960DBN): 去掉 heartbeat JSON 上报, MQTT PINGREQ 已保活
heartbeat 是单向设备上报, 平台不回复; MQTT PINGREQ/PINGRESP
已是双向保活。loop_data 按 g_report_cfg.interval 上报即可。

删除: iot_send_heartbeat() 函数 (省 ~40行 + BSS payload[512]→栈)
2026-07-23 15:28:10 +08:00
wangfq bdc2720d96 docs(vd960DBN): 开发日志 — V3.4 今日MQTT栈重构完整记录 2026-07-23 15:22:29 +08:00
wangfq 8f1bbf75f8 refactor(vd960DBN): 看门狗精简 — 去掉平台响应超时, 只保留硬件 IWDG
heartbeat 是单向设备上报, 平台不回复; 用 '3×心跳无平台回复→重启'
逻辑不成立。保活依赖三层已有机制:
  MQTT层: PINGREQ/PINGRESP (60s) → broker不回 → TCP超时
  TCP层:  KeepAlive → WCHNET SINT_STAT_DISCONNECT/TIMEOUT
  硬件层: IWDG (4s) → 主循环卡死 → 硬件复位
2026-07-23 15:21:59 +08:00
wangfq 127fed4b07 feat(vd960DBN): 硬件 IWDG 看门狗 + 平台响应超时重启
设计:
  1. 硬件 IWDG: LSI≈40kHz, Prescaler=256, Reload=625 → ~4s 超时
     主循环 iot_mqtt_poll() 每轮喂狗, 防死循环/硬故障

  2. 平台响应看门狗: 3×心跳周期(180s)内无平台回复 → NVIC_SystemReset
     _iot_last_recv_ms 刷新点:
     - iot_handle_publish() — 收到平台任意 PUBLISH 命令
     - iot_evt_handle_ack() — event_report 的 ACK
     - iot_handle_suback() — 刚连上 READY 时初始化计时

  3. 仅在 IOT_STATE_READY 时检查 (未连上不触发)
2026-07-23 14:49:07 +08:00
wangfq 199cb5d23f config: WCHNET_TCP_MSS 576→700, TCP_JSON_MAX_FRAME 4096→800
- WCHNET_TCP_MSS=700: 减少 TCP 分段, 配合 iot_mqtt_send 循环重试
  → RECE_BUF_LEN 自动扩至 1400 (原 1152)
- TCP_JSON_MAX_FRAME=800: 匹配实际 JSON 帧大小
2026-07-23 14:30:47 +08:00
wangfq 9e830639d3 fix(vd960DBN): iot_mqtt_send 循环重试防 WCHNET 部分发送致 _raw
root cause: WCHNET_SocketSend 返回部分发送(520/607B),
  剩余字节留在TCP发送队列, 与下一个MQTT包拼接,
  broker收到乱码 → _raw + RST断连

fix: iot_mqtt_send 加 while 循环, 指针偏移重试直到全部发出;
  最多10次重试, slen==0或错误时返回-1
2026-07-23 13:57:54 +08:00
wangfq a9c36e4c9d fix(vd960DBN): 心跳 msg_id 双递增修复 + %d→%lu
root cause: iot_send_heartbeat 先 ++g_iot_msg_id(JSON) 再
  iot_mqtt_publish 内部 ++g_iot_msg_id(MQTT pktid) → 跳号

fix: 心跳改用 g_iot_msg_id + 1 (与其他发布对齐)
2026-07-23 13:43:13 +08:00
wangfq 67976eca8e fix(vd960DBN): IoT MQTT 命令处理完善 — report_config + event_report ACK + initialize 走 IoT 路径
issues:
  1. iot_handle_suback 调 dev_initialize_pub() → 旧 mqttBuf 路径, 与 IoT 栈混淆
  2. report_config 命令无人处理 → 平台收到 code=4 unsupported
  3. event_report ACK (code=0) 被当 unsupported command → 重发闭环断裂

fix:
  1. 新增 iot_send_initialize() — IoT 路径, 用 iot_mqtt_publish
  2. 新增 report_config 处理 — 解析 enable/interval/once/env_eval/sensor_type
  3. 新增 event_report ACK 识别 — code=0 → iot_evt_handle_ack; ACK 不回复
2026-07-23 12:24:09 +08:00
wangfq a577537c38 fix(vd960DBN): 修复 WCHNET_SocketSend 参数类型不匹配致栈破坏 hard fault
root cause: iot_mqtt_send() 声明 uint16_t slen = len,
  传给 WCHNET_SocketSend(..., uint32_t *len) 时取址 &slen。
  WCHNET 写 4 字节返回值到 2 字节栈变量 → 破坏相邻栈内容
  (返回地址/帧指针) → 函数返回时 hard fault 重启

fix: slen 改为 uint32_t
2026-07-23 11:52:58 +08:00
wangfq 32f0edb875 fix(vd960DBN): MQTT CONNECT 改回中断上下文发送, 消除 SocketSend hard fault
root cause: WCHNET SocketSend 在中断上下文外调用会 hard fault。
  旧代码 mqtt_connect() 在 WCHNET_HandleSockInt CONNECT 中断里直接调;
  v4 改成了延后到主循环 iot_mqtt_poll() 里发 → hard fault

fix: iot_mqtt_handle_sock_int 的 CONNECT 分支直接调用
  iot_mqtt_send_connect() (对齐旧代码行为), 不再延后到 poll
2026-07-23 11:47:27 +08:00
wangfq 9521a5e667 fix(vd960DBN): 修复 SocketSend 后重启 — RecvBuf 用错 + 栈溢出
root cause:
  1. iot_mqtt_handle_sock_int 的 CONNECT 处理调了
     WCHNET_ModifyRecvBuf(_iot_wchnet_buf), 覆盖了 WCHNET 原生的
     SocketRecvBuf, 导致后续 SocketSend 时 DMA 访问非法内存 → hard fault
  2. uint8_t tmp[RECE_BUF_LEN](1152B) 在中断栈上, 叠加调用链
     可能撑爆 2KB 栈

fix:
  1. net_srv.c WCHNET_HandleSockInt: 保留 SocketRecvBuf + KeepLive,
     其余事件委托给 iot_mqtt_handle_sock_int
  2. iot_mqtt_handle_sock_int: 移除 ModifyRecvBuf(已在 wrapper 处理)
  3. tmp[1152] 改为 static (移到 BSS, 省 1152B 栈)
2026-07-23 11:39:52 +08:00
wangfq 3d017ccca9 fix(vd960DBN): IoT MQTT 栈独立接管 socket 事件, 消除双 CONNECT 致频繁 initialize
root cause: WCHNET_HandleSockInt 在 iot_enable=1 时仍走旧 MQTT 代码:
  CONNECT → mqtt_connect()     ← 发 MQTT CONNECT #1
  iot_mqtt_poll() → send_connect() ← 发 MQTT CONNECT #2 (同一socket)
  broker 见双 CONNECT → 协议违规踢线 → 4s 后重连 → 循环
  RECV → mqtt_data_manage()   ← 旧代码收 CONNACK
        → dev_initialize_pub() ← initialize msg_id=1, 强设 g_iot_state=READY

fix:
  1. net_srv.c WCHNET_HandleSockInt: iot_enable 时直接调 iot_mqtt_handle_sock_int
  2. peripheral_main.c: poll_mqtt() → iot_mqtt_poll() (IoT 自有 PINGREQ)

BSS savings: -1024B (data_json eliminated), send buffer 隔离 (v3)
2026-07-23 11:25:52 +08:00
wangfq 086dbc6d7c fix(vd960DBN): loop_data 改用 iot_mqtt_publish 发送, 缓冲隔离防 _raw 异常 v3
- root cause (v3): event_report 的 mqtt_publish → MQTTSerialize_publish(mqttBuf)
  写入的 MQTT 二进制泄漏到 loop_data 的 payload 缓冲, 产生 _raw 异常

- fix: loop_data 改用 iot_mqtt_publish() 发送, 其内部 buf[1024] 与
  全局 mqttBuf[1024] 物理隔离, event_report 和 loop_data 不再争用同一缓冲

- 同时: 消灭 data_json[1024] (v1), coil_count 硬限 (v1),
  msg_id 改预读避免 iot_mqtt_publish 内部 ++ 导致跳号

- note: iot_send_heartbeat 有同样 msg_id 双 ++ 问题, 待修
2026-07-23 11:04:17 +08:00
wangfq e11a80c859 fix(vd960DBN): 消灭 data_json[1024] 中间缓冲, 防 BSS 重叠致 MQTT 上报 _raw 异常
- root cause: data_json[1024] + payload[1400] BSS 合计 2424B,
  叠加 iot_mqtt_srv.c 其他缓冲 (~9KB total), 在 48KB RAM 上
  与 mqttBuf[1024] 重叠, event_report 的 MQTT 二进制
  泄漏到 loop_data payload

- fix: 直接在 payload 构建完整 JSON, 省 1024B BSS
- defense: coil_count > 4 硬限, 防 0xC0 坏帧溢出
- JSON 输出格式不变

- also: set_response_tran_to_notify 加返回值
- also: uart_srv BLE 通知逻辑重构
2026-07-23 09:26:18 +08:00
wangfq d61ff830aa fix(vd960DBN): BUFF_STACK_SIZE 512→64 对齐 Loop 侧串口帧缓冲
- 串口帧缓冲装的是 Loop↔DBN 的 0x7F 私有协议帧, 与 Loop 侧发送缓冲(64)同源
- 当前最大帧 0x0C 多线圈上报实测 56B (Len=51), 64 留 8B 余量, 512 严重浪费
- g_pkg_uart_1/2 各省 448B, 合计回收 ~896B SRAM
- 顺带删除零引用死结构体 USART_DMA_UNIT + RX_BUFFER_LEN 宏
2026-07-16 14:52:16 +08:00
wangfq 6284bca0cb fix(iot_mqtt): loop_data 陈旧快照 — 事件与缓存统一帧摄取 (同源)
现场: 长车离开后 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 (此前裸解析未校验)
2026-07-15 17:48:30 +08:00
wangfq a457b81916 feat(iot_mqtt): loop_data 上报调度升级三档 — car_state 沿立即上报
原双速率下进/出车沿最坏等 300ms 门控。改为三档:
- 立即档: 任一通道 car_state 翻转沿 → 直通间隔门控立即发布
- 快速档: |variation|>阈值 → 300ms (不变)
- 空闲档: 配置 interval, 默认 60s (不变)

要点:
- car_edge 优先级最高; 快照仍在门控后刷新, 同沿只触发一次立即档
- 防洪泛天然有界: Loop 事件帧最快 150ms/帧 且 car_state 为防抖后状态
- 协议 §5.2 补上报节奏说明; tests/test_report_cadence.c 8 组全过
2026-07-15 16:56:21 +08:00
wangfq 3e28174b40 feat(mqtt): 设备时钟同步方案B — 平台经 report_config 下发 Unix ts (协议V1.05)
设备无RTC/SNTP, 原上行 ts=mstick()/1000(上电秒数)。方案B:
- net_srv.c 新增 dev_time_sync()/dev_time_now(): 门槛≥1600000000挡上电秒数,
  已校准返回真Unix, 未校准退回上电秒数; 无符号相减处理mstick 49.7天回绕
- manage_mqtt_recv_message 从下行命令信封 ts 校准 (report_config主同步点)
- 全部上行 ts (14处命令响应+initialize+loop_data+event_report) 改 dev_time_now()
  heartbeat uptime 字段保留 mstick()/1000 (运行时长非时间戳)
- 协议 V1.05 §2.3 时间同步 + report_config 兼任同步点职责
- tests/test_dev_time_sync.c 6组全过

平台侧配合: 收到 initialize 后下发 report_config 时 ts 填当前 Unix 时间
2026-07-15 15:25:44 +08:00
wangfq e3eaa111fe fix(mqtt): 修复 loop_data>512B 溢出发垃圾包致 broker RST 重连风暴 (现场P0)
根因(非平台推测的环形缓冲/event_report队列):
  mqtt_publish 的 mqttBuf=512B < loop_data(4通道)604B
  → MQTTSerialize_publish 返回 -2 不写 buf
  → len 为 uint32_t, -2 变 42.9亿
  → WCHNET 把清零缓冲+越界相邻全局(temp_guide=report_config→report_c)
    当 520B 垃圾包发出 → broker 见非法类型0x00 RST → 重连风暴

修复(net_srv.c):
  - MAX_MQTTBUF_LEN 512→1024 (容纳 604B loop_data)
  - mqtt_publish: len uint32→int + 守卫 if(len<=0)return (序列化失败绝不发残缓冲)
  - keepalive 9→60 (报告建议, 9s过激)

event_report 协议违规修复(iot_mqtt_srv.c):
  - 重连重发保持原 msg_id/ts (V1.04 §5.3-2), 不再作废换号致平台去重失效

验证: tests/test_mqtt_publish_overflow.c 复现旧垃圾包+验证守卫/大缓冲;
      tests/test_event_report.c T8 断言重连同 msg_id. 均全过.
遗留: ts=上电秒数非Unix时间戳, 待定方案(设备无RTC/SNTP)

Refs: docs/incidents/2026-07-15-DC045A49718F-protocol-error.md
2026-07-15 14:58:03 +08:00
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
wangfq 4112b7cbbf feat(iot_mqtt): 实现 event_report 平台必答+设备重发 (协议 V1.04); 时间单位统一 50ms
时间单位:
- 串口协议 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
2026-07-15 12:27:22 +08:00
wangfq dc7dd1cb26 feat(iot_mqtt): 快速上报增加 car_state 翻转沿触发
- Step2: 任一通道 |variation|>=阈值 或 car_state 翻转沿, 均进入 300ms 快速档
  (高灵敏度档小车 variation 可能不过阈值, 但 Loop 已判翻转, 事件不应慢发)
- 修复首版两坑: if(fast_mode=0) 单等号致沿检测死代码;
  快照刷新移到间隔门控之后, 防止沿被门控吞掉后退化为空闲慢发
- gcc 隔离单测: buggy 沿后延迟 950ms vs fixed 250ms, 出车沿/负向variation回归通过
- devlog 补 2026-07-15 条目
2026-07-15 09:34:04 +08:00
wangfq 82e01cd337 docs(devlog): 记录 variation 2B->3B有符号升级 (V1.05)
- vd960Loop/docs/devlog.md: 组包端改动(uint32->int32,方案B,饱和防回绕),
  背景/影响范围/验证
- vd960DBN/docs/devlog.md: 接收端4处联动(结构体/解析步长/符号扩展/
  fast_mode abs阈值), 缓冲核查
两端均标注必须同版本发布
2026-07-14 16:48:25 +08:00
wangfq bfc9646e48 proto(V1.05): variation 由 2B无符号 扩展为 3B有符号
背景: variation 是遥测量, 用于离线跟踪分析车检器算法。原 2B 无符号
存在两个缺陷: (1)CAPVD域量级~131072, 大车全覆盖时偏差可超65535而回绕,
污染数据; (2)绝对值丢失方向, 分不清'车进入'与'反向漂移'。

改动 (三端联动, 同版本发布):
- 协议 DLD960Loop_串口通信协议.md: 变化量 2B->3B有符号补码, 定义
  variation=Origin-CAPVD (正=车/裕量, 负=反向漂移); 每单元11->12B,
  Len 47->51; 补符号扩展说明; 示例报文重算(XOR=FE SUM=38); 修订记录V1.05
- vd960Loop main.c: 组包 uint32->int32, Origin-CAPVD, 3B LE, +-2^23饱和防回绕
- vd960DBN loop_uart_proto.h: variation uint16->int32
- vd960DBN loop_uart_proto.c: 步长11->12, data_len/11->/12, misc偏移右移1B,
  variation 3B解码+bit23符号扩展
- vd960DBN iot_mqtt_srv.c: fast_mode 阈值判断改 abs(variation)>=10, 正负变化都加速

验证: 本地 gcc 隔离单测 编解码往返/符号扩展/饱和/方向语义/fast_mode 全通过;
大车 diff=100000 旧版回绕34464 新版正确保留。字节账: 帧56B < Loop缓冲64B
< DBN缓冲512B < MSS576, 均不溢出。整体固件编译需在 Keil/MounRiver 侧完成。
2026-07-14 14:47:39 +08:00
wangfq 403770e1c8 feat(iot_mqtt): 传感器MQTT主动上报改为双速率间隔上报
- 新增传感器数据缓存(_cached_sr), 0xC0帧到达时更新缓存, 不上报
- 根据各通道 variation 决定上报间隔:
  空闲态(diff < 10): 使用 g_report_cfg.interval 秒
  变化态(diff >= 10): 固定 300ms 快速上报
- interval=0 时自动退化为最小 1s 空闲间隔
- 断线时清缓存+复位计时, 重连后等新帧首发
- 消除对 g_pkg_uart_2.flag 的硬依赖, 从缓存自主定时上报

新增常量:
  IOT_MQTT_FAST_INTERVAL_MS     300
  IOT_MQTT_VARIATION_THRESHOLD  10
  IOT_MQTT_IDLE_INTERVAL_MIN_MS 1000
2026-07-13 10:57:40 +08:00
wangfq c2fb459a80 vd960DBN docs: 开发日志更新 V3.2 — 双主题统一 + initialize 对齐 + packet_id 断连修复 2026-07-10 15:02:04 +08:00
wangfq 01ab739d05 vd960DBN: 修复 mqtt_publish QoS>0 时 packet_id=0 导致 broker 断连
MQTT 规范要求 QoS≥1 的 PUBLISH 必须带非零 packet_id。
net_srv.c 的 mqtt_publish 硬编码 packet_id=0,broker 检测到协议违规后主动断开连接。
修复:QoS>0 时使用递增的 packet_id。
2026-07-10 09:20:14 +08:00