BabyIoT

BabyTechよりもただのDIY

ESP32用のBLEライブラリの軽量版 -NimBLE-

SwitchBot HubをESP32で自作(1) では、BLEのライブラリがデカすぎるため、Partitionを変更しないとアプリケーションサイズがでかすぎてエラーになるという問題がありました。

少し調べてみたところ、BLEの公式ライブラリが重すぎるからどうにかしてくれという意見がEspressifのGitにも多かったようで、有志がOSSで対応ライブラリNimBLEを別途公開してくれているようです。(どうやらこの界隈では、こっちのライブラリを使うのが当たり前になっているようですね。。。)

全然本筋が進んでいないのですが、今回はこのライブラリへの差し替えを行います。

なお、ArduinoIDE用のソースと共用されているのが少し都合が悪かったため、一からプロジェクトを作り直していますので、もしかしたら一部ソースが違うところがあるかもしれません。

開発環境

BLEライブラリの差し替え

GitHub - h2zero/NimBLE-Arduino: A fork of the NimBLE library structured for compilation with Ardruino, designed for use with ESP32.

公式のBLEライブラリをこのNimBLEに差し替えます

ライブラリの導入

外部ライブラリなので、追加で導入します。

Arduinoのライブラリとして公開されているので簡単に導入可能で、"NimBLE"と検索すると出てくるとおもいます。

f:id:babyjiji:20220126184128p:plain

PlatformIOであれば、こんなかんじで導入先Projectを指定すればよいです。

f:id:babyjiji:20220126184204p:plain

ソースの変更

どうやら、基本的にはソースの改変が不要で移植できるように作られているようです(こういうライブラリをほいほい作成・公開できるようになりたいですね。。。)

Includeの差し替え

BLEのライブラリを差し替え。

//(旧)#include "BLEDevice.h"
#include <NimBLEDevice.h>

ソースの修正

私の場合、一部変更しないとビルドが通りませんでした。

BLEAdvertisedDeviceCallbacksonResultの引数が実体からポインタになっているようですので、ポインタに変えていきます。

// アドバタイズ検出時のコールバック
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(switchBotMac)) {

          Serial.println("Found BLE");

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

        }
      }
    }
};

/* 旧ソース
// アドバタイズ検出時のコールバック
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(switchBotMac)) {
          
          Serial.println("Found BLE");

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

        }
      }
    }
};
*/

ビルド

前回同様PlatformIOでビルドします。

サイズ比較

1420102 bytes -> 1011618 bytes

と、アプリケーションサイズを7割程度まで縮小させることができ、初期Partition時のAppサイズ制限1310720bytesにも収まるようになりました。

まとめ

参照ライブラリの変更と少しのソース変更だけでビルドが通り、公式ライブラリからの移植も簡単でした。

今後にBLEライブラリを使う場合は、基本的に今回のNimBLEライブラリでよさそうですね。

ソース全文(一応)

#include <Arduino.h>
#include <WiFi.h>
#include <Espalexa.h>
#include <NimBLEDevice.h>

// prototypes
boolean connectWifi();
void InitializeBLE();
static bool connectAndSendCommand(BLEAddress pAddress, uint8_t isTurnOn);

//callback functions
void changeSwitch(uint8_t val);

/////////////////Constant values///////////////////////

//Alexa
const char* deviceName = "Bedroom light";

//wifi
const char* ssid = "WifiのSSID";
const char* password = "WifiのPW";

//Switch bot
const char* switchBotMac = "SwitchBotのMAC";
static BLEUUID SERV_SWITCHBOT("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
static BLEUUID CHAR_SWITCHBOT("cba20002-224d-11e6-9fb8-0002a5d5c51b");
//command of Switch bot
#define SWITCH_BOT_MODE 1 //0 : Button, 1 : Switch

#if SWITCH_BOT_MODE == 0
static uint8_t cmdPush[3]   = {0x57, 0x01, 0x00}; //Push and Pull
#else
static uint8_t cmdPress[3]  = {0x57, 0x01, 0x01}; //Turn On
static uint8_t cmdPull[3]   = {0x57, 0x01, 0x02}; //Turn Off
#endif //SWITCH_BOT_MODE

//Grobal val
boolean wifiConnected = false;
BLEScan* pBLEScan;
static BLEAddress *pGattServerAddress;
static BLEAdvertisedDevice* myDevice;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEClient*  pClient = NULL;

Espalexa espalexa;

void setup()
{
  int i = 0;
  Serial.begin(115200);
  InitializeBLE();

  for (; i < 100; i++) {
    wifiConnected = connectWifi();
    if(wifiConnected){

      espalexa.addDevice(deviceName, changeSwitch,0); //simplest definition, default state off
      espalexa.begin();
      break;
    }
    delay(3000);
  }
  if(!wifiConnected){
    Serial.println("Cannot connect to WiFi. Please check data and reset the ESP.");
    esp_restart();
  }
}

void loop()
{
   espalexa.loop();
   delay(100);
}

//callback functions
void changeSwitch(uint8_t val) {
    Serial.print("Switch is changed to ");

    if (val) {
      Serial.print("ON");
      Serial.print(val);
    }
    else  {
      Serial.println("OFF");
    }

    connectAndSendCommand(*pGattServerAddress ,val);
}

///////////////////////////Wifi////////////////////////////

// connect to wifi – returns true if successful or false if not
boolean connectWifi(){
  boolean state = true;
  int i = 0;

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi");

  // Wait for connection
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (i > 20){
      state = false; break;
    }
    i++;
  }
  Serial.println("");
  if (state){
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }
  else {
    Serial.println("Connection failed.");
  }
  return state;
}

////////////////////////////BLE/////////////////////////

// For debug
class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
      Serial.println("onConnect");
    }
    void onDisconnect(BLEClient* pclient) {
      Serial.println("onDisconnect");
    }
};

// アドバタイズ検出時のコールバック
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(switchBotMac)) {

          Serial.println("Found BLE");

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

        }
      }
    }
};

// SwitchBot の GATT サーバへ接続 ~ Press コマンド送信
static bool connectAndSendCommand(BLEAddress pAddress, uint8_t isTurnOn) {
  Serial.println("connectAndSendCommand Start!");
  try {
    pClient = BLEDevice::createClient();

    Serial.println(myDevice->getAddress().toString().c_str());
    pClient->setClientCallbacks(new MyClientCallback());

    Serial.println("Connecting BLE");
    while (!pClient->connect(myDevice)) {
      Serial.println("reconnect");
      delay(1000);
    }

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

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

      // キャラクタリスティックに Press コマンドを書き込む
#if SWITCH_BOT_MODE == 0
    Serial.println("Send Push");
    pRemoteCharacteristic->writeValue(cmdPush, sizeof(cmdPush), false);
#else
    Serial.println("check turnOn/off");
    if(isTurnOn){
      pRemoteCharacteristic->writeValue(cmdPress, sizeof(cmdPress), false);
      Serial.println("Send Press");
    }else{
      pRemoteCharacteristic->writeValue(cmdPull, sizeof(cmdPull), false);
      Serial.println("Send Pull");
    }
#endif //SWITCH_BOT_MODE

    delay(500); //ここが長すぎると、アレクサからエラー(デバイスの応答がない)が出るので注意
    pClient->disconnect();
    pClient = NULL;
  }
  catch (...) {
    Serial.println("Error");
    if (pClient) {
      pClient->disconnect();
      pClient = NULL;
    }
    return false;
  }

  return true;
}


void InitializeBLE(){
  // BLE 初期化
  BLEDevice::init("");
  // デバイスからのアドバタイズをスキャン
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new advdCallback());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5,false);
}