# Reporting Model Decision: FreeDMR 2 replaces the legacy dashboard/report socket model with a new structured reporting event model. The existing dashboard is not a compatibility constraint for the FreeDMR 2 core. It must be updated separately or supported by an optional out-of-process adapter. Reporting is observational only. Packet routing must not depend on dashboard/report consumers. The v2 event schema is the compatibility contract. Raw `BRIDGES`/`SYSTEM` state should not be exposed as the primary v2 API. Old dashboard/report socket event names should not shape the FreeDMR 2 core. Any old dashboard compatibility must live in a sidecar/adapter, not inside packet routing. FreeDMR 1.x/current code remains live until FreeDMR 2 is ready, so FreeDMR 2 can make a clean reporting break. ## Preferred Transport MQTT is the preferred external live reporting transport. Architecture: ```text packet path -> non-blocking bounded local event queue -> MQTT publisher worker -> local broker/feed ``` Constraints: - MQTT publishing must be asynchronous from the packet worker. - Use a bounded queue. - The bounded local event queue is the only coupling from packet path to reporting worker. - Drop or coalesce low-priority events under pressure. - Emit a later reporting-health event rather than blocking packet handling. - Voice stability takes precedence over reporting completeness. - Reconnect with exponential backoff. - Refresh retained state after reconnect. - Reporting backpressure must be visible through reporting-health events but must not delay DMR packets. Reporting is the first major candidate for out-of-process execution. The MQTT publisher should be an independent worker or sidecar where practical. The global lastheard exporter should be a separate process. Dashboard aggregation should not run in the packet hot path. Reporting worker crash must not affect packet routing. Reporting worker restart should refresh retained state after reconnect. ## Local Dashboard and Global Lastheard Local dashboard: - Consumes local MQTT live state/events. - Displays live client/repeater/server traffic. - Recovers from retained state after reconnect. Global lastheard: - Central/non-real-time. - Consumes summaries, not packet-plane traffic. - Should preferably be fed by a separate exporter process. - Central outage must not affect local packet handling or local dashboard. Preferred flow: ```text FreeDMR core -> local MQTT feed -> local dashboard -> global-exporter process -> network MQTT/collector ``` ## Initial Event Families - `server.started` - `server.stopping` - `client.connected` - `client.disconnected` - `client.options_changed` - `subscription.activated` - `subscription.deactivated` - `subscription.expired` - `call.started` - `call.ended` - `call.lost` - `mesh.peer_up` - `mesh.peer_down` - `mesh.source_quench` - `mesh.stun` - `loop.detected` - `packet.rate_limited` - `reporting.queue_overflow` - `reporting.publisher_disconnected` - `reporting.publisher_reconnected` - `reporting.events_dropped` ## Suggested MQTT Topics ```text freedmr/v2/{server_id}/state freedmr/v2/{server_id}/client/{client_id}/state freedmr/v2/{server_id}/client/{client_id}/slot/{slot}/activity freedmr/v2/{server_id}/subscription/{subscription_id}/state freedmr/v2/{server_id}/call/{stream_id}/start freedmr/v2/{server_id}/call/{stream_id}/end freedmr/v2/{server_id}/mesh/{peer_id}/state freedmr/v2/{server_id}/event ``` Use retained messages for current state and non-retained messages for transient events. ## Example Events ```json { "event": "server.started", "server_id": "2345", "version": "2.0-dev", "time": "2026-05-24T12:00:00Z" } ``` ```json { "event": "client.connected", "server_id": "2345", "client_id": 2345001, "listener": "hbp-public", "endpoint": "198.51.100.10:62031", "time": "2026-05-24T12:00:01Z" } ``` ```json { "event": "subscription.activated", "server_id": "2345", "subscription_id": "2345001-2-9-4400", "client_id": 2345001, "slot": 2, "rf_tg": 9, "conference_tg": 4400, "mode": "dial", "source": "dial-a-tg", "time": "2026-05-24T12:00:02Z" } ``` ```json { "event": "call.started", "server_id": "2345", "stream_id": 12345678, "client_id": 2345001, "slot": 2, "source_id": 2345678, "rf_tg": 9, "conference_tg": 4400, "source": "hbp", "time": "2026-05-24T12:00:03Z" } ``` ```json { "event": "call.ended", "server_id": "2345", "stream_id": 12345678, "reason": "terminator", "duration_ms": 18420, "packets": 614, "time": "2026-05-24T12:00:21Z" } ``` ```json { "event": "call.lost", "server_id": "2345", "stream_id": 12345678, "reason": "timeout", "last_seen_ms_ago": 7000, "time": "2026-05-24T12:00:28Z" } ``` ```json { "event": "mesh.source_quench", "server_id": "2345", "peer_id": "2350", "stream_id": 12345678, "conference_tg": 4400, "reason": "duplicate-source", "time": "2026-05-24T12:00:04Z" } ``` ```json { "event": "reporting.queue_overflow", "server_id": "2345", "dropped_events": 42, "queue_limit": 2048, "policy": "drop-low-priority", "time": "2026-05-24T12:00:05Z" } ``` ## Open Questions - Broker packaging and defaults: Mosquitto, embedded broker, external broker, or optional dependency. - Exact retained-state expiry policy. - Authentication model for MQTT clients. - Whether legacy dashboard compatibility is a supplied sidecar or a separate dashboard migration task.