From 4dc6c8d83e4b934529ba4c8384ab78ed1da54d91 Mon Sep 17 00:00:00 2001 From: 1sthandy Date: Mon, 8 Jun 2026 21:09:00 +0200 Subject: [PATCH] Default the Ethernet companion to DHCP with static fallback DHCP is now the default for the Ethernet companion, addressing the need for DHCP in managed/enterprise networks. It is done PoE-safely: - deferred to loop() after the converter is latched (~6 s), so the blocking DHCP exchange can't collapse the supply at cold start; - bounded timeout (12 s lease / 4 s response) instead of the 60 s default; - falls back to the static IP if no DHCP server answers, so the node is always reachable and never reboot-loops; - Ethernet.maintain() renews the lease. Use a DHCP reservation on the router for a stable address. Add -D ETH_STATIC_ONLY to opt out of DHCP and use the static IP directly. Tested on RAK4631 + RAK13800 + RAK19018: repeated PoE-only cold starts come up reliably with the DHCP-assigned address. Co-Authored-By: Claude Opus 4.8 --- examples/companion_radio/main.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 932f01f2..8a9ecec6 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -84,8 +84,9 @@ static uint32_t _atoi(const char* sp) { #ifndef TCP_PORT #define TCP_PORT 5000 #endif - // Static IP (no DHCP): predictable address for Home Assistant AND avoids - // the multi-second blocking DHCP that reboot-loops the device on PoE. + // Fallback static IP, used only if DHCP fails (or if ETH_STATIC_ONLY is + // set). DHCP is the default and is done deferred, after the PoE supply is + // latched, so it no longer reboot-loops the device on cold start. // Override per network if needed (octets are comma-separated). #ifndef ETH_STATIC_IP #define ETH_STATIC_IP 192,168,1,50 @@ -376,11 +377,30 @@ void loop() { // collapse the marginal PoE supply if done during setup() (reboot loop). static bool _eth_up = false; if (!_eth_up && millis() > 6000) { +#if defined(ETH_STATIC_ONLY) + // Static-only (opt-out of DHCP via -D ETH_STATIC_ONLY). IPAddress sip(ETH_STATIC_IP), sgw(ETH_GATEWAY), ssn(ETH_SUBNET); Ethernet.begin(g_eth_mac, sip, sgw, sgw, ssn); // inits chip mode/sockets (PHY soft-reset) serial_interface.begin(TCP_PORT); // start TCP server delay(50); eth_write_netcfg(g_eth_mac); // force IP/GW/SN/MAC (reliable here) +#else + // Default: DHCP, but only HERE (deferred) where the PoE supply is already + // latched, so the blocking DHCP exchange can't collapse the converter at + // cold start. Bounded timeout; fall back to the static IP if no DHCP server + // answers, so the node is always reachable and never reboot-loops. Use a + // DHCP reservation on the router for a stable address. + Serial.println("Ethernet: trying DHCP (deferred)..."); + int dhcp_ok = Ethernet.begin(g_eth_mac, 12000, 4000); // 12s lease, 4s resp + if (!dhcp_ok) { + IPAddress sip(ETH_STATIC_IP), sgw(ETH_GATEWAY), ssn(ETH_SUBNET); + Ethernet.begin(g_eth_mac, sip, sgw, sgw, ssn); + delay(50); + eth_write_netcfg(g_eth_mac); // force static into W5100S regs + Serial.println("Ethernet: DHCP failed -> static IP fallback"); + } + serial_interface.begin(TCP_PORT); // start TCP server +#endif _eth_up = true; IPAddress ip = Ethernet.localIP(); Serial.print("Ethernet up (deferred): "); @@ -388,5 +408,10 @@ void loop() { Serial.print(ip[2]); Serial.print('.'); Serial.print(ip[3]); Serial.print(":"); Serial.println(TCP_PORT); } +#if !defined(ETH_STATIC_ONLY) + else if (_eth_up) { + Ethernet.maintain(); // renew the DHCP lease in the background + } +#endif #endif }