ブログ/こばさんの wakwak 山歩き Twitter

2018-10-11 ESP32 と ダイソー Bluetooth リモコンシャッター で Lチカ(無改造

ESP32 と ダイソー Bluetooth リモコンシャッター で Lチカ(無改造版)

| ESP32 と ダイソー Bluetooth リモコンシャッター で Lチカ(無改造版)を含むブックマーク

 ESP32 の Arduinoライブラリ の中の Bluetooth 関係 には、「デバイス内に 同一な UUID を有する複数 Characteristic の存在が考慮されていない」という不具合があって、そのせいで素の(販売されている)状態の ダイソー Bluetooth リモコンシャッター の2つあるボタンを識別できません。

 そのため、一つ前の記事 では、リモートシャッター側を改造することで対処しましたが、不具合のあるライブラリ側を改修すればリモートシャッターを改造せずとも2つのボタンを識別できるようになります。

http://dl.ftrans.etr.jp/?a6641b963c11409a9837db580c3beba32c1336b5.jpg


 公式ライブラリが修正されるのを待つのがセオリーかと思いますが、待ってられない、って方に向けて自力で何とかする方法について記述いたします。


■不具合のあるライブラリの修正

  • libraries/ble/src/BLERemoteService.h
  • libraries/ble/src/BLERemoteService.cpp

が修正対象となります。

 BLERemoteService.h を読むと

std::map<uint16_t, BLERemoteCharacteristic *> m_characteristicMapByHandle;
void getCharacteristics(std::map<uint16_t, BLERemoteCharacteristic*>* pCharacteristicMap);

という風に、ハンドル番号に紐付いた Characteristic を収納するための map 変数、およびその map 変数を受け取るメソッドが宣言されているにも関わらず BLERemoteService.cpp の中で実装がなされていないという残念な状態。。。


 m_characteristicMapByHandle を使うように手を加えるとともに、引数で受け取る getCharacteristics は呼び出し側が面倒になるので、getCharacteristics() と同じ使い勝手になるような getCharacteristicsByHandle() という物を新設しました。

※ getCharacteristicsByHandle() というネーミングは @coppercele さん発案


 いつもはここでソースをベタ貼りするところですが、修正前後の差分を見ながら手作業で修正したい方もお見えになると思うので、本家から Fork した私のリポジトリへのリンクを張っておきますね

リポジトリ全体 (src の中の BLERemoteService.cpp と BLERemoteService.h)

差分

 いずれ公式ライブラリも改修されるとは思うんですが、なんとなく getCharacteristicsByHandle() が採用されるような気がしてます。


(追記)2018/10/17

 デストラクト処理にバグ(delete する必要のないものを delete して2重解放してしまっていた)を 見つけて 修正していますので、10/16 までにソースを持って行かれた方は差し替えて下さい。

 上のリンクは最新に繋がってるので、今は大丈夫です。

差分/BLERemoteService::removeCharacteristics()

■メインのスケッチ

#include "BLEDevice.h"

static int GPIO_LED_VOLUMEUP = 2;
static int GPIO_LED_ENTER    = 0;
static uint16_t GATT_HID = 0x1812;
static BLEUUID GATT_HID_REPORT((unsigned short)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)
{
  uint16_t value = pData[0] << 8 | pData[1];
  if(value == 0x0100)
    digitalWrite(GPIO_LED_VOLUMEUP, !digitalRead(GPIO_LED_VOLUMEUP));
  else if(value == 0x0028)
    digitalWrite(GPIO_LED_ENTER   , !digitalRead(GPIO_LED_ENTER   ));
}

void setup() {
  Serial.begin(115200);
  pinMode(GPIO_LED_VOLUMEUP, OUTPUT);
  pinMode(GPIO_LED_ENTER   , 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)
    {
      auto* pCharacteristicMap = pRemoteService->getCharacteristicsByHandle();
      for (auto itr : *pCharacteristicMap)
      {
        Serial.print(itr.second->toString().c_str());
        if(GATT_HID_REPORT.equals(itr.second->getUUID()) && itr.second->canNotify())
        {
          itr.second->registerForNotify(notifyCallback);
          Serial.print(" registered");
        }
        Serial.println();
      }

      for(int i=0; i<2; i++)
      {
        digitalWrite(GPIO_LED_VOLUMEUP, HIGH);
        digitalWrite(GPIO_LED_ENTER   , HIGH);
        delay(100);
        digitalWrite(GPIO_LED_VOLUMEUP, LOW);
        digitalWrite(GPIO_LED_ENTER   , LOW);
        delay(400);
      }

      connected = true;
    }
  }
}

 コンパイルが通らないときは、一つ前の作業に問題がある(BLERemoteService が修正できていない)可能性が大です。

 いつものとおり、LED を繋いだピンは適宜変更してください。(GPIO_LED_VOLUMEUP/GPIO_LED_ENTER)


 0x1812 を吹くデバイスを見つけたら節操なく繋ぎに行ってしまう点は、MACアドレスを精査するなり、BLEAdvertisedDevice->getName() して機器名を判別するなりテキトーに応用ください。


 Android ボタンを押すと2つの LED が共に変化しますが、これは Android ボタンが Enter と VolumeUp の2つのボタンを押したように振る舞うためです。

 きちんと Android ボタンと iOS ボタンとを識別したい場合、Android ボタンの判定は Enter 待ちでいいですが、VolumeUp はどちらもボタンであっても Notify されるので、直前に Enter が押されてない場合に限って iOS ボタン という風なロジックが必要になってきます。


 それを踏まえると、VolumeDown に改造したほうが判定がシンプルになって都合がいいですねー


