Help with BLE parsing, reading of the stream

Hello, I am reasonably new to the world of arduino, i used to code ZX Spectums. I am trying to make a screen that reads the bluetooth from my treadmill. I am using the BLE notify example to connect. It connects fine and reliably.

It finds the Service and the characteristic and reads the response. This is where I am stuck.

I get a ? question mark when I Serial.print with no readable text to interrogate. If I connect to the treadmill with an app on my phone it also gets the ?mark but it also gives a string of HEX. The speed is derived from the these 4 characters in the attached image and bold below:

0x8C05960084C5..............

The 4 digits change when I dial up the speed. I have worked out how to take the hex code and convert it to the speed.

It would be great if someone could help turn the question marks into man readable text and pull out the 4 characters. Its beyond my knowledge but willing to learn.

`#include "BLEDevice.h"
//#include "BLEScan.h"
#define DEF_BLE_ADDR "BLUETOOTHADDRESS"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("1826");  //Service
static BLEUUID    charUUID("2ACD");  //Characteristic

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *myDevice;

// Callback function to handle notifications
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(" of data length ");
 Serial.println(length);
 Serial.print("data: ");


String str = (char*)pData;



  Serial.println(str);
  //Serial.println((char*)str, HEX);
  Serial.println((char*)pData);
// Serial.write(pData, length);


 
}

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");
  pClient->setMTU(517);  //set client to request maximum MTU from server (default is 23 otherwise)

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

  // Obtain a reference to the characteristic in the service of the remote BLE server.
  pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
  if (pRemoteCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(charUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }
  Serial.println(" - Found our characteristic");

  // Read the value of the characteristic.
  if (pRemoteCharacteristic->canRead()) {
    String value = pRemoteCharacteristic->readValue();
    Serial.print("The characteristic value was: ");
    Serial.println(value.c_str());
  }

  if (pRemoteCharacteristic->canNotify()) {
    // Register/Subscribe for notifications
    pRemoteCharacteristic->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 nothing 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) {
    String newValue = "Time since boot: " + String(millis() / 1000);
    //Serial.println("Setting new characteristic value to \"" + newValue + "\"");

    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  } else if (doScan) {
    BLEDevice::getScan()->start(0);  // this is just example 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`

What happens is that Serial.print() tries to interpret the raw bytes as characters, but the treadmill is not sending text, it is sending binary data. Non-printable bytes show up as ?.

Your phone app shows you the same raw data but also gives the underlying hexadecimal representation, which is why you could spot the changing 4-byte pattern like 8C 05 96 00 84 C5. in your example.

so what's the formula?

In Arduino, you need to treat the characteristic value as a byte array, not as a String .

In your notifyCallback you do this:

String str = (char*)pData;
Serial.println(str);
Serial.println((char*)pData);

That treats the incoming binary data as if it were a null-terminated C string. The treadmill is sending raw bytes, not ASCII text, so the Arduino is just showing ? or garbage as they are not ASCII values.

Instead, you should print the bytes in hex (or decimal) so you can see the real values. You could replace your notifyCallback with something like:

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(" of data length ");
  Serial.println(length);
  Serial.print("data: ");

  for (size_t i = 0; i < length; i++) {
    if (pData[i] < 16) Serial.write('0');
    Serial.print(pData[i], HEX);
    Serial.write(' ');
  }
  Serial.println();
}

(typed here, untested)

Now you should get the same hex string your phone app shows, like 8C 05 96 00 84 C5 ....
Now that you have the bytes, you can inject your magic formula to build the speed information.

Superb thank you... code copied and paste worked first time..

Glad it helped