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>
This commit is contained in:
jedarden 2026-04-07 13:18:27 -04:00
parent 83a86faee4
commit a42c5e7ea1
5 changed files with 48 additions and 2 deletions

View file

@ -6,6 +6,7 @@ idf_component_register(
"ble.c"
"provision.c"
"nvs_migration.c"
"ntp.c"
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}/spaxel-firmware/main"
REQUIRES esp_wifi esp_netif nvs_flash mdns esp_http_client esp_timer bt driver log esp_http_server mbedtls esp_ota
)

View file

@ -131,6 +131,12 @@ static esp_err_t load_nvs_config(void) {
g_state.debug = (debug == 1);
}
// Load NTP server
len = sizeof(g_state.ntp_server);
if (nvs_get_str(nvs, NVS_KEY_NTP_SERVER, g_state.ntp_server, &len) != ESP_OK) {
strncpy(g_state.ntp_server, "pool.ntp.org", sizeof(g_state.ntp_server));
}
nvs_close(nvs);
ESP_LOGI(TAG, "NVS config loaded: provisioned=%d, role=%s, rate=%d Hz",
@ -200,8 +206,18 @@ static void state_machine_task(void *arg) {
if (bits & SPAXEL_EVENT_WIFI_CONNECTED) {
ESP_LOGI(TAG, "WiFi connected");
wifi_fail_count = 0;
g_state.state = NODE_STATE_MOTHERSHIP_DISCOVERY;
discovery_fail_count = 0;
// Initialize NTP after WiFi is up
ESP_LOGI(TAG, "Starting NTP sync with server: %s", g_state.ntp_server);
ntp_init();
ntp_start_sync(g_state.ntp_server);
if (!ntp_wait_sync(10000)) {
ESP_LOGW(TAG, "NTP sync failed, proceeding without stagger");
}
ntp_start_periodic_resync();
g_state.state = NODE_STATE_MOTHERSHIP_DISCOVERY;
} else if (bits & SPAXEL_EVENT_WIFI_FAILED) {
wifi_fail_count++;
ESP_LOGW(TAG, "WiFi failed (attempt %d)", wifi_fail_count);
@ -315,6 +331,15 @@ static void state_machine_task(void *arg) {
if (bits & SPAXEL_EVENT_WIFI_CONNECTED) {
ESP_LOGI(TAG, "WiFi reconnected");
// Re-sync NTP after reconnect
ntp_init();
ntp_start_sync(g_state.ntp_server);
if (!ntp_wait_sync(10000)) {
ESP_LOGW(TAG, "NTP resync failed after WiFi reconnect");
}
ntp_start_periodic_resync();
g_state.state = NODE_STATE_MOTHERSHIP_DISCOVERY;
} else {
wifi_fail_count++;

View file

@ -35,9 +35,15 @@ static void sntp_sync_time_callback(struct timeval *tv) {
// 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();
// No need to wait here - the callback will handle completion
}
esp_err_t ntp_init(void) {

View file

@ -83,6 +83,7 @@ typedef struct {
bool provisioned;
bool debug;
uint8_t mac[6];
char ntp_server[64];
EventGroupHandle_t events;
} spaxel_state_t;

View file

@ -2,6 +2,7 @@
#include "spaxel.h"
#include "csi.h"
#include "wifi.h"
#include "ntp.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_system.h"
@ -348,6 +349,9 @@ esp_err_t websocket_send_health(void) {
}
}
// NTP sync status
cJSON_AddBoolToObject(root, "ntp_synced", ntp_is_synced());
char *json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
@ -584,6 +588,15 @@ static void handle_config_msg(cJSON *root) {
g_variance_threshold = (float)var_thresh->valuedouble;
}
// NTP server (runtime reconfiguration)
cJSON *ntp = cJSON_GetObjectItem(root, "ntp_server");
if (ntp && cJSON_IsString(ntp) && strlen(ntp->valuestring) > 0) {
strncpy(g_state.ntp_server, ntp->valuestring, sizeof(g_state.ntp_server) - 1);
g_state.ntp_server[sizeof(g_state.ntp_server) - 1] = '\0';
ESP_LOGI(TAG, "NTP server changed to: %s", g_state.ntp_server);
ntp_start_sync(g_state.ntp_server);
}
if (changed) {
csi_set_rate(g_state.packet_rate);
}