ESP32でBLEデバイスを作る
※この記事はLuup Advent Calendarの4日目の記事です。
以前、あるBLEデバイスを操作するAndroidアプリを開発するのに、肝心のBLEデバイスの実機が入手できないということがありました。そこで通信仕様の資料を元にESP32でダミーのBLEデバイスを作ってみたところ、よい感じに動いてくれたので事なきを得たのでした[1]。
ESP32は安価で入手性もよく、Arduinoで手軽にプログラムを開発できるという利点があります。
本記事では、ESP32を使った簡単なBLEデバイスの作成例をご紹介します。
ところで、ハーラル1世という10世紀のデンマークの王様に、神経の死んだ歯があったことから付けられたあだ名「青歯王」が、Bluetoothの名前の由来だそうです[2]。
ブレッドボード上に回路を作る
このサンプルでは、LEDとボタンスイッチを使ったシンプルな構成のBLEデバイスを作成します。以下の図のように、ESP32-DevKitC-32Eの GPIO22
にLED、GPIO23
にボタンスイッチを接続します。
GPIO22
がHIGHになるとLEDが点灯します。LEDには電流制限用の抵抗を繋ぎます。
GPIO23
はプルアップ設定で使うので、ボタンを離すとHIGH、ボタンを押すとGNDと短絡してLOWになります。
BLEのしくみ
BLEの通信はセントラルとペリフェラルの間で行います。
- セントラル:接続の主体となるクライアント側(PCやスマートフォンなど)
- ペリフェラル:セントラルの要求を受けるサーバ側(BLEデバイスなど)
通信でやり取りする値ごとに、Characteristic が定義されます。CharacteristicはServiceに、ServiceはServer(ペリフェラル、BLEデバイス)に含まれます。
CharacteristicとServiceには、識別のためのUUIDが割り当てられます。本サンプルでは、事前に生成したランダム値をUUIDとしています。
Advertisingという仕組みによって、ブロードキャスト方式のデータ送信が可能です。相互通信はできませんが、省電力で済むのでiBeaconなどに使われています。
Arduinoのソースコード解説
ソースコード
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
//---------------------------------------------------------
// Constants
//---------------------------------------------------------
#define SERVICE_UUID "55725ac1-066c-48b5-8700-2d9fb3603c5e"
#define CHARACTERISTIC_UUID "69ddb59c-d601-4ea4-ba83-44f679a670ba"
#define BLE_DEVICE_NAME "MyBLEDevice"
#define LED_PIN 22
#define BUTTON_PIN 23
//---------------------------------------------------------
// Variables
//---------------------------------------------------------
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
std::string rxValue;
std::string txValue;
bool bleOn = false;
bool buttonPressed = false;
int buttonCount = 0;
//---------------------------------------------------------
// Callbacks
//---------------------------------------------------------
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("onConnect");
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("onDisconnect");
}
};
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
Serial.println("onWrite");
std::string rxValue = pCharacteristic->getValue();
if( rxValue.length() > 0 ){
bleOn = rxValue[0]!=0;
Serial.print("Received Value: ");
for(int i=0; i<rxValue.length(); i++ ){
Serial.print(rxValue[i],HEX);
}
Serial.println();
}
}
};
//---------------------------------------------------------
void setup() {
//
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(115200);
//
BLEDevice::init(BLE_DEVICE_NAME);
// Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
pCharacteristic->addDescriptor(new BLE2902());
//
pService->start();
// Advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0);
BLEDevice::startAdvertising();
Serial.println("startAdvertising");
}
//---------------------------------------------------------
void loop() {
// disconnecting
if(!deviceConnected && oldDeviceConnected){
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising();
Serial.println("restartAdvertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if(deviceConnected && !oldDeviceConnected){
oldDeviceConnected = deviceConnected;
}
// LED
digitalWrite(LED_PIN,bleOn?HIGH:LOW);
// BUTTON
if(digitalRead(BUTTON_PIN) == LOW){
if(buttonPressed == false){
buttonCount++;
String str = "BTN:"+String(buttonCount);
Serial.println(str);
// Notify
if( deviceConnected ){
txValue = str.c_str();
pCharacteristic->setValue(txValue);
pCharacteristic->notify();
}
buttonPressed = true;
delay(50);
}
}else{
if(buttonPressed == true){
buttonPressed = false;
delay(50);
}
}
}
LEDとボタンにはそれぞれ別のCharacteristicを定義するべきですが、本サンプルでは簡略化のために1つのCharacteristicの読み書き(NOTIFY/WRITE)に対応させています。
コールバック関数
BLEのコールバック処理を記述する2つの派生クラスを定義しています。
BLEServerCallbacks
の派生クラスでは、BLEの接続状態が変化した時のコールバック関数を定義します。
BLECharacteristicCallbacks
の派生クラスでは、Characteristicに関するイベントのコールバック関数を定義します。本サンプルではonWrite()
のみを使用し、セントラル側から受信したデータを元に、LEDの点灯状態を切り替える変数bleOn
の値を更新しています。
setup()
ー 初期化処理
ピンの入出力モード、デバッグ用のシリアル通信を設定した後に、BLEを初期化しています。
BLEDevice::init()
の引数で指定する文字列は、セントラル側で表示されるデバイス名になります。ServiceとCharacteristicにはそれぞれ対応するUUIDとコールバック関数クラスを指定します。Advertiseの設定も行っていますが、本サンプルではあまり使わないので特にデータは追加していません。
loop()
ー メインループ
BLEの切断を検知したらすぐに再接続するのではなく、500ms待機してから再接続を試みています。[3]
bleOn
変数でLEDの点灯状態を更新し、ボタンが押されたことを検知したら、カウント値を更新してnotify()
でセントラル側に送信します。delay(50)
はチャタリング対策のウェイトですが、メインループに影響が出てしまうため、やや手抜きの処理です。
BLE通信を試してみる
ESP32にソフトを書き込んだらスマートフォンから操作してみましょう。ここではBLEの検証ツールとしてnRF Connect for Mobileというアプリを使います。アプリを起動後にBLEデバイスをスキャンすると、Arduinoのコードで記述したデバイス名が表示されるので、CONNECTをタップして接続します。
"MyBLEDevice"というデバイスが表示されている
接続が完了すると、定義したServiceとCharacteristicが確認できます。値を書き込むために上向き矢印をタップします。
上向き矢印をタップ
表示されるダイアログからBYTE型の0x01
という値を送信すると、ESP32に接続したLEDが点灯します。0x00
を送信すると消灯します。
データ書き込み(WRITE)ダイアログ
次は、ESP32からデータを受信してみましょう。3本の下向き矢印のボタンをタップすると、NOTIFYの有効/無効が切り替わります。NOTIFYが有効化された状態(Notifications enabledと表示)で、ESP32側に接続したボタンスイッチを押すと、ESP32から送信されたデータがValueとして表示されます。ボタンスイッチを押すたびにValueの値が更新されます。
Valueに受信したデータ"BTN:1"がセットされている
おわりに
BLEが無線通信で使用する2.4GHzの周波数帯は、ISMバンドと呼ばれる混信の多い周波数帯です。BLEは周波数ホッピングという技術で、高速に通信周波数を切り替えて混信を回避しています。この技術は第二次世界大戦中、無線誘導の魚雷が妨害電波を回避するための仕組みとして発明されました。ハリウッド女優のヘディ・ラマーさんはこの技術の発明者です[4]。
Hedy Lamarr (1914-2000)
-
AndroidのBLE実装の困難さについては、あえてここでは触れていません。 ↩︎
-
https://www.bluetooth.com/ja-jp/about-us/bluetooth-origin/ ↩︎
-
Espressif社が提供しているサンプルコードを参考にしています。ESP32固有の処理のようです。AndroidのBLE実装でもこの手の待機処理を入れることがあり、俗に「思いやりタイマー」と呼ばれています。 ↩︎
Discussion
BLEDevice.h」に対して複数のライブラリが見つかりました
使用済:C:\Users(myuser)\Documents\Arduino\libraries\ArduinoBLE
未使用:C:\Users(myuser)\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.14\libraries\BLE
exit status 1
Compilation error: 'class BLECharacteristic' has no member named 'getValue'; did you mean 'setValue'?
というエラーがでてしまいました。どうしたらいいですか?
返信が遅れてしまい失礼しました。
ご報告いただいたエラーは私の手元の環境では再現できなかったのですが、BLEライブラリが競合を起こしているのかもしれません。ArduinoBLE ライブラリを削除してからビルドを試してみてください。
ライブラリ環境に問題がなければ、スケッチ例の「ESP32 Arduino BLE」内のサンプルプロジェクトもビルドできると思います。