Espruino:Примеры/Рассылка BLE-объявлений при помощи Node.js/Python/C
Рассылка BLE-объявлений при помощи Node.js/Python/C#/Android[1]
Коммуницировать с Puck.js через BLE можно тремя разными способами:
- Подключитесь к Puck.js с другого устройства и отправьте данные на сервис Nordic UART (или сервис, который вы создали сами) – большинство устройств (ПК, Mac, Linux, Android) одновременно могут быть подключены максимум к 5-6 устройствам.
- Подключите Puck.js к устройству и коммуницируйте с сервисами этого устройства – большинство устройств поддерживают только одно одновременное подключение.
- Рассылайте с помощью Puck.js объявления для устройств, находящихся в пределах BLE-диапазона – это работает только в одном направлении (от Puck.js к прослушивающему устройству), но так вы можете отправлять данные от какого угодно количества Puck.js.
Более подробно о том, что всё это значит, можно прочесть тут, а здесь мы сосредоточимся на способе с рассылкой BLE-объявлений.
Советы по использованию Bluetooth-соединения читайте тут:
- Подключение через другое Espruino-устройство.
- Подключение через веб-страницу с Web Bluetooth.
- Подключение через Node.js или Python.
Чтобы преобразовать данные объявления в MQTT, можно воспользоваться программой EspruinoHub (причём её также можно использовать вместе с Node-RED), но в этом руководстве мы сосредоточимся на написании собственного приложения-хоста.
Сначала нам нужно разослать BLE-объявления с данными. Это можно сделать при помощи метода NRF.setAdvertising().
Есть два главных типа рассылаемых данных:
- Сервисы – У каждого сервиса есть свой UUID. Он может быть 16-битным или 128-битным. 16-битные UUID присваиваются Bluetooth SIG, так что будьте внимательны и используйте правильные UUID. Вы можете задать собственные 128-битные UUID, но они, в-первых, должны быть случайными, и во-вторых, BLE-объявление очень невелико, так что после использования 128-битного UUID у вас останется мало свободного места. Например, можно воспользоваться UUID сервиса «0xFFFF» (для серийных устройств его использовать не стоит) с помощью команды NRF.setAdvertising({0xFFFF:"Hello"});.
- Данные производителя – Прошивка Espruino/Puck.js 1v95 и новее позволяет задать данные производителя. Это почти то же самое, как использовать сервисы с 16-битными UUID, однако для Espruino/Puck.js зарезервирован собственный 16-битный UUID (0x0590), который можно использовать для чего угодно.
Ниже мы воспользуемся данными производителя. Подключитесь к Puck.js с помощью IDE и загрузите на него следующий код:
var presses = 0;
NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:[presses]});
setWatch(function() {
presses++;
NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:[presses]});
}, BTN, {edge:"rising", repeat:1, debounce:20})
Этот код рассылает в BLE-объявлениях один байт («0»), но с каждым нажатием на кнопку это значение будет увеличиваться.
Теперь вам надо отключить онлайн-IDE от Puck.js, потому что Puck.js рассылает данные, только когда к нему не подключено никаких устройств.
Puck.js/Espruino
Кроме того, Puck.js умеет прослушивать BLE-объявления других устройств при помощи NRF.setScan() (для непрерывного прослушивания) или NRF.findDevices() (для прослушивания в пределах заданного периода времени и агрегации всех ответов). К примеру, вы можете запустить на другом Puck.js вот такой код...
NRF.findDevices(print)
…и в итоге получите примерно следующее:
[
BluetoothDevice {
"id": "de:70:d9:0c:eb:86 random",
"rssi": -44,
"data": new Uint8Array([2, 1, 5, 4, 255, 144, 5, 11, 20, 9, 69, 115, 112, 114, 117, 105, 110, 111, 32, 78, 82, 70, 53, 50, 56, 51, 50, 68, 75]).buffer,
"manufacturer": 1424,
"manufacturerData": new Uint8Array([0]).buffer,
"name": "Puck.js eb86"
}
]
Как и все прочие устройстве в пределах BLE-диапазона. После этого можно выполнить поиск manufacturer==0x0590 и извлечь передаваемые данные, прочитав поле manufacturerData.
Node.js
В Node.js есть отличный модуль под названием Noble, работающий на Windows, Mac OS и Linux.
- В данный момент в Mac OS Mojave в Noble сломана поддержка BLE, так что лучше воспользоваться вместо него библиотекой noble-mac.
- Пользователи Windows 10 вместо Noble могут воспользоваться noble-uwp – там поддержка Bluetooth есть по умолчанию.
- При использовании Windows вместе с USB-адаптером с BLE-функционалом не нужно устанавливать драйвер, идущий в комплекте с этим адаптером. Это значит, что вам необязательно иметь Windows 10, но если у вас есть Windows 10 и нет поддержки BLE, то вам нужно будет дополнительно подключить USB-адаптер с BLE-функционалом.
- Убедитесь, что у вас установлен Node.js.
- Затем установите noble при помощи npm.
# Устанавливаем Noble, чтобы получить поддержку BLE для Node.js:
npm install noble
# ТОЛЬКО ДЛЯ LINUX
# Разрешаем Node.js получить доступ к BLE
# даже без запуска при помощи «sudo»:
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`))
Теперь добавляем в advertising_nodejs.js код ниже:
var noble = require('noble');
function onDiscovery(peripheral) {
// peripheral.rssi - мощность сигнала
// peripheral.address - MAC-адрес
// peripheral.advertisement.localName - название устройства
// peripheral.advertisement.manufacturerData - данные
- производителя
// peripheral.advertisement.serviceData - обычные
- данные сервиса,
- рассылаемые
- в объявлении
// Игнорируем устройства без данных производителя:
if (!peripheral.advertisement.manufacturerData) return;
// Печатаем в консоли всё, что прочли:
console.log(
peripheral.address,
JSON.stringify(peripheral.advertisement.localName),
JSON.stringify(peripheral.advertisement.manufacturerData)
);
}
noble.on('stateChange', function(state) {
if (state!="poweredOn") return;
console.log("Начинаем сканирование...");
noble.startScanning([], true);
});
noble.on('discover', onDiscovery);
noble.on('scanStart', function() { console.log("Сканирование началось."); });
noble.on('scanStop', function() { console.log("Сканирование завершилось.");})
- И запускаем его при помощи node advertising_nodejs.js
У вас должно получиться примерно следующее:
Начинаем сканирование...
Сканирование началось.
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,0]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,0]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,0]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,0]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,1]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,1]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,2]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,2]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,3]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,4]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,5]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,5]}
de:70:d9:0c:eb:86 "Puck.js eb86" {"type":"Buffer","data":[144,5,5]}
Вы можете прочесть строчки и от других устройств, у которых есть данные производителя. Как видите, третий элемент в буфере увеличивается при каждом нажатии на кнопку Puck.js.
Это можно улучшить:
- Чтобы нам сообщали обо всех устройствах с данными производителя.
- Чтобы нам всегда присылали обратно рассылаемые данные – даже если они не изменились.
Теперь попробуем новый код. Обязательно скопируйте адрес Puck.js из кода выше в константу devices:
var noble = require('noble');
// Список разрешённых устройств:
const devices = [
"de:70:d9:0c:eb:86"
];
// Рассылаемые BLE-данные, полученные в прошлый раз:
var lastAdvertising = {
};
function onDeviceChanged(addr, data) {
console.log("Device ",addr,"changed data",JSON.stringify(data));
}
function onDiscovery(peripheral) {
// Мы знаем это устройство?
if (devices.indexOf(peripheral.address)<0) return;
// У него есть данные производителя с UUID Espruino/Puck.js's?
if (!peripheral.advertisement.manufacturerData ||
peripheral.advertisement.manufacturerData[0]!=0x90 ||
peripheral.advertisement.manufacturerData[1]!=0x05) return;
// Считываем только наши данные:
var data = peripheral.advertisement.manufacturerData.slice(2);
// Проверяем на предмет изменившихся сервисов:
if (lastAdvertising[peripheral.address] != data.toString())
onDeviceChanged(peripheral.address, data);
lastAdvertising[peripheral.address] = data;
}
noble.on('stateChange', function(state) {
if (state!="poweredOn") return;
console.log("Начинаем сканирование...");
noble.startScanning([], true);
});
noble.on('discover', onDiscovery);
noble.on('scanStart', function() { console.log("Сканирование началось."); });
noble.on('scanStop', function() { console.log("Сканирование завершилось.");});
В итоге получаем:
Начинаем сканирование...
Сканирование началось.
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[5]}
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[6]}
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[7]}
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[8]}
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[9]}
Device de:70:d9:0c:eb:86 changed data {"type":"Buffer","data":[10]}
Теперь вы можете написать собственный обработчик для onDeviceChanged, который делал бы то, что вам нужно, когда что-то происходит на Puck.js.
Python
Вы можете сделать то же самое на Linux (включая Raspberry Pi) при помощи Python и библиотеки bluepy. На данный момент на Windows и Mac OS библиотека bluepy не поддерживается.
- Python почти всегда установлен на Linux.
- Сначала устанавливаем библиотеку bluepy:
sudo apt-get install python-pip libglib2.0-dev
sudo pip install bluepy
Затем используем код ниже – обработка рассылаемых данных почти такая же, как и на Node.js, за исключением того, что в этом случае тип сервиса (ffff) находится в той же переменной value, что и сами данные.
- Добавляем в advertising_python.py код ниже:
from bluepy.btle import Scanner, DefaultDelegate
def onDeviceChanged(addr, data):
print "Device %s, value %s" % (addr,data)
# Устройства, которые мы ищем:
devices = [
"de:70:d9:0c:eb:86"
];
# Данные, которые были получены в прошлый раз:
lastAdvertising = {}
# Считываем данные сканирования:
class ScanDelegate(DefaultDelegate):
def __init__(self):
DefaultDelegate.__init__(self)
def handleDiscovery(self, dev, isNewDev, isNewData):
if not dev.addr in devices: return
for (adtype, desc, value) in dev.getScanData():
if adtype==255 and value[:4]=="9005": # Данные производителя
data = value[4:]
if not dev.addr in lastAdvertising or lastAdvertising[dev.addr] != data:
onDeviceChanged(dev.addr, data)
lastAdvertising[dev.addr] = data
# Начинаем сканирование:
scanner = Scanner().withDelegate(ScanDelegate())
scanner.clear()
scanner.start()
# Продолжаем сканировать 10-секундными отрезками:
while True: scanner.process(10)
# Если хотим закончить, нужно вызвать stop():
scanner.stop()
- Затем запускаем Python-файл. Чтобы получить доступ к Bluetooth-устройству, этот файл нужно запускать с правами администратора.
sudo python advertising_python.py
И вы получите примерно следующее:
Device de:70:d9:0c:eb:86, value 35
Device de:70:d9:0c:eb:86, value 37
Device de:70:d9:0c:eb:86, value 39
Device de:70:d9:0c:eb:86, value 3a
Device de:70:d9:0c:eb:86, value 3b
Device de:70:d9:0c:eb:86, value 3c
Device de:70:d9:0c:eb:86, value 3d
Обратите внимание, что data в onDeviceChanged – это шестнадцатеричная строка. Чтобы преобразовать её в целое число, воспользуйтесь int(data, 16).
Универсальная платформа Windows (UWP) – C#/JS/VB
Пример для Node.js выше будет работать и на Windows, но вы, возможно, захотите использовать какой-то другой язык программирования. Поддержка BLE есть только в Windows 10, так что ваше приложение будет ограничено только Windows 10 и новее.
В документации Microsoft есть пример кода для отслеживания Bluetooth-объявлений при помощи класса BluetoothLEAdvertisementWatcher – посмотреть его можно тут.
Кроме того, есть примеры, полностью написанные на C#, JavaScript и Visual Basic.
Android
Для Android есть хорошая документация по BLE – посмотрите раздел Find BLE Devices («Поиск BLE-устройств»).
Кроме того, по этой ссылке можно найти приложение-пример.
См.также
Внешние ссылки