Das Internet der Dinge ist in aller Munde und begeistert Tüftler und Programmierer wie mich. Es gibt nichts Cooleres, als eigene Erfindungen zum Leben zu erwecken und mit ihnen sprechen zu können.
IoT-Geräte, die Apps installieren, die Sie selten verwenden, können jedoch ärgerlich sein. Deshalb nutzen wir neue Webtechnologien wie das Physical Web und Web Bluetooth, um IoT-Geräte intuitiver und weniger aufdringlich zu machen.

Web und IoT – eine perfekte Kombination
Es gibt noch viele Hürden zu überwinden, bevor das Internet der Dinge ein großer Erfolg werden kann. Ein Hindernis sind Unternehmen und Produkte, bei denen Nutzer Apps für jedes gekaufte Gerät installieren müssen, wodurch die Smartphones der Nutzer mit einer Vielzahl von Apps überladen werden, die sie selten verwenden.
Aus diesem Grund sind wir sehr begeistert vom Projekt Physical Web, mit dem Geräte eine URL auf eine Online-Website auf unaufdringliche Weise übertragen können. In Kombination mit neuen Webtechnologien wie Web Bluetooth, Web USB und Web NFC können die Websites direkt mit dem Gerät verbunden werden oder zumindest die richtige Vorgehensweise erklären.
In diesem Artikel konzentrieren wir uns hauptsächlich auf Web Bluetooth. Einige Anwendungsfälle eignen sich jedoch möglicherweise besser für Web NFC oder Web USB. Web USB ist beispielsweise vorzuziehen, wenn Sie aus Sicherheitsgründen eine physische Verbindung benötigen.
Die Website kann auch als progressive Web-App (PWA) dienen. Wir empfehlen Ihnen, sich die Erläuterung von PWAs von Google anzusehen. PWAs sind Websites mit einer responsiven, App-ähnlichen Nutzeroberfläche, die offline funktionieren und dem Startbildschirm des Geräts hinzugefügt werden können.
Als Proof of Concept habe ich ein kleines Gerät mit dem Intel® Edison Arduino-Breakout-Board gebaut. Das Gerät enthält einen Temperatursensor (TMP36) sowie einen Aktor (Farb-LED-Kathode). Die Schaltpläne für dieses Gerät finden Sie am Ende dieses Artikels.

