我尝试用 ESP32 做 BLE,但是 notifyCallback 不起作用

I tried to do BLE with ESP32, but the notifyCallback doesn't work

这段文字已经过翻译,所以可能写得不好。

您好,我正在尝试使用 M5stack 获取我的 Polar OH1+ 的加速度和心电图,但 notifyCallback 无法正常工作。

我在 Python 中发现了一些执行类似操作的代码,因此我按照相同的步骤建立连接。

https://github.com/pareeknikhil/biofeedback/blob/master/Polar%20Device%20Data%20Stream/Accelerometer/main.py

据此,我发现这是要遵循的程序。

下面的程序试图用 M5stack 来实现。

//===== header file & define & global variable ===== 
#include"BLEDevice.h"

boolean doConnect = false;
volatile boolean isConnected = false;
boolean doScan = false;

BLEUUID pmd_serviceUUID ("FB005C80-02E7-F387-1CAD-8ACD2D8DF0C8");
BLEUUID pmd_dataUUID ("FB005C82-02E7-F387-1CAD-8ACD2D8DF0C8");
BLEUUID pmd_ctrlUUID ("FB005C81-02E7-F387-1CAD-8ACD2D8DF0C8");
BLEAdvertisedDevice* myDevice;
BLEClient* pClient;

String SensorName = "Polar OH1 87C4C425"; // SDから読み取る
//===========================================


//===== class & function ====================
class MyClientCallback: public BLEClientCallbacks{
  void onConnect(BLEClient* pclient){ }
  void onDisconnect(BLEClient* pclient){
    isConnected = false;
    Serial.println("onDisconnetct");  
  }
};

// BLEデバイスを検索する
class MyAdvertisedDeviceCallback: public BLEAdvertisedDeviceCallbacks{
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.println(advertisedDevice.toString().c_str());
    // 指定デバイスなら接続する
    if(SensorName.equals(advertisedDevice.getName().c_str())){
      Serial.print("Connect BLE device : ");
      Serial.println(advertisedDevice.toString().c_str());
      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;
    }
  }
};

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(" of data length ");
  Serial.println(length);
  Serial.print("data: ");
  for (int i = 0; i <= length - 1; i++) {
    Serial.print(String(*(pData + i), HEX));
    Serial.print(" ");
  }
  Serial.println();
}

bool connectToServer(){
  Serial.print("connection to : ");
  Serial.println(myDevice->getAddress().toString().c_str());
  pClient = BLEDevice::createClient();
  Serial.println(" - Created client");
  pClient->setClientCallbacks(new MyClientCallback() );
  pClient->connect(myDevice);
  Serial.println(" - Created to server");

  BLERemoteService* pRemoteService = pClient->getService(pmd_serviceUUID);
  if (pRemoteService == nullptr) {
     Serial.println("Failed to find our service UUID: ");
     Serial.println(pmd_serviceUUID.toString().c_str());
     pClient->disconnect();
     return false;
  }
  Serial.print(" - Found service ( ");
  Serial.print(pmd_serviceUUID.toString().c_str());
  Serial.println(" )");

  static BLERemoteCharacteristic* pControlCharacteristic;
  pControlCharacteristic = pRemoteService->getCharacteristic(pmd_ctrlUUID);
  if( pControlCharacteristic == nullptr ){
    Serial.print("Failed to find out characteristic UUID : ");
    Serial.println(pmd_ctrlUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }

  Serial.println("characteristics");
  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();
  }

  static BLERemoteCharacteristic* pDataCharacteristic;
  pDataCharacteristic = pRemoteService->getCharacteristic(pmd_dataUUID);
    if( pDataCharacteristic == nullptr ){
    Serial.print("Failed to find out characteristic UUID : ");
    Serial.println(pmd_dataUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }
  Serial.print(" - Add Notify ( ");
  Serial.print(pmd_dataUUID.toString().c_str());
  Serial.println(" )");
  
  if(pDataCharacteristic->canNotify()){
    std::string value = pControlCharacteristic->readValue();
    
    uint8_t data[14] = {0x02,
                        0x02,
                        0x00,
                        0x01,
                        0xC8,
                        0x00,
                        0x01,
                        0x01,
                        0x10,
                        0x00,
                        0x02,
                        0x01,
                        0x08,
                        0x00,};
    pControlCharacteristic->writeValue(data,14,false);
    Serial.println(" - Set value");
    
    Serial.println(" - Can Notify");
    pDataCharacteristic->registerForNotify(notifyCallback);
  }
  
  isConnected = true;
  return true;
}
//===========================================


//===== setting =============================
void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");
  static BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallback());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
}
//===========================================


