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.
<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
NimBLERemoteCharacteristic::readValue(time_t\*, bool)

View file

@ -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

View file

@ -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);

View file

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

View file

@ -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.
*

View file

@ -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<uint16_t> m_connectedPeersVec;
@ -74,6 +78,7 @@ private:
std::vector<NimBLECharacteristic*> m_notifyChrVec;
static int handleGapEvent(struct ble_gap_event *event, void *arg);
void resetGATT();
}; // 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.
*/
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;

View file

@ -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<NimBLECharacteristic*> m_chrVec;
}; // NimBLEService