Das Internet der Dinge ist heutzutage einfach nur in den Lippen, und es begeistert Tüfter 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.
Daher freuen wir uns sehr über das physische Web-Projekt, mit dem Geräte auf unaufdringliche Weise eine URL an eine Onlinewebsite ü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.
Obwohl wir uns in diesem Artikel hauptsächlich auf Web Bluetooth konzentrieren, sind einige Anwendungsfälle für Web NFC oder Web USB möglicherweise besser geeignet. 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 Lesern, sich die Erklärung von Google zu PWAs anzusehen. PWAs sind Websites, die eine responsive, appähnliche Benutzeroberfläche bieten, 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 es eine vollständige Linux*-Distribution ausführen kann. Daher kann ich es 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 Node-Modul, 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. Der Entwickler muss einige Anforderungen kennen, beispielsweise die Notwendigkeit, dass die Websites öffentlich zugänglich sein und HTTPS verwenden.
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.
Mit dem Sensor/Aktor kommunizieren
Wir verwenden Johnny-Five*, um mit unserem 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 der ersten Schaltfläche können wir die aktuelle LED-Farbe erkennen 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 geht das, indem Sie ein neues Objekt erstellen und von bleno.Characteristic
ableiten.
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);
Der aktuelle Temperaturwert wird in der Variablen this._lastValue
gespeichert. 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. Ich habe auch die Farbe der LED auf den Standardwert gesetzt (weiß, auch #ffffff
genannt).
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);
};
Ohne das bleno-Modul funktionieren die obigen Ausführungen nicht.
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
Wir können Ihnen als Beispiel eine in Polymer* erstellte responsive Benutzeroberfläche zeigen, ohne auf zu viele Fehler in Bezug auf die Funktionsweise der nicht Bluetooth-Komponenten der Client-App einzugehen. Die daraus resultierende App ist unten zu sehen:
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, über das 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 des folgenden Codes funktioniert nur mit der neuesten Web Bluetooth API und erfordert daher Chrome Dev (M49) unter 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 aus einem DataView
/ ArrayBuffer
(das von 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 ganz 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 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 und sich inspirieren lassen, wie Sie eigene Webanwendungen erstellen können, um eine Verbindung zu Ihren benutzerdefinierten Geräten für das Internet der Dinge 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: