Skip to content

feat: add Ethernet OTA support for RP2350/W5500 boards#10136

Open
cvaldess wants to merge 4 commits intomeshtastic:developfrom
cvaldess:feature/eth-ota
Open

feat: add Ethernet OTA support for RP2350/W5500 boards#10136
cvaldess wants to merge 4 commits intomeshtastic:developfrom
cvaldess:feature/eth-ota

Conversation

@cvaldess
Copy link
Copy Markdown

Summary

  • Adds over-the-air firmware update via Ethernet (W5500) for RP2350-based boards
  • New TCP server on port 4243 using the MOTA protocol: SHA256 challenge-response auth + CRC32-verified transfer
  • Python upload tool (bin/eth-ota-upload.py) — stdlib-only, auto GZIP-compresses firmware before upload
  • Watchdog (8s) enabled in rp2040Loop() so the device recovers if an OTA write stalls

Depends on: #10135 (Raspberry Pi Pico 2 + W5500 + E22-900M30S variant) — please review that PR first.

Protocol

  1. Device sends a 32-byte random nonce
  2. Client responds with SHA256(nonce || PSK) — constant-time comparison on device side
  3. Client sends 12-byte header: "MOTA" magic + firmware size (LE u32) + CRC32 (LE u32)
  4. Firmware transferred in 1 KB chunks; device verifies CRC32 and calls Update.end() (picoOTA)
  5. Device reboots — bootloader applies the update from LittleFS

The PSK defaults to meshtastic_ota_default_psk_v1!!! and can be overridden per-build via USERPREFS_OTA_PSK.

Files changed

File Change
src/mesh/eth/ethOTA.cpp New — OTA TCP server implementation
src/mesh/eth/ethOTA.h New — public API: initEthOTA(), ethOTALoop()
bin/eth-ota-upload.py New — Python upload tool
src/mesh/eth/ethClient.cpp Hook initEthOTA() after connect, ethOTALoop() every 5s
src/platform/rp2xx0/main-rp2xx0.cpp Enable watchdog 8s in rp2040Loop()
variants/rp2350/pico2_w5500_e22/platformio.ini filesystem_size=0.75m, -D HAS_ETHERNET_OTA

Usage

# Build
pio run -e pico2_w5500_e22

# Upload OTA (device must be connected to Ethernet with IP assigned)
python bin/eth-ota-upload.py --host 192.168.1.100 .pio/build/pico2_w5500_e22/firmware-*.bin

# With custom PSK
python bin/eth-ota-upload.py --host 192.168.1.100 --psk mySecretKey firmware.bin

Test plan

  • Build succeeds with HAS_ETHERNET_OTA (pio run -e pico2_w5500_e22)
  • OTA upload completes and device reboots with new firmware
  • CRC mismatch correctly rejected
  • Wrong PSK correctly rejected (with cooldown)
  • Build without HAS_ETHERNET_OTA (other boards) unaffected — all code guarded

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Ethernet-based OTA update support for RP2350 + W5500 boards in the Meshtastic firmware, integrating a new OTA TCP server into the existing Ethernet client flow and providing a companion upload tool and RP2350 variant configuration.

Changes:

  • Introduces a new Ethernet OTA server (TCP/4243) using the MOTA protocol with PSK-based SHA256 auth and CRC32 verification.
  • Adds a stdlib-only Python uploader that GZIP-compresses firmware before transfer.
  • Adds/updates RP2350 Pico 2 + W5500 + E22-900M30S variant configs/docs and enables an RP2xxx watchdog loop.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/mesh/eth/ethOTA.cpp Implements Ethernet OTA server, auth, transfer, CRC verification, and reboot staging.
