Skip to content

Commit 4f1f2e8

Browse files
committed
fix(ble): Fix notification timing
1 parent 3dbf481 commit 4f1f2e8

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

libraries/BLE/src/BLECharacteristic.cpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,52 @@ void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, esp
904904

905905
#if defined(CONFIG_NIMBLE_ENABLED)
906906

907+
/**
908+
* @brief Process a deferred write callback.
909+
*
910+
* This function is called as a FreeRTOS task to execute the onWrite callback
911+
* after the write response has been sent to the client. This maintains backwards
912+
* compatibility with Bluedroid, where the write response is sent before the
913+
* onWrite callback is invoked.
914+
*
915+
* The delay is based on the connection interval to ensure the write response
916+
* packet has been transmitted over the air before the callback executes.
917+
*
918+
* See: https://github.com/espressif/arduino-esp32/issues/11938
919+
*/
920+
void BLECharacteristic::processDeferredWriteCallback(void *pvParameters) {
921+
DeferredWriteCallback *pCallback = (DeferredWriteCallback *)pvParameters;
922+
923+
// Get connection parameters to calculate appropriate delay
924+
ble_gap_conn_desc desc;
925+
int rc = ble_gap_conn_find(pCallback->conn_handle, &desc);
926+
927+
if (rc == 0) {
928+
// Connection interval is in units of 1.25ms
929+
// Wait for at least one connection interval to ensure the write response
930+
// has been transmitted. Add a small buffer for processing.
931+
uint16_t intervalMs = (desc.conn_itvl * 125) / 100; // Convert to milliseconds
932+
uint16_t delayMs = intervalMs + 5; // Add 5ms buffer
933+
934+
log_v("Deferring write callback by %dms (conn_interval=%d units, %dms)", delayMs, desc.conn_itvl, intervalMs);
935+
vTaskDelay(pdMS_TO_TICKS(delayMs));
936+
} else {
937+
// If we can't get connection parameters, use a conservative default
938+
// Most connections use 7.5-30ms intervals, so 50ms should be safe
939+
log_w("Could not get connection parameters, using default 50ms delay");
940+
vTaskDelay(pdMS_TO_TICKS(50));
941+
}
942+
943+
// Call the onWrite callback now that the response has been transmitted
944+
pCallback->pCharacteristic->m_pCallbacks->onWrite(pCallback->pCharacteristic, &pCallback->desc);
945+
946+
// Free the allocated memory
947+
delete pCallback;
948+
949+
// Delete this one-shot task
950+
vTaskDelete(NULL);
951+
}
952+
907953
int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
908954
const ble_uuid_t *uuid;
909955
int rc;
@@ -955,7 +1001,29 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
9551001
rc = ble_gap_conn_find(conn_handle, &desc);
9561002
assert(rc == 0);
9571003
pCharacteristic->setValue(buf, len);
958-
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc);
1004+
1005+
// Defer the onWrite callback to maintain backwards compatibility with Bluedroid.
1006+
// In Bluedroid, the write response is sent BEFORE the onWrite callback is invoked.
1007+
// In NimBLE, the response is sent implicitly when this function returns.
1008+
// By deferring the callback to a separate task with a delay based on the connection
1009+
// interval, we ensure the response packet is transmitted before the callback executes.
1010+
// See: https://github.com/espressif/arduino-esp32/issues/11938
1011+
DeferredWriteCallback *pCallback = new DeferredWriteCallback();
1012+
pCallback->pCharacteristic = pCharacteristic;
1013+
pCallback->desc = desc;
1014+
pCallback->conn_handle = conn_handle;
1015+
1016+
// Create a one-shot task to execute the callback after the response is transmitted
1017+
// Using priority 1 (low priority) and sufficient stack for callback operations
1018+
// Note: Stack must be large enough to handle notify() calls from within onWrite()
1019+
xTaskCreate(
1020+
processDeferredWriteCallback,
1021+
"BLEWriteCB",
1022+
4096, // Stack size - increased to handle notify() operations
1023+
pCallback,
1024+
1, // Priority (low)
1025+
NULL // Task handle (not needed for one-shot task)
1026+
);
9591027

9601028
return 0;
9611029
}

libraries/BLE/src/BLECharacteristic.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
#include <host/ble_gatt.h>
4848
#include <host/ble_att.h>
4949
#include "BLEConnInfo.h"
50+
#include <freertos/FreeRTOS.h>
51+
#include <freertos/task.h>
5052
#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN
5153
#define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ
5254
#define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE
@@ -246,6 +248,13 @@ class BLECharacteristic {
246248
portMUX_TYPE m_readMux;
247249
uint8_t m_removed;
248250
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
251+
252+
// Deferred callback support for maintaining backwards compatibility with Bluedroid timing
253+
struct DeferredWriteCallback {
254+
BLECharacteristic *pCharacteristic;
255+
ble_gap_conn_desc desc;
256+
uint16_t conn_handle;
257+
};
249258
#endif
250259

251260
/***************************************************************************
@@ -271,6 +280,7 @@ class BLECharacteristic {
271280
#if defined(CONFIG_NIMBLE_ENABLED)
272281
void setSubscribe(struct ble_gap_event *event);
273282
static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
283+
static void processDeferredWriteCallback(void *pvParameters);
274284
#endif
275285
}; // BLECharacteristic
276286

0 commit comments

Comments
 (0)