#!/usr/bin/env python3 """archy-messh Phase 0 host-bridge. Connects to one radio per network (Meshtastic, MeshCore, Reticulum/RNode), each over its official client protocol, and relays text messages between all three. See docs/ARCHITECTURE.md for why this is Phase 0 (prove the bridging logic on 3 separate boards) rather than Phase 1 (single time-multiplexed radio on one Heltec V3). NOT YET TESTED END-TO-END — written without physical hardware attached to the dev machine. Needs Meshtastic + MeshCore + RNode-flashed boards to validate; each adapter's wire-level behavior is only as good as its upstream library's docs/examples (cited in each adapter's docstring/header). Usage (see README.md for full option list): python3 main.py \\ --meshtastic-port /dev/ttyUSB0 \\ --meshcore-port /dev/ttyUSB1 \\ --reticulum-storage ./rns-storage """ import argparse import sys import time from adapters.meshcore_adapter import MeshCoreAdapter from adapters.meshtastic_adapter import MeshtasticAdapter from adapters.reticulum_adapter import ReticulumAdapter from bridge_core import BridgeMessage, MessageBus def build_arg_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(description="archy-messh Phase 0 tri-protocol bridge") p.add_argument("--meshtastic-port", help="Meshtastic serial device (e.g. /dev/ttyUSB0)") p.add_argument("--meshtastic-host", help="Meshtastic TCP host, instead of serial") p.add_argument("--meshcore-port", help="MeshCore serial device (e.g. /dev/ttyUSB1)") p.add_argument("--meshcore-host", help="MeshCore TCP host, instead of serial") p.add_argument("--meshcore-channel", type=int, default=0, help="MeshCore public channel index") p.add_argument( "--reticulum-storage", default="./rns-storage", help="LXMF/RNS storage directory" ) p.add_argument( "--dedup-ttl", type=float, default=300.0, help="Seconds to suppress repeat-content loops" ) return p def main() -> int: args = build_arg_parser().parse_args() bus = MessageBus(dedup_ttl_seconds=args.dedup_ttl) def on_message(network: str, sender: str, text: str) -> None: bus.publish(BridgeMessage(network=network, sender=sender, text=text, received_at=time.time())) meshtastic = MeshtasticAdapter(on_message, port=args.meshtastic_port, host=args.meshtastic_host) meshcore = MeshCoreAdapter( on_message, port=args.meshcore_port, host=args.meshcore_host, channel_idx=args.meshcore_channel, ) reticulum = ReticulumAdapter(on_message, storage_path=args.reticulum_storage) adapters = { MeshtasticAdapter.NETWORK: meshtastic, MeshCoreAdapter.NETWORK: meshcore, ReticulumAdapter.NETWORK: reticulum, } print("Connecting to Meshtastic...") meshtastic.connect() print("Connecting to MeshCore...") meshcore.connect() print("Connecting to Reticulum...") reticulum.connect() print("All three adapters connected. Bridging...") try: while True: message = bus.get() if bus.is_duplicate(message.text): continue print(f"[{message.network}] {message.sender}: {message.text}") for network, adapter in adapters.items(): if network == message.network: continue # don't echo back onto the network it came from try: adapter.send(f"[{message.network}/{message.sender}] {message.text}") except Exception as exc: # noqa: BLE001 print(f" ! failed to relay onto {network}: {exc}", file=sys.stderr) except KeyboardInterrupt: pass finally: meshtastic.close() meshcore.close() reticulum.close() return 0 if __name__ == "__main__": raise SystemExit(main())