src/mesh/eth/ethOTA.h Declares OTA server init/loop API behind feature guards.
bin/eth-ota-upload.py Implements client-side MOTA upload with GZIP + SHA256 challenge-response.
src/mesh/eth/ethClient.cpp Hooks OTA init after Ethernet connect and polls OTA loop periodically; adds W5500 SPI0 init path.
src/platform/rp2xx0/main-rp2xx0.cpp Enables and feeds an 8s watchdog in rp2040Loop().
src/platform/rp2xx0/architecture.h Maps WIZNET_5500_EVB_PICO2 builds to PRIVATE_HW vendor model.
src/mesh/api/ethServerAPI.h Adds conditional include of Ethernet.h for the W5500/Pico2 path.
src/DebugConfiguration.h Adds conditional include of Ethernet.h for the W5500/Pico2 path.
variants/rp2350/pico2_w5500_e22/platformio.ini Adds new env with Ethernet OTA flag, bigger filesystem, and Ethernet library dependency.
variants/rp2350/pico2_w5500_e22/variant.h Defines pins/capabilities for Pico2 + W5500 + E22 wiring.
variants/rp2350/pico2_w5500_e22/README.md Documents wiring/build/network usage and technical notes for the variant.
variants/rp2350/pico2_w5500_e22/wiring.svg Adds a wiring diagram for the Pico2/W5500/E22 setup.

Comment thread variants/rp2350/pico2_w5500_e22/README.md Outdated
Comment thread src/mesh/eth/ethOTA.cpp Outdated
Comment thread src/mesh/eth/ethOTA.cpp Outdated
Comment thread src/mesh/eth/ethOTA.cpp Outdated
@cvaldess
Copy link
Copy Markdown
Author

All four Copilot review items were addressed in e9ac820:

  • OTA_ERR_TIMEOUT moved 0x06 → 0x08 so it no longer collides with OTA_ACK
  • USERPREFS_OTA_PSK handled as char[] + sizeof-1 to strip the PlatformIO-stringify trailing NUL
  • Auth cooldown path now calls client.stop() silently instead of writing a byte that the client would mis-consume as part of the nonce
  • README TX_GAIN_LORA value corrected to 7 for EBYTE_E22_900M30S

bfd2c94 follows up on the protocol-code change on the client side: bin/eth-ota-upload.py previously mapped 0x06 → Timeout, which stopped matching once 0x08 became the timeout code. The mapping is now aligned with the full OTAResponse enum (0x00–0x08) so every firmware response prints a human-readable message.

Ready for maintainer review whenever you have a moment — happy to rebase again if needed.

@cvaldess cvaldess force-pushed the feature/eth-ota branch 2 times, most recently from 00e86e9 to 75a0f70 Compare April 17, 2026 18:18
cvaldess and others added 4 commits April 20, 2026 12:44
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>
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

Firmware side:
- ethOTA.cpp / ethOTA.h: OTA TCP server on port 4243
- ethClient.cpp: calls initEthOTA() after Ethernet connects,
  ethOTALoop() polled every 5s
- main-rp2xx0.cpp: enable watchdog (8s) so device recovers if
  OTA write stalls; log watchdog-caused reboots
- pico2_w5500_e22: filesystem_size=0.75m (GZIP ~614KB fits),
  HAS_ETHERNET_OTA build flag

Upload tool:
- bin/eth-ota-upload.py: Python 3, stdlib-only, GZIP-compresses
  firmware before sending, shows progress bar + transfer speed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix OTA_ERR_TIMEOUT/OTA_ACK collision (0x06→0x08 for timeout)
- Fix USERPREFS_OTA_PSK trailing NUL byte via char[]+sizeof-1
- Fix auth cooldown: close connection silently instead of sending
  OTA_ERR_AUTH before the nonce (prevents byte being mis-consumed)
- Fix README TX_GAIN_LORA value: 10→7 for EBYTE_E22_900M30S
- Fix Trunk Check: clang-format ethOTA.cpp/h + variant.h, prettier
  README.md (MD040 code fence langs, MD060 table style), svgo wiring.svg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit moved OTA_ERR_TIMEOUT from 0x06 to 0x08 to free
0x06 for OTA_ACK, but bin/eth-ota-upload.py still mapped 0x06 to
"Timeout" — so a real timeout from the device (0x08) was printed as
"Unknown result 0x08" instead of being identified.

Align the mapping with the full OTAResponse enum in ethOTA.cpp:
keep 0x00–0x03, remove the stale 0x06, and add 0x04 (magic),
0x05 (begin), 0x07 (auth), 0x08 (timeout) so every firmware response
prints a human-readable message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-review Needs human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants