spaxel/firmware/main/ntp.c
jedarden a42c5e7ea1 feat: wire NTP client into firmware build and initialization
- Add ntp.c to CMakeLists.txt SRCS so it's compiled and linked
- Load ntp_server from NVS in load_nvs_config() (default: pool.ntp.org)
- Add ntp_server field to spaxel_state_t
- Initialize NTP after WiFi connects with 10s sync timeout, WARN on failure
- Re-sync NTP after WiFi reconnect (WIFI_LOST state)
- Start periodic 10-minute resync timer via esp_timer
- Add ntp_synced boolean to health JSON message
- Handle ntp_server field in downstream config message
- Fix periodic resync callback to properly stop/restart SNTP

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 13:19:07 -04:00

159 lines
4.2 KiB
C

#include "ntp.h"
#include "esp_log.h"
#include "esp_sntp.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include <string.h>
static const char *TAG = "ntp";
// NTP sync event bits
#define NTP_SYNC_BIT BIT0
static EventGroupHandle_t s_ntp_events = NULL;
static esp_timer_handle_t s_resync_timer = NULL;
static bool s_is_synced = false;
static char s_ntp_server[64] = "pool.ntp.org";
// Resync interval: 10 minutes (600 seconds)
#define NTP_RESYNC_INTERVAL_US (600LL * 1000000LL)
// SNTP callback - called when time sync completes
static void sntp_sync_time_callback(struct timeval *tv) {
if (tv) {
ESP_LOGI(TAG, "NTP synchronized: %lld.%06ld", (long long)tv->tv_sec, tv->tv_usec);
s_is_synced = true;
if (s_ntp_events) {
xEventGroupSetBits(s_ntp_events, NTP_SYNC_BIT);
}
} else {
ESP_LOGW(TAG, "NTP sync callback received NULL timeval");
}
}
// Periodic resync timer callback
static void periodic_resync_callback(void *arg) {
ESP_LOGI(TAG, "Periodic NTP resync triggered");
esp_sntp_stop();
s_is_synced = false;
if (s_ntp_events) {
xEventGroupClearBits(s_ntp_events, NTP_SYNC_BIT);
}
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, s_ntp_server);
sntp_set_time_sync_notification_cb(sntp_sync_time_callback);
esp_sntp_init();
}
esp_err_t ntp_init(void) {
if (s_ntp_events == NULL) {
s_ntp_events = xEventGroupCreate();
if (!s_ntp_events) {
ESP_LOGE(TAG, "Failed to create event group");
return ESP_ERR_NO_MEM;
}
}
ESP_LOGI(TAG, "NTP client initialized (server: %s)", s_ntp_server);
return ESP_OK;
}
esp_err_t ntp_start_sync(const char *ntp_server) {
if (!ntp_server) {
ntp_server = "pool.ntp.org";
}
// Store server for resync
strncpy(s_ntp_server, ntp_server, sizeof(s_ntp_server) - 1);
s_ntp_server[sizeof(s_ntp_server) - 1] = '\0';
ESP_LOGI(TAG, "Starting NTP sync with server: %s", s_ntp_server);
// Clear previous sync state
s_is_synced = false;
if (s_ntp_events) {
xEventGroupClearBits(s_ntp_events, NTP_SYNC_BIT);
}
// Configure SNTP
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, s_ntp_server);
// Set sync callback
sntp_set_time_sync_notification_cb(sntp_sync_time_callback);
// Start SNTP
esp_sntp_init();
return ESP_OK;
}
bool ntp_wait_sync(int timeout_ms) {
if (!s_ntp_events) {
ESP_LOGW(TAG, "NTP event group not initialized");
return false;
}
TickType_t ticks = pdMS_TO_TICKS(timeout_ms);
EventBits_t bits = xEventGroupWaitBits(s_ntp_events, NTP_SYNC_BIT,
pdFALSE, pdFALSE, ticks);
if (bits & NTP_SYNC_BIT) {
ESP_LOGI(TAG, "NTP sync successful");
return true;
}
ESP_LOGW(TAG, "NTP sync timeout after %d ms", timeout_ms);
return false;
}
bool ntp_is_synced(void) {
return s_is_synced;
}
const char* ntp_status_str(void) {
return s_is_synced ? "synced" : "unsynced";
}
void ntp_start_periodic_resync(void) {
if (s_resync_timer != NULL) {
ESP_LOGW(TAG, "Periodic resync timer already started");
return;
}
const esp_timer_create_args_t timer_args = {
.callback = &periodic_resync_callback,
.name = "ntp_resync",
};
esp_err_t err = esp_timer_create(&timer_args, &s_resync_timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create resync timer: %s", esp_err_to_name(err));
return;
}
err = esp_timer_start_periodic(s_resync_timer, NTP_RESYNC_INTERVAL_US);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start resync timer: %s", esp_err_to_name(err));
esp_timer_delete(s_resync_timer);
s_resync_timer = NULL;
return;
}
ESP_LOGI(TAG, "Periodic NTP resync started (interval: %lld seconds)",
NTP_RESYNC_INTERVAL_US / 1000000LL);
}
void ntp_stop(void) {
esp_sntp_stop();
if (s_resync_timer) {
esp_timer_stop(s_resync_timer);
esp_timer_delete(s_resync_timer);
s_resync_timer = NULL;
}
s_is_synced = false;
ESP_LOGI(TAG, "NTP client stopped");
}