* 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>
* add watchdog to rp2xx0
* feat(eth-api): phase 0 skeleton — HTTP API server on TCP/80 (503 only)
First step of porting mesh/http/ to RP2350 + W5500 (today ESP32-only).
Phase 0 stands up the listener over the existing arduino-libraries/Ethernet
stack (no Mongoose — its built-in TCP/IP would conflict with EthernetServer
and break OTA/MQTT/NTP). Skeleton parses request line, logs it, replies 503.
Real handlers (/api/v1/{info,fromradio,toradio}) come in later phases.
- New mesh/eth/ethApiServer.{h,cpp} gated by HAS_ETHERNET_API
- Wired into ethClient init+loop in parallel with existing ethHttpOTA (port 4244)
- Enabled in both wiznet_5500_evb_pico2_e22p and pico2_w5500_e22 variants
- Build impact on wiznet: +~9 KB flash (63.0% used), <1 KB RAM
* feat(eth-api): phase 1 — implement /api/v1/{fromradio,toradio} with PhoneAPI
Replace phase 0 skeleton with the real handlers that bridge the HTTP transport
to PhoneAPI, mirroring mesh/http/ContentHandler (ESP32) semantics.
- EthHttpAPI : public PhoneAPI (api_type=TYPE_HTTP, checkIsConnected=true),
outside the MESHTASTIC_EXCLUDE_WEBSERVER gate so it builds on RP2350.
- Minimal HTTP parser: method/path/query/Content-Length, no allocations beyond
Arduino String, capped at 32 header lines x 256 bytes (anti-DoS).
- OPTIONS preflight -> 204 with CORS + X-Protobuf-Schema.
- GET /api/v1/fromradio[?all=true]: stream-write up to 64 protobufs from
webAPI.getFromRadio(), Connection: close framing (HTTP/1.0 style).
- PUT /api/v1/toradio: read Content-Length bytes (<=512), call
webAPI.handleToRadio(), echo body back.
- 404 default for unknown paths, 405 for wrong method, 400 for bad body.
Validated e2e against wiznet @ 192.168.1.143:
- OPTIONS /api/v1/fromradio -> 204 + correct CORS headers
- PUT ToRadio{want_config_id=1} (2 bytes) -> 200 + echo
- GET /api/v1/fromradio?all=true -> 200 + 3476 bytes (config+channels+nodeinfo)
- GET /api/v1/nonexistent -> 404 unknown endpoint
- OTA HTTP on port 4244 untouched and still responsive
Build impact on wiznet_5500_evb_pico2_e22p: +2 KB flash (63.1%), +1.8 KB RAM
(17.6%) vs phase 0.
* fix(eth-api): move accept loop to dedicated OSThread (20ms tick)
The API server was being polled from the Ethernet client Periodic which runs
every 5s. Measured impact before this fix on wiznet @ 192.168.1.143:
req 1: ttfb=6.458s total=6.509s (matches the 5s tick + handler overhead)
req 2: connection refused (W5500 sockets exhausted)
req 3: connection refused
After: same hardware, same network, back-to-back requests:
req 2: ttfb=0.287s
req 3: ttfb=0.015s
req 4: ttfb=0.022s
req 5: ttfb=0.020s
The web client (meshtastic/web served from localhost) was visibly stalling
mid-handshake — it had pulled 109 nodes + 19 messages but channels and device
info never arrived. With the periodic-only polling, every request takes 5s
and the W5500's 4 hardware sockets fill up under the burst.
EthApiServerThread mirrors the WebServerThread pattern from ESP32
mesh/http/WebServer.cpp: adaptive interval — 20ms when there's recent
traffic, 100ms after 5s of idle, 500ms after 30s. Auto-registers with the
OSThread scheduler on construction.
ethHttpOTA still ticks from the periodic; left unchanged because OTA is one
large transfer that tolerates the latency, and minimizing scope here to one
behaviour change at a time.
* refactor(eth-api): extract handlers to shared module via IStreamReadWrite
Phase 2.0 of the TLS port — separate transport from request handling so
the upcoming HTTPS server can reuse the exact same parser + routing logic.
- New ethApiHandlers.{h,cpp}: Request, parser, CORS helpers, fromradio/toradio
handlers, EthHttpAPI : PhoneAPI subclass. All driven by a single
IStreamReadWrite interface that inherits Print (so client.print(...) keeps
working transparently).
- ethApiServer.cpp slimmed to ~90 LOC: now just the EthernetServer(80) +
OSThread + an EthernetClientStream adapter that forwards reads/writes to
the underlying EthernetClient. No behaviour change.
Validated on wiznet @ 192.168.1.143: 5 curl requests TTFB 18-110ms (same
as pre-refactor), protobuf round-trip PUT 200 + GET ?all=true 3499 bytes.
Flash impact: +200 bytes (63.2%); RAM unchanged (17.6%).
* feat(eth-tls): ECDSA P-256 self-signed cert generation for HTTPS API
mbedTLS-based cert generation module that produces a SAN=IP self-signed
ECDSA P-256 server certificate, persisted under LittleFS so subsequent
boots reuse the same key. Generation runs once on a dedicated OSThread
so the ECDSA keygen path (~430 ms) never blocks the Periodic stack or
the Ethernet reconnect loop.
- mbedTLS 3.6.2 sources compiled in via scripts/add_mbedtls_sources.py
(BuildSources of pico-sdk/lib/mbedtls/library/*.c, all 108 files).
MBEDTLS_USER_CONFIG_FILE injected as CPPDEFINES tuple in the script —
build_flags shell escape mangles the embedded quotes on Windows.
- src/mbedtls_user_config.h undefs MBEDTLS_HAVE_TIME, HAVE_TIME_DATE,
TIMING_C, NET_C, FS_IO, PSA_ITS_FILE_C, PSA_CRYPTO_STORAGE_C, and
defines MBEDTLS_NO_PLATFORM_ENTROPY (entropy_poll.c uses a raw
platform check, not gated by a flag).
- src/mesh/eth/ethCert.{h,cpp}: ECDSA P-256 keypair + cert with
SAN(IP=current) using pico-sdk get_rand_64() directly as f_rng,
bypassing mbedtls_entropy. DER buffers heap-allocated to keep the
OSThread stack within budget. Cert + key + ip persisted under
/prefs/eth_*.der so subsequent boots reuse the same identity.
- Gated by HAS_ETHERNET_TLS_API. Standalone phase: generation only;
HTTPS server (TCP/443) wired up in the follow-up commit.
* feat(eth-tls-api): phase 2.2 — HTTPS server on TCP/443 reusing handlers
Brings up an mbedTLS server on port 443 that defers to the ECDSA P-256
self-signed cert produced by [[ethCert]]. Reuses the HTTP request /
response handlers from [[ethApiHandlers]] via the IStreamReadWrite
interface — there is exactly one code path for routing / CORS / PhoneAPI
integration regardless of whether the transport is plain TCP or TLS.
EthTlsApiServerThread (OSThread):
- Phase A: poll isEthCertReady() every 500 ms while the cert worker
runs. Once true, parse cert chain + key, build ssl_config (TLS server,
stream transport, default preset, VERIFY_NONE since we are the cert
issuer), install own cert, run ssl_setup, bind tlsServer on 443.
- Phase B: standard adaptive accept loop (20 / 100 / 500 ms tick),
identical to the plain-HTTP server.
Per-connection flow:
1. session_reset on the static ssl context (1 in-flight session — multi-
session pool is Phase 3 if needed)
2. set_bio routes mbedtls I/O to two C callbacks (netSend / netRecv)
that bridge to the live EthernetClient via the void* ctx
3. handshake loop (sync, blocking, with 10 s recv timeout) — mbedTLS
errors are logged with mbedtls_strerror
4. wrap (ssl, client) in MbedTlsStream → handleApiClient(stream)
5. close_notify + client.stop
Stack budget continues the Phase 2.1-bis discipline: every large buffer
(ssl context, cert chain, pk_key, ssl_config) lives in BSS as a static
global. The OSThread stack only holds the per-connection EthernetClient
adapter and small mbedtls return codes.
Validated on wiznet 192.168.1.143:
- cert load-from-FS path: 13 ms (regen skipped on second boot)
- cert gen path: 290 ms (first boot only)
- ssl_setup chain: parse cert, parse key, ssl_config_defaults,
conf_own_cert, ssl_setup all return 0
- end-to-end: curl -k https://192.168.1.143/api/v1/fromradio → 200 OK
with application/x-protobuf, CORS, X-Protobuf-Schema headers, server
initiates close_notify cleanly
Footprint vs 2.0:
- flash 70.5% → 87.3% (+220 KB for mbedtls_ssl + x509 server-side code)
- RAM static 17.6% → 19.8% (+11 KB BSS for ssl_context + cert chain)
- heap at runtime adds ~32 KB per active session (mbedtls in/out
record buffers, default MBEDTLS_SSL_*_CONTENT_LEN=16384)
Next: validate from Firefox direct (https://192.168.1.143 → warning
accept → JSON visible), then from client.meshtastic.org hosted to
confirm the mixed-content block is gone.
* fix(eth-tls): add KeyUsage + EKU serverAuth and cap TLS 1.2 for browser compat
Two browser-compat fixes that surfaced in Firefox validation:
1. Cert v1 only had Basic Constraints + SAN. NSS / Firefox refuse to
treat a cert as a TLS server cert without an Extended Key Usage
extension naming id-kp-serverAuth since 2023 — the error surfaces as
a non-overridable 'Secure Connection Failed' with no 'Accept the
Risk' path. Add KeyUsage(digitalSignature + keyEncipherment, critical)
and ExtendedKeyUsage(serverAuth, critical). Bump cert/key/ip file
paths to '_v2' so live boards regenerate on next start instead of
loading a v1 cert that the browser silently refuses.
2. pico-sdk mbedtls defines MBEDTLS_SSL_PROTO_TLS1_3 in its default
config, but the server-side 1.3 plumbing in this vendored build is
incomplete: Firefox and openssl-3.5's s_client default to 1.3 and
the handshake dies 4 ms in with MBEDTLS_ERR_ERROR_GENERIC_ERROR
(-0x0001). curl/SChannel happened to default to 1.2 so it masked the
issue earlier. Cap min/max_tls_version to TLS 1.2 — clients downgrade
transparently and we keep the ECDHE-ECDSA + AES-GCM / CHACHA20-POLY1305
suites that already work end-to-end.
Validated on wiznet 192.168.1.143:
- cert v2 dumps with the 4 extensions visible (openssl x509 -text):
BasicConstraints CA:FALSE, KeyUsage(critical) digitalSignature +
keyEncipherment, ExtendedKeyUsage(critical) serverAuth, SAN IP.
- openssl s_client (no version flag): downgrades to TLS 1.2, handshake
completes, verify_return=18 (self-signed, expected).
- Firefox: warning self-signed -> Advanced -> Accept Risk -> handshake
OK in 632 ms with CHACHA20-POLY1305, request reaches handleApiClient
and returns the protobuf body.
* perf(eth-api): HTTP/1.1 keep-alive — one handshake per session, not per request
Before this change /fromradio used 'Connection: close' framing (HTTP/1.0
style with no Content-Length), forcing each poll to redo the full TLS
handshake. client.meshtastic.org needs ~80 sequential /fromradio polls
during initial sync (one per packet from the config-replay state
machine: MyInfo, channels, every Config_*, every ModuleConfig_*, every
NodeInfo), so the user-visible load time was ~50 s of pure handshake
overhead (80 requests * ~625 ms ECDSA P-256 each).
Three coordinated changes:
1. handleApiClient() now loops on the same connection until the peer
closes or parseRequest hits its 3 s idle timeout. requestsServed
counter keeps the 'bad/timeout request' debug log from firing on
the natural idle close after a keep-alive sequence.
2. handleFromRadio() buffers all packets into a std::vector before
writing, so it can emit a real Content-Length and 'Connection:
keep-alive'. Buffer is dynamic — common 1-packet response only
allocates ~256 B; ?all=true keeps the 64-packet cap which tops out
around 16 KB. handleToRadio + sendPreflight switched to keep-alive
too (they already had real Content-Length). sendError keeps close —
errors are terminal.
3. ethTlsApiServer netRecv RECV_TIMEOUT_MS dropped from 10 s to 3 s so
mbedtls_ssl_read can't outlast the handler's idle deadline (a quiet
browser leaving the socket open would otherwise wedge the OSThread
for 10 s past the natural close).
Measured on wiznet 192.168.1.143 against client.meshtastic.org:
- one TLS handshake (641 ms, CHACHA20-POLY1305)
- ~80 requests pipelined over the same session
- full config + 40 NodeInfos in ~6-8 s (vs ~50 s before)
- per-request latency post-handshake: ~10-25 ms
- curl -kv with two URLs: 'Reusing existing https: connection',
server returns 'Connection: keep-alive' explicitly.
* fix(eth-tls): watchdog + Chrome compat — handshake stability across browsers
Phase 3 keep-alive shipped a working Firefox flow but client.meshtastic.org
loops + Chrome 'Test Connection' both rebooted the board. Four distinct
issues; collectively they kept the OSThread inside serveClient() too long
or spin-looping without yielding, and pico-sdk mbedtls' TLS 1.3 code path
choked on Chrome's modern ClientHello.
1. Watchdog reset during keep-alive idle. Once a client drained the
replay queue and entered 3 s poll mode, the OSThread sat inside
netRecv()'s busy-wait waiting for the next request. Two consecutive
3 s waits plus prior handler time crossed the 8 s RP2350 hardware
watchdog. Pet the watchdog inside netRecv()'s poll loop (every 2 ms)
so a quiet client can never starve the watchdog. Same fix in the
ethApiHandlers per-request yield path.
2. Cap session at 64 requests + yield() between. Defense-in-depth: a
pathological client can't monopolize serveClient indefinitely; after
the cap it just re-handshakes (~625 ms), still vastly cheaper than
the per-request handshake we had before keep-alive.
3. TLS 1.3 code compiled out of mbedtls entirely
(#undef MBEDTLS_SSL_PROTO_TLS1_3 in mbedtls_user_config). Capping
max_tls_version=TLS1_2 at runtime is enough for Firefox / openssl
(they downgrade cleanly), but Chrome's ClientHello carries TLS 1.3
extensions — post-quantum key shares, Encrypted ClientHello,
etc. — that the vendored mbedtls 1.3 parser crashes on before the
downgrade decision happens. Removing the 1.3 sources sidesteps the
parser; ServerHello just announces TLS 1.2 and Chrome accepts.
4. netSend infinite WANT_WRITE spin. When W5500's TX buffer momentarily
filled mid-handshake (Chrome draining slower than Firefox during
ServerKeyExchange), EthernetClient::write() returned 0, our netSend
returned MBEDTLS_ERR_SSL_WANT_WRITE without delay, mbedtls retried
immediately, repeat at ~180k iter/sec until ... well, until the
board's other threads got nothing done. Log signature: ret=-0x6880
tight-looping in the handshake iter trace. Rewrite netSend to block
with delay(2) + watchdog_update() and a 3 s timeout — same shape as
netRecv. Return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY on disconnect
(was incorrectly returning WANT_WRITE).
Also added granular per-iter handshake logging gated on first 20 iters
+ every 50th after that, so any future regression localizes itself in
COM9 without RTT JLink.
Validated on wiznet 192.168.1.143:
- Firefox: client.meshtastic.org full sync + idle poll stable (no
reset during the previously-crashing 'replay drain complete' phase)
- Chrome: 'Test Connection' accepts the cert prompt and connects
- Edge: same as Chrome
- openssl s_client default + tls1_2 forced: both negotiate TLS 1.2
with ECDHE-ECDSA + AES-GCM / CHACHA20-POLY1305, verify=18 (self-
signed, expected)
* chore(eth-tls): drop verbose debug logs from cert + TLS init paths
The cert pipeline + TLS context init had step-by-step LOG_INFOs and
ubiquitous Serial.flush() that were essential while diagnosing the
Phase 2.1-bis stack overflow, the Chrome handshake crash, and the
keep-alive watchdog reset. Once those bugs were fixed the logs just
clutter COM9 on every boot.
Kept on hand:
- cert: 'loaded from FS', 'generating ...', 'generated N B in T ms',
'persisted to LittleFS', plus all LOG_ERROR / LOG_WARN paths
- tls: 'server listening on TCP port 443', 'client connected from',
'handshake OK in N ms ciphersuite=', 'handshake failed -0xXXXX (...)',
plus all init LOG_ERRORs
Dropped:
- cert: 'step 1/8 pk_setup' through 'step 8/8 copy key DER', 'thread
woke', 'pipeline OK' (now silent on success)
- tls: 'cert is ready, initializing', 'parsing cert chain', 'parsing
key', 'ssl_config_defaults', 'conf_own_cert', 'ssl_setup',
'server worker scheduled', and the per-iter handshake trace
- obsolete 'Optional mbedtls debug bridge' commented-out stub
- all Serial.flush() that were added defensively for the
debug-the-crash phase
Bin shrinks ~40 KB (logs + format strings). Validated on wiznet
192.168.1.143: HTTP 200 round-trip works post-flash, no regression.
* fix(eth): harden API/TLS build guards (review #10573)
- ethApiHandlers.{h,cpp}: gate on (HAS_ETHERNET_API || HAS_ETHERNET_TLS_API)
so the shared handleApiClient/IStreamReadWrite still compile for an
HTTPS-only variant (the TLS server depends on them).
- ethTlsApiServer.{cpp,h}, ethCert.{cpp,h}: require ARCH_RP2040 (they use Pico
SDK get_rand_64()/watchdog_update()); the ethClient.cpp TLS include + call
site are tightened to match, so enabling the flag on a non-RP2040 Ethernet
target is a clean no-op instead of a cryptic build break.
- clang-format (16) the touched eth files to satisfy Trunk Check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(eth): address review — listener/cert recovery + keep-alive framing
- Rebind HTTP/80 and HTTPS/443 after a W5500 chip reset. reconnectETH() now
tears the API/TLS servers down (deInitEthApiServer/deInitEthTlsApiServer) so
the restart path recreates them; the singleton guards previously left both
bound to dead sockets until reboot. The workers are kept (only the listener
is dropped) so nothing is deleted from another thread's runOnce.
- Stop the keep-alive loop after a handler error. /fromradio and /toradio
return whether the connection may stay open; a 400/405/408 response
(Connection: close) now ends the loop so unread body bytes can't be parsed
as the next request.
- Validate the cached cert/key before trusting it (parse both DERs), and clear
the IP commit-marker before rewriting the pair and write it last, so a reset
mid-persist regenerates instead of handing the TLS server a truncated or
mismatched pair (which would hard-disable HTTPS).
- Track a cert generation counter so the TLS server reloads and rebinds when a
DHCP lease change regenerates the cert for a new IP (the SAN must follow, or
browsers reject the new address). The cert worker is no longer one-shot.
- Silence the SCons F821/E402 lint on add_mbedtls_sources.py to match the other
extra_scripts.
Validated on a W5500 board: a forced chip reset rebinds 80 and 443 without an
MCU reboot; a truncated cached cert regenerates and HTTPS recovers; a simulated
lease change reissues the cert with the new IP in the SAN.
* fix(eth): clear cert readiness on regen failure + verify cached pair
Follow-up to the review:
- Reset ready_/certIp_ when ensureCertForIp() fails, so isReady() no longer
reports true with empty material — otherwise a later TLS reload fails
initTlsContext() and stays disabled instead of retrying on the next poll.
- certKeyParse() now also checks the cached cert and key form a matching pair
(mbedtls_pk_check_pair), rejecting an independently-parseable but mismatched
cert/key (e.g. if the IP commit-marker survived a failed clear).
Validated on a W5500 board: a valid cached pair still loads from FS, and a
regenerated cert reloads cleanly.
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
* 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.
* style(pico2_w5500_e22): apply trunk fmt — fixes Trunk Check
- variant.h: clang-format 16.0.3 (drop manual #define alignment)
- README.md: prettier + add `text` language to fenced code blocks (markdownlint MD040)
- wiring.svg: svgo optimization
Resolves the Trunk Check Runner failure on this PR (3 unformatted
files + 8 markdownlint issues). No functional changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(pico2_w5500_e22): address review — move to rp2350/diy, generic guards
Maintainer feedback from NomDeTom on PR #10135:
- Move the variant from variants/rp2350/ to variants/rp2350/diy/ to
distinguish DIY from prebuilt boards (matches the variants/*/diy/
pattern; still discovered via the existing variants/*/diy/*/platformio.ini
glob in the root platformio.ini).
- Replace the board-name macro PICO2_W5500_E22 in shared code with a
generic capability macro USE_ARDUINO_ETHERNET, defined from variant.h.
DebugConfiguration.h / ethServerAPI.h / ethClient.cpp no longer
reference a board name.
- Drop the architecture.h hook entirely: variant.h now defines PRIVATE_HW,
which the existing `#elif defined(PRIVATE_HW)` branch already handles.
No functional change. Build verified: pico2_w5500_e22 SUCCESS
(RAM 19.2%, Flash 28.1%).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(pico2_w5500_e22): drop unoptimized wiring.svg to fix trunk fmt check
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* ThinkNode G3, ETH support WIP
* rename variant and add guard macros
* older G3 operational. M7 next.
* Split out G3 and M7 to different variants. Completely new PCB design. The G3 stays on 'PRIVATE_HW'
* Define button behaviour and use all of the device flash
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
The W5100S Ethernet chip has only 4 hardware sockets. On RAK4631
Ethernet gateways with syslog and NTP enabled, all 4 sockets were
permanently consumed (NTP UDP + Syslog UDP + TCP API listener + TCP
API client), leaving none for MQTT, DHCP lease renewal, or additional
TCP connections.
- NTP: Remove permanent timeClient.begin() at startup; NTPClient::update()
auto-initializes when needed. Add timeClient.end() after each query to
release the UDP socket immediately.
- Syslog: Remove socket allocation from Syslog::enable(). Open and close
the UDP socket on-demand in _sendLog() around each message send.
- MQTT: Fix socket leak in isValidConfig() where a successful test
connection was never closed (PubSubClient destructor does not call
disconnect). Add explicit pubSub->disconnect() before returning.
Made-with: Cursor
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Fix RAK4631 Ethernet gateway API connection loss after W5100S brownout
PoE power instability can brownout the W5100S while the nRF52 MCU keeps
running, causing all chip registers (MAC, IP, sockets) to revert to
defaults. The firmware had no mechanism to detect or recover from this.
Changes:
- Detect W5100S chip reset by periodically verifying MAC address register
in reconnectETH(); on mismatch, perform full hardware reset and
re-initialize Ethernet interface and services
- Add deInitApiServer() for clean API server teardown during recovery
- Add ~APIServerPort destructor to prevent memory leaks
- Switch nRF52 from EthernetServer::available() to accept() to prevent
the same connected client from being repeatedly re-reported
- Add proactive dead-connection cleanup in APIServerPort::runOnce()
- Add 15-minute TCP idle timeout to close half-open connections that
consume limited W5100S hardware sockets
Fixesmeshtastic/firmware#6970
Made-with: Cursor
* Log actual elapsed idle time instead of constant timeout value
Address Copilot review comment: log millis() - lastContactMsec to show
the real time since last client activity, rather than always logging the
TCP_IDLE_TIMEOUT_MS constant.
Made-with: Cursor
* Update src/mesh/api/ServerAPI.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Stop UDP multicast handler during W5100S brownout recovery
After a W5100S chip brownout, the udpHandler isRunning flag stays
true while the underlying socket is dead. Without calling stop(),
the subsequent start() no-ops and multicast is silently broken
after recovery.
Made-with: Cursor
* Address Copilot review: recovery flags and timeout constant
Move ethStartupComplete and ntp_renew reset to immediately after
service teardown, before Ethernet.begin(). Previously, if DHCP
failed the early return left ethStartupComplete=true, preventing
service re-initialization on subsequent retries.
Replace #define TCP_IDLE_TIMEOUT_MS with static constexpr uint32_t
for type safety and better C++ practice.
Made-with: Cursor
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Resolve naming conflict of Syslog class with namespace
* do not include libpax headers if pax counter is excluded
* clean only top-level sdkconfigs, keep them in the variants directories
* Fix code formatting
I thought git would be smart enough to understand all the whitespace changes but even with all the flags I know to make it ignore theses it still blows up if there are identical changes on both sides.
I have a solution but it require creating a new commit at the merge base for each conflicting PR and merging it into develop.
I don't think blowing up all PRs is worth for now, maybe if we can coordinate this for V3 let's say.
This reverts commit 0d11331d18.
* Add missed include
* Another Warning fix
* Add another HAS_SCREEN
* Namespace fixes
* Removed depricated destination types and re-factored destination screen
* Get rid of Arduino Strings
* Clean up after Copilot
* SixthLine Def, Screen Rename
Added Sixth Line Definition Screen Rename, and Automatic Line Adjustment
* Consistency is hard - fixed "Sixth"
* System Frame Updates
Adjusted line construction to ensure we fit maximum content per screen.
* Fix up notifications
* Add a couple more ifdef HAS_SCREEN lines
* Add screen->isOverlayBannerShowing()
* Don't forget the invert!
* Adjust Nodelist Center Divider
Adjust Nodelist Center Divider
* Fix variable casting
* Fix entryText variable as empty before update to fix validation
* Altitude is int32_t
* Update PowerTelemetry to have correct data type
* Fix cppcheck warnings (#6945)
* Fix cppcheck warnings
* Adjust logic in Power.cpp for power sensor
---------
Co-authored-by: Jason P <applewiz@mac.com>
* More pixel wrangling so things line up NodeList edition
* Adjust NodeList alignments and plumb some background padding for a possible title fix
* Better alignment for banner notifications
* Move title into drawCommonHeader; initial screen tested
* Fonts make spacing items difficult
* Improved beeping booping and other buzzer based feedback (#6947)
* Improved beeping booping and other buzzer based feedback
* audible button feedback (#6949)
* Refactor
---------
Co-authored-by: todd-herbert <herbert.todd@gmail.com>
* Sandpapered the corners of the notification popup
* Finalize drawCommonHeader migration
* Update Title of Favorite Node Screens
* Update node metric alignment on LoRa screen
* Update the border for popups to separate it from background
* Update PaxcounterModule.cpp with CommonHeader
* Update WiFi screen with CommonHeader and related data reflow
* It was not, in fact, pointing up
* Fix build on wismeshtap
* T-deck trackball debounce
* Fix uptime on Device Focused page to actually detail
* Update Sys screen for new uptime, add label to Freq/Chan on LoRa
* Don't display DOP any longer, make Uptime consistent
* Revert Uptime change on Favorites, Apply to Device Focused
* Label the satelite number to avoid confusion
* Boop boop boop boop
* Correct GPS positioning and string consistency across strings for GPS
* Fix GPS text alignment
* Enable canned messages by default
* Don't wake screen on new nodes
* Cannedmessage list emote support added
* Fn+e emote picker for freetext screen
* Actually block CannedInput actions while display is shown
* Add selection menu to bannerOverlay
* Off by one
* Move to unified text layouts and spacing
* Still my Fav without an "e"
* Fully remove EVENT_NODEDB_UPDATED
* Simply LoRa screen
* Make some char pointers const to fix compilation on native targets
* Update drawCompassNorth to include radius
* Fix warning
* button thread cleanup
* Pull OneButton handling from PowerFSM and add MUI switch (#6973)
* Trunk
* Onebutton Menu Support
* Add temporary clock icon
* Add gps location to fsi
* Banner message state reset
* Cast to char to satisfy compiler
* Better fast handling of input during banner
* Fix warning
* Derp
* oops
* Update ref
* Wire buzzer_mode
* remove legacy string->print()
* Only init screen if one found
* Unsigned Char
* More buttonThread cleaning
* screen.cpp button handling cleanup
* The Great Event Rename of 2025
* Fix the Radiomaster
* Missed trackball type change
* Remove unused function
* Make ButtonThread an InputBroker
* Coffee hadn't kicked in yet
* Add clock icon for Navigation Bar
* Restore clock screen definition code - whoops
* ExternalNotifications now observe inputBroker
* Clock rework (#6992)
* Move Clock bits into ClockRenderer space
* Rework clock into all device navigation
* T-Watch Actually Builds Different
* Compile fix
---------
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
* Add AM/PM to Digital Clock
* Flip Seconds and AM/PM on Clock Display
* Tik-tok pixels are hard
* Fix builds on Thinknode M1
* Check for GPS and don't crash
* Don't endif til the end
* Rework the OneButton thread to be much less of a mess. (#6997)
* Rework the OneButton thread to be much less of a mess. And break lots of targets temporarily
* Update src/input/ButtonThread.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix GPS toggle
* Send the shutdown event, not just the kbchar
* Honor the back button in a notificaiton popup
* Draw the right size box for popup with options
* Try to un-break all the things
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 24-hour Clock Should have leading zero, but not 12-hour
* Fixup some compile errors
* Add intRoutine to ButtonThread init, to get more responsive user button back
* Add Timezone picker
* Fix Warning
* Optionally set the initial selection for the chooser popup
* Make back buttons work in canned messages
* Drop the wrapper classes
* LonPressTime now configurable
* Clock Frame can not longer be blank; just add valid time
* Back buttons everywhere!
* Key Verification confirm banner
* Make Elecrow M* top button a back button
* Add settings saves
* EInk responsiveness fixes
* Linux Input Fixes
* Add Native Trackball/Joystick support, and move UserButton to Input
* No Flight Stick Mode
* Send input event
* Add Channel Utilization to Device Focused frame
* Don't shift screens when we draw new ones
* Add showOverlayBanner arguments to no-op
* trunk
* Default Native trackball to NC
* Fix crash in simulator mode
* Add longLong button press
* Get the args right
* Adjust Bluetooth Pairing Screen to account for bottom navigation.
* Trackball everywhere, and unPhone buttons
* Remap visionmaster secondary button to TB_UP
* Kill ScanAndSelect
* trunk
* No longer need the canned messages input filter
* All Canned All the time
* Fix stm32 compile error regarding inputBroker
* Unify tft lineheights (#7033)
* Create variable line heights based upon SCREEN_HEIGHT
* Refactor textPositions into method -> getTextPositions
* Update SharedUIDisplay.h
---------
Co-authored-by: Jason P <applewiz@mac.com>
* Adjust top distance for larger displays
* Adjust icon sizes for larger displays
* Fix Paxcounter compile errors after code updates
* Pixel wrangling to make larger screens fit better
* Alert frame has precedence over banner -- for now
* Unify on ALT_BUTTON
* Align AM/PM to the digit, not the segment on larger displays
* Move some global pin defines into configuration.h
* Scaffolding for BMM150 9-axis gyro
* Alt button behavior
* Don't add the blank GPS frames without HAS_GPS
* EVENT_NODEDB_UPDATED has been retired
* Clean out LOG_WARN messages from debugging
* Add dismiss message function
* Minor buttonThread cleanup
* Add BMM150 support
* Clean up last warning from dev
* Simplify bmm150 init return logic
* Add option to reply to messages
* Add minimal menu upon selecting home screen
* Move Messages to slot 2, rename GPS to Position, move variables nearer functional usage in Screen.cpp
* Properly dismiss message
* T-Deck Trackball press is not user button
* Add select on favorite frame to launch cannedMessage DM
* Minor wording change
* Less capital letters
* Fix empty message check, time isn't reliable
* drop dead code
* Make UIRenderer a static class instead of namespace
* Fix the select on favorite
* Check if message is empty early and then 'return'
* Add kb_found, and show the option to launch freetype if appropriate
* Ignore impossible touchscreen touches
* Auto scroll fix
* Move linebreak after "from" for banners to maximize screen usage.
* Center "No messages to show" on Message frame
* Start consolidating buzzer behavior
* Fixed signed / unsigned warning
* Cast second parameter of max() to make some targets happy
* Cast kbchar to (char) to make arduino string happy
* Shorten the notice of "No messages"
* Add buzzer mode chooser
* Add regionPicker to Lora icon
* Reduce line spacing and reorder Position screen to resolve overlapping issues
* Update message titles, fix GPS icons, add Back options
* Leftover boops
* Remove chirp
* Make the region selection dismissable when a region is already set
* Add read-aloud functionality on messages w/ esp8266sam
* "Last Heard" is a better label
* tweak the beep
* 5 options
* properly tear down freetext upon cancel
* de-convelute canned messages just a bit
* Correct height of Mail icon in navigation bar
* Remove unused warning
* Consolidate time methods into TimeFormatters
* Oops
* Change LoRa Picker Cancel to Back
* Tweak selection characters on Banner
* Message render not scrolling on 5th line
* More fixes for message scrolling
* Remove the safety next on text overflow - we found that root cause
* Add pin definitions to fix compilation for obscure target
* Don't let the touchscreen send unitialized kbchar values
* Make virtual KB just a bit quicker
* No more double tap, swipe!
* Left is left, and Right is right
* Update horizontal lightning bolt design
* Move from solid to dashed separator for Message Frame
* Single emote feature fix
* Manually sort overlapping elements for now
* Freetext and clearer choices
* Fix ESP32 InkHUD builds on the unify-tft branch (#7087)
* Remove BaseUI branding
* Capitalization is fun
* Revert Meshtastic Boot Frame Changes
* Add ANZ_433 LoRa region to picker
* Update settings.json
---------
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: todd-herbert <herbert.todd@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
MESHTASTIC_EXCLUDE_SOCKETAPI disables the API Server when set.
Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: GUVWAF <thijs@havinga.eu>
remove newline from logging statements in code. The LOG_* functions will now magically add it at the end.
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Fixes (#3618) by allowing more time for slower requests.
Resolve Syslog not maintaining client causing issues on RAK13800.
Resolve Ethernet static IP setting subnet as gateway IP.
Reduce comment and log message ambiguity around API.
Remove duplicate #if !MESHTASTIC_EXCLUDE_WEBSERVER block.
With static ethernet config, `status` stayed `0` which let the function return
without setting `ethEvent`. Therefore, `reconnectETH` was never called and network services were never started.
Also, the RAK4631 uses little endian, which is why the IP addresses need to be
converted before setting them.
Fixes#2543
- Skip a lot of duplicate code
- add a hexDump output - might come in handy
- refactor directory names
- remove unused debugOut that was generating template errors