esp32-snippets icon indicating copy to clipboard operation
esp32-snippets copied to clipboard

BLERemoteCharacteristic::writeValue fails with '0x00'

Open ghost opened this issue 7 years ago • 12 comments

SdkVersion: v3.1-dev-239-g1c3dd23f-dirty Arduino IDE 1.8.5 with BT sources from esp32-snippets/cpp_utils/BLE*.* Example: ESP32_BLE_Arduino/examples/BLE_client/

Bug in the method: BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) in BLERemoteCharacteristic.cpp

data is a bytearray with binary data.

writeValue calls writeValue(std::string newValue, bool response) . Here .length() gets the length of the string up to the first \0 (0x00) -> if the binary data includes 0x00's the length is wong and only the first part of the bytearray is send...

Possible solution: Add a writeBinary fnc

void BLERemoteCharacteristic::writeBinary(uint8_t* data,size_t length, bool response) {
	ESP_LOGD(LOG_TAG, ">> writeBinary(), length: %d", length());

	// Check to see that we are connected.
	if (!getRemoteService()->getClient()->isConnected()) {
		ESP_LOGE(LOG_TAG, "Disconnected");
		//////////throw BLEDisconnectedException();
	}

	m_semaphoreWriteCharEvt.take("writeValue");

	// Invoke the ESP-IDF API to perform the write.
	esp_err_t errRc = ::esp_ble_gattc_write_char(
		m_pRemoteService->getClient()->getGattcIf(),
		m_pRemoteService->getClient()->getConnId(),
		getHandle(),
		length,
		data,
		response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP,
		ESP_GATT_AUTH_REQ_NONE
	);

	if (errRc != ESP_OK) {
		ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
		return;
	}

	m_semaphoreWriteCharEvt.wait("writeBinary");

	ESP_LOGD(LOG_TAG, "<< writeBinary");
} // writeBinary

and redirect or correct the writeValue() -Pit

ghost avatar Mar 01 '18 18:03 ghost

Integrating this change would be great, because this could explain and solve the problem I have in my project. I need to write the value 0x00000000 but it doesn't work as expected.

RobsyRocket avatar Mar 02 '18 10:03 RobsyRocket

It is my understanding that std::string in C++ standard library doesn't use the NULL character string termination convention but instead a std::string is an array of bytes of a given explicit length. Do you have a calling example where it is failing?

For example:

BLERemoteCharacteristic *pMyCharacteristic;
...
pMyCharacteristic->writeValue((char*)"AB\0DE", 5);

Should set the value to be the 5 bytes 'A', 'B', 0x00, 'D' and 'E'.

nkolban avatar Mar 10 '18 23:03 nkolban

I can follow your understanding, it's also my estimation, but the reality teachs me something else.

char ON[] = {0x42, 0x03, 0x4E, 0x04, 0xB2, 0x10, 0x81}; //working char OFF[] = {0x40, 0x03, 0xB2, 0x10, 0x81, 0x00, 0x00}; //fail - get only 0x40, 0x03, 0xB2, 0x10, 0x81

ghost avatar Mar 11 '18 01:03 ghost

Hi Guys, is there an update on this topic? I think, I still run into this error. When I try to send [0x45, 0x01] to my device, it reacts as I expect. In contrast to this, when I try to send [0x45, 0x00] it does not react at all.

I can confirm, that the device is able to process [0x45, 0x00] when I send it with RFConnect.

#include <Arduino.h>
// #include "WiFi.h"
#include "BLEDevice.h"
#include <esp_gattc_api.h>

// The remote service we wish to connect to.
static BLEUUID serviceUUID("3e135142-654f-9090-134a-a6ff5bb77046");
// The characteristic of the remote service we are interested in.
static BLEUUID commandCharacteristic("3fa4585a-ce4a-3bad-db4b-b8df8179ea09");
static BLEUUID notifyCharacteristic("d0e8434d-cd29-0996-af41-6c90f4e0eb2a");
static BLERemoteCharacteristic *pCommandRemoteCharacteristic;
static BLERemoteCharacteristic *pNotifyRemoteCharacteristic;

static BLEAddress address("00:1a:22:12:3b:5b");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static boolean isBoosted = false;
static bool hasBeenBoosted = false;

static BLEAdvertisedDevice *myDevice;
static void startScan();

static void printHex(uint8_t *p_Data, uint8_t length)
{
  uint8_t *p_Head = p_Data;
  uint8_t *p_Tail = p_Data + length - 1;
  do
  {
    Serial.printf("%#x, ", *p_Head);
  } while (p_Head++ <= (p_Tail - 1));
}

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

  printHex(pData, length);
  Serial.print("\n\n");
}

static void boost()
{
  Serial.println("Start boost");
  const char newValue[2] = {0x45, 0x00};
  pCommandRemoteCharacteristic->writeValue(newValue, 2);
  isBoosted = true;
}

