L'Internet des objets est sur toutes les lèvres de nos jours, et il rend les bricoleurs et programmeurs comme moi très enthousiastes. Rien n'est plus cool que de donner vie à vos propres inventions et de pouvoir leur parler !
Toutefois, les appareils IoT qui installent des applications que vous utilisez rarement peuvent être gênants. Nous profitons donc des technologies Web à venir, telles que le Web physique et le Web Bluetooth, pour rendre les appareils IoT plus intuitifs et moins intrusifs.
Web et IoT, un duo gagnant
De nombreux obstacles restent à surmonter pour que l'Internet des objets puisse être un succès. Les entreprises et les produits qui exigent des utilisateurs qu'ils installent des applications pour chaque appareil qu'ils achètent constituent un obstacle, car ils encombrent les téléphones des utilisateurs avec une multitude d'applications qu'ils utilisent rarement.
C'est pourquoi nous sommes très enthousiastes à propos du projet Web physique, qui permet aux appareils de diffuser une URL vers un site Web en ligne, de manière non intrusive. En combinaison avec les technologies Web émergentes telles que le Bluetooth Web, le USB Web et le NFC Web, les sites peuvent se connecter directement à l'appareil ou au moins expliquer comment procéder.
Bien que nous nous concentrions principalement sur le Bluetooth Web dans cet article, certains cas d'utilisation peuvent être mieux adaptés à Web NFC ou Web USB. Par exemple, une connexion Web USB 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 Google sur les PWA. Les PWA sont des sites qui offrent une expérience utilisateur responsive semblable à une application. Ils peuvent fonctionner hors connexion et peuvent être ajoutés à l'écran d'accueil de l'appareil.
À titre de preuve de concept, j'ai créé un petit appareil à l'aide de la carte de développement Arduino Intel® Edison. L'appareil contient un capteur de température (TMP36) ainsi qu'un actionneur (cathode de LED colorée). Vous trouverez les schémas de cet appareil à la fin de cet article.
L'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 l'Intel* XDK, ce qui facilite la prise en main, mais vous pouvez également programmer et importer manuellement sur votre appareil.
Pour mon application Node.js, j'ai eu besoin de trois modules Node, ainsi que de leurs dépendances:
eddystone-beacon
parse-color
johnny-five
Le premier installe automatiquement noble
, qui est le module de nœud que j'utilise pour communiquer via le 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, ce qui lui permet de voir les URL diffusées par les appareils à proximité. Le développeur doit connaître certaines exigences, comme le fait que les sites doivent être accessibles au public et utiliser HTTPS.
Le protocole Eddystone impose une limite de taille de 18 octets aux URL. Pour que l'URL de mon application de démonstration fonctionne (https://webbt-sensor-hub.appspot.com/), je dois utiliser un service de raccourcissement 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'});
}
}
C'est vraiment simple. Comme vous pouvez le voir dans l'image ci-dessous, Chrome trouve l'appareil parfaitement.
Communiquer avec le capteur/l'actionneur
Nous utilisons Johnny-Five* pour communiquer avec les améliorations de la carte. Johnny-Five propose une abstraction intéressante pour communiquer avec le capteur TMP36.
Vous trouverez ci-dessous le 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 le moment. Elles seront définies dans la section suivante sur l'interface avec le Bluetooth.
Comme vous pouvez le constater lors de l'instanciation de l'objet Thermomètre, je communique avec le contrôleur TMP36 via le port analogique A0
. Les pattes 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 la largeur d'impulsion (PWM, Pulse-width modulation) de la carte de répartition Edison Arduino.
Parler au Bluetooth
Il n'existe pas de moyen plus simple de communiquer avec le Bluetooth que d'utiliser 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 nous permet de nous abonner aux événements de changement de température.
Avec noble
, créer une caractéristique est assez simple. 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, la notification ou toute combinaison de ces éléments.
Le moyen le plus simple de procéder est de créer un objet et d'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 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. En gros, nous stockons simplement un rappel. Lorsque nous souhaitons envoyer une nouvelle raison de température, 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 un peu, nous devons lisser les valeurs que nous obtenons du capteur TMP36. J'ai choisi de prendre simplement la moyenne de 100 échantillons et de n'envoyer des mises à jour que lorsque la température change d'au moins 1 degrés.
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. Le voyant 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 d'"écriture". Elle possède un UUID différent de celui 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 à partir de l'objet, j'ajoute un membre this._led
que j'utilise pour stocker l'objet LED Johnny-Five. Je définis également la couleur de la LED sur sa valeur par défaut (blanc, ou #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 consister en un code de couleur CSS (par exemple, des noms CSS tels que rebeccapurple
ou des codes hexadécimaux tels que #ff00bb
). J'utilise un module de nœud appelé parse-color pour obtenir toujours la valeur hexadécimale, ce que Johnny-Five attend.
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, c'est assez simple:
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
Il ne nous reste plus qu'à diffuser 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 entrer dans trop de détails sur le fonctionnement des parties non Bluetooth de l'application cliente, nous pouvons montrer une interface utilisateur responsive créée en Polymer* à titre d'exemple. L'application obtenue est présentée ci-dessous:
Le côté droit affiche une version antérieure, qui présente un journal des erreurs simple que j'ai ajouté pour faciliter le développement.
Web Bluetooth facilite la communication avec les appareils Bluetooth à basse consommation. Examinons donc une version simplifiée de mon code de connexion. Si vous ne savez pas comment fonctionnent les promesses, consultez cette ressource avant de continuer.
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. Nous nous connectons ensuite au service GATT, obtenons le service principal et les caractéristiques associées, puis lisons les valeurs et configurons les rappels de notification.
La version simplifiée de notre code ci-dessous ne fonctionne qu'avec la dernière API Web Bluetooth et 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 DataView
/ ArrayBuffer
(ce que l'API WebBluetooth utilise) est aussi simple que d'utiliser Buffer
côté Node.js. Tout ce que nous avons besoin d'utiliser est 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);
},
La gestion 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 tout ! Comme vous pouvez le constater, la communication avec le Bluetooth basse consommation à l'aide de Web Bluetooth côté client et de Node.js sur Edison est assez simple et très efficace.
À l'aide du Web physique et du Web Bluetooth, Chrome détecte l'appareil et permet à l'utilisateur de s'y connecter facilement sans installer d'applications rarement utilisées qu'il ne souhaite peut-être pas et qui peuvent être mises à jour de temps en temps.
Démo
Vous pouvez essayer le client pour vous inspirer sur la façon de créer vos propres applications Web pour vous connecter à vos appareils IoT personnalisés.
Code source
Le code source est disponible sur cette page. N'hésitez pas à signaler des problèmes ou à envoyer des correctifs.
Croquis
Si vous êtes vraiment aventureux et que vous souhaitez reproduire ce que j'ai fait, consultez le schéma d'Edison et de la plaque à pain ci-dessous: