Understanding BLE characteristic.value()

So I’ve got a basic program that connects (successfully!) to my wireless pressure transducer. What I want to do is take the reading of that, which is 14 bytes, and put it into an array to convert it from hex to decimal so I can display and graph it.

I cannot for the life of me get the sensor data from characteristic.value() to be properly stored in some sort of array. I need this because bytes 0 and 1 are for temperature, 2-6 are for pressure etc. so I need to have them in some sort of data structure where I can split and manipulate them. I’m not 100% sure, but from the error messages, it looks like the characteristic.value() I am getting is a const unsigned char*, which is immutable. This is where I get stuck, as the only way I’ve gotten it to read correctly is by individually printing the characters of the output, with no way to store them. I’ve taken the snippet of code after my bluetooth device is connected and hooked up to the proper service and characteristic to read the data I want. See below:

printData(characteristic.value(), characteristic.valueLength());

//how do you get away without specifying an array length for data[] in the function declaration???
void printData(const unsigned char data[], int length) {
  for (int i = 0; i < length; i++) {
    unsigned char b = data[i];
    //not sure what this if statement does, but I think it adds 0 to small hex values that would otherwise be printed without them
    if (b < 16) {
      Serial.print("0");
    }
    //Prints each b value individually, I want to store the whole thing to a variable
    Serial.print(b, HEX);
  }
}

Welcome to the forum.

You used code tags. Congratulations. :slight_smile:

Can you please provide some more information?

  • Which Arduino board or module are you using?
  • Please provide a full example that shows the issue you have.
  • I suspect you use the ArduinoBLE library. Please confirm. If not please provide a link to the library e.g. on Github.
  • What is the wireless pressure transducer?
    -- Please provide a link to the product and datasheet.
    -- Is this a ready product or a module connected to another Arduino?
    -- Can you point me to the sensor data structure information?

I am using an Uno WiFi Rev 2
Full example is in the code tag. I have made some headway since my last post and can now get it to read using characteristic.readValue(). The documentation for this is not too clear on the difference between .readValue() and .value(). I’ve noted out my WIP integer to hexdecimal converting piece of code, so please ignore it.
Yes, I am using ArduinoBLE
The pressure transducer is a TE M5600. Datasheet is attached. The pertinent information is on page 19.
This is a standalone product not expressly made for Arduino.
See the datasheet for data structure.

#include <ArduinoBLE.h>

 
void setup() {
  Serial.begin(115200);
  while (!Serial);

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

    while (1);
  }

  Serial.println("BLE Central - Peripheral Explorer");

  // start scanning for peripherals that are advertising, returns true on success
  BLE.scan();
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available(); //BLEDevice object named peripherical created. Query for a discovered BLE device that was found during the scan

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

    // see if peripheral is a TESS M5600
    if (peripheral.localName() == "TESS 5600") {
      // stop scanning
      BLE.stopScan();
      // we call this to get all the parameters from the peripheral
      explorerPeripheral(peripheral);

      // peripheral disconnected, we are done
      while (1) {
        // do nothing
      }
    }
  }
}
//initial connection to peripheral object
void explorerPeripheral(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }
  // discover peripheral attributes
  Serial.println("Discovering attributes ...");
  //discover attributes is a percurosr that is needed before servicecount I guess?
  if (peripheral.discoverAttributes()) {
    Serial.println("Attributes discovered");
  } else {
    Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }
  int serviceCount = peripheral.serviceCount();

  Serial.print(serviceCount);
  Serial.println(" services discovered");
  BLEService tempService = peripheral.service("f000ab30-0451-4000-b000-000000000000");
  exploreService(tempService);
}

void exploreService(BLEService service) {
  BLECharacteristic tempChar = service.characteristic("f000ab31-0451-4000-b000-000000000000");
  exploreCharacteristic(tempChar);  
}

