Skip to content

Commit 5e26a7a

Browse files
h2zeroh2zero
authored andcommitted
Add asyncronous client connect and MTU exchange.
* Adds parameters `asyncConnect` and `exchangeMTU` to `NimBLEClient::connect`, default values work as the original connect method. * * `asyncConnect`; if true, will send the connect command and return immediately with a true value for successfully sending the command, else false. * * `exchangeMTU`; if true will send the exchange MTU command upon connection, otherwise not and the application can choose to do this later via the `exchangeMTU` method. * Adds `onMTUChange` callback to `NimBLEClientCallbacks` * Add `NimBLEDevice::getConnectedClients()` which returns a vector of pointers to the currently connected client instances. * Calling `NimBLEClient::connect` will no longer cancel already in progress connections.
1 parent 724e1a7 commit 5e26a7a

File tree

10 files changed

+267
-47
lines changed

10 files changed

+267
-47
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
project(NimBLE_Async_Client)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PROJECT_NAME := NimBLE_Async_Client
2+
3+
include $(IDF_PATH)/make/project.mk
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
set(COMPONENT_SRCS "main.cpp")
2+
set(COMPONENT_ADD_INCLUDEDIRS ".")
3+
4+
register_component()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#
2+
# "main" pseudo-component makefile.
3+
#
4+
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
/**
3+
* NimBLE_Async_client Demo:
4+
*
5+
* Demonstrates asynchronous client operations.
6+
*
7+
* Created: on November 4, 2024
8+
* Author: H2zero
9+
*
10+
*/
11+
12+
#include <NimBLEDevice.h>
13+
14+
static constexpr uint32_t scanTimeMs = 5 * 1000;
15+
16+
class ClientCallbacks : public NimBLEClientCallbacks {
17+
void onConnect(NimBLEClient* pClient) {
18+
printf("Connected to: %s\n", pClient->getPeerAddress().toString().c_str());
19+
}
20+
21+
void onDisconnect(NimBLEClient* pClient, int reason) {
22+
printf("%s Disconnected, reason = %d - Starting scan\n", pClient->getPeerAddress().toString().c_str(), reason);
23+
NimBLEDevice::getScan()->start(scanTimeMs);
24+
}
25+
} clientCB;
26+
27+
class scanCallbacks : public NimBLEScanCallbacks {
28+
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
29+
printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
30+
if (advertisedDevice->haveName() && advertisedDevice->getName() == "NimBLE-Server") {
31+
printf("Found Our Device\n");
32+
33+
auto pClient = NimBLEDevice::getDisconnectedClient();
34+
if (!pClient) {
35+
pClient = NimBLEDevice::createClient(advertisedDevice->getAddress());
36+
if (!pClient) {
37+
printf("Failed to create client\n");
38+
return;
39+
}
40+
}
41+
42+
pClient->setClientCallbacks(&clientCB, false);
43+
if (!pClient->connect(true, true, false)) { // delete attributes, async connect, no MTU exchange
44+
NimBLEDevice::deleteClient(pClient);
45+
printf("Failed to connect\n");
46+
return;
47+
}
48+
}
49+
}
50+
51+
void onScanEnd(NimBLEScanResults results) {
52+
printf("Scan Ended\n");
53+
NimBLEDevice::getScan()->start(scanTimeMs);
54+
}
55+
};
56+
57+
extern "C" void app_main(void) {
58+
printf("Starting NimBLE Async Client\n");
59+
NimBLEDevice::init("");
60+
NimBLEDevice::setPower(3); /** +3db */
61+
62+
NimBLEScan* pScan = NimBLEDevice::getScan();
63+
pScan->setScanCallbacks(new scanCallbacks());
64+
pScan->setInterval(45);
65+
pScan->setWindow(15);
66+
pScan->setActiveScan(true);
67+
pScan->start(scanTimeMs);
68+
69+
for (;;) {
70+
vTaskDelay(pdMS_TO_TICKS(1000));
71+
auto pClients = NimBLEDevice::getConnectedClients();
72+
if (!pClients.size()) {
73+
continue;
74+
}
75+
76+
for (auto& pClient : pClients) {
77+
printf("%s\n", pClient->toString().c_str());
78+
NimBLEDevice::deleteClient(pClient);
79+
}
80+
81+
NimBLEDevice::getScan()->start(scanTimeMs);
82+
}
83+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Override some defaults so BT stack is enabled
2+
# in this example
3+
4+
#
5+
# BT config
6+
#
7+
CONFIG_BT_ENABLED=y
8+
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
9+
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
10+
CONFIG_BTDM_CTRL_MODE_BTDM=n
11+
CONFIG_BT_BLUEDROID_ENABLED=n
12+
CONFIG_BT_NIMBLE_ENABLED=y

src/NimBLEClient.cpp

Lines changed: 113 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
6565
m_terminateFailCount{0},
6666
m_deleteCallbacks{false},
6767
m_connEstablished{false},
68+
m_asyncConnect{false},
69+
m_exchangeMTU{true},
6870
# if CONFIG_BT_NIMBLE_EXT_ADV
6971
m_phyMask{BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK},
7072
# endif
@@ -123,44 +125,62 @@ size_t NimBLEClient::deleteService(const NimBLEUUID& uuid) {
123125
} // deleteServices
124126

125127
/**
126-
* @brief Connect to the BLE Server.
128+
* @brief Connect to the BLE Server using the address of the last connected device, or the address\n
129+
* passed to the constructor.
127130
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
128-
* have created and clears the vectors after successful connection.
129-
* @return True on success.
131+
* have created when last connected.
132+
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
133+
* If false, this function will block until the connection is established or the connection attempt times out.
134+
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
135+
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
136+
* @return true on success.
130137
*/
131-
bool NimBLEClient::connect(bool deleteAttributes) {
132-
return connect(m_peerAddress, deleteAttributes);
138+
bool NimBLEClient::connect(bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
139+
return connect(m_peerAddress, deleteAttributes, asyncConnect, exchangeMTU);
133140
}
134141

135142
/**
136143
* @brief Connect to an advertising device.
137144
* @param [in] device The device to connect to.
138145
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
139-
* have created and clears the vectors after successful connection.
140-
* @return True on success.
146+
* have created when last connected.
147+
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
148+
* If false, this function will block until the connection is established or the connection attempt times out.
149+
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
150+
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
151+
* @return true on success.
141152
*/
142-
bool NimBLEClient::connect(NimBLEAdvertisedDevice* device, bool deleteAttributes) {
153+
bool NimBLEClient::connect(NimBLEAdvertisedDevice* device, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
143154
NimBLEAddress address(device->getAddress());
144-
return connect(address, deleteAttributes);
155+
return connect(address, deleteAttributes, asyncConnect, exchangeMTU);
145156
}
146157

147158
/**
148159
* @brief Connect to a BLE Server by address.
149160
* @param [in] address The address of the server.
150161
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
151-
* have created and clears the vectors after successful connection.
152-
* @return True on success.
162+
* have created when last connected.
163+
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
164+
* If false, this function will block until the connection is established or the connection attempt times out.
165+
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
166+
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
167+
* @return true on success.
153168
*/
154-
bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes) {
169+
bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
155170
NIMBLE_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
156171

157172
if (!NimBLEDevice::m_synced) {
158173
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
159174
return false;
160175
}
161176

162-
if (isConnected() || m_connEstablished || m_pTaskData != nullptr) {
163-
NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, handle=%d", std::string(m_peerAddress).c_str(), getConnHandle());
177+
if (isConnected() || m_connEstablished) {
178+
NIMBLE_LOGE(LOG_TAG, "Client already connected");
179+
return false;
180+
}
181+
182+
if (NimBLEDevice::isConnectionInProgress()) {
183+
NIMBLE_LOGE(LOG_TAG, "Connection already in progress");
164184
return false;
165185
}
166186

@@ -171,16 +191,24 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
171191
}
172192

173193
if (address.isNull()) {
174-
NIMBLE_LOGE(LOG_TAG, "Invalid peer address;(NULL)");
194+
NIMBLE_LOGE(LOG_TAG, "Invalid peer address; (NULL)");
175195
return false;
176196
} else {
177197
m_peerAddress = address;
178198
}
179199

180-
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
181-
BleTaskData taskData = {this, cur_task, 0, nullptr};
182-
m_pTaskData = &taskData;
200+
if (deleteAttributes) {
201+
deleteServices();
202+
}
203+
183204
int rc = 0;
205+
m_asyncConnect = asyncConnect;
206+
m_exchangeMTU = exchangeMTU;
207+
TaskHandle_t curTask = xTaskGetCurrentTaskHandle();
208+
BleTaskData taskData = {this, curTask, 0, nullptr};
209+
if (!asyncConnect) {
210+
m_pTaskData = &taskData;
211+
}
184212

185213
// Set the connection in progress flag to prevent a scan from starting while connecting.
186214
NimBLEDevice::setConnectionInProgress(true);
@@ -222,10 +250,7 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
222250
break;
223251

224252
case BLE_HS_EALREADY:
225-
// Already attempting to connect to this device, cancel the previous
226-
// attempt and report failure here so we don't get 2 connections.
227-
NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling", std::string(m_peerAddress).c_str());
228-
ble_gap_conn_cancel();
253+
NIMBLE_LOGE(LOG_TAG, "Already attempting to connect");
229254
break;
230255

231256
default:
@@ -239,16 +264,21 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
239264

240265
} while (rc == BLE_HS_EBUSY);
241266

242-
NimBLEDevice::setConnectionInProgress(false);
243267
m_lastErr = rc;
244268
if (rc != 0) {
269+
m_lastErr = rc;
245270
m_pTaskData = nullptr;
271+
NimBLEDevice::setConnectionInProgress(false);
246272
return false;
247273
}
248274

275+
if (m_asyncConnect) {
276+
return true;
277+
}
278+
249279
# ifdef ulTaskNotifyValueClear
250280
// Clear the task notification value to ensure we block
251-
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
281+
ulTaskNotifyValueClear(curTask, ULONG_MAX);
252282
# endif
253283
// Wait for the connect timeout time +1 second for the connection to complete
254284
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) {
@@ -278,10 +308,6 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
278308
NIMBLE_LOGI(LOG_TAG, "Connection established");
279309
}
280310

281-
if (deleteAttributes) {
282-
deleteServices();
283-
}
284-
285311
m_connEstablished = true;
286312
m_pClientCallbacks->onConnect(this);
287313

@@ -854,6 +880,41 @@ uint16_t NimBLEClient::getMTU() const {
854880
return ble_att_mtu(m_connHandle);
855881
} // getMTU
856882

883+
/**
884+
* @brief Callback for the MTU exchange API function.
885+
* @details When the MTU exchange is complete the API will call this and report the new MTU.
886+
*/
887+
int NimBLEClient::exchangeMTUCb(uint16_t conn_handle, const ble_gatt_error* error, uint16_t mtu, void* arg) {
888+
NIMBLE_LOGD(LOG_TAG, "exchangeMTUCb: status=%d, mtu=%d", error->status, mtu);
889+
890+
NimBLEClient* pClient = (NimBLEClient*)arg;
891+
if (pClient->getConnHandle() != conn_handle) {
892+
return 0;
893+
}
894+
895+
if (error->status != 0) {
896+
NIMBLE_LOGE(LOG_TAG, "exchangeMTUCb() rc=%d %s", error->status, NimBLEUtils::returnCodeToString(error->status));
897+
pClient->m_lastErr = error->status;
898+
}
899+
900+
return 0;
901+
}
902+
903+
/**
904+
* @brief Begin the MTU exchange process with the server.
905+
* @returns true if the request was sent successfully.
906+
*/
907+
bool NimBLEClient::exchangeMTU() {
908+
int rc = ble_gattc_exchange_mtu(m_connHandle, NimBLEClient::exchangeMTUCb, this);
909+
if (rc != 0) {
910+
NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
911+
m_lastErr = rc;
912+
return false;
913+
}
914+
915+
return true;
916+
} // exchangeMTU
917+
857918
/**
858919
* @brief Handle a received GAP event.
859920
* @param [in] event The event structure sent by the NimBLE stack.
@@ -908,29 +969,39 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
908969

909970
case BLE_GAP_EVENT_CONNECT: {
910971
// If we aren't waiting for this connection response we should drop the connection immediately.
911-
if (pClient->isConnected() || pClient->m_pTaskData == nullptr) {
972+
if (pClient->isConnected() || (!pClient->m_asyncConnect && pClient->m_pTaskData == nullptr)) {
912973
ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
913974
return 0;
914975
}
915976

977+
NimBLEDevice::setConnectionInProgress(false);
916978
rc = event->connect.status;
917979
if (rc == 0) {
918980
NIMBLE_LOGI(LOG_TAG, "Connected event");
919981

920982
pClient->m_connHandle = event->connect.conn_handle;
921-
922-
rc = ble_gattc_exchange_mtu(pClient->m_connHandle, NULL, NULL);
923-
if (rc != 0) {
924-
NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
925-
break;
983+
if (pClient->m_exchangeMTU) {
984+
if (!pClient->exchangeMTU() && !pClient->m_asyncConnect) {
985+
rc = pClient->m_lastErr;
986+
break;
987+
}
926988
}
927989

928990
// In the case of a multi-connecting device we ignore this device when
929991
// scanning since we are already connected to it
930992
NimBLEDevice::addIgnored(pClient->m_peerAddress);
931993
} else {
932994
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
933-
break;
995+
if (!pClient->m_asyncConnect) {
996+
break;
997+
}
998+
}
999+
1000+
if (pClient->m_asyncConnect) {
1001+
pClient->m_connEstablished = rc == 0;
1002+
pClient->m_pClientCallbacks->onConnect(pClient);
1003+
} else if (!pClient->m_exchangeMTU) {
1004+
break; // not wating for MTU exchange so release the task now.
9341005
}
9351006

9361007
return 0;
@@ -1072,7 +1143,9 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10721143
if (pClient->m_connHandle != event->mtu.conn_handle) {
10731144
return 0;
10741145
}
1075-
NIMBLE_LOGI(LOG_TAG, "mtu update event; conn_handle=%d mtu=%d", event->mtu.conn_handle, event->mtu.value);
1146+
1147+
NIMBLE_LOGI(LOG_TAG, "mtu update: mtu=%d", event->mtu.value);
1148+
pClient->m_pClientCallbacks->onMTUChange(pClient, event->mtu.value);
10761149
rc = 0;
10771150
break;
10781151
} // BLE_GAP_EVENT_MTU
@@ -1204,4 +1277,8 @@ void NimBLEClientCallbacks::onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t
12041277
NimBLEDevice::injectConfirmPasskey(connInfo, true);
12051278
}
12061279

1280+
void NimBLEClientCallbacks::onMTUChange(NimBLEClient* pClient, uint16_t mtu) {
1281+
NIMBLE_LOGD(CB_TAG, "onMTUChange: default");
1282+
}
1283+
12071284
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

0 commit comments

Comments
 (0)