Nefry BT(ESP32)からBLEでNode.jsにデータを送ってみよう
こんにちは、代表ののびすけ(@n0bisuke)です。Nefry BTを使ってBluetooth / BLEを利用する方法を紹介します。
ほぼESP32のコードなので、検証してませんが他のESP32系のボードでも動作すると思います。
BLEについて
BLEには大きく分けPeripheral (ペリフェラル)とCentral(セントラル)という二つの役割があります。
- Peripheral: 発信側端末、ビーコンやBLEタグなど受信端末に対して情報を送る側
- Central: 受信側端末、iPhoneやMacなどBLEデバイスの情報を探して受け取る側
Nefry BTは書き込むコードによって、PeripheralにもCentralにもなることができます。
環境
- Arduino IDE 1.8.5
- Nefry ライブラリ 1.1.4
- Nefry BT R2
- macOS High Sierra
- Node.js v9.2.0
Peripheralの作成
情報発信側のPeripheralをNefry BTで作成します。
UUIDの作成
BLEを利用するためにはSERVICE(サービス)とCHARACTERISTIC(キャラクタリスティック)という各機能を司るUUIDを設定する必要があります。プログラミングでいうクラスとメソッドの関係だと思うと良いかもしれません。
BLEデバイスは部屋の中やカフェ、駅などいたるところに存在するのでそれらのデバイスと自分が所持しているデバイスのIDが競合しないようにユニークな値にする必要があります。そこでUUIDを作成し、設定する必要があります。
https://www.uuidgenerator.net/
このサイトでUUIDが作成出きるので各自作成しましょう。
Nefry BTのスケッチ
大元のコードはnkolban氏のこちらのコードです。
#define SERVICE_UUID
の箇所と#define CHARACTERISTIC_UUID
の箇所に先ほど作成したUUIDを指定します。
またBLEDevice::init("");
の箇所にBLEデバイスの名前を設定できます。
以下のコードではNefryBT-n0bisukeという名前を指定しています。
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
uint8_t value = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "D5875408-FA51-4763-A75D-7D33CECEBC31"
#define CHARACTERISTIC_UUID "A4F01D8C-A037-43B6-9050-1876A8C23584"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("NefryBT-n0bisuke");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
Serial.printf("*** NOTIFY: %d ***\n", value);
char buffer[10];
sprintf(buffer, "{\"val\":%d}", value);
Serial.printf(buffer);
pCharacteristic->setValue(buffer);
pCharacteristic->notify();
//pCharacteristic->indicate();
value++;
}
delay(2000);
}
コンパイルエラーが出ないで書き込みが出来ればOKです。
確認
デバッグにはLightBlueなどのBLEデバッグ用のアプリケーションを利用することをお勧めします。
Nefry BTが起動すると先ほど指定したNefryBT-n0bisukeという名前でBLEデバイスが検出されます。
SERVICEのUUIDやCHARACTERISTICのUUIDも先ほど指定したものが表示されていると思います。
これで問題なく、Nefry BTから情報が発信されていることが確認出来ました。
Centralの作成
次は受信側のCentralを作成していきます。 データの確認だけであれば先ほどのLightBlueなどのアプリで確認でも良いのですが、自分のサービスに組み込む際には何かしらのプログラミング言語でアクセスできた方が都合が良いです。
nobleの利用
nobleはNode.js向けのBLEライブラリです。MacやWindows、Raspberry PiなどのデバイスをBLEのCentralにすることができます。
mkdir ble_central
cd ble_central
touch app.js
npm init -y
npm i --save noble
これで準備とnobleのインストールが完了しました。
app.js
に以下を記述します。SERVICE_UUIDやCHARACTERISTIC_UUIDは自分で作成したNefry BT側に書き込んだUUIDと同様のものを指定しましょう。
[WIP] Async/Awaitに書き換えたい。
'use strict';
const noble = require('noble');
const serviceuuid = `d5875408fa514763a75d7d33cecebc31`;
const charauuid = `a4f01d8ca03743b690501876a8c23584`;
//キャラクタリスティックにアクセスしてデータやりとり
const accessChara = (chara) => {
console.log('-----Start GATT Access-----')
chara.notify(true, (err) => {
if (err) {
console.log('listen notif error', err)
} else {
console.log('listen notif')
}
});
chara.on('data', (data, isNotif) => {
const jsonStr = data.toString('utf-8');
const jsonData = JSON.parse(jsonStr);
console.log(jsonData);
});
}
//discovered BLE device
const discovered = (peripheral) => {
console.log(`BLE Device Found: ${peripheral.advertisement.localName}(${peripheral.uuid}) RSSI${peripheral.rssi}`);
if(peripheral.advertisement.localName === 'NefryBT-n0bisuke'){
noble.stopScanning();
console.log('device found');
console.log(`service discover...`);
peripheral.connect(error => {
if (error) {
console.log("connection error:", error)
} else {
console.log("device connected");
}
peripheral.discoverServices([],(err, services) => {
if (error) {
console.log("discover service error", error)
}
console.log('discover service');
services.forEach(service => {
if(service.uuid === serviceuuid){
service.discoverCharacteristics([], (error, charas) => {
console.log('discover chara');
charas.forEach(chara => {
if(chara.uuid === charauuid){
console.log("found chara: ", chara.uuid)
accessChara(chara);
}
});
});
}
});
});
});
}
}
//BLE scan start
const scanStart = () => {
noble.startScanning();
noble.on('discover', discovered);
}
if(noble.state === 'poweredOn'){
scanStart();
}else{
noble.on('stateChange', scanStart);
}
実行
node app.js
で実行します。この時、先ほどのLightBlueなどのアプリでNefryBTにBLEアクセスしていると上手くいかないのでアプリ側の接続は解除しましょう。
Mac側のNode.jsのログはこんな感じで表示されます。
BLE Device Found: LED(59aa15c3a3274ed7b11d334b5c0d0900) RSSI-68
BLE Device Found: NefryBT-n0bisuke(d0b77d4611f54380b8b63e6d05765ad6) RSSI-49
device found
service discover...
device connected
discover service
discover chara
found chara: a4f01d8ca03743b690501876a8c23584
-----Start GATT Access-----
listen notif
{ val: 147 }
{ val: 148 }
・
・
・
解説
NefryBT側ではデバイスにアクセスがありCentralとのコネクションが確立すると、変数value
の値をセット(pCharacteristic->setValue)して送信(pCharacteristic->notify)し、valueの値をインクリメントします。
これを2秒ごとに行うので2秒間隔でNefryBTからMacのNode.jsに情報が送信されます。
・
(省略)
・
・
void loop() {
if (deviceConnected) {
Serial.printf("*** NOTIFY: %d ***\n", value);
char buffer[10];
sprintf(buffer, "{\"val\":%d}", value);
Serial.printf(buffer);
pCharacteristic->setValue(buffer);
pCharacteristic->notify();
//pCharacteristic->indicate();
value++;
}
delay(2000);
}
Node.js(noble)側では、PERIPHERAL -> SERVICE -> CHARACTERISTICと階層的にアクセスしていき、CHARACTERISTICまでアクセスが出きると、accessChara関数が呼ばれます。
この中のchara.on('data')
の箇所でデータが送られてくるたびにイベントが発火して、データの中身を確認できます。
・
・
(省略)
・
const accessChara = (chara) => {
console.log('-----Start GATT Access-----')
chara.notify(true, (err) => {
if (err) {
console.log('listen notif error', err)
} else {
console.log('listen notif')
}
});
chara.on('data', (data, isNotif) => {
const jsonStr = data.toString('utf-8');
const jsonData = JSON.parse(jsonStr);
console.log(jsonData);
});
}
・
・
(省略)
・
まとめ
Nefry BTでBLEを利用する方法を紹介しました。 Nefry BTでセンサーのデータを取得し、Centralに送信する方法なども応用して作れそうですね。
これを参考にNefry BTとBLEデバイスの連携などに活用していきましょう。
今回はNefry BTをPeripheralにする実装でしたが、別の機会でCentralにする方法も紹介できればと思っています。
それでは!
所感
今回の実装ですが色々と途中でのハマりが多いかつ、調べてもまだまだESP32のBLE利用をArduinoでやってる事例は少なくけっこう大変でした。この辺の大変だった知見はQiitaなどでまとめらたらと思っています。お疲れ様です笑