diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0369b5..eebc79f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,7 @@ jobs: - Bluetooth_5/NimBLE_extended_client - Bluetooth_5/NimBLE_extended_server - Bluetooth_5/NimBLE_multi_advertiser + - NimBLE_server_get_client_name exclude: - idf_target: "esp32" example: Bluetooth_5/NimBLE_extended_client diff --git a/examples/NimBLE_server_get_client_name/CMakeLists.txt b/examples/NimBLE_server_get_client_name/CMakeLists.txt new file mode 100644 index 0000000..21c12da --- /dev/null +++ b/examples/NimBLE_server_get_client_name/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(NimBLE_server_get_client_name) diff --git a/examples/NimBLE_server_get_client_name/main/CMakeLists.txt b/examples/NimBLE_server_get_client_name/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/NimBLE_server_get_client_name/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/NimBLE_server_get_client_name/main/main.cpp b/examples/NimBLE_server_get_client_name/main/main.cpp new file mode 100644 index 0000000..e255807 --- /dev/null +++ b/examples/NimBLE_server_get_client_name/main/main.cpp @@ -0,0 +1,83 @@ +/** NimBLE_server_get_client_name + * + * Demonstrates 2 ways for the server to read the device name from the connected client. + * + * Created: on June 24 2024 + * Author: H2zero + * + */ + +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define ENC_CHARACTERISTIC_UUID "9551f35b-8d91-42e4-8f7e-1358dfe272dc" + +NimBLEServer* pServer; + +class ServerCallbacks : public NimBLEServerCallbacks { + // Same as before but now includes the name parameter + void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name) override { + printf("Client address: %s Name: %s\n", connInfo.getAddress().toString().c_str(), name.c_str()); + } + + // Same as before but now includes the name parameter + void onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name) override { + if (!connInfo.isEncrypted()) { + NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); + printf("Encrypt connection failed - disconnecting client\n"); + return; + } + + printf("Encrypted Client address: %s Name: %s\n", connInfo.getAddress().toString().c_str(), name.c_str()); + } +}; + +extern "C" void app_main(void) { + printf("Starting BLE Server!\n"); + + NimBLEDevice::init("Connect to me!"); + NimBLEDevice::setSecurityAuth(true, false, true); // Enable bonding to see full name on phones. + + pServer = NimBLEDevice::createServer(); + NimBLEService* pService = pServer->createService(SERVICE_UUID); + NimBLECharacteristic* pCharacteristic = + pService->createCharacteristic(CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE); + pCharacteristic->setValue("Hello World says NimBLE!"); + + NimBLECharacteristic* pEncCharacteristic = pService->createCharacteristic( + ENC_CHARACTERISTIC_UUID, + (NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC)); + pEncCharacteristic->setValue("Hello World says NimBLE Encrypted"); + + pService->start(); + + pServer->setCallbacks(new ServerCallbacks()); + pServer->getPeerNameOnConnect(true); // Setting this will enable the onConnect callback that provides the name. + + BLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + + pAdvertising->start(); + printf("Advertising started, connect with your phone.\n"); + + while (true) { + auto clientCount = pServer->getConnectedCount(); + if (clientCount) { + printf("Connected clients:\n"); + for (auto i = 0; i < clientCount; ++i) { + NimBLEConnInfo peerInfo = pServer->getPeerInfo(i); + printf("Client address: %s Name: %s\n", peerInfo.getAddress().toString().c_str(), + // This function blocks until the name is retrieved, so cannot be used in callback functions. + pServer->getPeerName(peerInfo).c_str()); + + } + } + + vTaskDelay(pdMS_TO_TICKS(10000)); + } +} diff --git a/examples/NimBLE_server_get_client_name/sdkconfig.defaults b/examples/NimBLE_server_get_client_name/sdkconfig.defaults new file mode 100644 index 0000000..c829fc5 --- /dev/null +++ b/examples/NimBLE_server_get_client_name/sdkconfig.defaults @@ -0,0 +1,12 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index fb03ea4..4169298 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -27,6 +27,11 @@ #include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h" #endif +#include + +#define NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB 0 +#define NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB 1 + static const char* LOG_TAG = "NimBLEServer"; static NimBLEServerCallbacks defaultCallbacks; @@ -47,6 +52,7 @@ NimBLEServer::NimBLEServer() { #endif m_svcChanged = false; m_deleteCallbacks = true; + m_getPeerNameOnConnect = false; } // NimBLEServer @@ -274,6 +280,14 @@ void NimBLEServer::advertiseOnDisconnect(bool aod) { } // advertiseOnDisconnect #endif +/** + * @brief Set the server to automatically read the name from the connected peer before + * the onConnect callback is called and enables the override callback with name parameter. + * @param [in] enable Enable reading the connected peer name upon connection. + */ +void NimBLEServer::getPeerNameOnConnect(bool enable) { + m_getPeerNameOnConnect = enable; +} // getPeerNameOnConnect /** * @brief Return the number of connected clients. @@ -340,6 +354,113 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { return peerInfo; } // getPeerIDInfo +/** + * @brief Callback that is called after reading from the peer name characteristic. + * @details This will check the task pointer in the task data struct to determine + * the action to take once the name has been read. If there is a task waiting then + * it will be woken, if not, the the RC value is checked to determine which callback + * should be called. + */ +int NimBLEServer::peerNameCB(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) { + ble_task_data_t *pTaskData = (ble_task_data_t*)arg; + std::string *name = (std::string*)pTaskData->buf; + int rc = error->status; + + if (rc == 0) { + if (attr) { + name->append(OS_MBUF_DATA(attr->om, char*), OS_MBUF_PKTLEN(attr->om)); + return rc; + } + } + + if (rc == BLE_HS_EDONE) { + // No ask means this was read for a callback. + if (pTaskData->task == nullptr) { + NimBLEServer* pServer = (NimBLEServer*)pTaskData->pATT; + NimBLEConnInfo peerInfo{}; + ble_gap_conn_find(conn_handle, &peerInfo.m_desc); + + // Use the rc value as a flag to indicate which callback should be called. + if (pTaskData->rc == NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB) { + pServer->m_pServerCallbacks->onConnect(pServer, peerInfo, *name); + } else if (pTaskData->rc == NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB) { + pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo, *name); + } + } + } else { + NIMBLE_LOGE(LOG_TAG, "NimBLEServerPeerNameCB rc=%d; %s", rc, NimBLEUtils::returnCodeToString(rc)); + } + + if (pTaskData->task != nullptr) { + pTaskData->rc = rc; + xTaskNotifyGive(pTaskData->task); + } else { + // If the read was triggered for callback use then these were allocated. + delete name; + delete pTaskData; + } + + return rc; +} + +/** + * @brief Internal method that sends the read command. + */ +std::string NimBLEServer::getPeerNameInternal(uint16_t conn_handle, TaskHandle_t task, int cb_type) { + std::string *buf = new std::string{}; + ble_task_data_t *taskData = new ble_task_data_t{this, task, cb_type, buf}; + ble_uuid16_t uuid {{BLE_UUID_TYPE_16}, BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME}; + int rc = ble_gattc_read_by_uuid(conn_handle, + 1, + 0xffff, + ((ble_uuid_t*)&uuid), + NimBLEServer::peerNameCB, + taskData); + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "ble_gattc_read_by_uuid rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + NimBLEConnInfo peerInfo{}; + ble_gap_conn_find(conn_handle, &peerInfo.m_desc); + if (cb_type == NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB) { + m_pServerCallbacks->onConnect(this, peerInfo, *buf); + } else if (cb_type == NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB) { + m_pServerCallbacks->onAuthenticationComplete(peerInfo, *buf); + } + delete buf; + delete taskData; + } else if (task != nullptr) { +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + rc = taskData->rc; + std::string name{*(std::string*)taskData->buf}; + delete buf; + delete taskData; + + if (rc != 0 && rc != BLE_HS_EDONE) { + NIMBLE_LOGE(LOG_TAG, "getPeerName rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); + } + + return name; + } + // TaskData and name buffer will be deleted in the callback. + return ""; +} + +/** + * @brief Get the name of the connected peer. + * @param connInfo A reference to a NimBLEConnInfo instance to read the name from. + * @returns A string containing the name. + * @note This is a blocking call and should NOT be called from any callbacks! + */ +std::string NimBLEServer::getPeerName(const NimBLEConnInfo& connInfo) { + std::string name = getPeerNameInternal(connInfo.getConnHandle(), xTaskGetCurrentTaskHandle()); + return name; +} /** * @brief Handle a GATT Server Event. @@ -366,16 +487,22 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { #if !CONFIG_BT_NIMBLE_EXT_ADV NimBLEDevice::startAdvertising(); #endif - } - else { - pServer->m_connectedPeersVec.push_back(event->connect.conn_handle); - + } else { rc = ble_gap_conn_find(event->connect.conn_handle, &peerInfo.m_desc); if (rc != 0) { return 0; } - pServer->m_pServerCallbacks->onConnect(pServer, peerInfo); + pServer->m_connectedPeersVec.push_back(event->connect.conn_handle); + + if (pServer->m_getPeerNameOnConnect) { + pServer->getPeerNameInternal(event->connect.conn_handle, + nullptr, + NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB); + } else { + pServer->m_pServerCallbacks->onConnect(pServer, peerInfo); + } + } return 0; @@ -526,7 +653,13 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { return BLE_ATT_ERR_INVALID_HANDLE; } - pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo); + if (pServer->m_getPeerNameOnConnect) { + pServer->getPeerNameInternal(event->enc_change.conn_handle, + nullptr, + NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB); + } else { + pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo); + } return 0; } // BLE_GAP_EVENT_ENC_CHANGE @@ -857,6 +990,10 @@ void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& con NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default"); } // onConnect +void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name) { + NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default"); +} // onConnect + void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) { NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default"); @@ -884,4 +1021,8 @@ void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connI NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default"); } // onAuthenticationComplete +void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name){ + NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default"); +} // onAuthenticationComplete + #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index 0b175ea..bbb4ebf 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -80,6 +80,8 @@ public: NimBLEConnInfo getPeerInfo(size_t index); NimBLEConnInfo getPeerInfo(const NimBLEAddress& address); NimBLEConnInfo getPeerIDInfo(uint16_t id); + std::string getPeerName(const NimBLEConnInfo& connInfo); + void getPeerNameOnConnect(bool enable); #if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) void advertiseOnDisconnect(bool); #endif @@ -100,6 +102,7 @@ private: #if !CONFIG_BT_NIMBLE_EXT_ADV bool m_advertiseOnDisconnect; #endif + bool m_getPeerNameOnConnect; bool m_svcChanged; NimBLEServerCallbacks* m_pServerCallbacks; bool m_deleteCallbacks; @@ -112,10 +115,14 @@ private: std::vector m_notifyChrVec; static int handleGapEvent(struct ble_gap_event *event, void *arg); + static int peerNameCB(uint16_t conn_handle, const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, void *arg); + std::string getPeerNameInternal(uint16_t conn_handle, TaskHandle_t task, int cb_type = -1); void serviceChanged(); void resetGATT(); bool setIndicateWait(uint16_t conn_handle); void clearIndicateWait(uint16_t conn_handle); + }; // NimBLEServer @@ -130,11 +137,21 @@ public: * @brief Handle a client connection. * This is called when a client connects. * @param [in] pServer A pointer to the %BLE server that received the client connection. - * @param [in] connInfo A reference to a NimBLEConnInfo instance with information + * @param [in] connInfo A reference to a NimBLEConnInfo instance with information. * about the peer connection parameters. */ virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo); + /** + * @brief Handle a client connection. + * This is called when a client connects. + * @param [in] pServer A pointer to the %BLE server that received the client connection. + * @param [in] connInfo A reference to a NimBLEConnInfo instance with information. + * @param [in] name The name of the connected peer device. + * about the peer connection parameters. + */ + virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name); + /** * @brief Handle a client disconnection. * This is called when a client discconnects. @@ -174,6 +191,14 @@ public: */ virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo); + /** + * @brief Called when the pairing procedure is complete. + * @param [in] connInfo A reference to a NimBLEConnInfo instance with information + * @param [in] name The name of the connected peer device. + * about the peer connection parameters. + */ + virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name); + /** * @brief Called when the peer identity address is resolved. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information