ダイソーで販売されている300円のBluetoothリモコンシャッター(Remote Shutter)を、ESP32側から BLE HIDデバイス として検出・接続し、ボタンが押されたタイミングで LED を点灯/消灯させてみました。スマホ用のシャッターリモコンを、自作IoTのトリガーとして再利用するイメージです。
この記事では、ESP32とリモコンシャッターをBLEで繋ぐ最小スケッチ、PlatformIO 側で踏みやすい arduino-esp32 のバージョン問題、そして同じ仕組みを使いやすくまとめた自作ライブラリ「BlueShutHacker」の使い方までを紹介します。ESP32でのBLE開発の流れを掴みたい方や、安価な市販Bluetoothデバイスを入力源として使い回したい方向けです。
実装にあたっては、下記の二つの記事をとても参考にさせていただきました。
- https://wakwak-koba.hatenadiary.jp/entry/20181009/p1
- https://lang-ship.com/blog/work/m5stickc-esp32-bluetooth-shutter-1-0-4/
必要なもの
最小構成で動かす場合に用意したものは次の通りです。
- ESP32 開発ボード(ESP32 DevKitC など、BLE対応のもの)
- ダイソーの Bluetooth リモコンシャッター(300円・ボタン電池付属)
- LED 1個と電流制限抵抗(330Ω 前後)、配線用ジャンパー線
- PlatformIO(VS Code または CLion)または Arduino IDE
ESP32 自体の最初の動作確認がまだの場合は、先に ESP32でLチカするまでの設定 で Arduino IDE からの書き込みまで通しておくと、本記事の内容に集中できます。
リモコンシャッターでLチカさせる
ESP32をつかって、リモコンシャッターのボタンが押されたらLEDの点灯状態を変化させてみました。
ソースコード
こちらがそのソースコードです:
#include <Arduino.h>
#include "BLEDevice.h"
#define LED_PIN 13
static uint16_t GATT_HID = 0x1812;
//static BLEUUID GATT_HID_REPORT((uint16_t) 0x2a4d);
static BLEAddress *pServerAddress = NULL;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(GATT_HID)) {
advertisedDevice.getScan()->stop();
pServerAddress = new BLEAddress(advertisedDevice.getAddress());
Serial.print("found device:");
Serial.println(pServerAddress->toString().c_str()); // 2a:07:98:10:33:fa
}
}
};
void updateLedState() {
static boolean ledState = true;
if (ledState == 0) {
digitalWrite(LED_PIN, LOW);
} else {
digitalWrite(LED_PIN, HIGH);
}
ledState = !ledState;
}
static void
notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.println("notifyCallback");
switch (pData[0]) {
case 0x01: // Volume Up
Serial.println("Volume Up");
updateLedState();
break;
case 0x02: // Volume Down
Serial.println("Volume Down");
updateLedState();
break;
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
BLEDevice::init("");
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);
}
void loop() {
static boolean connected = false;
if (pServerAddress != NULL && !connected) {
BLEClient *pClient = BLEDevice::createClient();
pClient->connect(*pServerAddress);
if(pClient->isConnected()) {
Serial.println("connected");
} else {
return;
}
//! arduino-esp32のバージョンによってはクラッシュするので注意
//! ver 2.0.2 だとここで落ちる → ver 2.0.14 または ver 3.20014.0 で動作確認済み
BLERemoteService *pRemoteService = pClient->getService(GATT_HID);
if (pRemoteService) {
// BLERemoteCharacteristic *pRemoteCharacteristic = pRemoteService->getCharacteristic(GATT_HID_REPORT);
// pRemoteCharacteristic->registerForNotify(notifyCallback);
std::map<uint16_t, BLERemoteCharacteristic*>* mapCharacteristics = pRemoteService->getCharacteristicsByHandle();
for (std::map<uint16_t, BLERemoteCharacteristic*>::iterator i = mapCharacteristics->begin(); i != mapCharacteristics->end(); ++i) {
Serial.print("connected to:");
Serial.println(i->second->getUUID().toString().c_str());
if (i->second->canNotify()) {
Serial.println(" - Add Notify");
i->second->registerForNotify(notifyCallback);
}
}
connected = true;
}
}
delay(10); // 高負荷防止
}
注意点
ソースコードをアップロードするにあたって、注意点があります。PlatformIO を使う場合、arduino-esp32 のバージョンが古いとクラッシュします。ソースコード内のコメント注釈のとおり、version 2.0.2 だとダメでした。version 2.0.14 以降であれば問題なさそうです。
[env]
platform = https://github.com/platformio/platform-espressif32.git
board = esp32dev
framework = arduino
monitor_speed = 115200
platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#2.0.2
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#2.0.14
PlatformIO 自体の導入や JetBrains 製 IDE での開発環境を整える手順は、Clion x PlatformIOでESP32(Arduino)開発 にまとめてあります。
解説
BLE処理の流れ
BLE処理の流れは以下の通りです:
- デバイスのスキャン開始:
setup関数でBLEスキャンが開始され、指定したサービスUUIDを持つデバイスが見つかるまで続けられます。 - デバイス発見時の処理:
MyAdvertisedDeviceCallbacksのonResultコールバックが呼び出され、条件に合致するデバイスが見つかった場合、スキャンは停止され、デバイスのアドレスが保存されます。 - デバイスへの接続試行:
loop関数内で、保存されたデバイスアドレスに対して接続を試みます。接続に成功すると、次のステップに進みます。 - サービスの取得と通知の購読: 接続後、GATT HIDサービスを取得し、そのサービス内の特性(Characteristic)に対して通知を購読します。このステップでは、すべての特性をループして、通知可能な特性に対して
notifyCallbackを登録します。 - 通知の受信と処理:
notifyCallbackが、購読した特性から通知を受け取るたびに呼び出され、受け取ったデータに基づいてLEDの状態を切り替えます。
このスケッチは、BLEデバイスのスキャン、接続、および通知の購読という、BLE通信の基本的なプロセスを実演しています。それぞれのステップは、BLEデバイスとのインタラクションを制御し、特定のサービスや特性にアクセスするために重要です。
関数やクラスの説明
このArduinoスケッチは、BLE(Bluetooth Low Energy)を使用して特定のサービスUUID(この場合はGATT HIDサービス)を持つデバイスをスキャンし、見つけたデバイスに接続して、通知を購読するプロセスを実行します。以下に、主要なBluetooth関連のロジックを中心に解説したテーブルを示します。
| 関数/クラス名 | 説明 |
|---|---|
MyAdvertisedDeviceCallbacks | BLEデバイスのスキャン中に呼び出されるコールバック。指定したUUIDを持つデバイスを見つけた場合、そのデバイスのアドレスを保存し、スキャンを停止します。 |
notifyCallback | BLEデバイスからの通知を受け取ったときに呼び出されるコールバック。受け取ったデータに基づいて、特定のアクション(この例ではLEDの状態の切り替え)を実行します。 |
setup | Arduinoスケッチの初期設定を行う関数。BLEデバイスの初期化、スキャンの設定、そしてスキャンの開始を行います。 |
loop | Arduinoスケッチのメインループ。特定のデバイスが見つかり、まだ接続されていない場合、そのデバイスに接続し、GATT HIDサービスを取得して、通知を購読します。 |
| UUID | UUIDを通じて、BLEデバイスは特定のサービスや特性を識別し、操作を行うことができます。これにより、互換性のあるデバイス間での正確なデータ交換が可能になります。 |
| BLERemoteService | BLERemoteServiceは、BLEデバイス上で提供されるサービスを表します。サービスは、一連の特性(BLERemoteCharacteristic)をグルーピングしたもので、デバイスが提供する機能や情報のカテゴリを表します。たとえば、「バッテリーサービス」は、デバイスのバッテリーレベルに関する情報を提供する特性を含むことができます。 |
| BLERemoteCharacteristic | BLERemoteCharacteristicは、BLEサービスの下で定義されるデータポイントです。特性は、サービスが提供する具体的な情報や操作を表し、データの読み取り、書き込み、通知(データの変更があったときに自動的に送信される)をサポートできます。 |
BlueShutHackerのリポジトリとインストール
その後、ダイソーのリモコンシャッターをESP32で通信するためのロジックをライブラリ化してみましたのでご紹介いたします。その名も「BlueShutHacker」です!
リポジトリ
BlueShutHackerは、下記リポジトリでMITライセンスで公開中です。
ライブラリのインストール
リポジトリからzipファイルなどでダウンロードしていただき、SharedBlueShutHacker.hとSharedBlueShutHacker.cppファイルをプロジェクト内に適宜配置してください。
PlatformIOをお使いの方でしたら、platformio.iniに次のように記述することで自動インストールできます。
[env]
lib_deps = https://github.com/aragig/BlueShutHacker.git
BlueShutHackerの使い方と解説
#include <Arduino.h>
#include "BLEDevice.h"
#include "BlueShutHacker.h"
#define LED_PIN 13
BlueShutHacker blueShutHacker;
void updateLedState(boolean state) {
Serial.println("updateLedState");
digitalWrite(LED_PIN, state ? HIGH : LOW);
}
void onButtonClick(boolean state) {
updateLedState(state);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
blueShutHacker.begin(onButtonClick);
}
void loop() {
blueShutHacker.handler();
delay(10); // 高負荷防止
}
スキャンする時間の設定
コンストラクタに引数を渡すことで、スキャンする時間の設定ができます。次のコードでは60秒スキャン時間を設けています。
BlueShutHacker blueShutHacker(60);
コールバック関数の登録
setup内の次のコードで、ボタン押下時のコールバックイベントを登録します。
blueShutHacker.begin(onButtonClick);
コールバックされる際に、ボタンの状態をstateで返します。iOS と androidボタンの判別はありませんので、どちらのボタンが押されてもstate=trueになります。また、ボタンを離した瞬間にstate=falseが発生するようになってます。
void onButtonClick(boolean state) {
updateLedState(state);
}
関連記事
ESP32 と Bluetooth まわりをもう少し深く触りたい方には、次の記事もおすすめです。
- はじめてのBLE通信、iOSからESP32のLチカ ― 本記事と逆方向に、iOSアプリから ESP32 を BLE で操作する入門記事です。
- 【ESP32】BLE通信、ESP32からiOSでデータ受信 ― ESP32 をペリフェラルにして、Notify でセンサ値などを iOS に送る側のサンプルです。
- ESP32でBluetooth Classicを使ってAndroidと通信する ― BLE ではなく Bluetooth Classic(SPP)で Android アプリと繋ぐパターンを扱っています。
関連アイテム
実際に手を動かす際の入手導線です。Amazonの検索結果ページに飛びます。
- ESP32 開発ボード本体: ESP32 DevKitC を Amazonで探す
- 受信対象のリモコン: Bluetooth リモコンシャッターを Amazonで探す (同じ通信仕様の HID デバイスなら、ダイソー以外でも応用可)
- ESP32 にピンヘッダを実装し直す場合: はんだごてを Amazonで探す
