Read Data from a peripheral with a central via BLE

Hallo liebe Arduino-User,
seit heute morgen um 8:00 (jetzt ist es 1:02) versuche ich 2 Nano BLE's miteinander zu verbinden und vom Peripheral gesendete Daten zu lesen. Die Aufgabe ist dieselbe wie in dem (ungelösten?) Thema von asfaafasfsaf beschrieben (IMU an Headunit) und wie in diesem denke ich ebenfalls ungelösten Beitrag: Einfache Anzeige von Messwerten via BLE Bluetooth - #8 by tru2020
Bei mir scheitert es im Endeffekt daran, dass ich keine Möglichkeit finde mit dem Central Werte vom Peripheral auszulesen, obwohl schon verbunden. In dem vielzitierten Gesture-Beispiel und dem zugehörigen Tutorial gibt es diesen Fall "Daten vom Peripheral lesen" genauso wenig wie in allen 5 Beispielen der Bibliothek. In allen Beispielen werden die Nanos immer nur verbunden und danach vom Central kontrolliert.
Aber der eigentliche Zweck ist doch, dass der Central im Peripheral Daten abfragt/ bzw. ausliest? Ohne diese wichtigste Funktion wäre BLE doch nutzlos?
Leider ist nirgends beschrieben wie das geht. In der BLE-Referenz ArduinoBLE - Arduino Reference sind zwar z. B. die Befehle .value und .readValue aufgeführt, aber auch damit werden die gesendeten Daten nicht gelesen. Meine Handyapp zeigt aber schon an, dass die Daten zumindest korrekt gesendet wurden...
Ich würde ja meinen Test-Code posten aber welche der hundert Varianten soll ich denn da einstellen?
Vielleicht hat ja jemand das Problem schon mal gelöst und kann ein Beispiel des fehlenden Codebaustein "Daten lesen" mal zeigen? Oder hat einen Link, wo ich ein funktionierendes Beispiel finde wie man Daten nachdem man verbunden ist auch lesen kann?
Beste Grüße Jochen

How you read the characteristic in the central will depend on how it is defined in the peripheral. Can you please post your peripheral sketch.

Can you successfully read the data from the peripheral sketch with a phone app such as Light Blue or nrfConnect?

Hi Cattledog,
yes of course. Here the periphere test code, derived out of the ButtonLED-Example. With nrf scan I can read the data (unfortunatly only in HEX) and switch the LED on/off. But with the second I only revceive "0" for the "statusButton" (LED should be the same result).


const int ledPin = LED_BUILTIN; // set ledPin to on-board LED

int32_t statusLED = 0;
int32_t statusButton = 0;

//Service and Characteristics
BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service
BLEByteCharacteristic ledCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);          // create switch characteristic and allow remote device to read and write
BLEByteCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);      // create button characteristic and allow remote device to get notifications

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

  pinMode(ledPin, OUTPUT);   

  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  BLE.setLocalName("ButtonLED");                      // set the local name peripheral advertises
  BLE.setAdvertisedService(ledService);               // set the UUID for the service this peripheral advertises
  ledService.addCharacteristic(ledCharacteristic);    // add the characteristics to the service
  ledService.addCharacteristic(buttonCharacteristic);
  BLE.addService(ledService);                         // add the service

  ledCharacteristic.writeValue(0);
  buttonCharacteristic.writeValue(0);

  BLE.advertise();                                     
  
  Serial.println("Bluetooth® device active, waiting for connections...");

}

int32_t t;
int32_t dt;

void loop() {
  
  delay(1000);
  
  dt = millis() - t ;  t  = millis();
  Serial.print (dt); Serial.print("\t");

  BLEDevice central = BLE.central();

  if (central) {
    Serial.print("Connected to central: "); Serial.print (central.address()); Serial.print("\t");Serial.print("\t");
  }       

  statusLED = ledCharacteristic.value();
    
  Serial.print("Status LED: "); Serial.print (statusLED); Serial.print("\t");  Serial.print("Status Button: "); Serial.println (statusButton);

  statusButton = statusButton + 1;
  buttonCharacteristic.writeValue(statusButton);
  //buttonCharacteristic.writeValue((int32_t)statusButton);         // I also tried this, but can not figure out if it is working without a propper working central

  if (ledCharacteristic.written()) {
    if (ledCharacteristic.value()) {
      Serial.println("LED on");
      digitalWrite(ledPin, HIGH);
    } else {
      Serial.println("LED off");
      digitalWrite(ledPin, LOW);
    }
  }
}

With nrf scan I can read the data

You have made the buttonCharacteristic a ByteCharacterisitic with Read and Notify properties. That looks OK.

Do you see the value of the buttonCharacteristic going between 0 and 255 in nrf connect?

int32_t statusButton = 0;
If you have a ByteCharacteristic, this should be typed as a byte.

But with the second I only revceive "0" for the "statusButton"

Please post your best effort at a Central to read the statusButton.

You may want to simplify the peripheral so that the ButtonCharacteristic is only read and remove the notify. It will eliminate any issues about subscribing when you have the notify property.

