8.1.3. Kommunikation und Protokoll
Über dasselbe USB-Kabel laufen zwei unterschiedliche Datenwege. HID sendet Tastatur-, Medien- und Mausaktionen an den Computer. USB CDC bietet einen seriellen Kanal, über den die Desktop-Software Konfigurationen austauscht und Ereignisse empfängt.
Das hier beschriebene Geräteprotokoll hat Version 4. Die übertragenen Konfigurationen verwenden Schema 3. Beide Versionsnummern haben eine andere Bedeutung als die Revision einer konkreten Konfiguration.
Jede Nachricht ist ein UTF-8-JSON-Objekt auf genau einer Zeile, abgeschlossen mit einem LF-Zeilenumbruch. Die Verbindung verwendet nominal 115200 Baud, 8 Datenbits, keine Parität und ein Stoppbit. Bei USB CDC ist die Baudrate eine nominelle Einstellung. Der Client setzt DTR und hält den Port geöffnet.
Eine Anfrage besitzt eine numerische id und einen command. Die Antwort wiederholt die ID. ok zeigt Erfolg oder Fehler an. Bei Erfolg enthält value das Ergebnis, bei Fehler erklärt error die Ursache. Der Client wartet auf die Antwort, bevor er die nächste Anfrage stellt. Nach einer Subscription können außerdem Ereignisse zwischen den Antworten eintreffen.
Zuerst sendet der Computer die folgende Zeile.
{"id":1,"command":"hello"}
Eine erfolgreiche Antwort kann so aussehen. Die Geräte-ID ist hier ein Beispielwert.
{"id":1,"ok":true,"value":{"protocolVersion":4,"deviceId":"aabbccddeeff"}}
Der Client prüft die Version und die Identität des ausgewählten Geräts. Erst dann liest er dessen Konfiguration.
{"id":2,"protocolVersion":4,"command":"getConfig"}
Die Antwort enthält die vollständige Konfiguration in value. Der folgende Ausschnitt zeigt ausschließlich die Antwortstruktur und einige Identitätsfelder. Die Auslassung ist kein gültiges JSON und darf nicht als Konfiguration übertragen werden.
{"id":2,"ok":true,"value":{"version":3,"uuid":"…","revision":4,"commitId":"…", …}}
Für einen ersten eigenen Client ist die Kombination aus hello und getConfig ein guter Einstieg. Ein Schreibzugriff sollte erst folgen, wenn die gelesene Konfiguration vollständig verarbeitet werden kann.
| Befehl | Bedeutung |
|---|---|
getConfig / setConfig |
Vollständige Konfiguration lesen oder ersetzen |
getButton / setButton |
Einen vorhandenen Button anhand seiner ID lesen oder ändern |
setImage |
Eine Bildressource hinzufügen oder ersetzen |
getValues / setValues |
Geräteeinstellungen lesen oder einzelne Werte ändern |
getProfile / selectProfile |
Aktuelles Profil abfragen oder auswählen |
subscribe |
Ereignisse für einen abgeglichenen Konfigurationsstand abonnieren |
commandResult |
Ergebnis eines Programmbefehls zurückmelden |
Partielle Schreibzugriffe verwenden ein expected-Objekt mit UUID, Revision und Commit-ID des erwarteten Ausgangsstands. Eine veraltete Erwartung wird abgewiesen. Neue Buttons, eine andere Anordnung oder das Entfernen von Bildressourcen werden über eine vollständige Konfiguration übertragen.
Die Firmware bestätigt einen Konfigurationsschreibzugriff erst nach Validierung und erfolgreicher Speicherung. Ein Fehler kann beispielsweise so aussehen.
{"id":3,"ok":false,"error":"Configuration mismatch; copy a complete configuration"}
Nach dem Konfigurationsabgleich sendet der Client subscribe. value enthält eine frisch erzeugte Subscription-UUID, expected die Identität des tatsächlich angewendeten Konfigurationsstands. Die Subscription wird nur bei passender Identität angenommen.
Eine anschließende Änderung am Gerät erzeugt ein configChanged-Ereignis. Es enthält die Subscription-ID, den vorherigen Stand, die neue vollständige Konfiguration und Angaben zu den berührten Feldern. Der Desktop kann damit beispielsweise eine am Gerät geänderte Tastenbelegung übernehmen und andere lokale Entwurfsänderungen erhalten.
Ereignisse haben keine normale Anfrage-ID. Der Empfänger muss sie von Antworten unterscheiden. Die Java-Bibliothek verwendet dafür einen fortlaufenden Leser und bearbeitet Ereignis-Callbacks getrennt von der Antwortverarbeitung.
Eine vollständige Übertragung oder ein Verbindungsabbruch beendet die Subscription. Anschließend ist ein neuer Abgleich mit neuer Subscription nötig. Die Identität einer Subscription hilft außerdem, verspätete Ereignisse aus einer älteren Verbindung zu erkennen.
Ein Button des Typs command nennt einen Eintrag aus dem Desktop-Befehlskatalog. Beim Drücken entsteht ein appCommand-Ereignis mit eigener Request-ID, Button-ID, Befehlsname, Subscription und erwarteter Konfigurationsidentität. Der Desktop prüft die Zuordnung und meldet das Ergebnis über commandResult zurück.
| Ergebnis | Bedeutung |
|---|---|
started |
Der Prozess wurde gestartet |
alreadyRunning |
Die Parallelitätsregel hat einen weiteren Start verhindert |
unknown |
Der Befehl ist auf dem Computer unbekannt |
launchFailed |
Das Starten ist fehlgeschlagen |
mismatch |
Der zugrunde liegende Konfigurationsstand passt nicht |
started bestätigt den Prozessstart. Die Rückmeldung wartet nicht bis zum Prozessende. Pro Tastendruck gilt eine Frist von zwei Sekunden. Ein Fehler oder eine ausbleibende Antwort führt zu zwei Sekunden roter Rückmeldung am Button. alreadyRunning wird als bestätigte Aktion ohne weiteren Start behandelt.
Offline-Betätigungen werden nicht für einen späteren Start aufbewahrt. Der Client wiederholt einen Programmstart auch nach verlorener Antwort nicht automatisch. So führt ein unklarer Verbindungszustand nicht zu einem zweiten Start desselben Vorgangs.
Eine Anfragezeile darf höchstens 49.152 Byte ohne abschließendes LF umfassen. Die gespeicherte Konfiguration muss kleiner als 48.896 Byte sein. Die zusätzlichen Metadaten von Geräteereignissen können längere Antworten erforderlich machen. Die Java-Seite erlaubt dafür Ereigniszeilen bis 196.608 Byte. Für normale Protokollanfragen ist ein Timeout von zehn Sekunden vorgesehen.
Bricht die Verbindung während eines Schreibens ab, kann die Konfiguration bereits gespeichert worden sein. Nach dem erneuten Verbinden beginnt der Client daher wieder mit hello und getConfig. Aus einer fehlenden Antwort lässt sich der Commit-Status nicht ableiten. Bei abweichendem Stand wird eine vollständige Übertragung in die gewünschte Richtung angeboten.
Das Protokoll ist für eine lokale, vertrauenswürdige USB-Verbindung ausgelegt und enthält keine Authentifizierung. Für die detaillierte Implementierung sind im Firmwareprojekt PROTOCOL.md, main/pad_config.c und main/app_commands.c maßgeblich.