Een IoT-apparaat met internettoegang creëren met Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Het Internet of Things ligt tegenwoordig op ieders lippen en het maakt knutselaars en programmeurs zoals ik erg enthousiast. Niets is cooler dan je eigen uitvindingen tot leven brengen en ermee kunnen praten!

Maar IoT-apparaten die apps installeren die u zelden gebruikt, kunnen vervelend zijn. Daarom maken we gebruik van opkomende webtechnologieën zoals het Physical Web en Web Bluetooth om IoT-apparaten intuïtiever en minder opdringerig te maken.

Klanttoepassing

Web en IoT, een match to be

Er moeten nog veel hindernissen worden overwonnen voordat Internet of Things een groot succes kan worden. Eén obstakel zijn bedrijven en producten die van mensen eisen dat ze apps installeren voor elk apparaat dat ze kopen, waardoor de telefoons van gebruikers vol raken met een groot aantal apps die ze zelden gebruiken.

Om deze reden zijn we erg enthousiast over het Physical Web- project, waarmee apparaten op een niet-opdringerige manier een URL naar een online website kunnen uitzenden. In combinatie met opkomende webtechnologieën zoals Web Bluetooth , Web USB en Web NFC kunnen de sites rechtstreeks verbinding maken met het apparaat of op zijn minst uitleggen hoe dit moet.

Hoewel we ons in dit artikel vooral richten op Web Bluetooth, zijn sommige gebruiksscenario's mogelijk beter geschikt voor Web NFC of Web USB. Web-USB heeft bijvoorbeeld de voorkeur als u om veiligheidsredenen een fysieke verbinding nodig heeft.

De website kan ook dienen als Progressive Web App (PWA). We moedigen lezers aan om de uitleg van Google over PWA's te bekijken. PWA's zijn sites die een responsieve, app-achtige gebruikerservaring hebben, offline kunnen werken en kunnen worden toegevoegd aan het startscherm van het apparaat.

Als proof of concept heb ik een klein apparaat gebouwd met behulp van het Intel® Edison Arduino breakout-bord. Het apparaat bevat een temperatuursensor (TMP36) en een actuator (gekleurde LED-kathode). De schema's voor dit apparaat vindt u aan het einde van dit artikel.

Broodplank.

De Intel Edison is een interessant product omdat deze een volledige Linux*-distributie kan draaien. Daarom kan ik het eenvoudig programmeren met Node.js. Met het installatieprogramma kunt u Intel* XDK installeren, zodat u gemakkelijk aan de slag kunt, maar u kunt ook handmatig programmeren en naar uw apparaat uploaden.

Voor mijn Node.js-app had ik drie knooppuntmodules nodig, evenals hun afhankelijkheden:

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

De eerste installeert automatisch noble , de knooppuntmodule die ik gebruik om te praten via Bluetooth Low Energy.

Het package.json -bestand voor het project ziet er als volgt uit:

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

Aankondiging van de website

Vanaf versie 49 ondersteunt Chrome op Android Physical Web, waardoor Chrome URL's kan zien die worden uitgezonden door apparaten eromheen. Er zijn enkele vereisten waar de ontwikkelaar rekening mee moet houden, zoals de noodzaak dat de sites openbaar toegankelijk moeten zijn en HTTPS moeten gebruiken.

Het Eddystone-protocol heeft een limiet van 18 bytes voor URL's. Dus om de URL voor mijn demo-app te laten werken ( https://webbt-sensor-hub.appspot.com/ ), moet ik een URL-verkorter gebruiken.

Het uitzenden van de URL is vrij eenvoudig. Het enige dat u hoeft te doen, is de vereiste bibliotheken importeren en een paar functies aanroepen. Eén manier om dit te doen is door advertiseUrl aan te roepen wanneer de BLE-chip is ingeschakeld:

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

Dat kan echt niet eenvoudiger. Je ziet in onderstaande afbeelding dat Chrome het toestel aardig vindt.

Chrome kondigt fysieke webbakens in de buurt aan.
De web-app-URL wordt vermeld.

Communiceren met de sensor/actuator

We gebruiken Johnny-Five * om met onze bordverbeteringen te praten. Johnny-Five heeft een mooie abstractie voor het praten met de TMP36-sensor.

Hieronder vindt u de eenvoudige code waarmee u op de hoogte wordt gesteld van temperatuurveranderingen en waarmee u de initiële LED-kleur kunt instellen.

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

U kunt de bovenstaande *Characteristic variabelen voorlopig negeren; deze zullen worden gedefinieerd in het latere gedeelte over de interface met Bluetooth.

Zoals je misschien opmerkt bij het instantiëren van het Thermometer-object, praat ik met de TMP36 via de analoge A0 -poort. De spanningspoten op de kleuren-LED-kathode zijn verbonden met digitale pinnen 3, 5 en 6, die toevallig de pulsbreedtemodulatie (PWM) pinnen op het Edison Arduino breakout-bord zijn.

Edison-bord

Praten met Bluetooth

Praten met Bluetooth kan niet veel eenvoudiger dan met noble .

In het volgende voorbeeld creëren we twee Bluetooth Low Energy-kenmerken: één voor de LED en één voor de temperatuursensor. Met de eerste kunnen we de huidige LED-kleur lezen en een nieuwe kleur instellen. Met dit laatste kunnen we ons abonneren op temperatuurveranderingsgebeurtenissen.