//===== main ================================
void loop(){
  if(doConnect==true){
    if(connectToServer()){
      Serial.println("now connected to BLE Server.");
    }else{
      Serial.println("faild to connect to the server.");
    }
    doConnect = false;
  }

  if( isConnected == false && doScan == true ) BLEDevice::getScan()->start(0);
  delay(1000);
}
//===========================================

完成后,串口监视器显示如下。

Starting Arduino BLE Client application...
Name: , Address: 5a:f3:e5:97:72:be, manufacturer data: 060001092006319b0f7cab7c18b3ad1f11d4f6475cf638678bd51cf02d
Name: , Address: 33:20:7d:41:97:52, serviceUUID: 0000fd6f-0000-1000-8000-00805f9b34fb
Name: , Address: 11:27:f2:c5:92:98, manufacturer data: 0600010920029ac7ae5b723ad210a6450c28780429ca56a82bae79a076
Name: Polar OH1 87C4C425, Address: a0:9e:1a:87:c4:c4, manufacturer data: 6b00720851a77b02000000007a01053b005d, serviceUUID: 0000180d-0000-1000-8000-00805f9b34fb, serviceUUID: 0000feee-0000-1000-8000-00805f9b34fb
Connect BLE device : Name: Polar OH1 87C4C425, Address: a0:9e:1a:87:c4:c4, manufacturer data: 6b00720851a77b02000000007a01053b005d, serviceUUID: 0000180d-0000-1000-8000-00805f9b34fb, serviceUUID: 0000feee-0000-1000-8000-00805f9b34fb
connection to : a0:9e:1a:87:c4:c4
 - Created client
 - Created to server
 - Found service ( fb005c80-02e7-f387-1cad-8acd2d8df0c8 )
characteristics
 - characteristic UUID : fb005c81-02e7-f387-1cad-8acd2d8df0c8 Broadcast:X Read:O WriteNoResponse:X Write:O Notify:X Indicate:O
 - characteristic UUID : fb005c82-02e7-f387-1cad-8acd2d8df0c8 Broadcast:X Read:X WriteNoResponse:X Write:X Notify:O Indicate:X
 - Add Notify ( fb005c82-02e7-f387-1cad-8acd2d8df0c8 )
 - Set value
 - Can Notify
now connected to BLE Server.

它甚至显示可以通知,但notyfiCallback 不起作用。你能告诉我为什么它不起作用吗?

另外,写入pmd控件的字节序列应该是基于这个页面的。

https://github.com/polarofficial/polar-ble-sdk/blob/master/technical_documentation/Polar_Measurement_Data_Specification.pdf

另外,这是在ArduinoIDE中ESP32的CoreDebugLebel设置为Debug时串口监视器的输出。

