Hobby Science&Experiment

愛と工作の日々

趣味でやっている工作や勉強したことのメモ書きです。

M5StickCのボタンでSwitchbotを操作する

M5StickCのLチカに昨日成功ばかりの新参者ですが、本来の目的であるSwitchbotの操作に取り掛かって行きます。
M5StickC(ESP32チップ)でSwitchbotを操作しようと検索し、以下の2件を発見できました。
qiita.com
dsas.blog.klab.org
一件目はなんとか動作しました。しかし不安定というか、動作したりしなかったり、数十秒止まったりしてしまうようでした。
二件目は使い方が間違っているのかまったく動作しませんでした。シリアルモニタを見ると、文字化けしたテキストが延々吐き出されていました…。
コードを見比べるとキモ(スイッチボット操作)の部分は似通っており、二件目のコードをベースに一件目は作成されたようした。
接続に関する改良が図られているようなので、その点が効いていたのかもしれません。
f:id:tara-chang:20200531183926p:plain

しかし音をトリガーにするのではなく純粋にスイッチボットをON/OFFするコードがまず必要でしたので、動作した一件目をベースに改造することにしました。
私はC言語の経験と知識がほとんどなかったのでだいぶハードルが高かったのですが、
前述一件目のコードと以下のコードをかなり雑に合体させてとりあえず所望の動作を得ることが出来たので、記録しておきます。
M5StickCであそぶ 〜ボタンとLEDを使う〜 | MUDAなことをしよう。

コード①

M5ボタンを押すと(つまりPIN 37がON)スイッチボットが1回動作するようになってます。
static String MAC_SWITCHBOT = "xx:xx:xx:xx:xx:xx";のxxの部分にMACアドレスを入力してください。

#include <M5StickC.h>
#include <driver/i2s.h>
#include "BLEDevice.h"

//追加
#define BTN_A_PIN 37
#define BTN_B_PIN 39
#define LED_PIN   10

// このLEDは、GPIO10の電位を下げることで発光するタイプ
#define LED_ON  LOW
#define LED_OFF HIGH

// INPUT_PULLUPが有効かは不明だが、有効という前提で定義
#define BTN_ON  LOW
#define BTN_OFF HIGH

uint8_t prev_btn_a = BTN_OFF;
uint8_t btn_a      = BTN_OFF;
uint8_t prev_btn_b = BTN_OFF;
uint8_t btn_b      = BTN_OFF;

// このLEDは、GPIO10の電位を下げることで発光するタイプ
#define LED_ON  LOW
#define LED_OFF HIGH

// INPUT_PULLUPが有効かは不明だが、有効という前提で定義
#define BTN_ON  LOW
#define BTN_OFF HIGH

// 手元のSwitchBotのMACアドレス
static String MAC_SWITCHBOT = "xx:xx:xx:xx:xx:xx";

// SwitchBotのBLE情報
static BLEUUID SERV_SWITCHBOT("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
static BLEUUID CHAR_SWITCHBOT("cba20002-224d-11e6-9fb8-0002a5d5c51b");
// SwitchBot へ送信するコマンド
// アームを倒し、引く動作の場合 {0x57, 0x01, 0x00}
// 以下2つはスマートフォンアプリでモードを変える必要あり
// アームを倒す動作の場合 {0x57, 0x01, 0x01}
// アームを引く動作の場合 {0x57, 0x01, 0x02}
static uint8_t cmdPress[3] = {0x57, 0x01, 0x01};

#define PIN_CLK  0
#define PIN_DATA 34
#define READ_LEN (2 * 1024)
#define SAMPLING_FREQUENCY 44100

uint8_t BUFFER[READ_LEN] = {0};
uint16_t *adcBuffer = NULL;
//const uint16_t FFTsamples = 256;  // サンプル数は2のべき乗//消去
//double vReal[FFTsamples];  // vReal[]にサンプリングしたデーターを入れる//消去
//double vImag[FFTsamples];//消去
//arduinoFFT FFT = arduinoFFT(vReal, vImag, FFTsamples, SAMPLING_FREQUENCY);  // FFTオブジェクトを作る//消去

bool doScan = true;
BLEScan* pBLEScan;
static BLEAddress *pGattServerAddress;
static BLEAdvertisedDevice* myDevice;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEClient*  pClient = NULL;

unsigned int sampling_period_us;
float dmax = 10000.0;

bool doDetection = false;
bool sendFlg = false;
bool cas = false;


//int printCount = 0;

void log(String s) {
  Serial.println(s);
  M5.Lcd.println(s);
}


// BLEへの接続 コールバック
class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
      Serial.println("onConnect");
      //    log("cc conn");
    }
    void onDisconnect(BLEClient* pclient) {
      Serial.println("onDisconnect");
      //    log("dis");
      //    doScan = true;
      //    doDetection = false;
      if (cas) {
        esp_restart();
      }
    }
};

