mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-18 01:11:42 +10:00
* feat: add Raspberry Pi Pico 2 + W5500 + E22-900M30S variant Adds community variant for Raspberry Pi Pico 2 (RP2350, 4 MB flash) with external WIZnet W5500 Ethernet module and EBYTE E22-900M30S LoRa module (SX1262, 30 dBm PA, 868/915 MHz). Key details: - LoRa on SPI1: GP10/11/12/13 (SCK/MOSI/MISO/CS), RST=GP15, DIO1=GP14, BUSY=GP2, RXEN=GP3 (held HIGH via SX126X_ANT_SW) - W5500 on SPI0: GP16/17/18/19/20 (MISO/CS/SCK/MOSI/RST) - SX126X_DIO2_AS_RF_SWITCH: DIO2→TXEN bridge on module handles PA - SX126X_DIO3_TCXO_VOLTAGE 1.8: TCXO support via EBYTE_E22 flags - DHCP timeout reduced to 10 s to avoid blocking LoRa startup - GPS on UART1/Serial2: GP8 TX, GP9 RX - Reuses WIZNET_5500_EVB_PICO2 code paths for Ethernet init Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * pico2_w5500_e22: rename define and address review feedback Rename WIZNET_5500_EVB_PICO2 to PICO2_W5500_E22 so the variant-specific define matches the variant directory name and isn't confused with an on-board EVB SKU. Review fixes from PR #10135: - Gate the 10 s Ethernet DHCP timeout behind PICO2_W5500_E22 so other Ethernet builds keep the default 60 s behavior; apply the same timeout to reconnectETH() for consistency. - Drop the unused -D EBYTE_E22 flag; EBYTE_E22_900M30S already selects TX_GAIN_LORA / SX126X_MAX_POWER in src/configuration.h. - Rewrite "on-board W5500" comments to describe the external module. - Correct README TX_GAIN_LORA value (7, not 10) and drop the EBYTE_E22 row. * fix(pico2_w5500_e22): drop DEBUG_RP2040_PORT=Serial The arduino-pico framework hooks _write() when DEBUG_RP2040_PORT=Serial is set and dumps raw debug bytes onto USB CDC, corrupting any binary protobuf stream sent through StreamAPI (e.g. `meshtastic --port COMx`). The variant excludes BT and WiFi, so the primary client transport is Ethernet TCP via ethServerAPI — unaffected — but users who configure the node over USB serial would see protobuf decode failures from debug-byte interleaving. Removing the flag restores clean USB CDC. Debug output can still be enabled per-build by adding -D DEBUG_RP2040_PORT=Serial1 to redirect to UART0 instead of USB CDC. * feat: add Ethernet OTA support for RP2350/W5500 boards Adds over-the-air firmware update capability for RP2350-based boards with a WIZnet W5500 Ethernet module (e.g. pico2_w5500_e22). Protocol (MOTA): - SHA256 challenge-response authentication with a configurable PSK (override via USERPREFS_OTA_PSK; default key ships in source) - 12-byte header: magic "MOTA" + firmware size + CRC32 - Firmware received in 1 KB chunks, verified with CRC32, written via Updater (picoOTA), then device reboots to apply - Constant-time hash comparison prevents timing attacks on auth - 30s inactivity timeout + 5s cooldown after failed auth - Response codes 0x00-0x08 map 1:1 to OTAResponse enum Firmware side: - ethOTA.cpp / ethOTA.h: OTA TCP server on port 4243 - ethClient.cpp: wire initEthOTA/ethOTALoop into reconnect loop - main-rp2xx0.cpp: hardware watchdog (8s, paused during debug) - pico2_w5500_e22/platformio.ini: HAS_ETHERNET_OTA flag, filesystem_size bumped to 0.75m for OTA staging Host side: - bin/eth-ota-upload.py: Python uploader with progress and full result-code mapping (matches OTAResponse 0x00-0x08) * style(eth): clang-format ethOTA.cpp per repo .clang-format Reformat to the repo trunk clang-format config (IndentWidth 4, ColumnLimit 130). Resolves the Trunk Check 'Incorrect formatting' failure on PR #10136. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
305 lines
9.6 KiB
C++
305 lines
9.6 KiB
C++
#include "mesh/eth/ethClient.h"
|
|
#include "NodeDB.h"
|
|
#include "RTC.h"
|
|
#include "concurrency/Periodic.h"
|
|
#include "configuration.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
|
|
#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_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
|
|
|
|
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
|
|
|
|
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
|