ESP32 と ダイソー Bluetooth リモコンシャッター で Lチカ

 私の手持ちスマホ 201HW が Android 4.0.4 ということで、Bluetooth LE に対応していない・・・という理由だけで Bluetooth Classic で もがいていたのですが、HCI やら L2CAP やらを初歩から読解することに疲れ果ててしまいました。
 で、その過程で気がついたのです。

ダイソー Bluetooth リモコンシャッター は 201HW では使えない

 リモコンシャッターの裏紙には全く書かれていないと思うのですが、実は Android 4.4〜 じゃないと使えないんですよね。
 未だに Classic に固執していることこそ老害の始まりなのである・・・、ということに遂に気がついたのです。


 んで、流行は Bluetooth LE、LE と言えば GATT ですが、サクッと arduino-esp32 の最新版を pull してみたら、なんか知らないうちに GATT に対応したスタックが出来上がってる雰囲気じゃないですか。


 GATT に関する予備知識が全くない私でしたが、調べていくとそれは Bluetooth Classic の時代の様式とは全く異なる、21世紀ライクに洗練されたラクチンなプロトコルであることが分かりました。


 勉強を兼ねて、ダイソー Bluetooth リモコンシャッター を HID over GATT で ESP32 に接続してみることにしました。
 ただし、先に断っておきますが、ペンディング箇所が残っており完成じゃありません

追記(2018/10/08)
 コメント欄のやりとりを経てソースに不備を発見しました。
(というか、当時は動いたはずなのだが、ライブラリが変わったせいで動かなくなった気がする)
 最初のほうの

static uint16_t GATT_HID_REPORT = 0x2a4d;

static BLEUUID GATT_HID_REPORT((uint16_t)0x2a4d);

に書き換えて下さい!


 あと、Android ボタンを Volume Down に改造した場合、notifyCallback() の pData[0] で識別可能ぽいです。
 1:iOSボタン(Volume Up)、2:Androidボタン(Volume Down)


 未改造時はライブラリ(BLERemoteService.cpp)側の不備でボタンの識別ができません。


 この辺ひっくるめて改めて記事にする予定です。

#include "BLEDevice.h"

static int GPIO_LED = 2;
static uint16_t GATT_HID = 0x1812;
static uint16_t GATT_HID_REPORT = 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)
{
  static uint8_t last_pData = 0;
  static bool sw = false;
  
  if(last_pData && !pData[0])
  {
    sw = !sw;
    digitalWrite(GPIO_LED, (!sw ? LOW : HIGH) );
  }
  
  last_pData = pData[0];
}

void setup() {
  Serial.begin(115200);
  pinMode(GPIO_LED, 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, HIGH);
        delay(100);
        digitalWrite(GPIO_LED, LOW);
        delay(400);
      }

      connected = true;
    }
  }
}

 LED は IO2 に繋げる前提です。
 違うピンを使いたいときには最初のほうの GPIO_LED を弄って下さい。


 起動したらペアリング待ちに入ります。
 30秒で待ち受け終了しますので、その間にペアリングを済ませられなかったらリセットしてやってください。
(ペアリングと言っても、認証なしの無暗号通信モードみたいですが)


 Advertising 中にデバイスの機種名も取得できるので、やろうと思えばリモコンシャッターを識別できるんですが、手抜きで 0x1812 を吹くデバイスを見つけたら直ちに繋ぎに行っちゃってますのでご注意を。


 ペアリングに成功すると LED がチカチカってします。(ON・100ms/OFF・400ms/ON・100ms)


 ダイソーのシャッターボタンを押すとモーメンタリー的に LED が点いたり消えたりします。
(下の動画では分かりにくいですが)



※基板は 自作のブレークアウトボード です。 


ペンディング箇所(未解決の課題)
 初めて GATT なるものに触れてから3日くらいしか経ってなくて、キーコードの取得方法が分かりません。


 今回は notifyCallback で受けたときのデータ(2バイトのうち1バイト目)に pressed / unpressed が入ってるぽい挙動だったので、それを用いて LED 制御しています。
 こいつは USB-HID の modifier とは違う気がします。


 0x1812/0x2A4B を readValue() すると、USB-HID の仕様書に書かれてる感じな 68バイト のキーマップが取れてきました。

05 0c 09 01 a1 01 85 02 09 e9 09 ea 09 e2 09 30 15 01 25 0c 75 10 95 01 81 00 c0 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 15 00 25 f4 05 07 19 00 29 f4 81 00 c0

 お目当てのキーコードを受け取るのは 0x1812/0x2a4d じゃないか?と疑って readValue() してみるのですが、00 00 としか戻って来ません。
 VolumeUP に対応するキーコードが得られるべきはずなのですが、どうやって取るのでしょうか。
 キーコードが取得できれば、2つあるボタンを識別して違う動作をさせることができるのですが。


 ちなみに 0x1812/0x2a4d の中には 0x2902 と 0x2908 な descriptor が居るのですが、こいつらを readValue() するとフリーズ(入力待ちで処理がブロック)してしまうので、きっと descriptor の中にはいないのでしょう。
 てっきり USB-HID と同じレイアウトの8バイトが手軽に降ってくると期待していたのですが見事に裏切られ。


 専門(本業)の方、是非とも教えて下さい〜!!


 ところで Boot Protocol Mode にも期待したいのですが、どうやら非対応な気がします。
(0x1812 の中には 0x2A32 も 0x2A33 も存在しないみたい)


ペンディング箇所その2
 デバイスの切断を下位の関数では検知しているぽいのですが、上位の GATT ライブラリまで切断情報が上がってきていない雰囲気がします。
 

 BLEClient あたりに connected() みたいな接続状態を監査するプロパティが欲しいところ。
 だふん外人も同じ事に遭遇してフォーラムに意見を上げてくるとと思うので、本家の対応を待つか、Fork して勝手に弄るか、はてさて。
 まぁこれは大した問題じゃないです。


(追記)2018/10/09
 ここでペンディングとしていた課題が解消されつつあります。
 続きは こちら をどうぞ