Skip to content

Commit

Permalink
[NimBLEServer] Add getPeerName and get peer name on connect.
Browse files Browse the repository at this point in the history
* Adds a new method, getPeerName to NimBLEServer to read the name from the peers device name characteristic.
* Adds a setting to automatically get the name of the peer when connected and provide it as an additional parameter in the onConnect callback
* Adds callback with client name after authentication as it may change.
  • Loading branch information
h2zero committed Jun 30, 2024
1 parent 76ef4b7 commit 44a69ed
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/** 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 <NimBLEDevice.h>

// 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 {
Serial.print("Client address: ");
Serial.print(connInfo.getAddress().toString().c_str());
Serial.print(" Name: ");
Serial.println(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());
Serial.println("Encrypt connection failed - disconnecting client");
return;
}

Serial.print("Encrypted Client address: ");
Serial.print(connInfo.getAddress().toString().c_str());
Serial.print(" Name: ");
Serial.println(name.c_str());
}
};

void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Server!");

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();
Serial.println("Advertising started, connect with your phone.");
}

void loop() {
auto clientCount = pServer->getConnectedCount();
if (clientCount) {
Serial.println("Connected clients: ");
for (auto i = 0; i < clientCount; ++i) {
NimBLEConnInfo peerInfo = pServer->getPeerInfo(i);
Serial.print("Client address: ");
Serial.print(peerInfo.getAddress().toString().c_str());
Serial.print(" Name: ");
// This function blocks until the name is retrieved, so cannot be used in callback functions.
Serial.println(pServer->getPeerName(peerInfo).c_str());
}
}

delay(10000);
}
153 changes: 147 additions & 6 deletions src/NimBLEServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
#include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
#endif

#include <limits.h>

#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;

Expand All @@ -47,6 +52,7 @@ NimBLEServer::NimBLEServer() {
#endif
m_svcChanged = false;
m_deleteCallbacks = true;
m_getPeerNameOnConnect = false;
} // NimBLEServer


Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 */
27 changes: 26 additions & 1 deletion src/NimBLEServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class NimBLEServer {
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
Expand All @@ -100,6 +102,7 @@ class NimBLEServer {
#if !CONFIG_BT_NIMBLE_EXT_ADV
bool m_advertiseOnDisconnect;
#endif
bool m_getPeerNameOnConnect;
bool m_svcChanged;
NimBLEServerCallbacks* m_pServerCallbacks;
bool m_deleteCallbacks;
Expand All @@ -112,10 +115,14 @@ class NimBLEServer {
std::vector<NimBLECharacteristic*> 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


Expand All @@ -130,11 +137,21 @@ class NimBLEServerCallbacks {
* @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.
Expand Down Expand Up @@ -174,6 +191,14 @@ class NimBLEServerCallbacks {
*/
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
Expand Down

0 comments on commit 44a69ed

Please sign in to comment.