Both Clyde North observers sat WL_CONNECTED on an AP that kept beaconing after its bridge to the wired LAN died: association up, good RSSI, stale DHCP lease, but ARP-invisible and both MQTT brokers stuck in backoff. The firmware equated "associated" with "online", so neither node ever re-scanned and the outage held until a manual reassociation. Add a connectivity watchdog to NetworkService: while connected, ARP-probe the gateway every 30s (posted to the lwIP tcpip thread, safe on both the IDF4 and IDF5 cores). If the gateway stays silent for 3 minutes, clear the channel hint and force a full disconnect/rescan so the node can roam to a healthy AP, backing off exponentially (up to 48min) while the outage persists. Nodes without a gateway skip the probe. Add a `wifi reconnect` repeater command that triggers the same forced reassociation on demand, and report `gw:ok|lost wd:<count>` in `get wifi.status`. Teach the web panel parser the new fields (also fixes the IP metric rendering as "x.x.x.x channel:n") and show Gateway and Channel in the Wi-Fi card. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
2.1 KiB
C++
73 lines
2.1 KiB
C++
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
#include <helpers/IdentityStore.h>
|
|
#include <helpers/NetworkStateProvider.h>
|
|
|
|
#if defined(ESP_PLATFORM)
|
|
#include <WiFi.h>
|
|
|
|
#include <atomic>
|
|
#endif
|
|
|
|
#include "NetworkPrefs.h"
|
|
|
|
class NetworkService : public NetworkStateProvider {
|
|
public:
|
|
NetworkService();
|
|
|
|
void begin(FILESYSTEM* fs,
|
|
uint8_t legacy_wifi_powersave = 0,
|
|
const char* legacy_wifi_ssid = nullptr,
|
|
const char* legacy_wifi_pwd = nullptr);
|
|
void end();
|
|
void loop(bool network_required);
|
|
|
|
bool setWifiSSID(const char* ssid);
|
|
bool setWifiPassword(const char* pwd);
|
|
const char* getWifiSSID() const { return _prefs.wifi_ssid; }
|
|
bool setWifiPowerSave(const char* mode);
|
|
const char* getWifiPowerSave() const;
|
|
bool setNtpServer(uint8_t index, const char* server);
|
|
const char* getNtpServer(uint8_t index) const;
|
|
void formatWifiStatusReply(char* reply, size_t reply_size) const;
|
|
void reconnectWifi();
|
|
void forceReconnect();
|
|
|
|
bool isWifiConnected() const override;
|
|
bool hasTimeSync() const override { return _have_time_sync; }
|
|
|
|
private:
|
|
#if defined(ESP_PLATFORM)
|
|
static wifi_ps_type_t toEspPowerSave(uint8_t mode);
|
|
static const char* getPowerSaveLabel(uint8_t mode);
|
|
void ensureWifi(bool network_required);
|
|
void updateTimeSync();
|
|
void restartTimeSync();
|
|
void updateConnectivityWatchdog();
|
|
static void watchdogProbeCallback(void* arg);
|
|
#endif
|
|
static bool isValidNtpServer(const char* server);
|
|
bool savePrefs();
|
|
|
|
FILESYSTEM* _fs;
|
|
NetworkPrefs _prefs;
|
|
bool _wifi_started;
|
|
bool _sntp_started;
|
|
bool _have_time_sync;
|
|
int _last_wifi_status;
|
|
unsigned long _last_wifi_attempt;
|
|
#if defined(ESP_PLATFORM)
|
|
// Connectivity watchdog: _wd_gateway_seen and _wd_probe_pending are shared with
|
|
// the lwIP tcpip thread; _wd_gateway_ip is only written while no probe is pending.
|
|
std::atomic<bool> _wd_gateway_seen;
|
|
std::atomic<bool> _wd_probe_pending;
|
|
uint32_t _wd_gateway_ip;
|
|
bool _wd_was_connected;
|
|
unsigned long _wd_last_gateway_ok;
|
|
unsigned long _wd_last_probe;
|
|
uint8_t _wd_backoff_shift;
|
|
uint16_t _wd_reconnect_count;
|
|
#endif
|
|
};
|