Tworzenie połączonego z internetem urządzenia IoT z wykorzystaniem procesora Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Obecnie każdy ma dostęp do internetu rzeczy, który sprawia, że majsterkowicze i programiści tacy jak ja są podekscytowani. Nie ma nic fajniejszego niż tworzenie własnych wynalazków i możliwość rozmowy z nimi.

Jednak urządzenia IoT, które instalują rzadko używane aplikacje, bywają irytujące. Dlatego wykorzystujemy nowe technologie internetowe, takie jak internet rzeczy i Bluetooth Bluetooth, by urządzenia IoT były bardziej intuicyjne i mniej uciążliwe.

Aplikacja kliencka

Internet i IoT – dopasowanie

Zanim jednak internet rzeczy odniesie wielki sukces, przed nami jeszcze wiele przeszkód. Poważną przeszkodą są firmy i usługi, które wymagają od użytkowników instalowania aplikacji na każdym kupionym urządzeniu, przez co użytkownicy mają dostęp do mnóstwa aplikacji, których rzadko używają.

Z tego powodu bardzo cieszymy się z projektu internetu rzeczy, który umożliwia urządzeniom przesyłanie adresu URL do witryny internetowej w sposób, który nie zakłóca korzystania z aplikacji. W połączeniu z nowymi technologiami internetowymi, np. Web Bluetooth, Web USB i Web NFC, strony mogą łączyć się bezpośrednio z urządzeniem lub przynajmniej wyjaśnić właściwy sposób działania.

Chociaż w tym artykule skupiamy się głównie na technologii Bluetooth, w niektórych przypadkach użycie tej technologii może być bardziej przydatne. Na przykład Web USB jest preferowany, jeśli ze względów bezpieczeństwa wymagasz fizycznego połączenia.

Witryna może też służyć jako progresywna aplikacja internetowa (PWA). Zachęcamy czytelników do zapoznania się z objaśnieniem Google dotyczącym aplikacji PWA. Progresywne aplikacje internetowe to witryny, które dostosowują się do działania aplikacji i mogą działać w trybie offline oraz można je dodać do ekranu głównego urządzenia.

W ramach modelu koncepcyjnego stworzyłem małe urządzenie z płytką podkładową Intel® Edison Arduino. Urządzenie zawiera czujnik temperatury (TMP36) i aktywator (kolorową katodę LED). Schematy do tego urządzenia znajdziesz na końcu tego artykułu.

Płytka prototypowa.

Intel Edison to interesujący produkt, ponieważ może działać w pełnej dystrybucji Linux* Dzięki temu mogę go łatwo zaprogramować za pomocą Node.js. Instalator umożliwia instalację procesora Intel* XDK, dzięki czemu rozpoczęcie pracy jest bardzo łatwe. Możesz go jednak zaprogramować i przesłać na urządzenie ręcznie.

W przypadku mojej aplikacji Node.js potrzeba 3 modułów węzłów i ich zależności:

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

Pierwszy automatycznie instaluje noble, czyli moduł węzłów, którego używam do rozmów przez Bluetooth Low Energy.

.

Plik package.json projektu wygląda tak:

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

Informowanie o witrynie

Od wersji 49 Chrome na Androidzie obsługuje internet rzeczy, dzięki czemu Chrome może sprawdzać adresy URL przesyłane przez urządzenia znajdujące się w pobliżu. Deweloper musi znać pewne wymagania, np. muszą być publicznie dostępne i korzystać z protokołu HTTPS.

