#include "mesh/eth/ethClient.h" #include "NodeDB.h" #include "concurrency/Periodic.h" #include "configuration.h" #include "gps/RTC.h" #include "main.h" #include "mesh/api/ethServerAPI.h" #include "target_specific.h" #if HAS_ETHERNET && defined(HAS_ETHERNET_OTA) #include "mesh/eth/ethOTA.h" #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_API) #include "mesh/eth/ethApiServer.h" #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_TLS_API) && defined(ARCH_RP2040) #include "mesh/eth/ethCert.h" #include "mesh/eth/ethTlsApiServer.h" #endif #ifdef USE_ARDUINO_ETHERNET #include // arduino-libraries/Ethernet - supports W5100/W5200/W5500 // Shorter DHCP timeout so LoRa startup isn't blocked when no DHCP server is present. #define ETH_DHCP_TIMEOUT_MS 10000 #else #include #endif #include #if HAS_NETWORKING && !defined(USE_WS5500) && !defined(USE_CH390D) #ifndef DISABLE_NTP #include // NTP EthernetUDP ntpUDP; NTPClient timeClient(ntpUDP, config.network.ntp_server); uint32_t ntp_renew = 0; #endif EthernetUDP syslogClient; meshtastic::Syslog syslog(syslogClient); bool ethStartupComplete = 0; using namespace concurrency; 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 #if HAS_ETHERNET && defined(HAS_ETHERNET_API) // Drop the HTTP/80 listener; the restart path below rebinds it. Without // this the singleton guard makes initEthApiServer() a no-op and the // phone API stays dead on a stale socket until reboot. deInitEthApiServer(); #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_TLS_API) && defined(ARCH_RP2040) // Same for HTTPS/443 + its mbedTLS context. deInitEthTlsApiServer(); #endif #if HAS_UDP_MULTICAST if (udpHandler) { udpHandler->stop(); } #endif ethStartupComplete = false; #ifndef DISABLE_NTP ntp_renew = 0; #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 USE_ARDUINO_ETHERNET // Re-configure SPI0 for the W5500 module SPI.setRX(ETH_SPI0_MISO); SPI.setSCK(ETH_SPI0_SCK); SPI.setTX(ETH_SPI0_MOSI); SPI.begin(); Ethernet.init(PIN_ETHERNET_SS); #else #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); #endif int status = 0; if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) { #ifdef ETH_DHCP_TIMEOUT_MS status = Ethernet.begin(expectedMac, ETH_DHCP_TIMEOUT_MS); #else status = Ethernet.begin(expectedMac); #endif } 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]); } Ethernet.maintain(); if (!ethStartupComplete) { // Start web server LOG_INFO("Start Ethernet network services"); #ifndef DISABLE_NTP LOG_INFO("Start NTP time client"); timeClient.setUpdateInterval(60 * 60); // Update once an hour #endif if (config.network.rsyslog_server[0]) { LOG_INFO("Start Syslog client"); // Defaults int serverPort = 514; const char *serverAddr = config.network.rsyslog_server; String server = String(serverAddr); int delimIndex = server.indexOf(':'); if (delimIndex > 0) { String port = server.substring(delimIndex + 1, server.length()); server[delimIndex] = 0; serverPort = port.toInt(); serverAddr = server.c_str(); } syslog.server(serverAddr, serverPort); syslog.deviceHostname(getDeviceName()); syslog.appName("Meshtastic"); syslog.defaultPriority(LOGLEVEL_USER); syslog.enable(); } #if !MESHTASTIC_EXCLUDE_SOCKETAPI if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { initApiServer(); } #endif #if HAS_UDP_MULTICAST if (udpHandler && config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) { udpHandler->start(); } #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_OTA) initEthOTA(); #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_API) initEthApiServer(); #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_TLS_API) && defined(ARCH_RP2040) // Phase 2.1-bis - cert gen runs on its own OSThread so ECDSA keygen // + DER encoding + LittleFS write don't share the Periodic stack // (which overflowed in the original inline attempt). The thread // polls for a non-zero IP itself and runs once. initEthCertThread(); // Phase 2.2 - TLS server skeleton on TCP/443. The worker waits // until the cert thread signals isEthCertReady() before binding. initEthTlsApiServer(); #endif ethStartupComplete = true; } } #ifndef DISABLE_NTP if (isEthernetAvailable() && (ntp_renew < millis())) { LOG_INFO("Update NTP time from %s", config.network.ntp_server); if (timeClient.update()) { LOG_DEBUG("NTP Request Success - Set RTCQualityNTP if needed"); struct timeval tv; tv.tv_sec = timeClient.getEpochTime(); tv.tv_usec = 0; perhapsSetRTC(RTCQualityNTP, &tv); ntp_renew = millis() + 43200 * 1000; // success, refresh every 12 hours } else { LOG_ERROR("NTP Update failed"); ntp_renew = millis() + 300 * 1000; // failure, retry every 5 minutes } timeClient.end(); // W5100S: release UDP socket for other services } #endif #if HAS_ETHERNET && defined(HAS_ETHERNET_OTA) ethOTALoop(); #endif // ethApiServer runs on its own OSThread (20ms ticks) - not polled here. return 5000; // every 5 seconds } // Startup Ethernet bool initEthernet() { if (config.network.eth_enabled) { #ifdef PIN_ETH_POWER_EN pinMode(PIN_ETH_POWER_EN, OUTPUT); digitalWrite(PIN_ETH_POWER_EN, HIGH); // Power up. delay(100); #endif #ifdef PIN_ETHERNET_RESET pinMode(PIN_ETHERNET_RESET, OUTPUT); digitalWrite(PIN_ETHERNET_RESET, LOW); // Reset Time. delay(100); digitalWrite(PIN_ETHERNET_RESET, HIGH); // Reset Time. #endif #ifdef USE_ARDUINO_ETHERNET // Configure SPI0 for the W5500 module SPI.setRX(ETH_SPI0_MISO); SPI.setSCK(ETH_SPI0_SCK); SPI.setTX(ETH_SPI0_MOSI); SPI.begin(); Ethernet.init(PIN_ETHERNET_SS); #else #ifdef RAK11310 // Initialize the SPI port 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); #endif uint8_t mac[6]; int status = 0; // createSSLCert(); getMacAddr(mac); // FIXME use the BLE MAC for now... mac[0] &= 0xfe; // Make sure this is not a multicast MAC if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) { LOG_INFO("Start Ethernet DHCP"); #ifdef ETH_DHCP_TIMEOUT_MS status = Ethernet.begin(mac, ETH_DHCP_TIMEOUT_MS); #else status = Ethernet.begin(mac); #endif } else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) { LOG_INFO("Start Ethernet Static"); Ethernet.begin(mac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.gateway, config.network.ipv4_config.subnet); status = 1; } else { LOG_INFO("Ethernet Disabled"); return false; } if (status == 0) { if (Ethernet.hardwareStatus() == EthernetNoHardware) { LOG_ERROR("Ethernet shield was not found"); return false; } else if (Ethernet.linkStatus() == LinkOFF) { LOG_ERROR("Ethernet cable is not connected"); return false; } else { LOG_ERROR("Unknown Ethernet error"); return false; } } else { LOG_INFO("Local IP %u.%u.%u.%u", Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]); LOG_INFO("Subnet Mask %u.%u.%u.%u", Ethernet.subnetMask()[0], Ethernet.subnetMask()[1], Ethernet.subnetMask()[2], Ethernet.subnetMask()[3]); LOG_INFO("Gateway IP %u.%u.%u.%u", Ethernet.gatewayIP()[0], Ethernet.gatewayIP()[1], Ethernet.gatewayIP()[2], Ethernet.gatewayIP()[3]); LOG_INFO("DNS Server IP %u.%u.%u.%u", Ethernet.dnsServerIP()[0], Ethernet.dnsServerIP()[1], Ethernet.dnsServerIP()[2], Ethernet.dnsServerIP()[3]); } ethEvent = new Periodic("ethConnect", reconnectETH); return true; } else { LOG_INFO("Not using Ethernet"); return false; } } bool isEthernetAvailable() { if (!config.network.eth_enabled) { syslog.disable(); return false; } else if (Ethernet.hardwareStatus() == EthernetNoHardware) { syslog.disable(); return false; } else if (Ethernet.linkStatus() == LinkOFF) { syslog.disable(); return false; } else { return true; } } #endif