From 2a5df0c9051d48a1979abc7a91b53824fc47e53d Mon Sep 17 00:00:00 2001
From: h2zero <32826625+h2zero@users.noreply.github.com>
Date: Mon, 13 Jul 2020 21:24:07 -0600
Subject: [PATCH] Add feature: remove service (#17)
* Get service handles on server start
* remove service + indicate service changed
* Reset gatt services when no connections active and services changed.
* NimBLEServer::createService can now be used any time and will send service changed
indication if server was already active.
* Add ability to remove advertised serviceUUIDS
* Adds addService() method to server to be allow user to re-add a service previously removed
* Add destructior to NimBLEServer, NimBLEService and NimBLECharacteristic to release allocated resources.
---
docs/Improvements_and_updates.md | 8 ++
src/NimBLEAdvertising.cpp | 17 +++
src/NimBLEAdvertising.h | 1 +
src/NimBLECharacteristic.cpp | 3 +
src/NimBLEServer.cpp | 141 ++++++++++++++++++++++++-
src/NimBLEServer.h | 5 +
src/NimBLEService.cpp | 171 ++++++++++++++++++-------------
src/NimBLEService.h | 4 +-
8 files changed, 275 insertions(+), 75 deletions(-)
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