From a457b819161f83b54437ef7b9ee03260df3679da Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 15 Jul 2026 16:56:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(iot=5Fmqtt):=20loop=5Fdata=20=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E8=B0=83=E5=BA=A6=E5=8D=87=E7=BA=A7=E4=B8=89=E6=A1=A3?= =?UTF-8?q?=20=E2=80=94=20car=5Fstate=20=E6=B2=BF=E7=AB=8B=E5=8D=B3?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原双速率下进/出车沿最坏等 300ms 门控。改为三档: - 立即档: 任一通道 car_state 翻转沿 → 直通间隔门控立即发布 - 快速档: |variation|>阈值 → 300ms (不变) - 空闲档: 配置 interval, 默认 60s (不变) 要点: - car_edge 优先级最高; 快照仍在门控后刷新, 同沿只触发一次立即档 - 防洪泛天然有界: Loop 事件帧最快 150ms/帧 且 car_state 为防抖后状态 - 协议 §5.2 补上报节奏说明; tests/test_report_cadence.c 8 组全过 --- docs/DLD960_IoT_MQTT协议.md | 1 + .../APP/iot_mqtt_srv.c | 44 ++++++----- vd960DBN/docs/devlog.md | 30 +++++++ vd960DBN/tests/test_report_cadence.c | 78 +++++++++++++++++++ 4 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 vd960DBN/tests/test_report_cadence.c diff --git a/docs/DLD960_IoT_MQTT协议.md b/docs/DLD960_IoT_MQTT协议.md index c56e19f..e598b0a 100644 --- a/docs/DLD960_IoT_MQTT协议.md +++ b/docs/DLD960_IoT_MQTT协议.md @@ -679,6 +679,7 @@ dld960/{dev_serial}/{direction} > Topic: `dld960/{sn}/dev` > QoS: 0/1 +> **上报节奏(三档)**:① 任一通道 `iscar` 翻转(进/出车)→ **立即上报**,不受间隔限制;② 任一通道 `|diff|` 超阈值 → 300ms 快速档;③ 平稳 → 按 `report_config.interval`(默认 60s)。 ```json { diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c index 9a138e3..bd9e378 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c @@ -572,11 +572,14 @@ static void iot_process_recv(void) { } /*=========================================================================== - * 传感器数据上报 (双速率: 空闲态按 interval 上报, 变化态 300ms 快速上报) + * 传感器数据上报 (三档调度) + * 立即档: car_state 翻转沿 → 直通门控立即上报 (进/出车不等间隔) + * 快速档: |variation| > 阈值 → 300ms + * 空闲档: 按配置 interval (默认 60s, 最小 1s) * - * Step 1 — 消费 Loop MCU 新帧 → 更新缓存 - * Step 2 — 对所有通道检测 variation, 决定上报间隔 - * Step 3 — 间隔门控: 时间到了才从缓存发布 + * Step 1 — 消费 Loop MCU 新帧 → 事件沿检测 + 更新缓存 + * Step 2 — 判定上报档位 (car_edge / fast_mode / idle) + * Step 3 — 间隔门控 (car_edge 直通) *===========================================================================*/ void iot_mqtt_publish_sensor(void) { static uint8_t _last_car_state[4] = {0}; @@ -611,31 +614,33 @@ void iot_mqtt_publish_sensor(void) { /* 尚无缓存数据 → 不发 */ if (!_cached_sr_valid) return; - /*--- Step 2: 判断是否进入变化态 (任一通道 |variation|>=阈值, 或 car_state 翻转沿) ---*/ + /*--- Step 2: 判定上报档位 (三档) --- + car_edge : 任一通道 car_state 翻转沿 → 【立即上报】直通间隔门控 + fast_mode : 任一通道 |variation|>阈值 → 300ms 快速档 + 否则 : 空闲档, 按配置 interval (默认 60s, 最小 1s) */ uint32_t interval_ms; uint8_t fast_mode = 0; + uint8_t car_edge = 0; { uint8_t i; for (i = 0; i < _cached_sr.coil_count; i++) { + /* car_state 翻转沿(进车/出车)优先级最高, 命中即定档 */ + if (_last_car_state[i] != _cached_sr.coils[i].car_state) { + car_edge = 1; + break; + } // variation 为有符号(V1.05): 正=车/裕量, 负=反向漂移, 两向显著变化都加速 int32_t v = _cached_sr.coils[i].variation; int32_t av = (v >= 0) ? v : -v; if (av > IOT_MQTT_VARIATION_THRESHOLD) { - fast_mode = 1; - break; - } - /* car_state 翻转沿(进车/出车) 同样触发快速上报 - 注: 能走到这里 fast_mode 必为 0 (置 1 即 break), 无需再判 */ - if (_last_car_state[i] != _cached_sr.coils[i].car_state) { - fast_mode = 1; - break; + fast_mode = 1; /* 记录但不 break: 后续通道可能有沿(优先级更高) */ } } } - if (fast_mode) { - interval_ms = IOT_MQTT_FAST_INTERVAL_MS; // 300ms + if (fast_mode || car_edge) { + interval_ms = IOT_MQTT_FAST_INTERVAL_MS; // 300ms (car_edge 时门控被直通, 仅作记录) } else { /* 空闲态: 使用配置的 interval (秒→毫秒), 最小 1s */ uint32_t cfg_ms = (uint32_t)g_report_cfg.interval * 1000; @@ -643,12 +648,15 @@ void iot_mqtt_publish_sensor(void) { ? cfg_ms : IOT_MQTT_IDLE_INTERVAL_MIN_MS; } - /*--- Step 3: 间隔门控 ---*/ + /*--- Step 3: 间隔门控 (car_edge 直通: 进/出车立即上报, 不等间隔) --- + 防洪泛天然有界: Loop MCU 事件帧最快 150ms 一帧, 且发布后快照即同步, + 同一个沿不会重复触发立即档 */ { uint32_t now = mstick(); - if (_last_publish_ms != 0 + if (!car_edge + && _last_publish_ms != 0 && (now - _last_publish_ms) < interval_ms) { - return; // 未到间隔; _last_car_state 不刷新, 翻转沿保持"待发", 持续顶住 fast_mode + return; // 未到间隔; _last_car_state 不刷新, 翻转沿保持"待发" } _last_publish_ms = now; } diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 8cfe617..2355962 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -6,6 +6,36 @@ --- +## 2026-07-15 — loop_data 上报调度升级三档: car_state 沿立即上报 + +### 背景 + +原双速率 (空闲 interval / 变化态 300ms) 下, 进/出车沿最坏也要等 300ms 门控。车辆进出是最关键时刻 (道闸联动/计数), 王工要求: **car_state 翻转沿立即上报, 不等间隔**。 + +### 实现 (iot_mqtt_publish_sensor Step2/3) + +三档调度: + +| 档位 | 触发 | 间隔 | +|------|------|------| +| **立即档** | 任一通道 car_state 翻转沿 | **0 (直通门控)** | +| 快速档 | 任一通道 \|variation\| > 阈值(10) | 300ms | +| 空闲档 | 平稳 | 配置 interval (默认 60s, 最小 1s) | + +- car_edge 优先级最高, 命中即定档; variation 记录但不 break (后续通道可能有沿) +- 门控: `if (!car_edge && 未到间隔) return;` — 沿直通, 其余照旧 +- 快照仍在门控通过后刷新 → 同一个沿只触发一次立即发布, 下一轮回归常规档 + +### 防洪泛分析 + +立即档天然有界: Loop MCU 事件帧最快 150ms 一帧, car_state 是 Loop 侧防抖后的状态 (进入确认 3 次 + 离开检测), 不存在毛刺沿; 且发布后快照即同步, 同沿不重复。最坏情况 = 帧率上限 150ms/次, 可接受。 + +### 验证 + +`tests/test_report_cadence.c` 8 组全过: 空闲60s到点 / 进车沿距上次50ms立即发 / 同沿不重复 / 快速档仍受300ms门控 / 出车沿20ms立即 / 回落空闲 / 双通道同帧沿单包覆盖。 + +--- + ## 2026-07-15 — 设备时钟同步 (方案B): 平台经 report_config 下发 Unix ts ### 背景 diff --git a/vd960DBN/tests/test_report_cadence.c b/vd960DBN/tests/test_report_cadence.c new file mode 100644 index 0000000..b37d7be --- /dev/null +++ b/vd960DBN/tests/test_report_cadence.c @@ -0,0 +1,78 @@ +/* 隔离单测: 传感数据三档上报调度 (与 iot_mqtt_publish_sensor Step2/3 同构) + * 立即档: car_state 沿 → 直通门控 + * 快速档: |variation|>10 → 300ms + * 空闲档: interval (测试用 60s) + */ +#include +#include +#include + +#define THRESH 10 +#define FAST_MS 300 +#define IDLE_MS 60000 /* interval=60s */ +#define NCOIL 4 + +typedef struct { int32_t variation; uint8_t car_state; } Coil; + +static uint8_t last_car[NCOIL]; +static uint32_t last_pub; + +/* 返回 1=本轮发布 */ +static int publish_step(uint32_t now, Coil *coils, int n) { + uint8_t fast_mode = 0, car_edge = 0; int i; + for (i = 0; i < n; i++) { + if (last_car[i] != coils[i].car_state) { car_edge = 1; break; } + int32_t v = coils[i].variation, av = (v>=0)?v:-v; + if (av > THRESH) fast_mode = 1; + } + uint32_t itv = (fast_mode || car_edge) ? FAST_MS : IDLE_MS; + if (!car_edge && last_pub != 0 && (now - last_pub) < itv) return 0; + last_pub = now; + for (i = 0; i < n; i++) last_car[i] = coils[i].car_state; /* 门控后刷新快照 */ + return 1; +} + +static int fails = 0; +#define CHECK(c,m) do{ if(!(c)){ printf("FAIL: %s\n", m); fails++; } }while(0) + +int main(void) { + Coil coils[NCOIL]; memset(coils, 0, sizeof(coils)); + memset(last_car, 0, sizeof(last_car)); last_pub = 0; + + /* T1: 首发 (last_pub=0 直发) */ + CHECK(publish_step(1000, coils, NCOIL) == 1, "T1 首发"); + + /* T2: 空闲档 — 平稳时 60s 内不发, 到点才发 */ + CHECK(publish_step(1500, coils, NCOIL) == 0, "T2 空闲0.5s不发"); + CHECK(publish_step(31000, coils, NCOIL) == 0, "T2 空闲30s不发"); + CHECK(publish_step(61000, coils, NCOIL) == 1, "T2 空闲60s到点发"); + + /* T3: 立即档 — 进车沿, 距上次发布仅 50ms 也立即发 (旧版要等300ms) */ + coils[1].car_state = 1; /* ch2 进车 */ + CHECK(publish_step(61050, coils, NCOIL) == 1, "T3 进车沿距上次50ms仍立即发"); + + /* T4: 同一个沿不重复触发 — 快照已同步, 下一轮回到常规档 */ + coils[1].variation = 53; /* 有车, variation高 → 快速档 */ + CHECK(publish_step(61100, coils, NCOIL) == 0, "T4 沿已消费, 50ms后不再直通"); + CHECK(publish_step(61350, coils, NCOIL) == 1, "T4 快速档300ms到点发"); + + /* T5: 快速档仍受 300ms 门控 (variation大但无沿) */ + CHECK(publish_step(61500, coils, NCOIL) == 0, "T5 快速档150ms不发"); + CHECK(publish_step(61650, coils, NCOIL) == 1, "T5 快速档300ms发"); + + /* T6: 出车沿同样立即 — 距上次仅 20ms */ + coils[1].car_state = 0; coils[1].variation = 0; + CHECK(publish_step(61670, coils, NCOIL) == 1, "T6 出车沿20ms立即发"); + + /* T7: 出车后平稳 → 回落空闲档 */ + CHECK(publish_step(61970, coils, NCOIL) == 0, "T7 平稳300ms不发(已是空闲档)"); + CHECK(publish_step(121670, coils, NCOIL) == 1, "T7 空闲60s到点发"); + + /* T8: 多通道同帧翻转 → 一次立即发布覆盖全部 (单包含4通道) */ + coils[0].car_state = 1; coils[2].car_state = 1; + CHECK(publish_step(121700, coils, NCOIL) == 1, "T8 双通道沿立即发"); + CHECK(publish_step(121750, coils, NCOIL) == 0, "T8 快照已同步不重发"); + + printf(fails ? "\n== %d FAIL ==\n" : "\n== ALL PASS ==\n", fails); + return fails; +}