Créer un appareil IoT Web avec Intel Edison

Kenneth Christiansen
Kenneth Christiansen

L'Internet des objets est sur les lèvres de tout le monde de nos jours et il rend les bricoleurs et les programmeurs comme moi très enthousiastes. Quoi de plus cool que de donner vie à vos inventions et de pouvoir leur parler ?

Toutefois, les appareils IoT qui installent des applications que vous utilisez rarement peuvent s'avérer agaçants. C'est pourquoi nous tirons parti des technologies Web à venir telles que le Web physique et le Bluetooth Web pour rendre les appareils IoT plus intuitifs et moins intrusifs.

Application cliente

Web et IoT : une véritable aubaine pour

Il reste encore de nombreux obstacles à surmonter pour que l'Internet des objets connaisse un énorme succès. Un obstacle concerne les entreprises et les produits qui obligent les utilisateurs à installer des applications pour chaque appareil acheté, en encombrant les téléphones des utilisateurs avec une multitude d'applications qu'ils utilisent rarement.

C'est pourquoi nous sommes fiers du projet Web physique, qui permet aux appareils de diffuser une URL vers un site Web en ligne de manière non intrusive. Combinés à des technologies Web émergentes telles que le Bluetooth Web, le Web USB et le NFC Web, les sites peuvent se connecter directement à l'appareil ou au moins expliquer la bonne marche à suivre.

Bien que nous nous concentrions principalement sur le Bluetooth Web dans cet article, certains cas d'utilisation peuvent être mieux adaptés au Web NFC ou au Web USB. Par exemple, l'USB Web est préférable si vous avez besoin d'une connexion physique pour des raisons de sécurité.

Le site Web peut également servir de progressive web app (PWA). Nous encourageons les lecteurs à consulter les explications de Google sur les PWA. Les PWA sont des sites qui offrent une expérience utilisateur responsive semblable à une application, peuvent fonctionner hors connexion et peuvent être ajoutées à l'écran d'accueil de l'appareil.

Pour preuve de concept, j'ai conçu un petit appareil à l'aide de la carte de développement Intel® Edison Arduino. L'appareil contient un capteur de température (TMP36) et un actionneur (cathode à LED colorée). Les schémas de cet appareil sont disponibles à la fin de cet article.

Platine d'expérimentation.

Intel Edison est un produit intéressant, car il peut exécuter une distribution Linux* complète. Je peux donc facilement le programmer à l'aide de Node.js. Le programme d'installation vous permet d'installer Intel* XDK, ce qui facilite la mise en route, bien que vous puissiez également programmer et importer manuellement des fichiers sur votre appareil.

Pour mon application Node.js, j'avais besoin de trois modules de nœuds, ainsi que de leurs dépendances:

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

La première installe automatiquement noble, qui est le module de nœud que j'utilise pour communiquer via Bluetooth à basse consommation.

Le fichier package.json du projet se présente comme suit:

{
    "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"
    }
}

Annonce du site Web

À partir de la version 49, Chrome sur Android est compatible avec le Web physique, qui permet à Chrome de voir les URL diffusées par les appareils situés à proximité. Le développeur doit connaître certaines exigences, comme la nécessité pour les sites d'être accessibles publiquement et d'utiliser HTTPS.

Le protocole Eddystone limite la taille des URL à 18 octets. Par conséquent, pour que l'URL de mon application de démonstration fonctionne (https://webbt-sensor-hub.appspot.com/), j'ai besoin d'un réducteur d'URL.

La diffusion de l'URL est assez simple. Il vous suffit d'importer les bibliothèques requises et d'appeler quelques fonctions. Pour ce faire, vous pouvez appeler advertiseUrl lorsque la puce BLE est activée:

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

Rien de plus simple. Dans l'image ci-dessous, Chrome trouve l'appareil idéalement.

Chrome annonce les balises Web physique à proximité.
L'URL de l'application Web s'affiche.

Communication avec le capteur/l'actionneur

Nous utilisons Johnny-Five* pour parler des améliorations de notre tableau de bord. Johnny-Five dispose d'une bonne abstraction pour communiquer avec le capteur TMP36.

Vous trouverez ci-dessous un code simple permettant d'être averti des changements de température et de définir la couleur initiale du voyant.

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

Vous pouvez ignorer les variables *Characteristic ci-dessus pour l'instant. Elles seront définies dans la section ultérieure sur l'interface avec le Bluetooth.

Comme vous pouvez le remarquer lors de l'instanciation de l'objet Thermomètre, je communique avec le TMP36 via le port analogique A0. Les branches de tension de la cathode LED couleur sont connectées aux broches numériques 3, 5 et 6, qui sont les broches de modulation de largeur d'impulsion (PWM) sur la carte de répartition Edison Arduino.

Planche d'Edison

Communication via le Bluetooth...

Communiquer via le Bluetooth n'a jamais été aussi simple qu'avec noble.

Dans l'exemple suivant, nous créons deux caractéristiques Bluetooth à basse consommation: une pour la LED et une pour le capteur de température. Le premier nous permet de lire la couleur actuelle de la LED et de définir une nouvelle couleur. Ce dernier permet de nous abonner aux événements de changement de température.

