Создание устройства Интернета вещей с поддержкой Интернета с помощью Intel Edison

Кеннет Кристиансен
Kenneth Christiansen

Интернет вещей в наши дни у всех на устах, и он очень волнует таких мастеров и программистов, как я. Нет ничего круче, чем воплотить в жизнь свои собственные изобретения и иметь возможность поговорить с ними!

Но устройства IoT, на которых устанавливаются приложения, которые вы редко используете, могут раздражать, поэтому мы воспользуемся преимуществами новых веб-технологий, таких как Physical Web и Web Bluetooth, чтобы сделать устройства IoT более интуитивно понятными и менее навязчивыми.

Клиентское приложение

Интернет и Интернет вещей — идеальное сочетание

Прежде чем Интернет вещей сможет добиться огромного успеха, еще предстоит преодолеть множество препятствий. Одним из препятствий являются компании и продукты, которые требуют от людей устанавливать приложения на каждое приобретаемое ими устройство, загромождая телефоны пользователей множеством приложений, которыми они редко пользуются.

По этой причине мы очень рады проекту Physical Web , который позволяет устройствам ненавязчиво транслировать URL-адрес онлайн-сайта. В сочетании с новыми веб-технологиями, такими как Web Bluetooth , Web USB и Web NFC , сайты могут подключаться напрямую к устройству или, по крайней мере, объяснять, как это сделать.

Хотя в этой статье мы фокусируемся в первую очередь на веб-Bluetooth, некоторые варианты использования могут лучше подойти для веб-NFC или веб-USB. Например, веб-USB предпочтительнее, если вам требуется физическое соединение по соображениям безопасности.

Веб-сайт также может служить прогрессивным веб-приложением (PWA). Мы рекомендуем читателям ознакомиться с объяснением Google о PWA. PWA — это сайты, которые имеют адаптивный пользовательский интерфейс, похожий на приложения, могут работать в автономном режиме и могут быть добавлены на главный экран устройства.

В качестве доказательства концепции я создал небольшое устройство с использованием коммутационной платы Intel® Edison Arduino. Устройство содержит датчик температуры (TMP36), а также исполнительный механизм (цветной светодиодный катод). Схему этого устройства можно найти в конце статьи.

Макет.

Intel Edison — интересный продукт, поскольку на нем можно использовать полный дистрибутив Linux*. Поэтому я могу легко запрограммировать его с помощью Node.js. Программа установки позволяет установить Intel* XDK, что упрощает начало работы, хотя вы также можете программировать и загружать на свое устройство вручную.

Для моего приложения Node.js мне потребовались три модуля узла, а также их зависимости:

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

Первый автоматически устанавливает noble — модуль узла, который я использую для общения через Bluetooth Low Energy.

Файл package.json проекта выглядит следующим образом:

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

Анонсируем сайт

Начиная с версии 49, Chrome на Android поддерживает физическую сеть, которая позволяет Chrome видеть URL-адреса, транслируемые окружающими его устройствами. Существуют некоторые требования, о которых должен знать разработчик, например, необходимость того, чтобы сайты были общедоступными и использовали HTTPS.

