Architecture

How Perch & Coop fit together

Two programs, on purpose

radio ──USB/TCP/BLE──▶ ┌──────────────┐   REST + SSE   ┌───────────┐
                       │     COOP     │ ◀────────────▶ │   PERCH   │──▶ your browser
mqtt broker ──────────▶│  the engine  │                │ dashboard │    or the app
                       └──────┬───────┘                └───────────┘
                              ▼ write-through
                       mesh.db (SQLite, WAL)

Coop owns everything volatile: the radio link, decode/decrypt, capture. Perch is a stateless viewer over Coop's API. The split is why viewing never risks the capture, why a crashed dashboard costs nothing, and why remote/multi-radio setups are configuration rather than architecture.

The packet's journey

  1. Arrive — from the radio (serial/TCP/BLE) or the MQTT observer.
  2. Decrypt if needed — the radio decodes its own channels; Coop's keyring retries everything else (other channels, MQTT traffic) and tags the matching channel.
  3. Decode — text, nodeinfo, position, telemetry, routing ACKs, neighbor info, waypoints, detection alerts; request/reply correlation wakes any waiting traceroute or position probe.
  4. Record — into bounded in-memory rings (recent packets/messages/signal) and the roster map, deduped against relays and MQTT echoes.
  5. Persist — write-through to SQLite, off the hot path.
  6. Broadcast — one SSE event to every subscriber; slow clients drop frames rather than block the pipeline.

Why the capture survives anything

SQLite in WAL mode, one writer, synchronous=NORMAL: power loss loses at most the final moments, never the file. The radio's own memory is irrelevant — history lives on disk from the moment a packet is heard. Restarts rehydrate the dashboard from the file; retention purges + vacuums on a 6-hour cycle.

Build & portability choices