From 59823b4bf0e0680eeef7c449b1ff54e84e2ef73d Mon Sep 17 00:00:00 2001 From: h2zero Date: Thu, 7 May 2020 19:47:41 -0600 Subject: [PATCH] Implement client long read / write Client will now read/write long characteristics and descriptors. --- README.md | 15 +-- src/NimBLERemoteCharacteristic.cpp | 143 +++++++++++++++++------------ src/NimBLERemoteCharacteristic.h | 4 +- src/NimBLERemoteDescriptor.cpp | 100 +++++++++++++------- 4 files changed, 158 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 0ba328c..f7ea898 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/src/NimBLERemoteCharacteristic.cpp b/src/NimBLERemoteCharacteristic.cpp index a48354c..8d7c7a8 100644 --- a/src/NimBLERemoteCharacteristic.cpp +++ b/src/NimBLERemoteCharacteristic.cpp @@ -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(); diff --git a/src/NimBLERemoteCharacteristic.h b/src/NimBLERemoteCharacteristic.h index c107be7..abbe1f9 100644 --- a/src/NimBLERemoteCharacteristic.h +++ b/src/NimBLERemoteCharacteristic.h @@ -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. diff --git a/src/NimBLERemoteDescriptor.cpp b/src/NimBLERemoteDescriptor.cpp index d862a8c..123f0f2 100644 --- a/src/NimBLERemoteDescriptor.cpp +++ b/src/NimBLERemoteDescriptor.cpp @@ -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