Webfähige IoT-Geräte mit Intel Edison erstellen

Kenneth Christiansen
Kenneth Christiansen

Das Internet der Dinge ist heutzutage in aller Munde, es begeistert Täter und Programmierer wie ich sehr. Ihre eigenen Erfindungen werden zum Leben erweckt und Sie können mit ihnen kommunizieren.

IoT-Geräte, über die Anwendungen installiert werden, die Sie selten verwenden, können jedoch lästig sein. Daher nutzen wir neue Webtechnologien wie Physical Web und Web Bluetooth, um IoT-Geräte intuitiver und weniger aufdringlich zu gestalten.

Clientanwendung

Web und IoT

Es gibt noch viele Hürden, bevor das Internet der Dinge ein Riesenerfolg sein kann. Ein Hindernis sind Unternehmen und Produkte, bei denen Nutzer für jedes gekaufte Gerät Apps installieren müssen. Dadurch überfrachten die Smartphones der Nutzer eine Vielzahl von Apps, die sie selten verwenden.

Aus diesem Grund freuen wir uns sehr über das Physical Web-Projekt, mit dem Geräte ohne aufdringliche Weise eine URL zu einer Online-Website übertragen können. In Kombination mit neuen Webtechnologien wie Web Bluetooth, Web USB und Web NFC können die Websites eine direkte Verbindung zum Gerät herstellen oder zumindest erklären, wie dies richtig funktioniert.

Obwohl wir uns in diesem Artikel hauptsächlich auf Web Bluetooth konzentrieren, sind einige Anwendungsfälle möglicherweise besser für Web NFC oder Web USB geeignet. Web-USB wird beispielsweise bevorzugt, 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 Nutzererfahrung 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 Breakout von Intel® Edison Arduino entwickelt. Das Gerät enthält einen Temperatursensor (TMP36) sowie einen Auslöser (farbige LED-Kathode). Die Schaltpläne für dieses Gerät findest du am Ende dieses Artikels.

Steckplatine.

Intel Edison ist ein interessantes Produkt, da es eine vollständige Linux*-Distribution ausführen kann. Daher kann ich es einfach mit Node.js programmieren. Mit dem Installationsprogramm können Sie Intel* XDK installieren, wodurch der Einstieg erleichtert wird. Sie können jedoch auch manuell programmieren und Inhalte auf Ihr Gerät hochladen.

Für meine Node.js-Anwendung benötigte ich drei Knotenmodule sowie deren Abhängigkeiten:

  • eddystone-beacon
  • parse-color
  • johnny-five

Bei Ersterem wird automatisch noble installiert. Das ist das Knotenmodul, mit dem ich über Bluetooth Low Energy spreche.

Die Datei package.json 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"
    }
}

Bekanntgabe der Website

Ab Version 49 unterstützt Chrome unter Android Physical Web, wodurch Chrome URLs sehen kann, die von Geräten in der Umgebung übertragen werden. Es gibt einige Anforderungen, die der Entwickler kennen muss, z. B. muss die Website ö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 Demoanwendung funktioniert (https://webbt-sensor-hub.appspot.com/), muss ich einen URL-Kürzer verwenden.

Das Übertragen der URL ist ziemlich einfach. Sie müssen nur die erforderlichen Bibliotheken importieren und einige Funktionen aufrufen. Eine Möglichkeit dazu 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 könnte wirklich nicht einfacher sein. Sie sehen im Bild unten, dass Chrome das Gerät gut findet.

Chrome meldet Physical Web-Beacons in der Nähe.
Die Web-App-URL ist aufgeführt.

Kommunikation mit dem Sensor/Aktuator

Wir nutzen Johnny-Five*, um mit unseren Vorstandsmitgliedern zu sprechen. Johnny-Five hat eine schöne Abstraktion für die Kommunikation mit dem TMP36-Sensor.

Unten findest du den einfachen Code für die Benachrichtigung über Temperaturänderungen sowie die Einstellung der anfänglichen LED-Farbe.

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);
});

Sie können die obigen *Characteristic-Variablen vorerst ignorieren. Sie werden in einem späteren Abschnitt zur Schnittstelle zu Bluetooth definiert.

Wie Sie in der Instanziierung des Thermometer-Objekts sehen, spreche ich über den analogen A0-Anschluss mit dem TMP36. Die Spannungsstränge der Farb-LED-Kathode sind mit den digitalen Kontakten 3, 5 und 6 verbunden, bei denen es sich zufällig um die Pins für die Pulsbreitenmodulation (PWM) auf dem Edison Arduino-Breakout handelt.

Edison-Board

Über Bluetooth sprechen

Mit noble kannst du über Bluetooth sprechen.

Im folgenden Beispiel erstellen wir zwei Bluetooth Low Energy-Eigenschaften: eine für die LED und eine für den Temperatursensor. Mit Ersteren können wir die aktuelle LED-Farbe lesen und eine neue Farbe festlegen. Letzteres ermöglicht es, Temperaturänderungsereignisse zu abonnieren.

Mit noble ist das Erstellen eines Merkmals ziemlich einfach. Sie müssen lediglich definieren, 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 Daten von bleno.Characteristic zu übernehmen.

Das sich daraus ergebende 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 Wert der aktuellen Temperatur wird in der Variablen this._lastValue gespeichert. Wir müssen eine onReadRequest-Methode hinzufügen und den Wert codieren, damit „read“ 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 Kündigungen zu verarbeiten. Im Grunde speichern wir einfach einen Callback. Wenn wir einen neuen Temperaturgrund haben, den wir senden möchten, rufen wir diesen Callback 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 ein wenig schwanken können, müssen wir die vom TMP36-Sensor erhaltenen Werte glätten. Ich habe mich dafür entschieden, einfach durchschnittlich 100 Proben zu erfassen 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 sowie die "read"-Methode werden unten gezeigt. Die Eigenschaft ist so konfiguriert, dass Lese- und Schreibvorgänge zulässig sind, und hat eine andere UUID als die Temperatureigenschaft.

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 vom Objekt aus zu steuern, füge ich ein this._led-Mitglied hinzu, in dem ich das Johnny-Five-LED-Objekt speichere. Außerdem stelle ich die Farbe der LED auf den Standardwert (Weiß, bzw. #ffffff) ein.

board.on("ready", function() {
    ...
    colorCharacteristic._led = led;
    led.color(colorCharacteristic._value);
    led.intensity(30);
    ...
}

Die Methode „write“ empfängt einen String (wie „read“ sendet einen String), der aus einem CSS-Farbcode bestehen kann (z. B. CSS-Namen wie rebeccapurple oder Hexadezimalcodes wie #ff00bb). Ich verwende ein Knotenmodul namens parse-color, um immer den Hexadezimalwert zu erhalten, der 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 Modul bleno funktionieren alle oben genannten Funktionen nicht. eddystone-beacon funktioniert nicht mit Bleno, es sei denn, Sie verwenden die damit vertriebene Version noble. Zum Glück ist das ganz einfach:

var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');

Jetzt brauchen wir nur noch, dass es unser Gerät (UUID) und seine Eigenschaften (andere UUIDs) bewerben.

bleno.on('advertisingStart', function(error) {
    ...
    bleno.setServices([
        new bleno.PrimaryService({
        uuid: 'fc00',
        characteristics: [
            temperatureCharacteristic, colorCharacteristic
        ]
        })
    ]);
});

Client-Webanwendung erstellen

Wir können als Beispiel eine responsive Benutzeroberfläche zeigen, die in Polymer* erstellt wurde, ohne zu viele Details zur Funktionsweise der Nicht-Bluetooth-Teile der Clientanwendung zu erläutern. Die resultierende Anwendung wird unten angezeigt:

Client-App auf dem Smartphone
Eine Nachricht zu einem Fehler.

Rechts ist eine frühere Version mit einem einfachen Fehlerprotokoll zu sehen, das ich hinzugefügt habe, um die Entwicklung zu erleichtern.

Web Bluetooth erleichtert die Kommunikation mit Bluetooth Low Energy-Geräten. Sehen wir uns daher eine vereinfachte Version meines Verbindungscodes an. Wenn du nicht weißt, wie Promis funktionieren, lies dir diese Ressource durch, bevor du weiterliest.

Das Verbinden mit einem Bluetooth-Gerät beinhaltet 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 Merkmale ab. Dann 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) 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 DataView / ArrayBuffer, was die WebBluetooth API nutzt, ist genauso einfach wie Buffer auf der Node.js-Seite. Wir benötigen lediglich 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);
},

Der Umgang mit dem Ereignis characteristicvaluechanged 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 es, Leute! Wie Sie sehen, ist die Kommunikation mit Bluetooth Low Energy über Web Bluetooth auf der Clientseite und Node.js auf Edison recht einfach und sehr leistungsstark.

Mithilfe von Physical Web und Web Bluetooth findet Chrome das Gerät und ermöglicht dem Nutzer, eine einfache Verbindung zu ihm herzustellen, ohne selten verwendete Anwendungen zu installieren, die der Nutzer möglicherweise nicht möchte und die gelegentlich aktualisiert werden kann.

Demo

Sie können den Client ausprobieren, um sich inspirieren zu 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 Probleme melden oder Patches senden.

Sketch

Wenn Sie wirklich abenteuerlustig sind und reproduzieren möchten, was ich getan habe, sehen Sie sich die folgende Skizze von Edison und Steckplatine an:

Sketch