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

2018-11-14 ダイソー BLE リモートシャッター で SwitchBot を操る

ダイソー BLE リモートシャッター で SwitchBot を操る

| ダイソー BLE リモートシャッター で SwitchBot を操るを含むブックマーク

 ツイッターとやらを始めるとブログ更新が億劫になってくる・・・それは都市伝説ではなくて本当の話だったのですね。

 「××出来たっ」って報告を上げたらそれで満足しちゃいがちですが、「自慢はいいから、どうやったら出来るのか具体的に教えてくれ」っていうのがインターネッツだと思うので、やっぱ面倒くさいとは言え個人的に有益と思う情報(探し求めている人がいるに違いない情報)は、ちゃんとまとめてアーカイブしておかねばいけないと思う次第。


 公式には1台の BLEデバイス しか接続できない ESP32 の Arduino ライブラリですが、ESP32 としては少なくとも3台の同時接続を標準でサポートしています。

 また、Espressif の気分次第では(SDK側の準備次第では)、もっと多くのデバイスサポートされそうという風なのですが、arduino-esp32 の対応を待っていたのではいつになるか分からないため、不肖ながらも私めが恐れ多くも本家 ESP32_BLE_Arduino から Fork させていただき、複数デバイスへの同時接続に対応 させて頂きました。

 この辺は先にツイッターで呟いてます。

 もっと早くに知りたかったよって人は是非ともフォロー してやってくださいませ

 いちお、アドバタイズで拾い集めた端末情報を一覧にして、Notify に対応してる Characteristic はコールバック登録して Notify の内容をダンプするだけのサンプル(/)も付けておきましたが、いまいち実証的でありません。


 そこで、ダイソーの Bluetooth リモートシャッターと、普通の機器を無理矢理 IoT 化するサンワサプライ Switch Bot(400-RC005) 、計2台の BLEデバイス を1台の ESP32 でペアリングさせ、リモートシャッターを使って Switch Bot を操作する、ということをやってみたいと思います。


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


■ ESP32_BLE_Arduino を差し替える

 すでに Arduino IDE や arduino-esp32 は導入済みだと仮定して話を進めます。


 本家のままだと複数デバイスには対応しておらず、また同一の UUID を持つ Characteristic にも対応しておらず、色々と不備が目立つので私めが公開しているものに一部を差し替えます。


 github から 私めのリポジトリ をダウンロードして適当な場所に解凍してください。

 解凍したフォルダごと本家に上被せする形でコピーします。

 Windows の場合だったら、普通は %USERPROFILE%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.0\libraries\BLE が本家の位置なので、ごっそり丸コピーしてやってください。


 丸コピーに抵抗ある方は、以下の変更ファイルのみコピーでも構いません。

  • examples/ble_clients_1/ble_clients_1.ino
  • examples/ble_clients_2/ble_clients_2.ino
  • src/BLEClient.cpp
  • src/BLEClient.h
  • src/BLEDevice.cpp
  • src/BLEDevice.h
  • src/BLERemoteService.cpp
  • src/BLERemoteService.h

 いろいろと改良したとは言え前方互換は維持しているつもりなので、複数デバイスを想定していない旧来のスケッチもそのまま動作するはずです。


■ メインのスケッチ

#include "BLEDevice.h"

static BLEUUID UUID_ABSHUTTER("00001812-0000-1000-8000-00805f9b34fb");
static BLEUUID CHAR_ABSHUTTER("00002a4d-0000-1000-8000-00805f9b34fb");
static BLEUUID UUID_SWITCHBOT("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
static BLEUUID CHAR_SWITCHBOT("cba20002-224d-11e6-9fb8-0002a5d5c51b");

static BLEClient* BLEC_ABSHUTTER = NULL;
static BLEClient* BLEC_SWITCHBOT = NULL;

static uint16_t clicked_button = 0;

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if (advertisedDevice.haveServiceUUID()) {      
      if(advertisedDevice.getServiceUUID().equals(UUID_ABSHUTTER))  {
        BLEC_ABSHUTTER = BLEDevice::createClient(advertisedDevice.getAddress());
        Serial.print("registered:");
      } else if(advertisedDevice.getServiceUUID().equals(UUID_SWITCHBOT))  {
        BLEC_SWITCHBOT = BLEDevice::createClient(advertisedDevice.getAddress());
        Serial.print("registered:");
      } else
        Serial.print("           ");
        
      Serial.printf("%s %s", advertisedDevice.getServiceUUID().toString().c_str(), advertisedDevice.getName().c_str());
      Serial.println();

      if(BLEC_ABSHUTTER && BLEC_SWITCHBOT)
        advertisedDevice.getScan()->stop();
    }
  }
};

static void notifyCallback(BLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)
{
  if(length == 2 && !clicked_button)
      clicked_button = pData[0] << 8 | pData[1];
}

void setup() {
  Serial.begin(115200);
  BLEDevice::init("");

  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);  
  pBLEScan->start(30);
}

