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.
This commit is contained in:
h2zero 2020-07-13 21:24:07 -06:00 committed by GitHub
parent 5857879612
commit 2a5df0c905
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 275 additions and 75 deletions

View file

@ -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. A new method `NimBLEServer::advertiseOnDisconnect(bool)` has been implemented to control this, true(default) = enabled.
<br/> <br/>
`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`.
<br/>
# Client # Client
NimBLERemoteCharacteristic::readValue(time_t\*, bool) NimBLERemoteCharacteristic::readValue(time_t\*, bool)

View file

@ -63,6 +63,7 @@ NimBLEAdvertising::NimBLEAdvertising() {
*/ */
void NimBLEAdvertising::addServiceUUID(const NimBLEUUID &serviceUUID) { void NimBLEAdvertising::addServiceUUID(const NimBLEUUID &serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID); m_serviceUUIDs.push_back(serviceUUID);
m_advDataSet = false;
} // addServiceUUID } // addServiceUUID
@ -75,6 +76,22 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) {
} // addServiceUUID } // 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. * @brief Set the device appearance in the advertising data.
* The codes for distinct appearances can be found here:\n * The codes for distinct appearances can be found here:\n

View file

@ -75,6 +75,7 @@ public:
NimBLEAdvertising(); NimBLEAdvertising();
void addServiceUUID(const NimBLEUUID &serviceUUID); void addServiceUUID(const NimBLEUUID &serviceUUID);
void addServiceUUID(const char* serviceUUID); void addServiceUUID(const char* serviceUUID);
void removeServiceUUID(const NimBLEUUID &serviceUUID);
void start(); void start();
void stop(); void stop();
void setAppearance(uint16_t appearance); void setAppearance(uint16_t appearance);

View file

