Создание устройства Интернета вещей с поддержкой Интернета с помощью 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 находит устройство и позволяет пользователю легко подключиться к нему, не устанавливая редко используемые приложения, которые могут быть не нужны пользователю и которые могут время от времени обновляться.

Демо

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

Исходный код

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

Эскиз

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

Эскиз