Crea un dispositivo IoT habilitado para la Web con Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Hoy en día, la Internet de las cosas está en boca de todos, y sienta entusiasmo en los pequeños y programadores. No hay nada mejor que dar vida a tus propios inventos y poder hablar con ellos.

Sin embargo, los dispositivos de IoT que instalan apps que se usan con poca frecuencia pueden ser molestos, por lo que aprovechamos las próximas tecnologías web, como la Web física y el Bluetooth web, para que los dispositivos de IoT sean más intuitivos y menos intrusivos.

Aplicación cliente

IoT y la Web, una coincidencia

Todavía quedan muchos obstáculos por superar antes de que la Internet de las cosas pueda ser un gran éxito. Un obstáculo son las empresas y los productos que requieren que las personas instalen apps para cada dispositivo que compren, lo que desordena los teléfonos de los usuarios con una multitud de apps que rara vez usan.

Por este motivo, nos entusiasma mucho el proyecto Web física, que permite que los dispositivos transmitan una URL a un sitio web en línea no invasiva. En combinación con tecnologías web emergentes, como Web Bluetooth, Web USB y Web NFC, los sitios pueden conectarse directamente al dispositivo o, al menos, explicar la forma correcta de hacerlo.

Aunque en este artículo nos enfocamos principalmente en Web Bluetooth, algunos casos de uso podrían ser más adecuados para Web NFC o Web USB. Por ejemplo, se prefiere Web USB si necesitas una conexión física por motivos de seguridad.

El sitio web también puede funcionar como una app web progresiva (AWP). Recomendamos a los lectores que consulten la explicación de Google sobre las AWP. Las AWP son sitios que tienen una experiencia del usuario responsiva similar a la de una app, pueden funcionar sin conexión y se pueden agregar a la pantalla principal del dispositivo.

Como prueba de concepto, he estado compilando un dispositivo pequeño con la placa de desglose Intel® Edison Arduino. El dispositivo contiene un sensor de temperatura (TMP36) y un accionador (cátodo LED de color). El esquema de este dispositivo se puede encontrar al final de este artículo.

Placa de pruebas.

Intel Edison es un producto interesante porque puede ejecutar una distribución completa de Linux*. Por lo tanto, puedo programarlo fácilmente con Node.js. El instalador te permite instalar el Intel* XDK, lo que facilita los primeros pasos, aunque también puedes programarlo y subirlo a tu dispositivo de forma manual.

Para mi app de Node.js, necesitaba tres módulos de nodo, así como sus dependencias:

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

El primero instala noble automáticamente, que es el módulo de nodo que uso para comunicarme por Bluetooth de bajo consumo.

.

El archivo package.json del proyecto tiene el siguiente aspecto:

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

Anuncio del sitio web

A partir de la versión 49, Chrome en Android admite la Web física, que permite a Chrome ver las URLs que transmiten los dispositivos a su alrededor. Hay algunos requisitos que el desarrollador debe tener en cuenta, como la necesidad de que los sitios sean de acceso público y usen HTTPS.

El protocolo Eddystone tiene un límite de tamaño de 18 bytes en las URL. Por lo tanto, para que la URL de mi app de demostración funcione (https://webbt-sensor-hub.appspot.com/), necesito usar un acortador de URL.

Transmitir la URL es bastante simple. Todo lo que necesitas para importar las bibliotecas requeridas y llamar a algunas funciones. Una forma de hacerlo es llamar a advertiseUrl cuando el chip BLE está encendido:

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

No podría ser más fácil. En la imagen de abajo, se puede ver que Chrome encuentra el dispositivo muy bien.

Chrome anuncia balizas cercanas de la Web física.
Aparecerá la URL de la app web.

Comunicación con el sensor/actuador

Usamos Johnny-Five* para hablar con las mejoras de nuestra junta. Johnny-Five tiene una buena abstracción para hablar con el sensor TMP36.

A continuación, encontrarás un código simple para recibir notificaciones sobre cambios de temperatura y configurar el color inicial 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);
});

Por ahora, puedes ignorar las variables *Characteristic anteriores; estas se definirán en la sección posterior sobre cómo interactuar con Bluetooth.

Como puedes observar en la creación de instancias del objeto Termómetro, hablo con el TMP36 a través del puerto analógico A0. Las patas de voltaje del cátodo de LED de color están conectadas a los pines digitales 3, 5 y 6, que son los pines de modulación de ancho de pulso (PWM) en la placa de desglose Edison Arduino.

Junta de Edison

Hablar con Bluetooth

Hablar a través de Bluetooth no podría ser mucho más fácil de lo que es con noble.

En el siguiente ejemplo, creamos dos características de Bluetooth de bajo consumo: una para el LED y otra para el sensor de temperatura. El primero nos permite leer el color actual del LED y establecer un color nuevo. Esto último nos permite suscribirnos a eventos de cambio de temperatura.

