From 4723b1cc53c48e44037ca0d848c20fdb31bb0226 Mon Sep 17 00:00:00 2001 From: h2zero <32826625+h2zero@users.noreply.github.com> Date: Mon, 27 Jul 2020 21:11:38 -0600 Subject: [PATCH] Server: Add onSubscribe() callback for characteristics. (#21) Server: Add onSubscribe() callback for characteristics. Adds a new method to NimBLECharacteristicCallbacks that gets called when a client changes it's subscription status. * Remove NimBLE2902 class. As the NimBLE2902 class usefulness was only related to callback functions that were replaced by the NimBLECharacteristicCallbacks:onSubscribe() method this removes the NimBLE2902 class and moves all subscription handling to NimBLECharacteristic. * Update documents and examples to reflect this change. * Add getSubscribedCount() to get the number of subscribed clients. --- CMakeLists.txt | 1 - docs/BREAKING_API_CHANGES.md | 25 +++---- src/NimBLE2902.cpp | 79 --------------------- src/NimBLE2902.h | 59 ---------------- src/NimBLECharacteristic.cpp | 133 +++++++++++++++++++---------------- src/NimBLECharacteristic.h | 4 ++ src/NimBLEDescriptor.h | 1 - src/NimBLEServer.cpp | 10 +-- src/NimBLEService.cpp | 17 ----- 9 files changed, 93 insertions(+), 236 deletions(-) delete mode 100644 src/NimBLE2902.cpp delete mode 100644 src/NimBLE2902.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 53efc6a..80dd3f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ cmake_minimum_required(VERSION 3.5) set(SUPPORTED_TARGETS esp32) idf_component_register(SRCS "src/FreeRTOS.cpp" - "src/NimBLE2902.cpp" "src/NimBLE2904.cpp" "src/NimBLEAddress.cpp" "src/NimBLEAdvertisedDevice.cpp" diff --git a/docs/BREAKING_API_CHANGES.md b/docs/BREAKING_API_CHANGES.md index 70bd97b..bec4bf1 100644 --- a/docs/BREAKING_API_CHANGES.md +++ b/docs/BREAKING_API_CHANGES.md @@ -33,15 +33,18 @@ When creating a characteristic the properties are now set with `NIMBLE_PROPERTY: ### Descriptors Descriptors are now created using the `NimBLECharacteristic::createDescriptor()` method. -The previous method `BLECharacteristic::addDescriptor()` is now a private function in the library. +The previous method `BLECharacteristic::addDescriptor()` has been removed. -This was done because the NimBLE host automatically creates a 0x2902 descriptor if a characteristic has NOTIFY or INDICATE properties applied. -Due to this fact, the library also creates one automatically for your application. -The only reason to manually create this descriptor now is to assign callback functions. -If you do not require this functionality you can safely exclude the manual creation of the 0x2902 descriptor. +0x2902 Descriptor class: formerly known as BLE2902 or NimBLE2902 has been removed. +It was no longer useful as a new callback `NimBLECharacteristicCallbacks::onSubscribe` was added +to handle callback functionality and the client subscription status is handled internally. -For any other descriptor, (except 0x2904, see below) it should now be created just as characteristics are -by using the `NimBLECharacteristic::createDescriptor` method. +NimBLE automatically creates the 0x2902 descriptor if a characteristic has a notification or indication property assigned to it. + +**Note** Attempting to create a 0x2902 descriptor will trigger an assert to notify the error, +allowing the creation of it would cause a fault in the NimBLE stack. + +All other desctiptors are now created just as characteristics are by using the `NimBLECharacteristic::createDescriptor` methods (except 0x2904, see below). Which are defined as: ``` NimBLEDescriptor* createDescriptor(const char* uuid, @@ -67,15 +70,14 @@ pDescriptor = pCharacteristic->createDescriptor("ABCD", Would create a descriptor with the UUID 0xABCD, publicly readable but only writable if paired/bonded (encrypted) and has a max value length of 25 bytes.
-For the 0x2904 and 0x2902 descriptor, there is a special class that is created when you call `createDescriptor("2904")`or `createDescriptor("2902")`. +For the 0x2904, there is a special class that is created when you call `createDescriptor("2904"). -The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` or `NimBLE2902` so you must cast the returned pointer to -`NimBLE2904` or `NimBLE2902` to access the specific class methods. +The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` so you must cast the returned pointer to +`NimBLE2904` to access the specific class methods. ##### Example ``` p2904 = (NimBLE2904*)pCharacteristic->createDescriptor("2904"); -p2902 = (NimBLE2902*)pCharacteristic->createDescriptor("2902"); ```
@@ -91,7 +93,6 @@ Security is set on the characteristic or descriptor properties by applying one o When a peer wants to read or write a characteristic or descriptor with any of these properties applied it will trigger the pairing process. By default the "just-works" pairing will be performed automatically. This can be changed to use passkey authentication or numeric comparison. See [Security Differences](#security-differences) for details. -
# Client API Differences diff --git a/src/NimBLE2902.cpp b/src/NimBLE2902.cpp deleted file mode 100644 index 04a07b6..0000000 --- a/src/NimBLE2902.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * NimBLE2902.cpp - * - * Created: on March 10, 2020 - * Author H2zero - * - * Originally: - * - * BLE2902.cpp - * - * Created on: Jun 25, 2017 - * Author: kolban - */ - - -/* - * See also: - * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) - -#include "NimBLE2902.h" - -NimBLE2902::NimBLE2902(NimBLECharacteristic* pCharacterisitic) -: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2902), - BLE_GATT_CHR_F_READ | - BLE_GATT_CHR_F_WRITE, - 2, pCharacterisitic) -{ - uint8_t data[2] = { 0, 0 }; - setValue(data, 2); -} // NimBLE2902 - - -/** - * @brief Get the notifications value. - * @return The notifications value. True if notifications are enabled and false if not. - */ -bool NimBLE2902::getNotifications() { - return (getValue()[0] & (1 << 0)) != 0; -} // getNotifications - - -/** - * @brief Get the indications value. - * @return The indications value. True if indications are enabled and false if not. - */ -bool NimBLE2902::getIndications() { - return (getValue()[0] & (1 << 1)) != 0; -} // getIndications - - -/** - * @brief Set the indications flag. - * @param [in] flag The indications flag. - */ -void NimBLE2902::setIndications(bool flag) { - uint8_t *pValue = getValue(); - if (flag) pValue[0] |= 1 << 1; - else pValue[0] &= ~(1 << 1); -} // setIndications - - -/** - * @brief Set the notifications flag. - * @param [in] flag The notifications flag. - */ -void NimBLE2902::setNotifications(bool flag) { - uint8_t *pValue = getValue(); - if (flag) pValue[0] |= 1 << 0; - else pValue[0] &= ~(1 << 0); -} // setNotifications - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif diff --git a/src/NimBLE2902.h b/src/NimBLE2902.h deleted file mode 100644 index 2d84b73..0000000 --- a/src/NimBLE2902.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * NimBLE2902.h - * - * Created: on March 10, 2020 - * Author H2zero - * - * Originally: - * - * BLE2902.h - * - * Created on: Jun 25, 2017 - * Author: kolban - */ - -#ifndef MAIN_NIMBLE2902_H_ -#define MAIN_NIMBLE2902_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) - -#include "NimBLEDescriptor.h" - -#include - -#define NIMBLE_DESC_FLAG_NOTIFY 0x0001 -#define NIMBLE_DESC_FLAG_INDICATE 0x0002 - -typedef struct { - uint16_t conn_id; - uint16_t sub_val; -} chr_sub_status_t; - - -/** - * @brief Descriptor for Client Characteristic Configuration. - * - * This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902. - * - * See also: - * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml - */ -class NimBLE2902: public NimBLEDescriptor { -public: - bool getNotifications(); - bool getIndications(); - void setNotifications(bool flag); - void setIndications(bool flag); -private: - NimBLE2902(NimBLECharacteristic* pCharacterisitic); - friend class NimBLECharacteristic; - std::vector m_subscribedVec; - -}; // NimBLE2902 - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ -#endif /* MAIN_NIMBLE2902_H_ */ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index 4025d83..5c3ff94 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -16,16 +16,18 @@ #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLECharacteristic.h" -#include "NimBLE2902.h" #include "NimBLE2904.h" #include "NimBLEDevice.h" #include "NimBLELog.h" #define NULL_HANDLE (0xffff) +#define NIMBLE_SUB_NOTIFY 0x0001 +#define NIMBLE_SUB_INDICATE 0x0002 static NimBLECharacteristicCallbacks defaultCallback; static const char* LOG_TAG = "NimBLECharacteristic"; + /** * @brief Construct a characteristic * @param [in] uuid - UUID (const char*) for the characteristic. @@ -86,20 +88,9 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3 NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) { NimBLEDescriptor* pDescriptor = nullptr; if(uuid == NimBLEUUID(uint16_t(0x2902))) { - if(!(m_properties & BLE_GATT_CHR_F_NOTIFY) && !(m_properties & BLE_GATT_CHR_F_INDICATE)) { - assert(0 && "Cannot create 2902 descriptior without characteristic notification or indication property set"); - } - // We cannot have more than one 2902 descriptor, if it's already been created just return a pointer to it. - pDescriptor = getDescriptorByUUID(uuid); - if(pDescriptor == nullptr) { - pDescriptor = new NimBLE2902(this); - } else { - return pDescriptor; - } - + assert(0 && "0x2902 descriptors cannot be manually created"); } else if (uuid == NimBLEUUID(uint16_t(0x2904))) { pDescriptor = new NimBLE2904(this); - } else { pDescriptor = new NimBLEDescriptor(uuid, properties, max_len, this); } @@ -269,62 +260,64 @@ int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_han } +/** + * @brief Get the number of clients subscribed to the characteristic. + * @returns Number of clients subscribed to notifications / indications. + */ +size_t NimBLECharacteristic::getSubscribedCount() { + return m_subscribedVec.size(); +} + + /** * @brief Set the subscribe status for this characteristic.\n * This will maintain a vector of subscribed clients and their indicate/notify status. */ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) { - uint16_t subVal = 0; - if(event->subscribe.cur_notify) { - subVal |= NIMBLE_DESC_FLAG_NOTIFY; - } - if(event->subscribe.cur_indicate) { - subVal |= NIMBLE_DESC_FLAG_INDICATE; - } - - if(m_pTaskData != nullptr) { - m_pTaskData->rc = (subVal & NIMBLE_DESC_FLAG_INDICATE) ? 0 : - NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED; - xTaskNotifyGive(m_pTaskData->task); - } - - NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", - event->subscribe.conn_handle, subVal); - - NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID(uint16_t(0x2902)); - if(p2902 == nullptr){ - ESP_LOGE(LOG_TAG, "No 2902 descriptor found for %s", - std::string(getUUID()).c_str()); + ble_gap_conn_desc desc; + if(ble_gap_conn_find(event->subscribe.conn_handle, &desc) != 0) { return; } - p2902->setNotifications(subVal & NIMBLE_DESC_FLAG_NOTIFY); - p2902->setIndications(subVal & NIMBLE_DESC_FLAG_INDICATE); - p2902->m_pCallbacks->onWrite(p2902); + uint16_t subVal = 0; + if(event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) { + subVal |= NIMBLE_SUB_NOTIFY; + } + if(event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) { + subVal |= NIMBLE_SUB_INDICATE; + } + if(m_pTaskData != nullptr) { + m_pTaskData->rc = (subVal & NIMBLE_SUB_INDICATE) ? 0 : + NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED; + xTaskNotifyGive(m_pTaskData->task); + } + + NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", + event->subscribe.conn_handle, subVal); - auto it = p2902->m_subscribedVec.begin(); - for(;it != p2902->m_subscribedVec.end(); ++it) { - if((*it).conn_id == event->subscribe.conn_handle) { + m_pCallbacks->onSubscribe(this, &desc, subVal); + + auto it = m_subscribedVec.begin(); + for(;it != m_subscribedVec.end(); ++it) { + if((*it).first == event->subscribe.conn_handle) { break; } } if(subVal > 0) { - if(it == p2902->m_subscribedVec.end()) { - chr_sub_status_t client_sub; - client_sub.conn_id = event->subscribe.conn_handle; - client_sub.sub_val = subVal; - p2902->m_subscribedVec.push_back(client_sub); + if(it == m_subscribedVec.end()) { + m_subscribedVec.push_back({event->subscribe.conn_handle, subVal}); return; } - (*it).sub_val = subVal; + (*it).second = subVal; - } else if(it != p2902->m_subscribedVec.end()) { - p2902->m_subscribedVec.erase(it); - p2902->m_subscribedVec.shrink_to_fit(); + } else if(it != m_subscribedVec.end()) { + m_subscribedVec.erase(it); + m_subscribedVec.shrink_to_fit(); } + } @@ -348,15 +341,16 @@ void NimBLECharacteristic::indicate() { void NimBLECharacteristic::notify(bool is_notification) { NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", getDataLength()); - NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID(uint16_t(0x2902)); - if(p2902 == nullptr) { + if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) && + !(m_properties & NIMBLE_PROPERTY::INDICATE)) + { NIMBLE_LOGE(LOG_TAG, "<< notify-Error; Notify/indicate not enabled for characterisitc: %s", std::string(getUUID()).c_str()); } - if (p2902->m_subscribedVec.size() == 0) { + if (m_subscribedVec.size() == 0) { NIMBLE_LOGD(LOG_TAG, "<< notify: No clients subscribed."); return; } @@ -370,18 +364,18 @@ void NimBLECharacteristic::notify(bool is_notification) { (m_properties & BLE_GATT_CHR_F_READ_ENC); int rc = 0; - for (auto &it : p2902->m_subscribedVec) { - uint16_t _mtu = getService()->getServer()->getPeerMTU(it.conn_id); + for (auto &it : m_subscribedVec) { + uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first); // check if connected and subscribed - if(_mtu == 0 || it.sub_val == 0) { + if(_mtu == 0 || it.second == 0) { continue; } // check if security requirements are satisfied if(reqSec) { struct ble_gap_conn_desc desc; - rc = ble_gap_conn_find(it.conn_id, &desc); + rc = ble_gap_conn_find(it.first, &desc); if(rc != 0 || !desc.sec_state.encrypted) { continue; } @@ -391,13 +385,13 @@ void NimBLECharacteristic::notify(bool is_notification) { NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3); } - if(is_notification && (!(it.sub_val & NIMBLE_DESC_FLAG_NOTIFY))) { + if(is_notification && (!(it.second & NIMBLE_SUB_NOTIFY))) { NIMBLE_LOGW(LOG_TAG, "Sending notification to client subscribed to indications, sending indication instead"); is_notification = false; } - if(!is_notification && (!(it.sub_val & NIMBLE_DESC_FLAG_INDICATE))) { + if(!is_notification && (!(it.second & NIMBLE_SUB_INDICATE))) { NIMBLE_LOGW(LOG_TAG, "Sending indication to client subscribed to notification, sending notification instead"); is_notification = true; @@ -414,7 +408,7 @@ void NimBLECharacteristic::notify(bool is_notification) { ble_task_data_t taskData = {nullptr, xTaskGetCurrentTaskHandle(),0, nullptr}; m_pTaskData = &taskData; - rc = ble_gattc_indicate_custom(it.conn_id, m_handle, om); + rc = ble_gattc_indicate_custom(it.first, m_handle, om); if(rc != 0){ statusRC = NimBLECharacteristicCallbacks::Status::ERROR_GATT; } else { @@ -433,7 +427,7 @@ void NimBLECharacteristic::notify(bool is_notification) { statusRC = NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE; } } else { - rc = ble_gattc_notify_custom(it.conn_id, m_handle, om); + rc = ble_gattc_notify_custom(it.first, m_handle, om); if(rc == 0) { statusRC = NimBLECharacteristicCallbacks::Status::SUCCESS_NOTIFY; } else { @@ -574,5 +568,24 @@ void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacterist NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default"); } // onStatus + +/** + * @brief Callback function called when a client changes subscription status. + * @param [in] pCharacteristic The characteristic that is the source of the event. + * @param [in] desc The connection description struct that is associated with the client. + * @param [in] subValue The subscription status: + * * 0 = Un-Subscribed + * * 1 = Notifications + * * 2 = Indications + * * 3 = Notifications and Indications + */ +void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, + ble_gap_conn_desc* desc, + uint16_t subValue) +{ + NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default"); +} + + #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #endif /* CONFIG_BT_ENABLED */ diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index 58f18b0..1c7418a 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -110,6 +110,7 @@ public: std::string toString(); uint16_t getHandle(); + size_t getSubscribedCount(); private: @@ -145,6 +146,8 @@ private: ble_task_data_t *m_pTaskData; portMUX_TYPE m_valMux; time_t m_timestamp; + + std::vector> m_subscribedVec; }; // NimBLECharacteristic @@ -181,6 +184,7 @@ public: virtual void onWrite(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc); virtual void onNotify(NimBLECharacteristic* pCharacteristic); virtual void onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code); + virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue); }; #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h index 9bd52c9..16b6edc 100644 --- a/src/NimBLEDescriptor.h +++ b/src/NimBLEDescriptor.h @@ -106,7 +106,6 @@ public: }; #include "NimBLE2904.h" -#include "NimBLE2902.h" #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #endif /* CONFIG_BT_ENABLED */ diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 6ce97ae..decc630 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -182,10 +182,6 @@ void NimBLEServer::start() { // we do it now. if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) || (chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) { - - if(nullptr == chr->getDescriptorByUUID(uint16_t(0x2902))) { - chr->createDescriptor(uint16_t(0x2902)); - } m_notifyChrVec.push_back(chr); } } @@ -303,9 +299,9 @@ size_t NimBLEServer::getConnectedCount() { } // BLE_GAP_EVENT_DISCONNECT case BLE_GAP_EVENT_SUBSCRIBE: { - NIMBLE_LOGI(LOG_TAG, "subscribe event; cur_notify=%d\n value handle; " - "val_handle=%d\n", - event->subscribe.cur_notify, event->subscribe.attr_handle); + NIMBLE_LOGI(LOG_TAG, "subscribe event; attr_handle=%d, subscribed: %s", + event->subscribe.attr_handle, + (event->subscribe.cur_notify ? "true":"false")); for(auto &it : server->m_notifyChrVec) { if(it->getHandle() == event->subscribe.attr_handle) { diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index e9c62f9..3420da2 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -147,31 +147,14 @@ bool NimBLEService::start() { for(uint8_t i=0; i < numChrs;) { uint8_t numDscs = pCharacteristic->m_dscVec.size(); - if(numDscs) { - // skip 2902 as it's automatically created by NimBLE - // if Indicate or Notify flags are set - if(((pCharacteristic->m_properties & BLE_GATT_CHR_F_INDICATE) || - (pCharacteristic->m_properties & BLE_GATT_CHR_F_NOTIFY)) && - pCharacteristic->getDescriptorByUUID("2902") != nullptr) - { - numDscs--; - } - } if(!numDscs){ pChr_a[i].descriptors = NULL; } else { // Must have last descriptor uuid = 0 so we have to create 1 extra - //NIMBLE_LOGD(LOG_TAG, "Adding %d descriptors", numDscs); pDsc_a = new ble_gatt_dsc_def[numDscs+1]; NimBLEDescriptor* pDescriptor = *pCharacteristic->m_dscVec.begin(); for(uint8_t d=0; d < numDscs;) { - // skip 2902 - if(pDescriptor->m_uuid == NimBLEUUID(uint16_t(0x2902))) { - //NIMBLE_LOGD(LOG_TAG, "Skipped 0x2902"); - pDescriptor = *(pCharacteristic->m_dscVec.begin()+d+1); - continue; - } pDsc_a[d].uuid = &pDescriptor->m_uuid.getNative()->u; pDsc_a[d].att_flags = pDescriptor->m_properties; pDsc_a[d].min_key_size = 0;