Skip to content

Commit 878a120

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. * Fixes endless loop when calling scan start from the scan end callback when trying to connect.
1 parent 523e92f commit 878a120

File tree

6 files changed

+270
-40
lines changed

6 files changed

+270
-40
lines changed

.clang-format

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
BasedOnStyle: Google
2+
Language: Cpp
3+
ColumnLimit: 120
4+
IndentWidth: 4
5+
TabWidth: 4
6+
UseTab: Never
7+
SortIncludes: Never
8+
PPIndentWidth : 1
9+
IndentPPDirectives: AfterHash
10+
ReflowComments: true
11+
SpacesBeforeTrailingComments: 1
12+
AlignTrailingComments: true
13+
AlignAfterOpenBracket: Align
14+
AlignConsecutiveMacros:
15+
Enabled: true
16+
AcrossEmptyLines: true
17+
AcrossComments: true
18+
AlignConsecutiveAssignments:
19+
Enabled: true
20+
AcrossEmptyLines: false
21+
AcrossComments: true
22+
AlignCompound: true
23+
PadOperators: true
24+
AlignConsecutiveDeclarations:
25+
Enabled: true
26+
AcrossEmptyLines: false
27+
AcrossComments: true
28+
AlignEscapedNewlines: Left
29+
AccessModifierOffset: -2
30+
AlignArrayOfStructures: Right
31+
AlignOperands: Align
32+
AllowAllArgumentsOnNextLine: false
33+
AllowShortFunctionsOnASingleLine: Inline
34+
AllowShortBlocksOnASingleLine: Empty
35+
BreakBeforeBinaryOperators: None
36+
BinPackArguments: false
37+
BinPackParameters: false
38+
DerivePointerAlignment: false
39+
PenaltyBreakAssignment: 4
40+
PenaltyExcessCharacter: 4
41+
PenaltyBreakBeforeFirstCallParameter: 1
42+
PointerAlignment: Left
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
Serial.printf("Connected to: %s\n", pClient->getPeerAddress().toString().c_str());
19+
}
20+
21+
void onDisconnect(NimBLEClient* pClient, int reason) {
22+
Serial.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+
Serial.printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
30+
if (advertisedDevice->haveName() && advertisedDevice->getName() == "NimBLE-Server") {
31+
Serial.println("Found Our Device");
32+
33+
auto pClient = NimBLEDevice::getDisconnectedClient();
34+
if (!pClient) {
35+
pClient = NimBLEDevice::createClient(advertisedDevice->getAddress());
36+
if (!pClient) {
37+
Serial.println("Failed to create client");
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+
Serial.println("Failed to connect");
46+
return;
47+
}
48+
}
49+
}
50+
51+
void onScanEnd(NimBLEScanResults results) {
52+
Serial.println("Scan Ended");
53+
NimBLEDevice::getScan()->start(scanTimeMs);
54+
}
55+
};
56+
57+
void setup() {
58+
Serial.begin(115200);
59+
Serial.println("Starting NimBLE Client");
60+
NimBLEDevice::init("");
61+
NimBLEDevice::setPower(3); /** +3db */
62+
63+
NimBLEScan* pScan = NimBLEDevice::getScan();
64+
pScan->setScanCallbacks(new scanCallbacks());
65+
pScan->setInterval(45);
66+
pScan->setWindow(15);
67+
pScan->setActiveScan(true);
68+
pScan->start(scanTimeMs);
69+
}
70+
71+
void loop() {
72+
delay(1000);
73+
auto pClients = NimBLEDevice::getConnectedClients();
74+
if (pClients.size() == 0) {
75+
return;
76+
}
77+
78+
for (auto& pClient : pClients) {
79+
Serial.println(pClient->toString().c_str());
80+
NimBLEDevice::deleteClient(pClient);
81+
}
82+
83+
NimBLEDevice::getScan()->start(scanTimeMs);
84+
}

src/NimBLEClient.cpp

Lines changed: 102 additions & 29 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,37 +125,54 @@ 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

172+
if (deleteAttributes) {
173+
deleteServices();
174+
}
175+
157176
if (!NimBLEDevice::m_synced) {
158177
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
159178
return false;
@@ -177,10 +196,14 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
177196
m_peerAddress = address;
178197
}
179198

199+
int rc = 0;
200+
m_asyncConnect = asyncConnect;
201+
m_exchangeMTU = exchangeMTU;
180202
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
181203
BleTaskData taskData = {this, cur_task, 0, nullptr};
182-
m_pTaskData = &taskData;
183-
int rc = 0;
204+
if (!asyncConnect) {
205+
m_pTaskData = &taskData;
206+
}
184207

