Wi-Fi: SNTP could be started twice at a join, and ESP-IDF asserts on that

At every join the DNS and NTP setup ran twice in the same pass: the
"check now and then" timer compared this pass's time with a stamp taken
from millis() a moment later, and the unsigned difference underflowed.
Starting SNTP is only queued for the network task, so when the second run
looked before the first had been carried out, it queued a second start:
"Operating mode must not be set while SNTP client is running", a panic
nine seconds after boot. Rare (once in the dozen or so boots of this
branch's testing), there since v0.7.0. Rollback caught it: the update
was on Probation and the device went back to the build before.

The service now remembers that it started SNTP instead of asking
esp_sntp_enabled(), and the timer compares signed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-06 08:52:54 +02:00
co-authored by Claude Opus 5.5
parent b7aed8e91c
commit e14eb304b5
2 changed files with 9 additions and 2 deletions
+8 -2
View File
@@ -87,7 +87,10 @@ void WifiService::applyServers(Why why) {
touched = true; touched = true;
} }
} }
if (!esp_sntp_enabled()) { // Our own flag, not esp_sntp_enabled(): starting is only queued for the network task, and a
// second call that looked before it ran would start SNTP twice, which ESP-IDF asserts on.
if (!sntpStarted_) {
sntpStarted_ = true;
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_init(); esp_sntp_init();
} else if (touched || changed) { } else if (touched || changed) {
@@ -249,7 +252,10 @@ void WifiService::tick(uint32_t nowMs) {
apply(controller_.update(nowMs, settings_.getBool(Setting::WifiEnabled) && !paused_)); apply(controller_.update(nowMs, settings_.getBool(Setting::WifiEnabled) && !paused_));
if (controller_.state() == State::Connected && nowMs - serversCheckedMs_ >= kServersEveryMs) applyServers(Why::Check); // Signed: serversCheckedMs_ can be newer than this pass's nowMs (set from millis() just above),
// and the unsigned difference then ran the check again at once, at every join.
if (controller_.state() == State::Connected && static_cast<int32_t>(nowMs - serversCheckedMs_) >= static_cast<int32_t>(kServersEveryMs))
applyServers(Why::Check);
if (ntpWaiting_ && sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) { if (ntpWaiting_ && sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
ntpWaiting_ = false; ntpWaiting_ = false;
+1
View File
@@ -86,6 +86,7 @@ class WifiService : public Service {
std::vector<ScanEntry> listScan_; std::vector<ScanEntry> listScan_;
uint32_t listScanSeq_ = 0; uint32_t listScanSeq_ = 0;
bool ntpWaiting_ = false; bool ntpWaiting_ = false;
bool sntpStarted_ = false;
bool radioInitialised_ = false; bool radioInitialised_ = false;
bool paused_ = false; // Debug Builds: off for a moment, whatever the setting says bool paused_ = false; // Debug Builds: off for a moment, whatever the setting says
bool fixed_ = false; // the network in use has a Fixed address bool fixed_ = false; // the network in use has a Fixed address