Nano IoT 33 BLE as Central, unable to read value of remote heart rate sensor

Hi,

I’m hoping this is obvious to those with more BLE experience. The ArduinoBLE library has only limited examples at the moment and I’m stuck trying to read the heart rate characteristic from a BLE heart rate sensor.

I’ve tried other sensors and other characteristics so I’m sure it is the fault of my code which is based on the sensortag example and the library reference online.

The serial monitor generates…

Found c3:c9:8a:45:b8:f7 ‘THR10-01953’ 180d
got hr device scan stopped
Connecting …
Connected
Discovering hr service …
HR Service discovered
, value 0x
, value 0x
, value 0x
, value 0x

Please can someone point me in the right direction? I feel like I’m so ‘nearly there’!

Matt

#include <ArduinoBLE.h>

void setup() {
  Serial.begin(9600);


  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  Serial.println("BLE Central");
  Serial.println("Turn on sensor and check batteries");

  // Scan for heart rate service

  BLE.scanForUuid("180d");

}

void loop() {
  // check if a peripheral has been discovered and allocate it
  BLEDevice hrperipheral = BLE.available();

  if (hrperipheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(hrperipheral.address());
    Serial.print(" '");
    Serial.print(hrperipheral.localName());
    Serial.print("' ");
    Serial.print(hrperipheral.advertisedServiceUuid());
    Serial.println();

    // Check if the peripheral is a hr monitor (uuid check)

    if (hrperipheral.advertisedServiceUuid() == "180d") {
      // stop scanning
      BLE.stopScan();
      Serial.println("got hr device scan stopped");

      // Start monitoring
      monitorSensor(hrperipheral);

      // peripheral disconnected, start scanning again
      BLE.scan();
    }
  }
  delay(500);
}

void printData(const unsigned char data[], int length) {
  for (int i = 0; i < length; i++) {
    unsigned char b = data[i];

    if (b < 16) {
      Serial.print("0");
    }

    Serial.print(b, HEX);
  }
}

