Zum Hauptinhalt Zur Navigation

Bluetooth Low Energy und Websockets: Raspberry Pi als Schaltzentrale fürs Haus

Die Vision des Internets der Dinge ist die Steuerung technischer Geräte von überall und unabhängig von bestimmten Werkzeugen. Golem.de zeigt einen flexiblen Weg, fernbedienbare Geräte von überall zu steuern.
/ Alexander Merz
20 Kommentare News folgen (öffnet im neuen Fenster)
Raspberry Pi mit Touchscreen-Display und Bluetooth-Dongle (Bild: Fabian Hamacher/Golem.de)
Raspberry Pi mit Touchscreen-Display und Bluetooth-Dongle Bild: Fabian Hamacher/Golem.de

Im April haben wir gezeigt, wie Bluetooth Low Energy (BluetoothLE) funktioniert und wie wir darüber Dinge steuern können . Doch die Lösung hatte einen Schönheitsfehler: Es war nicht möglich, Einfluss auf vorprogrammierte Abläufe zu nehmen. Heute wollen wir das mit Hilfe von Websockets ändern. Damit werden wir ein Raspberry Pi mit Touchscreen-Monitor in eine Fernbedienung verwandeln, um einen Musikplayer zu steuern. Durch den Einsatz von Websockets kann das Raspberry diese Funktionalität auch an andere Rechner in einem Netzwerk weitergeben.

Flexible Lösung

Statt des Raspberry Pi kann auch jeder andere Rechner – mit oder ohne Display – zum Einsatz kommen, und der Art der steuerbaren Geräte sind keine Grenzen gesetzt. Bluetooth dient uns nur als ein Beispiel für die Steuerung, genauso gut kann eine IR-Diode eingesetzt werden oder ein Sender im 433-MHz-Band. Statt des Musikplayers kann auch ein fernsteuerbares Ambilight oder ein Fernseher verwendet werden. Und statt einer Webanwendung eine eigens entwickelte Android-App. Unser Beispiel bietet vor allem den Vorteil, dass es mit wenig Aufwand nachgestellt werden kann.

Websockets und Bluetooth Low Energy
Websockets und Bluetooth Low Energy (02:36)

Was Websockets sind

Beim klassischen HTTP öffnet ein Client eine Verbindung zu einem Server und schickt eine Anfrage. Der Server beantwortet diese Anfrage, danach wird die Verbindung wieder getrennt. Das ist ein sehr einfaches und robustes Schema. Ein Server kann aber nicht ohne vorherige Anfrage etwas zum Client schicken. Der Websocket-Standard(öffnet im neuen Fenster) ist HTTP sehr ähnlich – mit einem Unterschied: Wurde die Verbindung einmal geöffnet, bleibt sie bestehen und der Server darf dann auch von sich aus Daten senden.

Durch die Ähnlichkeit von Websockets und HTTP wird das Protokoll mittlerweile von aktuellen Browsern und Bibliotheken unterstützt, sondern es funktioniert praktisch auch überall dort, wo bislang schon HTTP genutzt werden kann.

Die Kommunikation über Sockets (ohne Web-Prefix) ist in der IT-Welt nicht neu. Über klassische (Unix-)Sockets fließen Datenströme zwischen Anwendungen hin und her. Praktisch jedes Betriebssystem bietet diese Kommunikationsmethode an. Sie funktioniert aber nicht über Gerätegrenzen hinweg. Außerdem ist eine Anwendung vollkommen frei darin, in welcher Form sie die Daten überträgt und einen Socket nutzt. Netzwerk-Sockets erweitern das Konzept um die geräteübergreifende Übertragung, auch hier gibt es keine Festlegungen für die Form der Daten. Das ist entsprechenden Protokollen – wie eben dem bekannten HTTP – überlassen.

Websockets sind nun ebenfalls ein solches Protokoll, das auf Netzwerk-Sockets aufsetzt. Websockets sind also kein Ersatz für klassische Netzwerk-Sockets, die Bezeichnung Socket zielt eher darauf ab, auf die Art der Nutzung hinzuweisen.

Wozu Websockets nützlich sind

Abgesehen davon, dass Websockets den Bau interaktiver Webanwendungen vereinfachen, sind sie ein ideales Instrument, um vielseitig verwendbare, betriebssystemunabhängige und nichtproprietäre Schnittstellen zu Hardware-Peripherie zu implementieren.