// アドバタイズ検出時のコールバック
class advdCallback: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
      if (advertisedDevice.haveServiceUUID()) {
        String addr = advertisedDevice.getAddress().toString().c_str();
        Serial.printf("It have service addr: %s \n", advertisedDevice.getAddress().toString().c_str());
        if (addr.equalsIgnoreCase(MAC_SWITCHBOT)) {
          // SwitchBot を発見
          //        if (advertisedDevice.getServiceUUID().equals(SERV_SWITCHBOT)) {
          log("found");

          advertisedDevice.getScan()->stop();
          pGattServerAddress = new BLEAddress(advertisedDevice.getAddress());
          myDevice = new BLEAdvertisedDevice(advertisedDevice);

          doScan = false;
          doDetection = true;
//          m5LED(500, 2);
        }
      }
    }
};




void setup() {
  Serial.begin(115200);
  M5.begin();
//  i2sInit();

  pinMode(BTN_A_PIN, INPUT_PULLUP);//追加
  pinMode(BTN_B_PIN, INPUT_PULLUP);//追加
  pinMode(LED_PIN,   OUTPUT);//追加
  digitalWrite(LED_PIN, LED_OFF);//追加 

  //  pinMode(M5_LED, OUTPUT);
  M5.Axp.ScreenBreath(8);    // 画面の輝度を少し下げる
  M5.Lcd.setTextSize(2);    // 画面内文字のサイズを大きく

  log("Start");

  //xTaskCreate(mic_fft_task, "mic_fft_task", 2048, NULL, 1, NULL);//消去

  // BLE 初期化
  BLEDevice::init("");
  // デバイスからのアドバタイズをスキャン
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new advdCallback());
  pBLEScan->setActiveScan(true);
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
}

void loop() {
 
  btn_a = digitalRead(BTN_A_PIN);//追加

  if (doScan) {
    log("Scan");
    pBLEScan->start(5, false);
  }

//  if (sendFlg == true && doScan == false) {
  if(prev_btn_a == BTN_OFF && btn_a == BTN_ON && doScan == false){//追加

    // log reset
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    digitalWrite(LED_PIN, LED_ON);
    delay(500);
    digitalWrite(LED_PIN, LED_OFF);

    //log("Detect Sound");
    if (connectAndSendCommand(*pGattServerAddress)) {
      log("Done!");
      sendFlg = false;
      doScan = false;
    } else {
      log("Failed.");
      doScan = true;
      delay(3000);
    }
  }

  vTaskDelay(500 / portTICK_RATE_MS);
}

// SwitchBot の GATT サーバへ接続 ~ Press コマンド送信
static bool connectAndSendCommand(BLEAddress pAddress) {
  cas = true;
  try {
    pClient = BLEDevice::createClient();

    pClient->setClientCallbacks(new MyClientCallback());

    log("Connecting");
    while (!pClient->connect(myDevice)) {
      log("reconnect");
      delay(1000);
    }

    // 対象サービスを得る
    BLERemoteService* pRemoteService = pClient->getService(SERV_SWITCHBOT);
    if (pRemoteService == nullptr) {
      log("e:service not found");
      return false;
    }

    // 対象キャラクタリスティックを得る
    pRemoteCharacteristic = pRemoteService->getCharacteristic(CHAR_SWITCHBOT);
    if (pRemoteCharacteristic == nullptr) {
      log("e:characteristic not found");
      return false;
    }

    // キャラクタリスティックに Press コマンドを書き込む
    pRemoteCharacteristic->writeValue(cmdPress, sizeof(cmdPress), false);
    //    pRemoteCharacteristic->writeValue("test", true);
    log("Send");

    cas = false;

    delay(3000);
    pClient->disconnect();
    pClient = NULL;
    delay(1000);
  }
  catch (...) {
    log("error");
    if (pClient) {
      pClient->disconnect();
      pClient = NULL;
    }
    return false;
  }

  return true;
}

動作

気持ち長押し目で押してやってください。ディレイに入っている間はボタンを押しても受け付けられないためです。
対策としては並列処理にしたり、タイミングを逃さないトグルスイッチを使用することでしょうか。

コード②

M5ボタンを一度押すと、10秒に一回スイッチボットを動作させます。
ループを解除するには側面ボタンを一回押します(PINを39をON)。

#include <M5StickC.h>
#include <driver/i2s.h>
#include "BLEDevice.h"

//追加
#define BTN_A_PIN 37
#define BTN_B_PIN 39
#define LED_PIN   10

// このLEDは、GPIO10の電位を下げることで発光するタイプ
#define LED_ON  LOW
#define LED_OFF HIGH

// INPUT_PULLUPが有効かは不明だが、有効という前提で定義
#define BTN_ON  LOW
#define BTN_OFF HIGH

uint8_t prev_btn_a = BTN_ON;
uint8_t btn_a      = BTN_ON;
uint8_t prev_btn_b = BTN_OFF;
uint8_t btn_b      = BTN_OFF;

// このLEDは、GPIO10の電位を下げることで発光するタイプ
#define LED_ON  LOW
#define LED_OFF HIGH

// INPUT_PULLUPが有効かは不明だが、有効という前提で定義
#define BTN_ON  LOW
#define BTN_OFF HIGH

// 手元のSwitchBotのMACアドレス
static String MAC_SWITCHBOT = "xx:xx:xx:xx:xx:xx";

// SwitchBotのBLE情報
static BLEUUID SERV_SWITCHBOT("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
static BLEUUID CHAR_SWITCHBOT("cba20002-224d-11e6-9fb8-0002a5d5c51b");
// SwitchBot へ送信するコマンド
// アームを倒し、引く動作の場合 {0x57, 0x01, 0x00}
// 以下2つはスマートフォンアプリでモードを変える必要あり
// アームを倒す動作の場合 {0x57, 0x01, 0x01}
// アームを引く動作の場合 {0x57, 0x01, 0x02}
static uint8_t cmdPress[3] = {0x57, 0x01, 0x01};

#define PIN_CLK  0
#define PIN_DATA 34
#define READ_LEN (2 * 1024)
#define SAMPLING_FREQUENCY 44100

uint8_t BUFFER[READ_LEN] = {0};
uint16_t *adcBuffer = NULL;
//const uint16_t FFTsamples = 256;  // サンプル数は2のべき乗//消去
//double vReal[FFTsamples];  // vReal[]にサンプリングしたデーターを入れる//消去
//double vImag[FFTsamples];//消去
//arduinoFFT FFT = arduinoFFT(vReal, vImag, FFTsamples, SAMPLING_FREQUENCY);  // FFTオブジェクトを作る//消去

bool doScan = true;
BLEScan* pBLEScan;
static BLEAddress *pGattServerAddress;
static BLEAdvertisedDevice* myDevice;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEClient*  pClient = NULL;

unsigned int sampling_period_us;
float dmax = 10000.0;

bool doDetection = false;
bool sendFlg = false;
bool cas = false;


//int printCount = 0;

void log(String s) {
  Serial.println(s);
  M5.Lcd.println(s);
}


// BLEへの接続 コールバック
class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
      Serial.println("onConnect");
      //    log("cc conn");
    }
    void onDisconnect(BLEClient* pclient) {
      Serial.println("onDisconnect");
      //    log("dis");
      //    doScan = true;
      //    doDetection = false;
      if (cas) {
        esp_restart();
      }
    }
};

// アドバタイズ検出時のコールバック
class advdCallback: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
      if (advertisedDevice.haveServiceUUID()) {
        String addr = advertisedDevice.getAddress().toString().c_str();
        Serial.printf("It have service addr: %s \n", advertisedDevice.getAddress().toString().c_str());
        if (addr.equalsIgnoreCase(MAC_SWITCHBOT)) {
          // SwitchBot を発見
          //        if (advertisedDevice.getServiceUUID().equals(SERV_SWITCHBOT)) {
          log("found");

          advertisedDevice.getScan()->stop();
          pGattServerAddress = new BLEAddress(advertisedDevice.getAddress());
          myDevice = new BLEAdvertisedDevice(advertisedDevice);

          doScan = false;
          doDetection = true;
//          m5LED(500, 2);
        }
      }
    }
};




void setup() {
  Serial.begin(115200);
  M5.begin();
//  i2sInit();

  pinMode(BTN_A_PIN, INPUT_PULLUP);//追加
  pinMode(BTN_B_PIN, INPUT_PULLUP);//追加
  pinMode(LED_PIN,   OUTPUT);//追加
  digitalWrite(LED_PIN, LED_OFF);//追加 

  //  pinMode(M5_LED, OUTPUT);
  M5.Axp.ScreenBreath(8);    // 画面の輝度を少し下げる
  M5.Lcd.setTextSize(2);    // 画面内文字のサイズを大きく

  log("Start");

  //xTaskCreate(mic_fft_task, "mic_fft_task", 2048, NULL, 1, NULL);//消去

  // BLE 初期化
  BLEDevice::init("");
  // デバイスからのアドバタイズをスキャン
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new advdCallback());
  pBLEScan->setActiveScan(true);
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
}

void loop() {
 
  btn_a = digitalRead(BTN_A_PIN);//追加
  btn_b = digitalRead(BTN_B_PIN);
  
  if (doScan) {
    log("Scan");
    pBLEScan->start(5, false);
  }

//  if (sendFlg == true && doScan == false) {
  if((prev_btn_a == BTN_ON || btn_a == BTN_ON) && doScan == false){//追加   

    // log reset
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);

    digitalWrite(LED_PIN, LED_ON);
   //digitalWrite(LED_PIN, LED_OFF);
    prev_btn_a = BTN_ON;
    prev_btn_b = BTN_OFF;

    log("Detect Sound");
    if (connectAndSendCommand(*pGattServerAddress)) {
      log("Done!");
      sendFlg = false;
      doScan = false;
      delay(10000);
    } else {
      log("Failed.");
      doScan = true;
      delay(3000);
    }
    if(prev_btn_b == BTN_ON || btn_b == BTN_ON){
    // ボタンBが押されたとき。今回は2回点滅
      digitalWrite(LED_PIN, LED_ON);
      delay(100);
      digitalWrite(LED_PIN, LED_OFF);
      delay(100);
      digitalWrite(LED_PIN, LED_ON);
      delay(100);
      digitalWrite(LED_PIN, LED_OFF);
      prev_btn_a = BTN_OFF;
      prev_btn_b = BTN_ON;
  }
  }

  vTaskDelay(500 / portTICK_RATE_MS);
}

// SwitchBot の GATT サーバへ接続 ~ Press コマンド送信
static bool connectAndSendCommand(BLEAddress pAddress) {
  cas = true;
  try {
    pClient = BLEDevice::createClient();

    pClient->setClientCallbacks(new MyClientCallback());

    log("Connecting");
    while (!pClient->connect(myDevice)) {
      log("reconnect");
      delay(1000);
    }

    // 対象サービスを得る
    BLERemoteService* pRemoteService = pClient->getService(SERV_SWITCHBOT);
    if (pRemoteService == nullptr) {
      log("e:service not found");
      return false;
    }

    // 対象キャラクタリスティックを得る
    pRemoteCharacteristic = pRemoteService->getCharacteristic(CHAR_SWITCHBOT);
    if (pRemoteCharacteristic == nullptr) {
      log("e:characteristic not found");
      return false;
    }

    // キャラクタリスティックに Press コマンドを書き込む
    pRemoteCharacteristic->writeValue(cmdPress, sizeof(cmdPress), false);
    //    pRemoteCharacteristic->writeValue("test", true);
    log("Send");

    cas = false;

    delay(3000);
    pClient->disconnect();
    pClient = NULL;
    delay(1000);
  }
  catch (...) {
    log("error");
    if (pClient) {
      pClient->disconnect();
      pClient = NULL;
    }
    return false;
  }

  return true;
}

不用な部分も残ってるかもしれませんが、これでお目当ての動作が得られました。
スイッチボットのトリガーを変更したい場合は、以下の部分をお好みに変化させてください。

 if((prev_btn_a == BTN_ON || btn_a == BTN_ON) && doScan == false)