时间单位: - 串口协议 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
241 lines
11 KiB
C
241 lines
11 KiB
C
/* 隔离单测: iot_mqtt_srv.c event_report 模块 (协议 V1.04)
|
|
* 覆盖: 沿检测 / 队列 / ACK出队 / 5s重发×3 / 挂起与解除 / 重连补报 / 包长上限 / 溢出
|
|
* 被测代码与固件同构 (mock: mstick, mqtt_publish, PRINT, LUP 类型)
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
/* ---------- mocks ---------- */
|
|
#define PRINT(...) do{}while(0)
|
|
#define LUP_COIL_COUNT 4
|
|
typedef struct { uint8_t car_state, loop_state, misc_type; struct { uint32_t passtime_ms; } misc; } LUP_CoilSensor;
|
|
typedef struct { uint8_t coil_count; LUP_CoilSensor coils[LUP_COIL_COUNT]; } LUP_SensorReport;
|
|
typedef enum { IOT_STATE_DISCONNECTED=0, IOT_STATE_READY=5 } IotMqttState;
|
|
static IotMqttState g_iot_state = IOT_STATE_READY;
|
|
static uint32_t _mock_ms = 0;
|
|
static uint32_t mstick(void) { return _mock_ms; }
|
|
static struct { uint8_t topic_pub[64]; } g_iot_topic = {"dld960/AA/dev"};
|
|
/* 捕获发布 */
|
|
static char last_payload[600]; static int pub_count = 0;
|
|
static void mqtt_publish(char *topic, char *payload, int qos) {
|
|
(void)topic; (void)qos;
|
|
strncpy(last_payload, payload, sizeof(last_payload)-1);
|
|
pub_count++;
|
|
}
|
|
|
|
/* ---------- 被测模块 (与固件同构) ---------- */
|
|
#define IOT_EVT_QUEUE_DEPTH 16
|
|
#define IOT_EVT_MAX_PER_PKT 6
|
|
#define IOT_EVT_ACK_TIMEOUT_MS 5000
|
|
#define IOT_EVT_MAX_RETRY 3
|
|
enum { IOT_EVT_CAR_ENTER = 0, IOT_EVT_CAR_LEAVE, IOT_EVT_LOOP_CUT, IOT_EVT_LOOP_RESTORE };
|
|
typedef struct { uint8_t type, ch; uint32_t value; } IotEvent;
|
|
static IotEvent _evt_queue[IOT_EVT_QUEUE_DEPTH];
|
|
static uint8_t _evt_head, _evt_count;
|
|
static uint8_t _evt_prev_car[LUP_COIL_COUNT], _evt_prev_loop[LUP_COIL_COUNT];
|
|
static uint32_t _evt_cut_ms[LUP_COIL_COUNT];
|
|
static uint8_t _evt_prev_valid;
|
|
static uint32_t _evt_msg_id, _evt_pend_id, _evt_pend_ts;
|
|
static uint8_t _evt_pend_n;
|
|
static uint32_t _evt_sent_ms;
|
|
static uint8_t _evt_retry, _evt_gaveup;
|
|
|
|
static void iot_evt_enqueue(uint8_t type, uint8_t ch, uint32_t value) {
|
|
if (_evt_count >= IOT_EVT_QUEUE_DEPTH) {
|
|
_evt_head = (_evt_head + 1) % IOT_EVT_QUEUE_DEPTH;
|
|
_evt_count--;
|
|
if (_evt_pend_id) { _evt_pend_id = 0; if (_evt_pend_n) _evt_pend_n = 0; }
|
|
}
|
|
{ IotEvent *e = &_evt_queue[(_evt_head + _evt_count) % IOT_EVT_QUEUE_DEPTH];
|
|
e->type = type; e->ch = ch; e->value = value; _evt_count++; }
|
|
_evt_gaveup = 0;
|
|
}
|
|
static void iot_evt_feed(const LUP_SensorReport *sr) {
|
|
uint8_t i;
|
|
if (!_evt_prev_valid) {
|
|
for (i = 0; i < LUP_COIL_COUNT; i++) {
|
|
_evt_prev_car[i] = (i < sr->coil_count) ? sr->coils[i].car_state : 0;
|
|
_evt_prev_loop[i] = (i < sr->coil_count) ? sr->coils[i].loop_state : 0;
|
|
}
|
|
_evt_prev_valid = 1; return;
|
|
}
|
|
for (i = 0; i < sr->coil_count && i < LUP_COIL_COUNT; i++) {
|
|
const LUP_CoilSensor *cs = &sr->coils[i];
|
|
if (!_evt_prev_loop[i] && !cs->loop_state) {
|
|
if (!_evt_prev_car[i] && cs->car_state) iot_evt_enqueue(IOT_EVT_CAR_ENTER, i+1, 0);
|
|
else if (_evt_prev_car[i] && !cs->car_state) {
|
|
uint32_t v = (cs->misc_type == 0) ? cs->misc.passtime_ms : 0;
|
|
iot_evt_enqueue(IOT_EVT_CAR_LEAVE, i+1, v);
|
|
}
|
|
}
|
|
if (!_evt_prev_loop[i] && cs->loop_state) {
|
|
_evt_cut_ms[i] = mstick(); iot_evt_enqueue(IOT_EVT_LOOP_CUT, i+1, 0);
|
|
} else if (_evt_prev_loop[i] && !cs->loop_state) {
|
|
iot_evt_enqueue(IOT_EVT_LOOP_RESTORE, i+1, (mstick() - _evt_cut_ms[i]) / 50);
|
|
}
|
|
_evt_prev_car[i] = cs->car_state; _evt_prev_loop[i] = cs->loop_state;
|
|
}
|
|
}
|
|
static void iot_evt_send(uint32_t id, uint32_t ts, uint8_t n) {
|
|
static const char *evt_names[] = {"car_enter","car_leave","loop_cut","loop_restore"};
|
|
static char payload[512];
|
|
int w, rem = sizeof(payload); char *p = payload; uint8_t i;
|
|
w = snprintf(p, rem, "{\"msg_id\":%u,\"cmd\":\"event_report\",\"ts\":%u,"
|
|
"\"data\":{\"events\":[", id, ts);
|
|
if (w < 0 || w >= rem) return;
|
|
p += w; rem -= w;
|
|
for (i = 0; i < n; i++) {
|
|
const IotEvent *e = &_evt_queue[(_evt_head + i) % IOT_EVT_QUEUE_DEPTH];
|
|
w = snprintf(p, rem, "%s{\"type\":\"%s\",\"ch\":%d,\"value\":%u}",
|
|
(i>0)?",":"", evt_names[e->type & 0x03], e->ch, e->value);
|
|
if (w < 0 || w >= rem) return;
|
|
p += w; rem -= w;
|
|
}
|
|
snprintf(p, rem, "]}}");
|
|
mqtt_publish((char *)g_iot_topic.topic_pub, payload, 1);
|
|
}
|
|
void iot_evt_handle_ack(uint32_t msg_id, int code) {
|
|
if (_evt_pend_id == 0 || msg_id != _evt_pend_id) return;
|
|
if (code != 0) return;
|
|
_evt_head = (_evt_head + _evt_pend_n) % IOT_EVT_QUEUE_DEPTH;
|
|
_evt_count -= _evt_pend_n;
|
|
_evt_pend_id = 0; _evt_pend_n = 0; _evt_retry = 0;
|
|
}
|
|
static void iot_evt_process(void) {
|
|
static uint8_t _was_ready = 0;
|
|
uint8_t ready = (g_iot_state == IOT_STATE_READY);
|
|
uint32_t now = mstick();
|
|
if (ready && !_was_ready) { _evt_pend_id = 0; _evt_gaveup = 0; }
|
|
_was_ready = ready;
|
|
if (!ready) return;
|
|
if (_evt_pend_id) {
|
|
if (now - _evt_sent_ms < IOT_EVT_ACK_TIMEOUT_MS) return;
|
|
if (_evt_retry >= IOT_EVT_MAX_RETRY) { _evt_pend_id = 0; _evt_gaveup = 1; return; }
|
|
_evt_retry++;
|
|
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n);
|
|
_evt_sent_ms = now;
|
|
return;
|
|
}
|
|
if (_evt_count == 0 || _evt_gaveup) return;
|
|
_evt_pend_n = (_evt_count > IOT_EVT_MAX_PER_PKT) ? IOT_EVT_MAX_PER_PKT : _evt_count;
|
|
_evt_pend_id = ++_evt_msg_id;
|
|
_evt_pend_ts = now / 1000;
|
|
_evt_retry = 0;
|
|
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n);
|
|
_evt_sent_ms = now;
|
|
}
|
|
|
|
/* ---------- 测试 ---------- */
|
|
static int fails = 0;
|
|
#define CHECK(cond, msg) do{ if(!(cond)){ printf("FAIL: %s\n", msg); fails++; } }while(0)
|
|
|
|
static LUP_SensorReport mkframe(uint8_t c1,uint8_t c2,uint8_t c3,uint8_t c4,
|
|
uint8_t l1,uint8_t l2,uint8_t l3,uint8_t l4, uint32_t pass) {
|
|
LUP_SensorReport sr; memset(&sr,0,sizeof(sr)); sr.coil_count=4;
|
|
uint8_t cars[4]={c1,c2,c3,c4}, loops[4]={l1,l2,l3,l4};
|
|
for(int i=0;i<4;i++){ sr.coils[i].car_state=cars[i]; sr.coils[i].loop_state=loops[i];
|
|
sr.coils[i].misc_type=0; sr.coils[i].misc.passtime_ms=pass; }
|
|
return sr;
|
|
}
|
|
|
|
int main(void) {
|
|
LUP_SensorReport sr;
|
|
|
|
/* T1: 首帧已有车 → 不产生事件 (只建快照) */
|
|
sr = mkframe(1,0,0,0, 0,0,0,0, 0);
|
|
iot_evt_feed(&sr);
|
|
CHECK(_evt_count == 0, "T1 首帧不出事件");
|
|
|
|
/* T2: ch2 进车沿 */
|
|
sr = mkframe(1,1,0,0, 0,0,0,0, 0);
|
|
iot_evt_feed(&sr);
|
|
CHECK(_evt_count == 1 && _evt_queue[0].type == IOT_EVT_CAR_ENTER
|
|
&& _evt_queue[0].ch == 2, "T2 car_enter ch2");
|
|
|
|
/* T3: 发送 → 5s 无应答重发×3 → 挂起 */
|
|
_mock_ms = 1000;
|
|
iot_evt_process(); /* 首发 msg_id=1 */
|
|
CHECK(pub_count == 1 && _evt_pend_id == 1, "T3 首发");
|
|
CHECK(strstr(last_payload, "\"msg_id\":1") && strstr(last_payload, "car_enter"),
|
|
"T3 payload 含 msg_id=1 + car_enter");
|
|
uint32_t first_ts = _evt_pend_ts;
|
|
for (int r = 1; r <= 3; r++) {
|
|
_mock_ms += 5100; iot_evt_process(); /* 重发 r */
|
|
CHECK(pub_count == 1 + r, "T3 重发次数");
|
|
CHECK(_evt_pend_ts == first_ts, "T3 重发 ts 不刷新");
|
|
CHECK(strstr(last_payload, "\"msg_id\":1") != NULL, "T3 重发同 msg_id");
|
|
}
|
|
_mock_ms += 5100; iot_evt_process(); /* 第4次超时 → 挂起 */
|
|
CHECK(pub_count == 4 && _evt_pend_id == 0 && _evt_gaveup == 1 && _evt_count == 1,
|
|
"T3 重试耗尽挂起, 事件保留");
|
|
_mock_ms += 5100; iot_evt_process();
|
|
CHECK(pub_count == 4, "T3 挂起期间不再发送");
|
|
|
|
/* T4: ch2 出车沿 (新事件) → 解除挂起, 合并重报 (含旧 car_enter) */
|
|
sr = mkframe(1,0,0,0, 0,0,0,0, 350);
|
|
iot_evt_feed(&sr);
|
|
CHECK(_evt_count == 2, "T4 car_leave 入队");
|
|
CHECK(_evt_queue[1].value == 350, "T4 car_leave 通过时间=350");
|
|
_mock_ms += 100; iot_evt_process(); /* 新包 msg_id=2, 含2条 */
|
|
CHECK(pub_count == 5 && _evt_pend_id == 2 && _evt_pend_n == 2, "T4 合并重报");
|
|
CHECK(strstr(last_payload, "car_enter") && strstr(last_payload, "car_leave"),
|
|
"T4 新包含新旧事件");
|
|
|
|
/* T5: ACK code!=0 → 不出队; ACK code=0 → 出队 */
|
|
iot_evt_handle_ack(2, 5);
|
|
CHECK(_evt_pend_id == 2 && _evt_count == 2, "T5 code!=0 不出队");
|
|
iot_evt_handle_ack(999, 0);
|
|
CHECK(_evt_pend_id == 2, "T5 错 msg_id 忽略");
|
|
iot_evt_handle_ack(2, 0);
|
|
CHECK(_evt_pend_id == 0 && _evt_count == 0, "T5 ACK 出队");
|
|
|
|
/* T6: 线圈断开→恢复, value=断开时长(50ms 单位); 断开期间屏蔽 car 沿 */
|
|
_mock_ms = 90000;
|
|
sr = mkframe(0,0,0,0, 0,0,0,0, 0); /* 归一化: 清 T4 残留的 prev_car(ch1=1) */
|
|
iot_evt_feed(&sr); /* 产生 ch1 car_leave, 随即排干 */
|
|
while (_evt_count) { iot_evt_process(); iot_evt_handle_ack(_evt_pend_id, 0); _mock_ms += 100; }
|
|
_mock_ms = 100000;
|
|
sr = mkframe(0,0,0,0, 0,0,1,0, 0); /* ch3 断开 */
|
|
iot_evt_feed(&sr);
|
|
CHECK(_evt_count == 1 && _evt_queue[_evt_head].type == IOT_EVT_LOOP_CUT, "T6 loop_cut");
|
|
_mock_ms = 103000; /* 3s 后 */
|
|
sr = mkframe(0,0,1,0, 0,0,0,0, 0); /* ch3 恢复且 car=1: 断开→正常帧不判 car 沿 */
|
|
iot_evt_feed(&sr);
|
|
CHECK(_evt_count == 2, "T6 恢复帧只出 loop_restore, car 沿被屏蔽");
|
|
{ IotEvent *e = &_evt_queue[(_evt_head+1)%IOT_EVT_QUEUE_DEPTH];
|
|
CHECK(e->type == IOT_EVT_LOOP_RESTORE && e->value == 60, "T6 断开时长 3000ms/50=60"); }
|
|
iot_evt_process(); iot_evt_handle_ack(_evt_pend_id, 0); /* 清场 */
|
|
|
|
/* T7: 单包上限 6 条 + 包长 < 500B */
|
|
for (int k = 0; k < 10; k++) {
|
|
sr = mkframe(1,0,0,0, 0,0,0,0, 0); iot_evt_feed(&sr); /* enter */
|
|
sr = mkframe(0,0,0,0, 0,0,0,0, 4294967295u); iot_evt_feed(&sr); /* leave, value 最大 */
|
|
}
|
|
CHECK(_evt_count == 16, "T7 队列封顶16 (20条丢4)");
|
|
iot_evt_process();
|
|
CHECK(_evt_pend_n == 6, "T7 单包6条");
|
|
CHECK(strlen(last_payload) < 500, "T7 包长<500B");
|
|
printf(" T7 包长实测: %zu B (6条, value含4294967295)\n", strlen(last_payload));
|
|
iot_evt_handle_ack(_evt_pend_id, 0);
|
|
CHECK(_evt_count == 10, "T7 ACK 后出队6条");
|
|
|
|
/* T8: 断线重连沿 → 未决包作废 + 挂起解除, 剩余事件补报 */
|
|
iot_evt_process(); /* 发下一包 (4条? 6条上限内=6... 剩10条→6) */
|
|
uint32_t pend_before = _evt_pend_id;
|
|
CHECK(pend_before != 0, "T8 有未决包");
|
|
g_iot_state = IOT_STATE_DISCONNECTED;
|
|
iot_evt_process(); /* 断线 */
|
|
g_iot_state = IOT_STATE_READY;
|
|
_mock_ms += 100;
|
|
iot_evt_process(); /* 重连沿: 作废旧包, 立即新包 */
|
|
CHECK(_evt_pend_id == pend_before + 1, "T8 重连后新 msg_id 补报");
|
|
/* 全部确认清空 */
|
|
while (_evt_count) { iot_evt_handle_ack(_evt_pend_id, 0); _mock_ms += 100; iot_evt_process(); }
|
|
CHECK(_evt_count == 0, "T8 清空");
|
|
|
|
printf(fails ? "\n== %d FAIL ==\n" : "\n== ALL PASS ==\n", fails);
|
|
return fails;
|
|
}
|