- Link down/switch edges now softDisconnect() every started slot instead of disconnect(). The full stop could wait forever for a DISCONNECTED event on the Core 0 MQTT task, and it destroyed and recreated every slot's esp-mqtt task on each Wi-Fi drop, where the bridge previously stopped none (measured: 62 s deauth, five slots, zero stops). softDisconnect() is bounded and keeps the task; reconnectSlotClient() then calls reconnect() on the new route. Rename the transition action to disconnect_started_slots to match. - Ethernet no-IP recovery and init retries rebuild the CH390 netif, so they now run inside the route-switch mutation gate. An OTA/WebConfig lock taken after maintain() samples the lock count can no longer have the interface torn down underneath it.
226 lines
7.9 KiB
C++
226 lines
7.9 KiB
C++
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
enum class NetworkTransition : uint8_t {
|
|
None,
|
|
Up,
|
|
Down,
|
|
Switched,
|
|
};
|
|
|
|
enum class NetworkMedium : uint8_t {
|
|
None,
|
|
Ethernet,
|
|
WiFi,
|
|
};
|
|
|
|
enum class NetworkDiagnosticReason : uint8_t {
|
|
EthernetActive,
|
|
EthernetInitFailed,
|
|
EthernetLinkUnknown,
|
|
EthernetLinkDown,
|
|
EthernetAwaitingIp,
|
|
EthernetStabilizing,
|
|
SwitchingLocked,
|
|
EthernetReady,
|
|
};
|
|
|
|
namespace NetworkPolicy {
|
|
|
|
struct MQTTTransitionActions {
|
|
bool disconnect_started_slots;
|
|
bool retry_disconnected_slots_now;
|
|
bool reset_reconnect_backoff;
|
|
};
|
|
|
|
static constexpr MQTTTransitionActions mqttActions(NetworkTransition transition) {
|
|
return {
|
|
transition == NetworkTransition::Down || transition == NetworkTransition::Switched,
|
|
transition == NetworkTransition::Up || transition == NetworkTransition::Switched,
|
|
transition == NetworkTransition::Switched,
|
|
};
|
|
}
|
|
|
|
static constexpr const char* mediumName(NetworkMedium medium) {
|
|
return medium == NetworkMedium::Ethernet ? "ethernet"
|
|
: medium == NetworkMedium::WiFi ? "wifi"
|
|
: "none";
|
|
}
|
|
|
|
struct AutomaticSelectionInput {
|
|
NetworkMedium selected;
|
|
bool ethernet_connected;
|
|
bool wifi_connected;
|
|
bool wifi_configured;
|
|
bool switching_locked;
|
|
uint32_t ethernet_stable_ms;
|
|
uint32_t selected_down_ms;
|
|
};
|
|
|
|
// Keep short link flaps from bouncing the default route. Ethernet is allowed
|
|
// to recover before Wi-Fi is started, and must then remain usable before it can
|
|
// preempt a working Wi-Fi fallback.
|
|
static constexpr uint32_t kEthernetDownGraceMs = 3000;
|
|
static constexpr uint32_t kEthernetFailbackStableMs = 10000;
|
|
static constexpr uint32_t kEthernetNoLinkBootGraceMs = 750;
|
|
static constexpr uint32_t kEthernetNoIpRecoveryMs = 120000;
|
|
static constexpr uint32_t kEthernetInitRetryMinMs = 5000;
|
|
static constexpr uint32_t kEthernetInitRetryMaxMs = 300000;
|
|
static constexpr uint32_t kNtpRetryMs = 30000;
|
|
static constexpr uint32_t kManualOtaSessionTimeoutMs =
|
|
15UL * 60UL * 1000UL;
|
|
|
|
static inline uint32_t ethernetInitRetryDelayMs(uint8_t attempt) {
|
|
if (attempt == 0) return 0;
|
|
uint32_t delay_ms = kEthernetInitRetryMinMs;
|
|
for (uint8_t i = 1; i < attempt && delay_ms < kEthernetInitRetryMaxMs; ++i) {
|
|
delay_ms = delay_ms > kEthernetInitRetryMaxMs / 2
|
|
? kEthernetInitRetryMaxMs : delay_ms * 2;
|
|
}
|
|
return delay_ms;
|
|
}
|
|
|
|
static inline bool ethernetInitRetryDue(uint8_t attempt,
|
|
uint32_t now_ms,
|
|
uint32_t last_attempt_ms) {
|
|
return attempt == 0 ||
|
|
(uint32_t)(now_ms - last_attempt_ms) >=
|
|
ethernetInitRetryDelayMs(attempt);
|
|
}
|
|
|
|
static constexpr bool ethernetNoIpRecoveryDue(bool initialized,
|
|
bool link_up,
|
|
bool connected,
|
|
uint32_t now_ms,
|
|
uint32_t no_ip_since_ms) {
|
|
return initialized && link_up && !connected && no_ip_since_ms != 0 &&
|
|
(uint32_t)(now_ms - no_ip_since_ms) >= kEthernetNoIpRecoveryMs;
|
|
}
|
|
|
|
static inline NetworkDiagnosticReason automaticDiagnosticReason(
|
|
bool ethernet_initialized, bool ethernet_link_known,
|
|
bool ethernet_link_up, bool ethernet_connected, NetworkMedium selected,
|
|
bool switching_locked, uint32_t ethernet_stable_ms) {
|
|
if (!ethernet_initialized) {
|
|
return NetworkDiagnosticReason::EthernetInitFailed;
|
|
}
|
|
if (!ethernet_link_known) {
|
|
return NetworkDiagnosticReason::EthernetLinkUnknown;
|
|
}
|
|
if (!ethernet_link_up) {
|
|
return NetworkDiagnosticReason::EthernetLinkDown;
|
|
}
|
|
if (!ethernet_connected) {
|
|
return NetworkDiagnosticReason::EthernetAwaitingIp;
|
|
}
|
|
if (selected == NetworkMedium::Ethernet) {
|
|
return NetworkDiagnosticReason::EthernetActive;
|
|
}
|
|
if (switching_locked) {
|
|
return NetworkDiagnosticReason::SwitchingLocked;
|
|
}
|
|
if (selected == NetworkMedium::WiFi &&
|
|
ethernet_stable_ms < kEthernetFailbackStableMs) {
|
|
return NetworkDiagnosticReason::EthernetStabilizing;
|
|
}
|
|
return NetworkDiagnosticReason::EthernetReady;
|
|
}
|
|
|
|
static inline const char* diagnosticReasonName(
|
|
NetworkDiagnosticReason reason) {
|
|
switch (reason) {
|
|
case NetworkDiagnosticReason::EthernetActive:
|
|
return "ethernet-active";
|
|
case NetworkDiagnosticReason::EthernetInitFailed:
|
|
return "ethernet-init-failed";
|
|
case NetworkDiagnosticReason::EthernetLinkUnknown:
|
|
return "ethernet-link-unknown";
|
|
case NetworkDiagnosticReason::EthernetLinkDown:
|
|
return "ethernet-link-down";
|
|
case NetworkDiagnosticReason::EthernetAwaitingIp:
|
|
return "ethernet-awaiting-ip";
|
|
case NetworkDiagnosticReason::EthernetStabilizing:
|
|
return "ethernet-stabilizing";
|
|
case NetworkDiagnosticReason::SwitchingLocked:
|
|
return "switching-locked";
|
|
case NetworkDiagnosticReason::EthernetReady:
|
|
return "ethernet-ready";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
static constexpr bool ntpPendingAfterConnectivitySample(
|
|
bool synced, bool pending, bool was_connected, bool connected) {
|
|
return pending || (!synced && connected && !was_connected);
|
|
}
|
|
|
|
static constexpr bool ntpRetryDue(bool synced, bool connected,
|
|
uint32_t now_ms, uint32_t last_attempt_ms) {
|
|
return !synced && connected &&
|
|
(uint32_t)(now_ms - last_attempt_ms) >= kNtpRetryMs;
|
|
}
|
|
|
|
static constexpr NetworkMedium bootSelection(bool ethernet_connected,
|
|
bool wifi_configured) {
|
|
return ethernet_connected ? NetworkMedium::Ethernet
|
|
: wifi_configured ? NetworkMedium::WiFi
|
|
: NetworkMedium::None;
|
|
}
|
|
|
|
// Give the PHY a short window to report carrier. Once a definitive link-down
|
|
// sample arrives, do not stall mesh startup for the full DHCP deadline. A
|
|
// present link (or an unknown PHY state) still receives the entire probe.
|
|
static constexpr bool ethernetBootProbePending(bool ethernet_initialized,
|
|
bool ethernet_connected,
|
|
bool link_known,
|
|
bool link_up,
|
|
uint32_t elapsed_ms,
|
|
uint32_t wait_ms) {
|
|
return ethernet_initialized && !ethernet_connected && elapsed_ms < wait_ms &&
|
|
(!link_known || link_up || elapsed_ms < kEthernetNoLinkBootGraceMs);
|
|
}
|
|
|
|
static inline NetworkMedium automaticSelection(
|
|
const AutomaticSelectionInput& input) {
|
|
if (input.switching_locked) return input.selected;
|
|
|
|
if (input.selected == NetworkMedium::Ethernet) {
|
|
if (input.ethernet_connected) return NetworkMedium::Ethernet;
|
|
if (input.wifi_configured && input.wifi_connected &&
|
|
input.selected_down_ms >= kEthernetDownGraceMs) {
|
|
return NetworkMedium::WiFi;
|
|
}
|
|
return NetworkMedium::Ethernet;
|
|
}
|
|
|
|
if (input.selected == NetworkMedium::WiFi) {
|
|
if (input.ethernet_connected &&
|
|
(!input.wifi_connected ||
|
|
input.ethernet_stable_ms >= kEthernetFailbackStableMs)) {
|
|
return NetworkMedium::Ethernet;
|
|
}
|
|
return NetworkMedium::WiFi;
|
|
}
|
|
|
|
if (input.ethernet_connected) return NetworkMedium::Ethernet;
|
|
if (input.wifi_configured && input.wifi_connected) return NetworkMedium::WiFi;
|
|
return NetworkMedium::None;
|
|
}
|
|
|
|
// `start ota` uses the selected LAN only when reachable and not explicitly
|
|
// forced to SoftAP. Manifest OTA has no fallback and checks connectivity itself.
|
|
static constexpr bool startOtaUsesSelectedNetwork(bool force_ap,
|
|
bool network_connected) {
|
|
return !force_ap && network_connected;
|
|
}
|
|
|
|
static constexpr bool manualOtaTimeoutDue(uint32_t now_ms,
|
|
uint32_t started_ms,
|
|
bool upload_in_progress) {
|
|
return !upload_in_progress &&
|
|
(uint32_t)(now_ms - started_ms) >= kManualOtaSessionTimeoutMs;
|
|
}
|
|
|
|
} // namespace NetworkPolicy
|