From 0d4a6fa350069af81ad7a8459cfb0aa0e46d6ec7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Nov 2025 21:41:17 -0600 Subject: [PATCH] [ble_client] Optimize ble_write memory usage - store static data in flash --- esphome/components/ble_client/__init__.py | 8 ++++- esphome/components/ble_client/automation.h | 37 +++++++++------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 768a345213..37db181584 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VALUE, ) +from esphome.core import ID AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix", "@clydebarrow"] @@ -198,7 +199,12 @@ async def ble_write_to_code(config, action_id, template_arg, args): templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_value_template(templ)) else: - cg.add(var.set_value_simple(value)) + # Generate static array in flash to avoid RAM copy + if isinstance(value, bytes): + value = list(value) + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*value)) + cg.add(var.set_value_simple(arr, len(value))) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index ce534501f3..46915be9ed 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -96,11 +96,8 @@ template class BLEClientWriteAction : public Action, publ BLEClientWriteAction(BLEClient *ble_client) { ble_client->register_ble_node(this); ble_client_ = ble_client; - this->construct_simple_value_(); } - ~BLEClientWriteAction() { this->destroy_simple_value_(); } - void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } @@ -110,16 +107,14 @@ template class BLEClientWriteAction : public Action, publ void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_value_template(std::vector (*func)(Ts...)) { - this->destroy_simple_value_(); this->value_.template_func = func; this->has_simple_value_ = false; } - void set_value_simple(const std::vector &value) { - if (!this->has_simple_value_) { - this->construct_simple_value_(); - } - this->value_.simple = value; + // Store pointer to static data in flash (no RAM copy) + void set_value_simple(const uint8_t *data, size_t len) { + this->value_.simple_data.ptr = data; + this->value_.simple_data.len = len; this->has_simple_value_ = true; } @@ -128,7 +123,12 @@ template class BLEClientWriteAction : public Action, publ void play_complex(const Ts &...x) override { this->num_running_++; this->var_ = std::make_tuple(x...); - auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...); + std::vector value; + if (this->has_simple_value_) { + value.assign(this->value_.simple_data.ptr, this->value_.simple_data.ptr + this->value_.simple_data.len); + } else { + value = this->value_.template_func(x...); + } // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. if (!write(value)) this->play_next_(x...); @@ -201,21 +201,14 @@ template class BLEClientWriteAction : public Action, publ } private: - void construct_simple_value_() { new (&this->value_.simple) std::vector(); } - - void destroy_simple_value_() { - if (this->has_simple_value_) { - this->value_.simple.~vector(); - } - } - BLEClient *ble_client_; - bool has_simple_value_ = true; + bool has_simple_value_{true}; union Value { - std::vector simple; std::vector (*template_func)(Ts...); - Value() {} // trivial constructor - ~Value() {} // trivial destructor - we manage lifetime via discriminator + struct { + const uint8_t *ptr; + size_t len; + } simple_data; } value_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_;