mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
fix(ble): back off BLE send retries instead of hammering a marginal link
At the edge of BLE range the phone repeatedly drops and reconnects, and each queued outgoing frame's write can block the main loop up to 100ms (Bluefruit's notify() waits on a free HVN packet slot). The fixed 250ms retry throttle re-triggered that blocking wait constantly during a bad link, making the device feel almost unresponsive. Retries now back off exponentially (250ms -> 2s cap) on repeated failure and reset on the next successful send. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
// Magic numbers came from actual testing
|
||||
#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds
|
||||
#define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected
|
||||
#define BLE_RETRY_THROTTLE_MAX_MS 2000 // Cap for backoff below — a marginal link that keeps
|
||||
// failing the (blocking, up to 100ms) send shouldn't get
|
||||
// re-probed every 250ms, stealing ~40% of loop time
|
||||
|
||||
// Connection parameters (units: interval=1.25ms, timeout=10ms)
|
||||
#define BLE_MIN_CONN_INTERVAL 12 // 15ms
|
||||
@@ -205,6 +208,7 @@ void SerialBLEInterface::clearBuffers() {
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
_last_retry_attempt = 0;
|
||||
_retry_backoff_ms = 0;
|
||||
bleuart.flush();
|
||||
}
|
||||
|
||||
@@ -302,7 +306,8 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
send_queue_len = 0;
|
||||
} else {
|
||||
unsigned long now = millis();
|
||||
bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS);
|
||||
unsigned long throttle_ms = _retry_backoff_ms > 0 ? _retry_backoff_ms : BLE_RETRY_THROTTLE_MS;
|
||||
bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < throttle_ms);
|
||||
|
||||
if (!throttle_active) {
|
||||
Frame frame_to_send = send_queue[0];
|
||||
@@ -311,19 +316,28 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
if (written == frame_to_send.len) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]);
|
||||
_last_retry_attempt = 0;
|
||||
_retry_backoff_ms = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else if (written > 0) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len);
|
||||
_last_retry_attempt = 0;
|
||||
_retry_backoff_ms = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else {
|
||||
if (!isConnected()) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame");
|
||||
_last_retry_attempt = 0;
|
||||
_retry_backoff_ms = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry");
|
||||
// Send blocked (peer not ACKing — e.g. marginal link range). Each attempt above can
|
||||
// itself block up to 100ms (Bluefruit's notify() waits on a free HVN packet slot), so
|
||||
// back off exponentially instead of re-probing every fixed 250ms — otherwise a
|
||||
// sustained bad link steals ~40% of loop time indefinitely.
|
||||
BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry, backoff=%lums", throttle_ms);
|
||||
_last_retry_attempt = now;
|
||||
unsigned long next = throttle_ms * 2;
|
||||
_retry_backoff_ms = next > BLE_RETRY_THROTTLE_MAX_MS ? BLE_RETRY_THROTTLE_MAX_MS : next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class SerialBLEInterface : public BaseSerialInterface {
|
||||
uint16_t _conn_handle;
|
||||
unsigned long _last_health_check;
|
||||
unsigned long _last_retry_attempt;
|
||||
unsigned long _retry_backoff_ms; // 0 == not yet backed off; grows on repeated failed sends
|
||||
|
||||
struct Frame {
|
||||
uint8_t len;
|
||||
@@ -49,6 +50,7 @@ public:
|
||||
_conn_handle = BLE_CONN_HANDLE_INVALID;
|
||||
_last_health_check = 0;
|
||||
_last_retry_attempt = 0;
|
||||
_retry_backoff_ms = 0;
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user