PTT-Dispatcher-Konsole
Eine kommerzielle Push-to-Talk-Dispatcher-Konsole, aus einem Proof of Concept zu Software neu aufgebaut, die ein Disponent eine ganze Schicht lang bedient.

Problem
Der Ausgangspunkt war ein Proof of Concept. Er konnte sich anmelden, einer Gruppe beitreten und einen Anruf aufbauen. Das reicht, um die Idee zu beweisen, und bei weitem nicht, um es einem Disponenten vorzusetzen.
Ein Disponent in der Leitstelle verlässt sich eine ganze Schicht lang auf diese Software, manchmal genau in dem Moment, in dem im Feld etwas schiefgegangen ist. Der Abstand zwischen einer Demo, die einen Anruf aufbaut, und einem Werkzeug, dem ein Disponent vertraut, ist das eigentliche Projekt: Sprache, die verständlich bleibt, wenn Feldteilnehmer im Mobilfunk sind, eine Rederechtssteuerung, die nie unklar lässt, wer gerade spricht, ein SOS, das den Disponenten erreicht und die Person in Not auf einer Karte verortet, und eine Wiederherstellung, die unsichtbar bleibt, wenn die Verbindung abbricht und zurückkommt.
Dazu kommt: Die Konsole spricht mit dem PTT-Backend über ein eigenes, internes Protokoll statt über einen fertigen Standard-Stack. Jede Schicht von der Leitung aufwärts musste gebaut und verantwortet werden.
Rahmenbedingungen
- Echtzeit, Halbduplex. Nur eine Person hält zur Zeit das Rederecht, und die Oberfläche muss das eindeutig zeigen und darf nie zwei Sprecher glauben lassen, dass sie es beide haben.
- Das Netz ist nicht freundlich: Feldteilnehmer sind im Mobilfunk, also muss der Sprachpfad Paketverlust und Reconnects überstehen, ohne die Sitzung abzubrechen.
- Es spricht ein eigenes, internes Protokoll mit dem PTT-Backend, kein SIP und keinen Standard-PTT-Stack. Das Produkt verantwortet jede Schicht von der Leitung aufwärts.
- Läuft eine ganze Schicht auf Windows-PCs in der Leitstelle und muss sich sauber von Netzabbrüchen und vom Abziehen von Audiogeräten mitten im Anruf erholen.
- Kommerzielle Software mit echten Nutzern, ausgeliefert als White-Label-Produkt, sodass dieselbe Codebasis sich in mehr als eine gebrandete Anwendung übersetzen lässt.
- Ein Disponent muss die ganze Lage auf einen Blick erfassen und mit einer einzigen Aktion sprechen, mit wenig oder gar keiner Schulung.
Architektur
- C++20 mit Qt 6 Widgets unter Windows. Eine dichte, andockbare Desktop-Konsole auf Basis des Qt Advanced Docking System, sodass sich jedes Panel (Nutzer und Gruppen, Karte, PTT, Nachrichten, Ereignisprotokoll, Wiedergabe, Geofencing) ausschwenken, in Tabs anordnen und umordnen lässt, wobei das Layout zwischen den Sitzungen gespeichert und wiederhergestellt wird.
- Das Kernnetzwerk sowie das Senden und Decodieren von Opus-/AMR-Sprache laufen außerhalb des GUI-Threads auf einem dedizierten Boost.Asio-io_context-Worker-Thread und erreichen die UI nur über Queued Signals und Slots, damit ein Netz-Aussetzer die Konsole nie einfriert.
- Eine eigene Protokollschicht verantwortet die Leitung: ein eigenes, binäres, längenpräfigiertes TLV-Protokoll (POC-Lite), das sowohl Signalisierung als auch Sprache über eine einzige TCP-Verbindung trägt, optional in TLS 1.2+ mit Certificate Pinning gehüllt, samt Heartbeat und automatischem Reconnect.
- Signalisierung und Sprachmedien teilen sich einen einzigen POC-Lite-Stream und werden als Command-Items gemultiplext. Ein separater WebSocket-Kanal überträgt Live-Standorte, die Wiedergabe von Talk-Bursts und Geofence-Updates, während HTTP-JSON-RPC Login und Gruppensteuerung übernimmt.
- Echtzeit-Sprach-Pipeline: Aufnahme und Wiedergabe über Qt Multimedia (QAudioSource und QAudioSink mit 8 kHz Mono), mit zwei wählbaren Codecs, standardmäßig AMR-NB und Opus (libopus 1.5.2) als Alternative, dazu softwareseitige Verstärkungsregelung sowie Live-Pegelanzeigen für Ein- und Ausgang.
- Model/View (QAbstractTableModel und Filter-Proxies) trägt die Live-Liste der Nutzer und Gruppen, Präsenz, Gruppenmitglieder, das Ereignisprotokoll und die Anrufhistorie, sodass hunderte Nutzer effizient aktualisiert werden, ohne die UI neu aufzubauen.
- Eingebettete Live-Karte mit den Standorten der Feldteilnehmer, gerendert mit Leaflet in einer Qt-WebEngine-Ansicht und über QWebChannel an C++ angebunden, mit vom Disponenten gezeichneten Geofences, Standorthistorie und Bewegungswiedergabe.
- Lokale Speicherung: SQLite für das Ereignisprotokoll, die Historie der Geofence-Übertritte und den Chatverlauf pro Konversation, sowie QSettings für die Konfiguration von Disponent und Anwendung.
Ergebnis
- Aus einem Proof of Concept ist kommerzielle Software geworden, die in echten Leitstellen läuft.
- Von einem Bildschirm aus sieht ein Disponent jeden Nutzer und jede Gruppe, spricht mit einer Person oder einer ganzen Gruppe, verfolgt Feldteilnehmer auf einer Karte, schickt ihnen Nachrichten, bearbeitet SOS- und Notfallmeldungen und gibt jeden aufgezeichneten Anruf wieder.
- Die Sprache hält echten Mobilfunknetzen stand, die Rederechtssteuerung bleibt auch unter Last korrekt, und die Sitzung stellt sich nach einem Abbruch von selbst wieder her.
- Von Anfang bis Ende selbst verantwortet: von Grund auf neu gebaut, jede Funktion von Hand ergänzt, von Login und Präsenz über die Kartendarstellung und das Geofencing bis zu Aufzeichnung und Wiedergabe.
- Wird als White-Label-Produkt ausgeliefert. Eine Codebasis übersetzt sich in mehrere gebrandete Dispatcher, und ein eingebauter Auto-Updater hält ausgerollte Clients aktuell.
Technologie
C++20, Qt 6 (Widgets, Multimedia, WebEngine, WebSockets, Sql) · AMR-NB und Opus (libopus 1.5.2) als Sprach-Codecs · Eigenes, binäres POC-Lite-TLV-Protokoll über TCP/TLS, dazu WebSocket und HTTP-JSON-RPC · Boost.Asio für Netzwerk und Audio außerhalb des GUI-Threads · Leaflet-Karte, eingebettet über Qt WebEngine und über QWebChannel angebunden · SQLite (Ereignisprotokoll, Geofence-Übertritte, Chatverlauf), QSettings · Qt Advanced Docking System, White-Label-Builds für Windows-Desktop
Vom Proof of Concept zum Produkt
Der Proof of Concept tat das eine, das die Idee beweist: anmelden, einer Gruppe beitreten, einen Anruf aufbauen. Das ist die einfache Achtzig-Prozent-Hälfte. Die übrigen zwanzig Prozent sind der ganze Grund, warum das Produkt existiert. Was der Disponent in dem Augenblick sieht, in dem er auf Sprechen drückt, und ob das Rederecht tatsächlich gewährt wurde. Wie sich die Konsole verhält, wenn die Verbindung stirbt und zurückkommt. Was passiert, wenn jemand das Headset mitten im Anruf abzieht. Was ein Disponent in dem Moment braucht, in dem ein SOS hereinkommt. Nichts davon zeigt sich in einer Demo, und alles davon entscheidet, ob ein Disponent dem Werkzeug vertraut.
Also habe ich es von Grund auf neu gebaut und Funktion für Funktion zu einem kommerziellen Produkt wachsen lassen: Präsenz und eine Live-Liste der Nutzer und Gruppen, Gruppen-, Einzel- und Dispatcher-Broadcast-Anrufe, eine Live-Karte der Feldteilnehmer mit Geofencing und Bewegungswiedergabe, Text- und Medien-Nachrichten, Anruf-Alerts, SOS- und Notfallbearbeitung, vollständige Anrufaufzeichnung mit durchsuchbarer Wiedergabe und die Reconnect-Logik, die es eine ganze Schicht lang verlässlich macht. Die interessante Arbeit war nie die Funktionsliste. Sie bestand darin, jede Funktion in einem echten Netz und einer echten Leitstelle standhalten zu lassen.
Echtzeit-Sprache und Rederecht richtig hinbekommen
Push-to-Talk ist Halbduplex: Eine Person hält das Rederecht, alle anderen hören zu. Das klingt simpel und ist der Teil, den man am ehesten genau richtig machen muss. Die Konsole muss eindeutig zeigen, wer gerade das Rederecht hält. Wenn der Disponent auf Sprechen drückt, aktualisiert sich die Oberfläche sofort: Die Taste drückt sich herunter, die Anzeige wechselt auf “Sie sprechen”, ein Startton ertönt, und das Mikrofon beginnt unmittelbar mit der Aufnahme. Der Client sendet die Sprechanforderung und startet die Aufnahme parallel, statt stumm auf eine Gewährung zu warten. Verweigert der Server das Rederecht, ertönt ein Fehlerton, und der Talk-Burst bricht ab.
Ich sollte präzise sein, wie die Rederechtssteuerung tatsächlich funktioniert, denn die ehrliche Version ist die glaubwürdige. Es ist keine lokale State-Machine-Engine. Der Client sendet beim Drücken speech-start und beim Loslassen speech-end und reagiert auf die Ablehnungscodes des Servers (Rederecht belegt, Nutzer beschäftigt, Zeit überschritten und so weiter). Die Arbitrierung liegt auf dem Server, und auf der Empfangsseite rastet ein Audio-Source-Manager pro Burst auf genau einen Sprecher ein und verwirft Konkurrenten, sodass der Disponent nie zwei Personen gleichzeitig hört.
Der Sprachpfad ist die andere schwierige Hälfte. Feldteilnehmer sind im Mobilfunk, also muss der Pfad Verlust und Reconnects überstehen. Qt Multimedia übernimmt Aufnahme und Wiedergabe mit 8 kHz Mono; darauf setzen zwei wählbare Codecs auf, standardmäßig das Schmalband-AMR-NB und Opus als Alternative, mit softwareseitiger Verstärkungsregelung und Live-Pegelanzeigen für Ein- und Ausgang. Ich will hier ehrlich sein, was die App tut und was nicht: Es gibt keinen Jitter-Buffer, keine akustische Echounterdrückung und keine Rauschunterdrückung, die die Anwendung obendrauf legt. Eingehende Pakete werden bei Ankunft decodiert und über ein kurzes Vorpufferung an den System-Audio-Sink ausgegeben. Die Robustheit kommt aus den Codecs, der Vorpufferung und selbstheilenden Audiogeräten, die sich neu öffnen, wenn ein Sink in den Leerlauf geht oder ein Headset abgezogen wird. Die zentrale Sprach- und Protokollarbeit läuft außerhalb des GUI-Threads auf einem Boost.Asio-Worker, denn das Einzige, was eine Dispatcher-Konsole nicht tun darf, ist zu stocken, während jemand zu sprechen versucht.
Mehr als Sprache: Karte, Geofences und Notfälle
Ein Disponent braucht das ganze Bild, nicht nur Audio. Die Konsole bettet eine echte interaktive Karte ein (Leaflet, das in einer Qt-WebEngine-Ansicht läuft und über QWebChannel an C++ angebunden ist), auf der jedes Feldfunkgerät als statusfarbiger Marker erscheint, der sich bewegt, sobald neue GPS-Positionen eintreffen. Disponenten können zwischen OpenStreetMap, Satellitenbildern und Google-Kacheln wechseln, die vergangenen Bewegungen jedes Nutzers als animierte Spur mit Wiedergabe-, Pause- und Geschwindigkeitssteuerung abspielen und kreisförmige oder polygonale Geofences pro Sprechgruppe zeichnen. Geofence-Übertritte werden in einer lokalen Datenbank protokolliert, als Windows-Toast-Benachrichtigungen angezeigt und lassen sich nach CSV exportieren.
Wenn etwas schiefgeht, übernimmt der Notfallpfad: Eingehende SOS-, Man-down- und Gruppennotfall-Meldungen lösen ein stets im Vordergrund liegendes Fenster aus, das einen Alarm ertönen lässt, anzeigt, wer in Not ist, und dessen Position an den exakten Koordinaten auf einer eingebetteten Karte verortet, mit Bestätigungs- und Stummschalt-Steuerung. Daneben gibt es Einzel- und Gruppennachrichten mit Bild-, Video- und Audioanhängen, Anruf-Alerts, die eine aufgezeichnete Sprachnotiz tragen können, und ein zentrales Ereignisprotokoll, das Logins, Talk-Bursts, Nachrichten, Präsenzänderungen, Geofence-Übertritte und Notfälle für Audit und Wiedergabe festhält.
Um ein eigenes Protokoll herum gebaut, als Produkt ausgeliefert
Die Konsole spricht mit dem PTT-Backend über ein eigenes, internes Protokoll statt über einen Standard-Stack. Ich habe diese Leitung hinter einer einzigen Protokollschicht gehalten: ein kompaktes, binäres, längenpräfigiertes TLV-Format (POC-Lite), das sowohl Signalisierung als auch Sprache über eine einzige TCP-Verbindung trägt, optional in TLS 1.2+ gehüllt, wobei das Server-Zertifikat auf einen bekannten Fingerprint gepinnt ist, authentifiziert über ein gehashtes Login und eine Token-basierte Re-Authentifizierung für den Stream. Ein Heartbeat überwacht die Verbindung, und der Client baut sie nach einem Abbruch von selbst wieder auf. Der Rest der Anwendung fasst nie rohe Bytes an; er reagiert stattdessen auf Signale: ein Nutzer ist online gegangen, das Rederecht wurde gewährt, ein Talk-Burst wurde aufgezeichnet.
Diese Grenze ist es, die es mir erlaubt hat, schnell weiter Funktionen zu ergänzen. Und weil es ein echtes Produkt ist, wird es als White-Label-Build ausgeliefert: Dieselbe Codebasis kompiliert sich in mehrere gebrandete Dispatcher, jeder mit eigenem Namen, Icon und Update-Server, aktuell gehalten durch einen eingebauten Auto-Updater, der beim Start nach einer neuen Version sucht, sie herunterlädt und an einen Helfer zur Installation übergibt. Die interessante Arbeit, von Anfang bis Ende, bestand darin, aus einer Demo, die einen Anruf aufbaut, etwas zu machen, auf das sich ein Disponent eine ganze Schicht lang verlassen kann.

