From 1bb4be435c6b9c384a5e9a77f566053aac18f08f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:39:37 -1000 Subject: [PATCH] [esp32_ble_tracker, ble_client] Reduce heap allocations with stack-based string formatting (#12982) --- esphome/components/ble_client/automation.h | 10 ++++-- .../ble_client/output/ble_binary_output.cpp | 32 ++++++++++++------- .../ble_client/sensor/ble_sensor.cpp | 23 +++++++++---- .../text_sensor/ble_text_sensor.cpp | 20 ++++++++---- esphome/components/esp32_ble/ble_uuid.cpp | 8 ++--- esphome/components/esp32_ble/ble_uuid.h | 2 +- .../esp32_ble_client/ble_client_base.cpp | 2 +- .../esp32_ble_client/ble_client_base.h | 5 ++- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 ++--- .../esp32_ble_tracker/esp32_ble_tracker.h | 7 ++++ 10 files changed, 77 insertions(+), 40 deletions(-) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index f9f613ae767..01590d1d538 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -186,8 +186,10 @@ template class BLEClientWriteAction : public Action, publ case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; esph_log_w("ble_write_action", "Characteristic %s was not found in service %s", - this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -199,11 +201,13 @@ template class BLEClientWriteAction : public Action, publ this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf)); break; } this->node_state = espbt::ClientState::ESTABLISHED; - esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), ble_client_->address_str()); break; } diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 1d874a65e4d..1cb83b9d8b3 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -9,12 +9,15 @@ static const char *const TAG = "ble_binary_output"; void BLEBinaryOutput::dump_config() { ESP_LOGCONFIG(TAG, "BLE Binary Output:"); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + this->service_uuid_.to_str(service_buf); + this->char_uuid_.to_str(char_buf); ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + this->parent_->address_str(), service_buf, char_buf); LOG_BINARY_OUTPUT(this); } @@ -24,8 +27,10 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(), - this->service_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf), + this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -37,20 +42,24 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf), this->require_response_ ? "" : "out"); break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), this->parent()->address_str()); this->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_WRITE_CHAR_EVT: { if (param->write.handle == this->char_handle_) { - if (param->write.status != 0) - ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); + if (param->write.status != 0) { + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status); + } } break; } @@ -60,18 +69,19 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i } void BLEBinaryOutput::write_state(bool state) { + char char_buf[esp32_ble::UUID_STR_LEN]; if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", - this->char_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf)); return; } uint8_t state_as_uint = (uint8_t) state; - ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); + ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint); esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_GATT_OK) - ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); + ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err); } } // namespace esphome::ble_client diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 38d90faff08..fe5f11bbc2b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -18,14 +18,17 @@ void BLESensor::loop() { void BLESensor::dump_config() { LOG_SENSOR("", "BLE Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -51,8 +54,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (chr == nullptr) { this->status_set_warning(); this->publish_state(NAN); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -61,9 +66,12 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (descr == nullptr) { this->status_set_warning(); this->publish_state(NAN); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; @@ -109,7 +117,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf)); } break; } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 415981a1ba2..53c9a9d10e7 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -21,14 +21,17 @@ void BLETextSensor::loop() { void BLETextSensor::dump_config() { LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -53,8 +56,10 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (chr == nullptr) { this->status_set_warning(); this->publish_state(EMPTY); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -63,9 +68,12 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (descr == nullptr) { this->status_set_warning(); this->publish_state(EMPTY); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 7bad8d1866f..334780e3b82 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -143,7 +143,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { return this->as_128bit() == uuid.as_128bit(); } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } -void ESPBTUUID::to_str(std::span output) const { +const char *ESPBTUUID::to_str(std::span output) const { char *pos = output.data(); switch (this->uuid_.len) { @@ -155,7 +155,7 @@ void ESPBTUUID::to_str(std::span output) const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F); *pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F); *pos = '\0'; - return; + return output.data(); case ESP_UUID_LEN_32: *pos++ = '0'; @@ -164,7 +164,7 @@ void ESPBTUUID::to_str(std::span output) const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F); } *pos = '\0'; - return; + return output.data(); default: case ESP_UUID_LEN_128: @@ -178,7 +178,7 @@ void ESPBTUUID::to_str(std::span output) const { } } *pos = '\0'; - return; + return output.data(); } } std::string ESPBTUUID::to_string() const { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 2d8c69e44ec..ae593955a44 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -47,7 +47,7 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; std::string to_string() const; - void to_str(std::span output) const; + const char *to_str(std::span output) const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 26eb5dd0925..149fcc79d5b 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -529,7 +529,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ case ESP_GAP_BLE_AUTH_CMPL_EVT: if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) return; - char addr_str[MAC_ADDR_STR_LEN]; + char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str); ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str); if (!param->ble_security.auth_cmpl.success) { diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 7786495915b..92c7444ee19 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -22,7 +22,6 @@ namespace esphome::esp32_ble_client { namespace espbt = esphome::esp32_ble_tracker; static const int UNSET_CONN_ID = 0xFFFF; -static constexpr size_t MAC_ADDR_STR_LEN = 18; // "AA:BB:CC:DD:EE:FF\0" class BLEClientBase : public espbt::ESPBTClient, public Component { public: @@ -111,8 +110,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { esp_gatt_status_t status_{ESP_GATT_OK}; // Group 4: Arrays - char address_str_[MAC_ADDR_STR_LEN]{}; // 18 bytes: "AA:BB:CC:DD:EE:FF\0" - esp_bd_addr_t remote_bda_; // 6 bytes + char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{}; + esp_bd_addr_t remote_bda_; // 6 bytes // Group 5: 2-byte types uint16_t conn_id_{UNSET_CONN_ID}; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 73a5dfb187c..995755ac84b 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -639,9 +639,8 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { } std::string ESPBTDevice::address_str() const { - char mac[18]; - format_mac_addr_upper(this->address_, mac); - return mac; + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return this->address_str_to(buf); } uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } @@ -676,7 +675,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { } this->already_discovered_.push_back(address); - ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str_to(addr_buf), device.get_rssi()); const char *address_type_s; switch (device.get_address_type()) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index b64e36279c6..f538a0eddc2 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -6,6 +6,7 @@ #include "esphome/core/helpers.h" #include +#include #include #include @@ -73,6 +74,12 @@ class ESPBTDevice { std::string address_str() const; + /// Format MAC address into provided buffer, returns pointer to buffer for convenience + const char *address_str_to(std::span buf) const { + format_mac_addr_upper(this->address_, buf.data()); + return buf.data(); + } + uint64_t address_uint64() const; const uint8_t *address() const { return address_; }