From b402e403a04b3daf3662d7bb233c010f41a14984 Mon Sep 17 00:00:00 2001 From: Evaldas Auryla Date: Tue, 6 Jan 2026 03:34:23 +0100 Subject: [PATCH 01/17] [radon_eye_rd200] update Radon Eye RD200 with v2/v3 support (#7962) Co-authored-by: Artem Butusov Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../radon_eye_ble/radon_eye_listener.cpp | 19 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 184 ++++++++++-------- .../radon_eye_rd200/radon_eye_rd200.h | 14 +- 3 files changed, 115 insertions(+), 102 deletions(-) diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index 0c6165c691..2c3ef77add 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -1,7 +1,6 @@ #include "radon_eye_listener.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include #ifdef USE_ESP32 @@ -11,17 +10,11 @@ namespace radon_eye_ble { static const char *const TAG = "radon_eye_ble"; bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (not device.get_name().empty()) { - // Vector containing the prefixes to search for - std::vector prefixes = {"FR:R", "FR:I", "FR:H"}; - - // Check if the device name starts with any of the prefixes - if (std::any_of(prefixes.begin(), prefixes.end(), - [&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) { - // Device found - ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), - device.address_str().c_str()); - } + // Radon Eye devices have names starting with "FR:" + if (device.get_name().starts_with("FR:")) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), + device.address_str_to(addr_buf)); } return false; } diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3ccb7bf082..1bd0b842fe 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -1,5 +1,7 @@ #include "radon_eye_rd200.h" +#include + #ifdef USE_ESP32 namespace esphome { @@ -7,6 +9,22 @@ namespace radon_eye_rd200 { static const char *const TAG = "radon_eye_rd200"; +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-1212-efde-1523-785feabcd123"); +static const uint8_t WRITE_COMMAND_V1 = 0x50; + +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-0000-1000-8000-00805f9b34fb"); +static const uint8_t WRITE_COMMAND_V2 = 0x40; + void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { @@ -23,6 +41,22 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_SEARCH_CMPL_EVT: { + if (this->parent()->get_service(SERVICE_UUID_V1) != nullptr) { + service_uuid_ = SERVICE_UUID_V1; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V1; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V1; + write_command_ = WRITE_COMMAND_V1; + } else if (this->parent()->get_service(SERVICE_UUID_V2) != nullptr) { + service_uuid_ = SERVICE_UUID_V2; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V2; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V2; + write_command_ = WRITE_COMMAND_V2; + } else { + ESP_LOGW(TAG, "No supported device has been found, disconnecting"); + parent()->set_enabled(false); + break; + } + this->read_handle_ = 0; auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); if (chr == nullptr) { @@ -32,90 +66,114 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } this->read_handle_ = chr->handle; - // Write a 0x50 to the write characteristic. auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); if (write_chr == nullptr) { ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_uuid_.to_string().c_str()); + sensors_write_characteristic_uuid_.to_string().c_str()); break; } this->write_handle_ = write_chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - - write_query_message_(); - - request_read_values_(); + esp_err_t status = + esp_ble_gattc_register_for_notify(gattc_if, this->parent()->get_remote_bda(), this->read_handle_); + if (status) { + ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status); + } break; } - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + case ESP_GATTC_WRITE_DESCR_EVT: { + if (param->write.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "write descr failed, error status = %x", param->write.status); break; } - if (param->read.handle == this->read_handle_) { - read_sensors_(param->read.value, param->read.value_len); + ESP_LOGV(TAG, "Write descr success, writing 0x%02X at write_handle=%d", this->write_command_, + this->write_handle_); + esp_err_t status = + esp_ble_gattc_write_char(gattc_if, this->parent()->get_conn_id(), this->write_handle_, sizeof(write_command_), + (uint8_t *) &write_command_, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error writing 0x%02x command, status=%d", write_command_, status); } break; } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.is_notify) { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value, %d bytes", param->notify.value_len); + } else { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value, %d bytes", param->notify.value_len); + } + read_sensors_(param->notify.value, param->notify.value_len); + break; + } + default: break; } } void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { - if (value_len < 20) { - ESP_LOGD(TAG, "Invalid read"); + if (value_len < 1) { + ESP_LOGW(TAG, "Unexpected empty message"); return; } - // Example data - // [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000 - ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X", - value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], - value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], - value[19]); + uint8_t command = value[0]; - if (value[0] != 0x50) { - // This isn't a sensor reading. + if ((command == WRITE_COMMAND_V1 && value_len < 20) || (command == WRITE_COMMAND_V2 && value_len < 68)) { + ESP_LOGW(TAG, "Unexpected command 0x%02X message length %d", command, value_len); return; } + // Example data V1: + // 501085EBB9400000000000000000220025000000 + // Example data V2: + // 4042323230313033525532303338330652443230304e56322e302e3200014a00060a00080000000300010079300000e01108001c00020000003822005c8f423fa4709d3f + ESP_LOGV(TAG, "radon sensors raw bytes"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, value, value_len, ESP_LOG_VERBOSE); + // Convert from pCi/L to Bq/m³ constexpr float convert_to_bwpm3 = 37.0; - RadonValue radon_value; - radon_value.chars[0] = value[2]; - radon_value.chars[1] = value[3]; - radon_value.chars[2] = value[4]; - radon_value.chars[3] = value[5]; - float radon_now = radon_value.number * convert_to_bwpm3; - if (is_valid_radon_value_(radon_now)) { - radon_sensor_->publish_state(radon_now); + float radon_now; // in Bq/m³ + float radon_day; // in Bq/m³ + float radon_month; // in Bq/m³ + if (command == WRITE_COMMAND_V1) { + // Use memcpy to avoid unaligned memory access + float temp; + memcpy(&temp, value + 2, sizeof(float)); + radon_now = temp * convert_to_bwpm3; + memcpy(&temp, value + 6, sizeof(float)); + radon_day = temp * convert_to_bwpm3; + memcpy(&temp, value + 10, sizeof(float)); + radon_month = temp * convert_to_bwpm3; + } else if (command == WRITE_COMMAND_V2) { + // Use memcpy to avoid unaligned memory access + uint16_t temp; + memcpy(&temp, value + 33, sizeof(uint16_t)); + radon_now = temp; + memcpy(&temp, value + 35, sizeof(uint16_t)); + radon_day = temp; + memcpy(&temp, value + 37, sizeof(uint16_t)); + radon_month = temp; + } else { + ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command); + return; } - radon_value.chars[0] = value[6]; - radon_value.chars[1] = value[7]; - radon_value.chars[2] = value[8]; - radon_value.chars[3] = value[9]; - float radon_day = radon_value.number * convert_to_bwpm3; + if (this->radon_sensor_ != nullptr) { + this->radon_sensor_->publish_state(radon_now); + } - radon_value.chars[0] = value[10]; - radon_value.chars[1] = value[11]; - radon_value.chars[2] = value[12]; - radon_value.chars[3] = value[13]; - float radon_month = radon_value.number * convert_to_bwpm3; - - if (is_valid_radon_value_(radon_month)) { - ESP_LOGV(TAG, "Radon Long Term based on month"); - radon_long_term_sensor_->publish_state(radon_month); - } else if (is_valid_radon_value_(radon_day)) { - ESP_LOGV(TAG, "Radon Long Term based on day"); - radon_long_term_sensor_->publish_state(radon_day); + if (this->radon_long_term_sensor_ != nullptr) { + if (radon_month > 0) { + ESP_LOGV(TAG, "Radon Long Term based on month"); + this->radon_long_term_sensor_->publish_state(radon_month); + } else { + ESP_LOGV(TAG, "Radon Long Term based on day"); + this->radon_long_term_sensor_->publish_state(radon_day); + } } ESP_LOGV(TAG, @@ -130,49 +188,23 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { parent()->set_enabled(false); } -bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; } - void RadonEyeRD200::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); - parent()->connect(); } else { ESP_LOGW(TAG, "Connection in progress"); } } } -void RadonEyeRD200::write_query_message_() { - ESP_LOGV(TAG, "writing 0x50 to write service"); - int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->write_handle_, sizeof(request), (uint8_t *) &request, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); - } -} - -void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->read_handle_, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); - } -} - void RadonEyeRD200::dump_config() { LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); } -RadonEyeRD200::RadonEyeRD200() - : PollingComponent(10000), - service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), - sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)), - sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {} +RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000) {} } // namespace radon_eye_rd200 } // namespace esphome diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h index 7b29be7bd8..f874c815f8 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.h +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -14,10 +14,6 @@ namespace esphome { namespace radon_eye_rd200 { -static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"; -static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123"; -static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123"; - class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { public: RadonEyeRD200(); @@ -32,25 +28,17 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } protected: - bool is_valid_radon_value_(float radon); - void read_sensors_(uint8_t *value, uint16_t value_len); - void write_query_message_(); - void request_read_values_(); sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; + uint8_t write_command_; uint16_t read_handle_; uint16_t write_handle_; esp32_ble_tracker::ESPBTUUID service_uuid_; esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; - - union RadonValue { - char chars[4]; - float number; - }; }; } // namespace radon_eye_rd200 From 0290ed5d23172a73c2d930f6a86805ff6eeec3e0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:14:33 -1000 Subject: [PATCH 02/17] [voice_assistant] Reduce heap allocation with stack-based timer formatting (#13001) --- .../voice_assistant/voice_assistant.cpp | 3 ++- .../components/voice_assistant/voice_assistant.h | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index de683113bb..05c356ae4c 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -866,11 +866,12 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse .is_active = msg.is_active, }; this->timers_[timer.id] = timer; + char timer_buf[Timer::TO_STR_BUFFER_SIZE]; ESP_LOGD(TAG, "Timer Event\n" " Type: %" PRId32 "\n" " %s", - msg.event_type, timer.to_string().c_str()); + msg.event_type, timer.to_str(timer_buf)); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_TIMER_STARTED: diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 8d3d3497ec..b1b3df7bbd 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -23,6 +23,7 @@ #endif #include "esphome/components/socket/socket.h" +#include #include #include @@ -71,10 +72,18 @@ struct Timer { uint32_t seconds_left; bool is_active; + /// Buffer size for to_str() - sufficient for typical timer names + static constexpr size_t TO_STR_BUFFER_SIZE = 128; + /// Format to buffer, returns pointer to buffer (may truncate long names) + const char *to_str(std::span buffer) const { + snprintf(buffer.data(), buffer.size(), + "Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", + this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active)); + return buffer.data(); + } std::string to_string() const { - return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", - this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, - YESNO(this->is_active)); + char buffer[TO_STR_BUFFER_SIZE]; + return this->to_str(buffer); } }; From 2d4cd4ce7e34dae4632b20cf13e42ec957820b21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:15:50 -1000 Subject: [PATCH 03/17] [midea] Reduce heap allocations with stack-based string formatting (#13000) --- esphome/components/midea_ir/midea_ir.cpp | 3 ++- esphome/components/remote_base/midea_protocol.cpp | 5 ++++- esphome/components/remote_base/midea_protocol.h | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index c269b2f7d9..eaee1c731c 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -165,7 +165,8 @@ bool MideaIR::on_receive(remote_base::RemoteReceiveData data) { } bool MideaIR::on_midea_(const MideaData &data) { - ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str()); + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_str(buf)); if (data.type() == MideaData::MIDEA_TYPE_CONTROL) { const ControlData status = data; if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY) diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index 8006fe4048..4fa717cf08 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -70,7 +70,10 @@ optional MideaProtocol::decode(RemoteReceiveData src) { return {}; } -void MideaProtocol::dump(const MideaData &data) { ESP_LOGI(TAG, "Received Midea: %s", data.to_string().c_str()); } +void MideaProtocol::dump(const MideaData &data) { + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGI(TAG, "Received Midea: %s", data.to_str(buf)); +} } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 94fb6f3d94..0a5de8e9df 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -30,6 +30,13 @@ class MideaData { void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } bool is_compliment(const MideaData &rhs) const; std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } + /// Buffer size for to_str(): 6 bytes = "AA.BB.CC.DD.EE.FF\0" + static constexpr size_t TO_STR_BUFFER_SIZE = format_hex_pretty_size(6); + /// Format to buffer, returns pointer to buffer + const char *to_str(char *buffer) const { + format_hex_pretty_to(buffer, TO_STR_BUFFER_SIZE, this->data_.data(), this->data_.size(), '.'); + return buffer; + } // compare only 40-bits bool operator==(const MideaData &rhs) const { return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin()); From c3e6a4178ccb267f08b342e0d3a889a95763acc0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:16:14 -1000 Subject: [PATCH 04/17] [thermopro_ble] Reduce heap allocation with stack-based string formatting (#12999) --- esphome/components/thermopro_ble/thermopro_ble.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp index 4b43c9b39e..2c90ee23f8 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.cpp +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -47,7 +47,8 @@ bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); // publish signal strength float signal_strength = float(device.get_rssi()); From 18217fbe101b67261dd4d2256d65a2f4e572aaa7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:16:47 -1000 Subject: [PATCH 05/17] [atc_mithermometer] Reduce heap allocations with stack-based string formatting (#12996) --- .../components/atc_mithermometer/atc_mithermometer.cpp | 10 ++++++---- .../components/atc_mithermometer/atc_mithermometer.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 9d550fcf8c..b4d2929742 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -21,7 +21,9 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -103,13 +105,13 @@ bool ATCMiThermometer::parse_message_(const std::vector &message, Parse return true; } -bool ATCMiThermometer::report_results_(const optional &result, const std::string &address) { +bool ATCMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature); diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index d22e3f069b..e37b5f4350 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace atc_mithermometer From 9b9a341db09996e98ddc2b3224314d77966dba74 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:17:37 -1000 Subject: [PATCH 06/17] [b_parasite] Reduce heap allocation with stack-based string formatting (#12998) --- esphome/components/b_parasite/b_parasite.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 2e548a8072..356f396476 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -22,7 +22,8 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &service_datas = device.get_service_datas(); if (service_datas.size() != 1) { ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size()); From 64da6d46e99e7d25ee132c2a5a30c64d46045b58 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:18:06 -1000 Subject: [PATCH 07/17] [ruuvi_ble] Reduce heap allocation with stack-based string formatting (#12997) --- esphome/components/ruuvi_ble/ruuvi_ble.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index bdd012cf5c..1b126bdef0 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -99,7 +99,8 @@ bool RuuviListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!res.has_value()) return false; - ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str_to(addr_buf)); if (res->humidity.has_value()) { ESP_LOGD(TAG, " Humidity: %.2f%%", *res->humidity); From e6e0be3345ce6eb19c4d85adb43702ab951dce37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:18:58 -1000 Subject: [PATCH 08/17] [bthome_mithermometer] Reduce heap allocations with stack-based string formatting (#12995) --- .../bthome_mithermometer/bthome_ble.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp index b8da51a783..d1c5165896 100644 --- a/esphome/components/bthome_mithermometer/bthome_ble.cpp +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -4,6 +4,7 @@ #include "esphome/core/log.h" #include +#include #ifdef USE_ESP32 @@ -12,15 +13,14 @@ namespace bthome_mithermometer { static const char *const TAG = "bthome_mithermometer"; -static std::string format_mac_address(uint64_t address) { +static const char *format_mac_address(std::span buffer, uint64_t address) { std::array mac{}; for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) { mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF; } - char buffer[MAC_ADDRESS_SIZE * 3]; - format_mac_addr_upper(mac.data(), buffer); - return buffer; + format_mac_addr_upper(mac.data(), buffer.data()); + return buffer.data(); } static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { @@ -127,8 +127,9 @@ static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { } void BTHomeMiThermometer::dump_config() { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "BTHome MiThermometer"); - ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str()); + ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_)); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -172,8 +173,9 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD return false; } + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; if (is_encrypted) { - ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str()); + ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf)); return false; } @@ -193,7 +195,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD } if (source_address != this->address_) { - ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str()); + ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address)); return false; } @@ -286,7 +288,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD } if (reported) { - ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str()); + ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str_to(addr_buf)); } return reported; From 82515135567840eabcb620be6ff6e82482d8a0d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:19:34 -1000 Subject: [PATCH 09/17] [bedjet] Use stack-based UUID formatting in logging (#12993) --- esphome/components/bedjet/bedjet_hub.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index a3054cf48e..fec34c5b2a 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -193,8 +193,9 @@ bool BedJetHub::discover_characteristics_() { result = false; } else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + char uuid_buf[espbt::UUID_STR_LEN]; ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_, - descr->uuid.to_string().c_str()); + descr->uuid.to_str(uuid_buf)); result = false; } else { this->config_descr_status_ = descr->handle; From a6adc29b141e8d5188c726b1ce65622b9b413b91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:20:51 -1000 Subject: [PATCH 10/17] [xiaomi_ble] Reduce heap allocations with stack-based string formatting (#12992) --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 4 ++-- esphome/components/xiaomi_ble/xiaomi_ble.h | 2 +- esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp | 6 ++++-- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 6 ++++-- esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 6 ++++-- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 6 ++++-- .../components/xiaomi_gcls002/xiaomi_gcls002.cpp | 6 ++++-- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 6 ++++-- .../xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp | 3 ++- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 6 ++++-- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 6 ++++-- .../components/xiaomi_lywsd02/xiaomi_lywsd02.cpp | 6 ++++-- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp | 6 ++++-- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 6 ++++-- .../components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 6 ++++-- .../components/xiaomi_mhoc303/xiaomi_mhoc303.cpp | 6 ++++-- .../components/xiaomi_mhoc401/xiaomi_mhoc401.cpp | 6 ++++-- .../components/xiaomi_miscale/xiaomi_miscale.cpp | 14 +++++++++----- esphome/components/xiaomi_miscale/xiaomi_miscale.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 6 ++++-- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 6 ++++-- .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 6 ++++-- esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp | 6 ++++-- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 6 ++++-- 24 files changed, 91 insertions(+), 48 deletions(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 9f25063133..0018d35f1f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -362,13 +362,13 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c return true; } -bool report_xiaomi_results(const optional &result, const std::string &address) { +bool report_xiaomi_results(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_xiaomi_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f°C", *result->temperature); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 77fb04fd78..42609a998b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -71,7 +71,7 @@ bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); -bool report_xiaomi_results(const optional &result, const std::string &address); +bool report_xiaomi_results(const optional &result, const char *address); class XiaomiListener : public esp32_ble_tracker::ESPBTDeviceListener { public: diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index d7f1ec3782..1aa542633a 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -27,7 +27,9 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 9151cbde41..a049854935 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -27,7 +27,9 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 54b50a2eee..da4bab6623 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -27,7 +27,9 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index db63beea89..2048c786d3 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -21,7 +21,9 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +42,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 990346e01e..159b6df80b 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -21,7 +21,9 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 30990b121d..e10754d832 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -22,7 +22,9 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +42,7 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp index 2bc52b8085..028d797ac1 100644 --- a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp @@ -23,7 +23,8 @@ bool XiaomiHHCCJCY10::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); bool success = false; for (auto &service_data : device.get_service_datas()) { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index 3ae29088bb..2d2447db27 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -19,7 +19,9 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -37,7 +39,7 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->moisture.has_value() && this->moisture_ != nullptr) diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index 1efebc2849..8216a92e54 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -21,7 +21,9 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index a6f27c58b9..e140835d03 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index da5229c100..edd9f67f56 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 44fdb3b816..2b4b67c92f 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 749ca83afb..65991ffa0e 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp index e613faec7e..1097b9c1e8 100644 --- a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -20,7 +20,9 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 55b81b301e..e1b808c54e 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -27,7 +27,9 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 29c9de1652..e4f77fb915 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -1,4 +1,5 @@ #include "xiaomi_miscale.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -19,7 +20,9 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -30,7 +33,7 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!parse_message_(service_data.data, *res)) continue; - if (!report_results_(res, device.address_str())) + if (!report_results_(res, addr_str)) continue; if (res->weight.has_value() && this->weight_ != nullptr) @@ -61,9 +64,10 @@ optional XiaomiMiscale::parse_header_(const esp32_ble_tracker::Serv } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) { result.version = 2; } else { + char uuid_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGVV(TAG, "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d", - service_data.uuid.to_string().c_str(), service_data.data.size()); + service_data.uuid.to_str(uuid_buf), service_data.data.size()); return {}; } @@ -145,13 +149,13 @@ bool XiaomiMiscale::parse_message_v2_(const std::vector &message, Parse return true; } -bool XiaomiMiscale::report_results_(const optional &result, const std::string &address) { +bool XiaomiMiscale::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address); if (result->weight.has_value()) { ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 10d308ef6c..3d793e07ac 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -37,7 +37,7 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis bool parse_message_(const std::vector &message, ParseResult &result); bool parse_message_v1_(const std::vector &message, ParseResult &result); bool parse_message_v2_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace xiaomi_miscale diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 16c0b42279..eb4862a7e9 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -22,7 +22,9 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -41,7 +43,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index 1a8e72bd2c..a3f9325946 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -18,7 +18,9 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -36,7 +38,7 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->has_motion.has_value()) { diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index 112bf442e0..d5b89507fe 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -30,7 +30,9 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index b57bf5cd05..b0e02e2372 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -20,7 +20,9 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->is_active.has_value()) { diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index 31e426f0cc..f126e8bdfd 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) From 95573bc1063b5482a9af6643029719aa419ce02c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:21:54 -1000 Subject: [PATCH 11/17] [mopeka] Reduce heap allocations with stack-based string formatting (#12990) --- esphome/components/mopeka_ble/mopeka_ble.cpp | 5 +- .../mopeka_pro_check/mopeka_pro_check.cpp | 3 +- .../mopeka_std_check/mopeka_std_check.cpp | 52 ++++++++++--------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp index 07c8ac5d71..b926beaff2 100644 --- a/esphome/components/mopeka_ble/mopeka_ble.cpp +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -36,6 +36,7 @@ static const uint8_t MANUFACTURER_NRF52_DATA_LENGTH = 10; */ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; // Fetch information about BLE device. const auto &service_uuids = device.get_service_uuids(); if (service_uuids.size() != 1) { @@ -62,7 +63,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[3] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } // Is the device maybe a Mopeka Pro (NRF52) sensor. @@ -78,7 +79,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[2] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 42d61f81a3..9bc9900a5a 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -31,7 +31,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &manu_datas = device.get_manufacturer_datas(); diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 231d09b909..6322b550c9 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -35,15 +35,17 @@ void MopekaStdCheck::dump_config() { * update the sensor state data. */ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - { - // Validate address. - if (device.address_uint64() != this->address_) { - return false; - } - - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + // Validate address. + if (device.address_uint64() != this->address_) { + return false; } + // Stack buffer for MAC address formatting - reused throughout function + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); + { // Validate service uuid const auto &service_uuids = device.get_service_uuids(); @@ -59,7 +61,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto &manu_datas = device.get_manufacturer_datas(); if (manu_datas.size() != 1) { - ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", addr_str, manu_datas.size()); return false; } @@ -68,11 +70,11 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)]; #endif - ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), + ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", addr_str, format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size())); if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { - ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", addr_str, manu_data.data.size()); return false; } @@ -82,21 +84,21 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL && static_cast(hardware_id) != ETRAILER && static_cast(hardware_id) != STANDARD_ALT) { - ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); + ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", addr_str, hardware_id); return false; } - ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate); - ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed); + ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", addr_str, mopeka_data->slow_update_rate); + ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", addr_str, mopeka_data->sync_pressed); for (u_int8_t i = 0; i < 3; i++) { - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1, - mopeka_data->val[i].value_0, mopeka_data->val[i].time_0); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2, - mopeka_data->val[i].value_1, mopeka_data->val[i].time_1); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3, - mopeka_data->val[i].value_2, mopeka_data->val[i].time_2); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4, - mopeka_data->val[i].value_3, mopeka_data->val[i].time_3); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 1, mopeka_data->val[i].value_0, + mopeka_data->val[i].time_0); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 2, mopeka_data->val[i].value_1, + mopeka_data->val[i].time_1); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 3, mopeka_data->val[i].value_2, + mopeka_data->val[i].time_2); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 4, mopeka_data->val[i].value_3, + mopeka_data->val[i].time_3); } // Get battery level first @@ -163,12 +165,12 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } - ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(), - number_of_usable_values, best_value, best_time); + ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", addr_str, number_of_usable_values, best_value, + best_time); if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) { // At least two measurement values must be present. - ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str()); + ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", addr_str); if (this->distance_ != nullptr) { this->distance_->publish_state(0); } @@ -177,7 +179,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } else { float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c); - ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound); + ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", addr_str, lpg_speed_of_sound); uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f; From 7ba4dc0f1a82b77140ea07dac7efbfaa058d4558 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:22:27 -1000 Subject: [PATCH 12/17] [airthings_wave_base, airthings_ble] Use stack-based string formatting in logging (#12989) --- .../airthings_ble/airthings_listener.cpp | 3 ++- .../airthings_wave_base.cpp | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index a36d614df5..58faf923f5 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -20,7 +20,8 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[3] << 24); - ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf)); return true; } } diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp index 16789ff454..e4c7d2a81d 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.cpp +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -1,4 +1,5 @@ #include "airthings_wave_base.h" +#include "esphome/components/esp32_ble/ble_uuid.h" // All information related to reading battery information came from the sensors.airthings_wave // project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave) @@ -93,8 +94,10 @@ void AirthingsWaveBase::update() { bool AirthingsWaveBase::request_read_values_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->sensors_data_characteristic_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->sensors_data_characteristic_uuid_.to_str(char_buf)); return false; } @@ -117,17 +120,20 @@ bool AirthingsWaveBase::request_battery_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_); if (chr == nullptr) { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s", - this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_, CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID); if (descr == nullptr) { - ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_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 CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } From 8518424a88a303ba6fa49cd7a511f47a920673fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:26:49 -1000 Subject: [PATCH 13/17] [esp8266] Add enable_serial/enable_serial1 helpers to exclude unused Serial objects (#12736) --- esphome/components/esp8266/__init__.py | 33 ++++++++++++ esphome/components/esp8266/const.py | 36 +++++++++++++ esphome/components/logger/__init__.py | 12 +++++ esphome/components/logger/logger_esp8266.cpp | 50 +++++++++---------- esphome/components/uart/__init__.py | 22 ++++++++ .../uart/uart_component_esp8266.cpp | 10 +++- esphome/core/defines.h | 4 ++ 7 files changed, 138 insertions(+), 29 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 77ccaf52c1..c7b5d5c130 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -23,12 +23,18 @@ from esphome.helpers import copy_file_if_changed from .boards import BOARDS, ESP8266_LD_SCRIPTS from .const import ( CONF_EARLY_PIN_INIT, + CONF_ENABLE_SERIAL, + CONF_ENABLE_SERIAL1, CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, + KEY_SERIAL1_REQUIRED, + KEY_SERIAL_REQUIRED, KEY_WAVEFORM_REQUIRED, + enable_serial, + enable_serial1, esp8266_ns, ) from .gpio import PinInitialState, add_pin_initial_states_array @@ -171,6 +177,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( *BUILD_FLASH_MODES, lower=True ), + cv.Optional(CONF_ENABLE_SERIAL): cv.boolean, + cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean, } ), set_core_data, @@ -231,6 +239,12 @@ async def to_code(config): if config[CONF_EARLY_PIN_INIT]: cg.add_define("USE_ESP8266_EARLY_PIN_INIT") + # Allow users to force-enable Serial objects for use in lambdas or external libraries + if config.get(CONF_ENABLE_SERIAL): + enable_serial() + if config.get(CONF_ENABLE_SERIAL1): + enable_serial1() + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of @@ -271,6 +285,7 @@ async def to_code(config): CORE.add_job(add_pin_initial_states_array) CORE.add_job(finalize_waveform_config) + CORE.add_job(finalize_serial_config) @coroutine_with_priority(CoroPriority.WORKAROUNDS) @@ -286,6 +301,24 @@ async def finalize_waveform_config() -> None: cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS") +@coroutine_with_priority(CoroPriority.WORKAROUNDS) +async def finalize_serial_config() -> None: + """Exclude unused Arduino Serial objects from the build. + + This runs at WORKAROUNDS priority (-999) to ensure all components + have had a chance to call enable_serial() or enable_serial1() first. + + The Arduino ESP8266 core defines two global Serial objects (32 bytes each). + By adding NO_GLOBAL_SERIAL or NO_GLOBAL_SERIAL1 build flags, we prevent + unused Serial objects from being linked, saving 32 bytes each. + """ + esp8266_data = CORE.data.get(KEY_ESP8266, {}) + if not esp8266_data.get(KEY_SERIAL_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL") + if not esp8266_data.get(KEY_SERIAL1_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL1") + + # Called by writer.py def copy_files() -> None: dir = Path(__file__).parent diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 14425cde68..229ac61f24 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -6,8 +6,12 @@ KEY_BOARD = "board" KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" +CONF_ENABLE_SERIAL = "enable_serial" +CONF_ENABLE_SERIAL1 = "enable_serial1" KEY_FLASH_SIZE = "flash_size" KEY_WAVEFORM_REQUIRED = "waveform_required" +KEY_SERIAL_REQUIRED = "serial_required" +KEY_SERIAL1_REQUIRED = "serial1_required" # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") @@ -29,3 +33,35 @@ def require_waveform() -> None: require_waveform() """ CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True + + +def enable_serial() -> None: + """Mark that Arduino Serial (UART0) is required. + + Call this from components that use the global Serial object. + If no component calls this, Serial is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial + + async def to_code(config): + enable_serial() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL_REQUIRED] = True + + +def enable_serial1() -> None: + """Mark that Arduino Serial1 (UART1) is required. + + Call this from components that use the global Serial1 object. + If no component calls this, Serial1 is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial1 + + async def to_code(config): + enable_serial1() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL1_REQUIRED] = True diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 7132cd8956..0a6035f8d1 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -337,6 +337,18 @@ async def to_code(config): is_at_least_very_verbose = this_severity >= very_verbose_severity has_serial_logging = baud_rate != 0 + # Add defines for which Serial object is needed (allows linker to exclude unused) + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + hw_uart = config.get(CONF_HARDWARE_UART, UART0) + if has_serial_logging and hw_uart in (UART0, UART0_SWAP): + cg.add_define("USE_ESP8266_LOGGER_SERIAL") + enable_serial() + elif has_serial_logging and hw_uart == UART1: + cg.add_define("USE_ESP8266_LOGGER_SERIAL1") + enable_serial1() + if ( (CORE.is_esp8266 or CORE.is_rp2040) and has_serial_logging diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 0fc73b747a..6cee1baca5 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -7,26 +7,21 @@ namespace esphome::logger { static const char *const TAG = "logger"; void Logger::pre_setup() { - if (this->baud_rate_ > 0) { - switch (this->uart_) { - case UART_SELECTION_UART0: - case UART_SELECTION_UART0_SWAP: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - Serial.swap(); - } - Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - } - } else { - uart_set_debug(UART_NO); +#if defined(USE_ESP8266_LOGGER_SERIAL) + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + Serial.swap(); } + Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#else + // No serial logging - disable debug output + uart_set_debug(UART_NO); +#endif global_logger = this; @@ -39,15 +34,16 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { } const LogString *Logger::get_uart_selection_() { - switch (this->uart_) { - case UART_SELECTION_UART0: - return LOG_STR("UART0"); - case UART_SELECTION_UART1: - return LOG_STR("UART1"); - case UART_SELECTION_UART0_SWAP: - default: - return LOG_STR("UART0_SWAP"); +#if defined(USE_ESP8266_LOGGER_SERIAL) + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + return LOG_STR("UART0_SWAP"); } + return LOG_STR("UART0"); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + return LOG_STR("UART1"); +#else + return LOG_STR("NONE"); +#endif } } // namespace esphome::logger diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 9ec95964ec..31e37a06e0 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -378,6 +378,28 @@ async def to_code(config): if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) + # ESP8266: Enable the Arduino Serial objects that might be used based on pin config + # The C++ code selects hardware serial at runtime based on these pin combinations: + # - Serial (UART0): TX=1 or null, RX=3 or null + # - Serial (UART0 swap): TX=15 or null, RX=13 or null + # - Serial1: TX=2 or null, RX=8 or null + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + tx_num = config[CONF_TX_PIN][CONF_NUMBER] if CONF_TX_PIN in config else None + rx_num = config[CONF_RX_PIN][CONF_NUMBER] if CONF_RX_PIN in config else None + + # Check if this config could use Serial (UART0 regular or swap) + if (tx_num is None or tx_num in (1, 15)) and ( + rx_num is None or rx_num in (3, 13) + ): + enable_serial() + cg.add_define("USE_ESP8266_UART_SERIAL") + # Check if this config could use Serial1 + if (tx_num is None or tx_num == 2) and (rx_num is None or rx_num == 8): + enable_serial1() + cg.add_define("USE_ESP8266_UART_SERIAL1") + CORE.add_job(final_step) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c78daa7462..504d494e2e 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -75,6 +75,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); +#ifdef USE_ESP8266_UART_SERIAL if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER @@ -100,11 +101,16 @@ void ESP8266UartComponent::setup() { this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); ESP8266UartComponent::serial0_in_use = true; - } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { + } else +#endif // USE_ESP8266_UART_SERIAL +#ifdef USE_ESP8266_UART_SERIAL1 + if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - } else { + } else +#endif // USE_ESP8266_UART_SERIAL1 + { this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, this->rx_buffer_size_); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index cee46a2df0..69684fd5c9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -248,7 +248,11 @@ #define USE_ADC_SENSOR_VCC #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) #define USE_CAPTIVE_PORTAL +#define USE_ESP8266_LOGGER_SERIAL +#define USE_ESP8266_LOGGER_SERIAL1 #define USE_ESP8266_PREFERENCES_FLASH +#define USE_ESP8266_UART_SERIAL +#define USE_ESP8266_UART_SERIAL1 #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_HTTP_REQUEST_RESPONSE #define USE_I2C From 110c892c3c6f6a663838a94eee43be4921202351 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:35:04 -1000 Subject: [PATCH 14/17] [esp8266] Avoid heap allocation in preferences save/load (#12465) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp8266/preferences.cpp | 131 ++++++++++++--------- 1 file changed, 77 insertions(+), 54 deletions(-) diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 197d244dc4..47987b4a95 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -1,7 +1,6 @@ #ifdef USE_ESP8266 #include -#include extern "C" { #include "spi_flash.h" } @@ -27,6 +26,16 @@ static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; +// RTC memory layout for preferences: +// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127) +// - Normal region: RTC words 32-127 (mapped from preference offset 0-95) +static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot +static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs +static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128 + +// Maximum preference size in words (limited by uint8_t length_words field) +static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; + #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) #ifdef USE_ESP8266_PREFERENCES_FLASH @@ -118,6 +127,10 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { return true; } +// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data) +// This handles virtually all real-world preferences without heap allocation +static constexpr size_t PREF_BUFFER_WORDS = 16; + class ESP8266PreferenceBackend : public ESPPreferenceBackend { public: uint32_t type = 0; @@ -126,36 +139,54 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { bool in_flash = false; bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; - } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization - memcpy(buffer.get(), data, len); - buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (in_flash) { - return save_to_flash(offset, buffer.get(), buffer_size); + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - return save_to_rtc(offset, buffer.get(), buffer_size); + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); } + bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; + + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); - bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size) - : load_from_rtc(offset, buffer.get(), buffer_size); + + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); if (!ret) return false; - uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (buffer[length_words] != crc) { + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) return false; - } - memcpy(data, buffer.get(), len); + memcpy(data, buffer, len); return true; } }; @@ -176,50 +207,42 @@ class ESP8266Preferences : public ESPPreferences { } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - uint32_t length_words = bytes_to_words(length); - if (length_words > 255) { - ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words); + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); return {}; } + + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; + if (in_flash) { - uint32_t start = current_flash_offset; - uint32_t end = start + length_words + 1; - if (end > ESP8266_FLASH_STORAGE_SIZE) + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(start); - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = true; - current_flash_offset = end; - return {pref}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; + } + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - uint32_t start = current_offset; - uint32_t end = start + length_words + 1; - bool in_normal = start < 96; - // Normal: offset 0-95 maps to RTC offset 32 - 127, - // Eboot: offset 96-127 maps to RTC offset 0 - 31 words - if (in_normal && end > 96) { - // start is in normal but end is not -> switch to Eboot - current_offset = start = 96; - end = start + length_words + 1; - in_normal = false; - } - - if (end > 128) { - // Doesn't fit in data, return uninitialized preference obj. - return {}; - } - - uint32_t rtc_offset = in_normal ? start + 32 : start - 96; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(rtc_offset); + pref->offset = offset; pref->type = type; pref->length_words = static_cast(length_words); - pref->in_flash = false; - current_offset += length_words + 1; + pref->in_flash = in_flash; return pref; } From 84dd17187ddfdcf45325ceb936d6b7be09e39919 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:35:22 -1000 Subject: [PATCH 15/17] [pvvx_mithermometer] Reduce heap allocations with stack-based string formatting (#12994) --- .../pvvx_mithermometer/display/pvvx_display.cpp | 7 +++++-- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 10 ++++++---- .../components/pvvx_mithermometer/pvvx_mithermometer.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 8436633619..4d4a5466bb 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -1,4 +1,5 @@ #include "pvvx_display.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,14 +9,16 @@ namespace pvvx_mithermometer { static const char *const TAG = "display.pvvx_mithermometer"; void PVVXDisplay::dump_config() { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, "PVVX MiThermometer display:\n" " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID : %s\n" " Auto clear : %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), YESNO(this->auto_clear_enabled_)); + this->parent_->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), YESNO(this->auto_clear_enabled_)); #ifdef USE_TIME ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); #endif diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 6975109952..5712447909 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -21,7 +21,9 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -111,13 +113,13 @@ bool PVVXMiThermometer::parse_message_(const std::vector &message, Pars return true; } -bool PVVXMiThermometer::report_results_(const optional &result, const std::string &address) { +bool PVVXMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.2f °C", *result->temperature); diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 9614a3c586..c15e1e7e22 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -41,7 +41,7 @@ class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevic optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace pvvx_mithermometer From 28cf3b7a9b94fe5720ad720abe3d3055be6ab539 Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 5 Jan 2026 19:35:32 -0800 Subject: [PATCH 16/17] [rd03d] Add Ai-Thinker RD-03D mmWave radar component (#12764) Co-authored-by: jas Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/rd03d/__init__.py | 50 ++++ esphome/components/rd03d/binary_sensor.py | 39 +++ esphome/components/rd03d/rd03d.cpp | 243 +++++++++++++++++++ esphome/components/rd03d/rd03d.h | 93 +++++++ esphome/components/rd03d/sensor.py | 105 ++++++++ tests/components/rd03d/common.yaml | 59 +++++ tests/components/rd03d/test.esp32-idf.yaml | 4 + tests/components/rd03d/test.esp8266-ard.yaml | 4 + tests/components/rd03d/test.rp2040-ard.yaml | 4 + 10 files changed, 602 insertions(+) create mode 100644 esphome/components/rd03d/__init__.py create mode 100644 esphome/components/rd03d/binary_sensor.py create mode 100644 esphome/components/rd03d/rd03d.cpp create mode 100644 esphome/components/rd03d/rd03d.h create mode 100644 esphome/components/rd03d/sensor.py create mode 100644 tests/components/rd03d/common.yaml create mode 100644 tests/components/rd03d/test.esp32-idf.yaml create mode 100644 tests/components/rd03d/test.esp8266-ard.yaml create mode 100644 tests/components/rd03d/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index a2267621e7..bdcc86ef0c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -395,6 +395,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet +esphome/components/rd03d/* @jasstrong esphome/components/resampler/speaker/* @kahrendt esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz diff --git a/esphome/components/rd03d/__init__.py b/esphome/components/rd03d/__init__.py new file mode 100644 index 0000000000..52e9a2c09a --- /dev/null +++ b/esphome/components/rd03d/__init__.py @@ -0,0 +1,50 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_THROTTLE + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +CONF_RD03D_ID = "rd03d_id" +CONF_TRACKING_MODE = "tracking_mode" + +rd03d_ns = cg.esphome_ns.namespace("rd03d") +RD03DComponent = rd03d_ns.class_("RD03DComponent", cg.Component, uart.UARTDevice) +TrackingMode = rd03d_ns.enum("TrackingMode", is_class=True) + +TRACKING_MODES = { + "single": TrackingMode.SINGLE_TARGET, + "multi": TrackingMode.MULTI_TARGET, +} + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RD03DComponent), + cv.Optional(CONF_TRACKING_MODE): cv.enum(TRACKING_MODES, lower=True), + cv.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "rd03d", + require_tx=False, + require_rx=True, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if CONF_TRACKING_MODE in config: + cg.add(var.set_tracking_mode(config[CONF_TRACKING_MODE])) + + if CONF_THROTTLE in config: + cg.add(var.set_throttle(config[CONF_THROTTLE])) diff --git a/esphome/components/rd03d/binary_sensor.py b/esphome/components/rd03d/binary_sensor.py new file mode 100644 index 0000000000..afb7527aa1 --- /dev/null +++ b/esphome/components/rd03d/binary_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_TARGET, DEVICE_CLASS_OCCUPANCY + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +MAX_TARGETS = 3 + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ), + } +).extend( + { + cv.Optional(f"target_{i + 1}"): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ) + for i in range(MAX_TARGETS) + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_config := config.get(CONF_TARGET): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(i, sens)) diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp new file mode 100644 index 0000000000..44e479c153 --- /dev/null +++ b/esphome/components/rd03d/rd03d.cpp @@ -0,0 +1,243 @@ +#include "rd03d.h" +#include "esphome/core/log.h" +#include + +namespace esphome::rd03d { + +static const char *const TAG = "rd03d"; + +// Delay before sending configuration commands to allow radar to initialize +static constexpr uint32_t SETUP_TIMEOUT_MS = 100; + +// Data frame format (radar -> host) +static constexpr uint8_t FRAME_HEADER[] = {0xAA, 0xFF, 0x03, 0x00}; +static constexpr uint8_t FRAME_FOOTER[] = {0x55, 0xCC}; + +// Command frame format (host -> radar) +static constexpr uint8_t CMD_FRAME_HEADER[] = {0xFD, 0xFC, 0xFB, 0xFA}; +static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01}; + +// RD-03D tracking mode commands +static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080; +static constexpr uint16_t CMD_MULTI_TARGET = 0x0090; + +// Decode coordinate/speed value from RD-03D format +// Per datasheet: MSB=1 means positive, MSB=0 means negative +static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) { + int16_t value = ((high_byte & 0x7F) << 8) | low_byte; + if ((high_byte & 0x80) == 0) { + value = -value; + } + return value; +} + +void RD03DComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up RD-03D..."); + this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); }); +} + +void RD03DComponent::dump_config() { + ESP_LOGCONFIG(TAG, "RD-03D:"); + if (this->tracking_mode_.has_value()) { + ESP_LOGCONFIG(TAG, " Tracking Mode: %s", + *this->tracking_mode_ == TrackingMode::SINGLE_TARGET ? "single" : "multi"); + } + if (this->throttle_ > 0) { + ESP_LOGCONFIG(TAG, " Throttle: %ums", this->throttle_); + } +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Target Count", this->target_count_sensor_); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); +#endif + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + ESP_LOGCONFIG(TAG, " Target %d:", i + 1); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "X", this->targets_[i].x); + LOG_SENSOR(" ", "Y", this->targets_[i].y); + LOG_SENSOR(" ", "Speed", this->targets_[i].speed); + LOG_SENSOR(" ", "Distance", this->targets_[i].distance); + LOG_SENSOR(" ", "Resolution", this->targets_[i].resolution); + LOG_SENSOR(" ", "Angle", this->targets_[i].angle); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Presence", this->target_presence_[i]); +#endif + } +} + +void RD03DComponent::loop() { + while (this->available()) { + uint8_t byte = this->read(); + ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_); + + // Check if we're looking for frame header + if (this->buffer_pos_ < FRAME_HEADER_SIZE) { + if (byte == FRAME_HEADER[this->buffer_pos_]) { + this->buffer_[this->buffer_pos_++] = byte; + } else if (byte == FRAME_HEADER[0]) { + // Start over if we see a potential new header + this->buffer_[0] = byte; + this->buffer_pos_ = 1; + } else { + this->buffer_pos_ = 0; + } + continue; + } + + // Accumulate data bytes + this->buffer_[this->buffer_pos_++] = byte; + + // Check if we have a complete frame + if (this->buffer_pos_ == FRAME_SIZE) { + // Validate footer + if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) { + this->process_frame_(); + } else { + ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2], + this->buffer_[FRAME_SIZE - 1]); + } + this->buffer_pos_ = 0; + } + } +} + +void RD03DComponent::process_frame_() { + // Apply throttle if configured + if (this->throttle_ > 0) { + uint32_t now = millis(); + if (now - this->last_publish_time_ < this->throttle_) { + return; + } + this->last_publish_time_ = now; + } + + uint8_t target_count = 0; + + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + // Calculate offset for this target's data + // Header is 4 bytes, each target is 8 bytes + uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE); + + // Extract raw bytes for this target + uint8_t x_low = this->buffer_[offset + 0]; + uint8_t x_high = this->buffer_[offset + 1]; + uint8_t y_low = this->buffer_[offset + 2]; + uint8_t y_high = this->buffer_[offset + 3]; + uint8_t speed_low = this->buffer_[offset + 4]; + uint8_t speed_high = this->buffer_[offset + 5]; + uint8_t res_low = this->buffer_[offset + 6]; + uint8_t res_high = this->buffer_[offset + 7]; + + // Decode values per RD-03D format + int16_t x = decode_value(x_low, x_high); + int16_t y = decode_value(y_low, y_high); + int16_t speed = decode_value(speed_low, speed_high); + uint16_t resolution = (res_high << 8) | res_low; + + // Check if target is present (non-zero coordinates) + bool target_present = (x != 0 || y != 0); + if (target_present) { + target_count++; + } + +#ifdef USE_SENSOR + this->publish_target_(i, x, y, speed, resolution); +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_presence_[i] != nullptr) { + this->target_presence_[i]->publish_state(target_present); + } +#endif + } + +#ifdef USE_SENSOR + if (this->target_count_sensor_ != nullptr) { + this->target_count_sensor_->publish_state(target_count); + } +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_count > 0); + } +#endif +} + +#ifdef USE_SENSOR +void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) { + TargetSensor &target = this->targets_[target_num]; + + // Publish X coordinate (mm) + if (target.x != nullptr) { + target.x->publish_state(x); + } + + // Publish Y coordinate (mm) + if (target.y != nullptr) { + target.y->publish_state(y); + } + + // Publish speed (convert from cm/s to mm/s) + if (target.speed != nullptr) { + target.speed->publish_state(static_cast(speed) * 10.0f); + } + + // Publish resolution (mm) + if (target.resolution != nullptr) { + target.resolution->publish_state(resolution); + } + + // Calculate and publish distance (mm) + if (target.distance != nullptr) { + float distance = std::hypot(static_cast(x), static_cast(y)); + target.distance->publish_state(distance); + } + + // Calculate and publish angle (degrees) + // Angle is measured from the Y axis (radar forward direction) + if (target.angle != nullptr) { + if (x == 0 && y == 0) { + target.angle->publish_state(0); + } else { + float angle = std::atan2(static_cast(x), static_cast(y)) * 180.0f / M_PI; + target.angle->publish_state(angle); + } + } +} +#endif + +void RD03DComponent::send_command_(uint16_t command, const uint8_t *data, uint8_t data_len) { + // Send header + this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); + + // Send length (command word + data) + uint16_t len = 2 + data_len; + this->write_byte(len & 0xFF); + this->write_byte((len >> 8) & 0xFF); + + // Send command word (little-endian) + this->write_byte(command & 0xFF); + this->write_byte((command >> 8) & 0xFF); + + // Send data if any + if (data != nullptr && data_len > 0) { + this->write_array(data, data_len); + } + + // Send footer + this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); + + ESP_LOGD(TAG, "Sent command 0x%04X with %d bytes of data", command, data_len); +} + +void RD03DComponent::apply_config_() { + if (this->tracking_mode_.has_value()) { + uint16_t mode_cmd = (*this->tracking_mode_ == TrackingMode::SINGLE_TARGET) ? CMD_SINGLE_TARGET : CMD_MULTI_TARGET; + this->send_command_(mode_cmd); + } +} + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/rd03d.h b/esphome/components/rd03d/rd03d.h new file mode 100644 index 0000000000..7413fe38f2 --- /dev/null +++ b/esphome/components/rd03d/rd03d.h @@ -0,0 +1,93 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#include + +namespace esphome::rd03d { + +static constexpr uint8_t MAX_TARGETS = 3; +static constexpr uint8_t FRAME_HEADER_SIZE = 4; +static constexpr uint8_t FRAME_FOOTER_SIZE = 2; +static constexpr uint8_t TARGET_DATA_SIZE = 8; +static constexpr uint8_t FRAME_SIZE = + FRAME_HEADER_SIZE + (MAX_TARGETS * TARGET_DATA_SIZE) + FRAME_FOOTER_SIZE; // 30 bytes + +enum class TrackingMode : uint8_t { + SINGLE_TARGET = 0, + MULTI_TARGET = 1, +}; + +#ifdef USE_SENSOR +struct TargetSensor { + sensor::Sensor *x{nullptr}; + sensor::Sensor *y{nullptr}; + sensor::Sensor *speed{nullptr}; + sensor::Sensor *distance{nullptr}; + sensor::Sensor *resolution{nullptr}; + sensor::Sensor *angle{nullptr}; +}; +#endif + +class RD03DComponent : public Component, public uart::UARTDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + +#ifdef USE_SENSOR + void set_target_count_sensor(sensor::Sensor *sensor) { this->target_count_sensor_ = sensor; } + void set_x_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].x = sensor; } + void set_y_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].y = sensor; } + void set_speed_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].speed = sensor; } + void set_distance_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].distance = sensor; } + void set_resolution_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].resolution = sensor; } + void set_angle_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].angle = sensor; } +#endif +#ifdef USE_BINARY_SENSOR + void set_target_binary_sensor(binary_sensor::BinarySensor *sensor) { this->target_binary_sensor_ = sensor; } + void set_target_binary_sensor(uint8_t target, binary_sensor::BinarySensor *sensor) { + this->target_presence_[target] = sensor; + } +#endif + + // Configuration setters (called from code generation) + void set_tracking_mode(TrackingMode mode) { this->tracking_mode_ = mode; } + void set_throttle(uint32_t throttle) { this->throttle_ = throttle; } + + protected: + void apply_config_(); + void send_command_(uint16_t command, const uint8_t *data = nullptr, uint8_t data_len = 0); + void process_frame_(); +#ifdef USE_SENSOR + void publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution); +#endif + +#ifdef USE_SENSOR + std::array targets_{}; + sensor::Sensor *target_count_sensor_{nullptr}; +#endif +#ifdef USE_BINARY_SENSOR + std::array target_presence_{}; + binary_sensor::BinarySensor *target_binary_sensor_{nullptr}; +#endif + + // Configuration (only sent if explicitly set) + optional tracking_mode_{}; + uint32_t throttle_{0}; + uint32_t last_publish_time_{0}; + + std::array buffer_{}; + uint8_t buffer_pos_{0}; +}; + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/sensor.py b/esphome/components/rd03d/sensor.py new file mode 100644 index 0000000000..4b4fcfd4e4 --- /dev/null +++ b/esphome/components/rd03d/sensor.py @@ -0,0 +1,105 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ANGLE, + CONF_DISTANCE, + CONF_RESOLUTION, + CONF_SPEED, + CONF_X, + CONF_Y, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_SPEED, + STATE_CLASS_MEASUREMENT, + UNIT_DEGREES, + UNIT_MILLIMETER, +) + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +CONF_TARGET_COUNT = "target_count" + +MAX_TARGETS = 3 + +UNIT_MILLIMETER_PER_SECOND = "mm/s" + +TARGET_SCHEMA = cv.Schema( + { + cv.Optional(CONF_X): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_Y): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_SPEED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SPEED, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ANGLE): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend({cv.Optional(f"target_{i + 1}"): TARGET_SCHEMA for i in range(MAX_TARGETS)}) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_count_config := config.get(CONF_TARGET_COUNT): + sens = await sensor.new_sensor(target_count_config) + cg.add(hub.set_target_count_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + if x_config := target_config.get(CONF_X): + sens = await sensor.new_sensor(x_config) + cg.add(hub.set_x_sensor(i, sens)) + if y_config := target_config.get(CONF_Y): + sens = await sensor.new_sensor(y_config) + cg.add(hub.set_y_sensor(i, sens)) + if speed_config := target_config.get(CONF_SPEED): + sens = await sensor.new_sensor(speed_config) + cg.add(hub.set_speed_sensor(i, sens)) + if distance_config := target_config.get(CONF_DISTANCE): + sens = await sensor.new_sensor(distance_config) + cg.add(hub.set_distance_sensor(i, sens)) + if resolution_config := target_config.get(CONF_RESOLUTION): + sens = await sensor.new_sensor(resolution_config) + cg.add(hub.set_resolution_sensor(i, sens)) + if angle_config := target_config.get(CONF_ANGLE): + sens = await sensor.new_sensor(angle_config) + cg.add(hub.set_angle_sensor(i, sens)) diff --git a/tests/components/rd03d/common.yaml b/tests/components/rd03d/common.yaml new file mode 100644 index 0000000000..b13d899ab3 --- /dev/null +++ b/tests/components/rd03d/common.yaml @@ -0,0 +1,59 @@ +rd03d: + id: rd03d_radar + +sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target_count: + name: Target Count + target_1: + x: + name: Target-1 X + y: + name: Target-1 Y + speed: + name: Target-1 Speed + angle: + name: Target-1 Angle + distance: + name: Target-1 Distance + resolution: + name: Target-1 Resolution + target_2: + x: + name: Target-2 X + y: + name: Target-2 Y + speed: + name: Target-2 Speed + angle: + name: Target-2 Angle + distance: + name: Target-2 Distance + resolution: + name: Target-2 Resolution + target_3: + x: + name: Target-3 X + y: + name: Target-3 Y + speed: + name: Target-3 Speed + angle: + name: Target-3 Angle + distance: + name: Target-3 Distance + resolution: + name: Target-3 Resolution + +binary_sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target: + name: Presence + target_1: + name: Target-1 Presence + target_2: + name: Target-2 Presence + target_3: + name: Target-3 Presence diff --git a/tests/components/rd03d/test.esp32-idf.yaml b/tests/components/rd03d/test.esp32-idf.yaml new file mode 100644 index 0000000000..2d29656c94 --- /dev/null +++ b/tests/components/rd03d/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.esp8266-ard.yaml b/tests/components/rd03d/test.esp8266-ard.yaml new file mode 100644 index 0000000000..5a05efa259 --- /dev/null +++ b/tests/components/rd03d/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.rp2040-ard.yaml b/tests/components/rd03d/test.rp2040-ard.yaml new file mode 100644 index 0000000000..f1df2daf83 --- /dev/null +++ b/tests/components/rd03d/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml + +<<: !include common.yaml From 22cb0da903aea0c372144b1eb1563d66f79ab1f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:45:51 -1000 Subject: [PATCH 17/17] [radon_eye_rd200, radon_eye_ble] Use stack-based string formatting in logging (#12991) --- .../components/radon_eye_rd200/radon_eye_rd200.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 1bd0b842fe..f2d32d51de 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -1,4 +1,5 @@ #include "radon_eye_rd200.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include @@ -60,16 +61,20 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->read_handle_ = 0; auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_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 read characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_read_characteristic_uuid_.to_str(char_buf)); break; } this->read_handle_ = chr->handle; auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); if (write_chr == nullptr) { - ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_write_characteristic_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 write characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_write_characteristic_uuid_.to_str(char_buf)); break; } this->write_handle_ = write_chr->handle;