Como criar um dispositivo de IoT ativado para a Web com o Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Atualmente, a Internet das Coisas está na boca de todos e deixa desenvolvedores e programadores como eu muito animados. Nada é mais legal do que trazer suas próprias invenções à vida e poder falar com elas.

No entanto, os dispositivos de IoT que instalam apps que você raramente usa podem ser irritantes. Por isso, aproveitamos as novas tecnologias da Web, como a Web física e o Bluetooth da Web, para tornar os dispositivos de IoT mais intuitivos e menos invasivos.

Aplicativo cliente

Web e IoT, uma combinação

Ainda há muitos obstáculos a serem superados antes que a Internet das Coisas seja um grande sucesso. Um obstáculo são as empresas e os produtos que exigem que as pessoas instalem apps em cada dispositivo comprado, deixando os smartphones dos usuários com uma infinidade de apps que eles raramente usam.

Por isso, estamos muito entusiasmados com o projeto da Web física, que permite que os dispositivos transmitam um URL para um site on-line de forma não invasiva. Em combinações com tecnologias da Web emergentes, como Web Bluetooth, Web USB e Web NFC, os sites podem se conectar diretamente ao dispositivo ou, pelo menos, explicar a maneira correta de fazer isso.

Embora o foco deste artigo seja o Web Bluetooth, alguns casos de uso podem ser mais adequados para Web NFC ou Web USB. Por exemplo, o USB da Web será preferível se você precisar de uma conexão física por motivos de segurança.

O site também pode funcionar como um Progressive Web App (PWA). Incentivamos os leitores a conferir a explicação do Google sobre PWAs. PWAs são sites que têm uma experiência do usuário responsiva, semelhante a apps, podem funcionar off-line e podem ser adicionados à tela inicial do dispositivo.

Como prova de conceito, estou criando um dispositivo pequeno usando a placa de fuga Intel® Edison Arduino. O dispositivo contém um sensor de temperatura (TMP36), bem como um atuador (cátodo de LED colorido). Os esquemas desse dispositivo podem ser encontrados no final deste artigo.

Placa de ensaio.

O Intel Edison é um produto interessante porque pode executar uma distribuição completa do Linux*. Portanto, posso programá-lo facilmente usando o Node.js. O instalador permite instalar o Intel* XDK, que facilita o início, embora você também possa programar e fazer upload para o dispositivo manualmente.

Para meu aplicativo Node.js, eu exigisse três módulos de nó, além das dependências deles:

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

O primeiro instala automaticamente noble, que é o módulo de nó usado para me comunicar por Bluetooth Low Energy.

O arquivo package.json do projeto tem esta aparência:

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

Anúncio do site

A partir da versão 49, o Chrome no Android oferece suporte à Web física, o que permite que o Chrome veja os URLs transmitidos por dispositivos ao redor. Há alguns requisitos que o desenvolvedor precisa conhecer, como a necessidade de os sites estarem acessíveis publicamente e usar HTTPS.

O protocolo Eddystone tem um limite de tamanho de 18 bytes em URLs. Portanto, para que o URL do meu app de demonstração funcione (https://webbt-sensor-hub.appspot.com/), preciso usar um encurtador de URL.

A transmissão do URL é bem simples. Você só precisa importar as bibliotecas necessárias e chamar algumas funções. Uma maneira de fazer isso é chamar advertiseUrl quando o ícone BLE estiver ativado:

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

Isso não poderia ser mais fácil. Veja na imagem abaixo que o Chrome acha o dispositivo muito bem.

O Chrome anuncia beacons da Web física nas proximidades.
O URL do app da Web é listado.

Comunicar-se com o sensor/atuador

Usamos a Johnny-Five* para conversar sobre as melhorias da diretoria. O Johnny-Five tem uma boa abstração para se comunicar com o sensor TMP36.

Confira abaixo o código simples para receber notificações sobre mudanças de temperatura e para definir a cor inicial do 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);
});

Você pode ignorar as variáveis *Characteristic acima por enquanto. Elas serão definidas na próxima seção sobre a interface com o Bluetooth.

Como você pode notar na instanciação do objeto Thermometer, eu falo com o TMP36 pela porta A0 analógica. Os segmentos de tensão no cátodo de LED de cor estão conectados aos pinos digitais 3, 5 e 6, que são os pinos de modulação por largura de pulso (PWM, na sigla em inglês) na placa de fuga do Edison Arduino.

Placa Edison

Falando com Bluetooth

Falar com o Bluetooth é muito mais fácil do que é com o noble.

No exemplo abaixo, criamos duas características do Bluetooth de baixa energia: uma para o LED e outra para o sensor de temperatura. O primeiro permite ler a cor do LED atual e definir uma nova cor. Já o segundo permite a inscrição em eventos de mudança de temperatura.

