* stm32wl: add hardware RTC support infrastructure Wires the STM32WL chip's internal RTC (running off the LSE 32.768kHz crystal) into meshtastic's existing time-of-day framework (perhapsSetRTC()/readFromRTC()), following the same pattern already used for I2C RTC chips (RV3028, PCF8563/85063, RX8130CE). LSE is started and polled manually before ever calling into the STM32RTC library, with our own bounded timeout - the library's own internal LSE startup path has no bounded fallback and hangs forever via Error_Handler() if the crystal never locks, so this is required for a board with a missing/faulty crystal to boot normally rather than hang. Gated behind a new HAS_LSE variant flag (currently unset everywhere, so this is inert until a variant opts in - see follow-up commit). Signed-off-by: Andrew Yong <me@ndoo.sg> Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> * gps: qualify RTC.h includes to avoid case-insensitive filesystem collision with STM32RTC The stm32duino STM32RTC library (added to lib_deps in a follow-up commit) ships its own src/rtc.h. On case-insensitive filesystems (the macOS default), an unqualified #include "RTC.h"/<RTC.h> from any file outside src/gps/ resolves to the library's rtc.h instead of src/gps/RTC.h, since PlatformIO's LDF puts lib_deps include paths ahead of the project's own -Isrc/gps. Qualify every include as gps/RTC.h so it can't collide with any same-named header a future dependency might ship, regardless of filesystem case sensitivity. Purely mechanical, no behavior change. Signed-off-by: Andrew Yong <me@ndoo.sg> Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> * stm32wl(rak3172): enable hardware RTC support Opts rak3172 into the HAS_LSE infrastructure added previously: sets STM32WL_LSE_DRIVE to a conservative default and pulls in the STM32RTC library. rak3172 has ~63KB flash headroom going in; build-verified at 76.7% flash usage after this change (up from a 73.8% baseline), well within budget. wio-e5 is not opted in here despite sharing the same STM32WLE5 chip - it's already at 96.8% flash usage today (GPS + I2C sensor support compiled in, unlike rak3172), leaving too little headroom to safely add STM32RTC without first trimming something else. Signed-off-by: Andrew Yong <me@ndoo.sg> Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> * stm32wl: add docstrings for LSE/RTC setup functions Addresses CodeRabbit's docstring coverage check on PR #10961. Signed-off-by: Andrew Yong <me@ndoo.sg> Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> * stm32wl: address CodeRabbit nitpicks on PR #10961 - Brace the single-statement HAS_LSE branch in perhapsSetRTC() to match the sibling readFromRTC() branch's style. - Quote the RTC.h include in PhoneAPI.cpp for consistency with every other qualified include site. Signed-off-by: Andrew Yong <me@ndoo.sg> Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> --------- Signed-off-by: Andrew Yong <me@ndoo.sg> Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
336 lines
11 KiB
C++
336 lines
11 KiB
C++
#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 <Ethernet.h> // 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 <RAK13800_W5100S.h>
|
|
#endif
|
|
#include <SPI.h>
|
|
|
|
#if HAS_NETWORKING && !defined(USE_WS5500) && !defined(USE_CH390D)
|
|
|
|
#ifndef DISABLE_NTP
|
|
#include <NTPClient.h>
|
|
|
|
// 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
|