Grundsätzlich ist das auch über HTTP möglich. Unüberschaubar ist zum Beispiel mittlerweile die Anzahl der Projekte, bei denen Raspberry Pi oder ähnliche Kleinrechner den Zugriff auf angeschlossene Sensoren und Aktuatoren über einen darauf laufenden Webserver ermöglichen.

Im einfachsten Fall läuft dabei ein Python- oder PHP-Skript, das auf Zuruf eine Webseite erzeugt und zum Beispiel die Sensordaten darstellt und Interaktionsmöglichkeit per Webformular oder HTML-Buttons bietet. Fittere Programmierer erweitern dieses Konzept auf AJAX-Aufrufe in der erzeugten Webseite, um Daten automatisch zu aktualisieren oder implementieren eine REST-API.

Obwohl gerade Letzteres den Stand der Technik repräsentiert, haben diese HTTP-basierten Methoden einen erheblichen Nachteil: Sie sind ressourcenintensiv.

HTTP geht nur in eine Richtung

Wie bereits angesprochen, kann der Server nicht von sich aus Daten an den Client senden. Dieser muss regelmäßig beim Server anfragen, ob sich etwas geändert hat. Und das dauert: Zuerst muss die Verbindung aufgebaut werden, dann muss die eigentliche Skriptlogik durchlaufen werden und die Daten müssen verschickt werden. Das kostet nicht nur Zeit, sondern belastet auch den Server und produziert viel überflüssigen Netzwerkverkehr. Überflüssig erst recht, wenn jedes Mal die gleichen Ressourcen vom gleichen Client angefordert werden. Da sind die übertragenen Header- und Metadaten schnell umfangreicher als die Nutzdaten.

Bei Websockets wird die Verbindung dauerhaft aufrechterhalten, die Übertragung von Metadaten wird minimiert. Daten können eventbasiert übertragen werden. Das führt zu kleineren Datenmengen über das Netzwerk und geringe Serverbelastung. Was effektiv bedeutet, dass weniger Strom gebraucht wird.

Aber auch wenn der Stromspareffekt nicht im Vordergrund steht – Websockets bieten sich auch da an, wo bislang klassische Sockets zum Einsatz kamen. Denn sie sind für den Anwendungsprogrammierer nicht schwerer zu nutzen, aber automatisch netzwerkfähig. Das wird zum Beispiel bereits von Leap Motion und Tinkerforge in dieser Form genutzt.

Beispiel: Leap Motion

Der Treiber des Controllers zur Gestensteuerung stellt die Daten des Fingertrackings über einen Websocket zur Verfügung. Dadurch kann eine Webanwendung die Gestensteuerung für eigene Zwecke einsetzen. Vom Hersteller wird eine Javascript-API bereitgestellt, mit der die Tracking-Daten ausgewertet werden können. Die Auswertung der Daten läuft rein lokal, auch wenn die Javascript-Logik initial vom Server geladen wird.

Die Anwendung der API haben wir im vergangenen Jahr in einem Artikel zu einem browserbasierten Gitarreneffektgerät gezeigt.

Die Websocket-Schnittstelle lässt sich aber grundsätzlich auch außerhalb eines Browsers benutzen.

Beispiel: Tinkerforge

Beim Kit Internet der Dinge(öffnet im neuen Fenster) hat Tinkerforge ebenfalls eine Websocket-Schnittstelle bereitgestellt. Das Kit zur Fernsteuerung von Dingen über das 433-MHz-Band kann über eine Webseite(öffnet im neuen Fenster) konfiguriert und mit anderen Diensten verknüpft werden. Den Zugriff auf die Hardware erhält die Webseite im Browser über einen lokalen Dienst, der per Websocket angesprochen wird.

Obwohl die Webseite über das Netz aufgerufen werden muss und damit von Tinkerforge jederzeit direkt verbessert werden kann, läuft auch hier die eigentliche Logik lokal auf dem Rechner des Anwenders und unter dessen Kontrolle.

Ein ferngesteuerter Musikplayer

