feat: add silent NTP time sync on boot via saved WiFi credentials
New "Auto Sync on Boot" toggle in Clock Settings. When enabled, a background FreeRTOS task scans for saved WiFi networks at boot, connects, syncs time via NTP, then tears down WiFi — all without blocking boot or requiring user interaction. If no saved network is found after two scan attempts (with a 3-second retry gap), it bails silently. Conflict guards (BootNtpSync::cancel()) added to all WiFi-using activities so the background task cleans up before any user-initiated WiFi flow. Also fixes clock not appearing in the header until a button press by detecting the invalid→valid time transition after NTP sync. Made-with: Cursor
This commit is contained in:
163
src/util/BootNtpSync.cpp
Normal file
163
src/util/BootNtpSync.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "BootNtpSync.h"
|
||||
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "WifiCredentialStore.h"
|
||||
#include "util/TimeSync.h"
|
||||
|
||||
namespace BootNtpSync {
|
||||
|
||||
static volatile bool running = false;
|
||||
static TaskHandle_t taskHandle = nullptr;
|
||||
|
||||
struct TaskParams {
|
||||
std::vector<WifiCredential> credentials;
|
||||
std::string lastConnectedSsid;
|
||||
};
|
||||
|
||||
static bool tryConnectToSavedNetwork(const TaskParams& params) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
LOG_DBG("BNTP", "Scanning WiFi networks...");
|
||||
int16_t count = WiFi.scanNetworks();
|
||||
if (count <= 0) {
|
||||
LOG_DBG("BNTP", "Scan returned %d networks", count);
|
||||
WiFi.scanDelete();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("BNTP", "Found %d networks, matching against %zu saved credentials", count, params.credentials.size());
|
||||
|
||||
// Find best match: prefer lastConnectedSsid, otherwise first saved match
|
||||
const WifiCredential* bestMatch = nullptr;
|
||||
for (int i = 0; i < count; i++) {
|
||||
std::string ssid = WiFi.SSID(i).c_str();
|
||||
for (const auto& cred : params.credentials) {
|
||||
if (cred.ssid == ssid) {
|
||||
if (!bestMatch || cred.ssid == params.lastConnectedSsid) {
|
||||
bestMatch = &cred;
|
||||
}
|
||||
if (cred.ssid == params.lastConnectedSsid) {
|
||||
break; // Can't do better than lastConnected
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestMatch && bestMatch->ssid == params.lastConnectedSsid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WiFi.scanDelete();
|
||||
|
||||
if (!bestMatch) {
|
||||
LOG_DBG("BNTP", "No saved network found in scan results");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("BNTP", "Connecting to %s", bestMatch->ssid.c_str());
|
||||
if (!bestMatch->password.empty()) {
|
||||
WiFi.begin(bestMatch->ssid.c_str(), bestMatch->password.c_str());
|
||||
} else {
|
||||
WiFi.begin(bestMatch->ssid.c_str());
|
||||
}
|
||||
|
||||
const unsigned long start = millis();
|
||||
constexpr unsigned long CONNECT_TIMEOUT_MS = 10000;
|
||||
while (WiFi.status() != WL_CONNECTED && millis() - start < CONNECT_TIMEOUT_MS) {
|
||||
if (!running) return false;
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
wl_status_t status = WiFi.status();
|
||||
if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
||||
LOG_DBG("BNTP", "Connection failed (status=%d)", status);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
LOG_DBG("BNTP", "Connected to %s", bestMatch->ssid.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_DBG("BNTP", "Connection timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void taskFunc(void* param) {
|
||||
auto* params = static_cast<TaskParams*>(param);
|
||||
|
||||
bool connected = tryConnectToSavedNetwork(*params);
|
||||
|
||||
if (!connected && running) {
|
||||
LOG_DBG("BNTP", "First scan failed, retrying in 3s...");
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
if (running) {
|
||||
connected = tryConnectToSavedNetwork(*params);
|
||||
}
|
||||
}
|
||||
|
||||
if (connected && running) {
|
||||
LOG_DBG("BNTP", "Starting NTP sync...");
|
||||
bool synced = TimeSync::waitForNtpSync(5000);
|
||||
TimeSync::stopNtpSync();
|
||||
if (synced) {
|
||||
LOG_DBG("BNTP", "NTP sync successful");
|
||||
} else {
|
||||
LOG_DBG("BNTP", "NTP sync timed out, continuing without time");
|
||||
}
|
||||
}
|
||||
|
||||
WiFi.disconnect(false);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
delete params;
|
||||
running = false;
|
||||
taskHandle = nullptr;
|
||||
LOG_DBG("BNTP", "Boot NTP task complete");
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (!SETTINGS.autoNtpSync) {
|
||||
return;
|
||||
}
|
||||
|
||||
WIFI_STORE.loadFromFile();
|
||||
const auto& creds = WIFI_STORE.getCredentials();
|
||||
if (creds.empty()) {
|
||||
LOG_DBG("BNTP", "No saved WiFi credentials, skipping boot NTP sync");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* params = new TaskParams{creds, WIFI_STORE.getLastConnectedSsid()};
|
||||
|
||||
running = true;
|
||||
xTaskCreate(taskFunc, "BootNTP", 4096, params, 1, &taskHandle);
|
||||
LOG_DBG("BNTP", "Boot NTP sync task started");
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (!running) return;
|
||||
LOG_DBG("BNTP", "Cancelling boot NTP sync...");
|
||||
running = false;
|
||||
// Wait for the task to notice and clean up (up to 2s)
|
||||
for (int i = 0; i < 20 && taskHandle != nullptr; i++) {
|
||||
delay(100);
|
||||
}
|
||||
LOG_DBG("BNTP", "Boot NTP sync cancelled");
|
||||
}
|
||||
|
||||
bool isRunning() { return running; }
|
||||
|
||||
} // namespace BootNtpSync
|
||||
16
src/util/BootNtpSync.h
Normal file
16
src/util/BootNtpSync.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace BootNtpSync {
|
||||
|
||||
// Spawn a background FreeRTOS task that scans for saved WiFi networks,
|
||||
// connects, syncs NTP, then tears down WiFi. Non-blocking; does nothing
|
||||
// if autoNtpSync is disabled or no credentials are stored.
|
||||
void start();
|
||||
|
||||
// Signal the background task to abort and wait for it to finish.
|
||||
// Call before starting any other WiFi operation.
|
||||
void cancel();
|
||||
|
||||
bool isRunning();
|
||||
|
||||
} // namespace BootNtpSync
|
||||
Reference in New Issue
Block a user