From 6284bca0cb34d59f74f9b2322ff003fe195257de Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 15 Jul 2026 17:48:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(iot=5Fmqtt):=20loop=5Fdata=20=E9=99=88?= =?UTF-8?q?=E6=97=A7=E5=BF=AB=E7=85=A7=20=E2=80=94=20=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E4=B8=8E=E7=BC=93=E5=AD=98=E7=BB=9F=E4=B8=80=E5=B8=A7=E6=91=84?= =?UTF-8?q?=E5=8F=96=20(=E5=90=8C=E6=BA=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现场: 长车离开后 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 (此前裸解析未校验) --- .../APP/iot_mqtt_srv.c | 32 ++++++++++++----- vd960DBN/docs/devlog.md | 36 +++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c index bd9e378..fa77e84 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c @@ -150,12 +150,23 @@ static void iot_evt_feed(const LUP_SensorReport *sr) { } } -/* lup 传感回调: uart_srv 消费路径的帧从这里喂入 (已过校验) */ +/* 统一帧摄取: 事件沿检测 + 刷新 loop_data 缓存 (单一数据源) + 两条消费路径 (uart_srv→lup回调 / Step1直读) 都必须走这里。 + 教训(2026-07-15): 此前回调只喂事件、缓存只在 Step1 更新, 而 Step1 赢得 + 帧竞争的概率实测 <2% → loop_data 发布 34s 前的陈旧快照(车走后 iscar + 仍 true), 且陈旧 diff 锁死 fast_mode 300ms 刷屏。事件与快照必须同源! */ +static void iot_sensor_ingest(const LUP_SensorReport *sr) { + iot_evt_feed(sr); // 事件沿检测 + memcpy(&_cached_sr, sr, sizeof(LUP_SensorReport)); // 刷新 loop_data 快照 + _cached_sr_valid = 1; +} + +/* lup 传感回调: uart_srv 消费路径的帧从这里喂入 (lup_process_frame 已过校验) */ static void iot_evt_sensor_cb(const uint8_t *pkg, uint16_t len) { LUP_SensorReport sr; memset(&sr, 0, sizeof(sr)); if (lup_parse_sensor_report(pkg, len, &sr) == 0) { - iot_evt_feed(&sr); + iot_sensor_ingest(&sr); } } @@ -584,24 +595,27 @@ static void iot_process_recv(void) { void iot_mqtt_publish_sensor(void) { static uint8_t _last_car_state[4] = {0}; - /*--- Step 1: 消费 0xC0 帧 → 事件沿检测 + 更新缓存 ---*/ + /*--- Step 1: 消费 0xC0 帧 (Step1 直读路径, 竞争窗口小, 少数帧走此路) ---*/ /* 注意: 置于 READY/enable 检查之前 —— 断网/未使能期间事件照样入队, - 重连后由 iot_evt_process() 补报 (协议 V1.04) */ + 重连后由 iot_evt_process() 补报 (协议 V1.04)。 + 多数帧由 uart_srv→lup 回调路径摄取, 两路都汇入 iot_sensor_ingest */ if (g_pkg_uart_2.flag != 0 && g_pkg_uart_2.pkg[0] == 0x7F && g_pkg_uart_2.pkg[3] == 0xC0) { LUP_SensorReport sr; + int ret = -100; memset(&sr, 0, sizeof(sr)); - int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); + /* 直读路径未经过 lup_process_frame, 须自行校验 checksum */ + if (lup_verify_checksum(g_pkg_uart_2.pkg, g_pkg_uart_2.offset) == 0) { + ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); + } InitPkgUart(&g_pkg_uart_2); // 读完即清, 不持有 if (ret == 0) { - iot_evt_feed(&sr); // 事件沿检测 (Step1 消费路径) - memcpy(&_cached_sr, &sr, sizeof(sr)); - _cached_sr_valid = 1; + iot_sensor_ingest(&sr); // 统一摄取: 事件沿 + loop_data 缓存 } else { - PRINT("IOT: sensor parse failed (%d)\n", ret); + PRINT("IOT: sensor frame invalid (%d)\n", ret); } } diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 2355962..3952722 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -6,6 +6,42 @@ --- +## 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 沿立即上报 ### 背景