Avec noble, il est assez facile de créer une caractéristique. Il vous suffit de définir la manière dont la caractéristique communique et de définir un UUID. Les options de communication sont la lecture, l'écriture, l'envoi de notifications ou une combinaison de ces options. Pour ce faire, le moyen le plus simple consiste à créer un objet et à hériter de bleno.Characteristic.

L'objet caractéristique obtenu se présente comme suit:

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

Nous stockons la valeur de température actuelle dans la variable this._lastValue. Nous devons ajouter une méthode onReadRequest et encoder la valeur pour qu'une "lecture" fonctionne.

TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
    var data = new Buffer(8);
    data.writeDoubleLE(this._lastValue, 0);
    callback(this.RESULT_SUCCESS, data);
};

Pour "notify", nous devons ajouter une méthode pour gérer les abonnements et les désabonnements. Il nous suffit de stocker un rappel. Lorsque nous avons un nouveau motif de température à envoyer, nous appelons ce rappel avec la nouvelle valeur (encodée comme ci-dessus).

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

Comme les valeurs peuvent fluctuer légèrement, nous devons lisser celles obtenues par le capteur TMP36. J'ai choisi de simplement prendre la moyenne de 100 échantillons et d'envoyer des mises à jour uniquement lorsque la température change d'au moins 1 degré.

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

C'était le capteur de température. La LED couleur est plus simple. L'objet et la méthode "read" sont présentés ci-dessous. La caractéristique est configurée pour autoriser les opérations de "lecture" et "écriture" et possède un UUID différent de la caractéristique de température.

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

Pour contrôler la LED de l'objet, j'ajoute un membre this._led que j'utilise pour stocker l'objet LED Johnny-Five. J'ai également défini la couleur de la LED sur sa valeur par défaut (blanc, c'est-à-dire #ffffff).

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

La méthode "write" reçoit une chaîne (tout comme "read" envoie une chaîne), qui peut être constituée d'un code couleur CSS (par exemple, des noms CSS tels que rebeccapurple ou des codes hexadécimaux comme #ff00bb). J'utilise un module de nœud appelé parse-color pour obtenir toujours la valeur hexadécimale, ce qui correspond à ce à quoi Johnny-Five s'attend à recevoir.

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

Tout ce qui précède ne fonctionnera pas si nous n'incluons pas le module bleno. eddystone-beacon ne fonctionne pas avec bleno, sauf si vous utilisez la version noble distribuée avec celui-ci. Heureusement, cela est assez simple:

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

Il ne nous reste plus qu'à annoncer notre appareil (UUID) et ses caractéristiques (autres UUID).

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

Créer l'application Web cliente

Sans aborder trop d'échecs sur le fonctionnement des parties non Bluetooth de l'application cliente, nous pouvons illustrer une interface utilisateur responsive créée dans Polymer* à titre d'exemple. L'application obtenue est présentée ci-dessous:

Application cliente sur le téléphone.
Message d&#39;erreur

La partie droite montre une version antérieure, qui présente un journal d'erreurs simple que j'ai ajouté pour faciliter le développement.

Web Bluetooth facilite la communication avec les appareils Bluetooth basse consommation. Voyons donc une version simplifiée de mon code de connexion. Si vous ne savez pas comment fonctionnent les promesses, consultez cette ressource avant de poursuivre votre lecture.

La connexion à un appareil Bluetooth implique une chaîne de promesses. Nous filtrons d'abord l'appareil (UUID: FC00, nom: Edison). Une boîte de dialogue s'affiche pour permettre à l'utilisateur de sélectionner l'appareil en fonction du filtre. Ensuite, nous nous connectons au service GATT et obtenons le service principal et les caractéristiques associées, puis nous lisons les valeurs et configurons des rappels de notification.

La version simplifiée du code ci-dessous ne fonctionne qu'avec la dernière API Web Bluetooth. Elle nécessite donc Chrome Dev (M49) sur 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.
});

Lire et écrire une chaîne à partir d'un élément DataView / ArrayBuffer (utilisé par l'API WebBluetooth) est tout aussi facile que d'utiliser Buffer côté Node.js. Il nous suffit d'utiliser TextEncoder et 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);
},

Le traitement de l'événement characteristicvaluechanged pour le capteur de température est également assez facile:

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

Résumé

C'est terminé ! Comme vous pouvez le voir, communiquer avec Bluetooth Low Energy en utilisant Web Bluetooth côté client et Node.js sur Edison est assez facile et très puissant.

À l'aide du Web physique et du Bluetooth Web, Chrome détecte l'appareil et permet à l'utilisateur de s'y connecter facilement sans installer d'applications rarement utilisées, que l'utilisateur ne souhaite pas forcément et qui peuvent être mises à jour de temps en temps.

Démonstration

Vous pouvez essayer le client pour savoir comment créer vos propres applications Web pour vous connecter à vos appareils personnalisés de l'Internet des objets.

Code source

Le code source est disponible ici. N'hésitez pas à signaler les problèmes ou à envoyer des correctifs.

Sketch

Si vous êtes vraiment audacieux et que vous souhaitez reproduire ce que j'ai fait, reportez-vous au croquis d'Edison et de la platine d'expérimentation ci-dessous:

Sketch