185208
do {
186209
# if CONFIG_BT_NIMBLE_EXT_ADV
@@ -207,10 +230,11 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
207230
break;
208231

209232
case BLE_HS_EBUSY:
210-
// Scan was still running, stop it and try again
233+
// Scan was active, stop it through the NimBLEScan API to release any tasks and call the callback.
211234
if (!NimBLEDevice::getScan()->stop()) {
212235
rc = BLE_HS_EUNKNOWN;
213236
}
237+
ble_gap_disc_cancel(); // we call this in case the app restarted the scan in the callback.
214238
break;
215239

216240
case BLE_HS_EDONE:
@@ -236,13 +260,16 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
236260

237261
} while (rc == BLE_HS_EBUSY);
238262

239-
m_lastErr = rc;
240-
241263
if (rc != 0) {
264+
m_lastErr = rc;
242265
m_pTaskData = nullptr;
243266
return false;
244267
}
245268

269+
if (m_asyncConnect) {
270+
return true;
271+
}
272+
246273
# ifdef ulTaskNotifyValueClear
247274
// Clear the task notification value to ensure we block
248275
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
@@ -275,10 +302,6 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes)
275302
NIMBLE_LOGI(LOG_TAG, "Connection established");
276303
}
277304

278-
if (deleteAttributes) {
279-
deleteServices();
280-
}
281-
282305
m_connEstablished = true;
283306
m_pClientCallbacks->onConnect(this);
284307

@@ -852,6 +875,41 @@ uint16_t NimBLEClient::getMTU() const {
852875
return ble_att_mtu(m_connHandle);
853876
} // getMTU
854877

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

907965
case BLE_GAP_EVENT_CONNECT: {
908966
// If we aren't waiting for this connection response we should drop the connection immediately.
909-
if (pClient->isConnected() || pClient->m_pTaskData == nullptr) {
967+
if (pClient->isConnected() || (!pClient->m_asyncConnect && pClient->m_pTaskData == nullptr)) {
910968
ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
911969
return 0;
912970
}
@@ -916,19 +974,28 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
916974
NIMBLE_LOGI(LOG_TAG, "Connected event");
917975

918976
pClient->m_connHandle = event->connect.conn_handle;
919-
920-
rc = ble_gattc_exchange_mtu(pClient->m_connHandle, NULL, NULL);
921-
if (rc != 0) {
922-
NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
923-
break;
977+
if (pClient->m_exchangeMTU) {
978+
if (!pClient->exchangeMTU() && !pClient->m_asyncConnect) {
979+
rc = pClient->m_lastErr;
980+
break;
981+
}
924982
}
925983

926984
// In the case of a multi-connecting device we ignore this device when
927985
// scanning since we are already connected to it
928986
NimBLEDevice::addIgnored(pClient->m_peerAddress);
929987
} else {
930988
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
931-
break;
989+
if (!pClient->m_asyncConnect) {
990+
break;
991+
}
992+
}
993+
994+
if (pClient->m_asyncConnect) {
995+
pClient->m_connEstablished = rc == 0;
996+
pClient->m_pClientCallbacks->onConnect(pClient);
997+
} else if (!pClient->m_exchangeMTU) {
998+
break; // not wating for MTU exchange so release the task now.
932999
}
9331000

9341001
return 0;
@@ -1070,7 +1137,9 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10701137
if (pClient->m_connHandle != event->mtu.conn_handle) {
10711138
return 0;
10721139
}
1073-
NIMBLE_LOGI(LOG_TAG, "mtu update event; conn_handle=%d mtu=%d", event->mtu.conn_handle, event->mtu.value);
1140+
1141+
NIMBLE_LOGI(LOG_TAG, "mtu update: mtu=%d", event->mtu.value);
1142+
pClient->m_pClientCallbacks->onMTUChange(pClient, event->mtu.value);
10741143
rc = 0;
10751144
break;
10761145
} // BLE_GAP_EVENT_MTU
@@ -1202,4 +1271,8 @@ void NimBLEClientCallbacks::onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t
12021271
NimBLEDevice::injectConfirmPasskey(connInfo, true);
12031272
}
12041273

1274+
void NimBLEClientCallbacks::onMTUChange(NimBLEClient* pClient, uint16_t mtu) {
1275+
NIMBLE_LOGD(CB_TAG, "onMTUChange: default");
1276+
}
1277+
12051278
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

0 commit comments

Comments
 (0)