diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 42579791..a83047ef 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -54,6 +54,7 @@ NimBLEServer::NimBLEServer() : m_gattsStarted{false}, m_svcChanged{false}, m_deleteCallbacks{false}, + m_registerServicesFirst{false}, # if !MYNEWT_VAL(BLE_EXT_ADV) m_advertiseOnDisconnect{false}, # endif @@ -366,6 +367,20 @@ void NimBLEServer::advertiseOnDisconnect(bool enable) { } // advertiseOnDisconnect # endif +/** + * @brief Register the application's services before the standard GAP/GATT services. + * @param [in] enable true == register the application services first (they take the + * low attribute handles, starting at 0x0001, and GAP/GATT are placed after them); + * false (default) == the standard behavior, GAP/GATT register first and take the low + * handles. + * @details Must be called before the services are started (i.e. before + * NimBLEServer::start()). Useful when a peripheral must expose a fixed attribute-table + * layout that another device relies on by hardcoded handle rather than discovery. + */ +void NimBLEServer::registerServicesFirst(bool enable) { + m_registerServicesFirst = enable; +} // registerServicesFirst + /** * @brief Return the number of connected clients. * @return The number of connected clients. @@ -897,14 +912,24 @@ bool NimBLEServer::resetGATT() { #endif ble_gatts_reset(); - ble_svc_gap_init(); + // Register the standard GAP (0x1800) and GATT (0x1801) services, restoring the + // device name/appearance that ble_gatts_reset() clears. By default this happens + // first, so GAP/GATT take the low attribute handles (0x0001+). When + // registerServicesFirst() is enabled, it is deferred until after the application + // services below, so those take the low handles instead. + auto initGapGattServices = [&]() { + ble_svc_gap_init(); #ifndef CONFIG_USING_NIMBLE_COMPONENT - ble_svc_gap_device_name_set(name.c_str()); - ble_svc_gap_device_appearance_set(appearance); + ble_svc_gap_device_name_set(name.c_str()); + ble_svc_gap_device_appearance_set(appearance); #endif + ble_svc_gatt_init(); + }; - ble_svc_gatt_init(); + if (!m_registerServicesFirst) { + initGapGattServices(); + } for (auto svcIt = m_svcVec.begin(); svcIt != m_svcVec.end();) { auto* pSvc = *svcIt; @@ -941,6 +966,12 @@ bool NimBLEServer::resetGATT() { if (pSvc->getRemoved() == 0) { if (!pSvc->start_internal()) { NIMBLE_LOGE(LOG_TAG, "Failed to start service: %s", pSvc->getUUID().toString().c_str()); + // When deferring GAP/GATT (registerServicesFirst), still register + // them on the failure path so the mandatory GAP/GATT services (and + // the restored name/appearance) are never left out of the database. + if (m_registerServicesFirst) { + initGapGattServices(); + } return false; } } @@ -949,6 +980,10 @@ bool NimBLEServer::resetGATT() { ++svcIt; } + if (m_registerServicesFirst) { + initGapGattServices(); + } + return true; } // resetGATT diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index b2b79e1e..70ff21d9 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -81,6 +81,7 @@ class NimBLEServer { NimBLEConnInfo getPeerInfo(const NimBLEAddress& address) const; NimBLEConnInfo getPeerInfoByHandle(uint16_t connHandle) const; void advertiseOnDisconnect(bool enable); + void registerServicesFirst(bool enable); void setDataLen(uint16_t connHandle, uint16_t tx_octets) const; bool updatePhy(uint16_t connHandle, uint8_t txPhysMask, uint8_t rxPhysMask, uint16_t phyOptions); bool getPhy(uint16_t connHandle, uint8_t* txPhy, uint8_t* rxPhy); @@ -129,6 +130,7 @@ class NimBLEServer { bool m_gattsStarted : 1; bool m_svcChanged : 1; bool m_deleteCallbacks : 1; + bool m_registerServicesFirst : 1; # if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER) bool m_advertiseOnDisconnect : 1; # endif