/* 隔离单测: iot_mqtt_srv.c event_report 模块 (协议 V1.04) * 覆盖: 沿检测 / 队列 / ACK出队 / 5s重发×3 / 挂起与解除 / 重连补报 / 包长上限 / 溢出 * 被测代码与固件同构 (mock: mstick, mqtt_publish, PRINT, LUP 类型) */ #include #include #include #include /* ---------- 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_gaveup = 0; if (_evt_pend_id) { /* 重连: 同 msg_id/ts 立即重发 */ _evt_retry = 0; iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n); _evt_sent_ms = now; _was_ready = ready; return; } } _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: 断线重连 → 未决包用【同 msg_id/ts】重发 (V1.04 §5.3-2, 平台去重不失效) */ iot_evt_process(); /* 发下一包 */ uint32_t pend_before = _evt_pend_id; uint32_t ts_before = _evt_pend_ts; CHECK(pend_before != 0, "T8 有未决包"); g_iot_state = IOT_STATE_DISCONNECTED; iot_evt_process(); /* 断线 (未 ACK) */ g_iot_state = IOT_STATE_READY; _mock_ms += 100; int pub_before = pub_count; iot_evt_process(); /* 重连沿: 同 msg_id 立即重发 */ CHECK(_evt_pend_id == pend_before, "T8 重连后保持同 msg_id (不换号)"); CHECK(_evt_pend_ts == ts_before, "T8 重连后保持原 ts"); CHECK(pub_count == pub_before + 1, "T8 重连触发一次重发"); { char idbuf[32]; snprintf(idbuf, sizeof(idbuf), "\"msg_id\":%u", pend_before); CHECK(strstr(last_payload, idbuf) != NULL, "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; }