feat(firmware): event-driven WiFi reconnect with exponential backoff
net_guard registers WiFi.onEvent() so disconnects are handled immediately instead of polled every 1s. Backoff 1s->2s->4s->...->60s cap. Every up/down transition is logged to the event log with the disconnect reason code, so field failures are diagnosable.
This commit is contained in:
55
firmware/lib/net_guard/net_guard.cpp
Normal file
55
firmware/lib/net_guard/net_guard.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// firmware/lib/net_guard/net_guard.cpp
|
||||
#include "net_guard.h"
|
||||
|
||||
uint32_t net_guard_next_backoff_ms(uint32_t attempt) {
|
||||
if (attempt >= 6) return 60000;
|
||||
return 1000u * (1u << attempt);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <WiFi.h>
|
||||
#include "event_log.h"
|
||||
|
||||
static const DeviceConfig* s_cfg = nullptr;
|
||||
static volatile uint8_t s_last_disconnect = 0;
|
||||
static volatile bool s_up = false;
|
||||
static volatile uint32_t s_attempts = 0;
|
||||
static volatile uint32_t s_next_retry_ms = 0;
|
||||
|
||||
static void on_wifi_event(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
s_up = true;
|
||||
s_attempts = 0;
|
||||
s_next_retry_ms = 0;
|
||||
event_log_write(EVT_WIFI_UP, (uint16_t)(int16_t)WiFi.RSSI(), 0);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
s_up = false;
|
||||
s_last_disconnect = (uint8_t)info.wifi_sta_disconnected.reason;
|
||||
event_log_write(EVT_WIFI_DOWN, s_last_disconnect, 0);
|
||||
s_next_retry_ms = millis() + net_guard_next_backoff_ms(s_attempts);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void net_guard_start(const DeviceConfig& cfg) {
|
||||
s_cfg = &cfg;
|
||||
WiFi.onEvent(on_wifi_event);
|
||||
WiFi.setAutoReconnect(false); // we drive reconnect ourselves
|
||||
}
|
||||
|
||||
bool net_guard_is_up() { return s_up; }
|
||||
|
||||
uint8_t net_guard_last_disconnect_reason() { return s_last_disconnect; }
|
||||
|
||||
extern "C" void net_guard_tick() {
|
||||
if (s_up || s_cfg == nullptr) return;
|
||||
if (millis() < s_next_retry_ms) return;
|
||||
s_attempts++;
|
||||
WiFi.disconnect(false, false);
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
24
firmware/lib/net_guard/net_guard.h
Normal file
24
firmware/lib/net_guard/net_guard.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// firmware/lib/net_guard/net_guard.h
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 60s, 60s, ...
|
||||
// attempt 0 -> 1000ms, clamped at 60000ms.
|
||||
uint32_t net_guard_next_backoff_ms(uint32_t attempt);
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include "config.h"
|
||||
|
||||
// Registers WiFi.onEvent() handler and starts auto-reconnect loop.
|
||||
// Must be called once after WiFi.begin() succeeds.
|
||||
void net_guard_start(const DeviceConfig& cfg);
|
||||
|
||||
// True iff WiFi is currently associated with IP.
|
||||
bool net_guard_is_up();
|
||||
|
||||
// Last disconnect reason code from WIFI_EVENT_STA_DISCONNECTED (0 = none).
|
||||
uint8_t net_guard_last_disconnect_reason();
|
||||
|
||||
// Non-blocking tick called from loop(); kicks reconnect if due.
|
||||
extern "C" void net_guard_tick();
|
||||
#endif
|
||||
32
firmware/test/test_net_guard/test_net_guard.cpp
Normal file
32
firmware/test/test_net_guard/test_net_guard.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// firmware/test/test_net_guard/test_net_guard.cpp
|
||||
#include <unity.h>
|
||||
#include "net_guard.h"
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
void test_backoff_starts_at_one_second() {
|
||||
TEST_ASSERT_EQUAL(1000, net_guard_next_backoff_ms(0));
|
||||
}
|
||||
|
||||
void test_backoff_doubles_each_attempt() {
|
||||
TEST_ASSERT_EQUAL(2000, net_guard_next_backoff_ms(1));
|
||||
TEST_ASSERT_EQUAL(4000, net_guard_next_backoff_ms(2));
|
||||
TEST_ASSERT_EQUAL(8000, net_guard_next_backoff_ms(3));
|
||||
TEST_ASSERT_EQUAL(16000, net_guard_next_backoff_ms(4));
|
||||
TEST_ASSERT_EQUAL(32000, net_guard_next_backoff_ms(5));
|
||||
}
|
||||
|
||||
void test_backoff_clamps_at_60s() {
|
||||
TEST_ASSERT_EQUAL(60000, net_guard_next_backoff_ms(6));
|
||||
TEST_ASSERT_EQUAL(60000, net_guard_next_backoff_ms(7));
|
||||
TEST_ASSERT_EQUAL(60000, net_guard_next_backoff_ms(100));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_backoff_starts_at_one_second);
|
||||
RUN_TEST(test_backoff_doubles_each_attempt);
|
||||
RUN_TEST(test_backoff_clamps_at_60s);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user