mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
Eliminate heap allocations in bluetooth_proxy connection state reporting
This commit is contained in:
@@ -1621,7 +1621,10 @@ message BluetoothConnectionsFreeResponse {
|
||||
|
||||
uint32 free = 1;
|
||||
uint32 limit = 2;
|
||||
repeated uint64 allocated = 3;
|
||||
repeated uint64 allocated = 3 [
|
||||
(fixed_array_size_define) = "BLUETOOTH_PROXY_MAX_CONNECTIONS",
|
||||
(fixed_array_skip_zero) = true
|
||||
];
|
||||
}
|
||||
|
||||
message BluetoothGATTErrorResponse {
|
||||
|
||||
@@ -1105,10 +1105,8 @@ void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg)
|
||||
|
||||
bool APIConnection::send_subscribe_bluetooth_connections_free_response(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||
BluetoothConnectionsFreeResponse resp;
|
||||
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
|
||||
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
||||
return this->send_message(resp, BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
|
||||
bluetooth_proxy::global_bluetooth_proxy->send_connections_free();
|
||||
return true;
|
||||
}
|
||||
|
||||
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
|
||||
|
||||
@@ -29,6 +29,7 @@ extend google.protobuf.FieldOptions {
|
||||
optional uint32 fixed_array_size = 50007;
|
||||
optional bool no_zero_copy = 50008 [default=false];
|
||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||
optional string fixed_array_size_define = 50010;
|
||||
|
||||
// container_pointer: Zero-copy optimization for repeated fields.
|
||||
//
|
||||
|
||||
@@ -2073,15 +2073,17 @@ void BluetoothGATTNotifyDataResponse::calculate_size(ProtoSize &size) const {
|
||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->free);
|
||||
buffer.encode_uint32(2, this->limit);
|
||||
for (auto &it : this->allocated) {
|
||||
buffer.encode_uint64(3, it, true);
|
||||
for (const auto &it : this->allocated) {
|
||||
if (it != 0) {
|
||||
buffer.encode_uint64(3, it, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void BluetoothConnectionsFreeResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->free);
|
||||
size.add_uint32(1, this->limit);
|
||||
if (!this->allocated.empty()) {
|
||||
for (const auto &it : this->allocated) {
|
||||
for (const auto &it : this->allocated) {
|
||||
if (it != 0) {
|
||||
size.add_uint64_force(1, it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2076,13 +2076,13 @@ class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage {
|
||||
class BluetoothConnectionsFreeResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 81;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 16;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 20;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_connections_free_response"; }
|
||||
#endif
|
||||
uint32_t free{0};
|
||||
uint32_t limit{0};
|
||||
std::vector<uint64_t> allocated{};
|
||||
std::array<uint64_t, BLUETOOTH_PROXY_MAX_CONNECTIONS> allocated{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
||||
@@ -87,6 +87,10 @@ async def to_code(config):
|
||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||
await esp32_ble_tracker.register_raw_ble_device(var, config)
|
||||
|
||||
# Define max connections for protobuf fixed array
|
||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
||||
|
||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
await cg.register_component(connection_var, connection_conf)
|
||||
|
||||
@@ -78,6 +78,20 @@ void BluetoothConnection::dump_config() {
|
||||
BLEClientBase::dump_config();
|
||||
}
|
||||
|
||||
void BluetoothConnection::set_address(uint64_t address) {
|
||||
// If we're clearing an address (disconnecting), update the pre-allocated message
|
||||
if (address == 0 && this->address_ != 0) {
|
||||
this->proxy_->free_connection_(this->address_);
|
||||
}
|
||||
// If we're setting a new address (connecting), update the pre-allocated message
|
||||
else if (address != 0 && this->address_ == 0) {
|
||||
this->proxy_->allocate_connection_(this, address);
|
||||
}
|
||||
|
||||
// Call parent implementation to actually set the address
|
||||
BLEClientBase::set_address(address);
|
||||
}
|
||||
|
||||
void BluetoothConnection::loop() {
|
||||
BLEClientBase::loop();
|
||||
|
||||
@@ -100,6 +114,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||
// (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT)
|
||||
// to detect incomplete service discovery rather than relying on us to
|
||||
// tell them about a partial list.
|
||||
|
||||
this->set_address(0);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_connections_free();
|
||||
|
||||
@@ -24,6 +24,8 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
|
||||
esp_err_t notify_characteristic(uint16_t handle, bool enable);
|
||||
|
||||
void set_address(uint64_t address) override;
|
||||
|
||||
protected:
|
||||
friend class BluetoothProxy;
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ void BluetoothProxy::setup() {
|
||||
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
||||
// Many devices in quiet areas will never need the overflow pool
|
||||
|
||||
this->connections_free_response_.limit = this->connections_.size();
|
||||
this->connections_free_response_.free = this->connections_.size();
|
||||
|
||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
@@ -134,20 +137,6 @@ void BluetoothProxy::dump_config() {
|
||||
YESNO(this->active_), this->connections_.size());
|
||||
}
|
||||
|
||||
int BluetoothProxy::get_bluetooth_connections_free() {
|
||||
int free = 0;
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->address_ == 0) {
|
||||
free++;
|
||||
ESP_LOGV(TAG, "[%d] Free connection", connection->get_connection_index());
|
||||
} else {
|
||||
ESP_LOGV(TAG, "[%d] Used connection by [%s]", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
}
|
||||
}
|
||||
return free;
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
for (auto *connection : this->connections_) {
|
||||
@@ -278,6 +267,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
if (connection->state() != espbt::ClientState::IDLE) {
|
||||
connection->disconnect();
|
||||
} else {
|
||||
// Manual disconnect for idle connection
|
||||
connection->set_address(0);
|
||||
this->send_device_connection(msg.address, false);
|
||||
this->send_connections_free();
|
||||
@@ -441,15 +431,9 @@ void BluetoothProxy::send_device_connection(uint64_t address, bool connected, ui
|
||||
void BluetoothProxy::send_connections_free() {
|
||||
if (this->api_connection_ == nullptr)
|
||||
return;
|
||||
api::BluetoothConnectionsFreeResponse call;
|
||||
call.free = this->get_bluetooth_connections_free();
|
||||
call.limit = this->get_bluetooth_connections_limit();
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->address_ != 0) {
|
||||
call.allocated.push_back(connection->address_);
|
||||
}
|
||||
}
|
||||
this->api_connection_->send_message(call, api::BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
|
||||
|
||||
this->api_connection_->send_message(this->connections_free_response_,
|
||||
api::BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_gatt_services_done(uint64_t address) {
|
||||
@@ -499,6 +483,33 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
true); // Set this to true to automatically start scanning again when it has cleaned up.
|
||||
}
|
||||
|
||||
void BluetoothProxy::allocate_connection_(BluetoothConnection *connection, uint64_t address) {
|
||||
// Update pre-allocated message directly
|
||||
this->connections_free_response_.free--;
|
||||
|
||||
// Find first zero slot and set it
|
||||
auto it = std::find(this->connections_free_response_.allocated.begin(),
|
||||
this->connections_free_response_.allocated.end(), 0);
|
||||
if (it != this->connections_free_response_.allocated.end()) {
|
||||
*it = address;
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::free_connection_(uint64_t address) {
|
||||
if (address == 0)
|
||||
return; // Safety check
|
||||
|
||||
// Update pre-allocated message directly
|
||||
this->connections_free_response_.free++;
|
||||
|
||||
// Find the address and set to 0
|
||||
auto it = std::find(this->connections_free_response_.allocated.begin(),
|
||||
this->connections_free_response_.allocated.end(), address);
|
||||
if (it != this->connections_free_response_.allocated.end()) {
|
||||
*it = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome::bluetooth_proxy
|
||||
|
||||
@@ -49,6 +49,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
};
|
||||
|
||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
friend class BluetoothConnection; // Allow connection to call free_connection_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
@@ -74,9 +75,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
||||
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
||||
|
||||
int get_bluetooth_connections_free();
|
||||
int get_bluetooth_connections_limit() { return this->connections_.size(); }
|
||||
|
||||
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags);
|
||||
void unsubscribe_api_connection(api::APIConnection *api_connection);
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
@@ -135,6 +133,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
|
||||
// Helper functions for connection state management
|
||||
void allocate_connection_(BluetoothConnection *connection, uint64_t address);
|
||||
void free_connection_(uint64_t address);
|
||||
|
||||
// Memory optimized layout for 32-bit systems
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
api::APIConnection *api_connection_{nullptr};
|
||||
@@ -149,6 +151,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
|
||||
// Pre-allocated response message - always ready to send
|
||||
api::BluetoothConnectionsFreeResponse connections_free_response_;
|
||||
|
||||
// Group 4: 1-byte types grouped together
|
||||
bool active_;
|
||||
uint8_t advertisement_count_{0};
|
||||
|
||||
@@ -48,7 +48,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
|
||||
void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; }
|
||||
|
||||
void set_address(uint64_t address) {
|
||||
virtual void set_address(uint64_t address) {
|
||||
this->address_ = address;
|
||||
this->remote_bda_[0] = (address >> 40) & 0xFF;
|
||||
this->remote_bda_[1] = (address >> 32) & 0xFF;
|
||||
|
||||
Reference in New Issue
Block a user