Send large BLE host frames across multiple notifies.
Prefer ATT MTU 517 and chunk oversized responses so CLIENT_INFO/BATTERY work with many pods; reset the goTool parser after timeouts so partial frames do not poison the next exchange. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-13
@@ -4,6 +4,7 @@
|
||||
#include "host_transport.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "nimble/nimble_port.h"
|
||||
@@ -22,6 +23,12 @@ static const char *DEVICE_NAME = "Powerpod";
|
||||
#define BLE_CONN_ITVL_MIN_UNITS 6 /* 7.5 ms */
|
||||
#define BLE_CONN_ITVL_MAX_UNITS 6
|
||||
#define BLE_CONN_TUNE_MAX_TRIES 6
|
||||
/** Prefer large ATT MTU so CLIENT_INFO / BATTERY fit in fewer notify chunks. */
|
||||
#define BLE_PREFERRED_ATT_MTU 517
|
||||
#define BLE_NOTIFY_CHUNK_RETRIES 24
|
||||
#define BLE_NOTIFY_CHUNK_RETRY_MS 5
|
||||
/** Pace multi-chunk notifies so the central parser sees contiguous frame bytes. */
|
||||
#define BLE_NOTIFY_CHUNK_GAP_MS 8
|
||||
|
||||
/*
|
||||
* Nordic UART style:
|
||||
@@ -43,6 +50,7 @@ static bool notify_enabled;
|
||||
static uint16_t att_mtu = 23;
|
||||
static esp_timer_handle_t conn_tune_timer;
|
||||
static uint8_t conn_tune_tries;
|
||||
static SemaphoreHandle_t s_tx_mu;
|
||||
|
||||
static int ble_gap_event(struct ble_gap_event *event, void *arg);
|
||||
static void ble_request_fast_conn_params(uint16_t handle);
|
||||
@@ -61,28 +69,83 @@ uint16_t ble_host_max_payload(void) {
|
||||
return (uint16_t)(att_mtu - 3);
|
||||
}
|
||||
|
||||
static esp_err_t ble_host_tx(const uint8_t *frame, size_t frame_len) {
|
||||
struct os_mbuf *om;
|
||||
int rc;
|
||||
/**
|
||||
* One ATT notification (≤ MTU-3). Retries on transient NimBLE buffer pressure.
|
||||
*/
|
||||
static esp_err_t ble_notify_chunk(const uint8_t *data, uint16_t len) {
|
||||
for (int attempt = 0; attempt < BLE_NOTIFY_CHUNK_RETRIES; attempt++) {
|
||||
struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len);
|
||||
if (om == NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BLE_NOTIFY_CHUNK_RETRY_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
int rc = ble_gatts_notify_custom(conn_handle, ble_chr_val_handle, om);
|
||||
if (rc == 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
/* Ownership stays with us on failure. */
|
||||
os_mbuf_free_chain(om);
|
||||
if (!ble_host_ready()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
ESP_LOGD(TAG, "notify chunk rc=%d attempt=%d len=%u", rc, attempt,
|
||||
(unsigned)len);
|
||||
vTaskDelay(pdMS_TO_TICKS(BLE_NOTIFY_CHUNK_RETRY_MS));
|
||||
}
|
||||
ESP_LOGW(TAG, "notify chunk gave up after retries len=%u", (unsigned)len);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a full host frame. If larger than ATT MTU-3, split across consecutive
|
||||
* notifies; goTool feeds every notify into the same byte parser, so the frame
|
||||
* reassembles without a protocol change. Mutex keeps stream ticks from
|
||||
* interleaving mid-frame.
|
||||
*/
|
||||
static esp_err_t ble_host_tx(const uint8_t *frame, size_t frame_len) {
|
||||
if (!ble_host_ready()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (frame_len == 0 || frame_len > ble_host_max_payload()) {
|
||||
if (frame == NULL || frame_len == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint16_t max_chunk = ble_host_max_payload();
|
||||
if (max_chunk == 0) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
om = ble_hs_mbuf_from_flat(frame, (uint16_t)frame_len);
|
||||
if (om == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
if (s_tx_mu == NULL ||
|
||||
xSemaphoreTake(s_tx_mu, pdMS_TO_TICKS(2000)) != pdTRUE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
rc = ble_gatts_notify_custom(conn_handle, ble_chr_val_handle, om);
|
||||
if (rc != 0) {
|
||||
os_mbuf_free_chain(om);
|
||||
return ESP_FAIL;
|
||||
esp_err_t result = ESP_OK;
|
||||
size_t off = 0;
|
||||
while (off < frame_len) {
|
||||
if (!ble_host_ready()) {
|
||||
result = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
size_t remain = frame_len - off;
|
||||
uint16_t n =
|
||||
(uint16_t)(remain > max_chunk ? max_chunk : remain);
|
||||
result = ble_notify_chunk(&frame[off], n);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGW(TAG, "TX abort at %u/%u (chunk %u, mtu_pay %u)",
|
||||
(unsigned)off, (unsigned)frame_len, (unsigned)n,
|
||||
(unsigned)max_chunk);
|
||||
break;
|
||||
}
|
||||
off += n;
|
||||
if (off < frame_len) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BLE_NOTIFY_CHUNK_GAP_MS));
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
xSemaphoreGive(s_tx_mu);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ble_conn_tune_timer_cb(void *arg) {
|
||||
@@ -365,6 +428,12 @@ esp_err_t init_ble_host(QueueHandle_t cmd_queue) {
|
||||
host_transport_set_cmd_queue(cmd_queue);
|
||||
host_transport_set_tx(ble_host_tx);
|
||||
|
||||
s_tx_mu = xSemaphoreCreateMutex();
|
||||
if (s_tx_mu == NULL) {
|
||||
ESP_LOGE(TAG, "failed to create TX mutex");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/* NVS may already be init via pod_settings; ignore ALREADY_INIT. */
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
@@ -404,9 +473,11 @@ esp_err_t init_ble_host(QueueHandle_t cmd_queue) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rc = ble_att_set_preferred_mtu(247);
|
||||
rc = ble_att_set_preferred_mtu(BLE_PREFERRED_ATT_MTU);
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(TAG, "preferred MTU set failed: %d", rc);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "preferred ATT MTU %u", (unsigned)BLE_PREFERRED_ATT_MTU);
|
||||
}
|
||||
|
||||
nimble_port_freertos_init(ble_host_task);
|
||||
|
||||
Reference in New Issue
Block a user