ESP32ライブラリ1.0.2のときに検証したものを、最新バージョンの1.0.3で検証しなおしました。M5StickCで実験しましたが、コード的には単なるESP32です。
※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。
概要
1.0.2での問題点は以下の2点でした。
- リセットがかかる
- 同一CharacteristicUUIDがあると1つしか取得できない
1個目のリセットがかかるについては、修正されておりました。2個目についてはまだ修正されていませんので、対策をする必要があります。
同一CharacteristicUUIDの取得方法
getCharacteristics()関数
/** * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID */ void BLERemoteService::getCharacteristics(std::map<uint16_t, BLERemoteCharacteristic*>* pCharacteristicMap) { pCharacteristicMap = &m_characteristicMapByHandle; } // Get the characteristics map.
上記の関数で、参照渡しでm_characteristicMapByHandle構造体を引き渡しているように見えますが、引数が参照ではなくコピーなので、実際には値が取得できないようです。
上記に詳しい解説があります。
getCharacteristicsByHandle()関数
こちら宣言はBLERemoteService.hにありますが、BLERemoteService.cppで実装されていません。
しかし、よく考えれば実装されていないってことは、自分で実装してしまえばいいのです!
解決方法
getCharacteristicsByHandle()関数を自分で作成
std::map<uint16_t, BLERemoteCharacteristic*>* BLERemoteService::getCharacteristicsByHandle() { if (!m_haveCharacteristics) { retrieveCharacteristics(); } return &m_characteristicMapByHandle; }
関数的には単純ですので、すぐに実装が可能です。1.0.3では実装されていないので、ライブラリに手を入れずに、自分のプロジェクトの中に追加することで利用可能になります。
Scanのサンプル(GitHub)
#include "BLEDevice.h" // 1.0.3で未定義のメソッドを実装する std::map<uint16_t, BLERemoteCharacteristic*>* BLERemoteService::getCharacteristicsByHandle() { if (!m_haveCharacteristics) { retrieveCharacteristics(); } return &m_characteristicMapByHandle; } // 検索するBLEデバイス。serviceUUIDを調べる場合には空にする(例はHuman Interface Device"00001812-0000-1000-8000-00805f9b34fb") static BLEUUID serviceUUID("1812"); static BLEAdvertisedDevice* myDevice; // 接続してCharacteristic一覧を取得 bool connectToServer() { Serial.print("接続先 : "); Serial.println(myDevice->getAddress().toString().c_str()); Serial.println("start createClient"); BLEClient* pClient = BLEDevice::createClient(); Serial.println("end createClient"); Serial.println("start connect"); pClient->connect(myDevice); Serial.println("end connect"); // サービス取得 Serial.println("start getService"); BLERemoteService* pRemoteService = pClient->getService(serviceUUID); Serial.println("end getService"); if (pRemoteService == nullptr) { pClient->disconnect(); return false; } // Characteristic一覧 Serial.println("characteristic一覧"); std::map<uint16_t, BLERemoteCharacteristic*>* mapCharacteristics = pRemoteService->getCharacteristicsByHandle(); for (std::map<uint16_t, BLERemoteCharacteristic*>::iterator i = mapCharacteristics->begin(); i != mapCharacteristics->end(); ++i) { Serial.print(" - characteristic UUID : "); Serial.print(i->second->getUUID().toString().c_str()); Serial.print(" Broadcast:"); Serial.print(i->second->canBroadcast()?'O':'X'); Serial.print(" Read:"); Serial.print(i->second->canRead()?'O':'X'); Serial.print(" WriteNoResponse:"); Serial.print(i->second->canWriteNoResponse()?'O':'X'); Serial.print(" Write:"); Serial.print(i->second->canWrite()?'O':'X'); Serial.print(" Notify:"); Serial.print(i->second->canNotify()?'O':'X'); Serial.print(" Indicate:"); Serial.print(i->second->canIndicate()?'O':'X'); Serial.println(); } // stop Serial.println("プログラム停止!"); while (1) delay(1000); return true; } // 検索したデバイスを受信するコールバック関数 class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { Serial.print("BLE デバイス発見 : "); Serial.println(advertisedDevice.toString().c_str()); if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { // 指定デバイスだったら接続する BLEDevice::getScan()->stop(); myDevice = new BLEAdvertisedDevice(advertisedDevice); } } }; void setup() { Serial.begin(115200); Serial.println("BLEデバイス検索開始..."); BLEDevice::init(""); BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); pBLEScan->start(5, false); } void loop() { if (myDevice != NULL) { connectToServer(); } delay(1000); }
1.0.2のときにはリセット回避のために、黒魔術を使っていましたが1.0.3の場合にはライブラリ関数を、自分で実装するだけで実現できました。
BLEデバイス検索開始... BLE デバイス発見 : Name: AB Shutter3 , Address: ff:ff:XX:XX:XX:XX, appearance: 961, serviceUUID: 1812-0000-1000-8000-00805f9b34fb 接続先 : ff:ff:XX:XX:XX:XX start createClient end createClient start connect end connect start getService end getService characteristic一覧 - characteristic UUID : 2a4e-0000-1000-8000-00805f9b34fb Broadcast:X Read:O WriteNoResponse:O Write:X Notify:X Indicate:X - characteristic UUID : 2a4d-0000-1000-8000-00805f9b34fb Broadcast:X Read:O WriteNoResponse:X Write:X Notify:O Indicate:X - characteristic UUID : 2a4d-0000-1000-8000-00805f9b34fb Broadcast:X Read:O WriteNoResponse:X Write:X Notify:O Indicate:X - characteristic UUID : 2a4b-0000-1000-8000-00805f9b34fb Broadcast:X Read:O WriteNoResponse:X Write:X Notify:X Indicate:X - characteristic UUID : 2a4a-0000-1000-8000-00805f9b34fb Broadcast:X Read:O WriteNoResponse:X Write:X Notify:X Indicate:X - characteristic UUID : 2a4c-0000-1000-8000-00805f9b34fb Broadcast:X Read:X WriteNoResponse:O Write:X Notify:X Indicate:X プログラム停止!
実行結果が上記です。
ボタン状態取得のサンプル(GitHub)
/** A BLE client example that is rich in capabilities. There is a lot new capabilities implemented. author unknown updated by chegewara */ #include "BLEDevice.h" // 1.0.3で未定義のメソッドを実装する std::map<uint16_t, BLERemoteCharacteristic*>* BLERemoteService::getCharacteristicsByHandle() { if (!m_haveCharacteristics) { retrieveCharacteristics(); } return &m_characteristicMapByHandle; } // The remote service we wish to connect to. static BLEUUID serviceUUID("1812"); static boolean doConnect = false; static boolean connected = false; static boolean doScan = false; static BLEAdvertisedDevice* myDevice; static void notifyCallback( BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { Serial.print("Notify callback for characteristic "); Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); Serial.print("("); Serial.print(pBLERemoteCharacteristic->getHandle()); Serial.print(") of data length "); Serial.print(length); Serial.print(" data: "); for ( int i = 0 ; i < length ; i++ ) { Serial.printf( "%02X ", pData[i] ); } Serial.println(); } class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { } void onDisconnect(BLEClient* pclient) { connected = false; Serial.println("onDisconnect"); } }; bool connectToServer() { Serial.print("Forming a connection to "); Serial.println(myDevice->getAddress().toString().c_str()); BLEClient* pClient = BLEDevice::createClient(); Serial.println(" - Created client"); pClient->setClientCallbacks(new MyClientCallback()); // Connect to the remove BLE Server. pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private) Serial.println(" - Connected to server"); // Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(serviceUUID); if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(serviceUUID.toString().c_str()); pClient->disconnect(); return false; } Serial.println(" - Found our service"); std::map<uint16_t, BLERemoteCharacteristic*>* mapCharacteristics = pRemoteService->getCharacteristicsByHandle(); for (std::map<uint16_t, BLERemoteCharacteristic*>::iterator i = mapCharacteristics->begin(); i != mapCharacteristics->end(); ++i) { if (i->second->canNotify()) { Serial.println(" - Add Notify"); i->second->registerForNotify(notifyCallback); } } connected = true; return true; } /** Scan for BLE servers and find the first one that advertises the service we are looking for. */ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { /** Called for each advertising BLE server. */ void onResult(BLEAdvertisedDevice advertisedDevice) { Serial.print("BLE Advertised Device found: "); Serial.println(advertisedDevice.toString().c_str()); // We have found a device, let us now see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { BLEDevice::getScan()->stop(); myDevice = new BLEAdvertisedDevice(advertisedDevice); doConnect = true; doScan = true; } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks void setup() { Serial.begin(115200); Serial.println("Starting Arduino BLE Client application..."); BLEDevice::init(""); // Retrieve a Scanner and set the callback we want to use to be informed when we // have detected a new device. Specify that we want active scanning and start the // scan to run for 5 seconds. BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); pBLEScan->start(5, false); } // End of setup. // This is the Arduino main loop function. void loop() { // If the flag "doConnect" is true then we have scanned for and found the desired // BLE Server with which we wish to connect. Now we connect to it. Once we are // connected we set the connected flag to be true. if (doConnect == true) { if (connectToServer()) { Serial.println("We are now connected to the BLE Server."); } else { Serial.println("We have failed to connect to the server; there is nothin more we will do."); } doConnect = false; } // If we are connected to a peer BLE Server, update the characteristic each time we are reached // with the current time since boot. if (connected) { } else if (doScan) { BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino } delay(1000); // Delay a second between loops. } // End of loop
BLE_clientに最低限の手を入れるだけで動くようになりました。
Starting Arduino BLE Client application... BLE Advertised Device found: Name: AB Shutter3 , Address: ff:ff:XX:XX:XX:XX, appearance: 961, serviceUUID: 1812-0000-1000-8000-00805f9b34fb Forming a connection to ff:ff:XX:XX:XX:XX - Created client - Connected to server - Found our service - Add Notify - Add Notify We are now connected to the BLE Server. Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(19) of data length 2 data: 01 00 Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(19) of data length 2 data: 00 00 Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(23) of data length 2 data: 00 28 Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(19) of data length 2 data: 01 00 Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(19) of data length 2 data: 00 00 Notify callback for characteristic 2a4d-0000-1000-8000-00805f9b34fb(23) of data length 2 data: 00 00
上記が実行結果です。2つあるキーのうち、iOSキーを押した場合、ハンドルID19の01 00(ボリュームアップ)と00 00(キーアップ)が飛んできます。
Androidキーを押した場合には、ハンドルID23の00 28(エンター)が押されたのち、ハンドルID19の01 00(ボリュームアップ)と00 00(キーアップ)がきて、ハンドルID23の00 00(キーアップ)が飛んできます。
新規問題点
上記でも報告されていますが、UUIDの桁数がおかしいです。
1.0.2 : 00001812-0000-1000-8000-00805f9b34fb
1.0.3 : 1812-0000-1000-8000-00805f9b34fb
先頭の0が消えています。
std::string BLEUUID::toString() { if (!m_valueSet) return "<NULL>"; // If we have no value, nothing to format. // If the UUIDs are 16 or 32 bit, pad correctly. if (m_uuid.len == ESP_UUID_LEN_16) { // If the UUID is 16bit, pad correctly. char hex[5]; snprintf(hex, sizeof(hex), "%04x", m_uuid.uuid.uuid16); return std::string(hex) + "-0000-1000-8000-00805f9b34fb"; } // End 16bit UUID if (m_uuid.len == ESP_UUID_LEN_32) { // If the UUID is 32bit, pad correctly. char hex[9]; snprintf(hex, sizeof(hex), "%08x", m_uuid.uuid.uuid32); return std::string(hex) + "-0000-1000-8000-00805f9b34fb"; } // End 32bit UUID
原因はBLEUUID.cppの上記箇所で、1812などの4文字の短縮形の場合m_uuid.uuid.uuid16を利用するのですが、先頭に0000を追加するの忘れています。
まとめ
まだまだ不安定なESP32のBluetoothライブラリです。
- BLERemoteService::getCharacteristicsByHandle()関数
- BLERemoteService::getCharacteristics()関数
- BLEUUID::toString()関数
上記3件は修正のプルリクエストを出してみました。取り込まれるといいんですが、、、
getCharacteristics()は参照渡しにしたほうがシンプルなのですが、定義自体が変わってしまうので、最小限の修正で済むようにしています。
コメント