Bei unserem Projekt steuert ein Anwender über ein Raspberry Pi mit Touchscreen einen Musikplayer auf einem anderen Rechner. Die Verbindung zwischen beiden Rechnern läuft über Bluetooth Low Energy. Als Benutzeroberfläche auf dem Raspberry dient eine Webanwendung in einem Browser.

Dabei sind drei Skripte involviert: Ein Javascript-Skript innerhalb der Webanwendung kommuniziert über einen Websocket. Diese wird auf dem Raspberry Pi mit einem Javascript-Skript auf Node.js-Basis bereitgestellt; es implementiert auch die BluetoothLE-Funktionalität. Das dritte Skript schließlich läuft auf einem zweiten Rechner und steuert aufgrund der Anweisungen, die durch BluetoothLE übertragen werden, einen Musikplayer per Kommandozeile.

Natürlich ließe sich die direkte Aufgabe als solches deutlich einfacher implementieren. Der Websocket hätte auch direkt die Ansprache des Musikplayers übernehmen können, der Bluetooth-Teil wäre unnötig. Tatsächlich ging es uns aber eigentlich nicht um den Musikplayer auf einem Laptop, an dessen Stelle könnte genauso gut eine eigenständige Arduino-/Raspberry-basierte Steuerung fürs Aquarium stehen – oder eine Stereoanlage mit Bluetooth-Steuerung, die so nachträglich tauglich für die Steuerung per WLAN gemacht werden kann.

Wir konzentrieren uns in diesem Artikel vor allem auf die Websocket-Implementierung, der Code für die Bluetooth-Kommunikation sollte auf Basis des Vorgängerartikels verständlich sein. Wir werden auf den Bluetooth-Code nur da eingehen, wo es im Detail interessant ist.

Der vollständige Quellcode aller Skripte kann als ZIP-Archiv heruntergeladen werden.

Websocket öffnen

Wir beginnen mit dem einfachsten Teil: der Webanwendung. Im HTML-Teil definieren wir lediglich einige Buttons für die Funktionen des Players wie Play, Stop etc., außerdem zwei Anzeigefelder für den Namen des aktuellen Titels und die Lautstärke:

