Grafana
Grafana gets telemetry two ways: a live push straight from the middleman (this page), and whatever historical dashboards you build against the InfluxDB datasource, which is operator-configured and not part of this repo.
Grafana Live push
transport/udp_receiver.py pushes every parsed packet to Grafana inline, synchronously, with no queue in between. It's the lowest-latency path in the whole system, at the cost of blocking the receive loop for the duration of the HTTP call.
Session lifecycle
A Grafana session opens on the first packet after the stream becomes active, if the Grafana sink is enabled, and closes when the stream goes inactive or the sink is disabled. It reopens automatically on the next qualifying packet.
Full-row snapshots from a split stream
The car splits its stream into a fast message (type 1, 20 Hz) and a slow message (type 2, 5 Hz) to save bandwidth. Grafana Live needs a full row on every push, so the receiver keeps a rolling snapshot dict: every field seen on any packet updates the dict, and each push serializes the merged snapshot, not just the fields carried by that one packet. The dict is cleared whenever the active session changes, so a new run doesn't inherit stale slow-block values from the previous one.
Auth and encoding
Requests carry a bearer token (GRAFANA_API_KEY) and Content-Type: text/plain. The body is standard InfluxDB line protocol (measurement field=value,field=value... timestamp_ns), built fresh per packet from schema.GRAFANA_FIELDS.
Grafana errors don't stop the pipeline
A failed push (bad status code or a request exception) is counted and logged, not retried. InfluxDB and Marple still get the packet through their own queues regardless of what happens to the Grafana push.
Connection limits
GF_LIVE_MAX_CONNECTIONS in .env caps concurrent Live subscribers on the Grafana container (200 in the shipped compose file). Raise it if more dashboards or viewers need to watch the same channel at once.