Intel Edison ist ein interessantes Produkt, da darauf eine vollständige Linux*-Distribution ausgeführt werden kann. Daher kann ich sie ganz einfach mit Node.js programmieren. Mit dem Installationsprogramm können Sie das Intel* XDK installieren, was den Einstieg erleichtert. Sie können die App aber auch manuell programmieren und auf Ihr Gerät hochladen.
Für meine Node.js-Anwendung benötigte ich drei Node-Module sowie deren Abhängigkeiten:
eddystone-beacon
parse-color
johnny-five
Mit ersterem wird automatisch noble
installiert, das Knotenmodul, mit dem ich über Bluetooth Low Energy kommuniziere.
Die package.json
-Datei für das Projekt sieht so aus:
{
"name": "edison-webbluetooth-demo-server",
"version": "1.0.0",
"main": "main.js",
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"eddystone-beacon": "^1.0.5",
"johnny-five": "^0.9.30",
"parse-color": "^1.0.0"
}
}
Website ankündigen
Ab Version 49 unterstützt Chrome auf Android das Physical Web. Dadurch kann Chrome URLs sehen, die von Geräten in der Nähe gesendet werden. Es gibt einige Anforderungen, die Entwickler beachten müssen, z. B. dass die Websites öffentlich zugänglich sein und HTTPS verwenden müssen.
Das Eddystone-Protokoll hat eine Größenbeschränkung von 18 Byte für URLs. Damit die URL für meine Demo-App (https://webbt-sensor-hub.appspot.com/) funktioniert, muss ich einen URL-Kürzungsdienst verwenden.
Das Streamen der URL ist ganz einfach. Sie müssen lediglich die erforderlichen Bibliotheken importieren und einige Funktionen aufrufen. Eine Möglichkeit hierfür ist das Aufrufen von advertiseUrl
, wenn der BLE-Chip aktiviert ist:
var beacon = require("eddystone-beacon");
var bleno = require('eddystone-beacon/node_modules/bleno');
bleno.on('stateChange', function(state) {
if (state === 'poweredOn') {
beacon.advertiseUrl("https://goo.gl/9FomQC", {name: 'Edison'});
}
}
Das geht wirklich ganz einfach. Im Bild unten sehen Sie, dass Chrome das Gerät problemlos findet.


Kommunikation mit dem Sensor/Aktor
Wir verwenden Johnny-Five*, um mit dem Board über Verbesserungen zu sprechen. Johnny-Five bietet eine gute Abstraktion für die Kommunikation mit dem TMP36-Sensor.
Unten finden Sie den einfachen Code, mit dem Sie über Temperaturänderungen benachrichtigt werden und die anfängliche LED-Farbe festlegen können.
var five = require("johnny-five");
var Edison = require("edison-io");
var board = new five.Board({
io: new Edison()
});
board.on("ready", function() {
// Johnny-Five's Led.RGB class can be initialized with
// an array of pin numbers in R, G, B order.
// Reference: http://johnny-five.io/api/led.rgb/#parameters
var led = new five.Led.RGB([ 3, 5, 6 ]);
// Johnny-Five's Thermometer class provides a built-in
// controller definition for the TMP36 sensor. The controller
// handles computing a Celsius (also Fahrenheit & Kelvin) from
// a raw analog input value.
// Reference: http://johnny-five.io/api/thermometer/
var temp = new five.Thermometer({
controller: "TMP36",
pin: "A0",
});
temp.on("change", function() {
temperatureCharacteristic.valueChange(this.celsius);
});
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
});
Die oben genannten *Characteristic
-Variablen können Sie vorerst ignorieren. Sie werden im späteren Abschnitt zur Bluetooth-Schnittstelle definiert.
Wie Sie bei der Instanziierung des Thermometer-Objekts vielleicht bemerkt haben, kommuniziere ich über den analogen A0
-Port mit dem TMP36. Die Spannungsbeine an der Kathode der Farb-LED sind mit den digitalen Pins 3, 5 und 6 verbunden, die zufällig die PWM-Pins (Pulse-Width Modulation) auf dem Edison-Arduino-Breakout-Board sind.

Mit Bluetooth sprechen
Die Kommunikation mit Bluetooth könnte nicht einfacher sein als mit noble
.
Im folgenden Beispiel erstellen wir zwei Bluetooth Low Energy-Characteristics: eine für die LED und eine für den Temperatursensor. Mit ersterem können wir die aktuelle LED-Farbe lesen und eine neue Farbe festlegen. Mit letzterem können wir Temperaturänderungsereignisse abonnieren.
Mit noble
ist es ganz einfach, eine Eigenschaft zu erstellen. Sie müssen lediglich festlegen, wie die Eigenschaft kommuniziert, und eine UUID definieren. Die Kommunikationsoptionen sind „Lesen“, „Schreiben“, „Benachrichtigen“ oder eine beliebige Kombination davon.
Am einfachsten ist es, ein neues Objekt zu erstellen und von bleno.Characteristic
zu erben.
Das resultierende charakteristische Objekt sieht so aus:
var TemperatureCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0a',
properties: ['read', 'notify'],
value: null
});
this._lastValue = 0;
this._total = 0;
this._samples = 0;
this._onChange = null;
};
util.inherits(TemperatureCharacteristic, bleno.Characteristic);
Wir speichern den aktuellen Temperaturwert in der Variablen this._lastValue
. Wir müssen eine onReadRequest
-Methode hinzufügen und den Wert codieren, damit eine „Lese“-Aktion funktioniert.
TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(8);
data.writeDoubleLE(this._lastValue, 0);
callback(this.RESULT_SUCCESS, data);
};
Für „notify“ müssen wir eine Methode hinzufügen, um Abos und deren Kündigung zu verarbeiten. Im Grunde speichern wir einfach einen Rückruf. Wenn wir einen neuen Grund für die Temperatur senden möchten, rufen wir diesen Rückruf mit dem neuen Wert auf (wie oben codiert).
TemperatureCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
console.log("Subscribed to temperature change.");
this._onChange = updateValueCallback;
this._lastValue = undefined;
};
TemperatureCharacteristic.prototype.onUnsubscribe = function() {
console.log("Unsubscribed to temperature change.");
this._onChange = null;
};
Da die Werte etwas schwanken können, müssen wir die Werte glätten, die wir vom TMP36-Sensor erhalten. Ich habe mich dafür entschieden, einfach den Durchschnitt von 100 Stichproben zu nehmen und nur dann Updates zu senden, wenn sich die Temperatur um mindestens 1 Grad ändert.
TemperatureCharacteristic.prototype.valueChange = function(value) {
this._total += value;
this._samples++;
if (this._samples < NO_SAMPLES) {
return;
}
var newValue = Math.round(this._total / NO_SAMPLES);
this._total = 0;
this._samples = 0;
if (this._lastValue && Math.abs(this._lastValue - newValue) < 1) {
return;
}
this._lastValue = newValue;
console.log(newValue);
var data = new Buffer(8);
data.writeDoubleLE(newValue, 0);
if (this._onChange) {
this._onChange(data);
}
};
Das war der Temperatursensor. Die Farb-LED ist einfacher. Das Objekt und die Methode „read“ sind unten dargestellt. Das Attribut ist so konfiguriert, dass Lese- und Schreibvorgänge zulässig sind. Es hat eine andere UUID als das Temperaturattribut.
var ColorCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0b',
properties: ['read', 'write'],
value: null
});
this._value = 'ffffff';
this._led = null;
};
util.inherits(ColorCharacteristic, bleno.Characteristic);
ColorCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(this._value);
callback(this.RESULT_SUCCESS, data);
};
Um die LED über das Objekt zu steuern, füge ich ein this._led
-Mitglied hinzu, in dem ich das Johnny-Five-LED-Objekt speichere. Außerdem habe ich die Farbe der LED auf den Standardwert (weiß, #ffffff
) gesetzt.
board.on("ready", function() {
...
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
...
}
Die Methode „write“ empfängt einen String (genau wie „read“ einen String sendet), der aus einem CSS-Farbcode bestehen kann (z. B. CSS-Namen wie rebeccapurple
oder Hexadezimalcodes wie #ff00bb
). Ich verwende ein Node-Modul namens parse-color, um immer den Hexadezimalwert zu erhalten, den Johnny-Five erwartet.
ColorCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
var value = parse(data.toString('utf8')).hex;
if (!value) {
callback(this.RESULT_SUCCESS);
return;
}
this._value = value;
console.log(value);
if (this._led) {
this._led.color(this._value);
}
callback(this.RESULT_SUCCESS);
};
All das funktioniert nicht, wenn wir das Modul bleno nicht einschließen.
eddystone-beacon
funktioniert nicht mit bleno, es sei denn, Sie verwenden die mitgelieferte noble
-Version. Das ist ganz einfach:
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
Jetzt müssen wir nur noch dafür sorgen, dass unser Gerät (UUID) und seine Eigenschaften (andere UUIDs) beworben werden.
bleno.on('advertisingStart', function(error) {
...
bleno.setServices([
new bleno.PrimaryService({
uuid: 'fc00',
characteristics: [
temperatureCharacteristic, colorCharacteristic
]
})
]);
});
Client-Webanwendung erstellen
Ohne zu sehr ins Detail zu gehen, wie die nicht Bluetooth-spezifischen Teile der Client-App funktionieren, können wir als Beispiel eine responsive Benutzeroberfläche zeigen, die in Polymer* erstellt wurde. Die resultierende App sieht so aus:


