有日期与有依据,是两件事
设备可能打印1970,也可能在复位后保留一个看似合理的日期。若日志只存年月日,之后无法判断采样时是否已经校时。这次每秒同时输出单调uptime、epoch、UTC、本地时间、online、state和sync_age_ms,未同步时格式化时间写NA。
本次交付已有真实构建和同源主机输入检查;本站尚无该固件联网、SNTP服务器通信、断网或重启实板结果。下面检查顺序是执行要求,不能当成已完成的实验记录。
- 做到这里,你应该看到
- 能够说明有IP、有墙上时间、本次启动收到同步通知是三个不同事实。
参考:[1] ESP-IDF v5.5.5 System Time:RTC/高分辨率时钟、SNTP模式、POSIX时区[2] ESP-IDF v5.5.5 esp_netif SNTP配置与默认宏
锁住目标,再填写自己的NTP源
使用指定S3板的USB-to-UART接口与115200串口,不接其他器件。本工程固定IDF5.5.5;下载包含根CMake、main、网络模块、Kconfig、公开defaults和真实lock,完整安装/烧录命令见README。
Demo configuration填写自己的2.4GHz WPA2网络、密码和NTP主机名或IPv4。NTP使用UDP123,不能填http://网址;默认REPLACE_WITH_NTP_SERVER不启动SNTP。凭据只在本地sdkconfig,源码和日志不包含真实密码,但编译后的固件仍含配置。
公共network-link.defaults只供编译检查实际网络分支,SSID/口令/192.0.2.123均为公开无效测试值。作者已核对ELF中的esp_wifi_connect和esp_netif_sntp_init符号;这不证明服务器可达。
idf.py --version
# 应为ESP-IDF v5.5.5;进入本项目根目录
idf.py set-target esp32s3
idf.py menuconfig
idf.py build
idf.py -p REPLACE_WITH_UART_PORT flash monitor- 做到这里,你应该看到
- 版本、板型、桥接串口、NTP服务器与网络权限都能逐项说明。
参考:[2] ESP-IDF v5.5.5 esp_netif SNTP配置与默认宏[5] ESP-IDF v5.5.5官方Wi-Fi STA事件示例
用本次启动与单调年龄管理可信状态
clock_policy.h只保存本次启动已同步标志、最后同步的单调毫秒和观察到的通知数。首次启动无论epoch大小都UNSYNCED;已同步且有IP为SYNCED,掉线为HOLDOVER,年龄超过应用阈值为STALE。即使网络仍ONLINE,太久未同步也会过期。
默认同步周期600秒、过期阈值7200秒。阈值可配60–86400秒,是本应用策略,不是两小时精度保证。SYNCED也不认证NTP服务器或网络时延。普通SNTP未提供本例的认证时间来源。
RTC可能跨某些复位保持时间,断电又不同;本例每次启动清本次同步标志,不从年份或保留日期恢复信任,也不把last_sync持久化。
| 状态 | 本次启动依据 | 时间展示 |
|---|---|---|
| UNSYNCED | 尚无本次SNTP通知;有IP也一样 | utc/local=NA,原始epoch保留 |
| SYNCED | 本次已同步、当前有IP、年龄≤阈值 | UTC及本地偏移 |
| HOLDOVER | 本次已同步、当前无IP、年龄≤阈值 | 继续显示并标保持 |
| STALE | 年龄>阈值或模型时基异常 | 保留诊断值并标过期 |
参考:[1] ESP-IDF v5.5.5 System Time:RTC/高分辨率时钟、SNTP模式、POSIX时区[2] ESP-IDF v5.5.5 esp_netif SNTP配置与默认宏
完整主循环:回调送通知,应用更新状态
首次ONLINE后才调用esp_netif_sntp_init,wait_for_sync=false不创建等待信号量;本例不调用esp_netif_sntp_sync_wait,因此主循环不等待对时;回调只复制epoch和单调时刻到长度1队列。最新通知会覆盖旧通知,因此notices_observed只统计应用看见的通知,不是全部NTP包。 主循环先出队保存是否收到通知,再采当前单调时间,避免回调在两句之间跨毫秒时,把合法首次通知当成未来输入消费丢弃。
smooth_sync=false采用立即校正,墙上时间可能前跳或后跳;超时和同步年龄仍使用esp_timer。网络模块的2/4/8/16/30秒退避由断线事件和单次计时器驱动,LOST_IP会单独清在线位,不在主循环无限等上线。
// SPDX-License-Identifier: MIT
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_netif_sntp.h"
#include "sdkconfig.h"
#include "net.h"
#include "clock_policy.h"
static const char *TAG = "N05";
typedef struct { int64_t mono_ms, epoch; } sync_notice_t;
static QueueHandle_t sync_queue;
// lwIP task callback: copy only, no logging/Wi-Fi calls/calendar conversion.
static void synced(struct timeval *tv) {
sync_notice_t notice = {.mono_ms = esp_timer_get_time() / 1000, .epoch = tv->tv_sec};
xQueueOverwrite(sync_queue, ¬ice); // latest event retained; intermediate notices may coalesce
}
void app_main(void) {
sync_queue = xQueueCreate(1, sizeof(sync_notice_t));
if (!sync_queue) { ESP_LOGE(TAG, "queue allocation failed"); return; }
if (setenv("TZ", CONFIG_DEMO_TZ, 1)) { ESP_LOGE(TAG, "TZ allocation failed"); return; }
tzset();
clock_policy_t clock = {0}; // deliberately distrust every new boot until this boot receives SNTP
bool started = false, server_valid = strlen(CONFIG_DEMO_NTP_SERVER) && strcmp(CONFIG_DEMO_NTP_SERVER, "REPLACE_WITH_NTP_SERVER");
if (!server_valid) ESP_LOGW(TAG, "NTP_CONFIG_MISSING: clock remains UNSYNCED");
net_start();
for (;;) {
bool online = net_online();
if (online && server_valid && !started) {
esp_sntp_config_t cfg = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_DEMO_NTP_SERVER);
cfg.sync_cb = synced;
cfg.wait_for_sync = false;
cfg.smooth_sync = false; // immediate wall-clock corrections can jump; intervals use monotonic time
esp_err_t err = esp_netif_sntp_init(&cfg);
started = err == ESP_OK;
ESP_LOGI(TAG, "sntp_init=%s", esp_err_to_name(err));
}
sync_notice_t notice;
// Dequeue first, then sample now: a callback in this interval must not look like a future notice.
const bool has_notice = xQueueReceive(sync_queue, ¬ice, 0) == pdTRUE;
const int64_t now_ms = esp_timer_get_time() / 1000;
if (has_notice && clock_note_sync(&clock, notice.mono_ms, now_ms))
ESP_LOGI(TAG, "SYNC_NOTICE received_epoch=%" PRId64, notice.epoch);
time_t now = time(NULL);
char utc[40] = "NA", local[48] = "NA";
if (clock.synced) {
struct tm u, l;
gmtime_r(&now, &u); localtime_r(&now, &l);
strftime(utc, sizeof utc, "%Y-%m-%dT%H:%M:%SZ", &u);
strftime(local, sizeof local, "%Y-%m-%dT%H:%M:%S%z", &l);
}
ESP_LOGI(TAG, "uptime_ms=%" PRId64 " epoch=%" PRId64 " state=%s online=%u sync_age_ms=%" PRId64 " notices_observed=%" PRIu32 " utc=%s local=%s TZ=%s",
now_ms, (int64_t)now, clock_state(&clock, online, now_ms, (int64_t)CONFIG_DEMO_STALE_SECONDS * 1000),
online, clock_age(&clock, now_ms), clock.notices, utc, local, CONFIG_DEMO_TZ);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
参考:[2] ESP-IDF v5.5.5 esp_netif SNTP配置与默认宏[3] ESP-IDF v5.5.5 SNTP封装:TCP/IP线程调用、通知回调与启动[5] ESP-IDF v5.5.5官方Wi-Fi STA事件示例
时区只改变展示,不给epoch加八小时
默认POSIX字符串CST-8表示UTC+8;负号方向与常写的“UTC+8”相反。setenv("TZ",...)后调用tzset,再用localtime_r格式化,UTC则使用gmtime_r并写Z。示意图采用epoch=0的合成输入,只说明格式差异,不是有效测量时刻。
不是IANA时区数据库,不把Asia/Shanghai写进本例就宣称已验证。日光节约规则须按实际地区的POSIX表达式另核对;本例采用CST-8和UTC0两个清楚的对照。
- 做到这里,你应该看到
- 同一epoch的UTC与本地字符串差8小时,但epoch本身不手动增加。
参考:[1] ESP-IDF v5.5.5 System Time:RTC/高分辨率时钟、SNTP模式、POSIX时区[4] ESP-IDF v5.5.5官方SNTP示例:CST-8、tzset与时间格式
按首次离线、成功同步、断网和重启顺序验收
先用占位配置验证首次离线的UNSYNCED;再仅填Wi-Fi、暂使NTP不可达,确认ONLINE仍不能使它变为SYNCED。接通可访问的NTP源后,保留首次SYNC_NOTICE和随后完整日志,并与可信参考时钟比较。
成功同步后关闭网络,观察HOLDOVER及超龄STALE。恢复网络只有下一次真实同步通知才能重置年龄,不承诺一恢复IP就立刻校时。可将阈值临时改60秒缩短过期实验,但同步周期仍是600秒。
分别RESET和断电重启,在离线条件下检查本应用重新UNSYNCED,之后重新校时;将TZ改UTC0再重建核对格式。CSV记录板号、源服务器、配置、断网和复位方式。以上实板项目当前均pending。
| 输入/步骤 | 必须保留的事实 | 当前证据 |
|---|---|---|
| 首次离线 | UNSYNCED、age=-1、utc/local=NA | 主机策略已测;实板待验 |
| IP可用但无SNTP | 仍UNSYNCED | 主机策略已测;UDP通信待验 |
| 同步后掉线/超龄 | HOLDOVER→STALE | 主机边界已测;实板待验 |
| RESET/断电 | 本次同步标志重置 | 主机策略已测;两种实板复位待验 |
参考:[1] ESP-IDF v5.5.5 System Time:RTC/高分辨率时钟、SNTP模式、POSIX时区[2] ESP-IDF v5.5.5 esp_netif SNTP配置与默认宏
先复现模型,再按故障层修复
host_check.c直接包含固件用clock_policy.h,实际通过9组检查,覆盖首次离线、仅有IP、非法通知、首次同步、保持、年龄边界、重新同步、重启策略及主机POSIX格式。主机libc的格式检查不等于ESPnewlib、无线或SNTP实测。
一直UNSYNCED先看配置、DNS、UDP123和NTP服务;差8小时同时检查UTC、epoch与TZ,不靠修改epoch解决。NVS报需恢复时不自动擦除,只有确认整片Flash数据可丢弃才手动擦;网络故障通常不需要这一步。下一篇可将时间可信状态与MQTT样本一起上报。
cc -std=c11 -Wall -Wextra -Werror host_check.c -o /tmp/n05-host
/tmp/n05-host参考:[1] ESP-IDF v5.5.5 System Time:RTC/高分辨率时钟、SNTP模式、POSIX时区[3] ESP-IDF v5.5.5 SNTP封装:TCP/IP线程调用、通知回调与启动[5] ESP-IDF v5.5.5官方Wi-Fi STA事件示例
资料来源与版本说明
按固定版本官方源码与具体板级核对;实际构建、主机输入检查和实板是分开记录的状态。没有把预期步骤写成本站已运行结果。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。