Espruino:Примеры/Рассылка BLE-объявлений при помощи Node.js/Python/C

Материал из Онлайн справочника
Перейти к навигации Перейти к поиску

Перевод: Максим Кузьмин (Cubewriter) Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Рассылка 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-соединения читайте тут:

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

Примечание: manufacturerData – это массив байтов (значений в диапазоне между 0 и 255). Более крупные значения будут обрезаны. Вы также можете задать строку – например, NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:"Hello"});. Если указать слишком много данных (больше 21 байта), это может обернуться исключением DATA_SIZE.

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.

Примечание: Сканирование рассылаемых BLE-данных – очень энергоёмкий процесс, т.к. из-за этого Bluetooth-компоненту Puck.js нужно всегда оставаться включённым. Так что, если у вас быстро расходуется заряд батареи, лучше пользоваться этой функцией как можно реже.

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-устройств»).

Кроме того, по этой ссылке можно найти приложение-пример.

См.также

Ссылки на полезные ресурсы

Внешние ссылки