void loop() {
  if(BLEC_ABSHUTTER && BLEC_ABSHUTTER->isDisconnected())
    if(BLEC_ABSHUTTER->connect()) {
      Serial.println("connected:ABShutter");
      auto* pCharacteristicMap = BLEC_ABSHUTTER->getService(UUID_ABSHUTTER)->getCharacteristicsByHandle();
      for(auto itr : *pCharacteristicMap)
        if(itr.second->getUUID().equals(CHAR_ABSHUTTER))
          itr.second->registerForNotify(notifyCallback);
    }

  if(BLEC_SWITCHBOT && BLEC_SWITCHBOT->isDisconnected() && BLEC_ABSHUTTER && BLEC_ABSHUTTER->isConnected())
  {
      BLEC_SWITCHBOT->connect();
      Serial.println("connected:SwitchBot");
  }
  
  if(BLEC_SWITCHBOT && BLEC_SWITCHBOT->isConnected() && clicked_button)
  {
    Serial.printf("button pressed:%04x", clicked_button);
    Serial.println();
    uint8_t buf[] = {0x57, 0x01, 0x00};
    switch(clicked_button)  {
      case 0x0100:  // Volume Up
        buf[2] = 0x03;
        break;
      case 0x0200:  // Volume Down      
        buf[2] = 0x04;
        break;
      case 0x0028:  // Enter
        break;
    }
    BLEC_SWITCHBOT->getService(UUID_SWITCHBOT)->setValue(CHAR_SWITCHBOT, std::string((char *)buf, sizeof(buf)));
    clicked_button = 0;
  }
}

 起動直後の30秒間がペアリングタイムです。

 リモートシャッター・SwitchBot の両方を見つけたら30秒を待たずに直ちにスキャンを終了します。


 なお、SwitchBot は電池をセットして初めてペアリングした相手を親だと記憶し続けるみたいなので、ESP32 とペアリングできないときは電池を抜くか、電池のそばにあるタクトスイッチ(出荷時に戻すボタンぽい)を押すかして過去の記憶は忘れてもらいましょう。


 ペアリング後は3つの操作に対応しています。

 割当てが気に入らない人はテキトーにソースを弄ってください。

リモートシャッター内部での識別コードSwitchBot備考
iOSボタン0x0100Turn On
Androidボタン0x0028+0x0100Press動作中のため 0x0100 は無視される
(改造版)Androidボタン0x0200Turn Off改造方法は こちら

 しばらく操作しないでいると、どちらも節電のためスリープモードに突入し、ESP32 との接続も切断されます。

 リモートシャッターのほうは OFF/ON するかスイッチ操作しないと復帰しませんが、ちゃんと(リセットかける必要なく)自動再接続されるようになってます。


 起動時にペアリングした相手を記憶していて、それとのみ再接続されるようにしてあるので、他の機器に割り込まれる心配もなく(mac アドレスのみによる無認証であるものの)一定のセキュリティを確保しています。


 接続処理 connect() のところで処理がブロッキングされますが、これは元々のライブラリが RTOS(マルチタスク)前提になっているためです。

 本稿のコードではその辺を端折ってますため、ブロッキングされたように見えますが、本来は BLEClient ごとにタスクを分けてセマフォを使ってタスク間通信の処理を書くのがセオリーのようです。


 notifyCallback() と loop() とは別タスクで同時に並行して動いているので、その点も注意が必要です。

anonymoussanonymouss 2018/12/06 17:08 はじめまして
BLUETOOTHのコントローラーの記事回ってたらたどり着いたのですが、このタイプとは別でよく見かける(100均では無く、あきばお〜とか)物があります
http://www.akibaoo.co.jp/c/item/4528483124298/
こっちは少しボタンもあるし切替もあるので改造出来たら色々な事が出来るんじゃないかと思って検索していたんですが全然情報が見付からず
もしご興味を持って検証して頂けたらと思い、コメントさせて頂きました
では失礼しました

UZTUZT 2019/01/21 21:59 はじめまして
本リモートシリーズの記事、いつも大変参考にさせていただいています

本記事のソースコードを丸々コピーしてコンパイルさせていただいたところ
「no matching function for call to 'BLEDevice::createClient(BLEAddress)'」
のエラーが出てしまいました。

ライブラリの方も丸コピーしているので後は明確に違う点といえば
ESP-32のボードマネージャのバージョンが1.0.0→1.0.1になっているくらいなのですが
お時間あればアドバイスを頂きたく思います

wakwak_kobawakwak_koba 2019/01/22 07:21  おはようございます。
 'BLEDevice::createClient(BLEAddress)'は私が拡張したメソッドです。
 再接続に備えて、BLEClient 側で BLEAddress の実体を(ポインタでなく)保持しておきたいという願望で作りました。

 1.0.1 が大幅に変わりすぎて、改めて Fork しなおしたのですが、'BLEDevice::createClient(BLEAddress)' の機能はまだ追加できていません。

 もうちょっと(今週末くらいまで)お待ちくださいませ〜

UZTUZT 2019/01/22 09:17 おはようございます。

返信いただけるとは!ありがとうございます。

やはり1.0.1が問題の可能性大ですか。
私の方でもメソッドをたどってみていたのですが素人目には正しく実装されているように見えたので気になっていた次第です。
(1.0.1と1.0.0がボードマネージャから簡単に共存できればいいのですが現時点ではボードマネージャで変更すると上書きされているので共存させようとすると手動で追加するしかなさそうです。)