Protokół Eddystone ma limit rozmiaru 18 bajtów adresów URL. Aby adres URL mojej aplikacji demonstracyjnej działał (https://webbt-sensor-hub.appspot.com/), muszę użyć narzędzia do skracania adresów URL.

Rozpowszechnianie adresu URL jest całkiem proste. Wystarczy zaimportować wymagane biblioteki i wywołać kilka funkcji. Możesz to zrobić na przykład przez wywołanie advertiseUrl, gdy element BLE jest włączony:

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

To naprawdę nie może być prostsze. Na zdjęciu poniżej widać, że Chrome dobrze znajduje urządzenie.

Chrome informuje o sygnałach beaconów w pobliżu internetu rzeczy.
Pojawi się adres URL aplikacji internetowej.

Komunikacja z czujnikiem/działem

Używamy Johnny-Five* do obsługi ulepszeń tablicy. Johnny-Five poradzi sobie z rozmawianiem z czujnikiem TMP36.

Poniżej znajduje się prosty kod powiadamiania o zmianach temperatury oraz ustawienia początkowego koloru diody.

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

Na razie możesz zignorować powyższe zmienne *Characteristic. Zostaną one zdefiniowane w dalszej sekcji na temat interakcji z Bluetoothem.

Jak można zauważyć przy tworzeniu instancji obiektu Thermometer, komunikuję się z TMP36 przez analogowy port A0. Nogi napięcia katody kolorowej LED są podłączone do cyfrowych styków 3, 5 i 6, które są stykami PWM na płytce elektronowej Edisona Arduino.

Tablica Edisona

Komunikuję się przez Bluetooth

Komunikacja przez Bluetooth nie może być znacznie łatwiejsza niż w przypadku urządzenia noble.

W tym przykładzie tworzymy 2 cechy Bluetooth Low Energy: jedną dla diody LED i jedną dla czujnika temperatury. Pierwszy pozwala odczytać bieżący kolor diody LED i ustawić nowy. To ostatnie pozwala nam subskrybować zdarzenia dotyczące zmian temperatury.

W przypadku noble utworzenie cechy jest całkiem łatwe. Wystarczy tylko określić sposób, w jaki cecha się komunikuje, i zdefiniować identyfikator UUID. Dostępne opcje to odczyt, zapis, powiadamianie lub dowolna ich kombinacja. Najprostszym sposobem jest utworzenie nowego obiektu i dziedziczenie go z zasobu bleno.Characteristic.

Gotowy obiekt charakterystyczny wygląda tak:

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

Bieżąca wartość temperatury jest przechowywana w zmiennej this._lastValue. Musimy dodać metodę onReadRequest i zakodować wartość, aby funkcja „read” działała.

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

„Powiadom mnie” musisz dodać metodę obsługi subskrypcji i anulowania subskrypcji. Zasadniczo przechowujemy tylko wywołanie zwrotne. Gdy chcemy wysłać nową wartość dotyczącą temperatury, wywołujemy to wywołanie z nową wartością (zakodowaną jak powyżej).

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

Ponieważ wartości mogą się nieco zmieniać, musimy wygładzić wartości pobierane z czujnika TMP36. Zdecydowałam się pobrać średnią ze 100 próbek i wysyłać aktualizacje tylko wtedy, gdy temperatura zmieni się o co najmniej 1 stopień.

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

Chodził o czujnik temperatury. Kolorowa dioda LED jest prostsza. Obiekt oraz metodę „read” są pokazane poniżej. Cecha jest skonfigurowana tak, by zezwalać na operacje „odczytu” i „zapisu” i ma inny identyfikator UUID niż charakter temperatury.

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

Aby sterować diodą LED w obiekcie, dodaję element this._led, którego używam do przechowywania obiektu LED Johnny-Five. Ustawiłam też wartość domyślną diody koloru (biały, czyli #ffffff).

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

Metoda „write” otrzymuje ciąg znaków (tak samo jak „odczyt” wysyła ciąg znaków), który może zawierać kod koloru CSS (np. nazwy CSS, takie jak rebeccapurple lub kody szesnastkowe, np. #ff00bb). Używam modułu węzła o nazwie parse-color, aby zawsze pobierać wartość szesnastkową, która jest zgodna z oczekiwaniami 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);
};

Wszystkie powyższe rozwiązania nie zadziałają, jeśli nie dołączymy modułu bleno. eddystone-beacon nie będzie działać z funkcją bleno, chyba że używasz w niej wersji noble rozprowadzanej razem z nią. Na szczęście jest to dość proste:

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

Teraz wystarczy, że zareklamuje ono nasze urządzenie (UUID) i jego cechy (inne identyfikatory UUID)

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

Tworzenie aplikacji internetowej klienta

Nie wdając się w przesadne debaty o tym, jak działają elementy aplikacji klienckiej, które nie są obsługiwane przez Bluetooth, możemy przykładowo zademonstrować elastyczny interfejs użytkownika utworzony w Polymer*. Aplikacja zostanie wyświetlona poniżej:

Aplikacja klienta na telefonie.
Komunikat o błędzie.

Po prawej znajduje się poprzednia wersja z prostym logiem błędów dodanym przeze mnie w celu ułatwienia programowania.

Web Bluetooth ułatwia komunikację z urządzeniami Bluetooth Low Energy. Spójrzmy na uproszczoną wersję kodu połączenia. Jeśli nie wiesz, jak działają obietnice, zapoznaj się najpierw z tymi materiałami.

Połączenie z urządzeniem Bluetooth wiąże się z kompletem obietnic. Najpierw filtrujemy według urządzenia (UUID: FC00, nazwa: Edison). Pojawia się okno, w którym użytkownik może wybrać urządzenie za pomocą filtra. Następnie łączymy się z usługą GATT, pobieramy usługę główną i powiązane z nią cechy. Następnie czytamy wartości i konfigurujemy wywołania zwrotne powiadomień.

Ta uproszczona wersja kodu działa tylko z najnowszym interfejsem Web Bluetooth API i dlatego wymaga Chrome Dev (M49) na Androidzie.

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

Odczytywanie i zapisywanie ciągu znaków z DataView / ArrayBuffer (którego używa interfejs WebBluetooth API) jest tak samo łatwe jak użycie Buffer po stronie Node.js. Potrzebujemy tylko tych funkcji: TextEncoder i 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);
},

Obsługa zdarzenia characteristicvaluechanged dla czujnika temperatury również jest dość prosta:

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

Podsumowanie

To by było na tyle! Jak widać, komunikowanie się z Bluetooth Low Energy przez Web Bluetooth po stronie klienta i Node.js w Edison jest całkiem łatwe i daje duże możliwości.

Korzystając z internetu rzeczy i Bluetootha, Chrome znajduje urządzenie i umożliwia użytkownikowi łatwe połączenie z nim bez instalowania rzadko używanych aplikacji, które mogą być co jakiś czas aktualizowane.

Pokaz

Możesz wypróbować klienta, aby się zainspirować i dowiedzieć się, jak tworzyć własne aplikacje internetowe, które łączą się z Twoimi urządzeniami korzystającymi z internetu rzeczy.

Kod źródłowy

Kod źródłowy jest dostępny tutaj. Zachęcamy do zgłaszania problemów i wysyłania poprawek.

Szkic

Jeśli lubisz przygody i chcesz odtworzyć to, co udało mi się osiągnąć, zapoznaj się ze szkicem Edisona i schematu:

Szkic