Com o noble, é bem fácil criar uma característica. Tudo o que você precisa fazer é definir como a característica se comunica e definir um UUID. As opções de comunicação são ler, gravar, notificar ou qualquer combinação delas. A maneira mais fácil de fazer isso é criar um novo objeto e herdar de bleno.Characteristic.

O objeto de característica resultante é semelhante ao seguinte:

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

Armazenamos o valor atual da temperatura na variável this._lastValue. Precisamos adicionar um método onReadRequest e codificar o valor para que uma "leitura" funcione.

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

Para "notificação", precisamos adicionar um método para processar assinaturas e cancelamentos de assinatura. Basicamente, armazenamos um callback. Quando temos um novo motivo de temperatura que queremos enviar, chamamos esse callback com o novo valor (codificado como acima).

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 os valores podem variar um pouco, precisamos suavizar os valores recebidos do sensor TMP36. Optei por simplesmente pegar a média de 100 amostras e enviar atualizações apenas quando a temperatura mudar em pelo menos 1 grau.

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

Esse era o sensor de temperatura. O LED colorido é mais simples. O objeto e o método "read" são mostrados abaixo. A característica é configurada para permitir operações de "leitura" e "gravação" e tem um UUID diferente da 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 o LED do objeto, adiciono um membro this._led que uso para armazenar o objeto de LED Johnny-Five. Também defino a cor do LED para o valor padrão (branco, também conhecido como #ffffff).

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

O método "write" recebe uma string (assim como "read" envia uma string), que pode consistir em um código de cor CSS. Por exemplo, nomes CSS, como rebeccapurple ou códigos hexadecimais, como #ff00bb. Eu uso um módulo de nó chamado parse-color para sempre conseguir o valor hexadecimal, que é o 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);
};

Todas as opções acima não funcionarão se não incluirmos o módulo bleno. eddystone-beacon não funciona com o bleno, a menos que você use a versão noble distribuída com ele. Felizmente, é bem simples fazer isso:

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

Agora, tudo que precisamos é que ela anuncie nosso dispositivo (UUID) e as características dele (outros UUIDs)

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

Como criar o app da Web do cliente

Sem entrar em muitos erros sobre o funcionamento das partes não Bluetooth do app cliente, podemos demonstrar uma interface do usuário responsiva criada no Polymer* como exemplo. Confira abaixo o app resultante:

App cliente no smartphone.
Mensagem de erro.

O lado direito mostra uma versão anterior, que apresenta um registro de erros simples que adicionei para facilitar o desenvolvimento.

O Web Bluetooth facilita a comunicação com dispositivos Bluetooth de baixa energia, então vamos analisar uma versão simplificada do meu código de conexão. Se você não sabe como as promessas funcionam, confira este recurso antes de ler mais.

A conexão com um dispositivo Bluetooth envolve uma cadeia de promessas. Primeiro, filtramos o dispositivo (UUID: FC00, nome: Edison). Isso mostra uma caixa de diálogo para permitir que o usuário selecione o dispositivo de acordo com o filtro. Em seguida, nos conectamos ao serviço GATT, recebemos o serviço principal e as características associadas e, em seguida, lemos os valores e configuramos callbacks de notificação.

A versão simplificada do nosso código abaixo só funciona com a API Web Bluetooth mais recente e, portanto, exige o Chrome Dev (M49) no 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.
});

Ler e gravar uma string de DataView / ArrayBuffer (o que a API WebBluetooth usa) é tão fácil quanto usar Buffer no Node.js. Tudo o que precisamos usar é 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);
},

Processar o evento characteristicvaluechanged do sensor de temperatura também é bem fácil:

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

Resumo

Foi isso, pessoal! Como você pode notar, a comunicação com o Bluetooth Low Energy usando o Web Bluetooth no lado do cliente e o Node.js no Edison é muito fácil e muito eficiente.

Usando a Web física e o Bluetooth da Web, o Chrome encontra o dispositivo e permite que o usuário se conecte a ele facilmente, sem instalar aplicativos pouco usados que o usuário pode não querer e que podem ser atualizados de tempos em tempos.

Demonstração

Peça ao cliente para se inspirar sobre como criar seus próprios apps da Web para se conectar aos seus dispositivos personalizados com a Internet das Coisas.

Código-fonte

O código-fonte está disponível aqui. Sinta-se à vontade para informar problemas ou enviar patches.

Sketch

Se você é realmente aventureiro e quer reproduzir o que eu fiz, consulte o esboço do Edison e da placa de ensaio abaixo:

Sketch