Der Javascript-Teil besteht ebenfalls aus erfreulich wenig Zeilen. Sobald die Webseite geladen ist, wird die start() -Funktion aufgerufen und darin mit new Websocket('ws://localhost:8001/') eine Websocket-Verbindung geöffnet. Die Protokoll-Angabe ws steht kurz für Websocket, ansonsten entspricht der Parameter einer ganz normalen URL. Das Websocket-Objekt wird von jedem Browser bereitgestellt, der den Websocket-Standard gemäß dem W3C(öffnet im neuen Fenster) implementiert.

        function start() { con = new WebSocket('ws://localhost:8001/'); con.onmessage = function(msg) { var message = {track:'',volume:''}; try {  message = JSON.parse(msg.data); } catch(e) {  console.log(e);  return; } document.getElementById('track').innerHTML = message.track; document.getElementById('volume').innerHTML = message.volume; }

Dass wir die Angabe hart codieren, erfolgt hier wegen der Übersichtlichkeit, in der Praxis sollte die URL vom Benutzer gesetzt werden können. Dann läuft die Webanwendung auch auf anderen Rechnern, wenn sie übers Netzwerk vom Raspberry geladen wird.

Als Nächstes wird ein Event-Handler registriert: onmessage . Die zugewiesene Funktion wird aufgerufen, wenn der Server Daten zum Client schickt, hier dem Browser. Diese Funktion erhält als Parameter die Daten, die der Server geschickt hat.

Anzeigen und Senden

Da wir serverseitig JSON als Datenformat für die übertragenen Daten vorgegeben haben, wird der Parameter entsprechend geparst. Dadurch erhalten wir eine Datenstruktur, die unter anderem den Namen des aktuellen Titels und die Lautstärke enthält. Diese Informationen werden direkt in die entsprechenden HTML-Elemente eingetragen.

Die Buttons sind mit onclick -Events versehen und rufen damit die send() -Funktion mit dem jeweiligen Kommando als Parameter auf. Diese Funktion ruft ihrerseits die send() -Methode des Websocket-Objekts auf. Gesendet wird dabei auch wieder eine Datenstruktur, die per JSON in Text umgewandelt wurde.

    
...
ᐸbutton id="btnPlay" onclick="send(COMMAND_PLAY)"ᐳPlayᐸ/buttonᐳ
ᐸbutton id="btnStop" onclick="send(COMMAND_STOP)"ᐳStopᐸ/buttonᐳ
...
        function send(val) { if(con == null) {  return; } if(COMMAND_VOLUP == val) {  con.send(JSON.stringify({vol:volume+1})); } else if(COMMAND_VOLDOWN == val) {  con.send(JSON.stringify({vol:volume-1})); } else {  var cmd_data = {cmd:val};  con.send(JSON.stringify(cmd_data)); } }

Das ist tatsächlich alles, mehr ist für eine bidirektionale Kommunikation nicht notwendig. AJAX und REST-APIs wirken dagegen komplizierter.

Musikplayer ansteuern

Bevor wir zur Serverkomponente für den Websocket kommen, betrachten wir noch das Skript zur Ansteuerung des Musikplayers. Es empfängt über Bluetooth Low Energy die Kommandos und steuert davon abhängig per Kommandozeile den Musikplayer. Wir setzten dazu iTunes ein, das sich im laufenden Betrieb per Applescript(öffnet im neuen Fenster) steuern lässt. Je nach Kommando ermittelt es auch die aktuell eingestellte Lautstärke und den laufenden Track, ebenfalls per Applescript. Beide Infos werden asynchron über BluetoothLE wieder übermittelt. Applescript und iTunes sind kein Muss, aber unter Mac OS praktisch. Unter anderen Betriebssystemen, insbesondere unter Linux, gibt es vergleichbare Optionen.

Mit dem Skript wird der Laptop zum angesteuerten BluetoothLE-Peripheral. In unserem Vorgängerartikel diente der Laptop dagegen als steuernde BluetoothLE-Central. Die Umsetzung erfolgt auf Basis der Node.js-Bibliothek Bleno(öffnet im neuen Fenster) .

Beim Peripheral wird ein Service mit drei Characteristics definiert: Die Kommando-Characteristic (CMD) kann beschrieben werden, darüber setzen wir das auszuführende Kommando, codiert als Zahl. Die Lautstärke-Characteristic (VOL) kann gelesen, abonniert und beschrieben werden. Das heißt: Darüber erhalten wir die aktuell eingestellte Lautstärke, oder wir können die Lautstärke in Prozent setzen (0 – 100%). Eine reine Characteristic zum Lesen ist schließlich Track (TRK) für den aktuell abgespielten Titel.

Das Kommando codieren wir aus Gründen der Datensparsamkeit als Zahl. Wer will, kann stattdessen auch Zeichenketten benutzen, was allerdings die Datenübertragung verlängert.

Mittler zwischen den Welten

Das letzte Skript läuft nun wieder auf dem Raspberry Pi: Es implementiert die Serverseite des Websockets und fungiert zugleich als BluetoothLE Central, Letzteres hin und wieder auch als Server bezeichnet.

Node.js unterstützt – derzeit – keine Websockets direkt, es gibt allerdings mehrere Bibliotheken dafür. Wir benutzen nodejs-websocket(öffnet im neuen Fenster) . Es ist sehr einfach zu benutzen, bringt aber keine Elemente für komplexere Webanwendungen mit. Hierfür sind unter Node.js zum Beispiel Websocket-Node(öffnet im neuen Fenster) und TotalJS(öffnet im neuen Fenster) geeignetere Kandidaten.

Nach Einbindung des Websocket-Moduls wird der Websocket-Server mit createServer() erzeugt, als Parameter übergeben wir eine Callback-Funktion. Sie wird mit einem Connection -Objekt aufgerufen, über das wir die weitere Websocket-Kommunikation abwickeln. Die Callback-Methode wird jedesmal aufgerufen, wenn sich ein Client mit dem Server verbindet. Es reicht also nicht, ein einzelnes Connection-Objekt zu verwalten, sondern es müssen alle einkommenden Objekte verwaltet werden.

Um den Server selbst zu starten, wird dessen Methode listen() mit der Angabe des Ports aufgerufen.

Am Connection-Objekt registrieren wir eine Listener-Funktion für den text -Event mit der Methode on() . Die Listener-Funktion erhält die Daten, welche ein verbundener Client sendet, jeweils als abgeschlossene Zeichenkette. Zum Senden von Daten wird die Methode sendText() verwendet.

        var connections = []; var server = ws.createServer(function (conn) {     connections.push(conn); conn.on("text", function (str) {  try {   var cmd_data = JSON.parse(str);  } catch(e) {   ...  }  if(cmd_data.vol && '' != cmd_data.vol) {   var vol = parseInt(cmd_data.vol);   sendVolume(vol);   return;  }  if(cmd_data.cmd && '' != cmd_data.cmd) {     ...   sendCommand(cmd_data.cmd);      var message = {    success : true,    cmd = cmd_data.cmd   };   conn.sendText(JSON.stringify(message));     return;      } }); }).listen(8001);

Der Bluetooth-Teil auf Basis von Noble(öffnet im neuen Fenster) enthält soweit keine Überraschungen. Über Noble werden Peripherals gesucht, die unsere spezielle Service-UUID aufweisen. Wurden sie gefunden, können wir mit deren Characteristics arbeiten. Für die Characteristics, welche die Lautstärke und den aktuellen Track übermitteln, werden Abonnements eingerichtet.

Daten per Websocket austauschen

Der Websocket-Standard definiert nicht, wie die Daten aussehen müssen, die per Websocket ausgetauscht werden. Wir benutzen JSON, in anderen Anwendungen ist womöglich die direkte Übergabe von HTML sinnvoll, oder es wird eine Kommandozeile nachgeahmt.

Wir haben uns für JSON entschieden, da wir damit eine komplette – und beliebig erweiterbare – Datenstruktur in Textform übertragen können, aber dafür keinen Parser implementieren müssen. JSON zu codieren und decodieren, beherrschen Browser wie auch Node.js direkt.

Damit der Client per Websocket eine Aktion auf dem Server auslösen kann, übermittelt er ein Objekt mit bis zu zwei Eigenschaften: cmd enthält den Namen eines Befehls in Textform und vol einen angestrebten Lautstärkewert.

Der Websocket-Server setzt diese Anweisungen in die entsprechenden Bluetooth-Anweisungen um. Wenn die cmd-Eigenschaft einen gültigen Wert besitzt, dann wird die dem Kommando zugeordnete Zahl ermittelt und diese an die CMD-Characteristic geschrieben. Ist die vol-Eigenschaft gesetzt, wird hingegen deren Wert in die VOL-Characteristic geschrieben. Bei einem Kommando sendet der Server das übermittelte Objekt zurück an den befehlenden Client, ergänzt um eine Eigenschaft, success . Sie ist true , wenn eine Aktion erfolgreich ausgeführt wurde. Das bedeutet aber nur, dass der Server kein Problem mit dem Befehl hatte. Eine Erfolgsmeldung, dass dieser tatsächlich beim Peripheral ankam und umgesetzt wurde, ist es nicht.

Eine Erfolgsmeldung dieser Art haben wir nur indirekt umgesetzt: Empfängt das Peripheral-Skript einen Befehl über die CMD-Characteristic, dann übermittelt es über die TRK-Characteristic den Namen des aktuellen Titels. Wurde hingegen über die VOL-Characteristic die Lautstärke geändert, dann übermittelt diese Characteristic ihrerseits den tatsächlich gesetzten Wert zurück.

Der Server erhält beide Arten von Benachrichtigungen, weil er die TRK- und VOL-Characteristic abonniert hat. Trifft eine entsprechende Benachrichtigung ein, dann übermittelt der Server an alle Clients ebenfalls ein Objekt analog zur obigen Erfolgsmeldung. Der einzige Unterschied ist ein leeres Feld für die cmd-Eigenschaft.

        function handleService(service) { service.discoverCharacteristics([], function(error, characteristics) {  ...   switch(characteristics[i].uuid) {    ...    case UUID_CHARACTERISTIC_VOL :     vol_char = characteristics[i];     vol_char.notify(true);     vol_char.on('read', function(value, isNotification) {        ...        message.volume = value.readUInt8(0);              if(connections.length) {        ...         connections[i].sendText(JSON.stringify(message));        ...       }     });     break;     ...   }  ... } }

Im Normalfall zieht ein Befehlsaufruf vom Client an den Server also immer zwei Benachrichtigungen des Servers an den befehlenden Client nach sich: einmal nach dem Versand eines Kommandos über Bluetooth und einmal die Benachrichtigung vom Peripheral.

Binäre Daten statt Text

In unserem Beispiel haben wir ausschließlich Textdaten über die Websockets verschickt. Diese sind übrigens entsprechend dem Standard immer UTF-8-codiert. Alternativ können aber auch binäre Ströme wie Bilddaten versendet werden.

Serverseitig wird dazu bei nodejs-websocket entweder die Methode sendBinary() für einen Datenblock verwendet oder beginBinary() für die Übertragung eines Streams. Clientseitig im Browser kommen diese Daten ebenfalls über die Callback-Funktion bei onmessage an. Die Unterscheidung, ob es sich um Text- oder Binärdaten handelt, muss über die Eigenschaft binaryType des Websocket-Objektes geprüft werden. Ist dessen Wert blob , handelt es sich um Binärdaten.

Die Übertragung von Binärdaten abseits von Bilddaten ist interessant, wenn tatsächlich die Datenmengen so klein wie möglich gehalten werden sollen, um eine schnelle Übertragung zu gewährleisten. Tinkerforge setzt zum Beispiel in seinem Kit darauf.

Das Henne-Ei-Problem

Wenn wir unsere Webanwendung starten, sollte sie sinnvollerweise die Ist-Werte des Players bei der Lautstärke und des Titels anzeigen. Es liegt nahe, dass der Server automatisch diese Werte an den Client übermittelt, sobald eine Verbindung hergestellt wurde. Allerdings muss der Verbindungsaufbau nicht automatisch heißen, dass der Client mit der Datenübertragung bereits etwas anfangen kann oder will.

Es bietet sich also an, dass der Client entweder ein Initial-Kommando sendet oder explizit die Infos anfordern kann, ohne vorher ein Player-Kommando zu übermitteln beziehungsweise die Lautstärke zu ändern. Ein Initial-Kommando könnte bei unserer Problemstellung auch damit einhergehen, überhaupt erst die Bluetooth-Verbindung aufzubauen – doch zu dieser Problematik mehr im nächsten Abschnitt.

Wir haben uns entschieden, explizit die Infos anzufordern. Dazu gibt es das spezielle Kommando nop (kurz für No Operation ), das eben nicht per Bluetooth weitergeleitet wird. Es wird nur vom Websocket-Server beachtet und er sendet daraufhin einfach die aktuell vorliegenden Informationen.

Doch woher bekommt der Server die Daten vom Musikplayer? Er kann nach dem Verbindungsaufbau mit dem Peripheral die entsprechenden Eigenschaften lesen. Allerdings hatten wir bei den ersten Schritten zu Debugging-Zwecken eine andere Logik implementiert, die auch jetzt noch präsent ist: Wenn ein Central, also unser Server, eine Eigenschaft abonniert, wird der Wert der Eigenschaft auch sofort an den Abonnenten geschickt. Praktisch ist das ein Test, ob die Verbindung tatsächlich in beide Richtungen funktioniert.

Verbindungsprobleme

Wir haben nun eine Lösung gebaut, bei der wir den Musikplayer über drei Wege steuern können: per Mini-Web-App auf dem Raspberry, per Mini-Web-App auf einem anderen Rechner oder Smartphone mit Netzwerkzugriff auf den Raspberry oder auch per direkten Zugriff auf die Bluetooth-Schnittstelle.

Die grundlegende Implementierung dafür haben wir besprochen. Einen Faktor haben wir aber bislang nicht miteinbezogen: die Verbindungsstabilität. Sowohl die Netzwerk- als auch die Bluetooth-Verbindung kann jederzeit abbrechen. Unter Umständen ist das sogar erwünscht, um Strom zu sparen. Wir müssen damit also irgendwie umgehen.

Wir konzentrieren uns hier auf die Bluetooth-Verbindung, sie erwies sich zeitweise als relativ instabil. Außerdem kann der Server nicht einfach per F5 neu gestartet werden, die Webanwendung hingegen schon. Die folgenden Überlegungen sollten aber trotzdem bei eigenen Projekten auch auf die Websocket-Verbindung angewendet werden. In unserem Skript versucht die Webanwendung einfach, die Websocket-Verbindung bei einem Abbruch wieder herzustellen, ohne dem Nutzer dies zu signalisieren.

Auf der Seite des Musikplayers können wir bei einem Verbindungsabbruch recht wenig tun, als Bluetooth-Peripheral ist es ein reiner Befehlsempfänger und kann keine Verbindung von selbst (wieder-) aufnehmen. Bleiben der Server und die Webanwendung übrig.

Und da gilt es, eine grundlegende Entscheidung zu treffen: Soll sich der Server als Vermittlungsstelle zwischen Websocket und Bluetooth um Verbindungsabbrüche kümmern oder die Webanwendung?

Variante 1: Der Server verfügt über keine Eigenintelligenz und ist vollständig von den Befehlen des Clients abhängig. Das schließt auch den Zustand der Bluetooth-Verbindung selbst ein. Der Server muss um entsprechende Befehle ergänzt werden, die BT-Verbindung zu öffnen und zu schließen. Außerdem muss er den Client über den erfolgreichen Aufbau der Verbindung wie auch einen Abbruch informieren. Vorteil ist, dass dadurch der Benutzer maximale Wahlfreiheit hat. Der Nachteil ist: Der Client beziehungsweise der Benutzer muss sich tatsächlich um alles kümmern.

Variante 2: Der Server kümmert sich um die BT-Verbindung und gaukelt dem Client stets eine existierende Bluetooth-Verbindung vor. Vorteil: Ein Client benötigt kaum eigene Logik und ist schnell implementiert. Der Server kann optimale Stromsparstrategien umsetzen. Nachteil: Der Client und der Benutzer sind in ihrer Flexibilität eingeschränkt. Der Aufwand, den Server zu implementieren, ist höher.

Variante 3: Der Server unterstützt beide Varianten, durch die über einen Serverbefehl umgeschaltet werden kann. Vorteil: maximale Flexibilität. Nachteil: hoher Programmieraufwand und Fehleranfälligkeit.

Dummer Client – cleverer Server

Wir haben uns für Variante 2 entschieden. Der Client sollte möglichst schlank bleiben, um die Benutzeroberfläche übersichtlich zu halten – so viele Bedienungselemente passen nicht auf das kleine Display des Raspberrys.

Nun gilt es, die Entscheidung zu treffen, wie der Server damit umgeht, wenn der Client zwar Kommandos sendet, aber diese wegen einer getrennten Bluetooth-Verbindung nicht weiterleiten kann. Sie können entweder verworfen oder gepuffert werden. Diese Entscheidung muss für jedes Nutzungsszenario individuell getroffen werden.

Wir haben uns für die Pufferung entschieden. Dafür haben wir einen Kommando-Buffer implementiert, der gefüllt wird, wenn die Übertragung fehlschlägt. Wird festgestellt, dass die Verbindung nicht mehr funktioniert, baut der Server sie neu auf.

War der Aufbau erfolgreich, wird der Kommando-Buffer abgearbeitet. Der Client bekommt davon nichts mit – zumindest theoretisch. Zur Erinnerung: Auf jedes Kommando erhält der Client zwei Benachrichtigungen vom Server per Websocket – einmal nach dem Verschicken des Befehls und dann die Antwort des Peripherals über die Lautstärke- und Track-Abonnements. Musste die BluetoothLE-Verbindung erst neu aufgebaut werden, dann ist die Zeitdifferenz zwischen beiden Benachrichtigungen deutlich größer als bei bestehender Verbindung.

Einfacher Einsatz – komplexe Szenarien

Websockets bereitzustellen und zu nutzen, erfordert keinen großen Aufwand, eine BluetoothLE-basierte Kommunikation aufzubauen, auch nicht. Aber wenn beide Dinge zusammenkommen, kann es kompliziert werden. Das gilt auch für andere Szenarien, bei denen verschiedene Kommunikations- und Steuerungstechniken zusammenkommen und idealerweise zusammen laufen sollen. Doch genau das wird eine der größten Herausforderungen des Internets der Dinge – mancher mag auch sagen: sein größtes Problem.

Doch wir wollten zeigen, dass mit ein wenig Code-Einsatz und einigen Überlegungen die Herausforderungen zu bewältigen sind. Wer schon einen eigenen Cloud- und E-Mail-Server sein Eigen nennt, kann demnächst auch seinen eigenen Heimautomatisierungsserver aufsetzen.


Relevante Themen