- vendor orlp/ed25519 (lib/ed25519, MIT — the exact lib MeshCore uses) for Curve25519 ECDH key exchange; switch identity/advert signing to it (same seed -> same pubkey, so peers still recognise us; sigs are byte-identical) - store pubkeys from heard MeshCore adverts (contacts table) so a DM's 1-byte src_hash can be resolved to a full sender key - decode PAYLOAD_TYPE_TXT_MSG: ECDH shared secret, HMAC-SHA256 MAC check, AES-128-ECB decrypt, parse timestamp+text, drive the message modal - verified end-to-end: real MeshCore node -> "hello from HP Pro Desk" decoded Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
firmware (Phase 1)
Embedded firmware for the single-device 3-in-1: one Heltec WiFi LoRa 32 V3
(ESP32-S3 + one SX1262) time-multiplexing its single radio across the
Meshtastic / MeshCore / Reticulum PHY configs. See ../docs/ARCHITECTURE.md
§2-§3 for why Phase 1 is a time-multiplexed single radio (Option A).
Current increment: RTC scanner + acid display + Meshtastic decode
src/main.cpp rotates the single SX1262 through each network's PHY preset
(Reticulum / meshTastic / meshCore = RTC), listens per dwell
window, logs every raw packet, and decodes Meshtastic on-chip (AES-128-CTR
via mbedtls + protobuf → text / node names). The 128×64 OLED shows an animated
acid-house UI: a smiley (that periodically freaks out — spins, stripes, eyes +
mouth open), a punk RTC header, three live network rows (packet counts + RX
blips), and the last decoded event.
Device identity Reticutasticore / RTC is baked in (DEVICE_NAME) as the
name this device will present on all three networks once the TX/participation
layer lands — so a message addressed to it will be routable.
Validated on hardware (2026-07-01):
- radio inits clean, all three presets cycle, display renders.
- Meshtastic: live broadcast packets at −34 to −44 dBm; on-chip decode
matches the offline reference (
tools/meshtastic_decode.py, verified against real captures — decoded node "Arch Optiplex"/"ARCH"). - MeshCore: received real packets too — so the
0x12sync-word guess is correct (was an open item). - Reticulum: silent — placeholder PHY config + likely no RNode nodes near.
Still RX-only: no bridging or transmit yet.
Open items baked into the config table
- MeshCore sync word (
src/main.cpp,CONFIGS[]) is a guess (0x12, RadioLib's private default). Confirm against MeshCoresrc/— this is the same open item as../docs/ARCHITECTURE.md§1/§5. - Reticulum PHY has no protocol-mandated preset; it's operator-configured. The entry is a placeholder — set it to your actual RNS RNode interface config or that window hears nothing.
- Meshtastic frequency is set to the documented 869.525 MHz, but Meshtastic hashes the channel name to a slot within the region plan, so real deployments may sit on a different slot.
Build / flash / monitor
Requires PlatformIO (pip install platformio). Versions are pinned in
platformio.ini (espressif32 7.0.1, RadioLib 7.7.1) — do not float them.
pio run # compile
pio run -t upload --upload-port /dev/cu.usbserial-0001 # flash (adjust port)
pio device monitor -b 115200 # watch the scan
Planned: message-received animation
When a text message arrives, play a short intro before showing it: the striped smiley spins upside-down / rotated, little lightning bolts flash, then a Meshtastic-style message modal slides in with the sender + text. (Spec'd, not yet built.)
Roadmap from here
- RX decode per protocol — turn logged raw packets into
{sender, text}(Meshtastic 16-byte header + AES-CTR; MeshCore framing; Reticulum via microReticulum). Start with Meshtastic since we already receive it. - On-device bridge/dedup — port the Phase 0
bridge_corerelay+dedup logic to C++. - TX — re-originate a decoded message onto the other two networks within their dwell windows (queue between windows).
- Revisit dwell timing / RAM budget with real per-protocol state (§3).