Протокол Eddystone имеет ограничение размера URL-адресов в 18 байт . Итак, чтобы URL-адрес моего демонстрационного приложения работал ( https://webbt-sensor-hub.appspot.com/ ), мне нужно использовать сокращатель URL-адресов.

Транслировать URL-адрес довольно просто. Все, что вам нужно сделать, это импортировать необходимые библиотеки и вызвать несколько функций. Один из способов сделать это — вызвать advertiseUrl при включении чипа BLE:

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

Это действительно не могло быть проще. На изображении ниже вы видите, что Chrome прекрасно находит устройство.

Chrome объявляет о ближайших физических веб-маяках.
URL-адрес веб-приложения указан.

Связь с датчиком/исполнительным устройством

Мы используем Johnny-Five * для обсуждения улучшений нашей доски. У Johnny-Five есть хорошая абстракция для общения с датчиком TMP36.

Ниже вы можете найти простой код для получения уведомлений об изменениях температуры, а также настройки исходного цвета светодиода.

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

Вы можете пока игнорировать указанные выше *Characteristic переменные; они будут определены в следующем разделе, посвященном взаимодействию с Bluetooth.

Как вы могли заметить при создании экземпляра объекта «Термометр», я общаюсь с TMP36 через аналоговый порт A0 . Выводы напряжения на катоде цветного светодиода подключены к цифровым контактам 3, 5 и 6, которые являются контактами широтно-импульсной модуляции (ШИМ) на коммутационной плате Edison Arduino.

доска Эдисона

Разговор по Bluetooth

Разговор с Bluetooth не может быть намного проще, чем с noble .

В следующем примере мы создаем две характеристики Bluetooth Low Energy: одну для светодиода и одну для датчика температуры. Первый позволяет нам считывать текущий цвет светодиода и устанавливать новый цвет. Последнее позволяет нам подписаться на события изменения температуры.

С noble создать характеристику довольно легко. Все, что вам нужно сделать, это определить, как взаимодействует характеристика, и определить UUID. Вариантами связи являются чтение, запись, уведомление или любая их комбинация. Самый простой способ сделать это — создать новый объект и наследовать его от bleno.Characteristic .

Результирующий объект характеристики выглядит следующим образом:

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

Мы сохраняем текущее значение температуры в переменной this._lastValue . Нам нужно добавить метод onReadRequest и закодировать значение, чтобы «чтение» работало.

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

Для «уведомления» нам нужно добавить метод для обработки подписок и отписок. По сути, мы просто сохраняем обратный вызов. Когда у нас есть новая причина температуры, которую мы хотим отправить, мы вызываем этот обратный вызов с новым значением (закодированным, как указано выше).

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

Поскольку значения могут немного колебаться, нам необходимо сгладить значения, которые мы получаем от датчика TMP36. Я решил просто взять среднее значение из 100 образцов и отправлять обновления только тогда, когда температура изменится хотя бы на 1 градус.

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

Это был датчик температуры. Цветной светодиод проще. Объект, а также метод «чтения» показаны ниже. Характеристика настроена на выполнение операций «чтения» и «записи» и имеет другой UUID, чем температурная характеристика.

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

Чтобы управлять светодиодом из объекта, я добавляю элемент this._led , который использую для хранения светодиодного объекта Johnny-Five. Я также установил цвет светодиода на значение по умолчанию (белый, он же #ffffff ).

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

Метод «write» получает строку (точно так же, как «read» отправляет строку), которая может состоять из цветового кода CSS (например: имена CSS, такие как rebeccapurple , или шестнадцатеричные коды, такие как #ff00bb ). Я использую модуль узла под названием parse-color, чтобы всегда получать шестнадцатеричное значение, чего и ожидает 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);
};

Все вышеперечисленное не будет работать, если мы не подключим модуль bleno . eddystone-beacon не будет работать с bleno , если вы не используете версию noble , поставляемую вместе с ним. К счастью, сделать это довольно просто:

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

Теперь все, что нам нужно, это чтобы он рекламировал наше устройство (UUID) и его характеристики (другие UUID)

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

Создание клиентского веб-приложения

Не вдаваясь в подробности о том, как работают части клиентского приложения, не поддерживающие Bluetooth, мы можем в качестве примера продемонстрировать адаптивный пользовательский интерфейс, созданный в Polymer *. Получившееся приложение показано ниже:

Клиентское приложение на телефоне.
Сообщение об ошибке.

Справа показана более ранняя версия, в которой представлен простой журнал ошибок, который я добавил для облегчения разработки.

Web Bluetooth упрощает связь с устройствами Bluetooth Low Energy, поэтому давайте посмотрим на упрощенную версию моего кода подключения. Если вы не знаете, как работают обещания, ознакомьтесь с этим ресурсом, прежде чем читать дальше.

Подключение к устройству Bluetooth предполагает цепочку обещаний. Сначала мы фильтруем устройство (UUID: FC00 , имя: Edison ). При этом отображается диалоговое окно, позволяющее пользователю выбрать устройство с учетом фильтра. Затем мы подключаемся к сервису GATT и получаем основной сервис и связанные с ним характеристики, а затем читаем значения и настраиваем обратные вызовы уведомлений.

Упрощенная версия нашего кода, приведенного ниже, работает только с последней версией API Web Bluetooth и поэтому требует наличия Chrome Dev (M49) на 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.
});

Чтение и запись строки из DataView / ArrayBuffer (то, что использует API WebBluetooth) так же просто, как использование Buffer на стороне Node.js. Все, что нам нужно использовать, это TextEncoder и 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);
},

Обработка события characteristicvaluechanged для датчика температуры также довольно проста:

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

Краткое содержание

Вот и все, ребята! Как видите, связь с Bluetooth Low Energy с использованием Web Bluetooth на стороне клиента и Node.js на Edison довольно проста и очень эффективна.

Используя Физическую сеть и Веб-Bluetooth, Chrome находит устройство и позволяет пользователю легко подключиться к нему, не устанавливая редко используемые приложения, которые могут не понадобиться пользователю и которые могут время от времени обновляться.

Демо

Вы можете попробовать клиент , чтобы получить представление о том, как создавать собственные веб-приложения для подключения к вашим пользовательским устройствам Интернета вещей.

Исходный код

Исходный код доступен здесь . Не стесняйтесь сообщать о проблемах или присылать исправления.

Эскиз

Если вы действительно предприимчивы и хотите воспроизвести то, что я сделал, обратитесь к эскизу Эдисона и макета ниже:

Эскиз