void exploreCharacteristic(BLECharacteristic characteristic) {
  while (characteristic.canRead()) { //true if characteristic is readable
    characteristic.read(); //
    if (characteristic.valueLength() > 0) { //the size of the value in bytes
      unsigned int dataHex[42] = {};
      characteristic.readValue(dataHex, 42);
      //unsigned int dataHex[14] = {};
      unsigned char charDataHex[14] = {};
      String s = "";
      for (int i = 0; i < 42; i++) {
        Serial.println(dataHex[i]);
        /*
        unsigned int dec = dataHex[i];
        Serial.println(dec);
        String answer = "";
        while (dec > 0) {
            int rem = dec % 16;
            if (rem > 9) {
                switch (rem) {
                   case 10: answer = "A" + answer; break;
                   case 11: answer = "B" + answer; break;
                   case 12: answer = "C" + answer; break;
                   case 13: answer = "D" + answer; break;
                   case 14: answer = "E" + answer; break;
                   case 15: answer = "F" + answer; break;
              }
            }
            else {
                answer = String(1, (char)rem-'0') + answer;
            }
            dec = dec / 16;
        }
        charDataHex[i] = dec;
        /*
        if (data[i] < 16) {
          for (int j = 42; j >= i; j--) {
            data[j] = data[j-1];
          data[i - 1] = 0;
          i++;
          }
        }
        */
        //Serial.print(charDataHex[i]);
        //Serial.print(" ");
      }
      Serial.println();
      delay(1000);
    }  
}
}

ENG_SS_M5600_U5600_Software_Manual_A5.pdf (1.06 MB)

Hi adoktor,

I obviously do not have this sensor, so it would have been hard to create a central without a way to test. Creating a peripheral is mostly doing the same thing, so I started with that. I downloaded the iOS app and my M6500 simulator based on the Arduino Nano 33 BLE Sense works quite well.

I did not want you to wait longer to finish a central example. So, here is what you need. I attach the full example for the peripheral as well.

Based on the datasheet you can create a union of a structure with the individual data and a byte array for the BLE characteristic. I do not have an Uno WiFi. Let us hope the syntax for packing structures is the same as for the Arduino Nano 33 BLE which has an ARM Cortex-M 32-bit processor.

Here is the one for the Data characteristic.

#define M5600_DATA_SIZE 14

typedef struct __attribute__( ( packed ) )
{
  int16_t T;
  int32_t P;
  int32_t Pmin;
  int32_t Pmax;
} m5600_data_t;

typedef union
{
  m5600_data_t values;
  uint8_t bytes[ M5600_DATA_SIZE ];
} m5600_data_ut;

m5600_data_ut m5600Data;

The BLE characteristic needs to be a generic one where you can define the size in bytes.

BLECharacteristic m5600DataCharacteristic( BLE_UUID_M5600_DATA, BLERead | BLENotify, sizeof m5600Data.bytes ); // on the peripheral side
BLECharacteristic m5600DataCharacteristic = peripheral.characteristic( BLE_UUID_M5600_DATA ); // on the central side

You can read and write the characteristic using the bytes array of the union called bytes. Read and write are identical.

m5600DataCharacteristic.writeValue( m5600Data.bytes, sizeof m5600Data.bytes );
m5600DataCharacteristic.readValue( m5600Data.bytes, sizeof m5600Data.bytes );

You can read and write the individual values using the values structure.

m5600Data.values.T = 1050; // This is 10.50 C
float temperature = m5600Data.values.T / 100.0;

BLE_M5600_Sim.ino (11.4 KB)

1 Like

This is awesome. I'm trying to parse out what code is used to simulate the M5600, and which bits I can drop in to mine to improve it. I'm eyeing this part here:

typedef struct __attribute__( ( packed ) )
{
  int16_t T;
  int32_t P;
  int32_t Pmin;
  int32_t Pmax;
} m5600_data_t;

Is this only valid for spoofing the M5600? Or can I put this in my central code to parse the data this way?

adoktor:
Is this only valid for spoofing the M5600? Or can I put this in my central code to parse the data this way?

You would use the union and not just the struct type. You need the array called bytes to read/write the characteristic and then retrieve the value "decoded" from the values part. The union overlays the byte array and the struct to the same memory address.

The code for the central is pretty much the same.

  1. Add the structures and union type at the beginning. Declare a variable of type m5600_data_ut named m5600Data.

  2. After you discovered the attributes get the characteristics from the peripheral. This is the part that is different between central and peripheral.

