← Alle Beiträge

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:

  1. REST: /v2/cluster/byName → ersetzt durch Filtern über /v2/cluster.
  2. 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

CommitBeschreibung
56a7f5cFix cluster lookup after Brandmeister API change
1e21e95Subscribe to LastHeard stream via join/everything on connect