A centralized management and monitoring system for DMR (Digital Mobile Radio) repeaters running MMDVMHost or HBlink. RepeaterManager aggregates real-time call logs and system status from multiple repeaters via MQTT, stores observations in a local SQLite database, and streams live updates to browser clients using Server-Sent Events (SSE).
RepeaterManager consists of three main components:
-
mmdvmhost_logmon.py-- A log monitoring daemon that runs on each repeater. It parses MMDVMHost log output (from log files viapyinotifyor from the systemd journal) and publishes structured JSON status messages to a central MQTT broker. -
repeater_manager.py-- The central aggregation server. It subscribes to MQTT topics from all repeaters, persists observations to a SQLite database, and fans out live data to connected browser clients. -
web_server.py-- An asyncio-based web server (usingaiohttp) that serves a browser UI and streams real-time repeater status to clients via SSE.
This project is an early prototype and work in progress. See the feature status below:
| Feature | Status |
|---|---|
mmdvmhost_logmon log parsing and MQTT publishing |
Working |
| SSE fanout to browser clients | Working |
| MQTT-based log transport | Working |
| Call log / system log persistent storage | Partial (~60%) |
| Web UI for historical and streaming data | Partial (~10%) |
| Direct HBlink support (without MMDVMHost) | Partial (~5%) |
| WebSocket fallback for browsers without SSE | Not started |
| Raw TCP log transport | Not started |
+------------------+ +------------------+
| Repeater 1 | | Repeater 2 |
| (MMDVMHost) | | (MMDVMHost) |
| mmdvmhost_logmon +----->| mmdvmhost_logmon |---+
+------------------+ | +------------------+ |
| |
v v
+------+------+ MQTT Broker |
| linux.spencerfowler.com |
+------+------+-------------------+
|
v
+---------+---------+
| repeater_manager |
| (MQTT subscriber) |
+---+----------+----+
| |
v v
+-----+---+ +--+--------+
| localdb | | WebServer |
| (SQLite) | | (aiohttp) |
+----------+ +--+--------+
|
v (SSE)
+-----------+
| Browser |
| Clients |
+-----------+
Each repeater publishes to topics based on its repeater ID and timeslot:
<repeater_id>/info-- Repeater configuration and metadata (retained)<repeater_id>/1-- Timeslot 1 status updates<repeater_id>/2-- Timeslot 2 status updates
Idle state:
{"Status": "Idle"}Active transmission:
{"Status": "TX", "Origin": "Net", "CallType": "Group", "Destination": "31131", "Source": "N4NQV", "Mode": "Voice"}Transmission end (with statistics):
{"Status": "Idle", "Origin": "Net", "Loss": "0%", "CallType": "Group", "Destination": "31131", "Source": "N4NQV", "Length": "4.5s", "Mode": "Voice", "BER": "0.0%"}Repeater info (published on startup):
{"RxFreq": "440000000", "TxFreq": "445000000", "Power": "50", "Latitude": "35.123", "Longitude": "-80.456", "Height": "100", "Location": "Somewhere, NC", "Callsign": "N4NQV", "ColorCode": "1", "Version": "1.5.2"}- Python 3
- paho-mqtt -- MQTT client library
- aiohttp -- Async HTTP server framework
- aiohttp-sse -- Server-Sent Events extension for aiohttp
- SQLite 3 (included with Python)
- Python 2 (uses
Queueandcmp()which are Python 2 constructs) - paho-mqtt
- One of the following for log source:
- pyinotify -- For monitoring MMDVMHost log files on disk
- python-systemd -- For reading from the systemd journal (used when
pyinotifyis not available)
An MQTT broker must be running and accessible to both the repeaters and the central server. The default broker address is linux.spencerfowler.com. To change this, edit the mqttc.connect() call in both mmdvmhost_logmon.py and repeater_manager.py.
Central server:
pip3 install paho-mqtt aiohttp aiohttp-sseLog monitor (on each repeater):
pip install paho-mqtt pyinotify
# or, for systemd journal mode:
pip install paho-mqtt systemd-pythonCopy mmdvmhost_logmon.py to each repeater running MMDVMHost. The script reads the repeater ID from /etc/mmdvmhost.
File-based mode (using pyinotify):
python mmdvmhost_logmon.py /var/log/mmdvm/MMDVM-*.logSystemd journal mode:
If pyinotify is not installed, the script automatically falls back to reading from the systemd journal for the mmdvmhost.service unit.
python mmdvmhost_logmon.pypython3 repeater_manager.pyThis starts:
- An MQTT subscriber listening for repeater status on topics
+/info,+/1, and+/2 - A SQLite database (
repeater_manager.db) for persisting observations - A web server on
http://127.0.0.1:8080
Open a browser to http://127.0.0.1:8080. The page displays a live feed of repeater messages streamed via SSE.
The SQLite database (default_schema.sql) contains the following tables:
| Table | Purpose |
|---|---|
Devices |
Registered repeater devices with metadata |
Observations |
Individual log entries with timestamps, topics, and values |
ObservationTypes |
Configurable topic-pattern matching rules for categorizing observations |
RecordingSessions |
Tracks recording sessions with start/end timestamps |
The database uses WAL (Write-Ahead Logging) mode for concurrent read/write access from the main thread and the background recording worker thread.
RepeaterManager/
repeater_manager.py # Entry point: MQTT subscriber + web server orchestration
web_server.py # Async web server with SSE streaming
localdb.py # SQLite database wrapper with threaded write queue
mmdvmhost_logmon.py # MMDVMHost log parser and MQTT publisher (runs on repeaters)
default_schema.sql # SQLite schema for observation storage
static/
index.html # Browser-based UI for viewing live repeater data
LICENSE # GPLv3
There are no configuration files. Key settings are currently hardcoded:
| Setting | Location | Default |
|---|---|---|
| MQTT broker address | repeater_manager.py:57, mmdvmhost_logmon.py:311 |
linux.spencerfowler.com |
| Web server host/port | repeater_manager.py:62 |
127.0.0.1:8080 |
| Database filename | repeater_manager.py:59 |
repeater_manager.db |
| Record queue size | localdb.py:18 |
500 |
| MMDVMHost config path | mmdvmhost_logmon.py:56 |
/etc/mmdvmhost |
This project is licensed under the GNU General Public License v3.0. See LICENSE for details.
Copyright 2019 Spencer Fowler