Creazione di un dispositivo IoT abilitato per il web con Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Al giorno d'oggi l'Internet of Things è sulla bocca di tutti e suscita l'entusiasmo da parte di esperti e programmatori come me. Non c'è niente di più bello che dare vita alle tue invenzioni ed essere in grado di parlare con loro.

Tuttavia, i dispositivi IoT che installano app che utilizzi raramente possono essere fastidiosi. Per questo motivo, utilizziamo le prossime tecnologie web come Physical Web e Bluetooth sul web per rendere i dispositivi IoT più intuitivi e meno invasivi.

Applicazione client

Web e IoT, insieme

Ci sono ancora molti ostacoli da superare prima che Internet of Things possa avere un enorme successo. Un ostacolo è rappresentato dalle aziende e dai prodotti che richiedono agli utenti di installare app per ogni dispositivo acquistato, inserendo gli smartphone degli utenti con una moltitudine di app che usano raramente.

Per questo motivo, siamo 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 tecnologie web emergenti come Web Bluetooth, Web USB e Web NFC, i siti possono connettersi direttamente al dispositivo o almeno spiegare il modo corretto di farlo.

Anche se in questo articolo ci concentriamo principalmente sul Bluetooth web, alcuni casi d'uso potrebbero essere più adatti a Web NFC o Web USB. Ad esempio, è preferibile una connessione Web USB se è necessaria 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 con un'esperienza utente adattabile, simile a quella delle app, che possono funzionare offline e possono essere aggiunti alla schermata Home del dispositivo.

Come proof of concept, ho creato un piccolo dispositivo utilizzando la scheda di lavoro Intel® Edison Arduino. Il dispositivo contiene un sensore di temperatura (TMP36) e un attuatore (catodo LED colorato). Gli schemi per questo dispositivo sono disponibili alla fine di questo articolo.

Breadboard.

Intel Edison è un prodotto interessante perché può eseguire una distribuzione Linux* completa. Quindi posso programmarlo facilmente utilizzando Node.js. Il programma di installazione consente di installare Intel* XDK, che semplifica l'avvio, ma puoi anche programmare e caricare sul tuo dispositivo manualmente.

Per la mia app Node.js, ho richiesto tre moduli di nodi, oltre alle loro dipendenze:

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

Il primo installa automaticamente noble, che è 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 Physical Web, 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 accessibili pubblicamente e utilizzino HTTPS.

Il protocollo Eddystone ha un limite di dimensione di 18 byte sugli URL. Per far funzionare l'URL della mia app demo (https://webbt-sensor-hub.appspot.com/), devo utilizzare uno strumento per accorciare gli URL.

La trasmissione dell'URL è piuttosto semplice. Tutto ciò che serve per importare le librerie richieste e chiamare alcune funzioni. Un modo per farlo è chiamare advertiseUrl quando il chip BLE è attivato:

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ù facile. Come vedi nell'immagine qui sotto, Chrome trova il dispositivo correttamente.

Chrome annuncia beacon Physical Web nelle vicinanze.
Viene indicato l'URL dell'app web.

Comunicazione con sensore/attuatore

Ci serviamo di Johnny-Five* per parlare con i miglioramenti del nostro CdA. Johnny-Five ha una bella astrazione per comunicare con il sensore TMP36.

Di seguito trovi il semplice codice per ricevere una notifica in caso di variazioni della temperatura e 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 ora puoi ignorare le variabili *Characteristic sopra riportate; saranno definite nella sezione successiva sull'interfaccia con Bluetooth.

Come avrai notato nell'istanza dell'oggetto Termometro, parlo con il TMP36 tramite la porta analogica A0. I piedini di tensione sul catodo LED colorato sono collegati ai pin digitali 3, 5 e 6, che sono i pin di modulazione di larghezza di polso (PWM) sulla scheda di lavoro Edison Arduino.

Lavagna vintage

Comunicazione tramite Bluetooth

