imaimai17468
imaimai17468

Reputation: 13

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

This text has been translated, so it may be poorly written.

Hello, I am trying to use M5stack to get the acceleration and ECG of my Polar OH1+, but the notifyCallback is not working.

I found some code in Python that did something similar, so I followed the same procedure to make the connection.

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

According to this, I found out that this is the procedure to follow.

The following program tries to achieve that with 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);
}
//===========================================

When this was done, the serial monitor display looked like the following.

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.

It even shows Can Notify, but notyfiCallback does not work. Can you please tell me why it is not working?

Also, the byte sequence that is written to pmd control is supposed to be based on this page.

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

Also, this is the output of the serial monitor when the CoreDebugLebel of the ESP32 is set to Debug in the ArduinoIDE.

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.

Upvotes: 1

Views: 3267

Answers (1)

Michael Kotzjan
Michael Kotzjan

Reputation: 2673

The specification you provided shows the correct flow of messages needed to start streaming. You have to start by requesting the streaming settings first.

For requesting the ppi settings just send 0x01 0x03 to the P=MD control point.

While looking at the source code of the polar-ble-sdk I found this in BlePMDClient.java (I added comments to the relevant parts):

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;
        }
    }
}

Now we know how to convert the 6 bytes of the data frame, but the data you receive has mostly 40 bytes and looks like this:

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

The conversion from the raw data to the different data frames happens in Line 888 of the same file.

The first 10 bytes of the received data is extracted here:

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);

Looking at the data you received we now know the following:

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

Since we are looking at PPI data the following case of the switch-case applies:

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;

Only a frametype of zero is allowed here and this is exactly what you received. You sometimes received 40 bytes and other times only 16. Since the first 10 are not part of the actual data we can now assume that the 40 bytes contain 5 PPI measurements (5 * 6 bytes = 30 bytes) and the 16 bytes contain only one measurement.

If we take a look at the first PPI measurement in the dataset you received we can now read the following:

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

Since you do not have a heart rate reading and the error bit is set I would assume something went wrong, maybe you did not wear the device while testing.

Upvotes: 1

Related Questions