mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 14:07:58 +00:00
feat(mqtt): add link diagnostics and improve network logging
This commit is contained in:
@@ -81,6 +81,7 @@ reboot
|
||||
```bash
|
||||
get wifi.ssid
|
||||
get link.status
|
||||
get link.diag
|
||||
get bridge.enabled
|
||||
get mqtt.rx
|
||||
get mqtt.tx
|
||||
@@ -547,6 +548,7 @@ These settings apply across all MQTT slots:
|
||||
- `get wifi.ssid` - Get WiFi SSID
|
||||
- `get wifi.pwd` - Get WiFi password
|
||||
- `get link.status` - Get the selected network medium, connection status, IP, signal when available, and uptime
|
||||
- `get link.diag` - Explain automatic Ethernet selection using controller initialization, link event, IP, WiFi fallback, route-lock, and reason state
|
||||
- `get wifi.status` - WiFi-only compatibility alias; reports n/a when another medium is selected
|
||||
- `get wifi.powersave` - Get WiFi power save mode (none/min/max)
|
||||
|
||||
@@ -859,6 +861,12 @@ the radio actually performs in that case.
|
||||
### Connection Handling
|
||||
- Automatic reconnection with exponential backoff per slot; a slot that stays down through
|
||||
the full backoff ladder is retried on a slow periodic probe instead of hammering the broker
|
||||
- Ethernet-preferred builds wait the full configured boot probe window for an Ethernet IP;
|
||||
delayed or unavailable PHY carrier reporting does not shorten the DHCP deadline. The boot
|
||||
selection log includes the measured probe duration.
|
||||
- Ethernet/WiFi transitions are logged. A lost or changed route stops every started MQTT
|
||||
client, including one whose disconnect callback arrived first; once a usable route returns,
|
||||
route-caused backoff is cleared and one immediate reconnect is allowed
|
||||
- Packets are queued while a slot is disconnected and flushed when it recovers
|
||||
|
||||
### Raw Radio Data Capture
|
||||
|
||||
@@ -1152,10 +1152,12 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
MQTTPrefs* obs = _cli.getObserverPrefs();
|
||||
Serial.printf("Network: probing Ethernet for up to %lums\n",
|
||||
(unsigned long)NETWORK_ETHERNET_BOOT_WAIT_MS);
|
||||
const uint32_t ethernet_probe_started_at = millis();
|
||||
boot_network.bootstrap(obs->wifi_ssid, obs->wifi_password,
|
||||
NETWORK_ETHERNET_BOOT_WAIT_MS);
|
||||
Serial.printf("Network: selected %s (%s)\n", boot_network.mediumName(),
|
||||
boot_network.statusName());
|
||||
Serial.printf("Network: selected %s (%s) after %lums\n",
|
||||
boot_network.mediumName(), boot_network.statusName(),
|
||||
(unsigned long)(millis() - ethernet_probe_started_at));
|
||||
}
|
||||
|
||||
acl.load(_fs, self_id);
|
||||
|
||||
@@ -953,10 +953,12 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
MQTTPrefs* obs = _cli.getObserverPrefs();
|
||||
Serial.printf("Network: probing Ethernet for up to %lums\n",
|
||||
(unsigned long)NETWORK_ETHERNET_BOOT_WAIT_MS);
|
||||
const uint32_t ethernet_probe_started_at = millis();
|
||||
boot_network.bootstrap(obs->wifi_ssid, obs->wifi_password,
|
||||
NETWORK_ETHERNET_BOOT_WAIT_MS);
|
||||
Serial.printf("Network: selected %s (%s)\n", boot_network.mediumName(),
|
||||
boot_network.statusName());
|
||||
Serial.printf("Network: selected %s (%s) after %lums\n",
|
||||
boot_network.mediumName(), boot_network.statusName(),
|
||||
(unsigned long)(millis() - ethernet_probe_started_at));
|
||||
}
|
||||
|
||||
acl.load(_fs, self_id);
|
||||
|
||||
@@ -500,6 +500,9 @@ GETTERS = {
|
||||
"link.status": lambda c: (
|
||||
"wifi: connected, IP: 192.168.1.42, RSSI: -58 dBm, Uptime: %dm"
|
||||
% (int(time.time() - ST.start) // 60)),
|
||||
"link.diag": lambda c: (
|
||||
"why:ethernet-not-enabled selected:wifi\n"
|
||||
"wifi:state:connected ip:192.168.1.42"),
|
||||
"mqtt.status": lambda c: cli_mqtt_status(c),
|
||||
"mqtt.presets": lambda c: "\n".join(
|
||||
"%2d. %s%s" % (i + 1, n, "" if nd == "none" else " (needs %s)" % nd)
|
||||
|
||||
@@ -1038,6 +1038,8 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
||||
} else {
|
||||
strcpy(reply, _mqtt_prefs.wifi_password[0] ? "> ******** (serial only)" : "> (not set)");
|
||||
}
|
||||
} else if (strcmp(config, "link.diag") == 0) {
|
||||
activeNetworkInterface().formatDiagnostics(reply, 160);
|
||||
} else if (memcmp(config, "link.status", 11) == 0 ||
|
||||
memcmp(config, "wifi.status", 11) == 0) {
|
||||
NetworkInterface& network = activeNetworkInterface();
|
||||
|
||||
@@ -199,12 +199,31 @@ class WiFiNetworkInterface final : public NetworkInterfaceBase {
|
||||
bool resolveHost(const char* hostname, IPAddress& address) const override {
|
||||
return WiFi.hostByName(hostname, address);
|
||||
}
|
||||
void formatDiagnostics(char* reply, size_t reply_size) const override {
|
||||
snprintf(reply, reply_size,
|
||||
"> why:ethernet-not-enabled selected:wifi\n"
|
||||
"wifi:state:%s ip:%s",
|
||||
statusName(),
|
||||
localIP().toString().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(NETWORK_PREFER_ETHERNET)
|
||||
class EthernetNetworkInterface final : public NetworkInterfaceBase {
|
||||
public:
|
||||
enum class EventState : uint8_t {
|
||||
None,
|
||||
Started,
|
||||
LinkDown,
|
||||
LinkUp,
|
||||
GotIp,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
private:
|
||||
bool _started = false;
|
||||
bool _event_registered = false;
|
||||
std::atomic<uint8_t> _event_state{static_cast<uint8_t>(EventState::None)};
|
||||
|
||||
public:
|
||||
const char* mediumName() const override { return "ethernet"; }
|
||||
@@ -220,14 +239,30 @@ class EthernetNetworkInterface final : public NetworkInterfaceBase {
|
||||
if (!_event_registered) {
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
_event_state.store(static_cast<uint8_t>(EventState::Started),
|
||||
std::memory_order_relaxed);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
_event_state.store(static_cast<uint8_t>(EventState::LinkUp),
|
||||
std::memory_order_relaxed);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
_event_state.store(static_cast<uint8_t>(EventState::GotIp),
|
||||
std::memory_order_relaxed);
|
||||
noteConnected(millis());
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
_event_state.store(static_cast<uint8_t>(EventState::LinkDown),
|
||||
std::memory_order_relaxed);
|
||||
// Ethernet has no 802.11 reason code; zero means unavailable.
|
||||
noteDisconnected(millis(), 0);
|
||||
_connected_at.store(0, std::memory_order_relaxed);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
_event_state.store(static_cast<uint8_t>(EventState::Stopped),
|
||||
std::memory_order_relaxed);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -282,6 +317,49 @@ class EthernetNetworkInterface final : public NetworkInterfaceBase {
|
||||
// DNS follows the selected esp_netif even though this entry point is named WiFi.
|
||||
return WiFi.hostByName(hostname, address);
|
||||
}
|
||||
void formatDiagnostics(char* reply, size_t reply_size) const override {
|
||||
snprintf(reply, reply_size, "> ethernet:%s ip=%s",
|
||||
statusName(), localIP().toString().c_str());
|
||||
}
|
||||
|
||||
EventState eventState() const {
|
||||
return static_cast<EventState>(
|
||||
_event_state.load(std::memory_order_relaxed));
|
||||
}
|
||||
bool sampleLink(bool& known) const {
|
||||
known = false;
|
||||
if (!_started) return false;
|
||||
|
||||
// IEEE 802.3 BMSR link status is latch-low. Read it twice so the second
|
||||
// value is the current carrier state rather than a remembered link flap.
|
||||
(void)CH390.readPHY(0x01);
|
||||
const uint32_t bmsr = CH390.readPHY(0x01) & 0xffffu;
|
||||
if (bmsr != 0 && bmsr != 0xffffu) {
|
||||
known = true;
|
||||
return (bmsr & (1u << 2)) != 0;
|
||||
}
|
||||
|
||||
// A failed/unsupported direct PHY read can still use the driver's events.
|
||||
const EventState state = eventState();
|
||||
known = state == EventState::LinkDown || state == EventState::LinkUp ||
|
||||
state == EventState::GotIp || state == EventState::Stopped;
|
||||
return state == EventState::LinkUp || state == EventState::GotIp;
|
||||
}
|
||||
bool linkUp() const {
|
||||
bool known = false;
|
||||
return sampleLink(known);
|
||||
}
|
||||
const char* eventName() const {
|
||||
switch (eventState()) {
|
||||
case EventState::None: return "none";
|
||||
case EventState::Started: return "started";
|
||||
case EventState::LinkDown: return "link-down";
|
||||
case EventState::LinkUp: return "link-up";
|
||||
case EventState::GotIp: return "got-ip";
|
||||
case EventState::Stopped: return "stopped";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
class AutomaticNetworkInterface final : public NetworkInterface {
|
||||
@@ -385,13 +463,9 @@ class AutomaticNetworkInterface final : public NetworkInterface {
|
||||
}
|
||||
|
||||
const uint32_t started_at = millis();
|
||||
const uint32_t link_wait_ms = wait_ms < 1500 ? wait_ms : 1500;
|
||||
while (_ethernet_started && !CH390.linkUp() &&
|
||||
(uint32_t)(millis() - started_at) < link_wait_ms) {
|
||||
delay(25);
|
||||
}
|
||||
while (_ethernet_started && CH390.linkUp() && !_ethernet.isConnected() &&
|
||||
(uint32_t)(millis() - started_at) < wait_ms) {
|
||||
while (NetworkPolicy::ethernetBootProbePending(
|
||||
_ethernet_started, _ethernet.isConnected(),
|
||||
(uint32_t)(millis() - started_at), wait_ms)) {
|
||||
delay(25);
|
||||
}
|
||||
|
||||
@@ -489,6 +563,35 @@ class AutomaticNetworkInterface final : public NetworkInterface {
|
||||
return _selected != NetworkMedium::None &&
|
||||
selectedInterface().resolveHost(hostname, address);
|
||||
}
|
||||
void formatDiagnostics(char* reply, size_t reply_size) const override {
|
||||
const bool ethernet_connected = _ethernet.isConnected();
|
||||
bool ethernet_link_known = false;
|
||||
bool ethernet_link_up = _ethernet.sampleLink(ethernet_link_known);
|
||||
ethernet_link_known = ethernet_link_known || ethernet_connected;
|
||||
ethernet_link_up = ethernet_link_up || ethernet_connected;
|
||||
const bool switching_locked =
|
||||
_switch_locks.load(std::memory_order_relaxed) != 0;
|
||||
const uint32_t now_ms = millis();
|
||||
const uint32_t ethernet_stable_ms = _ethernet_stable_since == 0
|
||||
? 0 : (uint32_t)(now_ms - _ethernet_stable_since);
|
||||
const NetworkDiagnosticReason reason =
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
_ethernet_started, ethernet_link_known, ethernet_link_up,
|
||||
ethernet_connected, _selected, switching_locked,
|
||||
ethernet_stable_ms);
|
||||
snprintf(reply, reply_size,
|
||||
"> why:%s selected:%s lock:%s\n"
|
||||
"eth:init:%s evt:%s link:%s ip:%s\n"
|
||||
"wifi:cfg:%s started:%s link:%s",
|
||||
NetworkPolicy::diagnosticReasonName(reason), mediumName(),
|
||||
switching_locked ? "yes" : "no",
|
||||
_ethernet_started ? "ok" : "failed", _ethernet.eventName(),
|
||||
ethernet_link_known ? (ethernet_link_up ? "up" : "down")
|
||||
: "unknown",
|
||||
_ethernet.localIP().toString().c_str(),
|
||||
wifiConfigured() ? "yes" : "no", _wifi_started ? "yes" : "no",
|
||||
(_wifi_started && _wifi.isConnected()) ? "up" : "down");
|
||||
}
|
||||
unsigned long connectedAtMillis() const override {
|
||||
return _selected == NetworkMedium::None ? 0 : selectedInterface().connectedAtMillis();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class NetworkInterface {
|
||||
virtual IPAddress localIP() const = 0;
|
||||
virtual int rssi() const = 0; // INT_MIN when the selected medium has no RSSI.
|
||||
virtual bool resolveHost(const char* hostname, IPAddress& address) const = 0;
|
||||
virtual void formatDiagnostics(char* reply, size_t reply_size) const = 0;
|
||||
|
||||
virtual unsigned long connectedAtMillis() const = 0;
|
||||
virtual uint8_t lastDisconnectReason() const = 0;
|
||||
|
||||
@@ -15,20 +15,39 @@ enum class NetworkMedium : uint8_t {
|
||||
WiFi,
|
||||
};
|
||||
|
||||
enum class NetworkDiagnosticReason : uint8_t {
|
||||
EthernetActive,
|
||||
EthernetInitFailed,
|
||||
EthernetLinkUnknown,
|
||||
EthernetLinkDown,
|
||||
EthernetAwaitingIp,
|
||||
EthernetStabilizing,
|
||||
SwitchingLocked,
|
||||
EthernetReady,
|
||||
};
|
||||
|
||||
namespace NetworkPolicy {
|
||||
|
||||
struct MQTTTransitionActions {
|
||||
bool disconnect_slots;
|
||||
bool stop_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::Up || 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;
|
||||
@@ -46,6 +65,58 @@ static constexpr uint32_t kEthernetDownGraceMs = 3000;
|
||||
static constexpr uint32_t kEthernetFailbackStableMs = 10000;
|
||||
static constexpr uint32_t kNtpRetryMs = 30000;
|
||||
|
||||
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);
|
||||
@@ -64,6 +135,16 @@ static constexpr NetworkMedium bootSelection(bool ethernet_connected,
|
||||
: NetworkMedium::None;
|
||||
}
|
||||
|
||||
// Once the Ethernet controller has initialized, allow the entire boot probe
|
||||
// window for link negotiation and DHCP. PHY carrier is useful diagnostic data,
|
||||
// but it must not shorten the advertised deadline when carrier reporting lags.
|
||||
static constexpr bool ethernetBootProbePending(bool ethernet_initialized,
|
||||
bool ethernet_connected,
|
||||
uint32_t elapsed_ms,
|
||||
uint32_t wait_ms) {
|
||||
return ethernet_initialized && !ethernet_connected && elapsed_ms < wait_ms;
|
||||
}
|
||||
|
||||
static inline NetworkMedium automaticSelection(
|
||||
const AutomaticSelectionInput& input) {
|
||||
if (input.switching_locked) return input.selected;
|
||||
|
||||
@@ -1307,16 +1307,7 @@ void MQTTBridge::mqttTaskLoop() {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool network_just_connected = handleNetworkConnection(now);
|
||||
if (network_just_connected) {
|
||||
// The uplink recovered — reset last_reconnect_attempt for disconnected slots so they
|
||||
// retry immediately rather than waiting up to 5 min for backoff timers to expire.
|
||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||
if (_slots[i].enabled && _slots[i].initial_connect_done && !_slots[i].connected) {
|
||||
_slots[i].last_reconnect_attempt = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
handleNetworkConnection(now);
|
||||
|
||||
// Schedule once per link-up edge. Failed syncs are owned by the 30-second
|
||||
// retry below; re-arming here on every loop would make a blocked NTP path
|
||||
@@ -2745,19 +2736,58 @@ void MQTTBridge::checkConfigurationMismatch() {
|
||||
}
|
||||
|
||||
bool MQTTBridge::handleNetworkConnection(unsigned long now) {
|
||||
const NetworkMedium previous_medium = _network->medium();
|
||||
const NetworkTransition transition =
|
||||
_network->maintain((uint32_t)now, _obs->wifi_power_save);
|
||||
const NetworkMedium selected_medium = _network->medium();
|
||||
|
||||
if (transition == NetworkTransition::Down) {
|
||||
MQTT_DEBUG_PRINTLN("Network: %s link down", NetworkPolicy::mediumName(previous_medium));
|
||||
} else if (transition == NetworkTransition::Up) {
|
||||
MQTT_DEBUG_PRINTLN("Network: %s link up (%s, IP %s)",
|
||||
NetworkPolicy::mediumName(selected_medium),
|
||||
_network->statusName(), _network->localIP().toString().c_str());
|
||||
} else if (transition == NetworkTransition::Switched) {
|
||||
MQTT_DEBUG_PRINTLN("Network: switched %s -> %s (%s, IP %s)",
|
||||
NetworkPolicy::mediumName(previous_medium),
|
||||
NetworkPolicy::mediumName(selected_medium),
|
||||
_network->statusName(), _network->localIP().toString().c_str());
|
||||
}
|
||||
|
||||
const NetworkPolicy::MQTTTransitionActions actions =
|
||||
NetworkPolicy::mqttActions(transition);
|
||||
if (actions.disconnect_slots) {
|
||||
if (actions.stop_started_slots) {
|
||||
// Broker ownership stays in the bridge. The physical adapter reports the
|
||||
// edge; the bridge explicitly closes every slot instead of waiting for
|
||||
// eventual socket timeouts.
|
||||
// edge; the bridge explicitly stops every started slot instead of waiting
|
||||
// for eventual socket timeouts. Do not gate this on slot.connected: the
|
||||
// ESP-MQTT disconnect callback can clear that flag before the network edge
|
||||
// reaches this task, but its client task and transport can still be alive.
|
||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||
if (_slots[i].client && _slots[i].connected) {
|
||||
if (_slots[i].client && _slots[i].client->isStarted()) {
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d stopping for network transition", i + 1);
|
||||
_slots[i].client->disconnect();
|
||||
}
|
||||
_slots[i].connected = false;
|
||||
_slots[i].connected_at_ms = 0;
|
||||
}
|
||||
updateCachedConnectionStatus();
|
||||
}
|
||||
|
||||
if (actions.reset_reconnect_backoff) {
|
||||
// A usable route is a new connection epoch. Failures earned on the old
|
||||
// route must not strand the replacement route on the 5-minute rung or at
|
||||
// the circuit breaker. Preserve JWTs and slot configuration, but give each
|
||||
// disconnected active slot one immediate, freshly guarded attempt.
|
||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||
if (_slots[i].enabled && _slots[i].initial_connect_done && !_slots[i].connected) {
|
||||
_slots[i].reconnect_backoff = 0;
|
||||
_slots[i].max_backoff_failures = 0;
|
||||
_slots[i].circuit_breaker_tripped = false;
|
||||
_slots[i].last_reconnect_attempt =
|
||||
now - MQTTConnectionPolicy::reconnectDelayMs(0, static_cast<uint8_t>(i));
|
||||
}
|
||||
}
|
||||
_last_slot_reconnect_ms = now - MQTTConnectionPolicy::kReconnectGuardMs;
|
||||
}
|
||||
return actions.retry_disconnected_slots_now;
|
||||
}
|
||||
|
||||
@@ -4,26 +4,36 @@
|
||||
|
||||
TEST(NetworkPolicy, MqttDownDisconnectsSlotsWithoutRequestingImmediateRetry) {
|
||||
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Down);
|
||||
EXPECT_TRUE(actions.disconnect_slots);
|
||||
EXPECT_TRUE(actions.stop_started_slots);
|
||||
EXPECT_FALSE(actions.retry_disconnected_slots_now);
|
||||
EXPECT_FALSE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, MqttUpRequestsImmediateRetryWithoutDisconnectingSlots) {
|
||||
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Up);
|
||||
EXPECT_FALSE(actions.disconnect_slots);
|
||||
EXPECT_FALSE(actions.stop_started_slots);
|
||||
EXPECT_TRUE(actions.retry_disconnected_slots_now);
|
||||
EXPECT_TRUE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, NoTransitionHasNoMqttSideEffects) {
|
||||
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::None);
|
||||
EXPECT_FALSE(actions.disconnect_slots);
|
||||
EXPECT_FALSE(actions.stop_started_slots);
|
||||
EXPECT_FALSE(actions.retry_disconnected_slots_now);
|
||||
EXPECT_FALSE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, LinkSwitchReconnectsMqttSlotsImmediately) {
|
||||
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Switched);
|
||||
EXPECT_TRUE(actions.disconnect_slots);
|
||||
EXPECT_TRUE(actions.stop_started_slots);
|
||||
EXPECT_TRUE(actions.retry_disconnected_slots_now);
|
||||
EXPECT_TRUE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, MediumNamesAreStableForTransitionLogs) {
|
||||
EXPECT_STREQ("none", NetworkPolicy::mediumName(NetworkMedium::None));
|
||||
EXPECT_STREQ("ethernet", NetworkPolicy::mediumName(NetworkMedium::Ethernet));
|
||||
EXPECT_STREQ("wifi", NetworkPolicy::mediumName(NetworkMedium::WiFi));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, BootPrefersEthernetAndOtherwiseUsesConfiguredWifi) {
|
||||
@@ -32,6 +42,15 @@ TEST(NetworkPolicy, BootPrefersEthernetAndOtherwiseUsesConfiguredWifi) {
|
||||
EXPECT_EQ(NetworkMedium::None, NetworkPolicy::bootSelection(false, false));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, EthernetBootProbeHonorsFullDeadlineUntilConnected) {
|
||||
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(true, false, 0, 8000));
|
||||
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(true, false, 1500, 8000));
|
||||
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(true, false, 7999, 8000));
|
||||
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(true, false, 8000, 8000));
|
||||
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(true, true, 100, 8000));
|
||||
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(false, false, 100, 8000));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, EthernetFailureWaitsForGraceAndConnectedWifi) {
|
||||
NetworkPolicy::AutomaticSelectionInput input = {
|
||||
NetworkMedium::Ethernet, false, true, true, false, 0,
|
||||
@@ -57,6 +76,41 @@ TEST(NetworkPolicy, SwitchingLockPinsTheCurrentMedium) {
|
||||
EXPECT_EQ(NetworkMedium::WiFi, NetworkPolicy::automaticSelection(input));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, DiagnosticsIdentifyEthernetFallbackCause) {
|
||||
using Reason = NetworkDiagnosticReason;
|
||||
EXPECT_EQ(Reason::EthernetInitFailed,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
false, false, false, false, NetworkMedium::WiFi, false, 0));
|
||||
EXPECT_EQ(Reason::EthernetLinkUnknown,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, false, false, false, NetworkMedium::WiFi, false, 0));
|
||||
EXPECT_EQ(Reason::EthernetLinkDown,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, false, false, NetworkMedium::WiFi, false, 0));
|
||||
EXPECT_EQ(Reason::EthernetAwaitingIp,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, true, false, NetworkMedium::WiFi, false, 0));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, DiagnosticsExplainWhyReadyEthernetHasNotBeenSelected) {
|
||||
using Reason = NetworkDiagnosticReason;
|
||||
EXPECT_EQ(Reason::SwitchingLocked,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, true, true, NetworkMedium::WiFi, true,
|
||||
NetworkPolicy::kEthernetFailbackStableMs));
|
||||
EXPECT_EQ(Reason::EthernetStabilizing,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, true, true, NetworkMedium::WiFi, false,
|
||||
NetworkPolicy::kEthernetFailbackStableMs - 1));
|
||||
EXPECT_EQ(Reason::EthernetReady,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, true, true, NetworkMedium::WiFi, false,
|
||||
NetworkPolicy::kEthernetFailbackStableMs));
|
||||
EXPECT_EQ(Reason::EthernetActive,
|
||||
NetworkPolicy::automaticDiagnosticReason(
|
||||
true, true, true, true, NetworkMedium::Ethernet, false, 0));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, NtpIsScheduledOncePerConnectivityEdge) {
|
||||
EXPECT_TRUE(NetworkPolicy::ntpPendingAfterConnectivitySample(
|
||||
false, false, false, true));
|
||||
|
||||
@@ -1547,6 +1547,7 @@ var CLI_KEYS=[
|
||||
["wifi.pwd","WiFi password",0],
|
||||
["wifi.powersave","WiFi power-save mode",0,"none|min|max"],
|
||||
["link.status","Selected network, IP, signal and uptime",1],
|
||||
["link.diag","Ethernet selection and fallback diagnostics",1],
|
||||
["wifi.status","WiFi connection, IP, RSSI and uptime",1],
|
||||
["mqtt.origin","Observer name in published messages",0],
|
||||
["mqtt.iata","IATA region code used in topic paths",0],
|
||||
|
||||
Reference in New Issue
Block a user