BLECharacteristic m5600DataCharacteristic  = peripheral.characteristic( BLE_UUID_M5600_DATA );
  1. Then read or write the characteristic. The call is the same in the central and peripheral code.
#include <ArduinoBLE.h>
#define BLE_UUID_M5600_DATA                       "F000AB31-0451-4000-B000-000000000000"
#define BLE_UUID_M5600_BATTERY_DATA               "F0002A19-0451-4000-B000-000000000000"


#define M5600_DATA_SIZE 14

typedef struct __attribute__( ( packed ) )
{
  int16_t T;
  int32_t P;
  int32_t Pmin;
  int32_t Pmax;
} m5600_data_t;

typedef union
{
  m5600_data_t values;
  uint8_t bytes[ M5600_DATA_SIZE ];
} m5600_data_ut;

m5600_data_ut m5600Data;

#define M5600_BATTERY_SIZE      2
#define DISCHARGING             0x00
#define CHARGING                0x01
#define DEFAULT_BATTERY_LEVEL   50

typedef struct __attribute__( ( packed ) )
{
  uint8_t batteryLevel;
  uint8_t status;
} m5600_battery_t;

typedef union
{
  m5600_battery_t values;
  uint8_t bytes[ M5600_BATTERY_SIZE ];
} m5600_battery_ut;

m5600_battery_ut m5600Battery;


void setup() {
  Serial.begin(115200);
  while (!Serial);

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

    while (1);
  }

  Serial.println("BLE Central - Peripheral Explorer");

  // start scanning for peripherals that are advertising, returns true on success
  BLE.scan();
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available(); //BLEDevice object named peripherical created. Query for a discovered BLE device that was found during the scan

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

    // see if peripheral is a TESS M5600
    if (peripheral.localName() == "TESS 5600") {
      // stop scanning
      BLE.stopScan();
      // we call this to get all the parameters from the peripheral
      if (peripheral.connect()) {
        Serial.println("Connected");
        if (peripheral.discoverAttributes()) {
          Serial.println("Attributes discovered");
        } else {
          Serial.println("Attribute discovery failed!");
          peripheral.disconnect();
          return;
    }
      } else {
        Serial.println("Failed to connect!");
        return;
      }
    }
    
    BLECharacteristic m5600DataCharacteristic  = peripheral.characteristic( BLE_UUID_M5600_DATA );
    BLECharacteristic m5600BatteryCharacteristic = peripheral.characteristic( BLE_UUID_M5600_BATTERY_DATA );
    while (peripheral.connected())
      if (m5600DataCharacteristic.canRead()) {
        m5600DataCharacteristic.read(); //
        if (m5600DataCharacteristic.valueLength() > 0) { //the size of the value in bytes
          m5600DataCharacteristic.readValue(m5600Data.bytes, 14);
            Serial.println(m5600Data.values.T/100.00);
            Serial.println(m5600Data.values.P/68947);
        }
      }
      if (m5600BatteryCharacteristic.canRead()) {
        m5600BatteryCharacteristic.read(); //
        if (m5600BatteryCharacteristic.valueLength() > 0) { //the size of the value in bytes
          m5600BatteryCharacteristic.readValue(m5600Battery.bytes, 2);
            Serial.println(m5600Battery.values.batteryLevel);            
        }  
      }
  }
}

I’m making great headway thanks to your help. I’ve got the data parsing out nicely with those structures - the only thing now is that the device will only output values to my serial monitor for a minute and then stop - is there something in my code that is facilitating that, or could it be a dreaded hardware issue with the sensor?

Edit: I’ve added some features to handle disconnects and immediately repeat the connect sequence, but it still will randomly stop after 20-30 minutes, and just fail to execute any more code, even though it should. Still stumped.

Another thing of note is that after it disconnects and reconnects, it spits about 30 readings in the space of 2 seconds, and then returns to the normal rate of about 1 reading per second - not sure why it behaves this way.