void monitorSensor(BLEDevice hrperipheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");
  if (hrperipheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  Serial.println("Discovering hr service ...");
  if (hrperipheral.discoverService("2a37")) {
    Serial.println("HR Service discovered");
  } else {
    Serial.println("HR Attribute discovery failed.");
    hrperipheral.disconnect();

    while (1);
    return;
  }

  // retrieve the HR characteristic
  BLECharacteristic heartrateCharacteristic = hrperipheral.characteristic("2a37");



  while (hrperipheral.connected()) {

    heartrateCharacteristic.read();

    Serial.println(", value 0x");
    printData(heartrateCharacteristic.value(), heartrateCharacteristic.valueLength());
    delay(500);
  }


  delay(500);
  Serial.println("Sensor disconnected!");
}

Do you have an App on your phone like BLE Scanner where you can read the GATT entries?

Maybe the HR sensor does not want you to read the data. Maybe it is protected.

Hi Klaus, thanks for your help!

The sensor connects to and can be read by nRF Connect on my phone, and by any other app that uses HR sensors so seems not to require special pairing.

I have modified the sketch to report the .canRead .canSubscribe properties and all come back as false. Also the .value does not change the contents of the array.

Same with other sensors.

In sequence then (as per the examples)…

BLE.scanForUuid(“someUUID”) - works
BLEDevice hrperipheral = BLE.available(); - works
hrperipheral.advertisedServiceUuid() == “someUUID” - works
hrperipheral.connect() - works
hrperipheral.discoverService(“SomeSerriceUUID”) - works and finds attribute

But then…

BLECharacteristic heartrateCharacteristic = hrperipheral.characteristic(“2a63”);

produces a BLECharacteristic (heartrateCharacteristic) that is not readable, and cannot subscribe

I suspect I am using the wrong method to connect to the characteristic

#include <ArduinoBLE.h>




void setup() {
Serial.begin(9600);
delay(500);

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  Serial.println("BLE Central");
  Serial.println("Turn on sensor and check batteries");

  // Scan for heart rate service

  BLE.scanForUuid("180d");

}

void loop() {
  
  // check if a peripheral has been discovered and allocate it
  BLEDevice hrperipheral = BLE.available();

  if (hrperipheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(hrperipheral.address());
    Serial.print(" '");
    Serial.print(hrperipheral.localName());
    Serial.print("' ");
    Serial.print(hrperipheral.advertisedServiceUuid());
    Serial.println();

    // Check if the peripheral is a hr monitor (uuid check)

    if (hrperipheral.advertisedServiceUuid() == "180d") {
      // stop scanning
      BLE.stopScan();
      Serial.println("got hr device scan stopped");

      // Start monitoring
      monitorSensor(hrperipheral);

      // peripheral disconnected, start scanning again
      BLE.scan();
    }
  }
  delay(500);
}

void printData(const unsigned char data[], int length) {
  for (int i = 0; i < length; i++) {
    unsigned char b = data[i];

    if (b < 16) {
      Serial.print("0");
    }

    Serial.print(b, HEX);
  }
}

void monitorSensor(BLEDevice hrperipheral) {
//   connect to the peripheral
  Serial.println("Connecting ...");
  if (hrperipheral.connect()) {
    Serial.println("Connected");

  } else {
    Serial.println("Failed to connect!");
    return;
  } 

  // discover peripheral attributes
  Serial.println("Discovering hr service ...");
  if (hrperipheral.discoverService("2a37")) {
    Serial.println("HR Service discovered");
       
    
  } else {
    Serial.println("HR Attribute discovery failed.");
    hrperipheral.disconnect();

    while (1);
    return;
  }
  
  // retrieve the HR characteristic
  BLECharacteristic heartrateCharacteristic = hrperipheral.characteristic("2a37");

 Serial.println("properties: ");
            Serial.println(heartrateCharacteristic.properties());

if (!heartrateCharacteristic.canWrite()){
   Serial.println("canWrite False");}

if (!heartrateCharacteristic.canSubscribe()){
   Serial.println("canSubscribe False");}


if(!heartrateCharacteristic.canRead()){
      Serial.println("canRead False");}


if(!heartrateCharacteristic.valueUpdated()){
      Serial.println("valueUpdated False");}
      
  while (hrperipheral.connected()) {
//if (heartrateCharacteristic.valueUpdated()) {

uint16_t holdvalues[6] = {1,2,3,4,5,6} ;

heartrateCharacteristic.readValue(holdvalues, 6);
byte rawValue0 = holdvalues[0];
byte rawValue1 = holdvalues[1];
byte rawValue2 = holdvalues[2];
byte rawValue3 = holdvalues[3];
byte rawValue4 = holdvalues[4];
byte rawValue5 = holdvalues[5];

    Serial.print("value 0: ");
    Serial.print(rawValue0);
    Serial.print("  value 1: ");
    Serial.print(rawValue1);
    Serial.print("  value 2: ");
    Serial.print(rawValue2);
      Serial.print("  value 3: ");
    Serial.print(rawValue3);
    Serial.print("  value 4: ");
    Serial.print(rawValue4);
    Serial.print("  value 5: ");
    Serial.println(rawValue5);  
   
   // printData(heartrateCharacteristic.value(), heartrateCharacteristic.valueLength());
    delay(500);
  }


  delay(500);
  Serial.println("Sensor disconnected!");
}

To confirm the hardware works ok, I can subscribe to the key attribute on a TI keyfob and receive notifications and values when the buttons are pressed. Could there be a bug in the ArduinoBLE library preventing subscription to certain characteristics/attributes?

With BLE you are supposed to be able to discover the attributes, thats why you can see the data you have so far, but for the values there are three kinds of permissions.

  • Access permission: controls Readable, Whriteable or both

  • Authentication: required or not, this can be initiated from the client (your Arduino in this case), but you need to find out how this is done with the library you are using, and if it is implemented

  • Authorization: this is a server (the HR monitor) thing, there is nothing the client can do, the server can ether reject the request or there is a procedure on how to add the client to the list of authorized devices

I think you need to figure out how to authenticate your client. The HR monitor likely wants YOU to be able to get the data, but not everyone one the road should be able to read your heart rate. :slight_smile:

Hi Klaus, absolutely agree likely to be authentication related but shouldn't the library handle non protected devices?

Some things make me think this could be a library issue - first being that I can connect the sensor to anything else without an authentication process (that I can see anyhow).

Others have been able to get similar sensors working with older CurieBLE library on the 101.

A virtual heart rate sensor in nRF connect also will not subscribe on my code.

There is this bug report BLECharacteristic::subscribe() fails with iTag · Issue #20 · arduino-libraries/ArduinoBLE · GitHub which sounds familiar - my code works with an old keyfob too but not any recent sensor.

Sadly I don't know how to implement the 'fix' that is described in the post - the code in the library at BLERemoteCharacteristic.cpp is rather beyond me tbh!

I will spend some more time learning about BLE authentication!

Thanks again for your thoughts.

Matt

Mystery solved. Apparently the official ArduinoBLE library upto and including 1.1.2 does NOT support authentication in any form yet. This presumably includes automatic methods that don't need user input hence old sensors work, new don't.

Solved my problem by routing through a NPE CABLE device (ANT+ to BLE) which allows the library to connect without authentication.

Thank you for the update.