-
Notifications
You must be signed in to change notification settings - Fork 0
Jakob Protokoll
Für die Echtzeitkommunikation der BladeGuardTracker und dem Server wird ein Protokoll verwendet, das auf HTML5-Standardkonformen Websockets aufbaut. Das WebSocket-Protokoll an sich bleibt dabei unverändert, und so sollte prinzipiell jede mehr oder minder vollständige Implementation für die Kommunikation verwendet werden können.
Um eine Abhörsicherheit für die Kommunikation mit dem BladeGuardServer gewährleisten zu können, wird eine verschlüsselte Verbindung eingesetzt; Das Zertifikat hierfür ist allerdings nicht von einer anerkannten Zertifizierungsstelle ausgestellt (für den Zweck der Verschlüsselung ist dies auch nicht zwingend erforderlich). Der verwendete Client muss also so eingestellt werden, dass das übertragene Zertifikat akzeptiert wird.
Die Websockets-Schnittstelle bietet zwei Vorteile gegenüber der Kommunikation über andere Schnittstellen:
- Browser-Unterstützung: Das beschriebene Protokoll kann ohne weiteren Aufwand auch via JavaScript aus den meisten aktuellen Browsern heraus bedient werden. Das macht es z.B. möglich, eine Übersichtskarte mit den aktuellen Informationen in eine Webseite zu Integrieren (Demo: https://bgt.justjakob.de/bgt/static/demo.html und https://bgt.justjakob.de/bgt/static/). Aber auch das Steuern des Servers über eine Webseite wäre möglich, ebenso wie der aufbau eines kompletten Trackers als reine HTML-Seite.
- WebSockets sind HTTP: Viele Provider verwenden (teilweise zweifelhafte) Methoden, um den Traffic ihrer Kunden in einem für sie (die Provider) wünschenswerten Rahmen zu lenken. Manche blockieren hierbei auch den Traffic über unbekannte Ports (whitelisting). Das HTTP-Protokoll und die dazugehörigen Ports (80 und 443) sind hierbei die wohl beste Wahl, wenn es darum geht eine zuverlässige Kommunikation für eine grösstmögliche Zahl an Anwendern bieten zu können.
Um eine unnötige Mehrarbeit bei der Entwicklung von neuen Komponenten zu vermeiden, hier eine Liste der im Projekt aktuell verwendeten Implementationen:
- Server: https://github.com/Worlize/WebSocket-Node
- Android-Client: https://github.com/codebutler/android-websockets (mit Anpassungen für den clientseitigen Ping-Timeout; die angepassten Sourcen hierzu finden sich in diesem Repository)
- iOS-Client: https://github.com/square/SocketRocket (ebenfalls mit Anpassungen für den clientseitigen Ping-Timeout; die angepassten Sourcen hierzu finden sich unter https://github.com/jketterl/SocketRocket)
Die gesamte Kommunikation läuft über einen gemeinsamen Endpunkt. Die URL lautet:
wss://bgt.justjakob.de/bgt/socket
Das gesamte Protokoll verwendet ausschliesslich UTF8-encodierte Datenpakete (binäre Nachrichten werden nicht verwendet). Die einzelnen Pakete sind jeweils in JSON encodiert und müssen einzeln parsebar (d.h.JSON-Sequenzen müssen abgeschlossen sein) sein (eine Segmentierung von Nachrichten in mehrere Pakete ist nicht vorgesehen, da dies bereits vom zugrundeliegenden WebSocket-Protokoll übernommen wird).
Nachdem die Verbindung zum Server hergestellt wurde, sollte der Client zunächst eine Handshake-Nachricht an den Server übertragen. Diese sollte mindestens die verwendete Plattform enthalten (Details hierzu können dem eigenen Abschnitt "Plattformen" entnommen werden). Eine Beispielnachricht sieht wie folgt aus:
{"build":13,"platform":"android","version":"0.5.2"}
Der Handshake ist eine spezielle Nachricht, die kein Kommando darstellt, und wird daher vom Server nicht quittiert.
Das Protokoll ist prinzipiell symetrisch aufgebaut, das Übertragen von Befehlen an den Client ist allerdings nur gestattet, wenn sich der Socket im "Kontrollmodus" befindet. Der Kontrollmodus kann nur vom Client selbst mit den Kommandos enableControl
und disableControl
gesteuert werden. Eine Aktivierung durch den Server ist nicht vorgesehen. Mehr dazu ist im eigenen Absatz "Kontrollsitzungen" dokumentiert.
Ein Kommando ist, wie alle anderen Nachrichten auch, als JSON-Object zu Übertragen. Das Objekt unterstützt die Folgenden Felder: command
, data
und requestId
.
-
command
: muss ein String sein und eines der im Abschnitt "Verfügbare Kommandos" aufgelisteten Kommandos ansprechen. -
data
: ist nicht bei jedem Kommando erforderlich und beinhaltet weitere, an das Kommando zu übertragende Daten. Ob das Feld erforderlich ist oder nicht kann ebenfalls dem Abschnitt "Verfügbare Kommandos" entnommen werden. -
requestId
: ist optional und kann vom Client frei gewählt werden. Es kann Zahlen oder Zeichenfolgen enthalten. Der Wert dieses Feldes wird bei der Beantwortung des Kommandos angehängt. Dies ermöglicht die parallele asynchrone Verarbeitung von mehreren Kommandos gleichzeitig.
Weitere Felder, die an den Empfänger geschickt werden, werden ignoriert.
Beispiel:
{"command":"auth","data":{"user":"maxmustermann","pass":"brooktree01"},"requestId":1}
Der Server beantwortet jedes Kommando mit einem entsprechenden Antwortobjekt. Das Antwortobjekt enthält die folgenden Felder:
-
success
: ein boolscher Wert, der mit true oder false angibt, ob das Kommando vom Server erfolgreich ausgeführt wurde. -
data
: ein JSON-Array, welches Antwortdaten des Servers enthalten kann. (Es handelt sich hierbei immer um ein Array; soll ein Objekt zurückgegeben werden, so wird es in einem Array gewrapt.) -
requestId
: nur vorhanden, wenn eine requestId vom Client übergeben wurde. Gibt den Wert des Client ohne Änderung zurück und ermöglicht so eine Zuuordnung von Antworten zu abgeschickten Kommandos.
Beispiel:
{"success":true,"data":{"uid":56,"name":"maxmustermann","team_name":"Team 1","admin":false},"requestId":1}
Mit diesem Kommando wird eine Benutzeranmeldung durchgeführt. War die Anmeldung erfolgreich, so werden ab sofort alle Kommandos im Benutzerkontext ausgeführt (gewisse Kommandos stehen nicht allen Benutzern zur Verfügung); Außerdem können ab diesem Zeitpunkt übertragene Benutzerkoordinaten (log
-Kommando) einem Benutzer (und damit auch einem Team) zugeordnet und entsprechend in der Karte dargestellt werden.
-
user
(String): Der Benutzername -
pass
(String): Das Passwort des Benutzers (Klartext) (HINWEIS: die Verbindung ist grundsätzlich verschlüsselt, eine Übertragung des Passwortes im Klartext ist also unkritisch.)
-
uid
(Integer): Die numerische Benutzer-ID -
name
(String): Der Benutzername -
team_name
(String): Der Name des Teams, das der Benutzer ausgewählt hat -
admin
(Boolean): Gibt an, ob der Benutzer ein Administrator ist (Dieses Feld kann von der Applikation verwendet werden, um bestimmte Eingabefelder im Sinne einer einfachen Benutzerführung gar nicht erst anzuzeigen. Die entsprechenden Kommandos sind serverseitig geschützt und können - unabhängig vom Design des Client - nur von den entsprechenden Benutzern ausgeführt werden.)
Dies ist das wohl am Abstand wichtigste Kommando für die eigentliche Anwendung. Mit diesem Kommando wird die aktuelle Benutzerposition an den Server übertragen.
-
lat
(Float): Aktueller Breitengrad des Benutzers -
lon
(Float): Aktueller Längengrad des Benutzers
-
speed
(Float): Aktuelle Geschwindigkeit des Benutzers in m/s, durch GPS-Dopplereffekt gemessen (Keine Messung durch Abstandsmessung zweier Geo-Koordinaten!) -
heading
(Float): Aktuelle Richtung, in der sich der Benutzer bewegt
Dieses Kommando erhält keine Antwortdaten.
Mit diesem Kommando benachrichtigt der Client den Server davon, dass er jetzt sein GPS abschaltet und ab sofort keine Positionsinformationen mehr senden wird.
Dieses Kommando beendet allerdings nicht die Verbindung zwischen Client und Server.
Dieses Kommando erfordert keine Daten.
Dieses Kommando erhält keine Antwortdaten.
Mit diesem Kommando benachrichtigt der Client den Server davon, dass seine GPS-Ortung im Moment unzureichend oder nicht verfügbar ist, und dementsprechend temporär keine Koordinaten an den Server übertragen werden können.
Die Wiederaufnahme der Datenübertragung erfolgt mit dem nächsten log
-Kommando seitens des Clients.
Dieses Kommando erfordert keine Daten.
Dieses Kommando erhält keine Antwortdaten.
##subscribeUpdates
Mit diesem Kommando werden eine oder mehrere Event-Kategorien vom Server abonniert. Der Client erhält ab sofort alle Events dieser Kategorie, bis er mit einem entsprechenden unSubscribeUpdates
-Kommando das Abonnement löscht.
Events und die verfügbaren Kategorien sind in einem eigenen Abschnitt beschrieben.
-
category
(String oder Array aus Strings): Eine oder mehrere Event-Kategorien, die abonniert werden sollen.
Dieses Kommando erhält keine Antwortdaten.
##unSubscribeUpdates
Mit diesem Kommand wird das Abonnement einer oder mehrerer Event-Kategorien beendet. Der Client erhält ab sofort keine Events der entsprechenden Kategorie(n) mehr.
Events und die verfügbaren Kategorien sind in einem eigenen Abschnitt beschrieben.
-
category
(String oder Array aus Strings): Eine oder mehrere Event-Kategorien, die abbestellt werden sollen
Dieses Kommando erhält keine Antwortdaten.
Mit diesem Kommando wird die für Push-Notifications erforderliche "Registration-ID" an den Server übertragen. Erst nach einer entsprechenden (plattform-spezifischen) Registrierung und der Übertragung der hieraus resultierenden Registration ID an den Server kann der Server dem Client Push-Nachrichten senden.
Für dieses Kommando ist ein Handshake zwingend erforderlich, da die Registration ID je nach verwendeter Plattform unterschiedlich verarbeitet werden muss.
Unterstützte Plattformen:
- Android über GCM (siehe http://developer.android.com/guide/google/gcm/index.html)
- iOS über APNS (siehe http://developer.apple.com/library/ios/#DOCUMENTATION/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html)
-
regId
(String): Die plattformspezifische Registration ID, die vom entsprechenden verwendeten Dienst bezogen wurde.
Dieses Kommando erhält keine Antwortdaten.
Dieses Kommando kann verwendet werden, um eine Liste der verfügbaren Teams vom Server abzurufen.
Dieses Kommando erfordert keine Daten.
Der Server Antwortet mit einem Array von Team-Objekten. Diese Team-Objekte besitzen die folgenden Felder:
-
id
(Integer): Numerische ID des Teams -
name
(String): Name des Teams
Dieses Kommando wird verwendet um das Team des aktuellen Benutzers zu wechseln.
-
id
(Integer): Numerische ID des Teams, dem der Benutzer zugeordnet werden soll. Die entsprechenden IDs können mit demgetTeams
-Kommando abgerufen werden.
Dieses Kommando erhält keine Antwortdaten.
Dieses Kommando kann verwendet werden um einen neuen Benutzer im System zu registrieren. Der neue Benutzer ist ab sofort im System vorhanden und kann für die Anmeldung benutzt werden. Der Server wird dieses Kommando negativ beantworten, wenn der gewünschte Benutzername im System bereits vorhanden ist.
-
user
(String): Der gewünschte Benutzername des neuen Benutzers -
pass
(String): Das gewünschte Passwort des Benutzers (Klartext)
Dieses Kommando erhält keine Antwortdaten.
Dieses Kommando kann genutzt werden um eine Liste mit den nächsten anstehenden Bladenights vom Server abzurufen.
Dieses Kommando erfordert keine Daten.
Der Server antwortet mit einem nach Startzeit sortierten Array (das nächste Event steht am Anfang) von Event-Objekten. Die Event-Objekte beinhalten die folgenden Felder:
((tbc))
Dieses Kommando teilt dem Server mit, dass der Client ab sofort eine Kontrollverbindung zu einem bestimmten Event aufbauen und die entsprechenden Client-Kommandos erhalten möchte.
Die Liste der in dieser Situation vom Client zu implementierenden Kommandos befindet sich im Abschnitt "Client-Kommandos".
-
eventId
(Integer): Numerische ID eines Events (Die IDs können mit demgetEvents
-Kommando abgefragt werden.)
Dieses Kommando erhält keine Antwortdaten.
Dieses Kommando beendet eine Kontrollsitzung und teilt dem Server mit, dass der Client ab sofort keine Kommandos mehr erhalten möchte.
-
eventId
(Integer): Numerische ID eines Events (Die IDs können mit demgetEvents
-Kommando abgefragt werden.)
Dieses Kommando erhält keine Antwortdaten.
Der Server stellt eine Reihe von Events zur Verfügung, die vom Client abonniert werden können. Dies stellt eine zeitnahe Übertragung von Informationen an den Client zur Verfügung.
Ein Client kann eine Event-Kategorie abonnieren, indem er ein subscribeUpdates
-Kommando mit den entsprechenden Daten an den Server schickt. Werden die entsprechenden Events nicht mehr benötigt, kann die Kategorie mit dem Kommando unSubscribeUpdates
wieder abbestellt werden.
Wichtig: Der Server behält die Informationen, welche Kategorien abonniert waren, nur solange der Socket geöffnet bleibt. Wird ein Socket (bewusst oder unfreiwillig) geschlossen, so müssen die Kategorien bei Bedarf neu abonniert werden.
Der Server erzeugt beim Abonnieren einer Kategorie jeweils ein zusammengefasstes Spezial-Event, das die aktuellen Informationen der jeweiligen Kategorie einmal vollständig an den Client überträgt. Ab diesem Zeitpunkt ist es Aufgabe des Clients, diese Daten mit den darauf folgenden Updates zu aktualisieren.
Events werden durch ein Objekt mit einem Feld event
mit dem Wert update
übertragen. Die eigentlichen Informationen zum Event befinden sich im data
-Feld des empfangenen Objekts.
Das data
-Objekt kann hierbei mehrere Events aus einer Kategorie als auch Events aus mehreren Kategorien zusammengefasst übertragen, um so Bandbreite zu sparen. Aus diesem Grund beinhaltet das data
-Objekt für jede Kategorie ein eigenes Feld, welches wiederum ein Array mit allen zusammengefassten Events der entsprechenden Kategorie enthält.
Beispiel für ein solches zusammengefasstes Event:
{"event":"update","data":{"movements":[{...},{...},{...}],"quit":[{...},{...}]}}
Ein Event wird von der empfangenen Seite nicht beantwortet oder quittiert.
Überträgt Bewegungen von anderen Benutzern. Ein movement
-Objekt beinhaltet die folgenden Felder:
-
user
(Object): Ein Benutzer-Objekt mit den folgenden Feldern: -
id
(Integer) numerische Benutzer-ID -
name
(String) Name des Benutzers -
team
(String) Name des Teams, dem der Benutzer zugeordnet ist -
location
(Object): Ein Positions-Objekt mit den folgenden Feldern: -
lat
(Float): Breitengrad der aktuellen Benutzerposition -
lon
(Float): Längengrad der aktuellen Benutzerposition
Benachrichtigt den Client davon, dass ein Benutzer nicht mehr vom Server verfolgt wird (durch disconnect oder timeout) und dementsprechend nicht mehr auf der Karte angezeigt werden soll. Ein quit
-Objekt umfasst die folgenden Felder:
-
user
(Object): Ein Benutzerobjekt mit den folgenden Feldern: -
id
(Integer): Numerische Benutzer-ID
Versorgt den Client mit aktuellen statistischen Information rund um das aktuelle Event.
Ein stats
-Objekt enhält die folgenden Felder immer:
-
users
(Integer): Gesamtanzahl der aktuell vom Server verfolgten Benutzer -
tracked
(Integer): Anzahl der aktuell vom Server verfolgten Benutzer die sich auf der aktuellen Strecke befinden -
speeded
(Integer): Anzahl der aktuell vom Server verfolgten Benutzer die sich auf der Strecke befinden und Geschwindigkeitsdaten zur Verfügung stellen
Ein stats
-Objekt kann weiter folgende optionale Felder enthalten (sind die Felder nicht vorhanden, konnten die entsprechenden Informationen vom Server nicht ermittelt werden. Der Client sollte dementsprechend Anzeigen, dass die Informationen aktuell nicht zur Verfügung stehen):
-
bladeNightLength
(Float): Länge der aktuellen Veranstaltung in Metern -
bladeNightSpeed
(Float): Durchschnittsgeschwindigkeit der aktuellen Veranstaltung in m/s -
between
(Array aus Integern): Ein Array mit genau zwei Werten. Die Werte geben an, in welchem Bereich sich die Bladenight aktuell bewegt. Die Werte sind Offsets despoints
-Arrays des zuletzt empfangenenmap
-Events. Der erste Wert gibt die Position des Zugendes, der zweite die des Zuganfangs an. Die Strecke sollte hierbei als Rundkurs angesehen werden, d.h. der Zuganfang kann sich am Anfang der Strecke befinden, während sich das Zugende am Ende der Strecke aufhält.
Dieses Event versorgt den Client mit der aktuellen Strecke. Dieses Event kann auch im Verlauf der Veranstaltung auftreten (Wechsel der Strecke von lang nach kurz). Ein map
-Objekt enthält die folgenden Felder:
-
name
(String): Der Name der aktuellen Strecke -
points
(Array): Die Koordinaten der aktuellen Strecke. Ein Koordinaten-Objekt enthält die folgenden Felder: -
lat
(Float): Breitengrad -
lon
(Float): Längengrad -
distanceToPrevious
(Float): Entfernung des aktuellen Punkts von seinem Vorgänger in Metern
Um eine für den Benutzer möglichst einfache Bedienung der Client-Anwendung zu erreichen wurden die sog. "Kontrollsitzungen" in das Protokoll eingebaut. Sie ermöglichen es dem User seine Teilnahme an einem Event durch eine einzige Checkbox in der App zu bestätigen.
Hat der Benutzer seine Teilnahme an einem Event bestätigt, so ist es Aufgabe des Clients zwei Stunden vor geplantem beginn der Veranstaltung eine Kontrollverbindung aufzubauen und solange aufrecht zu erhalten, bis die Veranstaltung beendet ist. Sollte die Netzwerkverbindung während der genannten Zeitspanne abreissen, so ist es Aufgabe des Clients sie alsbald wieder herzustellen, so dass der Server den Status des Clients wiederherstellen kann.
Über die Kontrollverbindung erhält der Client vom Server dann zum tatsächlichen Start der Veranstaltung das Kommando, seine GPS-Funktion zu aktivieren und mit einer Übertragung seiner Position zu beginnen.
Ebenso über die Kontrollverbindung erhält der Client zum tatsächlichen Ende der Veranstaltung das Kommando, sein GPS wieder zu deaktivieren. Nach dem Ende der Veranstaltung ist es dann wieder Aufgabe des Clients, die Kontrollsitzung zu beenden und sich bis zur nächsten Veranstaltung wieder passiv zu verhalten.
Die im folgenden beschriebenen Kommandos müssen nur dann vom Client implementiert werden, wenn der Client zu irgendeinem Zeitpunkt eine Kontrollsitzung vom Server anfordern wird.
Diese Kommando fordert den Client auf, sein GPS zu aktivieren und mit der Übertragung der so erhaltenen Daten an den Server zu beginnen.
Dieses Kommando erhält keine Daten vom Server.
Von diesem Kommando werden keine Antwortdaten erwartet.
Dieses Kommando fordert den Client auf, die Datenübertragung seiner GPS-Koordinaten zu beenden und die GPS-Funktionen zu deaktiveren. Die Kontrollsitzung bleibt beim Erhalt dieses Kommandos weiter bestehen, der Client kann also zu einem späteren Zeitpunkt wieder aufgefordert werden sein GPS zu aktivieren.
Dieses Kommando erhält keine Daten vom Server.
Von diesem Kommando werden keine Antwortdaten erwartet.
Dieses Kommando fordert den Client auf, die Datenübertragung zu beenden und sein GPS zu deaktivern, sowie seine Kontrollsitzung abzubauen. Es ist seitens des Servers mit keinen weiteren Kommandos zu der bestehenden Kontrollsitzung zu rechnen.
Dieses Kommando erhält keine Daten vom Server.
Von diesem Kommando werden keine Antwortdaten erwartet.