BLE writevalue help

Hello everyone, I have not been able to find how to write two or more bytes to a characteristics:
When I send xf101 in nrf connect it works.
HOW DO I WRITE THE SAME BYTES??
I am using esp32 board. Here are few lines in my code:
#include "BLEDevice.h"
static BLEUUID charDeviceDataUUID("583cb5b3-875d-40ed-9098-c39eb0c1983d");
THANK YOU

You have not provided enough information for us to help you.

Please read this
https://forum.arduino.cc/t/how-to-get-the-best-out-of-this-forum/679966

Post complete code using the code tags as described.

Here is the code, which needs some more modification once I am able to write to the mentioned characteristics: It does detect the service and both characteristics for now.
I would like to write the xf101 to the devicedata one after the start of notification.

#include "BLEDevice.h"`
`
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("1810");
// The characteristic of the remote service we are interested in.
static BLEUUID    charOximeterUUID("2A35");
static BLEUUID    charDeviceDataUUID("583cb5b3-875d-40ed-9098-c39eb0c1983d");
// The address of the target device (needed for connection when the device does not properly advertise services)
static BLEAddress WA2303("5c:d6:1f:c4:d9:fc");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristicOximeter;
static BLERemoteCharacteristic* pRemoteCharacteristicDeviceData;
static BLEAdvertisedDevice* myDevice;
static unsigned int connectionTimeMs = 0;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    // readable values 
    char output[45];
    for (int i = 0; i < length / 5; i++) {
      uint8_t value1 = pData[i*5 + 1]; 
      uint8_t value2 = pData[i*5 + 2]; 
      uint8_t bpm = pData[i*5 + 3];
      uint8_t spo2 = pData[i*5 + 4];
      sprintf(output, "SBP: %3u; DBP: %3u; HR: %2u", value1, value2, bpm);
      Serial.println(output);
    }

}

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

    // 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.
    pRemoteCharacteristicDeviceData = pRemoteService->getCharacteristic(charDeviceDataUUID);
    if (pRemoteCharacteristicDeviceData == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charDeviceDataUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.print(" - Found our characteristic ");
    Serial.println(charDeviceDataUUID.toString().c_str());

    // Read the value of the characteristic.
    if(pRemoteCharacteristicDeviceData->canRead()) {
      Serial.println(" - Our characteristic can be read.");
      std::string value = pRemoteCharacteristicDeviceData->readValue();
      byte buf[64]= {0};
      memcpy(buf,value.c_str(),value.length());
      Serial.print("The characteristic value was: (0x) ");
      for (int i = 0; i < value.length(); i++) {
        Serial.print(buf[i],HEX);
        Serial.print(" ");
      }
      Serial.println();
      Serial.println("Past value                  : (0x) 94 83 DB 50 A0 00");
    }
    else {
      Serial.println(" - Our characteristic cannot be read.");
    }

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

    // Read the value of the characteristic.
    if(pRemoteCharacteristicOximeter->canRead()) {
      Serial.println(" - Our characteristic can be read.");
      std::string value = pRemoteCharacteristicDeviceData->readValue();
      byte buf[64]= {0};
      memcpy(buf,value.c_str(),value.length());
      Serial.print("The characteristic value was: ");
      for (int i = 0; i < value.length(); i++) {
        Serial.print(buf[i],HEX);
        Serial.print(" ");
      }
      Serial.println();
    }
    else {
      Serial.println(" - Our characteristic cannot be read.");
    }

    if(pRemoteCharacteristicOximeter->canNotify()) {
      Serial.println(" - Our characteristic can notify us, registering notification callback.");
      pRemoteCharacteristicOximeter->registerForNotify(notifyCallback, true);
    }
    else {
      Serial.println(" - Our characteristic cannot notify us.");
    }

    if (pRemoteCharacteristicOximeter->canIndicate() == true) {
      Serial.println(" - Our characteristic can indicate.");
    } else {
      Serial.println(" - Our characteristic cannot indicate.");
    }

    // needed to start the notifications:
    pRemoteCharacteristicOximeter->readValue();
    const uint8_t notificationOn[] = {0x1, 0x0};
    pRemoteCharacteristicOximeter->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);

    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("\nBLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    Serial.print("Address: ");
    Serial.println(advertisedDevice.getAddress().toString().c_str());
    if (advertisedDevice.haveServiceUUID()) {
      Serial.println("Device has Service UUID");
      if (advertisedDevice.isAdvertisingService(serviceUUID)) {Serial.println("Device is advertising our Service UUID");}
      else {Serial.println("Device is not advertising our Service UUID");}
    }
    else {Serial.println("Device does not have Service UUID");}
    
    // We have found a device, let us now see if it contains the service we are looking for.
    if ((advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) || (advertisedDevice.getAddress().equals(WA2303))) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


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

  connectionTimeMs = millis();
  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 nothin 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) {


    if (pRemoteCharacteristicOximeter->canWrite()) {
      // Set the characteristic's value to be the array of bytes that is actually a string.
      String newValue = "Time since boot: " + String(millis()/1000);
      Serial.println("Setting new characteristic value to \"" + newValue + "\"");
      pRemoteCharacteristicOximeter->writeValue(newValue.c_str(), newValue.length());
    }
  }
  else {
    if (doScan) {
      BLEDevice::getScan()->start(0);  // this is just an example to start scan after disconnect, most likely there is better way to do it in arduino
    }
    else { // enable connects if no device was found on first boot
      if (millis() > connectionTimeMs + 6000) {
        Serial.println("Enabling scanning.");
        doScan = true;
      }
    }
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop

Please explain more. Is the value to be sent as an integer or a text string? What is the peripheral device going to do with the 0xF101?

Please explain more about the overall goals of the project and what you are trying to do with the pulse oximeter other than read the values it is putting out?

Do you want to write this value once when you find the characteristic and determine that it can notify like you do with the oximeter characteristic. You should be able to add a write statement.

 if(pRemoteCharacteristicOximeter->canNotify()) {
      Serial.println(" - Our characteristic can notify us, registering notification callback.");
      pRemoteCharacteristicOximeter->registerForNotify(notifyCallback, true);
    }

Or do you want to write to the characteristic in loop() every second like you do with

if (pRemoteCharacteristicOximeter->canWrite()) {
      // Set the characteristic's value to be the array of bytes that is actually a string.
      String newValue = "Time since boot: " + String(millis()/1000);
      Serial.println("Setting new characteristic value to \"" + newValue + "\"");
      pRemoteCharacteristicOximeter->writeValue(newValue.c_str(), newValue.length());
    }

Why are you writing a time string to the oximeter characterisitic which I would think is data to be read.

EDIT:
Can you provide a link for the pulse oximeter you are using and a reference for the communication protocol.

The service is a blood pressure measurement (I left names from a previous project.)
The machine starts when the target char receive two bytes: f101. At the end the first char will notify with new values: I have to make changes based on what I will get.
My goal now is to write to that char:
pRemoteCharacteristic->writeValue works only with one byte as I read.

Try
.writeValue( (uint8_t*)0xF101, 2);

I added the following after starting notification:

pRemoteCharacteristicDeviceData->writeValue( (uint8_t*)0xF101, 2);
Serial.println("Starting a measurement");

Nothing different, monitor does not even show the "Starting a measurement" after registering notification callback!

I added the following after starting notification:

What happens if you put the command in loop( ) after you are known to connect(where you write every second to a different characteristic) but with a boolean control variable to only write once?

Please clarify or provide example.

Some thing like this/

static boolean didItOnce = false;
if (didItOnce == false)

  {
    //do it
     didItOnce = true;
  }

I will be away from my computer for a while so wont be able to respond.
Without having the oximeter ble peripheral device to work with I am not really able to provide much help.

Serial monitor:
15:31:26.221 -> We are now connected to the BLE Server.
15:31:26.249 -> Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

Please post the code which led to the error. Is there a line you can comment out and make the error go away? Were all the previous serial print statements correct before the error?

If you are running the 1.8.19 ide you can use the esp32 exception decoder and get more information.

Here is the main loop:
// the writevalue line resolved the issue.

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 nothin 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) {



static boolean didItOnce = false;
if (didItOnce == false)

  {
    Serial.println("Starting a measurement");//do it
    //pRemoteCharacteristicDeviceData->writeValue( (uint8_t*)0xF101, 2);
    didItOnce = true;
  }


    if (pRemoteCharacteristicOximeter->canWrite()) {
      // Set the characteristic's value to be the array of bytes that is actually a string.
      String newValue = "Time since boot: " + String(millis()/1000);
      Serial.println("Setting new characteristic value to \"" + newValue + "\"");
      pRemoteCharacteristicOximeter->writeValue(newValue.c_str(), newValue.length());
    }
  }
  else {
    if (doScan) {
      BLEDevice::getScan()->start(0);  // this is just an example to start scan after disconnect, most likely there is better way to do it in arduino
    }
    else { // enable connects if no device was found on first boot
      if (millis() > connectionTimeMs + 6000) {
        Serial.println("Enabling scanning.");
        doScan = true;
      }
    }
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop

See if this variation works correctly

if (didItOnce == false)

  {
    Serial.println("Starting a measurement");//do it
    int16_t valToWrite = 0xF101;
    pRemoteCharacteristicDeviceData->writeValue((uint8_t*)&valToWrite, 2);
    //pRemoteCharacteristicDeviceData->writeValue( (uint8_t*)0xF101, 2);
    didItOnce = true;
  }

No crash, but no measurement started. Is there a way to check if those bytes were actually written to the char?

17:21:53.403 -> We are now connected to the BLE Server.

17:21:53.441 -> Starting a measurement

Do you see anything here that may help my case:

No. That is for a different library. I believe the syntax is correct for sending the two bytes of the command integer. The crash was caused by the BLE library wanting a pointer to an address and when we just had the 0xf101 value it was being seen as an invalid address to get a value from.

When you send the command from nrfConnect is it the integer, or a character array of the hex string?

If you have another esp32 , one thing you can do to test the syntax of the central is to write peripheral code which runs on an esp32 and mimics the actual device. Then you can see if the code is writing to the simulator.

I previously asked for any documentation you have on the blood pressure measurement device. Is there any part of the protocol which can acknowledge a command.

I'm also unclear about how the the time since boot is read by the device and what it is doing with that data?

I greatly appreciate your help and input.
Could not find the protocol you asked for, but I can see on nrfConnect that the target char is write/notify and when I look at the detailed communications with the device via bug report the following write on the char after notification enabled:
14 bytes of write request 0x12 handle 0x000f value 0301
10 bytes write response 0x13 handle 0x000f
then
14 bytes write request 0x12 handle 0x000f value f101
10 bytes write response 0x13 handle 0x000f

I only have to send f101 as bytes for the machine to start. Not sure what 0301 does.
THANKS AGAIN

Perhaps you can try send the 4 bytes with the 03 and 01.

if (didItOnce == false)

  {
    Serial.println("Starting a measurement");//do it
    uint8_t valToWrite[ ] ={03,01,f1,01};
    pRemoteCharacteristicDeviceData->writeValue(valToWrite, 4);
    didItOnce = true;
  }

There maybe endian issues, so you can also try
uint8_t valToWrite[ ] ={01,f1,01,03};

Perhaps you want to break up the 03,01 and f1.01 writes into two different statements with a delay in between.

It gave error for not declaring f1: I added 0x which went through, but no result.