diff --git a/docs/Improvements_and_updates.md b/docs/Improvements_and_updates.md index 4b0d345..abdfccd 100644 --- a/docs/Improvements_and_updates.md +++ b/docs/Improvements_and_updates.md @@ -43,6 +43,14 @@ as a type specified by the user. A new method `NimBLEServer::advertiseOnDisconnect(bool)` has been implemented to control this, true(default) = enabled.
+`NimBLEServer::removeService` takes an additional parameter `bool deleteSvc` that if true will delete the service +and all characteristics / descriptors belonging to it and invalidating any pointers to them. + +If false the service is only removed from visibility by clients. The pointers to the service and +it's characteristics / descriptors will remain valid and the service can be re-added in the future +using `NimBLEServer::addService`. +
+ # Client NimBLERemoteCharacteristic::readValue(time_t\*, bool) diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp index ccec223..fbe88ae 100644 --- a/src/NimBLEAdvertising.cpp +++ b/src/NimBLEAdvertising.cpp @@ -63,6 +63,7 @@ NimBLEAdvertising::NimBLEAdvertising() { */ void NimBLEAdvertising::addServiceUUID(const NimBLEUUID &serviceUUID) { m_serviceUUIDs.push_back(serviceUUID); + m_advDataSet = false; } // addServiceUUID @@ -75,6 +76,22 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) { } // addServiceUUID +/** + * @brief Add a service uuid to exposed list of services. + * @param [in] serviceUUID The UUID of the service to expose. + */ +void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) { + //m_serviceUUIDs.erase(std::remove_if(m_serviceUUIDs.begin(), m_serviceUUIDs.end(),[serviceUUID](const NimBLEUUID &s) {return serviceUUID == s;}), m_serviceUUIDs.end()); + for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) { + if((*it) == serviceUUID) { + m_serviceUUIDs.erase(it); + break; + } + } + m_advDataSet = false; +} // addServiceUUID + + /** * @brief Set the device appearance in the advertising data. * The codes for distinct appearances can be found here:\n diff --git a/src/NimBLEAdvertising.h b/src/NimBLEAdvertising.h index d81d797..4ac013a 100644 --- a/src/NimBLEAdvertising.h +++ b/src/NimBLEAdvertising.h @@ -75,6 +75,7 @@ public: NimBLEAdvertising(); void addServiceUUID(const NimBLEUUID &serviceUUID); void addServiceUUID(const char* serviceUUID); + void removeServiceUUID(const NimBLEUUID &serviceUUID); void start(); void stop(); void setAppearance(uint16_t appearance); diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index 5142a01..a6269d9 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -58,6 +58,9 @@ NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t prop * @brief Destructor. */ NimBLECharacteristic::~NimBLECharacteristic() { + for(auto &it : m_dscVec) { + delete it; + } } // ~NimBLECharacteristic diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 2a9bd13..6ce97ae 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -22,6 +22,10 @@ #include "NimBLEDevice.h" #include "NimBLELog.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + + static const char* LOG_TAG = "NimBLEServer"; static NimBLEServerCallbacks defaultCallbacks; @@ -37,9 +41,20 @@ NimBLEServer::NimBLEServer() { m_pServerCallbacks = &defaultCallbacks; m_gattsStarted = false; m_advertiseOnDisconnect = true; + m_svcChanged = false; } // NimBLEServer +/** + * @brief Destructor: frees all resources / attributes created. + */ +NimBLEServer::~NimBLEServer() { + for(auto &it : m_svcVec) { + delete it; + } +} + + /** * @brief Create a %BLE Service. * @param [in] uuid The UUID of the new service. @@ -71,6 +86,12 @@ NimBLEService* NimBLEServer::createService(const NimBLEUUID &uuid, uint32_t numH NimBLEService* pService = new NimBLEService(uuid, numHandles, this); m_svcVec.push_back(pService); // Save a reference to this service being on this server. + if(m_gattsStarted) { + ble_svc_gatt_changed(0x0001, 0xffff); + m_svcChanged = true; + resetGATT(); + } + NIMBLE_LOGD(LOG_TAG, "<< createService"); return pService; } // createService @@ -146,8 +167,16 @@ void NimBLEServer::start() { NIMBLE_LOGI(LOG_TAG, "Service changed characterisic handle: %d", m_svcChgChrHdl); */ - // Build a vector of characteristics with Notify / Indicate capabilities for event handling + // Get the assigned service handles and build a vector of characteristics + // with Notify / Indicate capabilities for event handling for(auto &svc : m_svcVec) { + if(svc->m_removed == 0) { + rc = ble_gatts_find_svc(&svc->getUUID().getNative()->u, &svc->m_handle); + if(rc != 0) { + abort(); + } + } + for(auto &chr : svc->m_chrVec) { // if Notify / Indicate is enabled but we didn't create the descriptor // we do it now. @@ -260,6 +289,11 @@ size_t NimBLEServer::getConnectedCount() { server->m_connectedPeersVec.end(), event->disconnect.conn.conn_handle), server->m_connectedPeersVec.end()); + + if(server->m_svcChanged) { + server->resetGATT(); + } + server->m_pServerCallbacks->onDisconnect(server); if(server->m_advertiseOnDisconnect) { @@ -445,6 +479,111 @@ void NimBLEServer::setCallbacks(NimBLEServerCallbacks* pCallbacks) { } // setCallbacks +/** + * @brief Remove a service from the server. + * + * @details Immediately removes access to the service by clients, sends a service changed indication, + * and removes the service (if applicable) from the advertisments. + * The service is not deleted unless the deleteSvc parameter is true, otherwise the service remains + * available and can be re-added in the future. If desired a removed but not deleted service can + * be deleted later by calling this method with deleteSvc set to true. + * + * @note The service will not be removed from the database until all open connections are closed + * as it requires resetting the GATT server. In the interim the service will have it's visibility disabled. + * + * @note Advertising will need to be restarted by the user after calling this as we must stop + * advertising in order to remove the service. + * + * @param [in] service The service object to remove. + * @param [in] deleteSvc true if the service should be deleted. + */ +void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) { + // Check if the service was already removed and if so check if this + // is being called to delete the object and do so if requested. + // Otherwise, ignore the call and return. + if(service->m_removed > 0) { + if(deleteSvc) { + for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) { + if ((*it)->getUUID() == service->getUUID()) { + delete *it; + m_svcVec.erase(it); + break; + } + } + } + + return; + } + + int rc = ble_gatts_svc_set_visibility(service->getHandle(), 0); + if(rc !=0) { + return; + } + + service->m_removed = deleteSvc ? 2 : 1; + m_svcChanged = true; + + ble_svc_gatt_changed(0x0001, 0xffff); + resetGATT(); + NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID()); +} + + +/** + * @brief Adds a service which was already created, but removed from availability. + * + * @note If it is desired to advertise the service it must be added by + * calling NimBLEAdvertising::addServiceUUID. + * + * @param [in} service The service object to add. + */ +void NimBLEServer::addService(NimBLEService* service) { + // If adding a service that was not removed just return. + if(service->m_removed == 0) { + return; + } + + service->m_removed = 0; + m_svcChanged = true; + + ble_svc_gatt_changed(0x0001, 0xffff); + resetGATT(); +} + + +/** + * @brief Resets the GATT server, used when services are added/removed after initialization. + */ +void NimBLEServer::resetGATT() { + if(getConnectedCount() > 0) { + return; + } + + NimBLEDevice::stopAdvertising(); + ble_gatts_reset(); + ble_svc_gap_init(); + ble_svc_gatt_init(); + + for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) { + if ((*it)->m_removed > 0) { + if ((*it)->m_removed == 2) { + delete *it; + it = m_svcVec.erase(it); + } else { + ++it; + } + continue; + } + + (*it)->start(); + ++it; + } + + m_svcChanged = false; + m_gattsStarted = false; +} + + /** * @brief Start advertising. * diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index 44e98ea..351a6fc 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -41,6 +41,8 @@ public: NimBLEService* createService(const char* uuid); NimBLEService* createService(const NimBLEUUID &uuid, uint32_t numHandles=15, uint8_t inst_id=0); + void removeService(NimBLEService* service, bool deleteSvc = false); + void addService(NimBLEService* service); NimBLEAdvertising* getAdvertising(); void setCallbacks(NimBLEServerCallbacks* pCallbacks); void startAdvertising(); @@ -59,12 +61,14 @@ public: private: NimBLEServer(); + ~NimBLEServer(); friend class NimBLECharacteristic; friend class NimBLEDevice; friend class NimBLEAdvertising; bool m_gattsStarted; bool m_advertiseOnDisconnect; + bool m_svcChanged; NimBLEServerCallbacks* m_pServerCallbacks; std::vector m_connectedPeersVec; @@ -74,6 +78,7 @@ private: std::vector m_notifyChrVec; static int handleGapEvent(struct ble_gap_event *event, void *arg); + void resetGATT(); }; // NimBLEServer diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index 35bbdf3..e9c62f9 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -49,13 +49,35 @@ NimBLEService::NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer * @param [in] a pointer to the server instance that this service belongs to. */ NimBLEService::NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pServer = pServer; - m_numHandles = numHandles; + m_uuid = uuid; + m_handle = NULL_HANDLE; + m_pServer = pServer; + m_numHandles = numHandles; + m_pSvcDef = nullptr; + m_removed = 0; + } // NimBLEService +NimBLEService::~NimBLEService() { + if(m_pSvcDef != nullptr) { + if(m_pSvcDef->characteristics != nullptr) { + for(int i=0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) { + if(m_pSvcDef->characteristics[i].descriptors) { + delete(m_pSvcDef->characteristics[i].descriptors); + } + } + delete(m_pSvcDef->characteristics); + } + + delete(m_pSvcDef); + } + + for(auto &it : m_chrVec) { + delete it; + } +} + /** * @brief Dump details of this BLE GATT service. * @return N/A. @@ -94,98 +116,101 @@ NimBLEUUID NimBLEService::getUUID() { * and registers it with the NimBLE stack. * @return bool success/failure . */ - bool NimBLEService::start() { NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str()); int rc = 0; - // Nimble requires an array of services to be sent to the api - // Since we are adding 1 at a time we create an array of 2 and set the type - // of the second service to 0 to indicate the end of the array. - ble_gatt_svc_def* svc = new ble_gatt_svc_def[2]; - ble_gatt_chr_def* pChr_a = nullptr; - ble_gatt_dsc_def* pDsc_a = nullptr; - svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY; - svc[0].uuid = &m_uuid.getNative()->u; - svc[0].includes = NULL; + if(m_pSvcDef == nullptr) { + // Nimble requires an array of services to be sent to the api + // Since we are adding 1 at a time we create an array of 2 and set the type + // of the second service to 0 to indicate the end of the array. + ble_gatt_svc_def* svc = new ble_gatt_svc_def[2]; + ble_gatt_chr_def* pChr_a = nullptr; + ble_gatt_dsc_def* pDsc_a = nullptr; - size_t numChrs = m_chrVec.size(); + svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY; + svc[0].uuid = &m_uuid.getNative()->u; + svc[0].includes = NULL; - NIMBLE_LOGD(LOG_TAG,"Adding %d characteristics for service %s", numChrs, toString().c_str()); + size_t numChrs = m_chrVec.size(); - if(!numChrs){ - svc[0].characteristics = NULL; - }else{ - // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end - // of the characteristics for the service. We create 1 extra and set it to null - // for this purpose. - pChr_a = new ble_gatt_chr_def[numChrs+1]; - NimBLECharacteristic* pCharacteristic = *m_chrVec.begin(); + NIMBLE_LOGD(LOG_TAG,"Adding %d characteristics for service %s", numChrs, toString().c_str()); - 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(!numChrs){ + svc[0].characteristics = NULL; + }else{ + // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end + // of the characteristics for the service. We create 1 extra and set it to null + // for this purpose. + pChr_a = new ble_gatt_chr_def[numChrs+1]; + NimBLECharacteristic* pCharacteristic = *m_chrVec.begin(); - 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; + 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--; } - pDsc_a[d].uuid = &pDescriptor->m_uuid.getNative()->u; - pDsc_a[d].att_flags = pDescriptor->m_properties; - pDsc_a[d].min_key_size = 0; - pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent; - pDsc_a[d].arg = pDescriptor; - d++; - pDescriptor = *(pCharacteristic->m_dscVec.begin() + d); } - pDsc_a[numDscs].uuid = NULL; - pChr_a[i].descriptors = pDsc_a; + 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; + pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent; + pDsc_a[d].arg = pDescriptor; + d++; + pDescriptor = *(pCharacteristic->m_dscVec.begin() + d); + } + + pDsc_a[numDscs].uuid = NULL; + pChr_a[i].descriptors = pDsc_a; + } + + pChr_a[i].uuid = &pCharacteristic->m_uuid.getNative()->u; + pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent; + pChr_a[i].arg = pCharacteristic; + pChr_a[i].flags = pCharacteristic->m_properties; + pChr_a[i].min_key_size = 0; + pChr_a[i].val_handle = &pCharacteristic->m_handle; + i++; + pCharacteristic = *(m_chrVec.begin() + i); } - pChr_a[i].uuid = &pCharacteristic->m_uuid.getNative()->u; - pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent; - pChr_a[i].arg = pCharacteristic; - pChr_a[i].flags = pCharacteristic->m_properties; - pChr_a[i].min_key_size = 0; - pChr_a[i].val_handle = &pCharacteristic->m_handle; - i++; - pCharacteristic = *(m_chrVec.begin() + i); + pChr_a[numChrs].uuid = NULL; + svc[0].characteristics = pChr_a; } - pChr_a[numChrs].uuid = NULL; - svc[0].characteristics = pChr_a; + // end of services must indicate to api with type = 0 + svc[1].type = 0; + m_pSvcDef = svc; } - // end of services must indicate to api with type = 0 - svc[1].type = 0; - - rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)svc); + rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; } - rc = ble_gatts_add_svcs((const ble_gatt_svc_def*)svc); + rc = ble_gatts_add_svcs((const ble_gatt_svc_def*)m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; diff --git a/src/NimBLEService.h b/src/NimBLEService.h index 4c1fa2a..191d97f 100644 --- a/src/NimBLEService.h +++ b/src/NimBLEService.h @@ -57,6 +57,7 @@ public: private: NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer); NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer); + ~NimBLEService(); friend class NimBLEServer; friend class NimBLEDevice; @@ -65,7 +66,8 @@ private: NimBLEServer* m_pServer; NimBLEUUID m_uuid; uint16_t m_numHandles; - + ble_gatt_svc_def* m_pSvcDef; + uint8_t m_removed; std::vector m_chrVec; }; // NimBLEService