esp32-snippets
esp32-snippets copied to clipboard
BLERemoteCharacteristic::writeValue fails with '0x00'
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
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.
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'.
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
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;
}
}
Hello again,
I solved this issue on my end, but it totally confused me. The issue was:
- passing a char array to
writeValueand a length only, results in the execution ofvoid BLERemoteCharacteristic::writeValue(std::string newValue, bool response). The key in my case was, that the value2is interpreted astruefor the argumentresponse. - Using this insight, I understood (in contrast to my expectation) that I need the parameter
responseto be set totrue. Applying this and callingvoid 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
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.
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
Maybe this:
std::string value = "\0x0";
std::string value("\0x0");
std::string value{"\0x0"};
One of those should works.
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)
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:
}
Yeah, thanks, this one works! Looks a bit hacky to me though.
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.