Real-time Reticulum network observer — REST + WebSocket API
Listens to Reticulum announce packets, builds a live peer model with topology and optional GPS (from Sideband/NomadNet announces), and exposes it via a clean REST and WebSocket API.
Use this as the data layer for any dashboard or tool that needs live visibility into a Reticulum mesh network — without shelling out to rnpath or depending on reticulum-mcp.
Part of the rns-atak-bridge suite.
reticulum-mcp and similar tools shell out to rnpath -t and parse text output. rns-mesh-observer uses the native RNS Python library directly, capturing real-time announce events and streaming updates over WebSocket.
Improvements over polling-based approaches:
- Real-time: peer updates pushed to clients immediately via WebSocket
- GPS-aware: extracts lat/lon from Sideband announce app_data
- Topology graph: derives D3-compatible edges from path table next_hop data
- Clean API: no parsing, no subprocess, typed JSON responses
- Standalone: no external services required beyond a running
rnsd
rnsd (Reticulum daemon)
│ announces + path table
▼
rns-mesh-observer
├── PeerStore (thread-safe in-memory peer model)
├── REST API GET /peers, /topology, /health, /announces
└── WebSocket ws://host:8024/ws (real-time peer events)
Requirements: Python 3.11+, a running rnsd reachable over TCP.
git clone https://github.com/sansscott/rns-mesh-observer
cd rns-mesh-observer
pip install -r requirements.txt
python observer.pydocker compose up -d
docker logs -f rns-mesh-observerrns:
peer_host: 127.0.0.1 # IP of your rnsd TCP server
peer_port: 4242
observer:
port: 8024 # API port (default 8024, avoids conflict with reticulum-mcp on 8023)
peer_expiry_minutes: 10 # Remove peers not seen in N minutes
announce_buffer_size: 100 # Keep last N announce events in memory| Method | Endpoint | Returns |
|---|---|---|
GET |
/health |
{status, peer_count, uptime_s} |
GET |
/peers |
[{hash, hops, interface, last_seen, name?, lat?, lon?}] |
GET |
/peers/{hash} |
Single peer or 404 |
GET |
/topology |
{nodes:[...], links:[...]} — D3-compatible graph |
GET |
/announces |
Last N raw announce events |
WS |
/ws |
Real-time event stream |
curl http://localhost:8024/peers | jq '.[].hash'curl http://localhost:8024/topology{
"nodes": [
{"id": "local", "hops": 0, "name": "This Node"},
{"id": "af60a4f4...", "hops": 1, "name": "SidebandUser", "lat": 41.7, "lon": -74.0}
],
"links": [
{"source": "local", "target": "af60a4f4...", "interface": "TCPClientInterface", "hops": 1}
]
}Connect to ws://host:8024/ws to receive real-time peer events:
{"type": "snapshot", "data": [{...peer...}, ...]} // sent on connect
{"type": "peer_new", "data": {peer}}
{"type": "peer_update", "data": {peer}}
{"type": "peer_expire", "data": {"hash": "..."}}
{"type": "announce", "data": {hash, timestamp, ...}}const ws = new WebSocket('ws://192.168.1.204:8024/ws');
ws.onmessage = (e) => {
const {type, data} = JSON.parse(e.data);
if (type === 'peer_new') console.log('New peer:', data.hash);
};{
"hash": "af60a4f4863c9bff...",
"hops": 2,
"interface": "TCPClientInterface",
"next_hop": "1234abcd...",
"last_seen": 1740000000.0,
"last_seen_ago": 12.3,
"expires": 1740000600.0,
"expired": false,
"name": "SidebandUser",
"lat": 41.7001,
"lon": -74.0
}Change RETICULUM_API from http://host:8023 to http://host:8024 and update response parsing to the schema above. Add a WebSocket client to get real-time push instead of polling.
The /topology endpoint returns a D3-ready graph suitable for a force-directed network visualization panel.
- rns-atak-bridge — Reticulum SA tracks in ATAK
- rns-lxmf-atak-chat — LXMF messages → ATAK GeoChat
- Reticulum — the mesh networking stack
MIT