D


 ダイソーの Bluetooth リモコンシャッター は利口なもんで、しばらく使わないでいると勝手にスリープに突入します。

 んでボタン押すと始動(恐らく電源ONと同じ)します。


 実用的に使おうとする場合、ESP32 側にも切断検知と再接続の処理も必要になります。

 BLEClient に isConnected() ってメソッドがあるので、こいつで監視したらいいのかな?


(追記)2018/11/14

 Fork したリポジトリをバージョンアップして、複数デバイスへの同時接続と再接続処理を追加しました。

 その上で ダイソー BLE リモートシャッター で SwitchBot を操る という本稿の続編になりそうな記事を書きましたので、興味ある方はどうぞ。


コーコー 2018/11/15 00:12 ESP32のBLEサンプルがまだまだ少ない中、大変有効な情報ありがとうございます。
300円リモコンで鍵を開けたりできないかと思いたどり着きました。
Arduino1.8.7をインストして、ESP32環境セットして、ボードを"ESP32 Dev Module"選択、
リポジトリを丸ごと上書きして、無改造版のメインスケッチをコンパイルして秋月電子で買った開発ボード(肝心のLEDは未実装ですが、ポートは12と14で)に書き込んで、準備完了。
今日、11月14日の書き込みを読んででようやくここまで来ました(...\Arduino15\packages\...に上書きせずに、zipをライブラリとして読み込んでました)、c言語もc#もあまり勉強できていない爺なもんで・・・ツイッターも参加できずにいます。
さて、準備ができたので、
リモコンの電源を入れてリモコンのLEDが早い点滅、Arduino IDEのシリアルモニタに
Characteristic の行が6行、内2行はregisteredと表示されました。おおーっすげー。

しかし、その後リモコンのLEDがゆっくりした点滅に変わります。ずっとこのままです。
試しに notifyCallback のdigitalWriteのところに Serial.print 入れて、リモコンのボタンを押してもメッセージはシリアルに出てこず、リモコンのLEDが2秒ほど早い点滅になるだけで、その後再びゆっくりした点滅になります。
connected = true;までは来ている様なんですが、どうもうまく動いてくれません。
なかなか難しいですね〜。

こばさんこばさん 2018/11/15 09:46 おはようございます。

>リモコンのLEDがゆっくりした点滅に変わります

ってことは、うまくペアリングできていないです。
上のソースだと、ダイソーのリモートシャッターを厳密には特定しておらず、単に「Bluetooth HIDキーボード」を探しまわって、見つかったら接続しに行ってしまう風になってます。

ESP32 の側が registerd になるということは、ESP32 はペアリング出来たと思い込んでる状態なので、なにか違う機材と繋がってしまってる状態な気がします。

「Serial.println(pServerAddress->toString().c_str());」の下に以下を追加して下さるとデバイスが名乗る名前も表示されるようになります。

Serial.println(advertisedDevice.getName().c_str());

リモートシャッターは「AB Shutter 3」なのですが、それをちゃんと掴んでいるかどうか確認してみて頂けますか?

コーコー 2018/11/15 22:14 こんばんは、夜にならないと作業できないので、遅くなってます。
お付き合いいただきありがとうございます。
早速、指示いただいた行と、ついでにどこを走ってるのか適当にprint入れて実行してみました。
Scan start. ← setup の pBLEScan->start(30);の前
Setup end. ← 同じく後。リモコンの電源を入れると直ぐに表示され、LED ONまで一気に表示される。
loop start. ← void loop() スタート
found device:ff:ff:c3:13:b1:11
"AB Shutter3 " ←スペースが入ってますが、ちゃんと合っているようです。""は後から入れました。
connected to:Characteristic: uuid: 00002a4d-0000-1000-8000-00805f9b34fb, handle: 19 0x13, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 1, indicate: 0, auth: 0
LED ON ← 本来はGPIOの出力ですが、繋いでないのでとりあえずprint
LED OFF
LED ON
LED OFF
Connected
このままループしているようです。
リモコンのほうは電源ON後、foundのタイミングでLEDが一度消えますが、2秒ほどすると早い点滅を再開し、更にそこから4秒ほどでゆっくりした点滅に変わります。

