From 1c7bc1b022151334192638efde2146cf32c87c52 Mon Sep 17 00:00:00 2001 From: Philip Lykov Date: Thu, 26 Feb 2026 14:53:42 +0200 Subject: [PATCH 1/3] Fix RAK4631 Ethernet gateway API connection loss after W5100S brownout PoE power instability can brownout the W5100S while the nRF52 MCU keeps running, causing all chip registers (MAC, IP, sockets) to revert to defaults. The firmware had no mechanism to detect or recover from this. Changes: - Detect W5100S chip reset by periodically verifying MAC address register in reconnectETH(); on mismatch, perform full hardware reset and re-initialize Ethernet interface and services - Add deInitApiServer() for clean API server teardown during recovery - Add ~APIServerPort destructor to prevent memory leaks - Switch nRF52 from EthernetServer::available() to accept() to prevent the same connected client from being repeatedly re-reported - Add proactive dead-connection cleanup in APIServerPort::runOnce() - Add 15-minute TCP idle timeout to close half-open connections that consume limited W5100S hardware sockets Fixes meshtastic/firmware#6970 Made-with: Cursor --- src/mesh/api/ServerAPI.cpp | 17 +++++++++- src/mesh/api/ServerAPI.h | 3 ++ src/mesh/api/ethServerAPI.cpp | 9 ++++++ src/mesh/api/ethServerAPI.h | 1 + src/mesh/eth/ethClient.cpp | 58 +++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 1a506421c8d..d8f079a4119 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -1,7 +1,10 @@ #include "ServerAPI.h" +#include "Throttle.h" #include "configuration.h" #include +#define TCP_IDLE_TIMEOUT_MS (15 * 60 * 1000UL) + template ServerAPI::ServerAPI(T &_client) : StreamAPI(&client), concurrency::OSThread("ServerAPI"), client(_client) { @@ -28,6 +31,12 @@ template bool ServerAPI::checkIsConnected() template int32_t ServerAPI::runOnce() { if (client.connected()) { + if (lastContactMsec > 0 && !Throttle::isWithinTimespanMs(lastContactMsec, TCP_IDLE_TIMEOUT_MS)) { + LOG_WARN("TCP connection timeout, no data for %lu ms", (unsigned long)TCP_IDLE_TIMEOUT_MS); + close(); + enabled = false; + return 0; + } return StreamAPI::runOncePart(); } else { LOG_INFO("Client dropped connection, suspend API service"); @@ -45,13 +54,19 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { + if (openAPI && !openAPI->isClientConnected()) { + LOG_INFO("Cleaning up disconnected TCP API client"); + delete openAPI; + openAPI = nullptr; + } + #ifdef ARCH_ESP32 #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) auto client = U::accept(); #else auto client = U::available(); #endif -#elif defined(ARCH_RP2040) +#elif defined(ARCH_RP2040) || defined(ARCH_NRF52) auto client = U::accept(); #else auto client = U::available(); diff --git a/src/mesh/api/ServerAPI.h b/src/mesh/api/ServerAPI.h index 111314476b0..3f8c5b726df 100644 --- a/src/mesh/api/ServerAPI.h +++ b/src/mesh/api/ServerAPI.h @@ -21,6 +21,8 @@ template class ServerAPI : public StreamAPI, private concurrency::OSTh /// override close to also shutdown the TCP link virtual void close(); + bool isClientConnected() { return client.connected(); } + protected: /// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to /// stay in the POWERED state to prevent disabling wifi) @@ -50,6 +52,7 @@ template class APIServerPort : public U, private concurrency: public: explicit APIServerPort(int port); + ~APIServerPort() { delete openAPI; } void init(); diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp index 10ff06df23a..43ed74cf811 100644 --- a/src/mesh/api/ethServerAPI.cpp +++ b/src/mesh/api/ethServerAPI.cpp @@ -17,6 +17,15 @@ void initApiServer(int port) } } +void deInitApiServer() +{ + if (apiPort) { + LOG_INFO("Deinit API server"); + delete apiPort; + apiPort = nullptr; + } +} + ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client) { LOG_INFO("Incoming ethernet connection"); diff --git a/src/mesh/api/ethServerAPI.h b/src/mesh/api/ethServerAPI.h index c616c87be7d..8f81ee6ffff 100644 --- a/src/mesh/api/ethServerAPI.h +++ b/src/mesh/api/ethServerAPI.h @@ -24,4 +24,5 @@ class ethServerPort : public APIServerPort }; void initApiServer(int port = SERVER_API_DEFAULT_PORT); +void deInitApiServer(); #endif diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index a811ec16cd5..e823bb46d19 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -32,6 +32,64 @@ static Periodic *ethEvent; static int32_t reconnectETH() { if (config.network.eth_enabled) { + + // Detect W5100S chip reset by verifying the MAC address register. + // PoE power instability can brownout the W5100S while the MCU keeps running, + // causing all chip registers (MAC, IP, sockets) to revert to defaults. + uint8_t currentMac[6]; + Ethernet.MACAddress(currentMac); + + uint8_t expectedMac[6]; + getMacAddr(expectedMac); + expectedMac[0] &= 0xfe; + + if (memcmp(currentMac, expectedMac, 6) != 0) { + LOG_WARN("W5100S MAC mismatch (chip reset detected), reinitializing Ethernet"); + + syslog.disable(); +#if !MESHTASTIC_EXCLUDE_SOCKETAPI + deInitApiServer(); +#endif + +#ifdef PIN_ETHERNET_RESET + pinMode(PIN_ETHERNET_RESET, OUTPUT); + digitalWrite(PIN_ETHERNET_RESET, LOW); + delay(100); + digitalWrite(PIN_ETHERNET_RESET, HIGH); + delay(100); +#endif + +#ifdef RAK11310 + ETH_SPI_PORT.setSCK(PIN_SPI0_SCK); + ETH_SPI_PORT.setTX(PIN_SPI0_MOSI); + ETH_SPI_PORT.setRX(PIN_SPI0_MISO); + ETH_SPI_PORT.begin(); +#endif + Ethernet.init(ETH_SPI_PORT, PIN_ETHERNET_SS); + + int status = 0; + if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) { + status = Ethernet.begin(expectedMac); + } else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) { + Ethernet.begin(expectedMac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, + config.network.ipv4_config.gateway, config.network.ipv4_config.subnet); + status = 1; + } + + if (status == 0) { + LOG_ERROR("Ethernet re-initialization failed, will retry"); + return 5000; + } + + LOG_INFO("Ethernet reinitialized - IP %u.%u.%u.%u", Ethernet.localIP()[0], Ethernet.localIP()[1], + Ethernet.localIP()[2], Ethernet.localIP()[3]); + + ethStartupComplete = false; +#ifndef DISABLE_NTP + ntp_renew = 0; +#endif + } + Ethernet.maintain(); if (!ethStartupComplete) { // Start web server From 477276c806502abfb5351cbe6ce4f50c443d59b1 Mon Sep 17 00:00:00 2001 From: Philip Lykov Date: Thu, 26 Feb 2026 15:49:27 +0200 Subject: [PATCH 2/3] Log actual elapsed idle time instead of constant timeout value Address Copilot review comment: log millis() - lastContactMsec to show the real time since last client activity, rather than always logging the TCP_IDLE_TIMEOUT_MS constant. Made-with: Cursor --- src/mesh/api/ServerAPI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index d8f079a4119..4c2913ca138 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -32,7 +32,7 @@ template int32_t ServerAPI::runOnce() { if (client.connected()) { if (lastContactMsec > 0 && !Throttle::isWithinTimespanMs(lastContactMsec, TCP_IDLE_TIMEOUT_MS)) { - LOG_WARN("TCP connection timeout, no data for %lu ms", (unsigned long)TCP_IDLE_TIMEOUT_MS); + LOG_WARN("TCP connection timeout, no data for %lu ms", (unsigned long)(millis() - lastContactMsec)); close(); enabled = false; return 0; From 1a7439dbb768434e782674728a0cb1a4f83107ee Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 27 Feb 2026 05:34:58 -0600 Subject: [PATCH 3/3] Update src/mesh/api/ServerAPI.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mesh/api/ServerAPI.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mesh/api/ServerAPI.h b/src/mesh/api/ServerAPI.h index 3f8c5b726df..e833b0aa834 100644 --- a/src/mesh/api/ServerAPI.h +++ b/src/mesh/api/ServerAPI.h @@ -52,7 +52,14 @@ template class APIServerPort : public U, private concurrency: public: explicit APIServerPort(int port); - ~APIServerPort() { delete openAPI; } + ~APIServerPort() + { + if (openAPI != nullptr) + { + delete openAPI; + openAPI = nullptr; + } + } void init();