Parlare tramite Bluetooth non potrebbe essere più facile di quello 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 l'attuale colore del LED e di impostarne uno nuovo. Quest'ultimo ci consente di iscriverti agli eventi di variazione della temperatura.

Con noble, creare una caratteristica è piuttosto semplice. Devi solo definire il modo in cui la caratteristica comunica e definire un UUID. Le opzioni di comunicazione sono lettura, scrittura, notifica o qualsiasi combinazione di queste. Il modo più semplice per farlo è creare un nuovo oggetto ed ereditare da bleno.Characteristic.

L'oggetto caratteristico risultante ha il seguente aspetto:

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

Stiamo memorizzando il valore della temperatura attuale nella variabile this._lastValue. Dobbiamo aggiungere un metodo onReadRequest e codificare il valore affinché un comando "lettura" funzioni.

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

Per la "notifica" dobbiamo aggiungere un metodo per gestire gli abbonamenti e l'annullamento dell'abbonamento. In sostanza, memorizziamo semplicemente una richiamata. Quando abbiamo un nuovo motivo per la temperatura che vogliamo inviare, lo chiamiamo chiamato 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 leggermente, dobbiamo livellare quelli ottenuti dal sensore TMP36. Ho optato per acquisire semplicemente la media di 100 campioni e 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 a colori è più semplice. Di seguito sono mostrati l'oggetto e il metodo "read". La caratteristica è configurata per consentire operazioni di "lettura" e "scrittura" e ha un UUID diverso dalla 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 uso per archiviare 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, proprio 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). Utilizzo un modulo dei nodi chiamato parse-color per ottenere sempre il valore esadecimale, che è quello che Johnny-Five si aspetta.

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

Tutte le risposte precedenti non funzioneranno se non includiamo il modulo bleno. eddystone-beacon non funzionerà con bleno a meno che non utilizzi la versione noble distribuita con questo dispositivo. Fortunatamente farlo è abbastanza semplice:

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

Ora tutto ciò che serve è 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 dover affrontare troppi errori sul funzionamento delle parti non Bluetooth dell'app client, possiamo dimostrare come esempio un'interfaccia utente reattiva creata in Polymer*. L'app risultante è mostrata di seguito:

App client sullo smartphone.
Messaggio di errore.

Il lato destro mostra una versione precedente, con un semplice log degli errori che ho aggiunto per facilitare lo sviluppo.

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 il dispositivo (UUID: FC00, nome: Edison). Viene visualizzata una finestra di dialogo che consente all'utente di selezionare il dispositivo in questione. Dopodiché ci connettiamo al servizio GATT e otteniamo il servizio principale e le caratteristiche associate, quindi leggiamo i valori e impostiamo i callback delle notifiche.

La versione semplificata del nostro codice riportato di seguito funziona solo con l'API Web Bluetooth più recente e 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 DataView / ArrayBuffer (utilizzo dell'API WebBluetooth) è semplice quanto l'uso di Buffer sul lato Node.js. Tutto quello che dobbiamo usare sono 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 è piuttosto 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

E questo era tutto! Come puoi vedere, comunicare con Bluetooth LowEnergy tramite Web Bluetooth sul lato client e Node.js su Edison è piuttosto semplice e molto potente.

Tramite Physical Web e Bluetooth Web, Chrome trova il dispositivo e consente all'utente di connettersi facilmente senza installare applicazioni raramente che l'utente potrebbe non volere e che potrebbero essere aggiornate di tanto in tanto.

Demo

Puoi provare il client per trovare l'ispirazione su come creare le tue app web per la connessione ai tuoi dispositivi Internet of Things personalizzati.

Codice sorgente

Il codice sorgente è disponibile qui. Non esitare a segnalare i problemi o a inviare patch.

Schizzo

Se sei davvero avventuroso e vuoi riprodurre ciò che ho fatto, fai riferimento allo schizzo di Edison e breadboard di seguito:

Schizzo