こばさんこばさん 2018/11/16 00:56 そういうのが出るということは、ちゃんとペアリングできてる感じですね。
自分が買ったのは1年くらい前で、途中で何か変わってるのかも!?
ってことで、明日にでも買い増ししてこようと思います。

ちなみに自分の個体だと

Characteristic: uuid: 00002a4e-0000-1000-8000-00805f9b34fb, handle: 17 0x11, props: broadcast: 0, read: 1, write_nr: 1, write: 0, notify: 0, indicate: 0, auth: 0
Characteristic: uuid: 00002a4d-0000-1000-8000-00805f9b34fb, handle: 19 0x13, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 1, indicate: 0, auth: 0 registered
Characteristic: uuid: 00002a4d-0000-1000-8000-00805f9b34fb, handle: 23 0x17, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 1, indicate: 0, auth: 0 registered
Characteristic: uuid: 00002a4b-0000-1000-8000-00805f9b34fb, handle: 27 0x1b, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 0, indicate: 0, auth: 0
Characteristic: uuid: 00002a4a-0000-1000-8000-00805f9b34fb, handle: 29 0x1d, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 0, indicate: 0, auth: 0
Characteristic: uuid: 00002a4c-0000-1000-8000-00805f9b34fb, handle: 31 0x1f, props: broadcast: 0, read: 0, write_nr: 1, write: 0, notify: 0, indicate: 0, auth: 0

って風に出て、リモートシャッターのLEDは消灯して、ボタンを押すと一瞬だけチカる感じです。

コーコー 2018/11/16 19:43 すいません、14日の時点では6行出てました。今、上のスケッチを再度コピペして実行したら、貼っていただいた内容とまったく同じ6行が出ました。が、症状は同じです。
一度ペアリング成功して、2秒後にリモコンが勝手にリブートしてるんですかね。
もしかして電池endかと思い、新品入れてみましたが、一緒でした。
試しに30秒後に connected = false; にしてみたら直ぐに connected to:Characteristic: uuid: 00002a4d-0000-1000-8000-00805f9b34fb, handle: 19 0x13, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 1, indicate: 0, auth: 0
が出て、最初と同じ状態になります。

コーコー 2018/11/17 00:03 何度もすいません、ひとつ勘違いしてました。ボリュームダウン改造版と、無改造版をごっちゃにしてテストしてました。
改造版は1行だけで、無改造版は6行出ているようです。
どんなやり取りしてるか、モニタしてみたい気はしますが、有線なら兎も角、いかんせん道具がありません。
ESP32側で切断検知を入れてみたらなんかわかるんでしょうかね。

こばさんこばさん 2018/11/17 07:36 すみません、昨日はダイソーの開いてる時間に帰ることが出来ませんで、最新ロットのを入手できていません。。。
それはそれとして、少し前に複数BLEデバイス対応な Fork ライブラリを公開しました。
https://github.com/wakwak-koba/ESP32_BLE_Arduino

その中で動作確認用の実証コードも載せてます。
https://github.com/wakwak-koba/ESP32_BLE_Arduino/blob/master/examples/ble_clients_1/ble_clients_1.ino
https://github.com/wakwak-koba/ESP32_BLE_Arduino/blob/master/examples/ble_clients_2/ble_clients_2.ino

Lチカなどはしないで、Notify で受け取ったデータを表示するだけのものなのですが、こちらでも試してみて頂けますでしょうか。

コーコー 2018/11/20 00:12 いろいろとありがとうございます。ble_clients_1.ino, _2.inoも見てみましたが、やっぱりc#系の理解が必要ですね。まだ勉強が足りません。>私
今日、進展がありました。
11月14日のスケッチを元にSWITCHBOTの関係をコメントアウトして動かしたらLEDが点灯/消灯する(メッセージが出せる)ようになりました。勿論ハードの固体は同じものを使用しているので、動かなかったのはソフトの何らかの相性とかそんな所かと思います。(私がちゃんと理解できていないのが最大の問題ですけど)
で、せっかく動いたので、これをベースに考えて行こうと思います。

こばさんこばさん 2018/11/20 07:34 SwitchBot のほうは切断後の再接続を実装しているので、何かの原因で接続した直後に切断してしまってるかもしれませんね。
(本稿のは再接続しない)

なにはともあれ、目処がたったぽいようなので良かったです。