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 组全过
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
### 背景
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/* 隔离单测: 传感数据三档上报调度 (与 iot_mqtt_publish_sensor Step2/3 同构)
|
||||
* 立即档: car_state 沿 → 直通门控
|
||||
* 快速档: |variation|>10 → 300ms
|
||||
* 空闲档: interval (测试用 60s)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user