feat(ble_esl): speed up OTS by lengthening ACL events

A 15 ms link with max_ce_len=0 sent one DLE PDU per event and capped
badge frames at ~17 KB/s. Request 7.5–15 ms and a long CE, and refill
the single CoC TX slot instead of treating EBUSY as a hard failure.
This commit is contained in:
jiminxiang
2026-09-11 19:57:18 +08:00
parent b17865bfa4
commit 6c9c9979d3
2 changed files with 69 additions and 31 deletions

View File

@@ -37,10 +37,15 @@ static const char *TAG = "esl_ap_conn";
/** Connection-establishment timeout for the PAwR connection procedure (ms) */
#define PAWR_CONNECT_TIMEOUT_MS 30000
/* Bulk OTS transfers need a low-latency, full-length 2M ACL link. */
#define ESL_CONN_ITVL_MIN 12 /* 15 ms */
#define ESL_CONN_ITVL_MAX 24 /* 30 ms */
/* Bulk OTS transfers need a low-latency, full-length 2M ACL link.
* One 1024-byte CoC SDU is ~5 DLE PDUs. With max_ce_len=0 the controller
* typically sends one PDU per event, which caps a 15 ms link at ~17 KB/s
* (the rate measured on 460800-byte badge frames). A long CE lets one
* event carry the whole SDU; NimBLE CoC still only holds one TX SDU. */
#define ESL_CONN_ITVL_MIN 6 /* 7.5 ms */
#define ESL_CONN_ITVL_MAX 12 /* 15 ms */
#define ESL_CONN_TIMEOUT 400 /* 4 s */
#define ESL_CONN_CE_LEN_MAX 0xffff
#define ESL_LL_TX_OCTETS 251
#define ESL_LL_TX_TIME 2120
@@ -71,7 +76,7 @@ static void request_fast_esl_link(uint16_t conn_handle)
.latency = 0,
.supervision_timeout = ESL_CONN_TIMEOUT,
.min_ce_len = 0,
.max_ce_len = 0,
.max_ce_len = ESL_CONN_CE_LEN_MAX,
};
int rc = ble_gap_update_params(conn_handle, &params);

View File

@@ -173,6 +173,8 @@ void ble_esl_ap_lifecycle_handle_ots_event(uint16_t conn_id,
/** Active image transfer context (one at a time) */
static image_transfer_ctx_t *s_image_ctx = NULL;
static bool s_image_pumping;
static bool s_image_done;
/** Active synchronize context (only one at a time; new requests are rejected while active) */
static synchronize_ctx_t *s_sync_ctx = NULL;
@@ -1192,6 +1194,8 @@ esp_err_t ble_esl_ap_transfer_image(const ble_esl_ap_image_transfer_params_t *pa
s_image_ctx->data_len = params->data_len;
s_image_ctx->truncate = params->truncate;
s_image_ctx->otc_mtu = 256; /* Default L2CAP OTC MTU; updated on CHANNEL_OPEN */
s_image_pumping = false;
s_image_done = false;
/* Step 1: Select the target object via OLCP Go To (over GATT).
* Per the OTS spec, the Current Object must be selected *before* the
@@ -1255,6 +1259,46 @@ static void image_transfer_finish(uint16_t conn_id, esp_err_t status)
free(s_image_ctx);
s_image_ctx = NULL;
s_image_pumping = false;
s_image_done = false;
}
/* NimBLE CoC TX has a single SDU slot. Keep filling it until the stack is
* busy or stalled; DATA_SENT / TX_UNSTALLED refill. Do not finish from the
* nested DATA_SENT that send_data() dispatches on success. */
static int image_transfer_pump(uint16_t conn_id)
{
if (s_image_ctx == NULL || s_image_pumping) {
return 0;
}
s_image_pumping = true;
int rc = 0;
for (int burst = 0; burst < 4; burst++) {
uint32_t offset = s_image_ctx->sent_offset;
if (offset >= s_image_ctx->data_len) {
break;
}
uint32_t left = s_image_ctx->data_len - offset;
uint16_t chunk = (left > s_image_ctx->otc_mtu) ?
s_image_ctx->otc_mtu : (uint16_t)left;
rc = ble_ots_client_send_data(conn_id, s_image_ctx->data + offset, chunk);
if (rc == BLE_HS_EBUSY) {
rc = 0;
break;
}
if (rc != 0) {
break;
}
s_image_ctx->sent_offset += chunk;
}
s_image_pumping = false;
if (rc == 0 && s_image_done) {
ESP_LOGI(TAG, "transfer_image: image transfer complete for index %d",
s_image_ctx->image_index);
image_transfer_finish(conn_id, ESP_OK);
}
return rc;
}
/**
@@ -1337,15 +1381,11 @@ void ble_esl_ap_lifecycle_handle_ots_event(uint16_t conn_id,
case BLE_OTS_CLIENT_EVT_OACP_RESPONSE: {
const ble_ots_client_oacp_response_t *oacp = (const ble_ots_client_oacp_response_t *)param;
if (oacp->request_opcode == 0x06 && oacp->result_code == 0x01) {
/* OACP Write success — send the first chunk of data.
* One send_data() call becomes one L2CAP SDU, so it must not
* exceed the negotiated OTC MTU (max SDU size) or ble_l2cap_send
* rejects it with BLE_HS_EBADDATA. Chunk by otc_mtu. */
/* OACP Write success — fill the CoC TX slot. One send_data() call
* is one L2CAP SDU and must stay within otc_mtu. */
ESP_LOGD(TAG, "transfer_image: OACP Write accepted, sending data");
uint16_t chunk_len = (s_image_ctx->data_len > s_image_ctx->otc_mtu) ?
s_image_ctx->otc_mtu : (uint16_t)s_image_ctx->data_len;
s_image_ctx->sent_offset = chunk_len;
int rc = ble_ots_client_send_data(conn_id, s_image_ctx->data, chunk_len);
s_image_done = false;
int rc = image_transfer_pump(conn_id);
if (rc != 0) {
ESP_LOGE(TAG, "transfer_image: send_data failed; rc=%d", rc);
image_transfer_finish(conn_id, ESP_FAIL);
@@ -1361,26 +1401,19 @@ void ble_esl_ap_lifecycle_handle_ots_event(uint16_t conn_id,
case BLE_OTS_CLIENT_EVT_DATA_SENT: {
const ble_ots_client_data_sent_t *sent = (const ble_ots_client_data_sent_t *)param;
if (sent->remaining == 0) {
/* Transfer complete */
ESP_LOGI(TAG, "transfer_image: image transfer complete for index %d",
s_image_ctx->image_index);
image_transfer_finish(conn_id, ESP_OK);
} else {
/* Send the next chunk, capped at the negotiated OTC MTU (one SDU). */
uint32_t offset = s_image_ctx->data_len - sent->remaining;
uint16_t chunk_len = (sent->remaining > s_image_ctx->otc_mtu) ?
s_image_ctx->otc_mtu : (uint16_t)sent->remaining;
s_image_ctx->sent_offset = offset + chunk_len;
ESP_LOGD(TAG, "transfer_image: sending next chunk at offset %lu, %u bytes "
"(%lu remaining)", (unsigned long)offset, chunk_len,
(unsigned long)sent->remaining);
int rc = ble_ots_client_send_data(conn_id,
s_image_ctx->data + offset,
chunk_len);
if (rc != 0) {
ESP_LOGE(TAG, "transfer_image: send_data failed for chunk; rc=%d", rc);
image_transfer_finish(conn_id, ESP_FAIL);
if (s_image_pumping) {
s_image_done = true;
} else {
ESP_LOGI(TAG, "transfer_image: image transfer complete for index %d",
s_image_ctx->image_index);
image_transfer_finish(conn_id, ESP_OK);
}
break;
}
int rc = image_transfer_pump(conn_id);
if (rc != 0) {
ESP_LOGE(TAG, "transfer_image: send_data failed for chunk; rc=%d", rc);
image_transfer_finish(conn_id, ESP_FAIL);
}
break;
}