Starting Arduino BLE Client application...
[D][BLEAdvertisedDevice.cpp:472] setRSSI(): - setRSSI(): rssi: -83
[D][BLEAdvertisedDevice.cpp:292] parseAdvertisement(): Type: 0xff (), length: 29, data: 0600010920021a14c867a55d256f3c35b8286b3c90bfe1fa95ce255ccb
[D][BLEAdvertisedDevice.cpp:449] setManufacturerData(): - manufacturer data: 0600010920021a14c867a55d256f3c35b8286b3c90bfe1fa95ce255ccb
Name: , Address: 7e:0a:d2:c6:94:26, manufacturer data: 0600010920021a14c867a55d256f3c35b8286b3c90bfe1fa95ce255ccb
[D][BLEAdvertisedDevice.cpp:472] setRSSI(): - setRSSI(): rssi: -84
[D][BLEAdvertisedDevice.cpp:292] parseAdvertisement(): Type: 0xff (), length: 29, data: 06000109200670be05b68e63a90d3ca0a091e9c4982a95f8f08888583f
[D][BLEAdvertisedDevice.cpp:449] setManufacturerData(): - manufacturer data: 06000109200670be05b68e63a90d3ca0a091e9c4982a95f8f08888583f
Name: , Address: 71:56:ce:5b:12:af, manufacturer data: 06000109200670be05b68e63a90d3ca0a091e9c4982a95f8f08888583f
[D][BLEAdvertisedDevice.cpp:472] setRSSI(): - setRSSI(): rssi: -40
[D][BLEAdvertisedDevice.cpp:292] parseAdvertisement(): Type: 0x01 (), length: 1, data: 04
[D][BLEAdvertisedDevice.cpp:292] parseAdvertisement(): Type: 0xff (), length: 15, data: 6b00720851a77b02000000003f0043
[D][BLEAdvertisedDevice.cpp:449] setManufacturerData(): - manufacturer data: 6b00720851a77b02000000003f0043
[D][BLEAdvertisedDevice.cpp:292] parseAdvertisement(): Type: 0x09 (), length: 18, data: 506f6c6172204f4831203837433443343235
[D][BLEAdvertisedDevice.cpp:461] setName(): - setName(): name: Polar OH1 87C4C425
Name: Polar OH1 87C4C425, Address: a0:9e:1a:87:c4:c4, manufacturer data: 6b00720851a77b02000000003f0043
Connect BLE device : Name: Polar OH1 87C4C425, Address: a0:9e:1a:87:c4:c4, manufacturer data: 6b00720851a77b02000000003f0043
connection to : a0:9e:1a:87:c4:c4
 - Created client
[I][BLEDevice.cpp:614] addPeerDevice(): add conn_id: 0, GATT role: client
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
 - Found service ( fb005c80-02e7-f387-1cad-8acd2d8df0c8 )
[D][BLERemoteService.cpp:193] retrieveCharacteristics(): Found a characteristic: Handle: 63, UUID: fb005c81-02e7-f387-1cad-8acd2d8df0c8
[D][BLERemoteCharacteristic.cpp:293] retrieveDescriptors(): Found a descriptor: Handle: 64, UUID: 00002902-0000-1000-8000-00805f9b34fb
[D][BLERemoteService.cpp:193] retrieveCharacteristics(): Found a characteristic: Handle: 66, UUID: fb005c82-02e7-f387-1cad-8acd2d8df0c8
[D][BLERemoteCharacteristic.cpp:293] retrieveDescriptors(): Found a descriptor: Handle: 67, UUID: 00002902-0000-1000-8000-00805f9b34fb
characteristics
 - characteristic UUID : fb005c81-02e7-f387-1cad-8acd2d8df0c8 Broadcast:X Read:O WriteNoResponse:X Write:O Notify:X Indicate:O
 - characteristic UUID : fb005c82-02e7-f387-1cad-8acd2d8df0c8 Broadcast:X Read:X WriteNoResponse:X Write:X Notify:O Indicate:X
 - Add Notify ( fb005c82-02e7-f387-1cad-8acd2d8df0c8 )
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown

 - Can Notify
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:177] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
now connected to BLE Server.

您提供的 specification 显示了开始流式传输所需的正确消息流。您必须首先请求流媒体设置。

要请求 ppi 设置,只需将 0x01 0x03 发送到 P=MD 控制点。

在查看 polar-ble-sdk 的源代码时,我在 BlePMDClient.java 中找到了这个(我在相关部分添加了注释):

