Skip to content

Commit f04746a

Browse files
PhilipLykovthebenternCopilot
authored
Fix RAK4631 Ethernet gateway API connection loss after W5100S brownout (#9754)
* 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 #6970 Made-with: Cursor * 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 * Update src/mesh/api/ServerAPI.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Stop UDP multicast handler during W5100S brownout recovery After a W5100S chip brownout, the udpHandler isRunning flag stays true while the underlying socket is dead. Without calling stop(), the subsequent start() no-ops and multicast is silently broken after recovery. Made-with: Cursor * Address Copilot review: recovery flags and timeout constant Move ethStartupComplete and ntp_renew reset to immediately after service teardown, before Ethernet.begin(). Previously, if DHCP failed the early return left ethStartupComplete=true, preventing service re-initialization on subsequent retries. Replace #define TCP_IDLE_TIMEOUT_MS with static constexpr uint32_t for type safety and better C++ practice. Made-with: Cursor --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1be2529 commit f04746a

File tree

4 files changed

+83
-1
lines changed

4 files changed

+83
-1
lines changed

src/mesh/api/ServerAPI.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include "ServerAPI.h"
2+
#include "Throttle.h"
23
#include "configuration.h"
34
#include <Arduino.h>
45

6+
static constexpr uint32_t TCP_IDLE_TIMEOUT_MS = 15 * 60 * 1000UL;
7+
58
template <typename T>
69
ServerAPI<T>::ServerAPI(T &_client) : StreamAPI(&client), concurrency::OSThread("ServerAPI"), client(_client)
710
{
@@ -28,6 +31,12 @@ template <typename T> bool ServerAPI<T>::checkIsConnected()
2831
template <class T> int32_t ServerAPI<T>::runOnce()
2932
{
3033
if (client.connected()) {
34+
if (lastContactMsec > 0 && !Throttle::isWithinTimespanMs(lastContactMsec, TCP_IDLE_TIMEOUT_MS)) {
35+
LOG_WARN("TCP connection timeout, no data for %lu ms", (unsigned long)(millis() - lastContactMsec));
36+
close();
37+
enabled = false;
38+
return 0;
39+
}
3140
return StreamAPI::runOncePart();
3241
} else {
3342
LOG_INFO("Client dropped connection, suspend API service");
@@ -57,7 +66,7 @@ template <class T, class U> int32_t APIServerPort<T, U>::runOnce()
5766
#else
5867
auto client = U::available();
5968
#endif
60-
#elif defined(ARCH_RP2040)
69+
#elif defined(ARCH_RP2040) || defined(ARCH_NRF52)
6170
auto client = U::accept();
6271
#else
6372
auto client = U::available();

src/mesh/api/ethServerAPI.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ void initApiServer(int port)
1717
}
1818
}
1919

20+
void deInitApiServer()
21+
{
22+
if (apiPort) {
23+
LOG_INFO("Deinit API server");
24+
delete apiPort;
25+
apiPort = nullptr;
26+
}
27+
}
28+
2029
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
2130
{
2231
LOG_INFO("Incoming ethernet connection");

src/mesh/api/ethServerAPI.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ class ethServerPort : public APIServerPort<ethServerAPI, EthernetServer>
2424
};
2525

2626
void initApiServer(int port = SERVER_API_DEFAULT_PORT);
27+
void deInitApiServer();
2728
#endif

src/mesh/eth/ethClient.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,69 @@ static Periodic *ethEvent;
3232
static int32_t reconnectETH()
3333
{
3434
if (config.network.eth_enabled) {
35+
36+
// Detect W5100S chip reset by verifying the MAC address register.
37+
// PoE power instability can brownout the W5100S while the MCU keeps running,
38+
// causing all chip registers (MAC, IP, sockets) to revert to defaults.
39+
uint8_t currentMac[6];
40+
Ethernet.MACAddress(currentMac);
41+
42+
uint8_t expectedMac[6];
43+
getMacAddr(expectedMac);
44+
expectedMac[0] &= 0xfe;
45+
46+
if (memcmp(currentMac, expectedMac, 6) != 0) {
47+
LOG_WARN("W5100S MAC mismatch (chip reset detected), reinitializing Ethernet");
48+
49+
syslog.disable();
50+
#if !MESHTASTIC_EXCLUDE_SOCKETAPI
51+
deInitApiServer();
52+
#endif
53+
#if HAS_UDP_MULTICAST
54+
if (udpHandler) {
55+
udpHandler->stop();
56+
}
57+
#endif
58+
59+
ethStartupComplete = false;
60+
#ifndef DISABLE_NTP
61+
ntp_renew = 0;
62+
#endif
63+
64+
#ifdef PIN_ETHERNET_RESET
65+
pinMode(PIN_ETHERNET_RESET, OUTPUT);
66+
digitalWrite(PIN_ETHERNET_RESET, LOW);
67+
delay(100);
68+
digitalWrite(PIN_ETHERNET_RESET, HIGH);
69+
delay(100);
70+
#endif
71+
72+
#ifdef RAK11310
73+
ETH_SPI_PORT.setSCK(PIN_SPI0_SCK);
74+
ETH_SPI_PORT.setTX(PIN_SPI0_MOSI);
75+
ETH_SPI_PORT.setRX(PIN_SPI0_MISO);
76+
ETH_SPI_PORT.begin();
77+
#endif
78+
Ethernet.init(ETH_SPI_PORT, PIN_ETHERNET_SS);
79+
80+
int status = 0;
81+
if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) {
82+
status = Ethernet.begin(expectedMac);
83+
} else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) {
84+
Ethernet.begin(expectedMac, config.network.ipv4_config.ip, config.network.ipv4_config.dns,
85+
config.network.ipv4_config.gateway, config.network.ipv4_config.subnet);
86+
status = 1;
87+
}
88+
89+
if (status == 0) {
90+
LOG_ERROR("Ethernet re-initialization failed, will retry");
91+
return 5000;
92+
}
93+
94+
LOG_INFO("Ethernet reinitialized - IP %u.%u.%u.%u", Ethernet.localIP()[0], Ethernet.localIP()[1],
95+
Ethernet.localIP()[2], Ethernet.localIP()[3]);
96+
}
97+
3598
Ethernet.maintain();
3699
if (!ethStartupComplete) {
37100
// Start web server

0 commit comments

Comments
 (0)