1年近く前、ダイソー Bluetooth リモコンシャッター を ESP32 に BLE 接続させることに成功し、ボタンが押されたことを検知するまでは出来たものの(当時の記事)、リモコンシャッターにある2つのボタンを識別すること叶わず、ずっとお蔵入りしていたのですが、1年を経てようやく解決の目を見ることになりました♪
ことの顛末を説明しきるのは大変なので端的に結論を申しますと @coppercele さんがライブラリの不備を見つけて下さった おかげで、ボタンを識別できない原因を突き止めることが出来たのです!
私は横から糞リプ入れていただけの身ですけど・・・(汗
公開されているライブラリの不備とは「デバイス内に 同一な UUID を有する複数 Characteristic の存在が考慮されていない」というもの。
当時、GATT を学習し始めて3日ほどしか経ってない私でしたから、同一 UUID が重複して存在するなんてことを予測できるはずもなく、ライブラリの不具合なんて疑いもしませんでした。
しかし、ダイソーの Bluetooth リモコンシャッター には「00002a4d-0000-1000-8000-00805f9b34fb」という UUID な Characteristic が2つあったのです。
その2つ両方の Characteristic からの Notify を拾っていればボタンを識別できていたのですが、公開ライブラリは最初に見つけた Characteristic だけを有効とし、次に見つけた Characteristic を読み飛ばす(ライブラリの利用者から見ると Characteristic が存在しないように見える)という動きをしていました。
最初に見つかる Characteristic は Volume キーを始めとするマルチメディアキーと称されるものを通知するためのようで、次に見つかる(ライブラリが読み飛ばす)Characteristic は、通常のキーを通知するために実装されているみたいでした。
ダイソーの Bluetooth リモコンシャッター は HIDキーボードとして振る舞います。
iOS ボタンは VolumeUp を、Android ボタンは Enter + VolumeUp をそれぞれ送出するのですが、VolumeUp ボタンはマルチメディアキーに属するので最初に見つかる Characteristic を通じて通知されるのですが、Enter ボタンは通常のキーの扱いのため、最初のとは異なる Characteristic を通じて通知されるにも関わらず、後者はライブラリが読み飛ばしていたため通知を受けれず、つまり Android ボタンの Enter が届かないでいて VolumeUp だけだもんで iOS ボタンと識別できず・・・そんな風な展開だったようなのです。
公開されているライブラリのソースを弄ったうえで、Enter + VolumeUp を取得することにも成功しましたが、恐らくは近々にライブラリ側が修正されると思われ、それを待ってもソース公開は遅くなかろう・・・ということで、今回の記事では別の方法で(現時点で公開されている不具合を抱えたままのライブラリで)動く方法をご紹介したいと思います。
公開ライブラリに手を加えずにボタンを識別する方法とは・・・
Android ボタンの Enter + VolumeUp を VolumeDown にしてしまう
公開ライブラリが原因で Enterキー を読めないのだから、Enter を使わなければ(VolumeUp と同じグループに属する VolumeDown にしてしまえば)うまくいくだろ、って話です。
VolumeDown に変更する方法については こちらの方の記事 の後半部分をご覧下さい。
改造がうまくいったかどうかは、手持ちスマホとペアリングさせて、音楽でも流しながらボタン操作してみて、音量が上下するかで確認できます。
そのうえで、VolumeUp と VolumeDown とを識別できるコードを。
#include "BLEDevice.h" static int GPIO_LED_1 = 2; // Volume Up 時の LED static int GPIO_LED_2 = 0; // Volume Down 時の LED 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()); } } }; static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { int LED = -1; switch(pData[0]) { case 0x01: // Volume Up LED = GPIO_LED_1; break; case 0x02: // Volume Down LED = GPIO_LED_2; break; } if(LED >= 0) digitalWrite(LED, !digitalRead(LED)); } void setup() { Serial.begin(115200); pinMode(GPIO_LED_1, OUTPUT); pinMode(GPIO_LED_2, OUTPUT); 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); BLERemoteService* pRemoteService = pClient->getService(GATT_HID); if(pRemoteService) { BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(GATT_HID_REPORT); pRemoteCharacteristic->registerForNotify(notifyCallback); Serial.print("connected to:"); Serial.println(pRemoteCharacteristic->toString().c_str()); for(int i=0; i<2; i++) { digitalWrite(GPIO_LED_1, HIGH); digitalWrite(GPIO_LED_2, HIGH); delay(100); digitalWrite(GPIO_LED_1, LOW); digitalWrite(GPIO_LED_2, LOW); delay(400); } connected = true; } } }
前回のソース と比較して大きくは変わってません。
BLEDevice.h の中の人が難しいことを一手に引き受けてくれるので、十分に短いコードで実現できました。
ボタン操作で LED がモーメンタリーに点滅するのも一緒ですが、ボタン毎に LED を分けてあります。
iOS ボタンが GPIO_LED_1、Android ボタンが GPIO_LED_2 になってます。
notifyCallback の中で、0x01 と 0x02 と決め打ちして識別してますが、この辺はダイソーの Bluetooth リモコンシャッターに依存している部分で、汎用的に作り込むのであれば先にキーマップを読み込んで 0x01、0x02 に対応するキーコードを取得したほうがベターです。
0x1812/0x2a4b(0x2a4d ではない) を readValue() するとキーマップが得られるので、そこを参照して実際のキーを特定したほうがいいです。
自分も目下勉強中なので、これ以上の説明はできませんが、たぶん この辺 を読んだら理解できるはず・・・
(予告)
本記事は ダイソーの Bluetooth リモコンシャッター の Android ボタンを初期状態の Enter+VolumeUp から VolumeDown に改造したうえでの識別でしたが、未改造状態でボタンを識別するソースコード例も数日内にお披露目しようと思ってます。
しばしお待ち下さいませ〜
(追記)2018/10/11
続きを書きました