You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
661 lines
26 KiB
661 lines
26 KiB
# FreeDMR 2.0 Architecture Decisions
|
|
|
|
This file records architectural decisions, requirements, assumptions and open
|
|
questions driven out during design discussion. It is intended as source material
|
|
for a later formal FreeDMR 2.0 design document.
|
|
|
|
## Project Philosophy
|
|
|
|
FreeDMR is open-source, open, intentionally understandable and intentionally
|
|
simple enough to encourage community implementation, experimentation and
|
|
operation by radio amateurs.
|
|
|
|
HBLink proved that a DMR server could be written in an open, readable way
|
|
without DMR being gatekept by commercial vendors. FreeDMR takes the next step:
|
|
it proves that a DMR network can be built this way without central control.
|
|
Before HBLink and FreeDMR, DMR server software and server-level network
|
|
membership were typically closed, gatekept or dependent on personal/team
|
|
approval. FreeDMR exists in part to lower that barrier and give radio amateurs
|
|
choice and freedom to experiment with global-scale ROIP networking.
|
|
FreeDMR does not need to gatekeep all private experimentation. The project
|
|
controls public listing: the process by which servers are shared with Pi-Star
|
|
and other HBP hotspots as legitimate public access servers. A sysop can run a
|
|
private server under their own DMR ID and arrange gatewaying with an existing
|
|
sysop, who effectively vouches for that traffic. Public listing has additional
|
|
requirements such as connectivity quality, sysop contactability and basic
|
|
operational expectations.
|
|
|
|
The FreeDMR mesh design is influenced by the late Bob Bruninga's APRS ideas,
|
|
Spanning Tree Protocol and related distributed-network approaches. The project
|
|
also has a social purpose: bringing together communities and people connected
|
|
to earlier amateur-radio networking work. FreeDMR is therefore both a technical
|
|
system and a diplomacy project; design choices must respect operational
|
|
autonomy, interoperability and trust between independent sysops.
|
|
|
|
FreeDMR is successful because it works in the amateur-radio sense: it is best
|
|
effort, experimental, approachable and deployable on ordinary low-cost systems
|
|
such as cheap VPS instances and Raspberry Pi-class hardware. It is not intended
|
|
to be a safety-assured commercial system. FreeDMR 2.0 should improve quality,
|
|
clarity and scalability without losing the ham-spirit/hacker-philosophy traits
|
|
that made the network useful and welcoming.
|
|
|
|
Design implications:
|
|
|
|
- Prefer clear, inspectable protocols over opaque mechanisms.
|
|
- Keep the implementation understandable by competent sysops and contributors.
|
|
- Keep the barrier to compatible implementations low where possible.
|
|
- Preserve low-cost deployment and modest hardware requirements.
|
|
- Avoid architectural choices that make FreeDMR dependent on heavyweight
|
|
infrastructure for ordinary single-server operation.
|
|
- Treat reliability as best-effort resilience appropriate to amateur radio, not
|
|
as commercial safety assurance.
|
|
- Preserve server autonomy and local policy.
|
|
- Avoid unnecessary central control.
|
|
- Distinguish private operation, vouched/gatewayed traffic and public listing.
|
|
- Security should protect authenticity and network integrity without hiding
|
|
amateur-radio traffic.
|
|
|
|
## Protected Model
|
|
|
|
The protected asset is the FreeDMR operating model, not the old HBLink-derived
|
|
object structure.
|
|
|
|
Preserve:
|
|
|
|
- packet model and protocol behaviour
|
|
- dial-a-TG semantics
|
|
- TG/DMR-ID centric routing
|
|
- loop control
|
|
- source quench
|
|
- mesh behaviour
|
|
- practical RF/network tolerance learned from live servers and real RF links
|
|
- "everything everywhere" principle, subject to documented exceptions
|
|
|
|
Replace or redesign where useful:
|
|
|
|
- configured `MASTER` stanza as primary runtime identity
|
|
- proxy-mediated client fan-out
|
|
- global mutable `BRIDGES` structure as authoritative state
|
|
- custom dashboard/reporting socket protocol
|
|
- packet-path coupling to dashboard/API/report consumers
|
|
|
|
## Layer Model
|
|
|
|
FreeDMR 2.0 should be described as layered:
|
|
|
|
- **Access layer**: client/server access protocols such as HBP today and
|
|
possible future non-trunk client protocols. Owns login/auth/options/keepalive,
|
|
client sessions, slot state and RF-facing TG presentation.
|
|
- **Subscription layer**: talkgroup conference membership. Owns direct TG
|
|
subscriptions, dial-a-TG subscriptions, static/default/user-activated
|
|
subscriptions, expiry and RF-visible TG to conference TG mapping.
|
|
- **Mesh layer**: inter-server FBP/OBP/trunk-style behaviour. Owns loop control,
|
|
source quench, hop/version handling and inter-server conference traffic.
|
|
- **Reporting layer**: local dashboard, API observers, logs, global lastheard
|
|
export and state snapshots. Reporting is observational and must not steer
|
|
packet handling.
|
|
|
|
## Reactor and Runtime Migration
|
|
|
|
Do not replace Twisted as part of the first FreeDMR 2.0 architecture work.
|
|
|
|
Decision:
|
|
|
|
- Keep Twisted's single-threaded reactor as a safety boundary initially.
|
|
- Extract and test the protocol/routing/subscription core behind deterministic
|
|
interfaces.
|
|
- Introduce explicit process/message boundaries only after the state model is
|
|
clear.
|
|
- Consider asyncio or another event loop only once Twisted has become a thin
|
|
transport shell around tested core logic.
|
|
|
|
Rationale:
|
|
|
|
- The current packet behaviour is subtle and validated through real RF/network
|
|
deployment.
|
|
- Replacing the event loop while also replacing the state model would mix too
|
|
many sources of behavioural change.
|
|
- Twisted's single-threaded reactor helps preserve current ordering assumptions
|
|
while bridge/subscription and reporting boundaries are made explicit.
|
|
- The first migration target is architectural clarity and scalability, not event
|
|
loop novelty.
|
|
|
|
## Identity Model
|
|
|
|
The configured master/listener is not the client identity.
|
|
|
|
FreeDMR 2.0 should move toward:
|
|
|
|
- listener identity: UDP socket/service instance
|
|
- client identity: DMR peer/client ID
|
|
- subscription identity: client ID + slot + RF-visible TG + conference TG
|
|
- mesh identity: server/peer/network ID
|
|
|
|
Server identity hierarchy:
|
|
|
|
- FreeDMR server IDs are 4-digit DMR IDs.
|
|
- Server sub-IDs are 5-digit IDs derived from the server ID space.
|
|
- Each sysop/server identity may therefore cover up to 10 server sub-IDs for
|
|
backend components, larger deployments, failover or fault-tolerant layouts.
|
|
- Identity verification should cover the base server ID and its authorized
|
|
sub-IDs rather than requiring unrelated credentials for each sub-ID.
|
|
|
|
A single master/listener UDP port should serve an arbitrary number of clients
|
|
directly, replacing the proxy where possible.
|
|
|
|
## Talkgroup Subscription Model
|
|
|
|
Conceptually, each TG is a conference bridge. Clients subscribe to conference
|
|
TGs. FreeDMR does not primarily decide where to send user traffic; users choose
|
|
the traffic they want to hear by subscription.
|
|
|
|
Subscriptions can be:
|
|
|
|
- direct TG: RF-visible TG equals conference TG
|
|
- dial-a-TG: RF-visible TG is currently TG9, conference TG is the selected TG
|
|
- alias/rewrite: RF-visible TG may be any configured TG, conference TG is the
|
|
FreeDMR network identity
|
|
|
|
Example:
|
|
|
|
```python
|
|
TalkgroupSubscription(
|
|
client_id=2345001,
|
|
slot=2,
|
|
conference_tg=4400,
|
|
rf_tg=9,
|
|
mode="dial",
|
|
active=True,
|
|
)
|
|
```
|
|
|
|
The invariant is:
|
|
|
|
```text
|
|
conference_tg = FreeDMR network/conference identity
|
|
rf_tg = client-facing RF presentation identity
|
|
```
|
|
|
|
This makes arbitrary TG rewrites possible without making TG9 structurally
|
|
special.
|
|
|
|
## Bridge Table Replacement
|
|
|
|
The legacy `BRIDGES` dict should be replaced internally by subscription-oriented
|
|
state and indexes. The `"#"` reflector naming convention does not need to be
|
|
preserved internally; it can be a compatibility/export detail.
|
|
|
|
Recommended hot-path structures:
|
|
|
|
- `dict` / `set` for O(1)-style local lookups
|
|
- `typing.NamedTuple` keys for readable hash keys
|
|
- `dataclass(slots=True)` records for mutable subscription/session state
|
|
- `heapq` for expiry timers using lazy invalidation
|
|
|
|
Recommended indexes:
|
|
|
|
```python
|
|
subscriptions_by_conference_tg[conference_tg] -> set[SubscriptionKey]
|
|
subscription_by_rf[(client_id, slot, rf_tg)] -> SubscriptionKey
|
|
subscriptions_by_client_slot[(client_id, slot)] -> set[SubscriptionKey]
|
|
expiry_heap -> (expires_at, generation, SubscriptionKey)
|
|
```
|
|
|
|
Packet handlers should not scan all subscriptions/bridges to find routing
|
|
targets.
|
|
|
|
## Packet Plane vs Control Plane
|
|
|
|
The packet plane is delay-sensitive.
|
|
|
|
Packet-plane rules:
|
|
|
|
- local in-memory hot state only
|
|
- no external database round trips
|
|
- no blocking API/dashboard/report calls
|
|
- no cross-process lock waits
|
|
- no dependency on reporting consumers being connected
|
|
|
|
External stores may be used for:
|
|
|
|
- config distribution
|
|
- API/dashboard state
|
|
- control-plane coordination
|
|
- snapshots
|
|
- global lastheard export
|
|
- optional clustering/multi-process coordination
|
|
|
|
General performance principle:
|
|
|
|
- Expensive processing should be considered for offload to separate processes
|
|
because CPython execution is constrained by the GIL for CPU-bound Python code.
|
|
- Offload is appropriate for reporting fanout, global export, dashboard
|
|
aggregation, historical database writes, heavy analytics, expensive
|
|
transcoding/codec experiments and non-critical maintenance jobs.
|
|
- Offload boundaries must be asynchronous from the packet path. If an offload
|
|
worker is slow or unavailable, packet handling must continue with local state.
|
|
- Do not offload hot-path routing decisions if doing so would add inter-process,
|
|
network or lock waits to every packet.
|
|
|
|
## DMR Data Packet Policy
|
|
|
|
FreeDMR must maintain DMR data packet forwarding support.
|
|
|
|
Decision:
|
|
|
|
- FreeDMR should forward supported DMR data packets according to the same
|
|
conference/subscription and mesh principles as other traffic.
|
|
- There must be no regression in existing data packet forwarding support.
|
|
- FreeDMR core should not become an application-level DMR data processor.
|
|
- GPS, SMS and similar application processing should be implemented by systems
|
|
connected via FBP or another mesh/access-adjacent interface.
|
|
- `DATA_GATEWAY` is understood as an earlier expression of this model: an FBP
|
|
link that carries data-oriented traffic rather than ordinary voice traffic.
|
|
- Existing `SUB_MAP` behaviour is intentional: data addressed to a DMR ID can be
|
|
routed toward the last known HBP/client location for that DMR ID.
|
|
|
|
Core FreeDMR may inspect/classify data packets only as needed for:
|
|
|
|
- packet admission and protocol validation
|
|
- routing/subscription decisions
|
|
- loop control and source quench
|
|
- reporting/logging
|
|
- preserving packet bytes and metadata across FBP/HBP boundaries
|
|
- maintaining the subscriber location map needed for data-client routing
|
|
|
|
Possible narrow exceptions:
|
|
|
|
- dial-a-TG control via DMR SMS
|
|
- DMR SMS alerts from a server to a sysop
|
|
|
|
Any such exceptions must be explicit control-plane features and must not turn
|
|
FreeDMR core into a general GPS/SMS application processor.
|
|
|
|
## Mesh Peer Authentication
|
|
|
|
FreeDMR should only accept mesh/FBP traffic from servers that can be validated
|
|
as legitimate members of the network.
|
|
|
|
Core principle:
|
|
|
|
- FreeDMR may sign/authenticate traffic and control messages, but should not
|
|
encrypt amateur-radio traffic or mesh traffic by default.
|
|
- Amateur radio is public in most jurisdictions and encryption is often not
|
|
permitted. FreeDMR users may also carry IP backhaul over amateur radio links.
|
|
- FreeDMR's security model is authenticity, integrity, membership validation and
|
|
local policy enforcement, not secrecy.
|
|
- This follows the existing FreeDMR principle, agreed historically by project
|
|
maintainers, that the network has nothing to hide and should remain cleartext.
|
|
|
|
Identity/listing distinction:
|
|
|
|
- Signed mesh identity should prove a server/sysop identity or a vouching
|
|
relationship. It should not automatically imply public listing.
|
|
- Public listing is a directory/discovery decision for clients and HBP hotspots.
|
|
- A public access server may need stronger operational requirements than a
|
|
private or gatewayed server.
|
|
- Local sysops may still choose whether to carry/vouch for traffic from private
|
|
servers, even when those servers are not publicly listed.
|
|
- If an individual 7-digit DMR ID is used as a server identity, traffic may pass
|
|
when a directly connected/listed sysop chooses to allow and gateway it.
|
|
- The vouching sysop is accountable to their peers for traffic they forward. If
|
|
that traffic harms the network, peers may choose to stop peering with the
|
|
vouching server. This preserves a self-policing social mechanism without
|
|
requiring central control for all private experimentation.
|
|
|
|
Analogue network bridges:
|
|
|
|
- Analogue ROIP/network bridges commonly connect as if they are DMR clients via
|
|
HBP.
|
|
- FreeDMR permits this and is generally more permissive than many other DMR
|
|
networks.
|
|
- FreeDMR works with/supports the DVSwitch community on this. DVSwitch provides
|
|
a common mechanism by which analogue networks can be bridged into DMR-style
|
|
access.
|
|
- These bridges are operationally sensitive: technical limitations can make
|
|
them effectively listen-only, consuming CPU and bandwidth while adding little
|
|
value if they do not contribute actual two-way user activity.
|
|
- Analogue bridges are often implemented using audio mixing/conference style
|
|
behaviour. This is a poor fit for DMR and similar digital modes, which enforce
|
|
one audio source at a time and rely on stream, hang-time and contention
|
|
behaviour rather than mixed audio.
|
|
- This mismatch comes partly from analogue repeater heritage: analogue systems
|
|
may maintain a continuous transmit carrier and mix notification sounds such as
|
|
pips, CWID and courtesy tones into the output audio. Analogue systems also
|
|
often have little or no strong source identity, whereas DMR traffic carries a
|
|
DMR ID.
|
|
- A common failure mode is that a feed from an analogue repeater keeps the DMR
|
|
stream open between analogue overs, plays courtesy/notification tones and then
|
|
carries the next analogue user in the same held stream. This can hold the TG
|
|
open and prevent a digital station from breaking in until the analogue
|
|
repeater times out and its carrier drops.
|
|
- Analogue bridges should therefore be subject to local sysop policy, public
|
|
listing expectations and peer accountability. Permitted does not mean
|
|
automatically valuable or immune from peering/listing consequences.
|
|
|
|
Other digital network bridges:
|
|
|
|
- Digital voice networks such as YSF and NXDN are generally a better technical
|
|
match for DMR than analogue networks because they also use AMBE-family vocoder
|
|
audio.
|
|
- AMBE-to-AMBE interworking can be lossless at the codec level and avoids
|
|
transcoding artifacts.
|
|
- Transcoding from analogue or unlike codecs can degrade audio quality
|
|
significantly and should be treated carefully.
|
|
|
|
Desired direction:
|
|
|
|
- Add PKI-backed mesh peer admission to the Bridge Control (`BCXX`) mechanism.
|
|
- A peer server presents public identity material signed by a FreeDMR network
|
|
master key or trusted network CA.
|
|
- The authenticated identity must bind at least:
|
|
- server ID
|
|
- authorized server sub-IDs
|
|
- public key
|
|
- validity period
|
|
- permitted protocol/features where useful
|
|
- Runtime admission should bind the authenticated server identity to the
|
|
observed transport endpoint, including IP address.
|
|
- If the observed IP address changes, the FBP peer must perform a new key
|
|
exchange/authentication step before its traffic is forwarded.
|
|
- Network membership should be represented by a signed sysop/server key that is
|
|
issued when the sysop/server joins the network and revoked when they leave or
|
|
are compromised. Runtime endpoint/session bindings are renewed separately and
|
|
do not require re-signing the long-lived membership key.
|
|
- One successful verification of the signed identity should authorize the
|
|
covered server ID and declared/authorized sub-IDs for that sysop, subject to
|
|
local policy and endpoint/session binding.
|
|
|
|
Packet-plane rule:
|
|
|
|
- Expensive signature/certificate validation happens during control-plane
|
|
admission or re-admission, not for every DMR packet.
|
|
- Per-packet mesh traffic should use a cached authenticated peer/session state
|
|
check keyed by server ID and endpoint.
|
|
|
|
Initial conceptual flow:
|
|
|
|
```text
|
|
FBP peer connects/sends keepalive
|
|
-> BC auth exchange presents signed server identity/public key
|
|
-> FreeDMR validates signature against trusted network key
|
|
-> FreeDMR binds server_id + endpoint + protocol features to peer session
|
|
-> DMR traffic is accepted only while that authenticated binding is valid
|
|
```
|
|
|
|
Security requirements:
|
|
|
|
- Reject unauthenticated FBP traffic by default once this mode is enabled.
|
|
- Reject traffic where server ID, key identity and source endpoint do not match
|
|
the authenticated binding.
|
|
- Expire authenticated bindings and require renewal.
|
|
- Support soft renewal: when an authenticated binding reaches its renewal
|
|
timestamp, schedule asynchronous re-authentication while allowing a bounded
|
|
grace period so in-flight voice is not interrupted purely by renewal timing.
|
|
- Hard-stop forwarding only for explicit authentication failure, revoked
|
|
identity/key, endpoint mismatch outside policy, expired grace period, or
|
|
policy requiring immediate re-authentication.
|
|
- Log authentication failure reasons clearly without leaking private material.
|
|
- Provide a controlled transition mode for existing networks while PKI is rolled
|
|
out.
|
|
|
|
Open questions:
|
|
|
|
- Whether to use X.509 certificates, raw Ed25519 public keys with signed
|
|
metadata, or another compact identity format.
|
|
- How network master keys/CAs are generated, rotated and revoked.
|
|
- Whether peer authorization policy should live in config, MQTT/control-plane
|
|
state, or a signed network membership list.
|
|
- How to handle legitimate dynamic-IP servers without weakening endpoint
|
|
binding.
|
|
- What renewal and grace-period defaults best preserve voice continuity without
|
|
weakening mesh admission.
|
|
|
|
### Distributed Key Gossip Option
|
|
|
|
FreeDMR may also use a peer-to-peer signed-key dissemination mechanism over the
|
|
Bridge Control (`BCXX`) out-of-band channel.
|
|
|
|
Concept:
|
|
|
|
- Each server periodically advertises the signed server public keys/membership
|
|
documents it knows to its direct FBP peers.
|
|
- Peers validate the signatures and build a local table of legitimate server
|
|
identities as knowledge propagates through the mesh.
|
|
- Each server uses its local signed-key table and local policy to decide whether
|
|
to route or reject packets that originated from a given source server, even
|
|
when that source server is not directly connected.
|
|
|
|
Rationale:
|
|
|
|
- FreeDMR is a peer network, not hub-and-spoke or master/slave.
|
|
- Servers are autonomous and independently operated.
|
|
- Direct FBP peers should not be blindly trusted to make correct routing
|
|
decisions on behalf of the local server.
|
|
- Open-source, human-readable code deliberately lowers the barrier to
|
|
modification, so each server must be able to protect itself from incorrect or
|
|
malicious upstream forwarding decisions.
|
|
|
|
Security requirements for key gossip:
|
|
|
|
- Only signed membership documents are accepted; peers cannot create trust by
|
|
merely repeating a key.
|
|
- Membership documents need issuer, subject server ID, public key fingerprint,
|
|
authorized sub-IDs, validity period, serial/version and signature.
|
|
- Revocation data must propagate by the same or a stronger mechanism.
|
|
- Each server must enforce local policy after validation. A valid signed key
|
|
proves membership, not mandatory carriage.
|
|
- Key gossip must be rate-limited and bounded so it cannot become a BCXX flood
|
|
or memory-growth vector.
|
|
- Received membership data must be replay-resistant enough to handle expiry,
|
|
superseded serials and revoked keys.
|
|
- The packet path must use cached key/policy state; signature validation and
|
|
gossip processing are control-plane work.
|
|
|
|
This complements direct-peer endpoint authentication. Direct-peer auth proves
|
|
the connected FBP peer is legitimate for this session; distributed signed-key
|
|
knowledge lets the local server make autonomous decisions about traffic whose
|
|
source server is elsewhere in the mesh.
|
|
|
|
## Reporting Protocol Decision
|
|
|
|
FreeDMR 2.0 should define a structured reporting event protocol and use MQTT as
|
|
the preferred external live reporting transport.
|
|
|
|
Rationale:
|
|
|
|
- MQTT is already familiar in DMR network dashboard/reporting contexts.
|
|
- BrandMeister uses MQTT, providing a useful precedent for dashboard consumers.
|
|
- MQTT topics map naturally to server/client/subscription/call state.
|
|
- Retained messages are useful for current state snapshots.
|
|
- Last Will and Testament can represent server/reporting disconnects.
|
|
- MQTT-over-WebSocket allows browser dashboards to subscribe directly when the
|
|
broker supports it.
|
|
|
|
Constraints:
|
|
|
|
- MQTT publishing must be asynchronous from the packet worker.
|
|
- Packet routing must continue if the MQTT broker/dashboard is down.
|
|
- Event generation must be state-change/summary oriented, not per DMR frame.
|
|
- The event schema is the compatibility contract; internal Python objects are
|
|
not.
|
|
- Local live dashboard and central global lastheard remain separate paths.
|
|
- Voice stability takes precedence over reporting completeness. If the system
|
|
must choose between dropping/reporting-losing events and delaying packet
|
|
handling, it must drop or coalesce reporting events.
|
|
|
|
Implementation requirement:
|
|
|
|
```text
|
|
packet path -> non-blocking local event queue -> MQTT publisher worker
|
|
```
|
|
|
|
The packet path must not call an MQTT broker synchronously. The local event
|
|
queue should be bounded. On overflow, the publisher layer should drop or
|
|
coalesce low-priority events and emit a later reporting-health event rather than
|
|
blocking packet handling.
|
|
|
|
Suggested event priority:
|
|
|
|
- retain/coalesce latest state: server/client/slot/subscription state
|
|
- keep best effort: call start/end summaries
|
|
- drop first under pressure: high-volume debug/warning/statistical updates
|
|
|
|
MQTT publishing should support reconnect with exponential backoff and should
|
|
refresh retained state after reconnect so a dashboard can recover even if
|
|
transient events were missed.
|
|
|
|
Suggested MQTT namespace:
|
|
|
|
```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:
|
|
|
|
```text
|
|
server state
|
|
client state
|
|
slot activity
|
|
subscription state
|
|
mesh peer state
|
|
```
|
|
|
|
Use non-retained messages for transient events:
|
|
|
|
```text
|
|
call start/end
|
|
loop-control event
|
|
source-quench event
|
|
packet-rate/loss summary
|
|
warnings
|
|
```
|
|
|
|
Example event:
|
|
|
|
```json
|
|
{
|
|
"version": 2,
|
|
"event_id": 1849281,
|
|
"type": "call.started",
|
|
"timestamp": 1710000000.123,
|
|
"server_id": 234099,
|
|
"client_id": 2345001,
|
|
"slot": 2,
|
|
"conference_tg": 4400,
|
|
"rf_tg": 9,
|
|
"source_id": 2351234,
|
|
"stream_id": 16909060,
|
|
"access": "hbp"
|
|
}
|
|
```
|
|
|
|
Dashboard delivery options:
|
|
|
|
- preferred: dashboard subscribes to MQTT over WebSocket
|
|
- alternative: local reporting sidecar translates MQTT to SSE/HTTP
|
|
- control actions should use authenticated HTTP APIs unless a future UI needs
|
|
bidirectional streaming
|
|
|
|
## Local Dashboard vs Global Lastheard
|
|
|
|
Each FreeDMR server has its own local live dashboard. The global lastheard
|
|
service is centrally hosted and non-real-time.
|
|
|
|
Local dashboard:
|
|
|
|
- consumes local MQTT live state/events
|
|
- displays current client/repeater traffic
|
|
- must tolerate reconnects and missed transient events by reloading retained
|
|
state topics
|
|
|
|
Global lastheard:
|
|
|
|
- consumes call summaries or batched exports
|
|
- should not depend on packet-plane or dashboard delivery
|
|
- should tolerate central outage via spool/retry
|
|
|
|
Possible MQTT global feed:
|
|
|
|
- Each server publishes local live dashboard topics to a local broker or local
|
|
reporting service.
|
|
- Prefer a separate exporter process for the curated global feed. The exporter
|
|
subscribes to the same local real-time MQTT feed as the dashboard, filters and
|
|
summarizes what is needed, then publishes to the network MQTT broker or writes
|
|
to the global collector.
|
|
- The exporter publishes only summary topics needed for the 30-day database,
|
|
such as call end summaries, client/server presence, selected mesh health and
|
|
selected subscription changes.
|
|
- Raw packet events and high-volume live slot updates should not be exported to
|
|
the global broker by default.
|
|
- Central broker, global dashboard or exporter failure must not back up into
|
|
local packet processing or local dashboard state.
|
|
|
|
Preferred flow:
|
|
|
|
```text
|
|
FreeDMR core -> local MQTT feed -> local dashboard
|
|
-> global-exporter process -> network MQTT/collector
|
|
```
|
|
|
|
Core publishing invariant:
|
|
|
|
- FreeDMR core emits each reporting event once to its configured local MQTT
|
|
broker/publisher queue.
|
|
- Fanout to dashboards, exporters, automation and global collectors is handled
|
|
by the MQTT broker and separate subscriber processes.
|
|
- Adding more reporting consumers must not increase FreeDMR packet-process work
|
|
beyond the single local event emission.
|
|
|
|
Suggested global MQTT subjects:
|
|
|
|
```text
|
|
freedmr/v2/global/{server_id}/call/end
|
|
freedmr/v2/global/{server_id}/client/state
|
|
freedmr/v2/global/{server_id}/server/state
|
|
freedmr/v2/global/{server_id}/mesh/state
|
|
```
|
|
|
|
## Reporting Event Types
|
|
|
|
Initial event families:
|
|
|
|
```text
|
|
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
|
|
loop.detected
|
|
packet.rate_limited
|
|
```
|
|
|
|
## Open Questions
|
|
|
|
- Which MQTT broker should be packaged by default: Mosquitto, EMQX, NATS MQTT
|
|
compatibility, or another option?
|
|
- Should MQTT be mandatory for FreeDMR 2.0 dashboards, or optional with an
|
|
embedded/local fallback?
|
|
- What authentication/authorization model should protect MQTT topics and
|
|
dashboard control APIs?
|
|
- What retained-topic expiry policy should be used to prevent stale state?
|
|
- Should global lastheard consume MQTT directly or use a separate HTTP/queue
|
|
exporter fed from reporting events?
|
|
- Should FreeDMR expose a legacy `BRIDGES` compatibility view during migration?
|