Skip to content

Commit 43ff77c

Browse files
committed
Properly set attribute handles and improve dynamic service changes
This changes how attribute handles are set so they can be correctly identified when there is more than one attribute with the same UUID. Instead of reading from the stack by UUID to get the handles this will now use the registration callback to set them correctly. This also improves handling of dynamic service changes by properly removing characteristics/descriptors when required and resetting the GATT when advertising is started instead of after the last client disconnects. * Adds NimBLEUUID constructor overload for ble_uuid_t*. * NimBLECharacteristic::getDescriptorByUUID now takes an optional index value to support multiple same-uuid descriptors.
1 parent 8576ae8 commit 43ff77c

7 files changed

Lines changed: 182 additions & 57 deletions

File tree

src/NimBLECharacteristic.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,27 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor* pDescriptor, bool
165165
/**
166166
* @brief Return the BLE Descriptor for the given UUID.
167167
* @param [in] uuid The UUID of the descriptor.
168+
* @param [in] index The index of the descriptor to return (used when multiple descriptors have the same UUID).
168169
* @return A pointer to the descriptor object or nullptr if not found.
169170
*/
170-
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) const {
171-
return getDescriptorByUUID(NimBLEUUID(uuid));
171+
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid, uint16_t index) const {
172+
return getDescriptorByUUID(NimBLEUUID(uuid), index);
172173
} // getDescriptorByUUID
173174

174175
/**
175176
* @brief Return the BLE Descriptor for the given UUID.
176177
* @param [in] uuid The UUID of the descriptor.
178+
* @param [in] index The index of the descriptor to return (used when multiple descriptors have the same UUID).
177179
* @return A pointer to the descriptor object or nullptr if not found.
178180
*/
179-
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid) const {
181+
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid, uint16_t index) const {
182+
uint16_t position = 0;
180183
for (const auto& dsc : m_vDescriptors) {
181184
if (dsc->getUUID() == uuid) {
182-
return dsc;
185+
if (position == index) {
186+
return dsc;
187+
}
188+
position++;
183189
}
184190
}
185191
return nullptr;

src/NimBLECharacteristic.h

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
6969
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
7070
uint16_t maxLen = BLE_ATT_ATTR_MAX_LEN);
7171
NimBLE2904* create2904();
72-
NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const;
73-
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const;
72+
NimBLEDescriptor* getDescriptorByUUID(const char* uuid, uint16_t index = 0) const;
73+
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid, uint16_t index = 0) const;
7474
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const;
7575
NimBLEService* getService() const;
7676

@@ -113,7 +113,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
113113
}
114114