static void stopBoost()
{
  Serial.println("Stop boost");
  uint8_t newValue[2] = {0x45, 0x00};
  pCommandRemoteCharacteristic->writeValue(newValue, 2);
  isBoosted = false;
}

class MyClientCallback : public BLEClientCallbacks
{
  void onConnect(BLEClient *pclient)
  {

    // pclient->setValue(serviceUUID, commandCharacteristic, "")
  }

  void onDisconnect(BLEClient *pclient)
  {
    connected = false;
    Serial.println("start searching device");
    doScan = true;
  }
};

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

  pNotifyRemoteCharacteristic = pRemoteService->getCharacteristic(notifyCharacteristic);
  if (pNotifyRemoteCharacteristic == nullptr)
  {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(notifyCharacteristic.toString().c_str());
    pClient->disconnect();
    return false;
  }
  Serial.println(" - Found our notify characteristic");

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

  if (pNotifyRemoteCharacteristic->canNotify())
    pNotifyRemoteCharacteristic->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) && advertisedDevice.getAddress().toString() == address.toString())
    {
      Serial.print(advertisedDevice.getAddress().toString().c_str());
      Serial.println("Found device");
      BLEDevice::getScan()->stop();
      Serial.println("Stop scan");
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      // doScan = true;

    } // Found our server
  }   // onResult
};    // MyAdvertisedD

static void startScan()
{
  BLEScan *pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30, false);
}

void setup()
{
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");

  BLEDevice::init("");
  startScan();
}

int serialInput = 0;
void loop()
{
  if (Serial.available())
  {
    serialInput = Serial.read();
    if (serialInput == 'w')
    {
      boost();
    }
    else if (serialInput == 's')
    {
      stopBoost();
    }
    else if (serialInput == 'r')
    {
      startScan();
    }
  }

  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;
  }
}

Flojolo avatar Jan 19 '21 13:01 Flojolo

Hello again,

I solved this issue on my end, but it totally confused me. The issue was:

  • passing a char array to writeValue and a length only, results in the execution of void BLERemoteCharacteristic::writeValue(std::string newValue, bool response). The key in my case was, that the value 2 is interpreted as true for the argument response.
  • Using this insight, I understood (in contrast to my expectation) that I need the parameter response to be set to true. Applying this and calling void BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool response) explicitly with 3 parameters solved my issue.

Edit: See here the first answer: https://stackoverflow.com/questions/4523841/how-are-char-interpreted-as-const-stdstrings-in-arguments/4523853#4523853?newreg=77ebd55a868e4ed5a62f129fe452b483

Flojolo avatar Jan 20 '21 10:01 Flojolo

Is there any reason you cant explicitly make std::string before passing it? Like for example here you can send 0x0:

char tmp[] = {0x0}; 
std::string value(tmp);

Im not 100% sure, but i believe this should works, because value is type of string and you can get its length (is not null terminated).

Another option is to cast char[] to uint8_t* and use BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool response) with 2 or 3 options.

chegewara avatar Jan 20 '21 17:01 chegewara

s there any reason you cant explicitly make std::string before passing it? Like for example here you can send 0x0:

char tmp[] = {0x0}; 
std::string value(tmp);

It does not work. The length of such string is 0, not 1. See small snippet here: https://ideone.com/5qIxPy

positron96 avatar Apr 08 '21 11:04 positron96

Maybe this:

std::string value = "\0x0";
std::string value("\0x0");
std::string value{"\0x0"};

One of those should works.

chegewara avatar Apr 08 '21 11:04 chegewara

Sorry, my understanding of C++ is limited, but as I understand it, these 3 lines produce identical strings. Their std::string::length() will just stop at first encountered zero. Here is the checking code: https://ideone.com/yQ1M2r (all 3 have zero length)

positron96 avatar Apr 08 '21 11:04 positron96

No worries, it was just my wrong assumption it should works. This time i tested code and this works for sure:

#include <iostream>
void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
delay(1000);
  std::string value1 = "\0x0";
  std::string value2("\0x0");
  std::string value3{"\0x0"};
  value1.resize(1);
  value2.resize(1);
  value3.resize(1);
 
  Serial.println(value1.length());
  Serial.println(value2.length());
  Serial.println(value3.length());

  Serial.printf("%d\n", value1[0]);
  Serial.printf("%d\n", value2[0]);
  Serial.printf("%d\n", value3[0]);

}

void loop() {
  // put your main code here, to run repeatedly:

}

chegewara avatar Apr 08 '21 12:04 chegewara

Yeah, thanks, this one works! Looks a bit hacky to me though.

positron96 avatar Apr 08 '21 13:04 positron96

I am guessing its the case when you are using conversion from char string to std::string, where the first one is null terminated and later is not.

chegewara avatar Apr 08 '21 13:04 chegewara