Con noble, crear una característica es bastante fácil. Lo único que debes hacer es definir cómo se comunica la característica y definir un UUID. Las opciones de comunicación son lectura, escritura, notificación o cualquier combinación de estas. La manera más fácil de hacerlo es crear un objeto nuevo y heredar contenido de bleno.Characteristic.

El objeto de característica resultante se ve de la siguiente manera:

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

Almacenamos el valor de la temperatura actual en la variable this._lastValue. Debemos agregar un método onReadRequest y codificar el valor para que funcione una "lectura".

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

En el caso de "notify", necesitamos agregar un método para controlar las suscripciones y anularlas. Básicamente, solo almacenamos una devolución de llamada. Cuando tenemos un nuevo motivo de temperatura que queremos enviar, llamamos a esa devolución de llamada con el valor nuevo (codificado como se muestra arriba).

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

Como los valores pueden fluctuar un poco, debemos suavizar los valores que obtenemos del sensor TMP36. Decidí simplemente tomar un promedio de 100 muestras y enviar actualizaciones solo cuando la temperatura cambia en al menos 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);
    }
};

Eso era el sensor de temperatura. El LED de color es más simple. El objeto y el método "read" se muestran a continuación. La característica está configurada para permitir las operaciones de “lectura” y “escritura”, y tiene un UUID diferente a la característica de 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);
};

Para controlar la luz LED desde el objeto, agrego un miembro this._led que uso para almacenar el objeto LED Johnny-Five. También configuro el color del LED con su valor predeterminado (blanco, también conocido como #ffffff).

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

El método "write" recibe una cadena (al igual que "read" envía una cadena), que puede consistir en un código de color CSS (por ejemplo, nombres de CSS como rebeccapurple o códigos hexadecimales como #ff00bb). Uso un módulo de nodo llamado parse-color para obtener siempre el valor hexadecimal, que es lo que Johnny-Five espera.

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

Todo lo anterior no funcionará si no incluimos el módulo bleno. eddystone-beacon no funcionará con bleno, a menos que uses la versión noble que se distribuye con él. Por suerte, es bastante simple:

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

Ahora, solo necesitamos que anuncie nuestro dispositivo (UUID) y sus características (otros UUID)

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

Crea la app web cliente

Sin entrar en demasiadas fallas sobre cómo funcionan las partes no Bluetooth de la app cliente, podemos demostrar una interfaz de usuario responsiva creada en Polymer* como ejemplo. La app resultante se muestra a continuación:

App cliente en el teléfono
Mensaje de error

En el lado derecho, se muestra una versión anterior, que muestra un registro de errores simple que agregué para facilitar el desarrollo.

Web Bluetooth facilita la comunicación con dispositivos con Bluetooth de bajo consumo, así que veamos una versión simplificada de mi código de conexión. Si no sabes cómo funcionan las promesas, consulta este recurso antes de seguir leyendo.

La conexión a un dispositivo Bluetooth implica una cadena de promesas. Primero, filtramos por el dispositivo (UUID: FC00, nombre: Edison). Se mostrará un diálogo para permitir que el usuario seleccione el dispositivo según el filtro. Luego, nos conectamos al servicio GATT y obtenemos el servicio principal y las características asociadas. Luego, leemos los valores y configuramos devoluciones de llamada de notificación.

La versión simplificada de nuestro código que aparece a continuación solo funciona con la API de Web Bluetooth más reciente y, por lo tanto, requiere Chrome Dev (M49) en 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.
});

Leer y escribir una cadena desde un DataView / ArrayBuffer (lo que usa la API de WebBluetooth) es tan fácil como usar Buffer en Node.js. Solo debemos usar TextEncoder y 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);
},

También es bastante fácil controlar el evento characteristicvaluechanged para el sensor de temperatura:

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

Resumen

¡Eso fue todo! Como puedes ver, la comunicación con Bluetooth Low Energía a través de Web Bluetooth del lado del cliente y Node.js en Edison es bastante fácil y muy potente.

A través de la Web física y la Web Bluetooth, Chrome encuentra el dispositivo y permite que el usuario se conecte fácilmente a él sin instalar aplicaciones que el usuario no quiera usar y que podrían actualizarse ocasionalmente.

Demostración

Puedes probar el cliente para obtener inspiración sobre cómo crear tus propias apps web y conectarte a tus dispositivos personalizados de Internet de las cosas.

Código fuente

El código fuente está disponible aquí. Puedes informar problemas o enviar parches.

Sketch

Si eres realmente audaz y quieres reproducir lo que hice, consulta el boceto de Edison y la placa de pruebas a continuación:

Sketch