115115
/**
116-
* @brief Template to send a notification with a value from a class that has a data() and size() method with value_type.
116+
* @brief Template to send a notification with a value from a class that has a data() and size() method with value_type.
117117
* @param [in] v The value to send.
118118
* @param [in] connHandle Optional, a connection handle to send the notification to.
119119
* @details Correctly calculates byte size for containers with multi-byte element types.
@@ -125,11 +125,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
125125
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
126126
# endif
127127
notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
128-
return notify(
129-
reinterpret_cast<const uint8_t*>(v.data()),
130-
v.size() * sizeof(typename T::value_type),
131-
connHandle
132-
);
128+
return notify(reinterpret_cast<const uint8_t*>(v.data()), v.size() * sizeof(typename T::value_type), connHandle);
133129
}
134130

135131
/**
@@ -193,11 +189,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
193189
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
194190
# endif
195191
indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
196-
return indicate(
197-
reinterpret_cast<const uint8_t*>(v.data()),
198-
v.size() * sizeof(typename T::value_type),
199-
connHandle
200-
);
192+
return indicate(reinterpret_cast<const uint8_t*>(v.data()), v.size() * sizeof(typename T::value_type), connHandle);
201193
}
202194

203195
/**
@@ -232,7 +224,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
232224
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
233225
if constexpr (Has_data_size<T>::value) {
234226
if constexpr (Has_value_type<T>::value) {
235-
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
227+
return notify(reinterpret_cast<const uint8_t*>(value.data()),
228+
value.size() * sizeof(typename T::value_type),
229+
connHandle);
236230
} else {
237231
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
238232
}
@@ -258,7 +252,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
258252
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
259253
if constexpr (Has_data_size<T>::value) {
260254
if constexpr (Has_value_type<T>::value) {
261-
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
255+
return indicate(reinterpret_cast<const uint8_t*>(value.data()),
256+
value.size() * sizeof(typename T::value_type),
257+
connHandle);
262258
} else {
263259
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
264260
}

src/NimBLEServer.cpp

Lines changed: 131 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
static const char* LOG_TAG = "NimBLEServer";
4040
static NimBLEServerCallbacks defaultCallbacks;
4141

42+
struct gattRegisterCallbackArgs {
43+
NimBLEService* pSvc{nullptr};
44+
NimBLECharacteristic* pChar{nullptr};
45+
};
46+
4247
/**
4348
* @brief Construct a BLE Server
4449
*
@@ -188,16 +193,96 @@ void NimBLEServer::serviceChanged() {
188193
}
189194
} // serviceChanged
190195

196+
/**
197+
* @brief Callback for GATT registration events,
198+
* used to obtain the assigned handles for services, characteristics, and descriptors.
199+
* @param [in] ctxt The context of the registration event.
200+
* @param [in] arg Unused.
201+
*/
202+
void NimBLEServer::gattRegisterCallback(ble_gatt_register_ctxt* ctxt, void* arg) {
203+
gattRegisterCallbackArgs* args = static_cast<gattRegisterCallbackArgs*>(arg);
204+
205+
if (ctxt->op == BLE_GATT_REGISTER_OP_SVC) {
206+
NimBLEUUID uuid(ctxt->svc.svc_def->uuid);
207+
for (auto i = 0; i < NimBLEDevice::getServer()->m_svcVec.size(); i++) {
208+
auto pSvc = NimBLEDevice::getServer()->getServiceByUUID(uuid, i);
209+
if (pSvc != nullptr && pSvc->m_handle == 0) {
210+
pSvc->m_handle = ctxt->svc.handle;
211+
NIMBLE_LOGD(LOG_TAG, "Service registered: %s, handle=%d", uuid.toString().c_str(), ctxt->svc.handle);
212+
// Set the arg to the service so we know that the following
213+
// characteristics and descriptors belong to this service
214+
args->pSvc = pSvc;
215+
break;
216+
}
217+
}
218+
219+
return;
220+
}
221+
222+
if (args->pSvc == nullptr) {
223+
// If the service is not found then this is likely a characteristic or descriptor that was registered as
224+
// part of the GATT server setup and not found in the service vector
225+
NIMBLE_LOGD(LOG_TAG, "Skipping characteristic or descriptor registered with unknown service");
226+
return;
227+
}
228+
229+
if (ctxt->op == BLE_GATT_REGISTER_OP_CHR) {
230+
NimBLEUUID uuid(ctxt->chr.chr_def->uuid);
231+
for (auto i = 0; i < args->pSvc->m_vChars.size(); i++) {
232+
auto pChr = args->pSvc->getCharacteristic(uuid, i);
233+
if (pChr != nullptr && pChr->m_handle == 0) {
234+
pChr->m_handle = ctxt->chr.val_handle;
235+
// Set the arg to the characteristic so we know that the following descriptors belong to this characteristic
236+
args->pChar = pChr;
237+
NIMBLE_LOGD(LOG_TAG,
238+
"Characteristic registered: %s, def_handle=%d, val_handle=%d",
239+
uuid.toString().c_str(),
240+
ctxt->chr.def_handle,
241+
ctxt->chr.val_handle);
242+
break;
243+
}
244+
}
245+
246+
return;
247+
}
248+
249+
if (ctxt->op == BLE_GATT_REGISTER_OP_DSC) {
250+
if (args->pChar == nullptr) {
251+
NIMBLE_LOGE(LOG_TAG, "Descriptor registered with unknown characteristic, skipping");
252+
return;
253+
}
254+
255+
NimBLEUUID uuid(ctxt->dsc.dsc_def->uuid);
256+
for (auto i = 0; i < args->pChar->m_vDescriptors.size(); i++) {
257+
auto pDsc = args->pChar->getDescriptorByUUID(uuid, i);
258+
if (pDsc != nullptr && pDsc->m_handle == 0) {
259+
pDsc->m_handle = ctxt->dsc.handle;
260+
NIMBLE_LOGD(LOG_TAG, "Descriptor registered: %s, handle=%d", uuid.toString().c_str(), ctxt->dsc.handle);
261+
return;
262+
}
263+
}
264+
}
265+
}
266+
191267
/**
192268
* @brief Start the GATT server.
193269
* @details Required to be called after setup of all services and characteristics / descriptors
194270
* for the NimBLE host to register them.
195271
*/
196272
void NimBLEServer::start() {
273+
if (m_svcChanged && !getConnectedCount()) {
274+
NIMBLE_LOGD(LOG_TAG, "Services have changed since last start, resetting GATT server");
275+
resetGATT();
276+
}
277+
197278
if (m_gattsStarted) {
198279
return; // already started
199280
}
200281

282+
ble_hs_cfg.gatts_register_cb = NimBLEServer::gattRegisterCallback;
283+
gattRegisterCallbackArgs args{};
284+
ble_hs_cfg.gatts_register_arg = &args;
285+
201286
int rc = ble_gatts_start();
202287
if (rc != 0) {
203288
NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
@@ -206,29 +291,20 @@ void NimBLEServer::start() {
206291

207292
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 4
208293
ble_gatts_show_local();
209-
# endif
210294

211-
// Get the assigned service handles and build a vector of characteristics
212-
// with Notify / Indicate capabilities for event handling
295+
// Check that all services were registered and log if any are missing.
213296
for (const auto& svc : m_svcVec) {
214297
if (svc->getRemoved() == 0) {
215-
rc = ble_gatts_find_svc(svc->getUUID().getBase(), &svc->m_handle);
298+
rc = ble_gatts_find_svc(svc->getUUID().getBase(), NULL);
216299
if (rc != 0) {
217-
NIMBLE_LOGW(LOG_TAG,
300+
NIMBLE_LOGD(LOG_TAG,
218301
"GATT Server started without service: %s, Service %s",
219302
svc->getUUID().toString().c_str(),
220303
svc->isStarted() ? "missing" : "not started");
221-
continue; // Skip this service as it was not started
222-
}
223-
}
224-
225-
// Set the descriptor handles now as the stack does not set these when the service is started
226-
for (const auto& chr : svc->m_vChars) {
227-
for (auto& desc : chr->m_vDescriptors) {
228-
ble_gatts_find_dsc(svc->getUUID().getBase(), chr->getUUID().getBase(), desc->getUUID().getBase(), &desc->m_handle);
229304
}
230305
}
231306
}
307+
# endif
232308

233309
// If the services have changed indicate it now
234310
if (m_svcChanged) {
@@ -427,10 +503,6 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
427503
}
428504
# endif
429505

430-
if (pServer->m_svcChanged) {
431-
pServer->resetGATT();
432-
}
433-
434506
peerInfo.m_desc = event->disconnect.conn;
435507
pServer->m_pServerCallbacks->onDisconnect(pServer, peerInfo, event->disconnect.reason);
436508
# if !MYNEWT_VAL(BLE_EXT_ADV)
@@ -784,32 +856,60 @@ void NimBLEServer::addService(NimBLEService* service) {
784856
* @brief Resets the GATT server, used when services are added/removed after initialization.
785857
*/
786858
void NimBLEServer::resetGATT() {
787-
if (getConnectedCount() > 0) {
788-
return;
789-
}
790-
791859
# if MYNEWT_VAL(BLE_ROLE_BROADCASTER)
792860
NimBLEDevice::stopAdvertising();
793861
# endif
862+
794863
ble_gatts_reset();
795864
ble_svc_gap_init();
796865
ble_svc_gatt_init();
797866

798-
for (auto it = m_svcVec.begin(); it != m_svcVec.end();) {
799-
if ((*it)->getRemoved() > 0) {
800-
if ((*it)->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
801-
delete *it;
802-
it = m_svcVec.erase(it);
803-
} else {
804-
++it;
805-
}
867+
// We clear the flag so the services can be started in the loop below
868+
m_svcChanged = false;
869+
870+
for (auto svcIt = m_svcVec.begin(); svcIt != m_svcVec.end();) {
871+
auto* pSvc = *svcIt;
872+
if (pSvc->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
873+
delete pSvc;
874+
svcIt = m_svcVec.erase(svcIt);
806875
continue;
807876
}
808877

809-
(*it)->start();
810-
++it;
878+
for (auto chrIt = pSvc->m_vChars.begin(); chrIt != pSvc->m_vChars.end();) {
879+
auto* pChr = *chrIt;
880+
if (pChr->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
881+
delete pChr;
882+
chrIt = pSvc->m_vChars.erase(chrIt);
883+
continue;
884+
}
885+
886+
for (auto dscIt = pChr->m_vDescriptors.begin(); dscIt != pChr->m_vDescriptors.end();) {
887+
auto* pDsc = *dscIt;
888+
if (pDsc->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
889+
delete pDsc;
890+
dscIt = pChr->m_vDescriptors.erase(dscIt);
891+
continue;
892+
}
893+
894+
pDsc->m_handle = 0;
895+
++dscIt;
896+
}
897+
898+
pChr->m_handle = 0;
899+
++chrIt;
900+
}
901+
902+
if (pSvc->getRemoved() == 0) {
903+
pSvc->start();
904+
}
905+
906+
pSvc->m_handle = 0;
907+
++svcIt;
811908
}
812909

910+
// Restore the flag to indicate the services have changed so that the
911+
// GATT server start will send a service changed indication.
912+
m_svcChanged = true;
813913
m_gattsStarted = false;
814914
} // resetGATT
815915

src/NimBLEServer.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ class NimBLEServer {
119119

120120
NimBLEServer();
121121
~NimBLEServer();
122-
static int handleGapEvent(struct ble_gap_event* event, void* arg);
123-
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
124-
void serviceChanged();
125-
void resetGATT();
122+
static int handleGapEvent(struct ble_gap_event* event, void* arg);
123+
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
124+
static void gattRegisterCallback(struct ble_gatt_register_ctxt* ctxt, void* arg);
125+
void serviceChanged();
126+
void resetGATT();
126127

127128
bool m_gattsStarted : 1;
128129
bool m_svcChanged : 1;

src/NimBLEService.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ void NimBLEService::dump() const {
8989
* @return bool success/failure .
9090
*/
9191
bool NimBLEService::start() {
92-
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
92+
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: UUID: %s", getUUID().toString().c_str());
93+
// If the server has started before then we need to reset the GATT server
94+
// to update the service/characteristic/descriptor definitions. If characteristics or descriptors
95+
// have been added/removed since the last server start then this service will be started on gatt reset.
96+
if (getServer()->m_svcChanged) {
97+
NIMBLE_LOGW(LOG_TAG, "<< start(): GATT server already started, cannot start service");
98+
return false;
99+
}
93100

94101
// If started previously and no characteristics have been added or removed,
95102
// then we can skip the service registration process.
@@ -113,7 +120,7 @@ bool NimBLEService::start() {
113120
++numChrs;
114121
}
115122

116-
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
123+
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, getUUID().toString().c_str());
117124
if (numChrs) {
118125
int i = 0;
119126

@@ -160,7 +167,7 @@ bool NimBLEService::start() {
160167
pChrs[i].arg = chr;
161168
pChrs[i].flags = chr->getProperties();
162169
pChrs[i].min_key_size = 0;
163-
pChrs[i].val_handle = &chr->m_handle;
170+
pChrs[i].val_handle = nullptr;
164171
++i;
165172
}
166173

src/NimBLEUUID.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ static const uint8_t ble_base_uuid[] = {
3838
*/
3939
NimBLEUUID::NimBLEUUID(const ble_uuid_any_t& uuid) : m_uuid{uuid} {}
4040

41+
/**
42+
* @brief Create a UUID from the native UUID pointer.
43+
* @param [in] uuid The native UUID pointer.
44+
*/
45+
NimBLEUUID::NimBLEUUID(const ble_uuid_t* uuid) {
46+
if (uuid == nullptr) {
47+
NIMBLE_LOGE(LOG_TAG, "Invalid UUID pointer");
48+
m_uuid.u.type = 0;
49+
return;
50+
}
51+
52+
ble_uuid_copy(&m_uuid, uuid);
53+
}
54+
4155
/**
4256
* @brief Create a UUID from a string.
4357
*

0 commit comments

Comments
 (0)