Oggi l'Internet of Things è sulla bocca di tutti e chi ama truccatori e programmatori come me. Non c'è niente di più bello che dare vita alle tue invenzioni e poterci parlare.
Tuttavia, i dispositivi IoT che installano app che usi raramente possono essere fastidiosi, quindi sfruttamo le tecnologie web imminenti come il web fisico e il web Bluetooth per rendere i dispositivi IoT più intuitivi e meno invadenti.
Web e IoT: una corrispondenza da
Ci sono ancora molti ostacoli da superare prima che l'Internet of Things possa avere un enorme successo. Un ostacolo sono le aziende e i prodotti che richiedono alle persone di installare app per ogni dispositivo acquistato, ingombrando i telefoni degli utenti con una moltitudine di app che utilizzano raramente.
Per questo motivo, siamo molto entusiasti del progetto Physical Web, che consente ai dispositivi di trasmettere un URL a un sito web online in modo non invadente. In combinazione con le tecnologie web emergenti come Web Bluetooth, Web USB e Web NFC, i siti possono collegarsi direttamente al dispositivo o almeno spiegare la procedura corretta per farlo.
Anche se in questo articolo ci concentriamo principalmente sul Web Bluetooth, alcuni casi d'uso potrebbero essere più adatti a NFC web o USB web. Ad esempio, la connessione USB web è preferita se hai bisogno di una connessione fisica per motivi di sicurezza.
Il sito web può anche essere utilizzato come app web progressiva (PWA). Invitiamo i lettori a consultare la spiegazione di Google sulle PWA. Le PWA sono siti che offrono un'esperienza utente adattabile e simile a quella delle app, possono funzionare offline e possono essere aggiunte alla schermata Home del dispositivo.
Come proof of concept, ho creato un piccolo dispositivo utilizzando la scheda breakout Arduino Intel® Edison. Il dispositivo contiene un sensore di temperatura (TMP36) e un attuatore (catodo LED colorato). Puoi trovare gli schemi per questo dispositivo alla fine di questo articolo.
Intel Edison è un prodotto interessante perché può eseguire una distribuzione Linux* completa. Pertanto, posso programmarlo facilmente utilizzando Node.js. L'installatore consente di installare Intel* XDK, il che semplifica la procedura di avvio, anche se puoi anche programmare e caricare manualmente sul tuo dispositivo.
Per la mia app Node.js, ho richiesto tre moduli di nodo e le relative dipendenze:
eddystone-beacon
parse-color
johnny-five
La prima installa automaticamente noble
, ovvero il modulo nodo
che uso per parlare tramite Bluetooth Low Energy.
Il file package.json
del progetto ha il seguente aspetto:
{
"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"
}
}
Annuncio del sito web
A partire dalla versione 49, Chrome su Android supporta il web fisico, che consente a Chrome di vedere gli URL trasmessi dai dispositivi nelle vicinanze. Lo sviluppatore deve essere a conoscenza di alcuni requisiti, ad esempio la necessità che i siti siano pubblicamente accessibili e utilizzino HTTPS.
Il protocollo Eddystone ha un limite di dimensione di 18 byte per gli URL. Pertanto, per far funzionare l'URL della mia app di dimostrazione (https://webbt-sensor-hub.appspot.com/), devo utilizzare uno strumento per accorciare gli URL.
La trasmissione dell'URL è abbastanza semplice. Devi solo importare le librerie richieste e chiamare alcune funzioni. Un modo per farlo è chiamare advertiseUrl
quando il chip BLE è attivo:
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'});
}
}
Non potrebbe essere più semplice. Nell'immagine di seguito puoi vedere che Chrome trova il dispositivo senza problemi.
Comunicazione con il sensore/attuatore
Utilizziamo Johnny-Five* per comunicare con i miglioramenti della scheda. Johnny-Five ha una bella astrazione per parlare con il sensore TMP36.
Di seguito puoi trovare il semplice codice per ricevere una notifica delle variazioni di temperatura nonché per impostare il colore iniziale del LED.
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);
});
Per il momento puoi ignorare le variabili *Characteristic
sopra indicate; verranno definite nella sezione successiva sull'interfaccia con il Bluetooth.
Come noterai dalla creazione di un'istanza dell'oggetto Termometro, parlo
con la TMP36 tramite la porta analogica A0
. I terminali di tensione sul catodo del LED colorato sono collegati ai pin digitali 3, 5 e 6, che sono i pin PWM (modulazione a larghezza di impulso) sulla scheda breakout Arduino Edison.
Parlare con il Bluetooth
Parlare con il Bluetooth non potrebbe essere più facile che con noble
.
Nell'esempio seguente, creiamo due caratteristiche Bluetooth Low Energy: una per il LED e una per il sensore di temperatura. Il primo ci consente di leggere il colore corrente del LED e di impostarne uno nuovo. Quest'ultimo ci consente di iscriverci agli eventi di variazione della temperatura.
Con noble
, creare una caratteristica è abbastanza semplice. Devi solo definire la modalità di comunicazione della caratteristica e un UUID. Le opzioni di comunicazione sono read, write, notify o qualsiasi combinazione di queste.
Il modo più semplice per farlo è creare un nuovo oggetto e ereditare da
bleno.Characteristic
.
L'oggetto caratteristica risultante è il seguente:
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);
Memorizziamo il valore della temperatura attuale nella variabile this._lastValue
. Dobbiamo aggiungere un metodo onReadRequest
e codificare il valore per far funzionare una "lettura".
TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(8);
data.writeDoubleLE(this._lastValue, 0);
callback(this.RESULT_SUCCESS, data);
};
Per "notifica" dobbiamo aggiungere un metodo per gestire gli abbonamenti e l'annullamento dell'iscrizione. In pratica, memorizziamo semplicemente un callback. Quando abbiamo un nuovo motivo per la temperatura che vogliamo inviare, lo richiamiamo con il nuovo valore (codificato come sopra).
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;
};
Poiché i valori possono variare un po', dobbiamo appianare quelli che получаем dal sensore TMP36. Ho scelto di prendere una media di 100 campioni e di inviare gli aggiornamenti solo quando la temperatura cambia di almeno 1 grado.
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);
}
};
Era il sensore di temperatura. Il LED colorato è più semplice. L'oggetto e il metodo "read" sono mostrati di seguito. La caratteristica è configurata per consentire operazioni di "lettura" e "scrittura" e ha un UUID diverso rispetto alla caratteristica della temperatura.
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);
};
Per controllare il LED dall'oggetto, aggiungo un membro this._led
che utilizzo per memorizzare l'oggetto LED Johnny-Five. Ho anche impostato il colore del LED
sul valore predefinito (bianco, noto anche come #ffffff
).
board.on("ready", function() {
...
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
...
}
Il metodo "write" riceve una stringa (esattamente come "read" invia
una stringa), che può essere costituita da un codice colore CSS (ad esempio nomi CSS
come rebeccapurple
o codici esadecimali come #ff00bb
). Uso un modulo
nodo chiamato parse-color
per ottenere sempre il valore esadecimale, che è quello che si aspetta Johnny-Five.
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);
};
Tutto quanto sopra non funzionerà se non includiamo il modulo bleno.
eddystone-beacon
non funzionerà con bleno, a meno che tu non utilizzi la versione noble
distribuita con il programma. Per fortuna, è abbastanza semplice:
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
Ora ci serve solo che pubblicizzi il nostro dispositivo (UUID) e le sue caratteristiche (altri UUID)
bleno.on('advertisingStart', function(error) {
...
bleno.setServices([
new bleno.PrimaryService({
uuid: 'fc00',
characteristics: [
temperatureCharacteristic, colorCharacteristic
]
})
]);
});
Creazione dell'app web client
Senza entrare troppo nei dettagli sul funzionamento delle parti non Bluetooth dell'app client, possiamo mostrare un'interfaccia utente adattabile creata in Polymer* come esempio. L'app risultante è mostrata di seguito:
A destra è mostrata una versione precedente, con un semplice log degli errori che ho aggiunto per facilitare lo sviluppo.
Il Web Bluetooth semplifica la comunicazione con i dispositivi Bluetooth Low Energy, quindi diamo un'occhiata a una versione semplificata del mio codice di connessione. Se non sai come funzionano le promesse, consulta questa risorsa prima di continuare a leggere.
La connessione a un dispositivo Bluetooth prevede una catena di promesse.
Innanzitutto, filtriamo in base al dispositivo (UUID: FC00
, nome: Edison
). Viene visualizzata una finestra di dialogo per consentire all'utente di selezionare il dispositivo in base al filtro. Poi ci colleghiamo al servizio GATT e otteniamo il servizio primario e le caratteristiche associate, quindi leggiamo i valori e impostiamo i callback di notifica.
La versione semplificata del nostro codice riportata di seguito funziona solo con l'API Web Bluetooth più recente, pertanto richiede Chrome Dev (M49) su 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.
});
Leggere e scrivere una stringa da un DataView
/ ArrayBuffer
(come viene utilizzata dall'API WebBluetooth) è facile come utilizzare Buffer
lato Node.js. Ci bastano TextEncoder
e 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);
},
Anche la gestione dell'evento characteristicvaluechanged
per il sensore di temperatura è abbastanza semplice:
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);
},
Riepilogo
È tutto, gente! Come puoi vedere, comunicare con Bluetooth Low Energy utilizzando Web Bluetooth sul lato client e Node.js su Edison è piuttosto semplice e molto potente.
Utilizzando il web fisico e il web Bluetooth, Chrome trova il dispositivo e consente all'utente di connettersi facilmente senza installare applicazioni raramente utilizzate che l'utente potrebbe non volere e che potrebbero aggiornarsi di volta in volta.
Demo
Puoi provare il client per farti ispirare su come creare le tue app web per connetterti ai tuoi dispositivi IoT personalizzati.
Codice sorgente
Il codice sorgente è disponibile qui. Non esitare a segnalare problemi o inviare patch.
Schizzo
Se hai voglia di sperimentare e vuoi riprodurre ciò che ho fatto, consulta lo sketch di Edison e della breadboard di seguito: