ESP32用のBLEライブラリの軽量版 -NimBLE-
SwitchBot HubをESP32で自作(1) では、BLEのライブラリがデカすぎるため、Partitionを変更しないとアプリケーションサイズがでかすぎてエラーになるという問題がありました。
少し調べてみたところ、BLEの公式ライブラリが重すぎるからどうにかしてくれという意見がEspressifのGitにも多かったようで、有志がOSSで対応ライブラリNimBLE
を別途公開してくれているようです。(どうやらこの界隈では、こっちのライブラリを使うのが当たり前になっているようですね。。。)
全然本筋が進んでいないのですが、今回はこのライブラリへの差し替えを行います。
なお、ArduinoIDE用のソースと共用されているのが少し都合が悪かったため、一からプロジェクトを作り直していますので、もしかしたら一部ソースが違うところがあるかもしれません。
開発環境
BLEライブラリの差し替え
公式のBLEライブラリをこのNimBLEに差し替えます
ライブラリの導入
外部ライブラリなので、追加で導入します。
Arduinoのライブラリとして公開されているので簡単に導入可能で、"NimBLE"と検索すると出てくるとおもいます。
PlatformIOであれば、こんなかんじで導入先Projectを指定すればよいです。
ソースの変更
どうやら、基本的にはソースの改変が不要で移植できるように作られているようです(こういうライブラリをほいほい作成・公開できるようになりたいですね。。。)
Includeの差し替え
BLEのライブラリを差し替え。
//(旧)#include "BLEDevice.h" #include <NimBLEDevice.h>
ソースの修正
私の場合、一部変更しないとビルドが通りませんでした。
BLEAdvertisedDeviceCallbacks
のonResult
の引数が実体からポインタになっているようですので、ポインタに変えていきます。
// アドバタイズ検出時のコールバック 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); }