微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决我试图用 ESP32 做 BLE,但 notifyCallback 不起作用

本文已翻译,所以可能写得不好。

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

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

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

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

  • 读取pmd控制uuid的值
  • 写入 pmd 控制 uuid
  • 读取 pmd 数据 uuid

以下程序尝试使用 M5stack 实现这一点。

interval

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

//===== 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,0x10,0x08,};
    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);
}
//===========================================

它甚至显示可以通知,但 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...
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,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.

解决方法

您提供的 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,8);
final long frameType = BleUtils.convertArrayToUnsignedLong(data,9,1);
final byte[] content = new byte[data.length - 10];
System.arraycopy(data,10,content,content.length);

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

类型 数据 说明
PmdMeasurementType 03 数据的类型,这里和请求的一样。 3 = PPI
时间戳 00 00 00 00 00 00 00 00 时间戳,不知何故这里是 0
帧类型 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 其余的原始数据称为内容并进一步使用

由于我们正在查看 PPI 数据,因此适用以下切换案例:

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 测量值,我们现在可以阅读以下内容:

原始数据 类型 价值
00 心率 0
34 02 峰峰值(毫秒) 564 毫秒
1e 00 峰峰值误差估计 30
07 -> 00000111 错误位、皮肤接触状态位、皮肤接触状态支持位 错误:true,皮肤接触:true,支持皮肤接触:true

由于您没有心率读数并且设置了错误位,我假设出现问题,可能您在测试时没有佩戴设备。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。