@ -58,6 +58,9 @@ NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t prop
* @brief Destructor. * @brief Destructor.
*/ */
NimBLECharacteristic::~NimBLECharacteristic() { NimBLECharacteristic::~NimBLECharacteristic() {
for(auto &it : m_dscVec) {
delete it;
}
} // ~NimBLECharacteristic } // ~NimBLECharacteristic

View file

@ -22,6 +22,10 @@
#include "NimBLEDevice.h" #include "NimBLEDevice.h"
#include "NimBLELog.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 const char* LOG_TAG = "NimBLEServer";
static NimBLEServerCallbacks defaultCallbacks; static NimBLEServerCallbacks defaultCallbacks;
@ -37,9 +41,20 @@ NimBLEServer::NimBLEServer() {
m_pServerCallbacks = &defaultCallbacks; m_pServerCallbacks = &defaultCallbacks;
m_gattsStarted = false; m_gattsStarted = false;
m_advertiseOnDisconnect = true; m_advertiseOnDisconnect = true;
m_svcChanged = false;
} // NimBLEServer } // NimBLEServer
/**
* @brief Destructor: frees all resources / attributes created.
*/
NimBLEServer::~NimBLEServer() {
for(auto &it : m_svcVec) {
delete it;
}
}
/** /**
* @brief Create a %BLE Service. * @brief Create a %BLE Service.
* @param [in] uuid The UUID of the new 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); NimBLEService* pService = new NimBLEService(uuid, numHandles, this);
m_svcVec.push_back(pService); // Save a reference to this service being on this server. 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"); NIMBLE_LOGD(LOG_TAG, "<< createService");
return pService; return pService;
} // createService } // createService
@ -146,8 +167,16 @@ void NimBLEServer::start() {
NIMBLE_LOGI(LOG_TAG, "Service changed characterisic handle: %d", m_svcChgChrHdl); 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) { 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) { for(auto &chr : svc->m_chrVec) {
// if Notify / Indicate is enabled but we didn't create the descriptor // if Notify / Indicate is enabled but we didn't create the descriptor
// we do it now. // we do it now.
@ -260,6 +289,11 @@ size_t NimBLEServer::getConnectedCount() {
server->m_connectedPeersVec.end(), server->m_connectedPeersVec.end(),
event->disconnect.conn.conn_handle), event->disconnect.conn.conn_handle),
server->m_connectedPeersVec.end()); server->m_connectedPeersVec.end());
if(server->m_svcChanged) {
server->resetGATT();
}
server->m_pServerCallbacks->onDisconnect(server); server->m_pServerCallbacks->onDisconnect(server);
if(server->m_advertiseOnDisconnect) { if(server->m_advertiseOnDisconnect) {
@ -445,6 +479,111 @@ void NimBLEServer::setCallbacks(NimBLEServerCallbacks* pCallbacks) {
} // setCallbacks } // 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. * @brief Start advertising.
* *

View file

@ -41,6 +41,8 @@ public:
NimBLEService* createService(const char* uuid); NimBLEService* createService(const char* uuid);
NimBLEService* createService(const NimBLEUUID &uuid, uint32_t numHandles=15, NimBLEService* createService(const NimBLEUUID &uuid, uint32_t numHandles=15,
uint8_t inst_id=0); uint8_t inst_id=0);
void removeService(NimBLEService* service, bool deleteSvc = false);
void addService(NimBLEService* service);
NimBLEAdvertising* getAdvertising(); NimBLEAdvertising* getAdvertising();
void setCallbacks(NimBLEServerCallbacks* pCallbacks); void setCallbacks(NimBLEServerCallbacks* pCallbacks);
void startAdvertising(); void startAdvertising();
@ -59,12 +61,14 @@ public:
private: private:
NimBLEServer(); NimBLEServer();
~NimBLEServer();
friend class NimBLECharacteristic; friend class NimBLECharacteristic;
friend class NimBLEDevice; friend class NimBLEDevice;
friend class NimBLEAdvertising; friend class NimBLEAdvertising;
bool m_gattsStarted; bool m_gattsStarted;
bool m_advertiseOnDisconnect; bool m_advertiseOnDisconnect;
bool m_svcChanged;
NimBLEServerCallbacks* m_pServerCallbacks; NimBLEServerCallbacks* m_pServerCallbacks;
std::vector<uint16_t> m_connectedPeersVec; std::vector<uint16_t> m_connectedPeersVec;
@ -74,6 +78,7 @@ private:
std::vector<NimBLECharacteristic*> m_notifyChrVec; std::vector<NimBLECharacteristic*> m_notifyChrVec;
static int handleGapEvent(struct ble_gap_event *event, void *arg); static int handleGapEvent(struct ble_gap_event *event, void *arg);
void resetGATT();
}; // NimBLEServer }; // NimBLEServer

View file

@ -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. * @param [in] a pointer to the server instance that this service belongs to.
*/ */
NimBLEService::NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer) { NimBLEService::NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer) {
m_uuid = uuid; m_uuid = uuid;
m_handle = NULL_HANDLE; m_handle = NULL_HANDLE;
m_pServer = pServer; m_pServer = pServer;
m_numHandles = numHandles; m_numHandles = numHandles;
m_pSvcDef = nullptr;
m_removed = 0;
} // NimBLEService } // 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. * @brief Dump details of this BLE GATT service.
* @return N/A. * @return N/A.
@ -94,98 +116,101 @@ NimBLEUUID NimBLEService::getUUID() {
* and registers it with the NimBLE stack. * and registers it with the NimBLE stack.
* @return bool success/failure . * @return bool success/failure .
*/ */
bool NimBLEService::start() { bool NimBLEService::start() {
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
int rc = 0; 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; if(m_pSvcDef == nullptr) {
svc[0].uuid = &m_uuid.getNative()->u; // Nimble requires an array of services to be sent to the api
svc[0].includes = NULL; // 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){ NIMBLE_LOGD(LOG_TAG,"Adding %d characteristics for service %s", numChrs, toString().c_str());
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();
for(uint8_t i=0; i < numChrs;) { if(!numChrs){
uint8_t numDscs = pCharacteristic->m_dscVec.size(); svc[0].characteristics = NULL;
if(numDscs) { }else{
// skip 2902 as it's automatically created by NimBLE // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end
// if Indicate or Notify flags are set // of the characteristics for the service. We create 1 extra and set it to null
if(((pCharacteristic->m_properties & BLE_GATT_CHR_F_INDICATE) || // for this purpose.
(pCharacteristic->m_properties & BLE_GATT_CHR_F_NOTIFY)) && pChr_a = new ble_gatt_chr_def[numChrs+1];
pCharacteristic->getDescriptorByUUID("2902") != nullptr) NimBLECharacteristic* pCharacteristic = *m_chrVec.begin();
{
numDscs--;
}
}
if(!numDscs){ for(uint8_t i=0; i < numChrs;) {
pChr_a[i].descriptors = NULL; uint8_t numDscs = pCharacteristic->m_dscVec.size();
} else { if(numDscs) {
// Must have last descriptor uuid = 0 so we have to create 1 extra // skip 2902 as it's automatically created by NimBLE
//NIMBLE_LOGD(LOG_TAG, "Adding %d descriptors", numDscs); // if Indicate or Notify flags are set
pDsc_a = new ble_gatt_dsc_def[numDscs+1]; if(((pCharacteristic->m_properties & BLE_GATT_CHR_F_INDICATE) ||
NimBLEDescriptor* pDescriptor = *pCharacteristic->m_dscVec.begin(); (pCharacteristic->m_properties & BLE_GATT_CHR_F_NOTIFY)) &&
for(uint8_t d=0; d < numDscs;) { pCharacteristic->getDescriptorByUUID("2902") != nullptr)
// skip 2902 {
if(pDescriptor->m_uuid == NimBLEUUID(uint16_t(0x2902))) { numDscs--;
//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; if(!numDscs){
pChr_a[i].descriptors = pDsc_a; 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[numChrs].uuid = NULL;
pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent; svc[0].characteristics = pChr_a;
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; // end of services must indicate to api with type = 0
svc[0].characteristics = pChr_a; svc[1].type = 0;
m_pSvcDef = svc;
} }
// end of services must indicate to api with type = 0 rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)m_pSvcDef);
svc[1].type = 0;
rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)svc);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; 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) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; return false;

View file

@ -57,6 +57,7 @@ public:
private: private:
NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer); NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer);
NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer); NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer);
~NimBLEService();
friend class NimBLEServer; friend class NimBLEServer;
friend class NimBLEDevice; friend class NimBLEDevice;
@ -65,7 +66,8 @@ private:
NimBLEServer* m_pServer; NimBLEServer* m_pServer;
NimBLEUUID m_uuid; NimBLEUUID m_uuid;
uint16_t m_numHandles; uint16_t m_numHandles;
ble_gatt_svc_def* m_pSvcDef;
uint8_t m_removed;
std::vector<NimBLECharacteristic*> m_chrVec; std::vector<NimBLECharacteristic*> m_chrVec;
}; // NimBLEService }; // NimBLEService