Интернет вещей в наши дни у всех на устах, и он очень волнует таких мастеров и программистов, как я. Нет ничего круче, чем воплотить в жизнь свои собственные изобретения и иметь возможность поговорить с ними!
Но устройства 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 прекрасно находит устройство.
Связь с датчиком/исполнительным устройством
Мы используем 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 находит устройство и позволяет пользователю легко подключиться к нему, не устанавливая редко используемые приложения, которые могут быть не нужны пользователю и которые могут время от времени обновляться.
Демо
Вы можете попробовать клиент , чтобы получить представление о том, как создавать собственные веб-приложения для подключения к вашим пользовательским устройствам Интернета вещей.
Исходный код
Исходный код доступен здесь . Не стесняйтесь сообщать о проблемах или присылать исправления.
Эскиз
Если вы действительно предприимчивы и хотите воспроизвести то, что я сделал, обратитесь к эскизу Эдисона и макета ниже: