ダイソーのBluetoothリモコンシャッターをESP32でハックする

はじめに

ダイソーで販売されている300円のBluetoothリモコンシャッター(Remote Shutter)を、ESP32と通信してボタンイベントを受信できるようにしてみました。この企画はすでに色々な方が挑戦されていて、情報も溢れています。ここでは下記の二つの記事を参考にさせてもらいながら、ESP32で実装してみました。

リモコンシャッターでLチカさせる

ESP32をつかって、リモコンシャッターのボタンが押されたらLEDの点灯状態を変化させてみました。

ソースコード

こちらがそのソースコードです:

start_up.ino
#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以降でしたら問題なさそうです。

platformio.ini
[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

解説

BLE処理の流れ

BLE処理の流れは以下の通りです:

  1. デバイスのスキャン開始: setup関数でBLEスキャンが開始され、指定したサービスUUIDを持つデバイスが見つかるまで続けられます。
  2. デバイス発見時の処理: MyAdvertisedDeviceCallbacksonResultコールバックが呼び出され、条件に合致するデバイスが見つかった場合、スキャンは停止され、デバイスのアドレスが保存されます。
  3. デバイスへの接続試行: loop関数内で、保存されたデバイスアドレスに対して接続を試みます。接続に成功すると、次のステップに進みます。
  4. サービスの取得と通知の購読: 接続後、GATT HIDサービスを取得し、そのサービス内の特性(Characteristic)に対して通知を購読します。このステップでは、すべての特性をループして、通知可能な特性に対してnotifyCallbackを登録します。
  5. 通知の受信と処理: notifyCallbackが、購読した特性から通知を受け取るたびに呼び出され、受け取ったデータに基づいてLEDの状態を切り替えます。

このスケッチは、BLEデバイスのスキャン、接続、および通知の購読という、BLE通信の基本的なプロセスを実演しています。それぞれのステップは、BLEデバイスとのインタラクションを制御し、特定のサービスや特性にアクセスするために重要です。

関数やクラスの説明

このArduinoスケッチは、BLE(Bluetooth Low Energy)を使用して特定のサービスUUID(この場合はGATT HIDサービス)を持つデバイスをスキャンし、見つけたデバイスに接続して、通知を購読するプロセスを実行します。以下に、主要なBluetooth関連のロジックを中心に解説したテーブルを示します。

関数/クラス名説明
MyAdvertisedDeviceCallbacksBLEデバイスのスキャン中に呼び出されるコールバック。指定したUUIDを持つデバイスを見つけた場合、そのデバイスのアドレスを保存し、スキャンを停止します。
notifyCallbackBLEデバイスからの通知を受け取ったときに呼び出されるコールバック。受け取ったデータに基づいて、特定のアクション(この例ではLEDの状態の切り替え)を実行します。
setupArduinoスケッチの初期設定を行う関数。BLEデバイスの初期化、スキャンの設定、そしてスキャンの開始を行います。
loopArduinoスケッチのメインループ。特定のデバイスが見つかり、まだ接続されていない場合、そのデバイスに接続し、GATT HIDサービスを取得して、通知を購読します。
UUIDUUIDを通じて、BLEデバイスは特定のサービスや特性を識別し、操作を行うことができます。これにより、互換性のあるデバイス間での正確なデータ交換が可能になります。
BLERemoteServiceBLERemoteServiceは、BLEデバイス上で提供されるサービスを表します。サービスは、一連の特性(BLERemoteCharacteristic)をグルーピングしたもので、デバイスが提供する機能や情報のカテゴリを表します。たとえば、「バッテリーサービス」は、デバイスのバッテリーレベルに関する情報を提供する特性を含むことができます。
BLERemoteCharacteristicBLERemoteCharacteristicは、BLEサービスの下で定義されるデータポイントです。特性は、サービスが提供する具体的な情報や操作を表し、データの読み取り、書き込み、通知(データの変更があったときに自動的に送信される)をサポートできます。

まとめ

今回のBLE通信のロジック部分をライブラリ化してみました。下記の記事よりダウンロードできますので、こちらもぜひご参考ください。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

ESP32搭載ボード
ESP32の書籍
Arduinoで人気の周辺パーツ
M5Stackのオススメ参考書
M5Stack製品
M5StickCで使えるHat
ESP32ボード
関連記事