Dorian dbfdf1c851 fix: close relay loop from prefixed dedup mismatch
The dispatcher deduped on inbound text but relayed a prefixed copy
("[net/sender] text"), so a re-heard relayed message hashed differently
and was re-relayed, stacking another prefix each hop — an unbounded loop,
exactly what the dedup was meant to prevent.

Add MessageBus.mark_seen() and record the on-air relayed string before
sending, so the bridge recognises and drops its own relayed copies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:31:52 +01:00
..

host-bridge (Phase 0)

A host-side Python bridge that connects to one radio per network — Meshtastic, MeshCore, and Reticulum (via an RNode-flashed board) — over each project's official client protocol, and relays text messages between all three. See ../docs/ARCHITECTURE.md for why this is Phase 0 (prove the cross-network translation logic on 3 separate boards, no custom firmware) rather than Phase 1 (one time-multiplexed radio on a single Heltec V3).

Status

Written, not yet hardware-tested. Every library call has been checked against the upstream project's actual source/docs (see comments in each adapters/*.py file for what was verified and how), and all four dependencies (meshtastic, meshcore, rns, lxmf) install and import cleanly. What's unverified is the actual on-air behavior once real radios are attached — that requires the three boards described below.

What you need to test this for real

  • A Meshtastic-flashed board (e.g. Heltec V3) connected via USB serial or reachable over TCP (ESP32 WiFi build)
  • A MeshCore companion-radio-flashed board, serial or TCP
  • An RNode-flashed board (any RNode-compatible LoRa board) reachable via RNS's serial interface — configure this the same way archy's own core/archipelago/src/mesh/reticulum.rs KISS interface does, since this bridge uses the standard rns/lxmf Python packages, not a custom implementation
  • All three radios need actual antennas/RF range to each other's respective real-world networks to see any traffic worth relaying — this is not simulatable without a radio in the loop

Install

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Run

python3 main.py \
    --meshtastic-port /dev/ttyUSB0 \
    --meshcore-port /dev/ttyUSB1 \
    --reticulum-storage ./rns-storage

Use --meshtastic-host/--meshcore-host instead of the -port flags if a board is reachable over TCP (e.g. an ESP32 WiFi build) instead of USB serial. Run python3 main.py --help for the full flag list.

How relaying works

Each adapter pushes received text onto a shared, thread-safe queue (bridge_core.MessageBus). A single dispatcher loop in main.py pulls each message off, tags it with its origin network + sender, and re-sends it on the other two networks — never back onto the network it came from.

Content-hash dedup (MessageBus.is_duplicate, 5-minute TTL by default) stops the obvious relay loop (the bridge re-hearing its own relayed copy). This is a known-simple v0 approach — it can't distinguish two different messages that happen to have identical text within the TTL window, and it doesn't help if there are multiple archy-messh bridges active near each other. Good enough to prove the concept; revisit once Phase 1 needs something more robust.

Known gaps (by design, for v0)

  • Reticulum has no broadcast-channel primitive. Meshtastic has channel PSKs, MeshCore has public channel indices — Reticulum/LXMF messaging is point-to-point between Destinations. ReticulumAdapter fakes broadcast by tracking a roster of peer delivery-destinations learned from their LXMF announces and fanning out to all of them individually. A shared symmetric-key Reticulum GROUP destination would be a closer analog to a real channel — noted as a follow-up, not implemented here.
  • MeshCore channel messages carry no sender identity at the protocol level (verified against meshcore/reader.pyCHANNEL_MSG_RECV has no pubkey/name field). MeshCore apps convey sender identity by convention inside the message text itself; this bridge doesn't attempt to parse that out, so messages relayed from MeshCore show ? as the sender.