#include <ArduinoBLE.h>
#define BLE_UUID_M5600_DATA                       "F000AB31-0451-4000-B000-000000000000"
#define BLE_UUID_M5600_BATTERY_DATA               "F0002A19-0451-4000-B000-000000000000"
#define BLE_UUID_M5600_DATA_RATE                  "F000AB32-0451-4000-B000-000000000000"


#define M5600_DATA_SIZE 14

typedef struct __attribute__( ( packed ) )
{
  int16_t T;
  int32_t P;
  int32_t Pmin;
  int32_t Pmax;
} m5600_data_t;

typedef union
{
  m5600_data_t values;
  uint8_t bytes[ M5600_DATA_SIZE ];
} m5600_data_ut;

m5600_data_ut m5600Data;

#define M5600_BATTERY_SIZE      2
#define DISCHARGING             0x00
#define CHARGING                0x01
#define DEFAULT_BATTERY_LEVEL   50

typedef struct __attribute__( ( packed ) )
{
  uint8_t batteryLevel;
  uint8_t status;
} m5600_battery_t;

typedef union
{
  m5600_battery_t values;
  uint8_t bytes[ M5600_BATTERY_SIZE ];
} m5600_battery_ut;

m5600_battery_ut m5600Battery;

#define M5600_DATA_RATE_SIZE 12

typedef struct __attribute__( ( packed ) )
{
  uint32_t dataRate;
  uint32_t min;
  uint32_t max;
} m5600_data_rate_t;

typedef union
{
  m5600_data_rate_t values;
  uint8_t bytes[ M5600_DATA_RATE_SIZE ];
} m5600_data_rate_ut;

m5600_data_rate_ut m5600DataRate;




void setup() {
  Serial.begin(115200);
  while (!Serial);

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

    while (1);
  }

  Serial.println("Sensing Prototype");

  // start scanning for peripherals that are advertising, returns true on success
  Serial.println("Scanning for devices");
  BLE.scan();
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available();//BLEDevice object named peripherical created. Query for a discovered BLE device that was found during the scan
    // see if peripheral is a TESS M5600
  if (peripheral.localName() == "TESS 5600") {
    // stop scanning
    BLE.stopScan();
    // we call this to get all the parameters from the peripheral
    sensorLoop(peripheral);
    BLE.scan();
    Serial.println("CODE 1");
  }
}

void sensorLoop(BLEDevice peripheral) {
  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect. Trying again...");
    return;
  }
  
  if (peripheral.discoverAttributes()) {
    Serial.println("Attributes discovered");
  } else {
    Serial.println("Failed to discover attributes");
    return;
  }
  BLECharacteristic m5600DataCharacteristic  = peripheral.characteristic( BLE_UUID_M5600_DATA );
  BLECharacteristic m5600BatteryCharacteristic = peripheral.characteristic( BLE_UUID_M5600_BATTERY_DATA );
  BLECharacteristic m5600DataRateCharacteristic = peripheral.characteristic( BLE_UUID_M5600_DATA_RATE );
  //if (!m5600DataCharacteristic.subscribe()) {
  //  Serial.println("cannot subscribe");
  //} else {
  //  Serial.println("Subscribed");
  //}
  while (peripheral.connected()) {
    //if (m5600DataCharacteristic.canRead()) {
    m5600DataCharacteristic.read(); //
    if (m5600DataCharacteristic.valueLength() > 0) { //the size of the value in bytes
      m5600DataCharacteristic.readValue(m5600Data.bytes, 14);
        Serial.print("Temperature: ");
        Serial.print(m5600Data.values.T/100.00);
        Serial.println(" C");
        Serial.print("Pressure: ");
        Serial.print(m5600Data.values.P/68947.000);
        Serial.println(" PSI");  
    }
    }
    Serial.println("Disconnected from Loop");
}

I’ve cleaned up and simplified the code, as I am having disconnects every 10-30 seconds. Can anyone offer some insight into why this is happening? I don’t think something in the code is prompting it, and the sensor is sitting right next to my arduino, so I’m a little befuddled. I measured the battery that powers the sensor, and it is still at maximum.

Thanks @Klaus_K for the code. This helped me out a ton.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.