Implement client long read / write

Client will now read/write long characteristics and descriptors.
This commit is contained in:
h2zero 2020-05-07 19:47:41 -06:00
parent 04b524d1f8
commit 59823b4bf0
4 changed files with 158 additions and 104 deletions

View file

@ -1,13 +1,10 @@
# *** UPDATE ***
Server now handles long reads and writes, still work to do on client.
NEW Client callback created - ```bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params)```
Called when the server wants to change the connection parameters, return true to accept them or false if not.
Check NimBLE_Client.ino example for a demonstration.
Client long read/write characteristics/descriptors now working.
We are now nearing 100% replacement of the original cpp_utils BLE library :smile:
# esp-nimble-cpp
A fork of the NimBLE stack restructured for compilation in the Ardruino IDE with a CPP library for use with ESP32.
NimBLE CPP library for use with ESP32 that attempts to maintain compatibility with the @nkolban cpp_uitls API.
Why? Because the Bluedroid library is too bulky.
@ -18,12 +15,16 @@ Initial client code testing has resulted in code size reduction of ~115k and red
Download as .zip and extract to components folder in your esp-idf project.
Run menuconfig, go to `Component config->Bluetooth->` enable Bluetooth and select NimBLE host.
`#include "NimBLEDevice.h"` in main.cpp.
# Usage:
This library is intended to be compatible with the original ESP32 BLE functions and types with minor changes.
This library is intended to be compatible with the original ESP32 BLE functions and types with minor changes.
Check [API_DIFFERENCES](https://github.com/h2zero/esp-nimble-cpp/blob/master/API_DIFFERENCES.md) for details.
# Acknowledgments:

View file

@ -53,6 +53,8 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
m_charProp = chr->properties;
m_pRemoteService = pRemoteService;
m_notifyCallback = nullptr;
m_rawData = nullptr;
m_dataLen = 0;
} // NimBLERemoteCharacteristic
@ -61,7 +63,9 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
*/
NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
removeDescriptors(); // Release resources for any descriptor information we may have allocated.
if(m_rawData != nullptr) free(m_rawData);
if(m_rawData != nullptr) {
free(m_rawData);
}
} // ~NimBLERemoteCharacteristic
/*
@ -319,9 +323,11 @@ std::string NimBLERemoteCharacteristic::readValue() {
int rc = 0;
int retryCount = 1;
// Clear the value before reading.
m_value = "";
NimBLEClient* pClient = getRemoteService()->getClient();
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
@ -330,25 +336,28 @@ std::string NimBLERemoteCharacteristic::readValue() {
do {
m_semaphoreReadCharEvt.take("readValue");
rc = ble_gattc_read(pClient->getConnId(), m_handle,
NimBLERemoteCharacteristic::onReadCB, this);
// long read experiment
/* rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteCharacteristic::onReadCB, this);
*/
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteCharacteristic::onReadCB,
this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d", rc);
m_semaphoreReadCharEvt.give();
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
m_semaphoreReadCharEvt.give(0);
return "";
}
rc = m_semaphoreReadCharEvt.wait("readValue");
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Characteristic is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
@ -361,7 +370,7 @@ std::string NimBLERemoteCharacteristic::readValue() {
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length());
return (rc == 0) ? m_value : "";
return m_value;
} // readValue
@ -374,36 +383,25 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteCharacteristic* characteristic = (NimBLERemoteCharacteristic*)arg;
uint16_t conn_id = characteristic->getRemoteService()->getClient()->getConnId();
// Make sure the discovery is for this device
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
// Make sure the read is for this client
if(conn_id != conn_handle) {
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
// long read experiment
/* if(attr && (attr->om->om_len >= (ble_att_mtu(characteristic->getRemoteService()->getClient()->getConnId()) - 1))){
return 0;
if(error->status == 0) {
if(attr) {
NIMBLE_LOGD(LOG_TAG, "Got %d bytes", attr->om->om_len);
characteristic->m_value += std::string((char*) attr->om->om_data, attr->om->om_len);
return 0;
}
}
*/
if(characteristic->m_rawData != nullptr) {
free(characteristic->m_rawData);
}
if (error->status == 0) {
characteristic->m_value = std::string((char*) attr->om->om_data, attr->om->om_len);
characteristic->m_rawData = (uint8_t*) calloc(attr->om->om_len, sizeof(uint8_t));
memcpy(characteristic->m_rawData, attr->om->om_data, attr->om->om_len);
characteristic->m_semaphoreReadCharEvt.give(0);
} else {
characteristic->m_rawData = nullptr;
characteristic->m_value = "";
characteristic->m_semaphoreReadCharEvt.give(error->status);
}
// characteristic->m_semaphoreReadCharEvt.give(error->status);
return 0; //1
// Read complete release semaphore and let the app can continue.
characteristic->m_semaphoreReadCharEvt.give(error->status);
return 0;
}
@ -523,7 +521,7 @@ bool NimBLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool r
NimBLEClient* pClient = getRemoteService()->getClient();
int rc = 0;
int retryCount = 1;
// uint16_t mtu;
uint16_t mtu;
// Check to see that we are connected.
if (!pClient->isConnected()) {
@ -531,29 +529,30 @@ bool NimBLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool r
return false;
}
// mtu = ble_att_mtu(pClient->getConnId()) - 3;
mtu = ble_att_mtu(pClient->getConnId()) - 3;
if(/*!length > mtu &&*/ !response) {
// Check if the data length is longer than we can write in 1 connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
}
do {
m_semaphoreWriteCharEvt.take("writeValue");
// long write experiment
/* if(length > mtu) {
NIMBLE_LOGD(LOG_TAG,"long write");
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteCharacteristic::onWriteCB,
NimBLERemoteCharacteristic::onWriteCB,
this);
} else {
*/
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteCharacteristic::onWriteCB,
this);
// }
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteCharacteristic::onWriteCB,
this);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write characteristic; rc=%d", rc);
m_semaphoreWriteCharEvt.give();
@ -564,6 +563,13 @@ bool NimBLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool r
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
@ -592,34 +598,49 @@ int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle,
{
NimBLERemoteCharacteristic* characteristic = (NimBLERemoteCharacteristic*)arg;
// Make sure the discovery is for this device
// Make sure the discovery is for this device
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
characteristic->m_semaphoreWriteCharEvt.give(error->status);
characteristic->m_semaphoreWriteCharEvt.give(0);
} else {
characteristic->m_semaphoreWriteCharEvt.give(error->status);
}
return 0;
}
/**
* @brief Read raw data from remote characteristic as hex bytes
* @return return pointer data read
* @return uint8_t pointer to the data read.
*/
uint8_t* NimBLERemoteCharacteristic::readRawData() {
if(m_rawData != nullptr) {
free(m_rawData);
m_rawData = nullptr;
}
m_dataLen = m_value.length();
// If we have data copy it to rawData
if(m_dataLen) {
m_rawData = (uint8_t*) calloc(m_dataLen, sizeof(uint8_t));
memcpy(m_rawData, m_value.data(), m_dataLen);
}
return m_rawData;
}
/**
* @brief Get the length of the data read from the remote characteristic.
* @return size_t length of the data in bytes.
*/
size_t NimBLERemoteCharacteristic::getDataLength() {
return m_value.length();
}
void NimBLERemoteCharacteristic::releaseSemaphores() {
for (auto &dPair : m_descriptorMap) {
dPair.second->releaseSemaphores();

View file

@ -60,6 +60,7 @@ public:
bool writeValue(uint8_t newValue, bool response = false);
std::string toString();
uint8_t* readRawData();
size_t getDataLength();
NimBLERemoteService* getRemoteService();
private:
@ -90,7 +91,8 @@ private:
FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt");
FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt");
std::string m_value;
uint8_t* m_rawData = nullptr;
uint8_t* m_rawData;
size_t m_dataLen;
notify_callback m_notifyCallback;
// We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID.

View file

@ -15,6 +15,7 @@
#if defined(CONFIG_BT_ENABLED)
#include "NimBLERemoteDescriptor.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLERemoteDescriptor";
@ -83,22 +84,26 @@ int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteDescriptor* desc = (NimBLERemoteDescriptor*)arg;
uint16_t conn_id = desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId();
// Make sure the discovery is for this device
if(desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
if(conn_id != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
desc->m_value = std::string((char*) attr->om->om_data, attr->om->om_len);
desc->m_semaphoreReadDescrEvt.give(0);
} else {
desc->m_value = "";
desc->m_semaphoreReadDescrEvt.give(error->status);
if(error->status == 0){
if(attr){
NIMBLE_LOGD(LOG_TAG, "Got %d bytes", attr->om->om_len);
desc->m_value += std::string((char*) attr->om->om_data, attr->om->om_len);
return 0;
}
}
// Read complete release semaphore and let the app can continue.
desc->m_semaphoreReadDescrEvt.give(error->status);
return 0;
}
@ -106,10 +111,12 @@ int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle,
std::string NimBLERemoteDescriptor::readValue() {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
int rc = 0;
int retryCount = 1;
// Clear the value before reading.
m_value = "";
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
// Check to see that we are connected.
if (!pClient->isConnected()) {
@ -120,12 +127,13 @@ std::string NimBLERemoteDescriptor::readValue() {
do {
m_semaphoreReadDescrEvt.take("ReadDescriptor");
rc = ble_gattc_read(pClient->getConnId(), m_handle,
NimBLERemoteDescriptor::onReadCB, this);
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteDescriptor::onReadCB,
this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Descriptor read failed, code: %d", rc);
m_semaphoreReadDescrEvt.give();
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read descriptor; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
m_semaphoreReadDescrEvt.give(0);
return "";
}
@ -133,8 +141,14 @@ std::string NimBLERemoteDescriptor::readValue() {
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Descriptor is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
@ -146,9 +160,8 @@ std::string NimBLERemoteDescriptor::readValue() {
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %d, rc: %d", m_value.length(), rc);
return (rc == 0) ? m_value : "";
NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %d", m_value.length());
return m_value;
} // readValue
@ -204,19 +217,15 @@ int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle,
{
NimBLERemoteDescriptor* descriptor = (NimBLERemoteDescriptor*)arg;
// Make sure the discovery is for this device
// Make sure the discovery is for this device
if(descriptor->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
descriptor->m_semaphoreDescWrite.give(0);
} else {
descriptor->m_semaphoreDescWrite.give(error->status);
}
descriptor->m_semaphoreDescWrite.give(error->status);
return 0;
}
@ -235,6 +244,7 @@ bool NimBLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool respo
int rc = 0;
int retryCount = 1;
uint16_t mtu;
// Check to see that we are connected.
if (!pClient->isConnected()) {
@ -242,18 +252,31 @@ bool NimBLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool respo
return false;
}
if(!response) {
mtu = ble_att_mtu(pClient->getConnId()) - 3;
// Check if the data length is longer than we can write in 1 connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
return (rc == 0);
}
do {
m_semaphoreDescWrite.take("WriteDescriptor");
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteDescriptor::onWriteCB,
this);
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteDescriptor::onWriteCB,
this);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteDescriptor::onWriteCB,
this);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write descriptor; rc=%d", rc);
m_semaphoreDescWrite.give();
@ -264,6 +287,13 @@ bool NimBLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool respo
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
@ -278,7 +308,7 @@ bool NimBLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool respo
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor writeValue, rc: %d",rc);
return (rc == 0); //true;
return (rc == 0);
} // writeValue