Acquisition and ingestion — device to database
Devices publish metrics, coordinates and binary data over MQTT. A Go bridge separates them into Redis Streams; workers decode Protobuf and DAQ samples, batch data and persist before acknowledging consumption. Ingestion keys deduplicate redelivered metrics and positions. Pending-message recovery and quarantine isolate failures while keeping durable ingestion separate from live distribution.
Time series — contextual history
TimescaleDB stores metric name, value, timestamp and session in hypertables; PostGIS stores geographic positions. Chart history filters variables and time range within the device’s latest session, averages values per second and sends time/value arrays. The screen starts with persisted data and appends new samples while retaining the experiment’s temporal reference.
Live charts — WebSocket and Protobuf
After persistence and ACK, workers publish binary data through Redis Pub/Sub. A relay forwards frames to per-device Django Channels groups; the WebSocket consumer applies selected filters and distinguishes history from new data. Vue decodes ArrayBuffer into series: history replaces the dataset and live samples append to it. Updates are grouped every 500 ms and incremental series retain up to 2,500 points per variable. Alarm events update the list alongside the charts.
Fleets and geography — SSE and maps
A separate Server-Sent Events flow delivers unit status and positions. A Pinia store receives an initial snapshot, updates units from the stream and attempts reconnection after interruptions. Leaflet presents locations and tracks while Chart.js presents time-series signals. Map SSE and experiment WebSocket use distinct contracts; each update does not require a full page reload.
Ingestion health — source-derived metrics
HealthSnapshots collects Redis Streams, Celery queues, Redis statistics and psutil. Backlog combines group pending messages and lag; the entry rate is estimated from cumulative counter differences between snapshots, rather than database insert counts. Dead letters, MQTT client counts from $SYS topics, queue lengths and process-visible resources feed the panel. Slow sources are read concurrently with timeouts; unavailable metrics remain unknown instead of becoming zero.
Observability — traces, logs and operational history
OpenTelemetry instruments Django, Celery, Redis, requests and psycopg2; spans are exported in batches over OTLP. Backend JSON logs include trace/span IDs, while Go services use structured slog with repeated-error rate limiting. The aggregator queries Prometheus for spans/s, error ratio, duration p99 and 24-hour history. The integration uses an OTLP collector and Prometheus series compatible with the aggregator queries.
System dashboard — SSE snapshots and connection state
The API streams health snapshots over SSE with a target cadence of 2 seconds, adjusted for collection time. Vue SystemHealth displays current indicators and historical charts; its consumer handles fragmented SSE frames, reconnects after stream loss and warns about potentially stale data. The enrichment panel adds periodically queried API usage, cache and error metrics. Device telemetry lives in TimescaleDB; operational history for this panel comes from Prometheus.
Other services — management, configuration and reports
Django/DRF manages devices, firmware, certificates, experiments and fleets. An A2L parser prepares acquisition configuration; report plugins query TimescaleDB and generate Matplotlib PDFs. The C++/MDF logger is a separate archival path. Docker Compose and GitLab CI support execution and delivery.