Architecture
The full path a message takes — from your device, through the broker, to your browser — and why the system is split the way it is.
The data path
A reading travels through a small number of stages, each with one job:
device
-> data broker (TLS MQTT, tenant-isolated by ACL)
-> processor (routes by data type)
- numb -> aggregator -> data store
- bool -> data store (immediate)
- status -> device twin
- config -> device twin
-> app backend (subscribes to what changed)
-> your browser (live, over a websocket)Each hop is decoupled by the broker, so a slow consumer never blocks a fast publisher, and any one stage can restart without the others noticing.
Why there are two brokers
The platform runs a data broker and a separate system broker. Your devices only ever touch the data broker — that's where your measurements, commands, and twin updates flow, under TLS and tenant isolation. The system broker is internal: it's how the platform's own services coordinate among themselves.
Keeping the two apart means device traffic and platform-control traffic can't interfere with each other, and a device never has a path to internal control topics. When the app needs to reach a device — an OTA notification, a server-issued action — it hands the request to the processor on the system side, and the processor is the one component bridging it onto the data broker. Devices can't address the system broker, and the system broker doesn't expose itself to them.
The services behind the dashboard
Behind the app backend sit a handful of focused services, each owning one slice of state:
- Data store — measurement history, bucketed into the retention tiers.
- Device twin — the live current-state snapshot for each device.
- Settings — your account configuration: rules, saved commands, aliases, OTA groups.
- OTA — firmware images and the upload/download endpoints.
- Mail — outbound email, SMS, and WhatsApp for rule actions.
- User management — accounts and MQTT credentials.
The same services back both the dashboard and the HTTP APIs. When you read the Data API, you're reading the data store the dashboard reads; when you publish through the Ingest API, your message joins the same broker path a device's would.
Tenant isolation
Every account is a tenant, and isolation is enforced at the broker, not just in the app. The broker's access rules tie a connection to its account's topic namespace, so a device literally cannot publish to or subscribe under another account's prefix — see Broker Configuration. The HTTP APIs resolve your auth key to your account before any request reaches a service, so the boundary holds whether you arrive over MQTT or over HTTPS.
Why split it at all
A single monolith would be simpler to draw and worse to live with. Splitting by job means the aggregator can be busy without slowing twin reads, OTA can serve a 4 MB binary without touching the measurement path, and the piece that sends your alert texts can fail and recover without dropping a single data point. For a platform aimed at projects that need to keep running, that independence is the point.
