From 1884a25a6de03feacf91c409f217bfd26766b80c Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 30 Jun 2026 19:54:40 -0400 Subject: [PATCH] Add protocol research synthesis and phased architecture plan --- docs/ARCHITECTURE.md | 131 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/ARCHITECTURE.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..5640a63 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,131 @@ +# archy-messh — Architecture & Design Space + +Target hardware (locked in): **Heltec WiFi LoRa 32 V3** — ESP32-S3FN8, 8MB flash, +512KB SRAM, **no PSRAM**, single SX1262 LoRa radio, dual-core LX7. + +This doc synthesizes research into Meshtastic, MeshCore, and Reticulum/RNode +firmware architecture, identifies the hard constraints the Heltec V3 imposes, +and lays out a phased plan toward a single device that can speak to and +repeat between all three networks. + +--- + +## 1. The three protocols, side by side + +| | Meshtastic | MeshCore | Reticulum (RNS + RNode) | +|---|---|---|---| +| **Default LoRa PHY** (EU) | 869.525 MHz, BW 250 kHz (or 812.5 wide), CR 4:5, **SF 11** | Compiled default identical: `869.525,250,11,5` — but community has since moved to **narrowband SF7-8 / BW 62.5 kHz** (Oct 2025) | No fixed preset — operator-configured freq/BW/SF/CR per deployment | +| **Sync word** | Custom `0x2b` (not LoRaWAN's public `0x34`) | Unconfirmed — needs direct check (RadioLib default vs custom) | Whatever the operator configures; not protocol-mandated | +| **Frequency selection** | Hashed per channel-name within region plan (hops, not fixed) | Single fixed frequency, no hopping found | Fixed, operator-set | +| **Radio chips** | SX126x/SX127x/SX128x/LR11x0/STM32WLE5JC, via RadioLib | Same chip families, also via a RadioLib-based wrapper | SX127x/SX126x/SX128x, custom firmware (not RadioLib) | +| **On-air routing** | Pure managed flood: hop_limit/hop_start (3-bit), SNR-weighted CSMA-style contention-window rebroadcast delay, optional learned next-hop since v2.5 | Flood-to-discover path, then **source-routed** reply using a recorded path (path bytes embedded in packet); dedup by content hash (path-independent), 160-entry ring buffer | Host-side `Transport`: announce-based path tables, store-and-forward, no on-device routing at all — RNode is a dumb modem | +| **Encryption** | AES-CTR (no auth tag), PSK = literal channel key bytes; **default "LongFast" key is hardcoded/public in source** | Public-channel PSK also a known default; DMs use per-contact ECDH (Ed25519→X25519) secret; **routing metadata (hashes/path) is cleartext, payload only is encrypted** — repeaters relay without decrypting | Ed25519 (signing) + X25519 (ECDH, per-packet *and* per-link) + AES-256-CBC + HMAC-SHA256; destination = truncated SHA-256 hash, not directly tied to a readable identity | +| **Node identity** | NodeNum from device MAC | Self-generated Ed25519 keypair, persisted to flash | Identity = Ed25519+X25519 keypair; Destination = hash derived from identity + app name | +| **Firmware structure** | One monolithic Arduino/PlatformIO app; maintainers confirm extracting just the mesh layer "would be a lot of refactoring" — RadioLib is the only cleanly reusable piece | `src/` is a genuine shared library (radio HAL, routing, crypto, identity) used by all role binaries (`examples/{companion_radio,simple_repeater,...}`) — more modular than Meshtastic | RNode firmware (device) is a thin modem; all protocol logic lives in host-side RNS (Python) or the C++ port **microReticulum** (ESP32/nRF52, PlatformIO) | +| **Documented host-bridge API** | ToRadio/FromRadio protobuf stream over Serial/BLE/TCP ("Client API") — official Python/JS/Android/iOS clients all use this | Companion Radio protocol over BLE (Nordic UART-style) / USB serial, official JS (`meshcore.js`) + Python (`meshcore_py`) clients | KISS-framed serial protocol (FEND/FESC escaping, command opcodes for freq/BW/SF/CR/TX/RX), same approach archy's existing `reticulum.rs` already validated against canonical source | +| **Reference ESP32 boards** | Heltec V3, T-Beam (Supreme), Station G2 | Same boards — Heltec V3, T-Beam, Station G2, XIAO+Wio-SX1262 | Same boards again — T-Beam, Heltec V3 all RNode-flashable | + +**Open items still unconfirmed** (verify in source before relying on them): +exact MeshCore sync word / RadioLib default; MeshCore's exact AES cipher mode +(sources disagree: AES-128 vs AES-128-ECB vs AES+HMAC); whether any two +protocols' *current* real-world default configs ever overlap enough for one +static radio config to receive both. + +--- + +## 2. The central constraint: one radio, three networks + +The Heltec V3 has **exactly one SX1262**. A LoRa radio only demodulates a +packet if it's currently configured with the matching frequency, bandwidth, +spreading factor, and sync word. Since Meshtastic, MeshCore, and Reticulum +each use independently-configured (and, per the table above, currently +non-overlapping) PHY parameters, **one radio cannot simultaneously listen to +all three networks**. This is true regardless of how clever the software is — +it's a property of the radio hardware, not the firmware. + +That leaves two real options for a single-board device: + +**A. Time-multiplexed single radio.** Rotate the SX1262 through three PHY +configs on a schedule (e.g. 1-2s dwell per network), queueing outbound +traffic between windows. Pros: works on a stock, unmodified Heltec V3. +Cons: it's a *probabilistic* repeater, not an always-on one — it will miss +packets transmitted by other nodes while parked on a different network's +config, and rebroadcast latency goes up on all three networks. Network +participants assume their peers are always listening; a node that's only +half the time will look unreliable to them (more so on networks like +Meshtastic where missed CSMA windows + flood timing matter). + +**B. Add cheap radio breakouts.** The ESP32-S3 has multiple SPI-capable +GPIO pairs; 1-2 additional SX1262 breakout modules (~$5-10 each) wired to +spare CS/IRQ/RESET lines turn the single Heltec V3 board into a host MCU +with **one dedicated radio per network**, each always listening, no time- +sharing. This keeps "one device" but is no longer a stock/unmodified board — +it requires soldering/wiring. RAM (512KB, no PSRAM) is the harder limit here: +running three protocols' framing/routing/crypto state concurrently as +FreeRTOS tasks, lean builds will be required regardless (drop WiFi entirely, +likely BLE too, cap NodeDB/path-table sizes). + +**Recommendation: start with A as the v1 proof of concept** (cheapest, zero +extra hardware, validates the bridging/translation logic), and treat B as +the v2 hardware revision once the time-multiplexed version proves the +software design is sound and someone decides duty-cycled coverage isn't +good enough. + +--- + +## 3. Recommended phased plan + +**Phase 0 — De-risk the bridge logic, not the hardware squeeze.** +Before fighting RAM and single-radio limits at all, build the +message-translation/repeater logic across **three separate official-firmware +boards** (e.g. 3 spare ESP32 LoRa boards, one flashed stock Meshtastic, one +stock MeshCore companion-radio, one RNode), each talking its own documented +serial/BLE client API to a host (a Pi or PC). This validates "can we +correctly receive a message on network A and re-originate/repeat it on +networks B and C" without touching firmware internals or memory limits at +all — pure host-side bridge code against three already-working radios. + +**Phase 1 — Single Heltec V3, time-multiplexed (Option A).** +Port the host-side bridge logic from Phase 0 onto the Heltec V3 itself, +replacing "three separate official boards" with "one onboard SX1262 +re-tuned on a schedule." This is where Meshtastic/MeshCore/RNode-equivalent +framing+crypto needs to be reimplemented in lean, embeddable form (most +realistic source for that: MeshCore's `src/` since it's already a reusable +library; microReticulum for the Reticulum side; for Meshtastic, RadioLib +directly plus a minimal from-scratch reimplementation of just the 16-byte +header/AES-CTR/dedup logic documented in ARCHITECTURE — not the full +Meshtastic app). + +**Phase 2 — Multi-radio hardware revision (Option B), if duty-cycled +coverage proves insufficient.** Add dedicated radio breakouts so each +network gets always-on reception; revisit RAM budget with leaner per- +protocol implementations informed by what Phase 1 actually needed. + +--- + +## 4. Why this is more tractable than "reimplement three mesh protocols" + +All three ecosystems independently converged on the same pattern: **don't +reimplement the radio/routing stack — run the real firmware (or a real +library) and talk to it over a documented protocol.** That's the basis for +Phase 0, and de-risks the project considerably versus a from-scratch clean- +room implementation of three different mesh routing algorithms. + +The harder, protocol-native reimplementation work (needed for Phase 1's +single-radio squeeze, since there's no separate module to "talk to" — it's +all one firmware image) is least risky for MeshCore (genuinely modular +`src/`) and Reticulum (microReticulum already exists as a C++ port, +though missing Link/Resource/Channel), and highest risk for Meshtastic +(monolithic firmware, no officially separable mesh library — would mean +reimplementing the 16-byte header / AES-CTR / PacketHistory dedup / +SNR-weighted CSMA rebroadcast logic from spec, documented above, rather +than reusing existing code). + +--- + +## 5. Next steps + +- [ ] Confirm MeshCore's actual sync word / cipher mode against `src/Utils.cpp` and the RadioLib config (open item from §1) +- [ ] Phase 0 prototype: pick 3 spare boards, flash stock firmware each, write host-side bridge translating one test message across all three +- [ ] Decide on a minimal common internal message format for the bridge (sender id, origin network, payload, hop/path metadata) that all three protocol adapters translate to/from +- [ ] Revisit RAM budget once Phase 1's actual per-protocol footprint is known