Add Phase 0 host-bridge: relay text across Meshtastic, MeshCore, Reticulum
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""Shared message bus + loop-prevention core for the archy-messh Phase 0 bridge.
|
||||
|
||||
Each protocol adapter runs on its own thread/event loop (Meshtastic's pubsub
|
||||
callbacks fire from its internal reader thread, MeshCore is asyncio-native,
|
||||
Reticulum/LXMF invoke callbacks from RNS's internal transport thread). All
|
||||
three push into this single thread-safe queue so one dispatcher can fan
|
||||
messages out across protocols without each adapter needing to know about
|
||||
the others.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class BridgeMessage:
|
||||
network: str # "meshtastic" | "meshcore" | "reticulum"
|
||||
sender: str
|
||||
text: str
|
||||
received_at: float
|
||||
|
||||
|
||||
class MessageBus:
|
||||
"""Thread-safe inbox with content-hash dedup to prevent repeat loops.
|
||||
|
||||
Dedup is content-hash based (not per-network packet-id based), since the
|
||||
whole point of the bridge is that the same text shows up natively on all
|
||||
three networks once relayed. A short TTL window is enough to stop the
|
||||
obvious loop (bridge re-hears its own relayed copy) without needing any
|
||||
cross-protocol message-id scheme, which doesn't exist.
|
||||
"""
|
||||
|
||||
def __init__(self, dedup_ttl_seconds: float = 300.0):
|
||||
self._queue: "queue.Queue[BridgeMessage]" = queue.Queue()
|
||||
self._dedup_ttl = dedup_ttl_seconds
|
||||
self._seen: dict[str, float] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@staticmethod
|
||||
def _content_hash(text: str) -> str:
|
||||
return hashlib.sha256(text.strip().encode("utf-8")).hexdigest()
|
||||
|
||||
def publish(self, message: BridgeMessage) -> None:
|
||||
self._queue.put(message)
|
||||
|
||||
def is_duplicate(self, text: str) -> bool:
|
||||
h = self._content_hash(text)
|
||||
now = time.monotonic()
|
||||
with self._lock:
|
||||
self._seen = {k: v for k, v in self._seen.items() if v > now}
|
||||
if h in self._seen:
|
||||
return True
|
||||
self._seen[h] = now + self._dedup_ttl
|
||||
return False
|
||||
|
||||
def get(self, timeout: float | None = None) -> BridgeMessage:
|
||||
return self._queue.get(timeout=timeout)
|
||||
Reference in New Issue
Block a user