public static class PpiData {
    public static class PPSample {
        public final int hr;
        public final int ppInMs;
        public final int ppErrorEstimate;
        public final int blockerBit;
        public final int skinContactStatus;
        public final int skinContactSupported;

        public PPSample(@NonNull byte[] data) {
            // Convert data[0] to heart rate using a bitwise AND operation
            // with 0xFF as long
            hr = (int) ((long) data[0] & 0xFFL);
            // Convert data[0] and data[1] to time between peaks in ms using
            // BleUtils.convertArrayToUnsignedLong
            ppInMs = (int) BleUtils.convertArrayToUnsignedLong(data, 1, 2);
            // Same for the error estimate
            ppErrorEstimate = (int) BleUtils.convertArrayToUnsignedLong(data, 3, 2);
            // The last byte contains multiple flags that get read by
            // bitshifting. First bit is the blockerBit, second the
            // skinContactStatus, third skinContactSupported
            blockerBit = data[5] & 0x01;
            skinContactStatus = (data[5] & 0x02) >> 1;
            skinContactSupported = (data[5] & 0x04) >> 2;
        }
    }

    public final List<PPSample> ppSamples = new ArrayList<>();
    public final long timeStamp;

    public PpiData(@NonNull byte[] data, long timeStamp) {
        int offset = 0;
        this.timeStamp = timeStamp;
        while (offset < data.length) {
            final int finalOffset = offset;
            ppSamples.add(new PPSample(Arrays.copyOfRange(data, finalOffset, finalOffset + 6)));
            offset += 6;
        }
    }
}

现在我们知道如何转换数据帧的 6 个字节,但是您收到的数据大部分是 40 个字节,看起来像这样:

03 00 00 00 00 00 00 00 00 00 00 34 02 1e 00 07 00 f2 01 1e 00 07 00 05 03 1e 00 07 00 63 03 1e 00 06 00 8e 03 1e 00 06

从原始数据到不同数据帧的转换发生在同一文件的 Line 888 中。

此处提取接收数据的前10个字节:

PmdMeasurementType type = PmdMeasurementType.fromId(data[0]);
final long timeStamp = BleUtils.convertArrayToUnsignedLong(data, 1, 8);
final long frameType = BleUtils.convertArrayToUnsignedLong(data, 9, 1);
final byte[] content = new byte[data.length - 10];
System.arraycopy(data, 10, content, 0, content.length);

查看您收到的数据,我们现在知道以下内容:

Type Data Description
PmdMeasurementType 03 The type of data, here it is the same as the requested. 3 = PPI
Timestamp 00 00 00 00 00 00 00 00 The timestamp, somehow 0 here
Frametype 00 Type of data that follows
content 00 34 02 1e 00 07 00 f2 01 1e 00 07 00 05 03 1e 00 07 00 63 03 1e 00 06 00 8e 03 1e 00 06 The rest of the raw data is called content and gets used further

由于我们正在查看 PPI 数据,因此适用以下 switch-case 情况:

case PPI:
    if (frameType == 0) {
        RxUtils.emitNext(ppiObservers, object -> object.onNext(new PpiData(content, timeStamp)));
    } else {
        BleLogger.w(TAG, "Unknown PPI frame type received");
    }
    break;

此处只允许帧类型为零,这正是您收到的。您有时会收到 40 个字节,而其他时候只收到 16 个。由于前 10 个不是实际数据的一部分,我们现在可以假设 40 个字节包含 5 个 PPI 测量值(5 * 6 个字节 = 30 个字节),而 16 个字节仅包含一个测量。

如果我们查看您收到的数据集中的第一个 PPI 测量值,我们现在可以阅读以下内容:

Raw data Type Value
00 Heart rate 0
34 02 Peak to peak in ms 564 ms
1e 00 Peak to peak error estimate 30
07 -> 00000111 Error bit, skin contact status bit, skin contact status supported bit Error: true, skin contact: true, skin contact supported: true

由于您没有心率读数并且设置了错误位,我认为出了点问题,也许您在测试时没有佩戴设备。