@@ -69,6 +69,8 @@ NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
6969 m_asyncSecureAttempt{0 },
7070 m_config{},
7171 m_connStatus{DISCONNECTED },
72+ m_connectCallbackPending{false },
73+ m_connectFailRetryCount{0 },
7274# if MYNEWT_VAL(BLE_EXT_ADV)
7375 m_phyMask{BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK },
7476# endif
@@ -80,13 +82,21 @@ NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
8082 BLE_GAP_INITIAL_SUPERVISION_TIMEOUT ,
8183 BLE_GAP_INITIAL_CONN_MIN_CE_LEN ,
8284 BLE_GAP_INITIAL_CONN_MAX_CE_LEN } {
85+ m_config.connectFailRetryCount = 1 ;
86+ ble_npl_callout_init (&m_connectEstablishedTimer,
87+ nimble_port_get_dflt_eventq (),
88+ NimBLEClient::connectEstablishedTimerCb,
89+ this );
8390} // NimBLEClient
8491
8592/* *
8693 * @brief Destructor, private - only callable by NimBLEDevice::deleteClient
8794 * to ensure proper disconnect and removal from device list.
8895 */
8996NimBLEClient::~NimBLEClient () {
97+ ble_npl_callout_stop (&m_connectEstablishedTimer);
98+ ble_npl_callout_deinit (&m_connectEstablishedTimer);
99+
90100 // We may have allocated service references associated with this client.
91101 // Before we are finished with the client, we must release resources.
92102 deleteServices ();
@@ -159,45 +169,8 @@ bool NimBLEClient::connect(bool deleteAttributes, bool asyncConnect, bool exchan
159169 return connect (m_peerAddress, deleteAttributes, asyncConnect, exchangeMTU);
160170} // connect
161171
162- /* *
163- * @brief Connect to a BLE Server by address.
164- * @param [in] address The address of the server.
165- * @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
166- * have created when last connected.
167- * @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
168- * If false, this function will block until the connection is established or the connection attempt times out.
169- * @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
170- * If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
171- * @return true on success.
172- */
173- bool NimBLEClient::connect (const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
174- NIMBLE_LOGD (LOG_TAG , " >> connect(%s)" , address.toString ().c_str ());
175- NimBLETaskData taskData (this );
176- const ble_addr_t * peerAddr = address.getBase ();
177- int rc = 0 ;
178-
179- if (!NimBLEDevice::m_synced) {
180- NIMBLE_LOGE (LOG_TAG , " Host not synced with controller." );
181- rc = BLE_HS_ENOTSYNCED ;
182- goto error;
183- }
184-
185- if (m_connStatus != DISCONNECTED ) {
186- NIMBLE_LOGE (LOG_TAG , " Client not disconnected, cannot connect" );
187- rc = BLE_HS_EREJECT ;
188- goto error;
189- }
190-
191- if (address.isNull ()) {
192- NIMBLE_LOGE (LOG_TAG , " Invalid peer address; (NULL)" );
193- rc = BLE_HS_EINVAL ;
194- goto error;
195- }
196-
197- m_connStatus = CONNECTING ;
198- m_peerAddress = address;
199- m_config.asyncConnect = asyncConnect;
200- m_config.exchangeMTU = exchangeMTU;
172+ int NimBLEClient::startConnectionAttempt (const ble_addr_t * peerAddr) {
173+ int rc = 0 ;
201174
202175 do {
203176# if MYNEWT_VAL(BLE_EXT_ADV)
@@ -255,6 +228,53 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes,
255228
256229 } while (rc == BLE_HS_EBUSY );
257230
231+ return rc;
232+ }
233+
234+ /* *
235+ * @brief Connect to a BLE Server by address.
236+ * @param [in] address The address of the server.
237+ * @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
238+ * have created when last connected.
239+ * @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
240+ * If false, this function will block until the connection is established or the connection attempt times out.
241+ * @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
242+ * If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
243+ * @return true on success.
244+ */
245+ bool NimBLEClient::connect (const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
246+ NIMBLE_LOGD (LOG_TAG , " >> connect(%s)" , address.toString ().c_str ());
247+ NimBLETaskData taskData (this );
248+ const ble_addr_t * peerAddr = address.getBase ();
249+ int rc = 0 ;
250+
251+ if (!NimBLEDevice::m_synced) {
252+ NIMBLE_LOGE (LOG_TAG , " Host not synced with controller." );
253+ rc = BLE_HS_ENOTSYNCED ;
254+ goto error;
255+ }
256+
257+ if (m_connStatus != DISCONNECTED ) {
258+ NIMBLE_LOGE (LOG_TAG , " Client not disconnected, cannot connect" );
259+ rc = BLE_HS_EREJECT ;
260+ goto error;
261+ }
262+
263+ if (address.isNull ()) {
264+ NIMBLE_LOGE (LOG_TAG , " Invalid peer address; (NULL)" );
265+ rc = BLE_HS_EINVAL ;
266+ goto error;
267+ }
268+
269+ m_connStatus = CONNECTING ;
270+ m_peerAddress = address;
271+ m_config.asyncConnect = asyncConnect;
272+ m_config.exchangeMTU = exchangeMTU;
273+ m_connectCallbackPending = false ;
274+ m_connectFailRetryCount = 0 ;
275+
276+ rc = startConnectionAttempt (peerAddr);
277+
258278 if (deleteAttributes) {
259279 deleteServices ();
260280 }
@@ -289,7 +309,6 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes,
289309 goto error;
290310 }
291311
292- m_pClientCallbacks->onConnect (this );
293312 NIMBLE_LOGD (LOG_TAG , " << connect()" );
294313 return true ;
295314
@@ -912,7 +931,7 @@ int NimBLEClient::exchangeMTUCb(uint16_t conn_handle, const ble_gatt_error* erro
912931 */
913932bool NimBLEClient::exchangeMTU () {
914933 int rc = ble_gattc_exchange_mtu (m_connHandle, NimBLEClient::exchangeMTUCb, this );
915- if (rc != 0 ) {
934+ if (rc != 0 && rc != BLE_HS_EALREADY ) {
916935 NIMBLE_LOGE (LOG_TAG , " MTU exchange error; rc=%d %s" , rc, NimBLEUtils::returnCodeToString (rc));
917936 m_lastErr = rc;
918937 return false ;
@@ -921,6 +940,48 @@ bool NimBLEClient::exchangeMTU() {
921940 return true ;
922941} // exchangeMTU
923942
943+ void NimBLEClient::startConnectEstablishedTimer (uint16_t connInterval) {
944+ // As per Bluetooth spec, the connection is only established after receiving a PDU
945+ // within 6 connections events, so we wait for 7 connection events plus a small margin for processing.
946+ uint32_t waitMs = (static_cast <uint32_t >(connInterval) * 35U + 3U ) / 4U ; // 7 * 1.25ms units
947+ if (waitMs == 0 ) {
948+ waitMs = 1 ;
949+ }
950+
951+ ble_npl_time_t waitTicks = 1 ;
952+ ble_npl_time_ms_to_ticks (waitMs, &waitTicks);
953+ if (waitTicks == 0 ) {
954+ waitTicks = 1 ;
955+ }
956+
957+ ble_npl_callout_reset (&m_connectEstablishedTimer, waitTicks);
958+ } // startConnectEstablishedTimer
959+
960+ bool NimBLEClient::completeConnectEstablished () {
961+ if (!m_connectCallbackPending) {
962+ return false ;
963+ }
964+
965+ m_connectCallbackPending = false ;
966+ ble_npl_callout_stop (&m_connectEstablishedTimer);
967+ m_pClientCallbacks->onConnect (this );
968+
969+ if (m_pTaskData != nullptr ) {
970+ NimBLEUtils::taskRelease (*m_pTaskData, 0 );
971+ }
972+
973+ return true ;
974+ } // completeConnectEstablished
975+
976+ void NimBLEClient::connectEstablishedTimerCb (struct ble_npl_event * event) {
977+ auto * pClient = static_cast <NimBLEClient*>(ble_npl_event_get_arg (event));
978+ if (pClient == nullptr || pClient->m_connStatus != CONNECTED ) {
979+ return ;
980+ }
981+
982+ pClient->completeConnectEstablished ();
983+ } // connectEstablishedTimerCb
984+
924985/* *
925986 * @brief Handle a received GAP event.
926987 * @param [in] event The event structure sent by the NimBLE stack.
@@ -968,12 +1029,33 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
9681029
9691030 NIMBLE_LOGD (LOG_TAG , " disconnect; reason=%d, %s" , rc, NimBLEUtils::returnCodeToString (rc));
9701031
1032+ pClient->m_connectCallbackPending = false ;
1033+ ble_npl_callout_stop (&pClient->m_connectEstablishedTimer );
9711034 pClient->m_terminateFailCount = 0 ;
9721035 pClient->m_asyncSecureAttempt = 0 ;
9731036 pClient->m_connStatus = DISCONNECTED ;
9741037
975- // Don't call the disconnect callback if we are waiting for a connection to complete and it fails
976- if (rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT ) && pClient->m_config .asyncConnect ) {
1038+ const int connEstablishFailReason = BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT ;
1039+ if (rc == connEstablishFailReason && pClient->m_connectFailRetryCount < pClient->m_config .connectFailRetryCount ) {
1040+ pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE ;
1041+ ++pClient->m_connectFailRetryCount ;
1042+ pClient->m_connStatus = CONNECTING ;
1043+ NIMBLE_LOGW (LOG_TAG ,
1044+ " Connection establishment failed (0x3e), retry %u/%u" ,
1045+ pClient->m_connectFailRetryCount ,
1046+ pClient->m_config .connectFailRetryCount );
1047+
1048+ const int retryRc = pClient->startConnectionAttempt (pClient->m_peerAddress .getBase ());
1049+ if (retryRc == 0 ) {
1050+ // A retry attempt is in progress; suppress user callbacks until final outcome.
1051+ return 0 ;
1052+ }
1053+
1054+ pClient->m_connStatus = DISCONNECTED ;
1055+ NIMBLE_LOGE (LOG_TAG , " Retry connect start failed, rc=%d %s" , retryRc, NimBLEUtils::returnCodeToString (retryRc));
1056+ }
1057+
1058+ if (rc == connEstablishFailReason) {
9771059 pClient->m_pClientCallbacks ->onConnectFail (pClient, rc);
9781060 } else {
9791061 pClient->m_pClientCallbacks ->onDisconnect (pClient, rc);
@@ -985,7 +1067,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
9851067 // If we are set to self delete on disconnect but we have a task waiting on the connection
9861068 // completion we will set the flag to delete on connect fail instead of deleting here
9871069 // to prevent segmentation faults or double deleting
988- if (pTaskData != nullptr && rc == ( BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT ) ) {
1070+ if (pTaskData != nullptr && rc == connEstablishFailReason ) {
9891071 pClient->m_config .deleteOnConnectFail = true ;
9901072 break ;
9911073 }
@@ -1009,23 +1091,28 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10091091 }
10101092
10111093 if (rc == 0 ) {
1012- pClient->m_connStatus = CONNECTED ;
1013- pClient->m_connHandle = event->connect .conn_handle ;
1094+ pClient->m_connStatus = CONNECTED ;
1095+ pClient->m_connHandle = event->connect .conn_handle ;
1096+ pClient->m_connectCallbackPending = true ;
10141097
1015- if (pClient->m_config .asyncConnect ) {
1016- pClient->m_pClientCallbacks ->onConnect (pClient);
1098+ ble_gap_conn_desc desc;
1099+ if (ble_gap_conn_find (event->connect .conn_handle , &desc) == 0 ) {
1100+ pClient->startConnectEstablishedTimer (desc.conn_itvl );
1101+ } else {
1102+ pClient->startConnectEstablishedTimer (pClient->m_connParams .itvl_max );
10171103 }
10181104
10191105 if (pClient->m_config .exchangeMTU ) {
1020- if (!pClient->exchangeMTU ()) {
1021- break ; // if exchangeMTU fails we will just continue, likely will disconnect immediately after
1022- }
1023-
1024- return 0 ; // return as we may have a task waiting for the MTU before releasing it.
1106+ pClient->exchangeMTU ();
10251107 }
1108+ // return as we may have a task waiting on the connection completion
1109+ // and will release it in the timer callback after the connection is fully established.
1110+ return 0 ;
10261111 } else {
1027- pClient->m_connStatus = DISCONNECTED ;
1028- pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE ;
1112+ pClient->m_connStatus = DISCONNECTED ;
1113+ pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE ;
1114+ pClient->m_connectCallbackPending = false ;
1115+ ble_npl_callout_stop (&pClient->m_connectEstablishedTimer );
10291116
10301117 if (pClient->m_config .asyncConnect ) {
10311118 pClient->m_pClientCallbacks ->onConnectFail (pClient, rc);
@@ -1057,6 +1144,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10571144 return 0 ;
10581145 }
10591146
1147+ if (pClient->completeConnectEstablished ()) {
1148+ pTaskData = nullptr ;
1149+ }
1150+
10601151 NIMBLE_LOGD (LOG_TAG , " Notify Received for handle: %d" , event->notify_rx .attr_handle );
10611152
10621153 NimBLERemoteCharacteristic* pChr = pClient->getCharacteristic (event->notify_rx .attr_handle );
@@ -1103,6 +1194,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11031194 if (pClient->m_connHandle != event->conn_update_req .conn_handle ) {
11041195 return 0 ;
11051196 }
1197+
1198+ if (pClient->completeConnectEstablished ()) {
1199+ pTaskData = nullptr ;
1200+ }
1201+
11061202 NIMBLE_LOGD (LOG_TAG , " Peer requesting to update connection parameters" );
11071203 NIMBLE_LOGD (LOG_TAG ,
11081204 " MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d" ,
@@ -1130,6 +1226,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11301226 if (pClient->m_connHandle != event->conn_update .conn_handle ) {
11311227 return 0 ;
11321228 }
1229+
1230+ if (pClient->completeConnectEstablished ()) {
1231+ pTaskData = nullptr ;
1232+ }
1233+
11331234 if (event->conn_update .status == 0 ) {
11341235 NIMBLE_LOGI (LOG_TAG , " Connection parameters updated." );
11351236 } else {
@@ -1143,6 +1244,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11431244 return 0 ;
11441245 }
11451246
1247+ if (pClient->completeConnectEstablished ()) {
1248+ pTaskData = nullptr ;
1249+ }
1250+
11461251 if (event->enc_change .status == 0 ||
11471252 event->enc_change .status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING )) {
11481253 NimBLEConnInfo peerInfo;
@@ -1170,6 +1275,14 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11701275 } // BLE_GAP_EVENT_ENC_CHANGE
11711276
11721277 case BLE_GAP_EVENT_IDENTITY_RESOLVED : {
1278+ if (pClient->m_connHandle != event->identity_resolved .conn_handle ) {
1279+ return 0 ;
1280+ }
1281+
1282+ if (pClient->completeConnectEstablished ()) {
1283+ pTaskData = nullptr ;
1284+ }
1285+
11731286 NimBLEConnInfo peerInfo;
11741287 rc = ble_gap_conn_find (event->identity_resolved .conn_handle , &peerInfo.m_desc );
11751288 if (rc != 0 ) {
@@ -1182,6 +1295,14 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11821295 } // BLE_GAP_EVENT_IDENTITY_RESOLVED
11831296
11841297 case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE : {
1298+ if (pClient->m_connHandle != event->phy_updated .conn_handle ) {
1299+ return 0 ;
1300+ }
1301+
1302+ if (pClient->completeConnectEstablished ()) {
1303+ pTaskData = nullptr ;
1304+ }
1305+
11851306 NimBLEConnInfo peerInfo;
11861307 rc = ble_gap_conn_find (event->phy_updated .conn_handle , &peerInfo.m_desc );
11871308 if (rc != 0 ) {
@@ -1197,6 +1318,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11971318 return 0 ;
11981319 }
11991320
1321+ if (pClient->completeConnectEstablished ()) {
1322+ pTaskData = nullptr ;
1323+ }
1324+
12001325 NIMBLE_LOGI (LOG_TAG , " mtu update: mtu=%d" , event->mtu .value );
12011326 pClient->m_pClientCallbacks ->onMTUChange (pClient, event->mtu .value );
12021327 rc = 0 ;
@@ -1208,6 +1333,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
12081333 return 0 ;
12091334 }
12101335
1336+ if (pClient->completeConnectEstablished ()) {
1337+ pTaskData = nullptr ;
1338+ }
1339+
12111340 NimBLEConnInfo peerInfo;
12121341 rc = ble_gap_conn_find (event->passkey .conn_handle , &peerInfo.m_desc );
12131342 if (rc != 0 ) {
0 commit comments