diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp index a19b49ea..719b44d8 100644 --- a/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp @@ -80,9 +80,6 @@ extern "C" void app_main(void) { pCharacteristic->setValue("Hello World"); - /** Start the service */ - pService->start(); - /** * Create an extended advertisement with the instance ID 0 and set the PHY's. * Multiple instances can be added as long as the instance ID is incremented. diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp index 3322e543..3752570f 100644 --- a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp @@ -98,9 +98,6 @@ extern "C" void app_main(void) { pCharacteristic->setValue("Hello World"); - /** Start the service */ - pService->start(); - /** Create our multi advertising instances */ /** extended scannable instance advertising on coded and 1m PHY's. */ diff --git a/examples/NimBLE_Server/main/main.cpp b/examples/NimBLE_Server/main/main.cpp index 7f8d2650..d2b76533 100644 --- a/examples/NimBLE_Server/main/main.cpp +++ b/examples/NimBLE_Server/main/main.cpp @@ -184,10 +184,6 @@ extern "C" void app_main(void) { pC01Ddsc->setValue("Send it back!"); pC01Ddsc->setCallbacks(&dscCallbacks); - /** Start the services when finished creating all Characteristics and Descriptors */ - pDeadService->start(); - pBaadService->start(); - /** Create an advertising instance and add the services to the advertised data */ NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->setName("NimBLE-Server"); diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp index a93e9eef..53ec2235 100644 --- a/src/NimBLEAdvertising.cpp +++ b/src/NimBLEAdvertising.cpp @@ -197,8 +197,9 @@ bool NimBLEAdvertising::start(uint32_t duration, const NimBLEAddress* dirAddr) { # if MYNEWT_VAL(BLE_ROLE_PERIPHERAL) NimBLEServer* pServer = NimBLEDevice::getServer(); - if (pServer != nullptr) { - pServer->start(); // make sure the GATT server is ready before advertising + if (pServer != nullptr && !pServer->start()) { // make sure the GATT server is ready before advertising + NIMBLE_LOGE(LOG_TAG, "Failed to start GATT server"); + return false; } # endif diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index 0ec9c83c..0b04dea2 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -1210,7 +1210,16 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) { break; } - if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + struct ble_sm_io pkey = {0, 0}; + pkey.action = event->passkey.params.action; + pkey.passkey = NimBLEDevice::getSecurityPasskey(); + if (pkey.passkey == 123456) { + pkey.passkey = pClient->m_pClientCallbacks->onPassKeyDisplay(peerInfo); + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp); pClient->m_pClientCallbacks->onConfirmPasskey(peerInfo, event->passkey.params.numcmp); } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { @@ -1314,6 +1323,11 @@ void NimBLEClientCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo) { NimBLEDevice::injectPassKey(connInfo, 123456); } // onPassKeyEntry +uint32_t NimBLEClientCallbacks::onPassKeyDisplay(NimBLEConnInfo& connInfo) { + NIMBLE_LOGD(CB_TAG, "onPassKeyDisplay: default"); + return NimBLEDevice::getSecurityPasskey(); +} // onPassKeyDisplay + void NimBLEClientCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo) { NIMBLE_LOGD(CB_TAG, "onAuthenticationComplete: default"); } // onAuthenticationComplete diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h index 2dac6563..56cf7ae2 100644 --- a/src/NimBLEClient.h +++ b/src/NimBLEClient.h @@ -187,6 +187,13 @@ class NimBLEClientCallbacks { */ virtual void onPassKeyEntry(NimBLEConnInfo& connInfo); + /** + * @brief Called when using passkey entry pairing and the passkey should be displayed. + * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. + * @return The passkey to display to the user. The peer device must enter this passkey to complete the pairing. + */ + virtual uint32_t onPassKeyDisplay(NimBLEConnInfo& connInfo); + /** * @brief Called when the pairing procedure is complete. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n diff --git a/src/NimBLECppVersion.h b/src/NimBLECppVersion.h new file mode 100644 index 00000000..fec436bd --- /dev/null +++ b/src/NimBLECppVersion.h @@ -0,0 +1,80 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NIMBLE_CPP_VERSION_H_ +#define NIMBLE_CPP_VERSION_H_ + +/** @brief NimBLE-Arduino library major version number. */ +#define NIMBLE_CPP_VERSION_MAJOR 2 + +/** @brief NimBLE-Arduino library minor version number. */ +#define NIMBLE_CPP_VERSION_MINOR 3 + +/** @brief NimBLE-Arduino library patch version number. */ +#define NIMBLE_CPP_VERSION_PATCH 9 + +/** + * @brief Macro to create a version number for comparison. + * @param major Major version number. + * @param minor Minor version number. + * @param patch Patch version number. + * @details Example usage: + * @code{.cpp} + * #if NIMBLE_CPP_VERSION >= NIMBLE_CPP_VERSION_VAL(2, 0, 0) + * // Using NimBLE-Arduino v2 or later + * #endif + * @endcode + */ +#define NIMBLE_CPP_VERSION_VAL(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) + +/** + * @brief The library version as a single integer for compile-time comparison. + * @details Format: (major << 16) | (minor << 8) | patch + */ +#define NIMBLE_CPP_VERSION \ + NIMBLE_CPP_VERSION_VAL(NIMBLE_CPP_VERSION_MAJOR, NIMBLE_CPP_VERSION_MINOR, NIMBLE_CPP_VERSION_PATCH) + +/** @cond NIMBLE_CPP_INTERNAL */ +#define NIMBLE_CPP_VERSION_STRINGIFY_IMPL(x) #x +#define NIMBLE_CPP_VERSION_STRINGIFY(x) NIMBLE_CPP_VERSION_STRINGIFY_IMPL(x) +/** @endcond */ + +/** + * @brief Optional Semantic Versioning prerelease suffix. + * @details Include the leading '-' when defined, for example: "-beta.1" + */ +#ifndef NIMBLE_CPP_VERSION_PRERELEASE +# define NIMBLE_CPP_VERSION_PRERELEASE "" +#endif + +/** + * @brief Optional Semantic Versioning build metadata suffix. + * @details Include the leading '+' when defined, for example: "+sha.abcd1234" + */ +#ifndef NIMBLE_CPP_VERSION_BUILD_METADATA +# define NIMBLE_CPP_VERSION_BUILD_METADATA "" +#endif + +/** @brief library version as a prefixed Semantic Versioning string. */ +#define NIMBLE_CPP_VERSION_STR \ + "NimBLE-CPP " \ + NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_MAJOR) "." \ + NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_MINOR) "." \ + NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_PATCH) \ + NIMBLE_CPP_VERSION_PRERELEASE NIMBLE_CPP_VERSION_BUILD_METADATA + +#endif // NIMBLE_CPP_VERSION_H_ diff --git a/src/NimBLEDevice.cpp b/src/NimBLEDevice.cpp index cca52901..b26c1e92 100644 --- a/src/NimBLEDevice.cpp +++ b/src/NimBLEDevice.cpp @@ -122,9 +122,6 @@ extern "C" int ble_vhci_disc_duplicate_mode_enable(int mode); NimBLEServer* NimBLEDevice::createServer() { if (NimBLEDevice::m_pServer == nullptr) { NimBLEDevice::m_pServer = new NimBLEServer(); - ble_gatts_reset(); - ble_svc_gap_init(); - ble_svc_gatt_init(); } return m_pServer; @@ -867,7 +864,7 @@ void NimBLEDevice::onSync(void) { * @brief The main host task. */ void NimBLEDevice::host_task(void* param) { - NIMBLE_LOGI(LOG_TAG, "BLE Host Task Started"); + NIMBLE_LOGI(LOG_TAG, "NimBLE Started!"); nimble_port_run(); // This function will return only when nimble_port_stop() is executed nimble_port_freertos_deinit(); } // host_task @@ -878,6 +875,7 @@ void NimBLEDevice::host_task(void* param) { */ bool NimBLEDevice::init(const std::string& deviceName) { if (!m_initialized) { + NIMBLE_LOGD(LOG_TAG, "Starting %s", getVersion()); # ifdef ESP_PLATFORM # if defined(CONFIG_ENABLE_ARDUINO_DEPENDS) && SOC_BT_SUPPORTED @@ -999,6 +997,7 @@ bool NimBLEDevice::init(const std::string& deviceName) { } m_initialized = true; // Set the initialization flag to ensure we are only initialized once. + NIMBLE_LOGD(LOG_TAG, "Initialized"); return true; } // init @@ -1329,6 +1328,14 @@ std::string NimBLEDevice::toString() { return getAddress().toString(); } // toString +/** + * @brief Return the library version as a string. + * @return A const char* containing library version information. + */ +const char* NimBLEDevice::getVersion() { + return NIMBLE_CPP_VERSION_STR; +} // getVersion + # if MYNEWT_VAL(NIMBLE_CPP_DEBUG_ASSERT_ENABLED) || __DOXYGEN__ /** * @brief Debug assert - weak function. diff --git a/src/NimBLEDevice.h b/src/NimBLEDevice.h index df63b3dc..211c05c3 100644 --- a/src/NimBLEDevice.h +++ b/src/NimBLEDevice.h @@ -18,6 +18,7 @@ #ifndef NIMBLE_CPP_DEVICE_H_ #define NIMBLE_CPP_DEVICE_H_ +#include "NimBLECppVersion.h" #include "syscfg/syscfg.h" #if CONFIG_BT_NIMBLE_ENABLED # ifdef ESP_PLATFORM @@ -123,6 +124,7 @@ class NimBLEDevice { static bool isInitialized(); static NimBLEAddress getAddress(); static std::string toString(); + static const char* getVersion(); static bool whiteListAdd(const NimBLEAddress& address); static bool whiteListRemove(const NimBLEAddress& address); static bool onWhiteList(const NimBLEAddress& address); diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index d3d826ad..bcbec9a3 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -269,14 +269,18 @@ void NimBLEServer::gattRegisterCallback(ble_gatt_register_ctxt* ctxt, void* arg) * @details Required to be called after setup of all services and characteristics / descriptors * for the NimBLE host to register them. */ -void NimBLEServer::start() { +bool NimBLEServer::start() { if (m_svcChanged && !getConnectedCount()) { NIMBLE_LOGD(LOG_TAG, "Services have changed since last start, resetting GATT server"); - resetGATT(); + m_gattsStarted = false; } if (m_gattsStarted) { - return; // already started + return true; // already started + } + + if (!resetGATT()) { + return false; } ble_hs_cfg.gatts_register_cb = NimBLEServer::gattRegisterCallback; @@ -286,7 +290,7 @@ void NimBLEServer::start() { int rc = ble_gatts_start(); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); - return; + return false; } # if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 4 @@ -313,6 +317,7 @@ void NimBLEServer::start() { } m_gattsStarted = true; + return true; } // start /** @@ -335,7 +340,7 @@ bool NimBLEServer::disconnect(uint16_t connHandle, uint8_t reason) const { * @brief Disconnect the specified client with optional reason. * @param [in] connInfo Connection of the client to disconnect. * @param [in] reason code for disconnecting. - * @return NimBLE host return code. + * @return True if successful. */ bool NimBLEServer::disconnect(const NimBLEConnInfo& connInfo, uint8_t reason) const { return disconnect(connInfo.getConnHandle(), reason); @@ -505,7 +510,7 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) { peerInfo.m_desc = event->disconnect.conn; pServer->m_pServerCallbacks->onDisconnect(pServer, peerInfo, event->disconnect.reason); -# if !MYNEWT_VAL(BLE_EXT_ADV) +# if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER) if (pServer->m_advertiseOnDisconnect) { pServer->startAdvertising(); } @@ -684,6 +689,15 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) { // } // rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); // NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_OOB; ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + NIMBLE_LOGD(LOG_TAG, "Enter the passkey"); + + rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc); + if (rc != 0) { + return BLE_ATT_ERR_INVALID_HANDLE; + } + + pServer->m_pServerCallbacks->onPassKeyEntry(peerInfo); } else if (event->passkey.params.action == BLE_SM_IOACT_NONE) { NIMBLE_LOGD(LOG_TAG, "No passkey action required"); } @@ -854,8 +868,13 @@ void NimBLEServer::addService(NimBLEService* service) { /** * @brief Resets the GATT server, used when services are added/removed after initialization. + * @return True if successful. + * @details This will reset the GATT server and re-register all services, characteristics, and + * descriptors that have not been removed. Services, characteristics, and descriptors that have been + * removed but not deleted will be skipped and have their handles cleared, and those that have been + * deleted will be removed from the server's service vector. */ -void NimBLEServer::resetGATT() { +bool NimBLEServer::resetGATT() { # if MYNEWT_VAL(BLE_ROLE_BROADCASTER) NimBLEDevice::stopAdvertising(); # endif @@ -864,8 +883,6 @@ void NimBLEServer::resetGATT() { ble_svc_gap_init(); ble_svc_gatt_init(); - m_gattsStarted = false; - for (auto svcIt = m_svcVec.begin(); svcIt != m_svcVec.end();) { auto* pSvc = *svcIt; if (pSvc->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) { @@ -899,12 +916,17 @@ void NimBLEServer::resetGATT() { } if (pSvc->getRemoved() == 0) { - pSvc->start(); + if (!pSvc->start_internal()) { + NIMBLE_LOGE(LOG_TAG, "Failed to start service: %s", pSvc->getUUID().toString().c_str()); + return false; + } } pSvc->m_handle = 0; ++svcIt; } + + return true; } // resetGATT /** @@ -1119,6 +1141,11 @@ uint32_t NimBLEServerCallbacks::onPassKeyDisplay() { return 123456; } // onPassKeyDisplay +void NimBLEServerCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo) { + NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyEntry: default: 123456"); + NimBLEDevice::injectPassKey(connInfo, 123456); +} // onPassKeyEntry + void NimBLEServerCallbacks::onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pin) { NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPasskey: default: true"); NimBLEDevice::injectConfirmPasskey(connInfo, true); diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index 0177fcc6..7c998cb1 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -61,7 +61,7 @@ class NimBLEClient; */ class NimBLEServer { public: - void start(); + bool start(); uint8_t getConnectedCount() const; bool disconnect(uint16_t connHandle, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM) const; bool disconnect(const NimBLEConnInfo& connInfo, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM) const; @@ -123,12 +123,12 @@ class NimBLEServer { static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg); static void gattRegisterCallback(struct ble_gatt_register_ctxt* ctxt, void* arg); void serviceChanged(); - void resetGATT(); + bool resetGATT(); bool m_gattsStarted : 1; bool m_svcChanged : 1; bool m_deleteCallbacks : 1; -# if !MYNEWT_VAL(BLE_EXT_ADV) +# if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER) bool m_advertiseOnDisconnect : 1; # endif NimBLEServerCallbacks* m_pServerCallbacks; @@ -180,6 +180,15 @@ class NimBLEServerCallbacks { */ virtual uint32_t onPassKeyDisplay(); + /** + * @brief Called when using passkey entry pairing and the peer requires the passkey to be entered. + * @param [in] connInfo A reference to a NimBLEConnInfo instance with information + * about the peer connection parameters. + * @details The application should call NimBLEDevice::injectPassKey with the passkey + * displayed on the peer device to complete the pairing process. + */ + virtual void onPassKeyEntry(NimBLEConnInfo& connInfo); + /** * @brief Called when using numeric comparision for pairing. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index 47a6fb12..aebe59fd 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -83,22 +83,8 @@ void NimBLEService::dump() const { * and registers it with the NimBLE stack. * @return bool success/failure . */ -bool NimBLEService::start() { +bool NimBLEService::start_internal() { NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: UUID: %s", getUUID().toString().c_str()); - // If the server has started before then we need to reset the GATT server - // to update the service/characteristic/descriptor definitions. If characteristics or descriptors - // have been added/removed since the last server start then this service will be started on gatt reset. - if (getServer()->m_svcChanged && getServer()->m_gattsStarted) { - NIMBLE_LOGW(LOG_TAG, "<< start(): GATT change pending, cannot start service"); - return false; - } - - // If started previously and no characteristics have been added or removed, - // then we can skip the service registration process. - if (m_pSvcDef->characteristics && !getServer()->m_svcChanged) { - return true; - } - // Make sure the definitions are cleared first clearServiceDefinitions(); @@ -169,12 +155,14 @@ bool NimBLEService::start() { int rc = ble_gatts_count_cfg(m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + clearServiceDefinitions(); // Clear the definitions to free memory and reset the service for re-registration. return false; } rc = ble_gatts_add_svcs(m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + clearServiceDefinitions(); // Clear the definitions to free memory and reset the service for re-registration. return false; } diff --git a/src/NimBLEService.h b/src/NimBLEService.h index 58e91c70..dcbe1f94 100644 --- a/src/NimBLEService.h +++ b/src/NimBLEService.h @@ -37,11 +37,19 @@ class NimBLEService : public NimBLELocalAttribute { NimBLEService(const NimBLEUUID& uuid); ~NimBLEService(); - NimBLEServer* getServer() const; - std::string toString() const; - void dump() const; - bool isStarted() const; - bool start(); + NimBLEServer* getServer() const; + std::string toString() const; + void dump() const; + bool isStarted() const; + + /** + * @brief Dummy function to start the service. Services are started when the server is started. + * This will be removed in a future release. Use `NimBLEServer::start()` to start the server and all associated services. + */ + __attribute__((deprecated("NimBLEService::start() has no effect. " + "Services are started when the server is started."))) + bool start() { return true; } + NimBLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); @@ -61,6 +69,7 @@ class NimBLEService : public NimBLELocalAttribute { private: friend class NimBLEServer; + bool start_internal(); void clearServiceDefinitions(); std::vector m_vChars{}; diff --git a/src/NimBLEStream.cpp b/src/NimBLEStream.cpp index 3e276a28..3b72ace5 100644 --- a/src/NimBLEStream.cpp +++ b/src/NimBLEStream.cpp @@ -608,6 +608,8 @@ bool NimBLEStreamServer::begin( return false; } + m_deleteSvcOnEnd = true; // mark service for deletion on end since we created it here + // Create characteristic with notify + write properties for bidirectional stream uint32_t props = 0; if (txBufSize > 0) { @@ -630,13 +632,6 @@ bool NimBLEStreamServer::begin( goto error; } - m_deleteSvcOnEnd = true; // mark service for deletion on end since we created it here - - if (!pSvc->start()) { - NIMBLE_LOGE(LOG_TAG, "Failed to start service"); - goto error; - } - if (!begin(pChr, txBufSize, rxBufSize)) { NIMBLE_LOGE(LOG_TAG, "Failed to initialize stream with characteristic"); goto error;