3.6 KiB
BrandMeister LastHeard Telegram Bot
A small bot that listens to the BrandMeister DMR "LastHeard" stream and posts a formatted message to a Telegram channel whenever a relevant transmission ends. It is useful for keeping a club or regional channel informed about activity on a specific Talkgroup or repeater cluster.
What it does
The bot connects to the BrandMeister LastHeard Socket.IO stream and reacts to
Session-Stop events. It sends a Telegram message when either of the
following matches:
- Mirror Talkgroup — the transmission's destination is the Talkgroup you set
via
TG_TO_WATCH. - TG8 cluster — the transmission is on TG8 and came in via a repeater that
belongs to the cluster named in
CLUSTER_TO_WATCH(only whenCLUSTER_WATCHis enabled). Cluster membership is resolved at startup from the BrandMeister API.
Only recent events (stopped within the last ~20 seconds) trigger a message, so old/replayed events are ignored.
Each Telegram message includes the source callsign, ID, name, talker alias, the repeater/hotspot used to access the network, slot, Talkgroup, master, RSSI and BER.
Requirements
- A Telegram bot token (create one via @BotFather) and a channel the bot can post to.
- Either Docker (recommended) or Python 3.12+.
Configuration
The bot is configured entirely through environment variables. Copy the example file and fill in your values:
cp .env.example .env
| Variable | Required | Description |
|---|---|---|
TG_TO_WATCH |
yes | Talkgroup ID to mirror to Telegram (e.g. 26235). |
CLUSTER_TO_WATCH |
yes | BrandMeister cluster name used for the TG8 check (e.g. Cluster Name). |
TELEGRAM_CHANNEL |
yes | Target channel/chat ID (e.g. @your_channel). |
TELEGRAM_API_KEY |
yes | Telegram bot token from BotFather. |
CLUSTER_WATCH |
no | true/false — enable the TG8 cluster check. Defaults to true. |
SLOT_EXCLUDE |
no | Comma-separated repeaterid:slot pairs that never count as a slot match (e.g. 262399:2,262383:2). |
TG8_EXCLUDE_REPEATERS |
no | Comma-separated repeater IDs whose TG8 traffic is ignored (e.g. 262399). |
Running
With Docker Compose (recommended)
docker compose up -d --build
The service restarts automatically unless explicitly stopped. View logs with:
docker compose logs -f
With plain Docker
docker build -t bm-lh .
docker run --env-file .env --restart unless-stopped bm-lh
Locally with Python
pip install -r requirements.txt
set -a && source .env && set +a # load env vars into the shell
python -u main.py
How it works
- On startup the bot queries the BrandMeister API (
/v2/cluster) to find the cluster matchingCLUSTER_TO_WATCH, then fetches its member repeaters (/v2/cluster/<id>/members). - It opens a WebSocket connection to the LastHeard stream
(
https://api.brandmeister.network, path/lh/socket.io) and subscribes by emittingjoinwitheverything. - For every incoming
mqttevent it parses the payload and decides whether to forward a Telegram message based on the rules described above. - The connection auto-reconnects on errors.
License
Released under the MIT License.