mirror of
https://github.com/esphome/esphome.git
synced 2025-08-02 16:37:46 +00:00
[bluetooth_proxy] Implement dynamic service batching based on MTU constraints (#10001)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
161f51e1f4
commit
d4ff1bcf5c
@ -37,6 +37,37 @@ static void fill_gatt_uuid(std::array<uint64_t, 2> &uuid_128, uint32_t &short_uu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants for size estimation
|
||||||
|
static constexpr uint8_t SERVICE_OVERHEAD_LEGACY = 25; // UUID(20) + handle(4) + overhead(1)
|
||||||
|
static constexpr uint8_t SERVICE_OVERHEAD_EFFICIENT = 10; // UUID(6) + handle(4)
|
||||||
|
static constexpr uint8_t CHAR_SIZE_128BIT = 35; // UUID(20) + handle(4) + props(4) + overhead(7)
|
||||||
|
static constexpr uint8_t DESC_SIZE_128BIT = 25; // UUID(20) + handle(4) + overhead(1)
|
||||||
|
static constexpr uint8_t DESC_SIZE_16BIT = 10; // UUID(6) + handle(4)
|
||||||
|
static constexpr uint8_t DESC_PER_CHAR = 1; // Assume 1 descriptor per characteristic
|
||||||
|
|
||||||
|
// Helper to estimate service size before fetching all data
|
||||||
|
/**
|
||||||
|
* Estimate the size of a Bluetooth service based on the number of characteristics and UUID format.
|
||||||
|
*
|
||||||
|
* @param char_count The number of characteristics in the service.
|
||||||
|
* @param use_efficient_uuids Whether to use efficient UUIDs (16-bit or 32-bit) for newer APIVersions.
|
||||||
|
* @return The estimated size of the service in bytes.
|
||||||
|
*
|
||||||
|
* This function calculates the size of a Bluetooth service by considering:
|
||||||
|
* - A service overhead, which depends on whether efficient UUIDs are used.
|
||||||
|
* - The size of each characteristic, assuming 128-bit UUIDs for safety.
|
||||||
|
* - The size of descriptors, assuming one 128-bit descriptor per characteristic.
|
||||||
|
*/
|
||||||
|
static size_t estimate_service_size(uint16_t char_count, bool use_efficient_uuids) {
|
||||||
|
size_t service_overhead = use_efficient_uuids ? SERVICE_OVERHEAD_EFFICIENT : SERVICE_OVERHEAD_LEGACY;
|
||||||
|
// Always assume 128-bit UUIDs for characteristics to be safe
|
||||||
|
size_t char_size = CHAR_SIZE_128BIT;
|
||||||
|
// Assume one 128-bit descriptor per characteristic
|
||||||
|
size_t desc_size = DESC_SIZE_128BIT * DESC_PER_CHAR;
|
||||||
|
|
||||||
|
return service_overhead + (char_size + desc_size) * char_count;
|
||||||
|
}
|
||||||
|
|
||||||
bool BluetoothConnection::supports_efficient_uuids_() const {
|
bool BluetoothConnection::supports_efficient_uuids_() const {
|
||||||
auto *api_conn = this->proxy_->get_api_connection();
|
auto *api_conn = this->proxy_->get_api_connection();
|
||||||
return api_conn && api_conn->client_supports_api_version(1, 12);
|
return api_conn && api_conn->client_supports_api_version(1, 12);
|
||||||
@ -95,16 +126,21 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
// Check if client supports efficient UUIDs
|
// Check if client supports efficient UUIDs
|
||||||
bool use_efficient_uuids = this->supports_efficient_uuids_();
|
bool use_efficient_uuids = this->supports_efficient_uuids_();
|
||||||
|
|
||||||
// Prepare response for up to 3 services
|
// Prepare response
|
||||||
api::BluetoothGATTGetServicesResponse resp;
|
api::BluetoothGATTGetServicesResponse resp;
|
||||||
resp.address = this->address_;
|
resp.address = this->address_;
|
||||||
|
|
||||||
// Process up to 3 services in this iteration
|
// Dynamic batching based on actual size
|
||||||
uint8_t services_to_process =
|
// Conservative MTU limit for API messages (accounts for WPA3 overhead)
|
||||||
std::min(MAX_SERVICES_PER_BATCH, static_cast<uint8_t>(this->service_count_ - this->send_service_));
|
static constexpr size_t MAX_PACKET_SIZE = 1360;
|
||||||
resp.services.reserve(services_to_process);
|
|
||||||
|
|
||||||
for (int service_idx = 0; service_idx < services_to_process; service_idx++) {
|
// Keep running total of actual message size
|
||||||
|
size_t current_size = 0;
|
||||||
|
api::ProtoSize size;
|
||||||
|
resp.calculate_size(size);
|
||||||
|
current_size = size.get_size();
|
||||||
|
|
||||||
|
while (this->send_service_ < this->service_count_) {
|
||||||
esp_gattc_service_elem_t service_result;
|
esp_gattc_service_elem_t service_result;
|
||||||
uint16_t service_count = 1;
|
uint16_t service_count = 1;
|
||||||
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
|
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
|
||||||
@ -118,15 +154,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->send_service_++;
|
// Get the number of characteristics BEFORE adding to response
|
||||||
resp.services.emplace_back();
|
|
||||||
auto &service_resp = resp.services.back();
|
|
||||||
|
|
||||||
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, service_result.uuid, use_efficient_uuids);
|
|
||||||
|
|
||||||
service_resp.handle = service_result.start_handle;
|
|
||||||
|
|
||||||
// Get the number of characteristics directly with one call
|
|
||||||
uint16_t total_char_count = 0;
|
uint16_t total_char_count = 0;
|
||||||
esp_gatt_status_t char_count_status =
|
esp_gatt_status_t char_count_status =
|
||||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||||
@ -139,11 +167,22 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_char_count == 0) {
|
// If this service likely won't fit, send current batch (unless it's the first)
|
||||||
// No characteristics, continue to next service
|
size_t estimated_size = estimate_service_size(total_char_count, use_efficient_uuids);
|
||||||
continue;
|
if (!resp.services.empty() && (current_size + estimated_size > MAX_PACKET_SIZE)) {
|
||||||
|
// This service likely won't fit, send current batch
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now add the service since we know it will likely fit
|
||||||
|
resp.services.emplace_back();
|
||||||
|
auto &service_resp = resp.services.back();
|
||||||
|
|
||||||
|
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, service_result.uuid, use_efficient_uuids);
|
||||||
|
|
||||||
|
service_resp.handle = service_result.start_handle;
|
||||||
|
|
||||||
|
if (total_char_count > 0) {
|
||||||
// Reserve space and process characteristics
|
// Reserve space and process characteristics
|
||||||
service_resp.characteristics.reserve(total_char_count);
|
service_resp.characteristics.reserve(total_char_count);
|
||||||
uint16_t char_offset = 0;
|
uint16_t char_offset = 0;
|
||||||
@ -181,8 +220,8 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||||
|
|
||||||
if (desc_count_status != ESP_GATT_OK) {
|
if (desc_count_status != ESP_GATT_OK) {
|
||||||
ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_,
|
ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
||||||
this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -221,9 +260,40 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
desc_offset++;
|
desc_offset++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // end if (total_char_count > 0)
|
||||||
|
|
||||||
|
// Calculate the actual size of just this service
|
||||||
|
api::ProtoSize service_sizer;
|
||||||
|
service_resp.calculate_size(service_sizer);
|
||||||
|
size_t service_size = service_sizer.get_size() + 1; // +1 for field tag
|
||||||
|
|
||||||
|
// Check if adding this service would exceed the limit
|
||||||
|
if (current_size + service_size > MAX_PACKET_SIZE) {
|
||||||
|
// We would go over - pop the last service if we have more than one
|
||||||
|
if (resp.services.size() > 1) {
|
||||||
|
resp.services.pop_back();
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Service %d would exceed limit (current: %d + service: %d > %d), sending current batch",
|
||||||
|
this->connection_index_, this->address_str().c_str(), this->send_service_, current_size, service_size,
|
||||||
|
MAX_PACKET_SIZE);
|
||||||
|
// Don't increment send_service_ - we'll retry this service in next batch
|
||||||
|
} else {
|
||||||
|
// This single service is too large, but we have to send it anyway
|
||||||
|
ESP_LOGV(TAG, "[%d] [%s] Service %d is too large (%d bytes) but sending anyway", this->connection_index_,
|
||||||
|
this->address_str().c_str(), this->send_service_, service_size);
|
||||||
|
// Increment so we don't get stuck
|
||||||
|
this->send_service_++;
|
||||||
|
}
|
||||||
|
// Send what we have
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message with 1-3 services
|
// Now we know we're keeping this service, add its size
|
||||||
|
current_size += service_size;
|
||||||
|
// Successfully added this service, increment counter
|
||||||
|
this->send_service_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message with dynamically batched services
|
||||||
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||||
static const int DONE_SENDING_SERVICES = -2;
|
static const int DONE_SENDING_SERVICES = -2;
|
||||||
static const uint8_t MAX_SERVICES_PER_BATCH = 3;
|
|
||||||
|
|
||||||
using namespace esp32_ble_client;
|
using namespace esp32_ble_client;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user