BrandMeister LastHeard Bot wieder zum Laufen gebracht
Der Telegram-Bot, der den BrandMeister-Talkgroup 26239 (Cluster „NI Ost”)
überwacht und Aktivität in einen Telegram-Channel spiegelt, lief plötzlich
nicht mehr. Heute habe ich ihn in zwei Schritten repariert — beide Ursachen
lagen in zwischenzeitlichen Änderungen der BrandMeister-API.
Der Bot ist Open Source und lässt sich generisch für eine beliebige Talkgroup bzw. einen beliebigen Cluster konfigurieren: DK1DA/brandmeister-lastheard-telegram-bot. Die hier beschriebenen Fixes sind dort bereits eingeflossen.
Symptom 1: Crash direkt beim Start
Beim Start stürzte der Container sofort ab und ging in eine Restart-Schleife:
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
...
File "/app/bm-lh-26239.py", line 34, in <module>
bm_clustermasters_json = response.json()
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Ursache
Der Bot ermittelt die Repeater eines Clusters über die REST-API. Bisher geschah das über den Endpoint:
GET https://api.brandmeister.network/v2/cluster/byName?name=NI Ost
Dieser Endpoint existiert nicht mehr und liefert jetzt HTTP 404 mit einer
HTML-Fehlerseite. response.json() versucht dann, HTML als JSON zu parsen —
und scheitert mit dem JSONDecodeError oben.
Lösung
Statt der byName-Suche wird jetzt die komplette Cluster-Liste abgerufen und
clientseitig nach dem Namen gefiltert:
# Der frühere Endpoint /v2/cluster/byName?name=... existiert nicht mehr.
# Stattdessen liefert /v2/cluster die komplette Liste aller Cluster, aus der wir
# den passenden anhand des clusterName heraussuchen.
bm_clusterinfo_uri = "https://api.brandmeister.network/v2/cluster"
response = requests.get(bm_clusterinfo_uri)
bm_clusters_json = response.json()
# Den/die Cluster mit passendem Namen herausfiltern
bm_clustermasters_json = [c for c in bm_clusters_json if c["clusterName"] == cluster_to_watch]
if not bm_clustermasters_json:
print(f"Cluster '{cluster_to_watch}' nicht gefunden in der Brandmeister API")
Der nachgelagerte Members-Endpoint
(/v2/cluster/{id}/members → [0]["members"]) ist unverändert geblieben.
Für „NI Ost” (Cluster-ID 200010) werden so wieder die 14 Repeater geladen.
Commit:
56a7f5c — Fix cluster lookup after Brandmeister API change
Symptom 2: Verbindung steht, aber keine Nachrichten
Nach dem ersten Fix startete der Bot zwar sauber durch, aber es kamen keinerlei Nachrichten mehr im Telegram-Channel an. Die WebSocket-Verbindung schien nichts zu liefern.
Diagnose
Mit einem minimalen Test-Client (exakt nach dem offiziellen BrandMeister-Wiki-Beispiel) ließ sich das Verhalten von einer völlig anderen Maschine reproduzieren. Roher Mitschnitt der Engine.IO-Frames über 30 Sekunden:
Sending packet MESSAGE data 0{}
Received packet MESSAGE data 0{"sid":"..."} # Handshake OK
Received packet PING # Keep-Alive OK
# ...und sonst nichts. Kein einziges Daten-Frame.
Die Verbindung wird also sauber aufgebaut, der Namespace verbindet sich,
Ping/Pong hält sie stabil — aber der Server pusht null mqtt-Events.
Der entscheidende Hinweis kam aus dem Mitschnitt der Web-Oberfläche: Der Browser sendet direkt nach dem Connect zwei Events, die im Wiki-Beispiel fehlen:
-> 42["join","everything"]
-> 42["searchMongo",{"query":{"sql":""},"amount":200}]
Das join-Event mit dem Argument "everything" abonniert den Live-Stream.
Ohne dieses Abo bleibt die Verbindung offen, aber stumm. (searchMongo lädt nur
die History der letzten 200 Einträge und ist für den Live-Betrieb nicht nötig.)
Lösung
Im connect-Handler wird jetzt direkt nach dem Verbinden das join-Event
gesendet:
@sio.event
def connect():
print('connected to server')
# Seit der API-Umstellung muss der Stream aktiv abonniert werden, sonst
# haelt der Server die Verbindung zwar offen, sendet aber keine Daten.
sio.emit("join", "everything")
print('subscribed to LastHeard stream (join/everything)')
Verifiziert: Vorher 0 Events in 50 Sekunden, danach 145 Events in 12 Sekunden.
Commit:
1e21e95 — Subscribe to LastHeard stream via join/everything on connect
Fazit
Beide Probleme waren keine Bugs im Bot selbst, sondern Folgen stiller API-Änderungen bei BrandMeister:
- REST:
/v2/cluster/byName→ ersetzt durch Filtern über/v2/cluster. - WebSocket: Der LastHeard-Stream muss seit der Umstellung aktiv per
emit("join", "everything")abonniert werden — das offizielle Wiki-Beispiel ist veraltet.
Mit beiden Fixes spiegelt der Bot wieder zuverlässig die Talkgroup-Aktivität nach Telegram.
Heutige Commits
| Commit | Beschreibung |
|---|---|
56a7f5c | Fix cluster lookup after Brandmeister API change |
1e21e95 | Subscribe to LastHeard stream via join/everything on connect |