A library example program for a central which reads a value is the "Sensor Tag Button". You might get some ideas from there.

I'm leaving my computer for awhile so my response to your central won't be immediate. I'll also set up two Nano33BLE units so I can test the actual programs.

Hi cattledog,

with nrf connect I can read only Hex-Code.
The int32_t I will change for testing in "int" so it is the easiest way.
Unfortunatly I am also not at home till Monday evening so I can not test it.

In the evening I will try to take a second look at the sensor tag and try to understand what is different. Maybe my first look at it was not deep enough to understand, that it is also reading data.

My best guess in my opinion is the following code. I used the gesture-example as an code-basis. It works fine till the loop begins. Then it struggles, when starting to read the characteristic:

#include <ArduinoBLE.h>

const char* deviceServiceUuid = "19B10010-E8F2-537E-4F6C-D104768A1214";
const char* deviceServiceCharacteristicUuid = "19B10012-E8F2-537E-4F6C-D104768A1214";

//BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214");     
//BLEByteCharacteristic ledCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);         
//BLEByteCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

void setup() {
  Serial.begin(9600);
  while (!Serial);
  
  if (!BLE.begin()) {
    Serial.println("* Starting Bluetooth® Low Energy module failed!");
    while (1);
  }
  
  BLE.setLocalName("Nano 33 BLE (Central)"); 
  BLE.advertise();

  BLEDevice peripheral;                                   // Name ist hier "peripheral" !
  
  Serial.println("- Discovering peripheral device...");

  do
  {
    BLE.scanForUuid(deviceServiceUuid);
    peripheral = BLE.available();                         // Name gefundenes BLE ist hier "peripheral" ? 
  } while (!peripheral);
  
  if (peripheral) {
    Serial.println("* Peripheral device found!");
    Serial.print("* Device MAC address: ");       Serial.println(peripheral.address());    
    Serial.print("* Device name: ");              Serial.println(peripheral.localName());    
    Serial.print("* Advertised service UUID: ");  Serial.println(peripheral.advertisedServiceUuid());    Serial.println(" ");
    BLE.stopScan();
  }

  Serial.println("- Connecting to peripheral device...");

  if (peripheral.connect()) {
    Serial.println("* Connected to peripheral device!");            Serial.println(" ");
  } else {
    Serial.println("* Connection to peripheral device failed!");    Serial.println(" ");
    return;
  }

  Serial.println("- Discovering peripheral device attributes...");     // "BLE.discoverAttributes" ist in der Referenz nicht zu finden, was macht das wirklich, welchen Zweck haben die Attribute und wie passen sie in die Struktur? Brauche ich den Befehl überhaupt?
  if (peripheral.discoverAttributes()) {              
    Serial.println("* Peripheral device attributes discovered!");          Serial.println(" ");
  } else {
    Serial.println("* Peripheral device attributes discovery failed!");    Serial.println(" ");
    peripheral.disconnect();
    return;
  }

}

void loop() {
  
  //BLE.poll();

  BLECharacteristic buttonCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
  buttonCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
  
  int statusButton = buttonCharacteristic.value();

  Serial.print("StatusButton: "); Serial.print(statusButton);
}

You were close. I have left the statusButton value as a byte and did not modify the peripheral from post #3.

I made some minor changes to your central sketch

I made this variable global

BLEDevice peripheral; //make this global

The reading of the value was changed to this

//int statusButton = buttonCharacteristic.value();

  byte statusButton;
  buttonCharacteristic.readValue(statusButton);

Here's the revised sketch

#include <ArduinoBLE.h>

const char* deviceServiceUuid = "19B10010-E8F2-537E-4F6C-D104768A1214";
const char* deviceServiceCharacteristicUuid = "19B10012-E8F2-537E-4F6C-D104768A1214";

//BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214");
//BLEByteCharacteristic ledCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
//BLEByteCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

BLEDevice peripheral; //make this global

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

  if (!BLE.begin()) {
    Serial.println("* Starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  BLE.setLocalName("Nano 33 BLE (Central)");
  //BLE.advertise(); //central does not advertise
  Serial.println("- Discovering peripheral device...");

  do
  {
    BLE.scanForUuid(deviceServiceUuid);
    peripheral = BLE.available();                         // Name gefundenes BLE ist hier "peripheral" ?
  } while (!peripheral);

  if (peripheral) {
    Serial.println("* Peripheral device found!");
    Serial.print("* Device MAC address: ");       Serial.println(peripheral.address());
    Serial.print("* Device name: ");              Serial.println(peripheral.localName());
    Serial.print("* Advertised service UUID: ");  Serial.println(peripheral.advertisedServiceUuid());    Serial.println(" ");
    BLE.stopScan();
  }

  Serial.println("- Connecting to peripheral device...");

  if (peripheral.connect()) {
    Serial.println("* Connected to peripheral device!");            Serial.println(" ");
  } else {
    Serial.println("* Connection to peripheral device failed!");    Serial.println(" ");
    return;
  }

  Serial.println("- Discovering peripheral device attributes...");     // "BLE.discoverAttributes" ist in der Referenz nicht zu finden, was macht das wirklich, welchen Zweck haben die Attribute und wie passen sie in die Struktur? Brauche ich den Befehl überhaupt?
  if (peripheral.discoverAttributes()) {
    Serial.println("* Peripheral device attributes discovered!");          Serial.println(" ");
  } else {
    Serial.println("* Peripheral device attributes discovery failed!");    Serial.println(" ");
    peripheral.disconnect();
    return;
  }

}

void loop() {

  //BLE.poll();

  BLECharacteristic buttonCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
  buttonCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);

  //int statusButton = buttonCharacteristic.value();

  byte statusButton;
  buttonCharacteristic.readValue(statusButton);

  Serial.print("StatusButton: "); Serial.println(statusButton);
}

