44#include " ble_gap.h"
55#include " ble_hci.h"
66
7+ // Magic numbers came from actual testing
78#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds
9+ #define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected
10+
11+ // Connection parameters (units: interval=1.25ms, timeout=10ms)
12+ #define BLE_MIN_CONN_INTERVAL 12 // 15ms
13+ #define BLE_MAX_CONN_INTERVAL 24 // 30ms
14+ #define BLE_SLAVE_LATENCY 4
15+ #define BLE_CONN_SUP_TIMEOUT 200 // 2000ms
16+
17+ // Advertising parameters
18+ #define BLE_ADV_INTERVAL_MIN 32 // 20ms (units: 0.625ms)
19+ #define BLE_ADV_INTERVAL_MAX 244 // 152.5ms (units: 0.625ms)
20+ #define BLE_ADV_FAST_TIMEOUT 30 // seconds
21+
22+ // RX drain buffer size for overflow protection
23+ #define BLE_RX_DRAIN_BUF_SIZE 32
824
925static SerialBLEInterface* instance = nullptr ;
1026
@@ -38,14 +54,18 @@ void SerialBLEInterface::onSecured(uint16_t connection_handle) {
3854 // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic."
3955 // So we explicitly set it here to make Android & Apple match
4056 ble_gap_conn_params_t conn_params;
41- conn_params.min_conn_interval = 12 ; // 15ms
42- conn_params.max_conn_interval = 24 ; // 30ms
43- conn_params.slave_latency = 0 ;
44- conn_params.conn_sup_timeout = 200 ; // 2000ms
57+ conn_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
58+ conn_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
59+ conn_params.slave_latency = BLE_SLAVE_LATENCY ;
60+ conn_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
4561
4662 uint32_t err_code = sd_ble_gap_conn_param_update (connection_handle, &conn_params);
4763 if (err_code == NRF_SUCCESS) {
48- BLE_DEBUG_PRINTLN (" Connection parameter update requested: 15-30ms interval, 2s timeout" );
64+ BLE_DEBUG_PRINTLN (" Connection parameter update requested: %u-%ums interval, latency=%u, %ums timeout" ,
65+ conn_params.min_conn_interval * 5 / 4 , // convert to ms (1.25ms units)
66+ conn_params.max_conn_interval * 5 / 4 ,
67+ conn_params.slave_latency ,
68+ conn_params.conn_sup_timeout * 10 ); // convert to ms (10ms units)
4969 } else {
5070 BLE_DEBUG_PRINTLN (" Failed to request connection parameter update: %lu" , err_code);
5171 }
@@ -116,14 +136,18 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
116136
117137 // Connection interval units: 1.25ms, supervision timeout units: 10ms
118138 ble_gap_conn_params_t ppcp_params;
119- ppcp_params.min_conn_interval = 12 ; // 15ms
120- ppcp_params.max_conn_interval = 24 ; // 30ms
121- ppcp_params.slave_latency = 0 ;
122- ppcp_params.conn_sup_timeout = 200 ; // 2000ms
139+ ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
140+ ppcp_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
141+ ppcp_params.slave_latency = BLE_SLAVE_LATENCY ;
142+ ppcp_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
123143
124144 uint32_t err_code = sd_ble_gap_ppcp_set (&ppcp_params);
125145 if (err_code == NRF_SUCCESS) {
126- BLE_DEBUG_PRINTLN (" PPCP set: 15-30ms interval, 2s timeout" );
146+ BLE_DEBUG_PRINTLN (" PPCP set: %u-%ums interval, latency=%u, %ums timeout" ,
147+ ppcp_params.min_conn_interval * 5 / 4 , // convert to ms (1.25ms units)
148+ ppcp_params.max_conn_interval * 5 / 4 ,
149+ ppcp_params.slave_latency ,
150+ ppcp_params.conn_sup_timeout * 10 ); // convert to ms (10ms units)
127151 } else {
128152 BLE_DEBUG_PRINTLN (" Failed to set PPCP: %lu" , err_code);
129153 }
@@ -153,8 +177,8 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
153177
154178 Bluefruit.ScanResponse .addName ();
155179
156- Bluefruit.Advertising .setInterval (32 , 244 );
157- Bluefruit.Advertising .setFastTimeout (30 );
180+ Bluefruit.Advertising .setInterval (BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX );
181+ Bluefruit.Advertising .setFastTimeout (BLE_ADV_FAST_TIMEOUT );
158182
159183 Bluefruit.Advertising .restartOnDisconnect (true );
160184
@@ -163,6 +187,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
163187void SerialBLEInterface::clearBuffers () {
164188 send_queue_len = 0 ;
165189 recv_queue_len = 0 ;
190+ _last_retry_attempt = 0 ;
166191 bleuart.flush ();
167192}
168193
@@ -257,21 +282,30 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
257282 BLE_DEBUG_PRINTLN (" writeBytes: connection invalid, clearing send queue" );
258283 send_queue_len = 0 ;
259284 } else {
260- Frame frame_to_send = send_queue[0 ];
261-
262- size_t written = bleuart.write (frame_to_send.buf , frame_to_send.len );
263- if (written == frame_to_send.len ) {
264- BLE_DEBUG_PRINTLN (" writeBytes: sz=%u, hdr=%u" , (unsigned )frame_to_send.len , (unsigned )frame_to_send.buf [0 ]);
265- shiftSendQueueLeft ();
266- } else if (written > 0 ) {
267- BLE_DEBUG_PRINTLN (" writeBytes: partial write, sent=%u of %u, dropping corrupted frame" , (unsigned )written, (unsigned )frame_to_send.len );
268- shiftSendQueueLeft ();
269- } else {
270- if (!isConnected ()) {
271- BLE_DEBUG_PRINTLN (" writeBytes failed: connection lost, dropping frame" );
285+ unsigned long now = millis ();
286+ bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS);
287+
288+ if (!throttle_active) {
289+ Frame frame_to_send = send_queue[0 ];
290+
291+ size_t written = bleuart.write (frame_to_send.buf , frame_to_send.len );
292+ if (written == frame_to_send.len ) {
293+ BLE_DEBUG_PRINTLN (" writeBytes: sz=%u, hdr=%u" , (unsigned )frame_to_send.len , (unsigned )frame_to_send.buf [0 ]);
294+ _last_retry_attempt = 0 ;
295+ shiftSendQueueLeft ();
296+ } else if (written > 0 ) {
297+ BLE_DEBUG_PRINTLN (" writeBytes: partial write, sent=%u of %u, dropping corrupted frame" , (unsigned )written, (unsigned )frame_to_send.len );
298+ _last_retry_attempt = 0 ;
272299 shiftSendQueueLeft ();
273300 } else {
274- BLE_DEBUG_PRINTLN (" writeBytes failed (buffer full), keeping frame for retry" );
301+ if (!isConnected ()) {
302+ BLE_DEBUG_PRINTLN (" writeBytes failed: connection lost, dropping frame" );
303+ _last_retry_attempt = 0 ;
304+ shiftSendQueueLeft ();
305+ } else {
306+ BLE_DEBUG_PRINTLN (" writeBytes failed (buffer full), keeping frame for retry" );
307+ _last_retry_attempt = now;
308+ }
275309 }
276310 }
277311 }
@@ -329,9 +363,9 @@ void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) {
329363
330364 if (avail > MAX_FRAME_SIZE) {
331365 BLE_DEBUG_PRINTLN (" onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all" , avail);
332- uint8_t drain_buf[32 ];
366+ uint8_t drain_buf[BLE_RX_DRAIN_BUF_SIZE ];
333367 while (instance->bleuart .available () > 0 ) {
334- int chunk = instance->bleuart .available () > 32 ? 32 : instance->bleuart .available ();
368+ int chunk = instance->bleuart .available () > BLE_RX_DRAIN_BUF_SIZE ? BLE_RX_DRAIN_BUF_SIZE : instance->bleuart .available ();
335369 instance->bleuart .readBytes (drain_buf, chunk);
336370 }
337371 continue ;
@@ -349,5 +383,5 @@ bool SerialBLEInterface::isConnected() const {
349383}
350384
351385bool SerialBLEInterface::isWriteBusy () const {
352- return send_queue_len >= (FRAME_QUEUE_SIZE - 1 );
386+ return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3 );
353387}
0 commit comments