fix(firmware): net_guard boot-state seed + no spurious disconnect

- Seed s_up from WiFi.status() in net_guard_start so the first
  STA_GOT_IP (fired during setup's busy-wait, before onEvent was
  registered) is not missed — prevents a reconnect flap on every boot.
- Drop WiFi.disconnect() from net_guard_tick; WiFi.begin() alone
  re-associates cleanly and avoids a spurious STA_DISCONNECTED that
  was double-logging EVT_WIFI_DOWN on every retry.
- Re-check s_up after the millis() timing gate to close the
  GOT_IP-vs-tick race.
- Document the volatile-only shared-state contract.
This commit is contained in:
2026-04-23 13:31:47 -07:00
parent 9f293b4639
commit 84d9ba349b

View File

@@ -10,6 +10,9 @@ uint32_t net_guard_next_backoff_ms(uint32_t attempt) {
#include <WiFi.h> #include <WiFi.h>
#include "event_log.h" #include "event_log.h"
// Shared with the WiFi event task. 32-bit aligned loads/stores are atomic on
// Xtensa; volatile suffices. Tick re-evaluates every loop iteration, so stale
// reads self-correct within ~200ms.
static const DeviceConfig* s_cfg = nullptr; static const DeviceConfig* s_cfg = nullptr;
static volatile uint8_t s_last_disconnect = 0; static volatile uint8_t s_last_disconnect = 0;
static volatile bool s_up = false; static volatile bool s_up = false;
@@ -36,6 +39,10 @@ static void on_wifi_event(WiFiEvent_t event, WiFiEventInfo_t info) {
void net_guard_start(const DeviceConfig& cfg) { void net_guard_start(const DeviceConfig& cfg) {
s_cfg = &cfg; s_cfg = &cfg;
// Seed s_up from the current WiFi state. setup()'s busy-wait on
// WiFi.begin() can produce a STA_GOT_IP before onEvent() is registered;
// without this seed, the first tick would force a spurious reconnect.
if (WiFi.status() == WL_CONNECTED) s_up = true;
WiFi.onEvent(on_wifi_event); WiFi.onEvent(on_wifi_event);
WiFi.setAutoReconnect(false); // we drive reconnect ourselves WiFi.setAutoReconnect(false); // we drive reconnect ourselves
} }
@@ -47,8 +54,11 @@ uint8_t net_guard_last_disconnect_reason() { return s_last_disconnect; }
extern "C" void net_guard_tick() { extern "C" void net_guard_tick() {
if (s_up || s_cfg == nullptr) return; if (s_up || s_cfg == nullptr) return;
if (millis() < s_next_retry_ms) return; if (millis() < s_next_retry_ms) return;
if (s_up) return; // re-check after the timing gate — closes GOT_IP-vs-tick race
s_attempts++; s_attempts++;
WiFi.disconnect(false, false); // WiFi.begin() alone re-associates cleanly; a prior WiFi.disconnect() call
// synchronously emits STA_DISCONNECTED on the event task, which would
// double-log EVT_WIFI_DOWN (reason=ASSOC_LEAVE) on every retry.
WiFi.begin(s_cfg->wifi_ssid.c_str(), s_cfg->wifi_pass.c_str()); WiFi.begin(s_cfg->wifi_ssid.c_str(), s_cfg->wifi_pass.c_str());
s_next_retry_ms = millis() + net_guard_next_backoff_ms(s_attempts); s_next_retry_ms = millis() + net_guard_next_backoff_ms(s_attempts);
} }