with nrf connect I can read only Hex-Code.

I prefer to use LightBlue as my basic phone app for testing peripheral sketches. It has more options than nrfConnect for displaying the data.

https://play.google.com/store/apps/details?id=com.punchthrough.lightblueexplorer&hl=en_US

https://apps.apple.com/us/app/lightblue/id557428110

The sketch will require some simple changes if you want to make a different peripheral sketch with a charactceristic of more than a byte.

Thanks a lot, I will test it tomorrow evening, when I am back home. But what changes do I need to transfer more than Byte? Nomally I should transfer a float or a int32_t, because the data will be the IMU values which I use for calculation of flight attitude every 30 ms. Without the precision, the results will drift a lot.

First thing to do is write the peripheral sketch you want and read the values with Light Blue ,or nrfConnect if you prefer.

Once you have the working peripheral you can design the central around reading the characteristics.

You will use something along these lines depending on what is read

float accelleration = 0.0;
characteristic.readValue(&accelleration, 4);

int16_t temperature = 0;
characteristic.readValue(&temperature, 2);

Thanks a lot for the comments! The transfer of int (Byte) is working perfect with your changes. Not so easy to understand the pointer logic.

But when I sent int16_t and float, the results in Light Blue with Little Endian in all of the syntax-variants I could find were only 1 Byte instead of 2 or 4 Bytes. Here the absolute minimum code I wrote:

#include <ArduinoBLE.h>

BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service
BLEByteCharacteristic temperatureCharacteristic("19B10013-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);      
int16_t statusTemperature = 0;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1);
  }
  
  BLE.setLocalName("ButtonLED");                      // set the local name peripheral advertises
  BLE.setAdvertisedService(ledService);               // set the UUID for the service this peripheral advertises
  ledService.addCharacteristic(temperatureCharacteristic);
  BLE.addService(ledService);                         // add the service
  BLE.advertise();                                     // start advertising
    
  Serial.println("Bluetooth® device active, waiting for connections...");
}

void loop() {
  
  BLEDevice central = BLE.central();

  if (central) {
    Serial.print("Connected to central: "); Serial.print (central.address()); Serial.print("\t");Serial.print("\t");
  }       
  
  statusTemperature = statusTemperature + 1;  

  temperatureCharacteristic.writeValue(statusTemperature);            
          //Result: Light Blue, Unsigned Little-Endian: 0...255 
  //temperatureCharacteristic.writeValue((int16_t)statusTemperature);   
          //Syntax like in the BLE Reference, result: Light Blue, Unsigned Little-Endian: 0...255 
  //temperatureCharacteristic.writeValue(&statusTemperature,2);         
          //Result IDE: No matching function for call to 'BLEByteCharacteristic::writeValue(int16_t*, int)'
  //temperatureCharacteristic.writeValue(statusTemperature,2);          
          //Result IDE: No matching function for call to 'BLEByteCharacteristic::writeValue(int16_t*, int)'

Serial.print("Status temperature: "); Serial.println(statusTemperature);
}

You have the wrong typed characteristic for an integer. You want either
BLEIntCharacteristic or BLEUnsignedIntCharacteristic.

#include <ArduinoBLE.h>

BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service
//BLEByteCharacteristic temperatureCharacteristic("19B10013-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEIntCharacteristic temperatureCharacteristic("19B10013-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

int16_t statusTemperature = 0;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  BLE.setLocalName("ButtonLED");                      // set the local name peripheral advertises
  BLE.setAdvertisedService(ledService);               // set the UUID for the service this peripheral advertises
  ledService.addCharacteristic(temperatureCharacteristic);
  BLE.addService(ledService);                         // add the service
  BLE.advertise();                                     // start advertising

  Serial.println("Bluetooth® device active, waiting for connections...");
}

void loop() {

  BLEDevice central = BLE.central();

  if (central) {
    Serial.print("Connected to central: "); Serial.print (central.address()); Serial.print("\t"); Serial.print("\t");
  }

  statusTemperature = statusTemperature + 1;

  temperatureCharacteristic.writeValue(statusTemperature);

  Serial.print("Status temperature: "); Serial.println(statusTemperature);
}

Ah thanks a lot! Now it is working. I was searching about 5 hours but did not come to that conclusion :wink:
Best regards, Jochen