Auf der rechten Seite ist eine frühere Version zu sehen, die ein einfaches Fehlerprotokoll enthält, das ich zur Vereinfachung der Entwicklung hinzugefügt habe.
Mit Web Bluetooth können Sie ganz einfach mit Bluetooth Low Energy-Geräten kommunizieren. Sehen wir uns eine vereinfachte Version meines Verbindungscodes an. Wenn Sie nicht wissen, wie Versprechen funktionieren, lesen Sie diesen Artikel, bevor Sie fortfahren.
Die Verbindung mit einem Bluetooth-Gerät erfordert eine Reihe von Versprechen.
Zuerst filtern wir nach dem Gerät (UUID: FC00
, Name: Edison
). Daraufhin wird ein Dialogfeld angezeigt, in dem der Nutzer das Gerät anhand des Filters auswählen kann. Anschließend stellen wir eine Verbindung zum GATT-Dienst her und rufen den primären Dienst und die zugehörigen Eigenschaften ab. Danach lesen wir die Werte und richten Benachrichtigungs-Callbacks ein.
Die vereinfachte Version unseres Codes unten funktioniert nur mit der neuesten Web Bluetooth API und erfordert daher Chrome Dev (M49) auf Android.
navigator.bluetooth.requestDevice({
filters: [{ name: 'Edison' }],
optionalServices: [0xFC00]
})
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService(0xFC00))
.then(service => {
let p1 = () => service.getCharacteristic(0xFC0B)
.then(characteristic => {
this.colorLedCharacteristic = characteristic;
return this.readLedColor();
});
let p2 = () => service.getCharacteristic(0xFC0A)
.then(characteristic => {
characteristic.addEventListener(
'characteristicvaluechanged', this.onTemperatureChange);
return characteristic.startNotifications();
});
return p1().then(p2);
})
.catch(err => {
// Catch any error.
})
.then(() => {
// Connection fully established, unless there was an error above.
});
Das Lesen und Schreiben eines Strings von einem DataView
/ ArrayBuffer
(wie es in der WebBluetooth API verwendet wird) ist genauso einfach wie die Verwendung von Buffer
auf der Node.js-Seite. Dazu benötigen wir nur TextEncoder
und TextDecoder
:
readLedColor: function() {
return this.colorLedCharacteristic.readValue()
.then(data => {
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let decoder = new TextDecoder("utf-8");
let decodedString = decoder.decode(data);
document.querySelector('#color').value = decodedString;
});
},
writeLedColor: function() {
let encoder = new TextEncoder("utf-8");
let value = document.querySelector('#color').value;
let encodedString = encoder.encode(value.toLowerCase());
return this.colorLedCharacteristic.writeValue(encodedString);
},
Die Verarbeitung des characteristicvaluechanged
-Ereignisses für den Temperatursensor ist ebenfalls recht einfach:
onTemperatureChange: function(event) {
let data = event.target.value;
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let temperature = data.getFloat64(0, /*littleEndian=*/ true);
document.querySelector('#temp').innerHTML = temperature.toFixed(0);
},
Zusammenfassung
Das war's, Leute! Wie Sie sehen, ist die Kommunikation mit Bluetooth Low Energy über Web Bluetooth auf der Clientseite und Node.js auf der Edison-Seite recht einfach und sehr leistungsstark.
Mit dem Physical Web und Web Bluetooth findet Chrome das Gerät und ermöglicht dem Nutzer, eine einfache Verbindung herzustellen, ohne selten verwendete Anwendungen zu installieren, die der Nutzer möglicherweise nicht benötigt und die von Zeit zu Zeit aktualisiert werden.
Demo
Sie können den Client ausprobieren, um sich inspirieren zu lassen, wie Sie Ihre eigenen Web-Apps erstellen, um eine Verbindung zu Ihren benutzerdefinierten IoT-Geräten herzustellen.
Quellcode
Den Quellcode finden Sie hier. Sie können uns gerne Probleme melden oder Patches senden.
Skizze
Wenn Sie wirklich mutig sind und das nachbauen möchten, was ich getan habe, sehen Sie sich die folgende Skizze von Edison und dem Breadboard an: