From 3e28174b409026e3acfd20783705063e9fdd0b55 Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 15 Jul 2026 15:25:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(mqtt):=20=E8=AE=BE=E5=A4=87=E6=97=B6?= =?UTF-8?q?=E9=92=9F=E5=90=8C=E6=AD=A5=E6=96=B9=E6=A1=88B=20=E2=80=94=20?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E7=BB=8F=20report=5Fconfig=20=E4=B8=8B?= =?UTF-8?q?=E5=8F=91=20Unix=20ts=20(=E5=8D=8F=E8=AE=AEV1.05)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 设备无RTC/SNTP, 原上行 ts=mstick()/1000(上电秒数)。方案B: - net_srv.c 新增 dev_time_sync()/dev_time_now(): 门槛≥1600000000挡上电秒数, 已校准返回真Unix, 未校准退回上电秒数; 无符号相减处理mstick 49.7天回绕 - manage_mqtt_recv_message 从下行命令信封 ts 校准 (report_config主同步点) - 全部上行 ts (14处命令响应+initialize+loop_data+event_report) 改 dev_time_now() heartbeat uptime 字段保留 mstick()/1000 (运行时长非时间戳) - 协议 V1.05 §2.3 时间同步 + report_config 兼任同步点职责 - tests/test_dev_time_sync.c 6组全过 平台侧配合: 收到 initialize 后下发 report_config 时 ts 填当前 Unix 时间 --- docs/DLD960_IoT_MQTT协议.md | 16 +++++ .../APP/include/net_srv.h | 2 + .../APP/iot_mqtt_srv.c | 14 ++-- .../OnlyUpdateApp_Peripheral/APP/net_srv.c | 66 ++++++++++++++----- vd960DBN/docs/devlog.md | 26 ++++++++ vd960DBN/tests/test_dev_time_sync.c | 65 ++++++++++++++++++ 6 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 vd960DBN/tests/test_dev_time_sync.c diff --git a/docs/DLD960_IoT_MQTT协议.md b/docs/DLD960_IoT_MQTT协议.md index 26ea87b..c56e19f 100644 --- a/docs/DLD960_IoT_MQTT协议.md +++ b/docs/DLD960_IoT_MQTT协议.md @@ -96,6 +96,20 @@ dld960/{dev_serial}/{direction} | 5 | 内部错误 | | 6 | 数据超长 | +## 2.3 设备时钟同步(`ts` 语义) + +设备无 RTC/SNTP 时间源,上电后本地时钟为**上电秒数**(从 0 递增)。为使上行数据带真实 Unix 时间: + +1. 设备上电连接成功后发布 `initialize`(此时 `ts` = 上电秒数,值很小,平台据此识别"未校准"); +2. **平台收到 `initialize` 后,下发 `report_config` 命令,其信封 `ts` 字段填当前 Unix 时间**; +3. 设备收到后以该 `ts` 校准本地时钟基准,之后所有上行消息(`loop_data` / `event_report` / 心跳 / 命令响应)的 `ts` 均为**真实 Unix 时间**。 + +补充约定: + +- `report_config` 为约定的**主同步点**;设备亦接受任意下行命令中**合法**(≥ 1600000000,即 2020-09 之后)的 `ts` 进行校准,非法/过小值被忽略。 +- 校准前(含首个 `initialize`),`ts` 为上电秒数;平台应能容忍并可按数量级区分。 +- 设备重启后需重新校准(无掉电保持)。平台在每次设备 `initialize` 后都应下发一次带 `ts` 的 `report_config`。 + --- # 3 命令详表 @@ -590,6 +604,7 @@ dld960/{dev_serial}/{direction} ## 4.15 设置主动上报 `report_config` > Topic: `dld960/{sn}/srv` +> **⚠️ 兼任设备时钟同步点**:本命令信封 `ts` 须填当前 Unix 时间,设备据此校准本地时钟(见 §2.3)。平台应在每次收到设备 `initialize` 后下发一次。 **请求:** @@ -876,4 +891,5 @@ dld960/{dev_serial}/{direction} | V1.02 | 2026-07-09 | 缩写相关字段 | wangfq | | V1.03 | 2026-07-09 | 增加设备上电初始化指令 | wangfq | | V1.04 | 2026-07-15 | `event_report` 增加**平台必答**机制:应答格式(回显 `msg_id`)、设备 5s 超时重发(同 `msg_id`/`ts`,最多 3 次)、待发队列合并上报、平台去重与先落库后应答要求 | wangfq | +| V1.05 | 2026-07-15 | 增加**设备时钟同步**(§2.3,方案B):设备无 RTC,`initialize` 上线后平台经 `report_config` 命令下发 Unix `ts`,设备据此校准,之后上行 `ts` 为真实 Unix 时间;校准前为上电秒数 | wangfq | diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_srv.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_srv.h index f24606c..384492b 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_srv.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_srv.h @@ -280,6 +280,8 @@ void manage_mqtt_recv_message(char * msg, int length); void poll_mqtt(void); void mqtt_publish(char *topic, char *message, int req_qos); void dev_initialize_pub(void); // 发布上线 initialize 消息 (V1.03) +void dev_time_sync(uint32_t unix_ts); // 由下行命令 ts 校准本地时钟 (方案B) +uint32_t dev_time_now(void); // 当前 Unix 秒(已校准)或上电秒数(未校准) void GetMacAddr(unsigned char *pMAC); diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c index 1887189..9a138e3 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c @@ -245,7 +245,7 @@ static void iot_evt_process(void) { /* 发新包: 队列头 N 条合并 */ _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_pend_ts = dev_time_now(); // 首发时刻 Unix 时间(已校准), 重发保持不刷新 _evt_retry = 0; iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n); _evt_sent_ms = now; @@ -412,7 +412,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ "\"model\":\"%s\",\"product_code\":\"960001\"," "\"sub_code\":{\"net\":%s,\"iot\":%s}," "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, PRODUCT_MODEL, g_sub_code_enable.net_enable ? "true" : "false", g_sub_code_enable.iot_enable ? "true" : "false"); @@ -443,7 +443,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); PRINT("IOT: Auth success via MQTT\n"); } else { @@ -451,7 +451,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," "\"ts\":%lu,\"code\":2,\"msg\":\"password incorrect\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); } @@ -461,7 +461,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"%s\"," "\"ts\":%lu,\"code\":4,\"msg\":\"unsupported command\"}", - msg_id, cmd_str, mstick() / 1000); + msg_id, cmd_str, dev_time_now()); iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); } } @@ -708,7 +708,7 @@ void iot_mqtt_publish_sensor(void) { /* Wrap and publish — V1.01 双主题模型: dld960/{sn}/dev */ snprintf(payload, sizeof(payload), "{\"msg_id\":%d,\"cmd\":\"loop_data\",\"ts\":%lu,\"data\":%s}", - ++g_iot_msg_id, mstick() / 1000, data_json); + ++g_iot_msg_id, dev_time_now(), data_json); { char topic[IOT_MQTT_TOPIC_MAX_LEN]; @@ -736,7 +736,7 @@ static void iot_send_heartbeat(void) { "\"ts\":%lu,\"data\":{" "\"uptime\":%lu,\"loop_status\":[%s,%s,%s,%s]," "\"net_status\":true,\"iot_status\":true}}", - ++g_iot_msg_id, mstick() / 1000, + ++g_iot_msg_id, dev_time_now(), mstick() / 1000, loop_ok[0] ? "true" : "false", loop_ok[1] ? "true" : "false", loop_ok[2] ? "true" : "false", loop_ok[3] ? "true" : "false"); diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c index b31fb7d..730d87a 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c @@ -82,6 +82,32 @@ void clear_mqtt_buf(void) memset(mqttBuf, 0, MAX_MQTTBUF_LEN); } +/*=========================================================================== + * 设备时钟同步 (方案B, 2026-07-15) + * 设备无 RTC/SNTP。上电后本地时间 = 上电秒数。平台在收到 initialize 上线后, + * 下发 report_config 命令时于 ts 字段填当前 Unix 时间; 设备据此校准。 + * 校准后所有上行 ts = 真 Unix 时间; 未校准前退回上电秒数(平台可按量级识别)。 + * 接受任意下行命令的合法 ts 校准 (report_config 为约定主同步点)。 + *===========================================================================*/ +static uint32_t _dev_time_base_unix = 0; // 同步基准 Unix 秒, 0=未同步 +static uint32_t _dev_time_base_tick = 0; // 同步时刻的 mstick() + +void dev_time_sync(uint32_t unix_ts) +{ + if (unix_ts < 1600000000UL) return; // 合法性门槛(>2020-09), 挡掉上电秒数/0/异常 + _dev_time_base_unix = unix_ts; + _dev_time_base_tick = mstick(); + PRINT("TIME: synced unix=%lu\n", (unsigned long)unix_ts); +} + +uint32_t dev_time_now(void) +{ + if (_dev_time_base_unix == 0) // 未同步 → 退回上电秒数(兼容) + return (uint32_t)(mstick() / 1000); + /* mstick 无符号相减天然处理 49.7 天回绕 */ + return _dev_time_base_unix + (uint32_t)((mstick() - _dev_time_base_tick) / 1000); +} + /********************************************************************* * @fn mStopIfError * @@ -196,7 +222,8 @@ void dev_initialize_pub(void) // V1.03: 上线后发布 initialize,告知服务器设备信息 // 格式: {msg_id, cmd:"initialize", ts, data:{dev_serial, model, hard_ver, soft_ver, extra_info:{code,csq,location}}} - uint32_t now = (mstick() > 0) ? (uint32_t)(mstick() / 1000) : 0; + // 首次上电尚未时钟同步 → ts=上电秒数; 平台收到本消息后经 report_config 回填真 Unix 时间 + uint32_t now = dev_time_now(); char topic[64]; snprintf(topic, sizeof(topic), "dld960/%s/dev", g_dev_number_str); sprintf((char *)mBuff, @@ -1116,6 +1143,15 @@ void manage_mqtt_recv_message(char * msg, int length) uint32_t msg_id = 0; simple_parse_json(msg, "\"msg_id\"", tmp); if(strlen(tmp) > 0) msg_id = (uint32_t)strtoul(tmp, NULL, 10); + + // 方案B: 由下行命令 ts 校准本地时钟。平台在 initialize 上线后经 report_config + // 下发真 Unix 时间; dev_time_sync 内部有合法性门槛, 非法/上电秒数会被忽略。 + // report_config 为约定主同步点, 此处对任意携带合法 ts 的下行命令均校准。 + { + char ts_str[16] = {0}; + simple_parse_json(msg, "\"ts\"", ts_str); + if(strlen(ts_str) > 0) dev_time_sync((uint32_t)strtoul(ts_str, NULL, 10)); + } // 去掉 temp_guide 里的引号 char *cmd_str = temp_guide; @@ -1137,7 +1173,7 @@ void manage_mqtt_recv_message(char * msg, int length) "\"model\":\"%s\",\"product_code\":\"960001\"," "\"sub_code\":{\"net\":%s,\"iot\":%s}," "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, PRODUCT_MODEL, g_sub_code_enable.net_enable ? "true" : "false", g_sub_code_enable.iot_enable ? "true" : "false"); @@ -1170,7 +1206,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: pwd_verify OK\n"); } else { @@ -1178,7 +1214,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," "\"ts\":%lu,\"code\":2,\"msg\":\"password incorrect\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); } return; @@ -1191,7 +1227,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"dev_serial_set\"," "\"ts\":%lu,\"code\":4,\"msg\":\"not implemented yet\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); return; } @@ -1209,7 +1245,7 @@ void manage_mqtt_recv_message(char * msg, int length) "\"lssc_ip\":\"%d.%d.%d.%d\"," "\"dns\":\"%d.%d.%d.%d\"," "\"port\":%d}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), local_net_cfg.lip[0], local_net_cfg.lip[1], local_net_cfg.lip[2], local_net_cfg.lip[3], local_net_cfg.sub[0], local_net_cfg.sub[1], local_net_cfg.sub[2], local_net_cfg.sub[3], local_net_cfg.gw[0], local_net_cfg.gw[1], local_net_cfg.gw[2], local_net_cfg.gw[3], @@ -1285,7 +1321,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"ssc_net_set\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: ssc_net_set done\n"); return; @@ -1300,7 +1336,7 @@ void manage_mqtt_recv_message(char * msg, int length) "\"data\":{" "\"host\":\"%s\",\"port\":%d," "\"client_id\":\"%s\",\"username\":\"%s\",\"password\":\"%s\"}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), iot_net_info.remote_addr, iot_net_info.mqtt_port, iot_net_info.client_id, iot_net_info.username, iot_net_info.password); mqtt_publish(resp_topic, resp, 1); @@ -1363,7 +1399,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"iot_net_set\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: iot_net_set done\n"); return; @@ -1378,7 +1414,7 @@ void manage_mqtt_recv_message(char * msg, int length) "\"data\":{" "\"client_id_enable\":%s," "\"topic_pub\":\"%s\",\"topic_sub\":\"%s\"}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), g_iot_topic.clientid_enable ? "true" : "false", g_iot_topic.topic_pub, g_iot_topic.topic_sub); mqtt_publish(resp_topic, resp, 1); @@ -1425,7 +1461,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"iot_topic_set\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: iot_topic_set done\n"); return; @@ -1437,7 +1473,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"pwd_set\"," "\"ts\":%lu,\"code\":4,\"msg\":\"not implemented yet\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); return; } @@ -1448,7 +1484,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"factory_reset\"," "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); + msg_id, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: factory_reset — resetting...\n"); Delay_Ms(200); @@ -1516,7 +1552,7 @@ void manage_mqtt_recv_message(char * msg, int length) "\"data\":{" "\"sensor_type\":%d,\"enable\":%s,\"once\":%s,\"env_eval\":%s," "\"interval\":%u,\"ack_required\":%s,\"timeout\":%u}}", - msg_id, mstick() / 1000, + msg_id, dev_time_now(), g_report_cfg.sensor_type, g_report_cfg.enable ? "true" : "false", g_report_cfg.once ? "true" : "false", @@ -1546,7 +1582,7 @@ void manage_mqtt_recv_message(char * msg, int length) snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"%s\"," "\"ts\":%lu,\"code\":4,\"msg\":\"unsupported command\"}", - msg_id, cmd_str, mstick() / 1000); + msg_id, cmd_str, dev_time_now()); mqtt_publish(resp_topic, resp, 1); PRINT("IOT: unsupported cmd=%s\n", cmd_str); } diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 410efce..8cfe617 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -6,6 +6,32 @@ --- +## 2026-07-15 — 设备时钟同步 (方案B): 平台经 report_config 下发 Unix ts + +### 背景 + +原上行 `ts` = `mstick()/1000` (上电秒数), 非 Unix 时间戳 (现场事故报告实锤)。设备无 RTC/SNTP。王工定方案B: **initialize 上线 → 平台经 report_config 下发真 Unix ts → 设备校准**。 + +### 实现 + +- `net_srv.c` 新增时钟模块: `dev_time_sync(unix_ts)` (合法性门槛 ≥1600000000 挡掉上电秒数/0) + `dev_time_now()` (已校准→真 Unix; 未校准→退回上电秒数)。基准用 `base_unix + (mstick()-base_tick)/1000`, 无符号相减天然处理 49.7 天回绕。 +- `manage_mqtt_recv_message` 解析 msg_id 后, 从任意下行命令信封 `ts` 校准 (report_config 为主同步点)。 +- 全部上行 `ts` 由 `mstick()/1000` 改 `dev_time_now()`: net_srv.c 14 处命令响应 + initialize; iot_mqtt_srv.c loop_data + event_report(首发时刻, 重发不刷新) + 遗留响应。**heartbeat 的 `uptime` 字段保留 `mstick()/1000`** (那本就是运行时长, 非时间戳)。 + +### 关键点 + +- 校准前 (含首个 initialize) ts=上电秒数, 平台按量级识别未校准。 +- 设备重启无掉电保持 → 每次 initialize 后平台都须重下发 report_config 带 ts。 +- 门槛 1600000000 (2020-09) 挡掉上电秒数(几百)/0/异常, 防污染基准。 + +### 验证 + +`tests/test_dev_time_sync.c` 6 组全过: 未同步退回上电秒数 / 非法ts被拒 / 校准瞬间对齐 / 校准后随时钟递增 / 门槛边界 / mstick 49.7天回绕无符号相减正确。 + +⚠️ **平台侧须配合**: 收到 initialize 后, 下发 report_config 时信封 `ts` 填当前 Unix 时间 (协议 V1.05 §2.3)。 + +--- + ## 2026-07-15 — 🔴 现场事故: MQTT protocol error 重连风暴 (根因/修复) ### 现象 diff --git a/vd960DBN/tests/test_dev_time_sync.c b/vd960DBN/tests/test_dev_time_sync.c new file mode 100644 index 0000000..450e0d5 --- /dev/null +++ b/vd960DBN/tests/test_dev_time_sync.c @@ -0,0 +1,65 @@ +/* 隔离单测: 设备时钟同步模块 (方案B) — 与 net_srv.c dev_time_sync/dev_time_now 同构 */ +#include +#include + +static uint32_t _mock_ms = 0; +static uint32_t mstick(void) { return _mock_ms; } +#define PRINT(...) do{}while(0) + +/* ── 被测: 与固件同构 ── */ +static uint32_t _dev_time_base_unix = 0; +static uint32_t _dev_time_base_tick = 0; +void dev_time_sync(uint32_t unix_ts) { + if (unix_ts < 1600000000UL) return; + _dev_time_base_unix = unix_ts; + _dev_time_base_tick = mstick(); +} +uint32_t dev_time_now(void) { + if (_dev_time_base_unix == 0) return (uint32_t)(mstick() / 1000); + return _dev_time_base_unix + (uint32_t)((mstick() - _dev_time_base_tick) / 1000); +} + +static int fails = 0; +#define CHECK(c,m) do{ if(!(c)){ printf("FAIL: %s\n", m); fails++; } }while(0) + +int main(void) { + /* T1: 未同步 → 返回上电秒数 */ + _mock_ms = 372000; /* 上电 372s */ + CHECK(dev_time_now() == 372, "T1 未同步=上电秒数372"); + + /* T2: 非法 ts(上电秒数/0/小值) 被门槛挡掉, 不污染基准 */ + dev_time_sync(372); + dev_time_sync(0); + dev_time_sync(1599999999UL); /* 门槛下沿-1 */ + CHECK(_dev_time_base_unix == 0, "T2 非法ts被拒, 仍未同步"); + CHECK(dev_time_now() == 372, "T2 仍退回上电秒数"); + + /* T3: 平台经 report_config 下发真 Unix 时间 → 校准 */ + uint32_t T = 1784000000UL; /* 某真实 Unix 秒 (>2020) */ + _mock_ms = 373000; /* 校准时刻 373s */ + dev_time_sync(T); + CHECK(dev_time_now() == T, "T3 校准瞬间 = 下发 Unix 时间"); + + /* T4: 校准后随 mstick 前进, 真时间同步递增 */ + _mock_ms = 373000 + 60000; /* 60s 后 */ + CHECK(dev_time_now() == T + 60, "T4 60s后 = T+60"); + _mock_ms = 373000 + 3600000; /* 1h 后 */ + CHECK(dev_time_now() == T + 3600, "T4 1h后 = T+3600"); + + /* T5: 门槛边界 (>=1600000000 才认) */ + _dev_time_base_unix = 0; _dev_time_base_tick = 0; _mock_ms = 1000; + dev_time_sync(1600000000UL); + CHECK(_dev_time_base_unix == 1600000000UL, "T5 门槛下沿被接受"); + + /* T6: mstick 49.7天回绕 — 无符号相减仍正确 */ + _dev_time_base_unix = T; + _dev_time_base_tick = 0xFFFFFF00UL; /* 接近 uint32 上限 */ + _mock_ms = 0x00000100UL; /* 回绕后 = 基准 + 512ms */ + /* 经过 (0x100 - 0xFFFFFF00) mod 2^32 = 0x200 = 512ms → +0s (整除) */ + CHECK(dev_time_now() == T + 0, "T6 回绕512ms → +0s (无符号相减正确)"); + _mock_ms = 0x00000100UL + 5000; /* +5.5s */ + CHECK(dev_time_now() == T + 5, "T6 回绕后 +5s 正确"); + + printf(fails ? "\n== %d FAIL ==\n" : "\n== ALL PASS ==\n", fails); + return fails; +}