Met noble is het creëren van een kenmerk vrij eenvoudig. Het enige dat u hoeft te doen, is definiëren hoe het kenmerk communiceert en een UUID definiëren. De communicatieopties zijn lezen, schrijven, informeren of een combinatie daarvan. De eenvoudigste manier om dit te doen is door een nieuw object te maken en dit over te nemen van bleno.Characteristic .

Het resulterende karakteristieke object ziet er als volgt uit:

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

We slaan de huidige temperatuurwaarde op in de variabele this._lastValue . We moeten een onReadRequest methode toevoegen en de waarde coderen om "read" te laten werken.

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

Voor "notificeren" moeten we een methode toevoegen om abonnementen en uitschrijvingen af ​​te handelen. In principe slaan wij gewoon een terugbelverzoek op. Wanneer we een nieuwe temperatuurreden hebben die we willen verzenden, bellen we die callback met de nieuwe waarde (gecodeerd zoals hierboven).

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

Omdat waarden een beetje kunnen fluctueren, moeten we de waarden die we van de TMP36-sensor krijgen, gladstrijken. Ik heb ervoor gekozen om simpelweg het gemiddelde van 100 monsters te nemen en pas updates te sturen als de temperatuur met minimaal 1 graad verandert.

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

Dat was de temperatuursensor. De kleuren-LED is eenvoudiger. Het object en de "lees" -methode worden hieronder weergegeven. De karakteristiek is geconfigureerd om "lees"- en "schrijf"-bewerkingen mogelijk te maken en heeft een andere UUID dan de temperatuurkarakteristiek.

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

Om de LED vanaf het object te besturen, voeg ik een this._led lid toe dat ik gebruik om het Johnny-Five LED-object op te slaan. Ik heb ook de kleur van de LED ingesteld op de standaardwaarde (wit, ook bekend als #ffffff ).

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

De "write"-methode ontvangt een string (net zoals "read" een string verzendt), die kan bestaan ​​uit een CSS-kleurcode (bijvoorbeeld: CSS-namen zoals rebeccapurple of hexadecimale codes zoals #ff00bb ). Ik gebruik een knooppuntmodule genaamd parse-color om altijd de hexadecimale waarde te krijgen, wat Johnny-Five verwacht.

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

Al het bovenstaande zal niet werken als we de bleno- module niet opnemen. eddystone-beacon werkt niet met bleno , tenzij je de noble versie gebruikt die ermee wordt gedistribueerd. Gelukkig is dat vrij eenvoudig:

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

Het enige dat we nu nodig hebben, is dat het reclame maakt voor ons apparaat (UUID) en de kenmerken ervan (andere UUID's)

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

De clientweb-app maken

Zonder al te veel fouten te maken over hoe de niet-Bluetooth-onderdelen van de client-app werken, kunnen we als voorbeeld een responsieve gebruikersinterface demonstreren die in Polymer * is gemaakt. De resulterende app wordt hieronder weergegeven:

Client-app op telefoon.
Foutmelding.

De rechterkant toont een eerdere versie, waarin een eenvoudig foutenlogboek wordt getoond dat ik heb toegevoegd om de ontwikkeling te vergemakkelijken.

Web Bluetooth maakt het gemakkelijk om te communiceren met Bluetooth Low Energy-apparaten, dus laten we eens kijken naar een vereenvoudigde versie van mijn verbindingscode. Als je niet weet hoe beloften werken, bekijk dan deze informatiebron voordat je verder leest.

Verbinding maken met een Bluetooth-apparaat brengt een reeks beloften met zich mee. Eerst filteren we op het apparaat (UUID: FC00 , naam: Edison ). Hierdoor wordt een dialoogvenster weergegeven waarmee de gebruiker het apparaat kan selecteren op basis van het filter. Vervolgens maken we verbinding met de GATT-service en krijgen we de primaire service en bijbehorende kenmerken, en vervolgens lezen we de waarden en stellen we callbacks voor meldingen in.

De vereenvoudigde versie van onze onderstaande code werkt alleen met de nieuwste Web Bluetooth API en vereist daarom Chrome Dev (M49) op 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.
});

Het lezen en schrijven van een string vanuit een DataView / ArrayBuffer (wat de WebBluetooth API gebruikt) is net zo eenvoudig als het gebruik van Buffer aan de Node.js-kant. Het enige dat we nodig hebben is TextEncoder en 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);
},

Het afhandelen van de characteristicvaluechanged gebeurtenis voor de temperatuursensor is ook vrij eenvoudig:

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

Samenvatting

Dat was het mensen! Zoals je kunt zien, is communiceren met Bluetooth Low Energy met behulp van Web Bluetooth aan de clientzijde en Node.js op de Edison vrij eenvoudig en zeer krachtig.

Met behulp van het fysieke web en web Bluetooth vindt Chrome het apparaat en kan de gebruiker er eenvoudig verbinding mee maken zonder zelden gebruikte applicaties te installeren die de gebruiker misschien niet wil en die van tijd tot tijd kunnen worden bijgewerkt.

Demo

U kunt de client uitproberen om inspiratie op te doen over hoe u uw eigen webapps kunt maken om verbinding te maken met uw aangepaste Internet of Things-apparaten.

Broncode

De broncode is hier beschikbaar. Voel je vrij om problemen te melden of patches te sturen.

Schetsen

Als je echt avontuurlijk bent en wilt reproduceren wat ik heb gedaan, raadpleeg dan de Edison- en breadboard-schets hieronder:

Schetsen