From 097aac2183641c087f161b2b34648d26fbc497d2 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 11 Jul 2025 22:33:36 -0500 Subject: [PATCH 01/64] [ld2420] Memory optimization, code clean-up (#9426) --- .../binary_sensor/ld2420_binary_sensor.cpp | 4 +- .../ld2420/button/reconfig_buttons.cpp | 2 +- esphome/components/ld2420/ld2420.cpp | 123 ++++++++++-------- esphome/components/ld2420/ld2420.h | 20 ++- .../ld2420/number/gate_config_number.cpp | 2 +- .../ld2420/select/operating_mode_select.cpp | 2 +- .../ld2420/sensor/ld2420_sensor.cpp | 4 +- .../ld2420/text_sensor/text_sensor.cpp | 4 +- 8 files changed, 84 insertions(+), 77 deletions(-) diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp index c6ea0a348b..d8632e9c19 100644 --- a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.binary_sensor"; +static const char *const TAG = "ld2420.binary_sensor"; void LD2420BinarySensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:"); + ESP_LOGCONFIG(TAG, "Binary Sensor:"); LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_); } diff --git a/esphome/components/ld2420/button/reconfig_buttons.cpp b/esphome/components/ld2420/button/reconfig_buttons.cpp index 3537c1d64a..fb8ec2b5a6 100644 --- a/esphome/components/ld2420/button/reconfig_buttons.cpp +++ b/esphome/components/ld2420/button/reconfig_buttons.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "LD2420.button"; +static const char *const TAG = "ld2420.button"; namespace esphome { namespace ld2420 { diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 8a7d7de23b..0baff368c8 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple"; // Memory-efficient lookup tables struct StringToUint8 { const char *str; - uint8_t value; + const uint8_t value; }; static constexpr StringToUint8 OP_MODE_BY_STR[] = { @@ -155,8 +155,9 @@ static constexpr const char *ERR_MESSAGE[] = { // Helper function for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { for (const auto &entry : arr) { - if (str == entry.str) + if (str == entry.str) { return entry.value; + } } return 0xFF; // Not found } @@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() { void LD2420Component::loop() { // If there is a active send command do not process it here, the send command call will handle it. - if (!this->get_cmd_active_()) { - if (!this->available()) - return; - static uint8_t buffer[2048]; - static uint8_t rx_data; - while (this->available()) { - rx_data = this->read(); - this->readline_(rx_data, buffer, sizeof(buffer)); - } + while (!this->cmd_active_ && this->available()) { + this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH); } } @@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() { // Store average and peak values this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; - if (this->gate_peak[gate] < peak) + if (this->gate_peak[gate] < peak) { this->gate_peak[gate] = peak; + } uint32_t calculated_value = (static_cast(this->gate_peak[gate]) + (move_factor * static_cast(this->gate_peak[gate]))); @@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) { } } else { // Set the current data back so we don't have new data that can be applied in error. - if (this->get_calibration_()) + if (this->get_calibration_()) { memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); + } this->set_calibration_(false); } } else { @@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) { } void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { - static int pos = 0; - - if (rx_data >= 0) { - if (pos < len - 1) { - buffer[pos++] = rx_data; - buffer[pos] = 0; - } else { - pos = 0; - } - if (pos >= 4) { - if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { - this->set_cmd_active_(false); // Set command state to inactive after responce. - this->handle_ack_data_(buffer, pos); - pos = 0; - } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && - (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { - this->handle_simple_mode_(buffer, pos); - pos = 0; - } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && - (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { - this->handle_energy_mode_(buffer, pos); - pos = 0; - } - } + if (rx_data < 0) { + return; // No data available + } + if (this->buffer_pos_ < len - 1) { + buffer[this->buffer_pos_++] = rx_data; + buffer[this->buffer_pos_] = 0; + } else { + // We should never get here, but just in case... + ESP_LOGW(TAG, "Max command length exceeded; ignoring"); + this->buffer_pos_ = 0; + } + if (this->buffer_pos_ < 4) { + return; // Not enough data to process yet + } + if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { + this->cmd_active_ = false; // Set command state to inactive after response + this->handle_ack_data_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; + } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) && + (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { + this->handle_simple_mode_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; + } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && + (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { + this->handle_energy_mode_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; } } @@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { // Resonable refresh rate for home assistant database size health const int32_t current_millis = App.get_loop_component_start_time(); - if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) + if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) { return; + } this->last_periodic_millis = current_millis; for (auto &listener : this->listeners_) { listener->on_distance(this->get_distance_()); @@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { } } outbuf[index] = '\0'; - if (index > 1) + if (index > 1) { this->set_distance_(strtol(outbuf, &endptr, 10)); + } if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { // Resonable refresh rate for home assistant database size health const int32_t current_millis = App.get_loop_component_start_time(); - if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) + if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) { return; + } this->last_normal_periodic_millis = current_millis; for (auto &listener : this->listeners_) listener->on_distance(this->get_distance_()); @@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { int LD2420Component::send_cmd_from_array(CmdFrameT frame) { uint32_t start_millis = millis(); uint8_t error = 0; - uint8_t ack_buffer[64]; - uint8_t cmd_buffer[64]; + uint8_t ack_buffer[MAX_LINE_LENGTH]; + uint8_t cmd_buffer[MAX_LINE_LENGTH]; this->cmd_reply_.ack = false; - if (frame.command != CMD_RESTART) - this->set_cmd_active_(true); // Restart does not reply, thus no ack state required. + if (frame.command != CMD_RESTART) { + this->cmd_active_ = true; + } // Restart does not reply, thus no ack state required uint8_t retry = 3; while (retry) { frame.length = 0; @@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); frame.length += sizeof(frame.footer); - for (uint16_t index = 0; index < frame.length; index++) { - this->write_byte(cmd_buffer[index]); - } + this->write_array(cmd_buffer, frame.length); error = 0; if (frame.command == CMD_RESTART) { @@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { while (!this->cmd_reply_.ack) { while (this->available()) { - this->readline_(read(), ack_buffer, sizeof(ack_buffer)); + this->readline_(this->read(), ack_buffer, sizeof(ack_buffer)); } delay_microseconds_safe(1450); // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. @@ -641,10 +641,12 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { break; } } - if (this->cmd_reply_.ack) + if (this->cmd_reply_.ack) { retry = 0; - if (this->cmd_reply_.error > 0) + } + if (this->cmd_reply_.error > 0) { this->handle_cmd_error(error); + } } return error; } @@ -764,8 +766,9 @@ void LD2420Component::set_system_mode(uint16_t mode) { cmd_frame.data_length += sizeof(unknown_parm); cmd_frame.footer = CMD_FRAME_FOOTER; ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); - if (this->send_cmd_from_array(cmd_frame) == 0) + if (this->send_cmd_from_array(cmd_frame) == 0) { this->set_mode_(mode); + } } void LD2420Component::get_firmware_version_() { @@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) { #ifdef USE_NUMBER void LD2420Component::init_gate_config_numbers() { - if (this->gate_timeout_number_ != nullptr) + if (this->gate_timeout_number_ != nullptr) { this->gate_timeout_number_->publish_state(static_cast(this->current_config.timeout)); - if (this->gate_select_number_ != nullptr) + } + if (this->gate_select_number_ != nullptr) { this->gate_select_number_->publish_state(0); - if (this->min_gate_distance_number_ != nullptr) + } + if (this->min_gate_distance_number_ != nullptr) { this->min_gate_distance_number_->publish_state(static_cast(this->current_config.min_gate)); - if (this->max_gate_distance_number_ != nullptr) + } + if (this->max_gate_distance_number_ != nullptr) { this->max_gate_distance_number_->publish_state(static_cast(this->current_config.max_gate)); - if (this->gate_move_sensitivity_factor_number_ != nullptr) + } + if (this->gate_move_sensitivity_factor_number_ != nullptr) { this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); - if (this->gate_still_sensitivity_factor_number_ != nullptr) + } + if (this->gate_still_sensitivity_factor_number_ != nullptr) { this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); + } for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { if (this->gate_still_threshold_numbers_[gate] != nullptr) { this->gate_still_threshold_numbers_[gate]->publish_state( diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index d574a25c89..812c408cfd 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -20,8 +20,9 @@ namespace esphome { namespace ld2420 { -static const uint8_t TOTAL_GATES = 16; static const uint8_t CALIBRATE_SAMPLES = 64; +static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer +static const uint8_t TOTAL_GATES = 16; enum OpMode : uint8_t { OP_NORMAL_MODE = 1, @@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice { float gate_move_sensitivity_factor{0.5}; float gate_still_sensitivity_factor{0.5}; - int32_t last_periodic_millis = millis(); - int32_t report_periodic_millis = millis(); - int32_t monitor_periodic_millis = millis(); - int32_t last_normal_periodic_millis = millis(); + int32_t last_periodic_millis{0}; + int32_t report_periodic_millis{0}; + int32_t monitor_periodic_millis{0}; + int32_t last_normal_periodic_millis{0}; uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES]; uint16_t gate_avg[TOTAL_GATES]; uint16_t gate_peak[TOTAL_GATES]; @@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice { void set_presence_(bool presence) { this->presence_ = presence; }; uint16_t get_distance_() { return this->distance_; }; void set_distance_(uint16_t distance) { this->distance_ = distance; }; - bool get_cmd_active_() { return this->cmd_active_; }; - void set_cmd_active_(bool active) { this->cmd_active_ = active; }; void handle_simple_mode_(const uint8_t *inbuf, int len); void handle_energy_mode_(uint8_t *buffer, int len); void handle_ack_data_(uint8_t *buffer, int len); @@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice { std::vector gate_move_threshold_numbers_ = std::vector(16); #endif - uint32_t max_distance_gate_; - uint32_t min_distance_gate_; + uint16_t distance_{0}; uint16_t system_mode_; uint16_t gate_energy_[TOTAL_GATES]; - uint16_t distance_{0}; - uint8_t config_checksum_{0}; + uint8_t buffer_pos_{0}; // where to resume processing/populating buffer + uint8_t buffer_data_[MAX_LINE_LENGTH]; char firmware_ver_[8]{"v0.0.0"}; bool cmd_active_{false}; bool presence_{false}; diff --git a/esphome/components/ld2420/number/gate_config_number.cpp b/esphome/components/ld2420/number/gate_config_number.cpp index e5eaafb46d..a373753770 100644 --- a/esphome/components/ld2420/number/gate_config_number.cpp +++ b/esphome/components/ld2420/number/gate_config_number.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "LD2420.number"; +static const char *const TAG = "ld2420.number"; namespace esphome { namespace ld2420 { diff --git a/esphome/components/ld2420/select/operating_mode_select.cpp b/esphome/components/ld2420/select/operating_mode_select.cpp index 1c59f443a5..2d576e7cc6 100644 --- a/esphome/components/ld2420/select/operating_mode_select.cpp +++ b/esphome/components/ld2420/select/operating_mode_select.cpp @@ -5,7 +5,7 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.select"; +static const char *const TAG = "ld2420.select"; void LD2420Select::control(const std::string &value) { this->publish_state(value); diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.cpp b/esphome/components/ld2420/sensor/ld2420_sensor.cpp index 97f0c594b7..723604f396 100644 --- a/esphome/components/ld2420/sensor/ld2420_sensor.cpp +++ b/esphome/components/ld2420/sensor/ld2420_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.sensor"; +static const char *const TAG = "ld2420.sensor"; void LD2420Sensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 Sensor:"); + ESP_LOGCONFIG(TAG, "Sensor:"); LOG_SENSOR(" ", "Distance", this->distance_sensor_); } diff --git a/esphome/components/ld2420/text_sensor/text_sensor.cpp b/esphome/components/ld2420/text_sensor/text_sensor.cpp index 1dcdcf7d60..73af3b3660 100644 --- a/esphome/components/ld2420/text_sensor/text_sensor.cpp +++ b/esphome/components/ld2420/text_sensor/text_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.text_sensor"; +static const char *const TAG = "ld2420.text_sensor"; void LD2420TextSensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 TextSensor:"); + ESP_LOGCONFIG(TAG, "Text Sensor:"); LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); } From 9beb4e2cd418a64098a369536cefa086deb2f8c3 Mon Sep 17 00:00:00 2001 From: Peter Zich Date: Sat, 12 Jul 2025 18:22:07 -0700 Subject: [PATCH 02/64] (Maybe?) fix I2S speaker internal DAC mode (#9435) --- esphome/components/i2s_audio/speaker/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index bb9f24bf0b..cb7b876a40 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -180,7 +180,7 @@ async def to_code(config): await speaker.register_speaker(var, config) if config[CONF_DAC_TYPE] == "internal": - cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL])) + cg.add(var.set_internal_dac_mode(config[CONF_MODE])) else: cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) if use_legacy(): From 8f42bc6aaca742c2cb1dd729174ef84cbd0547f5 Mon Sep 17 00:00:00 2001 From: Peter Zich Date: Sat, 12 Jul 2025 22:43:32 -0700 Subject: [PATCH 03/64] [lvgl] Post-process size arguments in meter config (#9466) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/widgets/meter.py | 33 ++++++++++++++---------- tests/components/lvgl/lvgl-package.yaml | 10 +++---- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/esphome/components/lvgl/widgets/meter.py b/esphome/components/lvgl/widgets/meter.py index 840511da69..f836a1eca5 100644 --- a/esphome/components/lvgl/widgets/meter.py +++ b/esphome/components/lvgl/widgets/meter.py @@ -29,9 +29,9 @@ from ..defines import ( ) from ..helpers import add_lv_use, lvgl_components_required from ..lv_validation import ( - angle, get_end_value, get_start_value, + lv_angle, lv_bool, lv_color, lv_float, @@ -162,7 +162,7 @@ SCALE_SCHEMA = cv.Schema( cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_, cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_, cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360), - cv.Optional(CONF_ROTATION): angle, + cv.Optional(CONF_ROTATION): lv_angle, cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA), } ) @@ -187,7 +187,7 @@ class MeterType(WidgetType): for scale_conf in config.get(CONF_SCALES, ()): rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2 if CONF_ROTATION in scale_conf: - rotation = scale_conf[CONF_ROTATION] // 10 + rotation = await lv_angle.process(scale_conf[CONF_ROTATION]) with LocalVariable( "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var) ) as meter_var: @@ -205,21 +205,20 @@ class MeterType(WidgetType): var, meter_var, ticks[CONF_COUNT], - ticks[CONF_WIDTH], - ticks[CONF_LENGTH], + await size.process(ticks[CONF_WIDTH]), + await size.process(ticks[CONF_LENGTH]), color, ) if CONF_MAJOR in ticks: major = ticks[CONF_MAJOR] - color = await lv_color.process(major[CONF_COLOR]) lv.meter_set_scale_major_ticks( var, meter_var, major[CONF_STRIDE], - major[CONF_WIDTH], - major[CONF_LENGTH], - color, - major[CONF_LABEL_GAP], + await size.process(major[CONF_WIDTH]), + await size.process(major[CONF_LENGTH]), + await lv_color.process(major[CONF_COLOR]), + await size.process(major[CONF_LABEL_GAP]), ) for indicator in scale_conf.get(CONF_INDICATORS, ()): (t, v) = next(iter(indicator.items())) @@ -233,7 +232,11 @@ class MeterType(WidgetType): lv_assign( ivar, lv_expr.meter_add_needle_line( - var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] + var, + meter_var, + await size.process(v[CONF_WIDTH]), + color, + await size.process(v[CONF_R_MOD]), ), ) if t == CONF_ARC: @@ -241,7 +244,11 @@ class MeterType(WidgetType): lv_assign( ivar, lv_expr.meter_add_arc( - var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] + var, + meter_var, + await size.process(v[CONF_WIDTH]), + color, + await size.process(v[CONF_R_MOD]), ), ) if t == CONF_TICK_STYLE: @@ -257,7 +264,7 @@ class MeterType(WidgetType): color_start, color_end, v[CONF_LOCAL], - v[CONF_WIDTH], + size.process(v[CONF_WIDTH]), ), ) if t == CONF_IMAGE: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 2edc62b6a1..fbcd2a3fba 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -919,21 +919,21 @@ lvgl: text_color: 0xFFFFFF scales: - ticks: - width: 1 + width: !lambda return 1; count: 61 - length: 20 + length: 20% color: 0xFFFFFF range_from: 0 range_to: 60 angle_range: 360 - rotation: 270 + rotation: !lambda return 2700; indicators: - line: opa: 50% id: minute_hand color: 0xFF0000 - r_mod: -1 - width: 3 + r_mod: !lambda return -1; + width: !lambda return 3; - angle_range: 330 rotation: 300 From 77d1d0414d6903a2c77e32e6d30fb607b3bdc2a2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 12 Jul 2025 22:09:55 -1000 Subject: [PATCH 04/64] Automatically disable interrupts for ESP8266 GPIO16 binary sensors (#9467) --- .../components/gpio/binary_sensor/__init__.py | 27 +++++++- .../gpio/test_gpio_binary_sensor.py | 69 +++++++++++++++++++ .../gpio/test_gpio_binary_sensor.yaml | 11 +++ .../gpio/test_gpio_binary_sensor_esp8266.yaml | 20 ++++++ .../gpio/test_gpio_binary_sensor_polling.yaml | 12 ++++ 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 tests/component_tests/gpio/test_gpio_binary_sensor.py create mode 100644 tests/component_tests/gpio/test_gpio_binary_sensor.yaml create mode 100644 tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml create mode 100644 tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 9f50fd779a..867a8efe49 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -1,11 +1,16 @@ +import logging + from esphome import pins import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv -from esphome.const import CONF_PIN +from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN +from esphome.core import CORE from .. import gpio_ns +_LOGGER = logging.getLogger(__name__) + GPIOBinarySensor = gpio_ns.class_( "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component ) @@ -41,6 +46,22 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) - cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT])) - if config[CONF_USE_INTERRUPT]: + # Check for ESP8266 GPIO16 interrupt limitation + # GPIO16 on ESP8266 is a special pin that doesn't support interrupts through + # the Arduino attachInterrupt() function. This is the only known GPIO pin + # across all supported platforms that has this limitation, so we handle it + # here instead of in the platform-specific code. + use_interrupt = config[CONF_USE_INTERRUPT] + if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16: + _LOGGER.warning( + "GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. " + "Falling back to polling mode (same as in ESPHome <2025.7). " + "The sensor will work exactly as before, but other pins have better " + "performance with interrupts.", + config.get(CONF_NAME, config[CONF_ID]), + ) + use_interrupt = False + + cg.add(var.set_use_interrupt(use_interrupt)) + if use_interrupt: cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor.py b/tests/component_tests/gpio/test_gpio_binary_sensor.py new file mode 100644 index 0000000000..74fa2ab1c1 --- /dev/null +++ b/tests/component_tests/gpio/test_gpio_binary_sensor.py @@ -0,0 +1,69 @@ +"""Tests for the GPIO binary sensor component.""" + +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path + +import pytest + + +def test_gpio_binary_sensor_basic_setup( + generate_main: Callable[[str | Path], str], +) -> None: + """ + When the GPIO binary sensor is set in the yaml file, it should be registered in main + """ + main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml") + + assert "new gpio::GPIOBinarySensor();" in main_cpp + assert "App.register_binary_sensor" in main_cpp + assert "bs_gpio->set_use_interrupt(true);" in main_cpp + assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp + + +def test_gpio_binary_sensor_esp8266_gpio16_disables_interrupt( + generate_main: Callable[[str | Path], str], + caplog: pytest.LogCaptureFixture, +) -> None: + """ + Test that ESP8266 GPIO16 automatically disables interrupt mode with a warning + """ + main_cpp = generate_main( + "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" + ) + + # Check that interrupt is disabled for GPIO16 + assert "bs_gpio16->set_use_interrupt(false);" in main_cpp + + # Check that the warning was logged + assert "GPIO16 on ESP8266 doesn't support interrupts" in caplog.text + assert "Falling back to polling mode" in caplog.text + + +def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt( + generate_main: Callable[[str | Path], str], +) -> None: + """ + Test that ESP8266 pins other than GPIO16 still use interrupt mode + """ + main_cpp = generate_main( + "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" + ) + + # GPIO5 should still use interrupts + assert "bs_gpio5->set_use_interrupt(true);" in main_cpp + assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp + + +def test_gpio_binary_sensor_explicit_polling_mode( + generate_main: Callable[[str | Path], str], +) -> None: + """ + Test that explicitly setting use_interrupt: false works + """ + main_cpp = generate_main( + "tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml" + ) + + assert "bs_polling->set_use_interrupt(false);" in main_cpp diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor.yaml b/tests/component_tests/gpio/test_gpio_binary_sensor.yaml new file mode 100644 index 0000000000..e258fe0cb4 --- /dev/null +++ b/tests/component_tests/gpio/test_gpio_binary_sensor.yaml @@ -0,0 +1,11 @@ +esphome: + name: test + +esp32: + board: esp32dev + +binary_sensor: + - platform: gpio + pin: 5 + name: "Test GPIO Binary Sensor" + id: bs_gpio diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml b/tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml new file mode 100644 index 0000000000..aec26fe572 --- /dev/null +++ b/tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml @@ -0,0 +1,20 @@ +esphome: + name: test + +esp8266: + board: d1_mini + +binary_sensor: + - platform: gpio + pin: + number: 16 + mode: INPUT_PULLDOWN_16 + name: "GPIO16 Touch Sensor" + id: bs_gpio16 + + - platform: gpio + pin: + number: 5 + mode: INPUT_PULLUP + name: "GPIO5 Button" + id: bs_gpio5 diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml b/tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml new file mode 100644 index 0000000000..7c53e09265 --- /dev/null +++ b/tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + board: esp32dev + +binary_sensor: + - platform: gpio + pin: 5 + name: "Polling Mode Sensor" + id: bs_polling + use_interrupt: false From c13317f807f320d6d7989462ead61cdc17415131 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Sun, 13 Jul 2025 23:58:52 +0200 Subject: [PATCH 05/64] [substitutions] Fix #7189 (#9469) --- esphome/components/substitutions/__init__.py | 7 +++++-- .../fixtures/substitutions/00-simple_var.approved.yaml | 2 ++ .../fixtures/substitutions/00-simple_var.input.yaml | 2 ++ .../fixtures/substitutions/03-closures.approved.yaml | 1 + .../fixtures/substitutions/closures_package.yaml | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 5878af43b2..e07c2e3859 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -151,8 +151,11 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing): if sub is not None: item[k] = sub for old, new in replace_keys: - item[new] = merge_config(item.get(old), item.get(new)) - del item[old] + if str(new) == str(old): + item[new] = item[old] + else: + item[new] = merge_config(item.get(old), item.get(new)) + del item[old] elif isinstance(item, str): sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing) if isinstance(sub, JinjaStr) or sub != item: diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml index c031399c37..f5d2f8aa20 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml @@ -17,3 +17,5 @@ test_list: - ${undefined_var} - $undefined_var - ${ undefined_var } + - key1: 1 + key2: 2 diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml index 88a4ffb991..5717433c7e 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml @@ -19,3 +19,5 @@ test_list: - ${undefined_var} - $undefined_var - ${ undefined_var } + - key${var1}: 1 + key${var2}: 2 diff --git a/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml b/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml index c8f7d9976c..dad3be8aa7 100644 --- a/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml @@ -6,6 +6,7 @@ package_result: root file - Double substitution also works; the value of var7 is 79, where A is a package var + - key79: Key should substitute to key79 local_results: - The value of B is 5 - 'You will see, however, that diff --git a/tests/unit_tests/fixtures/substitutions/closures_package.yaml b/tests/unit_tests/fixtures/substitutions/closures_package.yaml index e87908814d..4a0be24b93 100644 --- a/tests/unit_tests/fixtures/substitutions/closures_package.yaml +++ b/tests/unit_tests/fixtures/substitutions/closures_package.yaml @@ -1,3 +1,4 @@ package_result: - The value of A*B is ${A * B}, where A is a package var and B is a substitution in the root file - Double substitution also works; the value of var7 is ${var$A}, where A is a package var + - key${var7}: Key should substitute to key79 From 9207bf97f316c5ad93da2ffa9f267c8c182e40f0 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:47:52 +1000 Subject: [PATCH 06/64] [esp_ldo] Component schema; default priority (#9479) --- esphome/components/esp_ldo/__init__.py | 18 ++++++++++-------- esphome/components/esp_ldo/esp_ldo.h | 3 +++ .../components/esp_ldo/test.esp32-p4-idf.yaml | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp_ldo/__init__.py b/esphome/components/esp_ldo/__init__.py index ce24028083..38e684c537 100644 --- a/esphome/components/esp_ldo/__init__.py +++ b/esphome/components/esp_ldo/__init__.py @@ -20,14 +20,16 @@ adjusted_ids = set() CONFIG_SCHEMA = cv.All( cv.ensure_list( - { - cv.GenerateID(): cv.declare_id(EspLdo), - cv.Required(CONF_VOLTAGE): cv.All( - cv.voltage, cv.float_range(min=0.5, max=2.7) - ), - cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True), - cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean, - } + cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(EspLdo), + cv.Required(CONF_VOLTAGE): cv.All( + cv.voltage, cv.float_range(min=0.5, max=2.7) + ), + cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True), + cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean, + } + ) ), cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32P4]), diff --git a/esphome/components/esp_ldo/esp_ldo.h b/esphome/components/esp_ldo/esp_ldo.h index fb5abf7a3a..bafa32db6b 100644 --- a/esphome/components/esp_ldo/esp_ldo.h +++ b/esphome/components/esp_ldo/esp_ldo.h @@ -17,6 +17,9 @@ class EspLdo : public Component { void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; } void set_voltage(float voltage) { this->voltage_ = voltage; } void adjust_voltage(float voltage); + float get_setup_priority() const override { + return setup_priority::BUS; // LDO setup should be done early + } protected: int channel_; diff --git a/tests/components/esp_ldo/test.esp32-p4-idf.yaml b/tests/components/esp_ldo/test.esp32-p4-idf.yaml index 0e91aaf082..38bd6ac411 100644 --- a/tests/components/esp_ldo/test.esp32-p4-idf.yaml +++ b/tests/components/esp_ldo/test.esp32-p4-idf.yaml @@ -6,6 +6,7 @@ esp_ldo: - id: ldo_4 channel: 4 voltage: 2.0V + setup_priority: 900 esphome: on_boot: From e43efdaaecb6bf0fb6783b6da7fc20722d55c4ed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 14:41:49 -1000 Subject: [PATCH 07/64] Follow logging best practices by removing redundant component prefix (#9481) --- esphome/core/component.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 9d863e56cd..b360e1d20b 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -138,7 +138,7 @@ void Component::call_dump_config() { } } } - ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg); + ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), error_msg); } } @@ -191,7 +191,7 @@ bool Component::should_warn_of_blocking(uint32_t blocking_time) { return false; } void Component::mark_failed() { - ESP_LOGE(TAG, "Component %s was marked as failed", this->get_component_source()); + ESP_LOGE(TAG, "%s was marked as failed", this->get_component_source()); this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ |= COMPONENT_STATE_FAILED; this->status_set_error(); @@ -229,7 +229,7 @@ void IRAM_ATTR HOT Component::enable_loop_soon_any_context() { } void Component::reset_to_construction_state() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { - ESP_LOGI(TAG, "Component %s is being reset to construction state", this->get_component_source()); + ESP_LOGI(TAG, "%s is being reset to construction state", this->get_component_source()); this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; // Clear error status when resetting @@ -275,14 +275,14 @@ void Component::status_set_warning(const char *message) { return; this->component_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING; - ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message); + ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message); } void Component::status_set_error(const char *message) { if ((this->component_state_ & STATUS_LED_ERROR) != 0) return; this->component_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR; - ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); + ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message); if (strcmp(message, "unspecified") != 0) { // Lazy allocate the error messages vector if needed if (!component_error_messages) { @@ -303,13 +303,13 @@ void Component::status_clear_warning() { if ((this->component_state_ & STATUS_LED_WARNING) == 0) return; this->component_state_ &= ~STATUS_LED_WARNING; - ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source()); + ESP_LOGW(TAG, "%s cleared Warning flag", this->get_component_source()); } void Component::status_clear_error() { if ((this->component_state_ & STATUS_LED_ERROR) == 0) return; this->component_state_ &= ~STATUS_LED_ERROR; - ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source()); + ESP_LOGE(TAG, "%s cleared Error flag", this->get_component_source()); } void Component::status_momentary_warning(const std::string &name, uint32_t length) { this->status_set_warning(); @@ -403,7 +403,7 @@ uint32_t WarnIfComponentBlockingGuard::finish() { } if (should_warn) { const char *src = component_ == nullptr ? "" : component_->get_component_source(); - ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time); + ESP_LOGW(TAG, "%s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time); ESP_LOGW(TAG, "Components should block for at most 30 ms"); } From 10ca7ed85b5bc90e0f653445f3dcfe88e1c4ccae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 14:58:07 -1000 Subject: [PATCH 08/64] Fix dormant bug in RAMAllocator::reallocate() manual_size calculation (#9482) --- esphome/core/helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 58f162ff9d..c3b404ae60 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -783,7 +783,7 @@ template class RAMAllocator { T *reallocate(T *p, size_t n) { return this->reallocate(p, n, sizeof(T)); } T *reallocate(T *p, size_t n, size_t manual_size) { - size_t size = n * sizeof(T); + size_t size = n * manual_size; T *ptr = nullptr; #ifdef USE_ESP32 if (this->flags_ & Flags::ALLOC_EXTERNAL) { From b4521e1d8c6dad3f8de33a72951609d610d228f2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:08:24 +1200 Subject: [PATCH 09/64] Bump version to 2025.7.0b3 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 05731fa4ef..42af703a94 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.7.0b2 +PROJECT_NUMBER = 2025.7.0b3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 0753ea32d4..de15050d0c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.7.0b2" +__version__ = "2025.7.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 740c0ef9d7efb0fefd953abbccb6baa7cf77c472 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 12:44:21 -1000 Subject: [PATCH 10/64] Fix pre-commit CI failures by skipping local hooks that require virtual environment (#9476) --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 831473c325..cf00f9bd47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,13 @@ --- # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks + +ci: + autoupdate_commit_msg: 'pre-commit: autoupdate' + autoupdate_schedule: weekly + # Skip hooks that have issues in pre-commit CI environment + skip: [pylint, clang-tidy-hash, yamllint] + repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. From 4153380f99af004e7072f0e293610e4feb4b0304 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:48:41 +1200 Subject: [PATCH 11/64] Remove hook that doesnt exist on beta --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf00f9bd47..837c71cf38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: autoupdate_commit_msg: 'pre-commit: autoupdate' autoupdate_schedule: weekly # Skip hooks that have issues in pre-commit CI environment - skip: [pylint, clang-tidy-hash, yamllint] + skip: [pylint, yamllint] repos: - repo: https://github.com/astral-sh/ruff-pre-commit From 90f0ebb22b5a8294b295a002748378ba05f16aaf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:12:26 +1200 Subject: [PATCH 12/64] Dont autofix PR --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 837c71cf38..83ea2dc823 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ ci: autoupdate_commit_msg: 'pre-commit: autoupdate' autoupdate_schedule: weekly + autofix_prs: false # Skip hooks that have issues in pre-commit CI environment skip: [pylint, yamllint] From e7d819a6564483a67f1df4a0446e386d2109c8fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 21:47:52 -1000 Subject: [PATCH 13/64] Suppress spurious volatile and Python syntax warnings during builds (#9488) --- esphome/platformio_api.py | 2 ++ esphome/writer.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 808db03231..e34ac028f8 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -78,6 +78,8 @@ def run_platformio_cli(*args, **kwargs) -> str | int: os.environ.setdefault( "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path()) ) + # Suppress Python syntax warnings from third-party scripts during compilation + os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning") cmd = ["platformio"] + list(args) if not CORE.verbose: diff --git a/esphome/writer.py b/esphome/writer.py index 943dfa78cc..ca9e511c19 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -162,6 +162,9 @@ def get_ini_content(): # Sort to avoid changing build unflags order CORE.add_platformio_option("build_unflags", sorted(CORE.build_unflags)) + # Add extra script for C++ flags + CORE.add_platformio_option("extra_scripts", ["pre:cxx_flags.py"]) + content = "[platformio]\n" content += f"description = ESPHome {__version__}\n" @@ -222,6 +225,9 @@ def write_platformio_project(): write_gitignore() write_platformio_ini(content) + # Write extra script for C++ specific flags + write_cxx_flags_script() + DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ #pragma once @@ -394,3 +400,16 @@ def write_gitignore(): if not os.path.isfile(path): with open(file=path, mode="w", encoding="utf-8") as f: f.write(GITIGNORE_CONTENT) + + +CXX_FLAGS_SCRIPT = """# Auto-generated ESPHome script for C++ specific compiler flags +Import("env") + +# Add C++ specific warning flags +env.Append(CXXFLAGS=["-Wno-volatile"]) +""" + + +def write_cxx_flags_script() -> None: + path = CORE.relative_build_path("cxx_flags.py") + write_file_if_changed(path, CXX_FLAGS_SCRIPT) From e713b0bd8ca8ed56c30e0a7f8f712db729e63f7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 22:01:44 -1000 Subject: [PATCH 14/64] Remove parsed advertisement support from bluetooth_proxy to save memory --- .../components/bluetooth_proxy/__init__.py | 4 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 41 +++++------ .../bluetooth_proxy/bluetooth_proxy.h | 7 +- .../esp32_ble_client/ble_client_base.cpp | 2 + .../esp32_ble_client/ble_client_base.h | 2 + .../components/esp32_ble_tracker/__init__.py | 69 ++++++++++++++++++- .../components/esp32_ble_tracker/automation.h | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 10 ++- .../esp32_ble_tracker/esp32_ble_tracker.h | 6 ++ 9 files changed, 112 insertions(+), 31 deletions(-) diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index 5c144cadcc..a1e9d464df 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -85,13 +85,13 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_active(config[CONF_ACTIVE])) - await esp32_ble_tracker.register_ble_device(var, config) + await esp32_ble_tracker.register_raw_ble_device(var, config) for connection_conf in config.get(CONF_CONNECTIONS, []): connection_var = cg.new_Pvariable(connection_conf[CONF_ID]) await cg.register_component(connection_var, connection_conf) cg.add(var.register_connection(connection_var)) - await esp32_ble_tracker.register_client(connection_var, connection_conf) + await esp32_ble_tracker.register_raw_client(connection_var, connection_conf) if config.get(CONF_CACHE_SERVICES): add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index a5e8ec0860..1c856b8d93 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -42,15 +42,13 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta this->api_connection_->send_message(resp); } +#ifdef USE_ESP32_BLE_DEVICE bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_) - return false; - - ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), - device.get_rssi()); - this->send_api_packet_(device); - return true; + // This method should never be called since bluetooth_proxy always uses raw advertisements + // but we need to provide an implementation to satisfy the virtual method requirement + return false; } +#endif // Batch size for BLE advertisements to maximize WiFi efficiency // Each advertisement is up to 80 bytes when packaged (including protocol overhead) @@ -69,7 +67,7 @@ std::vector batch_buffer; static std::vector &get_batch_buffer() { return batch_buffer; } bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) { - if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) + if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) return false; // Get the batch buffer reference @@ -116,6 +114,7 @@ void BluetoothProxy::flush_pending_advertisements() { this->api_connection_->send_message(resp); } +#ifdef USE_ESP32_BLE_DEVICE void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { api::BluetoothLEAdvertisementResponse resp; resp.address = device.address_uint64(); @@ -153,14 +152,14 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi this->api_connection_->send_message(resp); } +#endif // USE_ESP32_BLE_DEVICE void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); ESP_LOGCONFIG(TAG, " Active: %s\n" - " Connections: %d\n" - " Raw advertisements: %s", - YESNO(this->active_), this->connections_.size(), YESNO(this->raw_advertisements_)); + " Connections: %d", + YESNO(this->active_), this->connections_.size()); } int BluetoothProxy::get_bluetooth_connections_free() { @@ -188,15 +187,13 @@ void BluetoothProxy::loop() { } // Flush any pending BLE advertisements that have been accumulated but not yet sent - if (this->raw_advertisements_) { - static uint32_t last_flush_time = 0; - uint32_t now = App.get_loop_component_start_time(); + static uint32_t last_flush_time = 0; + uint32_t now = App.get_loop_component_start_time(); - // Flush accumulated advertisements every 100ms - if (now - last_flush_time >= 100) { - this->flush_pending_advertisements(); - last_flush_time = now; - } + // Flush accumulated advertisements every 100ms + if (now - last_flush_time >= 100) { + this->flush_pending_advertisements(); + last_flush_time = now; } for (auto *connection : this->connections_) { if (connection->send_service_ == connection->service_count_) { @@ -318,9 +315,7 @@ void BluetoothProxy::loop() { } esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() { - if (this->raw_advertisements_) - return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; - return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS; + return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; } BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { @@ -565,7 +560,6 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection return; } this->api_connection_ = api_connection; - this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; this->parent_->recalculate_advertisement_parser_types(); this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state()); @@ -577,7 +571,6 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti return; } this->api_connection_ = nullptr; - this->raw_advertisements_ = false; this->parent_->recalculate_advertisement_parser_types(); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index f0632350e0..3ccf0706a7 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -51,7 +51,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t { class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: BluetoothProxy(); +#ifdef USE_ESP32_BLE_DEVICE bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +#endif bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override; void dump_config() override; void setup() override; @@ -129,7 +131,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com } protected: +#ifdef USE_ESP32_BLE_DEVICE void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); +#endif void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state); BluetoothConnection *get_connection_(uint64_t address, bool reserve); @@ -143,8 +147,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com // Group 3: 1-byte types grouped together bool active_; - bool raw_advertisements_{false}; - // 2 bytes used, 2 bytes padding + // 1 byte used, 3 bytes padding }; extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 7d0a3bbfd5..bf425b3730 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -105,6 +105,7 @@ void BLEClientBase::dump_config() { } } +#ifdef USE_ESP32_BLE_DEVICE bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { if (!this->auto_connect_) return false; @@ -122,6 +123,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { this->remote_addr_type_ = device.get_address_type(); return true; } +#endif void BLEClientBase::connect() { ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index bf3b589b1b..457a88ec1d 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -31,7 +31,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void dump_config() override; void run_later(std::function &&f); // NOLINT +#ifdef USE_ESP32_BLE_DEVICE bool parse_device(const espbt::ESPBTDevice &device) override; +#endif void on_scan_end() override {} bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 547cf84ed1..68f4657515 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -31,6 +31,8 @@ from esphome.const import ( CONF_TRIGGER_ID, ) from esphome.core import CORE +from esphome.enum import StrEnum +from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] @@ -50,6 +52,25 @@ IDF_MAX_CONNECTIONS = 9 _LOGGER = logging.getLogger(__name__) + +# Enum for BLE features +class BLEFeatures(StrEnum): + ESP_BT_DEVICE = "ESP_BT_DEVICE" + + +# Set to track which features are needed by components +_required_features: set[BLEFeatures] = set() + + +def register_ble_features(features: set[BLEFeatures]) -> None: + """Register BLE features that a component needs. + + Args: + features: Set of BLEFeatures enum members + """ + _required_features.update(features) + + esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_( "ESP32BLETracker", @@ -277,6 +298,15 @@ async def to_code(config): cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) + + # Register ESP_BT_DEVICE feature if any of the automation triggers are used + if ( + config.get(CONF_ON_BLE_ADVERTISE) + or config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE) + or config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE) + ): + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) + for conf in config.get(CONF_ON_BLE_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: @@ -334,6 +364,11 @@ async def to_code(config): cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") + + # Add feature-specific defines based on what's needed + if BLEFeatures.ESP_BT_DEVICE in _required_features: + cg.add_define("USE_ESP32_BLE_DEVICE") + if config.get(CONF_SOFTWARE_COEXISTENCE): cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE") @@ -382,13 +417,43 @@ async def esp32_ble_tracker_stop_scan_action_to_code( return var -async def register_ble_device(var, config): +async def register_ble_device( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_listener(var)) return var -async def register_client(var, config): +async def register_client(var: cg.SafeExpType, config: ConfigType) -> cg.SafeExpType: + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) + paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) + cg.add(paren.register_client(var)) + return var + + +async def register_raw_ble_device( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + """Register a BLE device listener that only needs raw advertisement data. + + This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice + will not be compiled in if this is the only registration method used. + """ + paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) + cg.add(paren.register_listener(var)) + return var + + +async def register_raw_client( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + """Register a BLE client that only needs raw advertisement data. + + This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice + will not be compiled in if this is the only registration method used. + """ paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_client(var)) return var diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 6bef9edcb3..ef677922e3 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -7,6 +7,7 @@ namespace esphome { namespace esp32_ble_tracker { +#ifdef USE_ESP32_BLE_DEVICE class ESPBTAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { public: explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } @@ -87,6 +88,7 @@ class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { bool parse_device(const ESPBTDevice &device) override { return false; } void on_scan_end() override { this->trigger(); } }; +#endif // USE_ESP32_BLE_DEVICE template class ESP32BLEStartScanAction : public Action { public: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index d950ccb5f1..44577afbbd 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -141,6 +141,7 @@ void ESP32BLETracker::loop() { } if (this->parse_advertisements_) { +#ifdef USE_ESP32_BLE_DEVICE ESPBTDevice device; device.parse_scan_rst(scan_result); @@ -162,6 +163,7 @@ void ESP32BLETracker::loop() { if (!found && !this->scan_continuous_) { this->print_bt_device_info(device); } +#endif // USE_ESP32_BLE_DEVICE } // Move to next entry in ring buffer @@ -511,6 +513,7 @@ void ESP32BLETracker::set_scanner_state_(ScannerState state) { this->scanner_state_callbacks_.call(state); } +#ifdef USE_ESP32_BLE_DEVICE ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } optional ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) { if (!data.uuid.contains(0x4C, 0x00)) @@ -751,13 +754,16 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { } } } + std::string ESPBTDevice::address_str() const { char mac[24]; snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2], this->address_[3], this->address_[4], this->address_[5]); return mac; } + uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } +#endif // USE_ESP32_BLE_DEVICE void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, "BLE Tracker:"); @@ -796,6 +802,7 @@ void ESP32BLETracker::dump_config() { } } +#ifdef USE_ESP32_BLE_DEVICE void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { const uint64_t address = device.address_uint64(); for (auto &disc : this->already_discovered_) { @@ -866,8 +873,9 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const { return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) && ecb_ciphertext[13] == ((addr64 >> 16) & 0xff); } +#endif // USE_ESP32_BLE_DEVICE } // namespace esp32_ble_tracker } // namespace esphome -#endif +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index f5ed75a93e..e10f4551e8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -39,6 +39,7 @@ struct ServiceData { adv_data_t data; }; +#ifdef USE_ESP32_BLE_DEVICE class ESPBLEiBeacon { public: ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); } @@ -116,13 +117,16 @@ class ESPBTDevice { std::vector service_datas_{}; const BLEScanResult *scan_result_{nullptr}; }; +#endif // USE_ESP32_BLE_DEVICE class ESP32BLETracker; class ESPBTDeviceListener { public: virtual void on_scan_end() {} +#ifdef USE_ESP32_BLE_DEVICE virtual bool parse_device(const ESPBTDevice &device) = 0; +#endif virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; }; virtual AdvertisementParserType get_advertisement_parser_type() { return AdvertisementParserType::PARSED_ADVERTISEMENTS; @@ -237,7 +241,9 @@ class ESP32BLETracker : public Component, void register_client(ESPBTClient *client); void recalculate_advertisement_parser_types(); +#ifdef USE_ESP32_BLE_DEVICE void print_bt_device_info(const ESPBTDevice &device); +#endif void start_scan(); void stop_scan(); From 2a10f58bdd3a287fc81f1b2c5efbe0d2ea478483 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Jul 2025 22:09:53 -1000 Subject: [PATCH 15/64] Remove parsed advertisement support from bluetooth_proxy to save memory --- esphome/core/defines.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8ed8f4b5aa..7ddb3436cd 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -145,6 +145,7 @@ #define USE_CAPTIVE_PORTAL #define USE_ESP32_BLE #define USE_ESP32_BLE_CLIENT +#define USE_ESP32_BLE_DEVICE #define USE_ESP32_BLE_SERVER #define USE_I2C #define USE_IMPROV From c069a6662561e6339fc0bda92cf4494d4fe8ccd5 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 6 May 2025 18:26:49 +0000 Subject: [PATCH 16/64] bump ArduinoJSON library to 7.4.1 --- esphome/components/json/__init__.py | 2 +- esphome/components/json/json_util.cpp | 40 +++++++++++++-------------- platformio.ini | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 6a0e4c50d2..399cf708f6 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("bblanchon/ArduinoJson", "6.18.5") + cg.add_library("bblanchon/ArduinoJson", "7.4.1") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 6c66476dc1..f5b662d543 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -6,30 +6,39 @@ namespace json { static const char *const TAG = "json"; -static std::vector global_json_build_buffer; // NOLINT -static const auto ALLOCATOR = RAMAllocator(RAMAllocator::ALLOC_INTERNAL); +static auto ALLOCATOR = RAMAllocator( + RAMAllocator::NONE); // Attempt to allocate in PSRAM before falling back into internal + +// Build an allocator for the JSON Library using the RAMAllocator class +struct SpiRamAllocator : ArduinoJson::Allocator { + void *allocate(size_t size) { return ALLOCATOR.allocate(size); } + + void deallocate(void *pointer) { + free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + } + + void *reallocate(void *ptr, size_t new_size) { return ALLOCATOR.reallocate(static_cast(ptr), new_size); } +}; + +static auto DOC_ALLOCATOR = SpiRamAllocator(); std::string build_json(const json_build_t &f) { // Here we are allocating up to 5kb of memory, // with the heap size minus 2kb to be safe if less than 5kb // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` - auto free_heap = ALLOCATOR.get_max_free_block_size(); - size_t request_size = std::min(free_heap, (size_t) 512); while (true) { ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size); DynamicJsonDocument json_document(request_size); if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, largest free heap block: %zu bytes", - request_size, free_heap); + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; } JsonObject root = json_document.to(); f(root); if (json_document.overflowed()) { if (request_size == free_heap) { - ESP_LOGE(TAG, "Could not allocate memory for document! Overflowed largest free heap block: %zu bytes", - free_heap); + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; } request_size = std::min(request_size * 2, free_heap); @@ -48,30 +57,21 @@ bool parse_json(const std::string &data, const json_parse_t &f) { // with the heap size minus 2kb to be safe if less than that // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` - auto free_heap = ALLOCATOR.get_max_free_block_size(); - size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); while (true) { DynamicJsonDocument json_document(request_size); if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, free heap: %zu", request_size, - free_heap); + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; } DeserializationError err = deserializeJson(json_document, data); - json_document.shrinkToFit(); JsonObject root = json_document.as(); if (err == DeserializationError::Ok) { return f(root); } else if (err == DeserializationError::NoMemory) { - if (request_size * 2 >= free_heap) { - ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); - return false; - } - ESP_LOGV(TAG, "Increasing memory allocation."); - request_size *= 2; - continue; + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + return false; } else { ESP_LOGE(TAG, "Parse error: %s", err.c_str()); return false; diff --git a/platformio.ini b/platformio.ini index 54c72eb28d..b2478d356e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.10 ; api improv/Improv@1.2.4 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@6.18.5 ; json + bblanchon/ArduinoJson@7.4.1 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier From 1155e9b88ae9b65ae2fbca33678c197d200fef71 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 13:34:43 +0000 Subject: [PATCH 17/64] use new syntax instead of containsKey --- .../update/http_request_update.cpp | 12 ++++---- .../components/light/light_json_schema.cpp | 30 +++++++++---------- esphome/components/mqtt/mqtt_date.cpp | 6 ++-- esphome/components/mqtt/mqtt_datetime.cpp | 12 ++++---- esphome/components/mqtt/mqtt_time.cpp | 6 ++-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 202c7b88b2..b6b160d60e 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -83,7 +83,7 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { + if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } @@ -91,26 +91,26 @@ void HttpRequestUpdate::update_task(void *params) { this_update->update_info_.latest_version = root["version"].as(); for (auto build : root["builds"].as()) { - if (!build.containsKey("chipFamily")) { + if (!build["chipFamily"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build.containsKey("ota")) { + if (!build["ota"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } auto ota = build["ota"]; - if (!ota.containsKey("path") || !ota.containsKey("md5")) { + if (!ota["path"].is() || !ota["md5"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } this_update->update_info_.firmware_url = ota["path"].as(); this_update->update_info_.md5 = ota["md5"].as(); - if (ota.containsKey("summary")) + if (ota["summary"].is()) this_update->update_info_.summary = ota["summary"].as(); - if (ota.containsKey("release_url")) + if (ota["release_url"].is()) this_update->update_info_.release_url = ota["release_url"].as(); return true; diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 6f8cc11f25..306103244c 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -73,7 +73,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { } void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { - if (root.containsKey("state")) { + if (root["state"].is()) { auto val = parse_on_off(root["state"]); switch (val) { case PARSE_ON: @@ -90,40 +90,40 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root.containsKey("brightness")) { + if (root["brightness"].is()) { call.set_brightness(float(root["brightness"]) / 255.0f); } - if (root.containsKey("color")) { + if (root["color"].is()) { JsonObject color = root["color"]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; - if (color.containsKey("r")) { + if (color["r"].is()) { float r = float(color["r"]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } - if (color.containsKey("g")) { + if (color["g"].is()) { float g = float(color["g"]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } - if (color.containsKey("b")) { + if (color["b"].is()) { float b = float(color["b"]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } - if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { + if (color["r"].is() || color["g"].is() || color["b"].is()) { call.set_color_brightness(max_rgb); } - if (color.containsKey("c")) { + if (color["c"].is()) { call.set_cold_white(float(color["c"]) / 255.0f); } - if (color.containsKey("w")) { + if (color["w"].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color.containsKey("c")) { + if (color["c"].is()) { call.set_warm_white(float(color["w"]) / 255.0f); } else { call.set_white(float(color["w"]) / 255.0f); @@ -131,11 +131,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root.containsKey("white_value")) { // legacy API + if (root["white_value"].is()) { // legacy API call.set_white(float(root["white_value"]) / 255.0f); } - if (root.containsKey("color_temp")) { + if (root["color_temp"].is()) { call.set_color_temperature(float(root["color_temp"])); } } @@ -143,17 +143,17 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); - if (root.containsKey("flash")) { + if (root["flash"].is()) { auto length = uint32_t(float(root["flash"]) * 1000); call.set_flash_length(length); } - if (root.containsKey("transition")) { + if (root["transition"].is()) { auto length = uint32_t(float(root["transition"]) * 1000); call.set_transition_length(length); } - if (root.containsKey("effect")) { + if (root["effect"].is()) { const char *effect = root["effect"]; call.set_effect(effect); } diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 088a4788ed..7349e7c64a 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -20,13 +20,13 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root.containsKey("year")) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root.containsKey("month")) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root.containsKey("day")) { + if (root["day"].is()) { call.set_day(root["day"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 4ae6d0d416..44ce8ec8f2 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -20,22 +20,22 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root.containsKey("year")) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root.containsKey("month")) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root.containsKey("day")) { + if (root["day"].is()) { call.set_day(root["day"]); } - if (root.containsKey("hour")) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root.containsKey("minute")) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root.containsKey("second")) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 332ef53cbc..b49071c4fd 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root.containsKey("hour")) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root.containsKey("minute")) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root.containsKey("second")) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); From 8648acab5de2d64e0b9bb76949701787c63c3fb0 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 13:54:45 +0000 Subject: [PATCH 18/64] remove old capacity() call --- esphome/components/json/json_util.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index f5b662d543..a9c6821112 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -44,8 +44,6 @@ std::string build_json(const json_build_t &f) { request_size = std::min(request_size * 2, free_heap); continue; } - json_document.shrinkToFit(); - ESP_LOGV(TAG, "Size after shrink %zu bytes", json_document.capacity()); std::string output; serializeJson(json_document, output); return output; From 8040c7cd92731153cad39d76374e3ef0b5f4f9b1 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 14:18:36 +0000 Subject: [PATCH 19/64] update createdNestedArray calls --- .../mqtt/mqtt_alarm_control_panel.cpp | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 8 ++++---- esphome/components/mqtt/mqtt_event.cpp | 2 +- esphome/components/mqtt/mqtt_light.cpp | 4 ++-- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/web_server/web_server.cpp | 18 +++++++++--------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 0a38598679..9e1d283504 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -55,7 +55,7 @@ void MQTTAlarmControlPanelComponent::dump_config() { } void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - JsonArray supported_features = root.createNestedArray(MQTT_SUPPORTED_FEATURES); + JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); if (acp_supported_features & ACP_FEAT_ARM_AWAY) { supported_features.add("arm_away"); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a8768114a4..9890654c04 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -28,7 +28,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // mode_state_topic root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray modes = root.createNestedArray(MQTT_MODES); + JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -89,7 +89,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // preset_mode_state_topic root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); // presets - JsonArray presets = root.createNestedArray("preset_modes"); + JsonArray presets = root["preset_modes"].to(); if (traits.supports_preset(CLIMATE_PRESET_HOME)) presets.add("home"); if (traits.supports_preset(CLIMATE_PRESET_AWAY)) @@ -119,7 +119,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // fan_mode_state_topic root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes - JsonArray fan_modes = root.createNestedArray("fan_modes"); + JsonArray fan_modes = root["fan_modes"].to(); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) fan_modes.add("on"); if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) @@ -150,7 +150,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // swing_mode_state_topic root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes - JsonArray swing_modes = root.createNestedArray("swing_modes"); + JsonArray swing_modes = root["swing_modes"].to(); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) swing_modes.add("off"); if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index cf0b90e3d6..e459ba9d5b 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -16,7 +16,7 @@ using namespace esphome::event; MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {} void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES); + JsonArray event_types = root[MQTT_EVENT_TYPES].to(); for (const auto &event_type : this->event_->get_event_types()) event_types.add(event_type); diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index f970da7d8c..988d582453 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -42,7 +42,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; - JsonArray color_modes = root.createNestedArray("supported_color_modes"); + JsonArray color_modes = root["supported_color_modes"].to(); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) @@ -67,7 +67,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray effect_list = root.createNestedArray(MQTT_EFFECT_LIST); + JsonArray effect_list = root[MQTT_EFFECT_LIST].to(); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index ea5130f823..99b9b0168f 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -35,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray options = root.createNestedArray(MQTT_OPTIONS); + JsonArray options = root[MQTT_OPTIONS].to(); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 8ced5b7e18..9b1bfa1a5e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -792,7 +792,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi light::LightJSONSchema::dump_json(*obj, root); if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("effects"); + JsonArray opt = root["effects"].to(); opt.add("None"); for (auto const &option : obj->get_effects()) { opt.add(option->get_name()); @@ -1238,7 +1238,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("option"); + JsonArray opt = root["option"].to(); for (auto &option : obj->traits.get_options()) { opt.add(option); } @@ -1330,32 +1330,32 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf char buf[16]; if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("modes"); + JsonArray opt = root["modes"].to(); for (climate::ClimateMode m : traits.get_supported_modes()) opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root.createNestedArray("fan_modes"); + JsonArray opt = root["fan_modes"].to(); for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root.createNestedArray("custom_fan_modes"); + JsonArray opt = root["custom_fan_modes"].to(); for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) opt.add(custom_fan_mode); } if (traits.get_supports_swing_modes()) { - JsonArray opt = root.createNestedArray("swing_modes"); + JsonArray opt = root["swing_modes"].to(); for (auto swing_mode : traits.get_supported_swing_modes()) opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); } if (traits.get_supports_presets() && obj->preset.has_value()) { - JsonArray opt = root.createNestedArray("presets"); + JsonArray opt = root["presets"].to(); for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { - JsonArray opt = root.createNestedArray("custom_presets"); + JsonArray opt = root["custom_presets"].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); } @@ -1635,7 +1635,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty root["event_type"] = event_type; } if (start_config == DETAIL_ALL) { - JsonArray event_types = root.createNestedArray("event_types"); + JsonArray event_types = root["event_types"].to(); for (auto const &event_type : obj->get_event_types()) { event_types.add(event_type); } From ef072eb655150d4448d9baa875f21af210a89d04 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 14:19:54 +0000 Subject: [PATCH 20/64] update createNestedObject --- esphome/components/light/light_json_schema.cpp | 2 +- esphome/components/mqtt/mqtt_component.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 306103244c..8ecda4918e 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -52,7 +52,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { if (values.get_color_mode() & ColorCapability::BRIGHTNESS) root["brightness"] = uint8_t(values.get_brightness() * 255); - JsonObject color = root.createNestedObject("color"); + JsonObject color = root["color"].to(); if (values.get_color_mode() & ColorCapability::RGB) { color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index eee5644c9d..a98892aa24 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -155,7 +155,7 @@ bool MQTTComponent::send_discovery_() { } std::string node_area = App.get_area(); - JsonObject device_info = root.createNestedObject(MQTT_DEVICE); + JsonObject device_info = root[MQTT_DEVICE].to(); const auto mac = get_mac_address(); device_info[MQTT_DEVICE_IDENTIFIERS] = mac; device_info[MQTT_DEVICE_NAME] = node_friendly_name; From d97f473e4adeb6869bec7d703c0d7b34a1f74059 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 14:46:54 +0000 Subject: [PATCH 21/64] include proper header for allocator and mark the functions as override --- esphome/components/json/json_util.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index a9c6821112..c55ee4e401 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,6 +1,8 @@ #include "json_util.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace json { @@ -11,13 +13,15 @@ static auto ALLOCATOR = RAMAllocator( // Build an allocator for the JSON Library using the RAMAllocator class struct SpiRamAllocator : ArduinoJson::Allocator { - void *allocate(size_t size) { return ALLOCATOR.allocate(size); } + void *allocate(size_t size) override { return ALLOCATOR.allocate(size); } - void deallocate(void *pointer) { + void deallocate(void *pointer) override { free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) } - void *reallocate(void *ptr, size_t new_size) { return ALLOCATOR.reallocate(static_cast(ptr), new_size); } + void *reallocate(void *ptr, size_t new_size) override { + return ALLOCATOR.reallocate(static_cast(ptr), new_size); + } }; static auto DOC_ALLOCATOR = SpiRamAllocator(); From 8ad4d3b6f544916d11a8fa2d1f28a542d9c47658 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 20 May 2025 14:52:17 +0000 Subject: [PATCH 22/64] fix type of ota object --- esphome/components/http_request/update/http_request_update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index b6b160d60e..eb2d1e68ef 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -100,7 +100,7 @@ void HttpRequestUpdate::update_task(void *params) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - auto ota = build["ota"]; + JsonObject ota = build["ota"].as(); if (!ota["path"].is() || !ota["md5"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; From 44f97e2de42afc3afacfff86b38552ff4f267ffd Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Wed, 21 May 2025 20:05:13 +0000 Subject: [PATCH 23/64] move allocator to be a protected variable --- esphome/components/json/json_util.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index c55ee4e401..4181f60f66 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -8,23 +8,22 @@ namespace json { static const char *const TAG = "json"; -static auto ALLOCATOR = RAMAllocator( - RAMAllocator::NONE); // Attempt to allocate in PSRAM before falling back into internal - // Build an allocator for the JSON Library using the RAMAllocator class struct SpiRamAllocator : ArduinoJson::Allocator { - void *allocate(size_t size) override { return ALLOCATOR.allocate(size); } + void *allocate(size_t size) override { return this->allocator_.allocate(size); } void deallocate(void *pointer) override { + // RAMAllocator requires passing the size of the allocated space which don't know, so use free directly free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) } void *reallocate(void *ptr, size_t new_size) override { - return ALLOCATOR.reallocate(static_cast(ptr), new_size); + return this->allocator_.reallocate(static_cast(ptr), new_size); } -}; -static auto DOC_ALLOCATOR = SpiRamAllocator(); + protected: + RAMAllocator allocator_{RAMAllocator(RAMAllocator::NONE)}; +}; std::string build_json(const json_build_t &f) { // Here we are allocating up to 5kb of memory, @@ -32,9 +31,9 @@ std::string build_json(const json_build_t &f) { // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` while (true) { - ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size); - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { + auto DOC_ALLOCATOR = SpiRamAllocator(); + JsonDocument json_document(&DOC_ALLOCATOR); + if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; } @@ -60,8 +59,9 @@ bool parse_json(const std::string &data, const json_parse_t &f) { // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` while (true) { - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { + auto DOC_ALLOCATOR = SpiRamAllocator(); + JsonDocument json_document(&DOC_ALLOCATOR); + if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; } From 9ef982fa4d1fc831287855a8ae096fcb913ab4a7 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Wed, 21 May 2025 21:51:45 +0000 Subject: [PATCH 24/64] clang fix --- esphome/components/json/json_util.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 4181f60f66..51317df343 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -31,8 +31,8 @@ std::string build_json(const json_build_t &f) { // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` while (true) { - auto DOC_ALLOCATOR = SpiRamAllocator(); - JsonDocument json_document(&DOC_ALLOCATOR); + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; @@ -59,8 +59,8 @@ bool parse_json(const std::string &data, const json_parse_t &f) { // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` while (true) { - auto DOC_ALLOCATOR = SpiRamAllocator(); - JsonDocument json_document(&DOC_ALLOCATOR); + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; From a1281febe90764ded8891a6020562580c6f69791 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 10:23:39 -0400 Subject: [PATCH 25/64] bump to 7.4.2 --- esphome/components/json/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 399cf708f6..9773bf67ce 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("bblanchon/ArduinoJson", "7.4.1") + cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/platformio.ini b/platformio.ini index b2478d356e..f9e4e31ece 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.10 ; api improv/Improv@1.2.4 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@7.4.1 ; json + bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier From 815744b0f6eb204b4b46717df509940754ebcbc2 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 10:47:38 -0400 Subject: [PATCH 26/64] fix merge issues and clean up old comments --- esphome/components/json/json_util.cpp | 76 ++++++++++----------------- 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 51317df343..d4f268bc87 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,59 +26,41 @@ struct SpiRamAllocator : ArduinoJson::Allocator { }; std::string build_json(const json_build_t &f) { - // Here we are allocating up to 5kb of memory, - // with the heap size minus 2kb to be safe if less than 5kb - // as we can not have a true dynamic sized document. - // The excess memory is freed below with `shrinkToFit()` - while (true) { - auto doc_allocator = SpiRamAllocator(); - JsonDocument json_document(&doc_allocator); - if (json_document.overflowed()) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); - return "{}"; - } - JsonObject root = json_document.to(); - f(root); - if (json_document.overflowed()) { - if (request_size == free_heap) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); - return "{}"; - } - request_size = std::min(request_size * 2, free_heap); - continue; - } - std::string output; - serializeJson(json_document, output); - return output; + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return "{}"; } + JsonObject root = json_document.to(); + f(root); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return "{}"; + } + std::string output; + serializeJson(json_document, output); + return output; } bool parse_json(const std::string &data, const json_parse_t &f) { - // Here we are allocating 1.5 times the data size, - // with the heap size minus 2kb to be safe if less than that - // as we can not have a true dynamic sized document. - // The excess memory is freed below with `shrinkToFit()` - while (true) { - auto doc_allocator = SpiRamAllocator(); - JsonDocument json_document(&doc_allocator); - if (json_document.overflowed()) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); - return false; - } - DeserializationError err = deserializeJson(json_document, data); + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return false; + } + DeserializationError err = deserializeJson(json_document, data); - JsonObject root = json_document.as(); + JsonObject root = json_document.as(); - if (err == DeserializationError::Ok) { - return f(root); - } else if (err == DeserializationError::NoMemory) { - ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); - return false; - } else { - ESP_LOGE(TAG, "Parse error: %s", err.c_str()); - return false; - } - }; + if (err == DeserializationError::Ok) { + return f(root); + } else if (err == DeserializationError::NoMemory) { + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + return false; + } + ESP_LOGE(TAG, "Parse error: %s", err.c_str()); return false; } From 51eecac2de120364e41008919afb000bd0b8edae Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 11:43:31 -0400 Subject: [PATCH 27/64] testing a different approach --- esphome/components/web_server/web_server.cpp | 47 ++++++++++---------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 9b1bfa1a5e..52840b94b7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -436,7 +436,7 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail if (!obj->get_unit_of_measurement().empty()) state += " " + obj->get_unit_of_measurement(); } - set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); + set_json_icon_state_value(root, obj, ("sensor-" + obj->get_object_id()).c_str(), state, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); if (!obj->get_unit_of_measurement().empty()) @@ -476,7 +476,7 @@ std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, voi std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); + set_json_icon_state_value(root, obj, ("text_sensor-" + obj->get_object_id()).c_str(), value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -523,7 +523,8 @@ std::string WebServer::switch_all_json_generator(WebServer *web_server, void *so } std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); + set_json_icon_state_value(root, obj, ("switch-" + obj->get_object_id()).c_str(), value ? "ON" : "OFF", value, + start_config); if (start_config == DETAIL_ALL) { root["assumed_state"] = obj->assumed_state(); this->add_sorting_info_(root, obj); @@ -560,7 +561,7 @@ std::string WebServer::button_all_json_generator(WebServer *web_server, void *so } std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("button-" + obj->get_object_id()).c_str(), start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -597,7 +598,7 @@ std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, v } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, + set_json_icon_state_value(root, obj, ("binary_sensor-" + obj->get_object_id()).c_str(), value ? "ON" : "OFF", value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -671,7 +672,7 @@ std::string WebServer::fan_all_json_generator(WebServer *web_server, void *sourc } std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, + set_json_icon_state_value(root, obj, ("fan-" + obj->get_object_id()).c_str(), obj->state ? "ON" : "OFF", obj->state, start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { @@ -787,7 +788,7 @@ std::string WebServer::light_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("light-" + obj->get_object_id()).c_str(), start_config); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; light::LightJSONSchema::dump_json(*obj, root); @@ -869,8 +870,8 @@ std::string WebServer::cover_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", - obj->position, start_config); + set_json_icon_state_value(root, obj, ("cover-" + obj->get_object_id()).c_str(), + obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_position()) @@ -928,7 +929,7 @@ std::string WebServer::number_all_json_generator(WebServer *web_server, void *so } std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("number-" + obj->get_object_id()).c_str(), start_config); if (start_config == DETAIL_ALL) { root["min_value"] = value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); @@ -1003,7 +1004,7 @@ std::string WebServer::date_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("date-" + obj->get_object_id()).c_str(), start_config); std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); root["value"] = value; root["state"] = value; @@ -1061,7 +1062,7 @@ std::string WebServer::time_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "time-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("time-" + obj->get_object_id()).c_str(), start_config); std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); root["value"] = value; root["state"] = value; @@ -1119,7 +1120,7 @@ std::string WebServer::datetime_all_json_generator(WebServer *web_server, void * } std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("datetime-" + obj->get_object_id()).c_str(), start_config); std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); root["value"] = value; @@ -1174,7 +1175,7 @@ std::string WebServer::text_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_id(root, obj, "text-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("text-" + obj->get_object_id()).c_str(), start_config); root["min_length"] = obj->traits.get_min_length(); root["max_length"] = obj->traits.get_max_length(); root["pattern"] = obj->traits.get_pattern(); @@ -1236,7 +1237,7 @@ std::string WebServer::select_all_json_generator(WebServer *web_server, void *so } std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); + set_json_icon_state_value(root, obj, ("select-" + obj->get_object_id()).c_str(), value, value, start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root["option"].to(); for (auto &option : obj->traits.get_options()) { @@ -1323,7 +1324,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s } std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("climate-" + obj->get_object_id()).c_str(), start_config); const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); @@ -1449,8 +1450,8 @@ std::string WebServer::lock_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, - start_config); + set_json_icon_state_value(root, obj, ("lock-" + obj->get_object_id()).c_str(), lock::lock_state_to_string(value), + value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -1517,8 +1518,8 @@ std::string WebServer::valve_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", - obj->position, start_config); + set_json_icon_state_value(root, obj, ("valve-" + obj->get_object_id()).c_str(), + obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_position()) @@ -1589,7 +1590,7 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { char buf[16]; - set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), + set_json_icon_state_value(root, obj, ("alarm-control-panel-" + obj->get_object_id()).c_str(), PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1630,7 +1631,7 @@ std::string WebServer::event_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { return json::build_json([this, obj, event_type, start_config](JsonObject root) { - set_json_id(root, obj, "event-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("event-" + obj->get_object_id()).c_str(), start_config); if (!event_type.empty()) { root["event_type"] = event_type; } @@ -1683,7 +1684,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so } std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); + set_json_id(root, obj, ("update-" + obj->get_object_id()).c_str(), start_config); root["value"] = obj->update_info.latest_version; switch (obj->state) { case update::UPDATE_STATE_NO_UPDATE: From 0a8af3ec85da1c1fb73e450c4464816e8658ceb7 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 11:52:26 -0400 Subject: [PATCH 28/64] Revert "testing a different approach" This reverts commit 51eecac2de120364e41008919afb000bd0b8edae. --- esphome/components/web_server/web_server.cpp | 47 ++++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 52840b94b7..9b1bfa1a5e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -436,7 +436,7 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail if (!obj->get_unit_of_measurement().empty()) state += " " + obj->get_unit_of_measurement(); } - set_json_icon_state_value(root, obj, ("sensor-" + obj->get_object_id()).c_str(), state, value, start_config); + set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); if (!obj->get_unit_of_measurement().empty()) @@ -476,7 +476,7 @@ std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, voi std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("text_sensor-" + obj->get_object_id()).c_str(), value, value, start_config); + set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -523,8 +523,7 @@ std::string WebServer::switch_all_json_generator(WebServer *web_server, void *so } std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("switch-" + obj->get_object_id()).c_str(), value ? "ON" : "OFF", value, - start_config); + set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); if (start_config == DETAIL_ALL) { root["assumed_state"] = obj->assumed_state(); this->add_sorting_info_(root, obj); @@ -561,7 +560,7 @@ std::string WebServer::button_all_json_generator(WebServer *web_server, void *so } std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("button-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -598,7 +597,7 @@ std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, v } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("binary_sensor-" + obj->get_object_id()).c_str(), value ? "ON" : "OFF", value, + set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -672,7 +671,7 @@ std::string WebServer::fan_all_json_generator(WebServer *web_server, void *sourc } std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("fan-" + obj->get_object_id()).c_str(), obj->state ? "ON" : "OFF", obj->state, + set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { @@ -788,7 +787,7 @@ std::string WebServer::light_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("light-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; light::LightJSONSchema::dump_json(*obj, root); @@ -870,8 +869,8 @@ std::string WebServer::cover_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("cover-" + obj->get_object_id()).c_str(), - obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); + set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_position()) @@ -929,7 +928,7 @@ std::string WebServer::number_all_json_generator(WebServer *web_server, void *so } std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_id(root, obj, ("number-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); if (start_config == DETAIL_ALL) { root["min_value"] = value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); @@ -1004,7 +1003,7 @@ std::string WebServer::date_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("date-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); root["value"] = value; root["state"] = value; @@ -1062,7 +1061,7 @@ std::string WebServer::time_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("time-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "time-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); root["value"] = value; root["state"] = value; @@ -1120,7 +1119,7 @@ std::string WebServer::datetime_all_json_generator(WebServer *web_server, void * } std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("datetime-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); root["value"] = value; @@ -1175,7 +1174,7 @@ std::string WebServer::text_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_id(root, obj, ("text-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "text-" + obj->get_object_id(), start_config); root["min_length"] = obj->traits.get_min_length(); root["max_length"] = obj->traits.get_max_length(); root["pattern"] = obj->traits.get_pattern(); @@ -1237,7 +1236,7 @@ std::string WebServer::select_all_json_generator(WebServer *web_server, void *so } std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("select-" + obj->get_object_id()).c_str(), value, value, start_config); + set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root["option"].to(); for (auto &option : obj->traits.get_options()) { @@ -1324,7 +1323,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s } std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("climate-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); @@ -1450,8 +1449,8 @@ std::string WebServer::lock_all_json_generator(WebServer *web_server, void *sour } std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("lock-" + obj->get_object_id()).c_str(), lock::lock_state_to_string(value), - value, start_config); + set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, + start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -1518,8 +1517,8 @@ std::string WebServer::valve_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, ("valve-" + obj->get_object_id()).c_str(), - obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); + set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_position()) @@ -1590,7 +1589,7 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro JsonDetail start_config) { return json::build_json([this, obj, value, start_config](JsonObject root) { char buf[16]; - set_json_icon_state_value(root, obj, ("alarm-control-panel-" + obj->get_object_id()).c_str(), + set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1631,7 +1630,7 @@ std::string WebServer::event_all_json_generator(WebServer *web_server, void *sou } std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { return json::build_json([this, obj, event_type, start_config](JsonObject root) { - set_json_id(root, obj, ("event-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "event-" + obj->get_object_id(), start_config); if (!event_type.empty()) { root["event_type"] = event_type; } @@ -1684,7 +1683,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so } std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { return json::build_json([this, obj, start_config](JsonObject root) { - set_json_id(root, obj, ("update-" + obj->get_object_id()).c_str(), start_config); + set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); root["value"] = obj->update_info.latest_version; switch (obj->state) { case update::UPDATE_STATE_NO_UPDATE: From ab454e99288af430a011f17fc81ca94d2255ce11 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 11:54:02 -0400 Subject: [PATCH 29/64] explicitly define support for std::string --- esphome/components/json/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 9773bf67ce..ae626d177c 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -14,4 +14,5 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_define("USE_JSON") + cg.add_define("ARDUINOJSON_ENABLE_STD_STRING", "1") cg.add_global(json_ns.using) From de235b638abc5899b63c2aa7a68e0ee574346a62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 07:12:07 -1000 Subject: [PATCH 30/64] Fix LibreTiny compilation error by updating ESPAsyncWebServer to 3.7.10 --- esphome/components/async_tcp/__init__.py | 2 +- esphome/components/web_server_base/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 29097ce1b6..4a469fa0e0 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): if CORE.is_esp32 or CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP - cg.add_library("ESP32Async/AsyncTCP", "3.4.4") + cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: # https://github.com/ESP32Async/ESPAsyncTCP cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 754bf7d433..9f3371c233 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -40,4 +40,4 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json - cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.8") + cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10") From acb0fdc288d1730627836106f5ac67a1961631f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 07:16:41 -1000 Subject: [PATCH 31/64] one more dep --- esphome/components/json/__init__.py | 2 +- platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 6a0e4c50d2..9773bf67ce 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("bblanchon/ArduinoJson", "6.18.5") + cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/platformio.ini b/platformio.ini index 54c72eb28d..df2dfbe0ae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.10 ; api improv/Improv@1.2.4 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@6.18.5 ; json + bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier @@ -235,7 +235,7 @@ build_flags = -DUSE_ZEPHYR -DUSE_NRF52 lib_deps = - bblanchon/ArduinoJson@7.0.0 ; json + bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code pavlodn/HaierProtocol@0.9.31 ; haier functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 From f76cba0af64a46334992253291f38f67ef3609d0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:10:23 -1000 Subject: [PATCH 32/64] do not analyze platformio files --- script/clang-tidy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/clang-tidy b/script/clang-tidy index b5905e0e4e..7e51e3192b 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -152,7 +152,9 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): if sys.stdout.isatty(): invocation.append("--use-color") - invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") + invocation.append( + f"--header-filter=^{os.path.abspath(basepath)}/(?!.*\\.platformio/).*" + ) invocation.append(os.path.abspath(path)) invocation.append("--") invocation.extend(options) From 55a7926670247ca9ccc90821d2ec3ffa05878b08 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:13:45 -1000 Subject: [PATCH 33/64] do not analyze platformio files --- script/clang-tidy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/clang-tidy b/script/clang-tidy index 7e51e3192b..b068b194bf 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -152,6 +152,8 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): if sys.stdout.isatty(): invocation.append("--use-color") + # Exclude PlatformIO files from analysis since we can't fix issues in external code + # and they often generate false positives invocation.append( f"--header-filter=^{os.path.abspath(basepath)}/(?!.*\\.platformio/).*" ) From 33389f9c7fbfd4645f9d50ef492f6e2ffb45f2e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:45:06 -1000 Subject: [PATCH 34/64] Revert "do not analyze platformio files" This reverts commit 55a7926670247ca9ccc90821d2ec3ffa05878b08. --- script/clang-tidy | 2 -- 1 file changed, 2 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index b068b194bf..7e51e3192b 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -152,8 +152,6 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): if sys.stdout.isatty(): invocation.append("--use-color") - # Exclude PlatformIO files from analysis since we can't fix issues in external code - # and they often generate false positives invocation.append( f"--header-filter=^{os.path.abspath(basepath)}/(?!.*\\.platformio/).*" ) From aeb56cc3d0ea1ab1f34263048b4fe01334f242d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:45:15 -1000 Subject: [PATCH 35/64] Revert "do not analyze platformio files" This reverts commit f76cba0af64a46334992253291f38f67ef3609d0. --- script/clang-tidy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 7e51e3192b..b5905e0e4e 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -152,9 +152,7 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): if sys.stdout.isatty(): invocation.append("--use-color") - invocation.append( - f"--header-filter=^{os.path.abspath(basepath)}/(?!.*\\.platformio/).*" - ) + invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append(os.path.abspath(path)) invocation.append("--") invocation.extend(options) From 96d39403f497c232c4f8be261737fba2bff08ca0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:45:33 -1000 Subject: [PATCH 36/64] try another way --- script/clang-tidy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/clang-tidy b/script/clang-tidy index b5905e0e4e..934c454655 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -121,6 +121,8 @@ def clang_options(idedata): ) ) or (directory.startswith(f"{root_path}") and "/.pio/" in directory) + or "/.platformio/" + in directory # Also treat any .platformio directory as system ): cmd.extend(["-isystem", directory]) From 4f10a0ccf73e22b283cd0f5e3aa57e8f1368d382 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 08:53:32 -1000 Subject: [PATCH 37/64] more aggressive fix --- script/clang-tidy | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/clang-tidy b/script/clang-tidy index 934c454655..4fca12ab27 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -105,7 +105,7 @@ def clang_options(idedata): # idedata contains include directories for all toolchains of this platform, only use those from the one in use toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") for directory in idedata["includes"]["toolchain"]: - if directory.startswith(toolchain_dir): + if directory.startswith(toolchain_dir) or "/.platformio/" in directory: cmd.extend(["-isystem", directory]) # add library include directories using -isystem to suppress their errors @@ -129,6 +129,11 @@ def clang_options(idedata): # add the esphome include directory using -I cmd.extend(["-I", root_path]) + # Also ensure any remaining directories with .platformio are system includes + for i in range(len(cmd)): + if cmd[i] == "-I" and i + 1 < len(cmd) and "/.platformio/" in cmd[i + 1]: + cmd[i] = "-isystem" + return cmd From 6afda9d4dc44707bd79f1fc5afd64897d088f054 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 14:36:18 -0400 Subject: [PATCH 38/64] don't set string define --- esphome/components/json/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index ae626d177c..9773bf67ce 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -14,5 +14,4 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_define("USE_JSON") - cg.add_define("ARDUINOJSON_ENABLE_STD_STRING", "1") cg.add_global(json_ns.using) From 5e8f1d82c3ca4b039afec2824f1d93dcf974839d Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 14:56:51 -0400 Subject: [PATCH 39/64] specify data types --- .../update/http_request_update.cpp | 13 ++++---- .../components/light/light_json_schema.cpp | 30 +++++++++---------- esphome/components/mqtt/mqtt_date.cpp | 6 ++-- esphome/components/mqtt/mqtt_datetime.cpp | 12 ++++---- esphome/components/mqtt/mqtt_time.cpp | 6 ++-- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index eb2d1e68ef..5e6e7b75d9 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -83,7 +83,8 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { + if (!root["name"].is() || !root["version"].is() || + !root["builds"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } @@ -91,26 +92,26 @@ void HttpRequestUpdate::update_task(void *params) { this_update->update_info_.latest_version = root["version"].as(); for (auto build : root["builds"].as()) { - if (!build["chipFamily"].is()) { + if (!build["chipFamily"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build["ota"].is()) { + if (!build["ota"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } JsonObject ota = build["ota"].as(); - if (!ota["path"].is() || !ota["md5"].is()) { + if (!ota["path"].is() || !ota["md5"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } this_update->update_info_.firmware_url = ota["path"].as(); this_update->update_info_.md5 = ota["md5"].as(); - if (ota["summary"].is()) + if (ota["summary"].is()) this_update->update_info_.summary = ota["summary"].as(); - if (ota["release_url"].is()) + if (ota["release_url"].is()) this_update->update_info_.release_url = ota["release_url"].as(); return true; diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 8ecda4918e..c38ddb922d 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -73,7 +73,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { } void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { - if (root["state"].is()) { + if (root["state"].is()) { auto val = parse_on_off(root["state"]); switch (val) { case PARSE_ON: @@ -90,40 +90,40 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["brightness"].is()) { + if (root["brightness"].is()) { call.set_brightness(float(root["brightness"]) / 255.0f); } - if (root["color"].is()) { + if (root["color"].is()) { JsonObject color = root["color"]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; - if (color["r"].is()) { + if (color["r"].is()) { float r = float(color["r"]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } - if (color["g"].is()) { + if (color["g"].is()) { float g = float(color["g"]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } - if (color["b"].is()) { + if (color["b"].is()) { float b = float(color["b"]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } - if (color["r"].is() || color["g"].is() || color["b"].is()) { + if (color["r"].is() || color["g"].is() || color["b"].is()) { call.set_color_brightness(max_rgb); } - if (color["c"].is()) { + if (color["c"].is()) { call.set_cold_white(float(color["c"]) / 255.0f); } - if (color["w"].is()) { + if (color["w"].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color["c"].is()) { + if (color["c"].is()) { call.set_warm_white(float(color["w"]) / 255.0f); } else { call.set_white(float(color["w"]) / 255.0f); @@ -131,11 +131,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["white_value"].is()) { // legacy API + if (root["white_value"].is()) { // legacy API call.set_white(float(root["white_value"]) / 255.0f); } - if (root["color_temp"].is()) { + if (root["color_temp"].is()) { call.set_color_temperature(float(root["color_temp"])); } } @@ -143,17 +143,17 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); - if (root["flash"].is()) { + if (root["flash"].is()) { auto length = uint32_t(float(root["flash"]) * 1000); call.set_flash_length(length); } - if (root["transition"].is()) { + if (root["transition"].is()) { auto length = uint32_t(float(root["transition"]) * 1000); call.set_transition_length(length); } - if (root["effect"].is()) { + if (root["effect"].is()) { const char *effect = root["effect"]; call.set_effect(effect); } diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 7349e7c64a..e3506fae16 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -20,13 +20,13 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root["year"].is()) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root["month"].is()) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root["day"].is()) { + if (root["day"].is()) { call.set_day(root["day"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 44ce8ec8f2..f2c5e1d07a 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -20,22 +20,22 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root["year"].is()) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root["month"].is()) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root["day"].is()) { + if (root["day"].is()) { call.set_day(root["day"]); } - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index b49071c4fd..fbcb416ba0 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); From b95449615ff9d09fc2a9a0b2213144984995c988 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:21:36 -1000 Subject: [PATCH 40/64] Revert "more aggressive fix" This reverts commit 4f10a0ccf73e22b283cd0f5e3aa57e8f1368d382. --- script/clang-tidy | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 4fca12ab27..934c454655 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -105,7 +105,7 @@ def clang_options(idedata): # idedata contains include directories for all toolchains of this platform, only use those from the one in use toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") for directory in idedata["includes"]["toolchain"]: - if directory.startswith(toolchain_dir) or "/.platformio/" in directory: + if directory.startswith(toolchain_dir): cmd.extend(["-isystem", directory]) # add library include directories using -isystem to suppress their errors @@ -129,11 +129,6 @@ def clang_options(idedata): # add the esphome include directory using -I cmd.extend(["-I", root_path]) - # Also ensure any remaining directories with .platformio are system includes - for i in range(len(cmd)): - if cmd[i] == "-I" and i + 1 < len(cmd) and "/.platformio/" in cmd[i + 1]: - cmd[i] = "-isystem" - return cmd From 2057af83966fb44e21c24f5d0475d41bbd2849d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:21:42 -1000 Subject: [PATCH 41/64] Revert "try another way" This reverts commit 96d39403f497c232c4f8be261737fba2bff08ca0. --- script/clang-tidy | 2 -- 1 file changed, 2 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 934c454655..b5905e0e4e 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -121,8 +121,6 @@ def clang_options(idedata): ) ) or (directory.startswith(f"{root_path}") and "/.pio/" in directory) - or "/.platformio/" - in directory # Also treat any .platformio directory as system ): cmd.extend(["-isystem", directory]) From f8c45573f3375ea9379e5c6fe1267e6436bc9497 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:24:20 -1000 Subject: [PATCH 42/64] Refactor WebServer request handling for improved maintainability (#9470) --- esphome/components/web_server/web_server.cpp | 343 ++++++++----------- 1 file changed, 140 insertions(+), 203 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 8ced5b7e18..2aa6acde0e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1711,162 +1711,161 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c #endif bool WebServer::canHandle(AsyncWebServerRequest *request) const { - if (request->url() == "/") + const auto &url = request->url(); + const auto method = request->method(); + + // Simple URL checks + if (url == "/") return true; #ifdef USE_ARDUINO - if (request->url() == "/events") { + if (url == "/events") return true; - } #endif #ifdef USE_WEBSERVER_CSS_INCLUDE - if (request->url() == "/0.css") + if (url == "/0.css") return true; #endif #ifdef USE_WEBSERVER_JS_INCLUDE - if (request->url() == "/0.js") + if (url == "/0.js") return true; #endif #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { + if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) return true; - } #endif - // Store the URL to prevent temporary string destruction - // request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF) - // UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url() - const auto &url = request->url(); + // Parse URL for component checks UrlMatch match = match_url(url.c_str(), url.length(), true); if (!match.valid) return false; + + // Common pattern check + bool is_get = method == HTTP_GET; + bool is_post = method == HTTP_POST; + bool is_get_or_post = is_get || is_post; + + if (!is_get_or_post) + return false; + + // GET-only components + if (is_get) { #ifdef USE_SENSOR - if (request->method() == HTTP_GET && match.domain_equals("sensor")) - return true; + if (match.domain_equals("sensor")) + return true; #endif - -#ifdef USE_SWITCH - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("switch")) - return true; -#endif - -#ifdef USE_BUTTON - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("button")) - return true; -#endif - #ifdef USE_BINARY_SENSOR - if (request->method() == HTTP_GET && match.domain_equals("binary_sensor")) - return true; + if (match.domain_equals("binary_sensor")) + return true; #endif - -#ifdef USE_FAN - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("fan")) - return true; -#endif - -#ifdef USE_LIGHT - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("light")) - return true; -#endif - #ifdef USE_TEXT_SENSOR - if (request->method() == HTTP_GET && match.domain_equals("text_sensor")) - return true; + if (match.domain_equals("text_sensor")) + return true; #endif - -#ifdef USE_COVER - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("cover")) - return true; -#endif - -#ifdef USE_NUMBER - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("number")) - return true; -#endif - -#ifdef USE_DATETIME_DATE - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("date")) - return true; -#endif - -#ifdef USE_DATETIME_TIME - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("time")) - return true; -#endif - -#ifdef USE_DATETIME_DATETIME - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("datetime")) - return true; -#endif - -#ifdef USE_TEXT - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("text")) - return true; -#endif - -#ifdef USE_SELECT - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("select")) - return true; -#endif - -#ifdef USE_CLIMATE - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("climate")) - return true; -#endif - -#ifdef USE_LOCK - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("lock")) - return true; -#endif - -#ifdef USE_VALVE - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("valve")) - return true; -#endif - -#ifdef USE_ALARM_CONTROL_PANEL - if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain_equals("alarm_control_panel")) - return true; -#endif - #ifdef USE_EVENT - if (request->method() == HTTP_GET && match.domain_equals("event")) - return true; + if (match.domain_equals("event")) + return true; #endif + } -#ifdef USE_UPDATE - if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("update")) - return true; + // GET+POST components + if (is_get_or_post) { +#ifdef USE_SWITCH + if (match.domain_equals("switch")) + return true; #endif +#ifdef USE_BUTTON + if (match.domain_equals("button")) + return true; +#endif +#ifdef USE_FAN + if (match.domain_equals("fan")) + return true; +#endif +#ifdef USE_LIGHT + if (match.domain_equals("light")) + return true; +#endif +#ifdef USE_COVER + if (match.domain_equals("cover")) + return true; +#endif +#ifdef USE_NUMBER + if (match.domain_equals("number")) + return true; +#endif +#ifdef USE_DATETIME_DATE + if (match.domain_equals("date")) + return true; +#endif +#ifdef USE_DATETIME_TIME + if (match.domain_equals("time")) + return true; +#endif +#ifdef USE_DATETIME_DATETIME + if (match.domain_equals("datetime")) + return true; +#endif +#ifdef USE_TEXT + if (match.domain_equals("text")) + return true; +#endif +#ifdef USE_SELECT + if (match.domain_equals("select")) + return true; +#endif +#ifdef USE_CLIMATE + if (match.domain_equals("climate")) + return true; +#endif +#ifdef USE_LOCK + if (match.domain_equals("lock")) + return true; +#endif +#ifdef USE_VALVE + if (match.domain_equals("valve")) + return true; +#endif +#ifdef USE_ALARM_CONTROL_PANEL + if (match.domain_equals("alarm_control_panel")) + return true; +#endif +#ifdef USE_UPDATE + if (match.domain_equals("update")) + return true; +#endif + } return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (request->url() == "/") { + const auto &url = request->url(); + + // Handle static routes first + if (url == "/") { this->handle_index_request(request); return; } #ifdef USE_ARDUINO - if (request->url() == "/events") { + if (url == "/events") { this->events_.add_new_client(this, request); return; } #endif #ifdef USE_WEBSERVER_CSS_INCLUDE - if (request->url() == "/0.css") { + if (url == "/0.css") { this->handle_css_request(request); return; } #endif #ifdef USE_WEBSERVER_JS_INCLUDE - if (request->url() == "/0.js") { + if (url == "/0.js") { this->handle_js_request(request); return; } @@ -1879,147 +1878,85 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif - // See comment in canHandle() for why we store the URL reference - const auto &url = request->url(); + // Parse URL for component routing UrlMatch match = match_url(url.c_str(), url.length(), false); + // Component routing using minimal code repetition + struct ComponentRoute { + const char *domain; + void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &); + }; + + static const ComponentRoute routes[] = { #ifdef USE_SENSOR - if (match.domain_equals("sensor")) { - this->handle_sensor_request(request, match); - return; - } + {"sensor", &WebServer::handle_sensor_request}, #endif - #ifdef USE_SWITCH - if (match.domain_equals("switch")) { - this->handle_switch_request(request, match); - return; - } + {"switch", &WebServer::handle_switch_request}, #endif - #ifdef USE_BUTTON - if (match.domain_equals("button")) { - this->handle_button_request(request, match); - return; - } + {"button", &WebServer::handle_button_request}, #endif - #ifdef USE_BINARY_SENSOR - if (match.domain_equals("binary_sensor")) { - this->handle_binary_sensor_request(request, match); - return; - } + {"binary_sensor", &WebServer::handle_binary_sensor_request}, #endif - #ifdef USE_FAN - if (match.domain_equals("fan")) { - this->handle_fan_request(request, match); - return; - } + {"fan", &WebServer::handle_fan_request}, #endif - #ifdef USE_LIGHT - if (match.domain_equals("light")) { - this->handle_light_request(request, match); - return; - } + {"light", &WebServer::handle_light_request}, #endif - #ifdef USE_TEXT_SENSOR - if (match.domain_equals("text_sensor")) { - this->handle_text_sensor_request(request, match); - return; - } + {"text_sensor", &WebServer::handle_text_sensor_request}, #endif - #ifdef USE_COVER - if (match.domain_equals("cover")) { - this->handle_cover_request(request, match); - return; - } + {"cover", &WebServer::handle_cover_request}, #endif - #ifdef USE_NUMBER - if (match.domain_equals("number")) { - this->handle_number_request(request, match); - return; - } + {"number", &WebServer::handle_number_request}, #endif - #ifdef USE_DATETIME_DATE - if (match.domain_equals("date")) { - this->handle_date_request(request, match); - return; - } + {"date", &WebServer::handle_date_request}, #endif - #ifdef USE_DATETIME_TIME - if (match.domain_equals("time")) { - this->handle_time_request(request, match); - return; - } + {"time", &WebServer::handle_time_request}, #endif - #ifdef USE_DATETIME_DATETIME - if (match.domain_equals("datetime")) { - this->handle_datetime_request(request, match); - return; - } + {"datetime", &WebServer::handle_datetime_request}, #endif - #ifdef USE_TEXT - if (match.domain_equals("text")) { - this->handle_text_request(request, match); - return; - } + {"text", &WebServer::handle_text_request}, #endif - #ifdef USE_SELECT - if (match.domain_equals("select")) { - this->handle_select_request(request, match); - return; - } + {"select", &WebServer::handle_select_request}, #endif - #ifdef USE_CLIMATE - if (match.domain_equals("climate")) { - this->handle_climate_request(request, match); - return; - } + {"climate", &WebServer::handle_climate_request}, #endif - #ifdef USE_LOCK - if (match.domain_equals("lock")) { - this->handle_lock_request(request, match); - - return; - } + {"lock", &WebServer::handle_lock_request}, #endif - #ifdef USE_VALVE - if (match.domain_equals("valve")) { - this->handle_valve_request(request, match); - return; - } + {"valve", &WebServer::handle_valve_request}, #endif - #ifdef USE_ALARM_CONTROL_PANEL - if (match.domain_equals("alarm_control_panel")) { - this->handle_alarm_control_panel_request(request, match); - - return; - } + {"alarm_control_panel", &WebServer::handle_alarm_control_panel_request}, #endif - #ifdef USE_UPDATE - if (match.domain_equals("update")) { - this->handle_update_request(request, match); - return; - } + {"update", &WebServer::handle_update_request}, #endif + }; + + // Check each route + for (const auto &route : routes) { + if (match.domain_equals(route.domain)) { + (this->*route.handler)(request, match); + return; + } + } // No matching handler found - send 404 - ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str()); + ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str()); request->send(404, "text/plain", "Not Found"); } From 808066f5643ed71e8db9a45ff250eabb53dd7c52 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:24:38 -1000 Subject: [PATCH 43/64] no real good option but to disable them all manually --- esphome/components/json/json_util.cpp | 2 ++ esphome/components/light/light_json_schema.cpp | 1 + esphome/components/mqtt/mqtt_client.cpp | 1 + esphome/components/mqtt/mqtt_component.cpp | 1 + 4 files changed, 5 insertions(+) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index d4f268bc87..e82a5db2e3 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -32,6 +32,7 @@ std::string build_json(const json_build_t &f) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; } + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonObject root = json_document.to(); f(root); if (json_document.overflowed()) { @@ -50,6 +51,7 @@ bool parse_json(const std::string &data, const json_parse_t &f) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; } + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson DeserializationError err = deserializeJson(json_document, data); JsonObject root = json_document.as(); diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index c38ddb922d..44f744c31a 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -9,6 +9,7 @@ namespace light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema void LightJSONSchema::dump_json(LightState &state, JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) root["effect"] = state.get_effect_name(); diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index ab7fd15a35..804cb3be31 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -95,6 +95,7 @@ void MQTTClientComponent::send_device_info_() { this->publish_json( topic, [](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson uint8_t index = 0; for (auto &ip : network::get_ip_addresses()) { if (ip.is_set()) { diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index a98892aa24..685c0e6c6c 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -73,6 +73,7 @@ bool MQTTComponent::send_discovery_() { return global_mqtt_client->publish_json( this->get_discovery_topic_(discovery_info), [this](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson SendDiscoveryConfig config; config.state_topic = true; config.command_topic = true; From b9cb6909867066900facd2f20fa7347acbb59582 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 15:28:36 -0400 Subject: [PATCH 44/64] use better types --- .../components/light/light_json_schema.cpp | 22 +++++++++---------- esphome/components/mqtt/mqtt_date.cpp | 6 ++--- esphome/components/mqtt/mqtt_datetime.cpp | 12 +++++----- esphome/components/mqtt/mqtt_time.cpp | 6 ++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 44f744c31a..5742088e4f 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -91,7 +91,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["brightness"].is()) { + if (root["brightness"].is()) { call.set_brightness(float(root["brightness"]) / 255.0f); } @@ -99,32 +99,32 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO JsonObject color = root["color"]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; - if (color["r"].is()) { + if (color["r"].is()) { float r = float(color["r"]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } - if (color["g"].is()) { + if (color["g"].is()) { float g = float(color["g"]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } - if (color["b"].is()) { + if (color["b"].is()) { float b = float(color["b"]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } - if (color["r"].is() || color["g"].is() || color["b"].is()) { + if (color["r"].is() || color["g"].is() || color["b"].is()) { call.set_color_brightness(max_rgb); } - if (color["c"].is()) { + if (color["c"].is()) { call.set_cold_white(float(color["c"]) / 255.0f); } - if (color["w"].is()) { + if (color["w"].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color["c"].is()) { + if (color["c"].is()) { call.set_warm_white(float(color["w"]) / 255.0f); } else { call.set_white(float(color["w"]) / 255.0f); @@ -132,11 +132,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["white_value"].is()) { // legacy API + if (root["white_value"].is()) { // legacy API call.set_white(float(root["white_value"]) / 255.0f); } - if (root["color_temp"].is()) { + if (root["color_temp"].is()) { call.set_color_temperature(float(root["color_temp"])); } } @@ -149,7 +149,7 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject call.set_flash_length(length); } - if (root["transition"].is()) { + if (root["transition"].is()) { auto length = uint32_t(float(root["transition"]) * 1000); call.set_transition_length(length); } diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index e3506fae16..5a0c6eccd8 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -20,13 +20,13 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root["year"].is()) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root["month"].is()) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root["day"].is()) { + if (root["day"].is()) { call.set_day(root["day"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index f2c5e1d07a..6d3e162f1f 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -20,22 +20,22 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root["year"].is()) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root["month"].is()) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root["day"].is()) { + if (root["day"].is()) { call.set_day(root["day"]); } - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index fbcb416ba0..e5bdc84c7e 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); From a7e74bb7debe52b7c637366c7d2ad05b1372485a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:30:43 -1000 Subject: [PATCH 45/64] no real good option but to disable them all manually --- esphome/components/mqtt/mqtt_alarm_control_panel.cpp | 1 + esphome/components/mqtt/mqtt_binary_sensor.cpp | 1 + esphome/components/mqtt/mqtt_button.cpp | 1 + esphome/components/mqtt/mqtt_climate.cpp | 2 ++ esphome/components/mqtt/mqtt_cover.cpp | 1 + esphome/components/mqtt/mqtt_date.cpp | 1 + esphome/components/mqtt/mqtt_datetime.cpp | 1 + esphome/components/mqtt/mqtt_event.cpp | 7 +++++-- esphome/components/mqtt/mqtt_fan.cpp | 1 + esphome/components/mqtt/mqtt_lock.cpp | 1 + 10 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 9e1d283504..94460c31a7 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -55,6 +55,7 @@ void MQTTAlarmControlPanelComponent::dump_config() { } void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); if (acp_supported_features & ACP_FEAT_ARM_AWAY) { diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 6d12e88391..2ce4928574 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -30,6 +30,7 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor } void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->binary_sensor_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 204f60fe67..6dfdf649cb 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -32,6 +32,7 @@ void MQTTButtonComponent::dump_config() { void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { config.state_topic = false; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 9890654c04..1eeb01ee05 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -14,6 +14,7 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { @@ -28,6 +29,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // mode_state_topic root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 8d09d836f3..6fb61ee469 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -67,6 +67,7 @@ void MQTTCoverComponent::dump_config() { } } void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->cover_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index e3506fae16..f454022f53 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -55,6 +55,7 @@ bool MQTTDateComponent::send_initial_state() { } bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["year"] = year; root["month"] = month; root["day"] = day; diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index f2c5e1d07a..6315317e1a 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -68,6 +68,7 @@ bool MQTTDateTimeComponent::send_initial_state() { bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["year"] = year; root["month"] = month; root["day"] = day; diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index e459ba9d5b..f972d545c6 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -16,6 +16,7 @@ using namespace esphome::event; MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {} void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray event_types = root[MQTT_EVENT_TYPES].to(); for (const auto &event_type : this->event_->get_event_types()) event_types.add(event_type); @@ -40,8 +41,10 @@ void MQTTEventComponent::dump_config() { } bool MQTTEventComponent::publish_event_(const std::string &event_type) { - return this->publish_json(this->get_state_topic_(), - [event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; }); + return this->publish_json(this->get_state_topic_(), [event_type](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + root[MQTT_EVENT_TYPE] = event_type; + }); } std::string MQTTEventComponent::component_type() const { return "event"; } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 35713bdab6..fa17b53c3b 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -144,6 +144,7 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_direction()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic(); root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic(); } diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index f4a5126d0c..b8fe3aa559 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -39,6 +39,7 @@ std::string MQTTLockComponent::component_type() const { return "lock"; } const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->lock_->traits.get_assumed_state()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; if (this->lock_->traits.get_supports_open()) root[MQTT_PAYLOAD_OPEN] = "OPEN"; From 238909c0dee7c44997618075565234b3027033bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:31:22 -1000 Subject: [PATCH 46/64] no real good option but to disable them all manually --- esphome/components/mqtt/mqtt_light.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 988d582453..0bb0ea661a 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -32,12 +32,15 @@ void MQTTJSONLightComponent::setup() { MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} bool MQTTJSONLightComponent::publish_state_() { - return this->publish_json(this->get_state_topic_(), - [this](JsonObject root) { LightJSONSchema::dump_json(*this->state_, root); }); + return this->publish_json(this->get_state_topic_(), [this](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + LightJSONSchema::dump_json(*this->state_, root); + }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["schema"] = "json"; auto traits = this->state_->get_traits(); From ee5242ec8d764743d1f64b350c04fbc35b824019 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:33:33 -1000 Subject: [PATCH 47/64] no real good option but to disable them all manually --- esphome/components/mqtt/mqtt_number.cpp | 1 + esphome/components/mqtt/mqtt_select.cpp | 1 + esphome/components/mqtt/mqtt_sensor.cpp | 1 + esphome/components/mqtt/mqtt_switch.cpp | 1 + esphome/components/mqtt/mqtt_text.cpp | 1 + esphome/components/mqtt/mqtt_text_sensor.cpp | 1 + esphome/components/mqtt/mqtt_time.cpp | 1 + esphome/components/mqtt/mqtt_update.cpp | 1 + esphome/components/mqtt/mqtt_valve.cpp | 1 + 9 files changed, 9 insertions(+) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 3a6ea97967..a44632ff30 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -40,6 +40,7 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 99b9b0168f..b851348306 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -35,6 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray options = root[MQTT_OPTIONS].to(); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 2cbc291ccf..455faed45c 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -45,6 +45,7 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); if (!this->sensor_->get_unit_of_measurement().empty()) diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 3fd578825a..35da350b56 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -46,6 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->switch_->assumed_state()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index cb852b64cd..fc4a54a24d 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -39,6 +39,7 @@ void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfi root[MQTT_MODE] = "text"; break; case TEXT_MODE_PASSWORD: + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MODE] = "password"; break; } diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index b0754bc8b3..8ee0bdb478 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -16,6 +16,7 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index e5bdc84c7e..a19c16bb21 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -55,6 +55,7 @@ bool MQTTTimeComponent::send_initial_state() { } bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["hour"] = hour; root["minute"] = minute; root["second"] = second; diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 2ed8faf074..5d4807c7f3 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -41,6 +41,7 @@ bool MQTTUpdateComponent::publish_state() { } void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["schema"] = "json"; root[MQTT_PAYLOAD_INSTALL] = "INSTALL"; } diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 85e06fe79c..59761f87a8 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -50,6 +50,7 @@ void MQTTValveComponent::dump_config() { } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (!this->valve_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); auto traits = this->valve_->get_traits(); From b47f9158b2eedb76d83303a14f0ce7ec39e13537 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 15:47:22 -0400 Subject: [PATCH 48/64] fix a few wrong types --- esphome/components/light/light_json_schema.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 5742088e4f..26615bae5c 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -121,10 +121,10 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO if (color["c"].is()) { call.set_cold_white(float(color["c"]) / 255.0f); } - if (color["w"].is()) { + if (color["w"].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color["c"].is()) { + if (color["c"].is()) { call.set_warm_white(float(color["w"]) / 255.0f); } else { call.set_white(float(color["w"]) / 255.0f); @@ -132,7 +132,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["white_value"].is()) { // legacy API + if (root["white_value"].is()) { // legacy API call.set_white(float(root["white_value"]) / 255.0f); } @@ -144,7 +144,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); - if (root["flash"].is()) { + if (root["flash"].is()) { auto length = uint32_t(float(root["flash"]) * 1000); call.set_flash_length(length); } From 85351bb95242959eb70db232a6900459ed9f5833 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 09:50:29 -1000 Subject: [PATCH 49/64] a few more --- esphome/components/json/json_util.cpp | 2 ++ esphome/components/mqtt/mqtt_component.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index e82a5db2e3..d92a7a9794 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,6 +26,7 @@ struct SpiRamAllocator : ArduinoJson::Allocator { }; std::string build_json(const json_build_t &f) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); if (json_document.overflowed()) { @@ -45,6 +46,7 @@ std::string build_json(const json_build_t &f) { } bool parse_json(const std::string &data, const json_parse_t &f) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); if (json_document.overflowed()) { diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 685c0e6c6c..5f00bfddf6 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -189,6 +189,7 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area; } + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson device_info[MQTT_DEVICE_CONNECTIONS][0][0] = "mac"; device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac; }, From d3ab7f320e76eab9f127fe63d273a377e6986b09 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 15:52:40 -0400 Subject: [PATCH 50/64] a few missing nolint messages --- esphome/components/json/json_util.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index e82a5db2e3..0b10392442 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -28,6 +28,7 @@ struct SpiRamAllocator : ArduinoJson::Allocator { std::string build_json(const json_build_t &f) { auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; @@ -35,6 +36,7 @@ std::string build_json(const json_build_t &f) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonObject root = json_document.to(); f(root); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; @@ -47,6 +49,7 @@ std::string build_json(const json_build_t &f) { bool parse_json(const std::string &data, const json_parse_t &f) { auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; From 8a3cb32531de52760a830ae4dabe1585fa7a055d Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 15:57:01 -0400 Subject: [PATCH 51/64] a few formatting things --- esphome/components/mqtt/mqtt_lock.cpp | 3 ++- esphome/components/mqtt/mqtt_sensor.cpp | 3 ++- esphome/components/mqtt/mqtt_switch.cpp | 3 ++- esphome/components/mqtt/mqtt_text_sensor.cpp | 3 ++- esphome/components/mqtt/mqtt_valve.cpp | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index b8fe3aa559..d01309343c 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -38,9 +38,10 @@ void MQTTLockComponent::dump_config() { std::string MQTTLockComponent::component_type() const { return "lock"; } const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (this->lock_->traits.get_assumed_state()) + if (this->lock_->traits.get_assumed_state()) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; + } if (this->lock_->traits.get_supports_open()) root[MQTT_PAYLOAD_OPEN] = "OPEN"; } diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 455faed45c..b49f026e80 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -44,9 +44,10 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_device_class().empty()) + if (!this->sensor_->get_device_class().empty()) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + } if (!this->sensor_->get_unit_of_measurement().empty()) root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 35da350b56..8c7a875c78 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -45,9 +45,10 @@ void MQTTSwitchComponent::dump_config() { std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (this->switch_->assumed_state()) + if (this->switch_->assumed_state()) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; + } } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 8ee0bdb478..a65f7b8e91 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -15,9 +15,10 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_device_class().empty()) + if (!this->sensor_->get_device_class().empty()) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + } config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 59761f87a8..8506db27cf 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -49,9 +49,10 @@ void MQTTValveComponent::dump_config() { } } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->valve_->get_device_class().empty()) + if (!this->valve_->get_device_class().empty()) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); + } auto traits = this->valve_->get_traits(); if (traits.get_is_assumed_state()) { From 4b3393ce645947d5fff6b49ad9bc7deb1fb7d78f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 10:12:46 -1000 Subject: [PATCH 52/64] location fixes --- esphome/components/mqtt/mqtt_button.cpp | 2 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_lock.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_switch.cpp | 2 +- esphome/components/mqtt/mqtt_text.cpp | 2 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_valve.cpp | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 6dfdf649cb..e9f81dafcd 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -31,8 +31,8 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - config.state_topic = false; // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + config.state_topic = false; if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index fa17b53c3b..70e1ae3b4a 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -143,8 +143,8 @@ void MQTTFanComponent::dump_config() { bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (this->state_->get_traits().supports_direction()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic(); root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic(); } diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index d01309343c..0412624983 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -38,8 +38,8 @@ void MQTTLockComponent::dump_config() { std::string MQTTLockComponent::component_type() const { return "lock"; } const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (this->lock_->traits.get_assumed_state()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; } if (this->lock_->traits.get_supports_open()) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index a44632ff30..1217dd56a7 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -38,9 +38,9 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b851348306..c1c958b9e7 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -33,9 +33,9 @@ std::string MQTTSelectComponent::component_type() const { return "select"; } const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray options = root[MQTT_OPTIONS].to(); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index b49f026e80..9324ea9bb1 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -44,8 +44,8 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->sensor_->get_device_class().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); } diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 8c7a875c78..8b1323bdb2 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -45,8 +45,8 @@ void MQTTSwitchComponent::dump_config() { std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (this->switch_->assumed_state()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_OPTIMISTIC] = true; } } diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index fc4a54a24d..5ab0ca9688 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -34,12 +34,12 @@ std::string MQTTTextComponent::component_type() const { return "text"; } const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson switch (this->text_->traits.get_mode()) { case TEXT_MODE_TEXT: root[MQTT_MODE] = "text"; break; case TEXT_MODE_PASSWORD: - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MODE] = "password"; break; } diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index a65f7b8e91..0cc5de07a3 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -15,8 +15,8 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->sensor_->get_device_class().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); } config.command_topic = false; diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 8506db27cf..551398cf42 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -49,8 +49,8 @@ void MQTTValveComponent::dump_config() { } } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->valve_->get_device_class().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); } From f78e71c86a66fb37d4fcc1420a6247481555b494 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 10:13:24 -1000 Subject: [PATCH 53/64] Fix WebServer routes constant naming convention (#9497) --- esphome/components/web_server/web_server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 2aa6acde0e..da5c6b7cd7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1887,7 +1887,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &); }; - static const ComponentRoute routes[] = { + static const ComponentRoute ROUTES[] = { #ifdef USE_SENSOR {"sensor", &WebServer::handle_sensor_request}, #endif @@ -1948,7 +1948,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { }; // Check each route - for (const auto &route : routes) { + for (const auto &route : ROUTES) { if (match.domain_equals(route.domain)) { (this->*route.handler)(request, match); return; From 40d436746c8e0fb633e5bea51046d9a241ff7fcc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 10:28:10 -1000 Subject: [PATCH 54/64] webserver needs as well --- esphome/components/web_server/web_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b88615ce47..61e749e059 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1322,6 +1322,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); } std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); From 13ceda899b95b47110da15c9fa625c92f02a5ba1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 11:00:25 -1000 Subject: [PATCH 55/64] add some more , rearrange --- esphome/components/mqtt/mqtt_client.cpp | 2 +- esphome/components/mqtt/mqtt_component.cpp | 2 +- esphome/components/web_server/web_server.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 804cb3be31..3bd485a386 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -92,10 +92,10 @@ void MQTTClientComponent::send_device_info_() { std::string topic = "esphome/discover/"; topic.append(App.get_name()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson this->publish_json( topic, [](JsonObject root) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson uint8_t index = 0; for (auto &ip : network::get_ip_addresses()) { if (ip.is_set()) { diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 5f00bfddf6..9eb8d6175d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -70,10 +70,10 @@ bool MQTTComponent::send_discovery_() { ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return global_mqtt_client->publish_json( this->get_discovery_topic_(discovery_info), [this](JsonObject root) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson SendDiscoveryConfig config; config.state_topic = true; config.command_topic = true; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 61e749e059..01643858ba 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1683,6 +1683,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); } std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); root["value"] = obj->update_info.latest_version; From d6e05061f88ec1f7f430a9265b42553dc1b1918c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 11:19:49 -1000 Subject: [PATCH 56/64] a few more --- esphome/components/mqtt/mqtt_button.cpp | 5 +++-- esphome/components/mqtt/mqtt_client.cpp | 1 + esphome/components/mqtt/mqtt_climate.cpp | 2 +- esphome/components/mqtt/mqtt_component.cpp | 1 + esphome/components/mqtt/mqtt_light.cpp | 1 + esphome/components/mqtt/mqtt_number.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/web_server/web_server.cpp | 8 ++++++++ 8 files changed, 17 insertions(+), 5 deletions(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index e9f81dafcd..bf96f2343b 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -31,10 +31,11 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson config.state_topic = false; - if (!this->button_->get_device_class().empty()) + if (!this->button_->get_device_class().empty()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); + } } std::string MQTTButtonComponent::component_type() const { return "button"; } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 3bd485a386..9ab903a2d9 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -103,6 +103,7 @@ void MQTTClientComponent::send_device_info_() { index++; } } + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["name"] = App.get_name(); if (!App.get_friendly_name().empty()) { root["friendly_name"] = App.get_friendly_name(); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 1eeb01ee05..4477e6afe3 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -14,10 +14,10 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } // current_humidity_topic diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 9eb8d6175d..adb24517a7 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -81,6 +81,7 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Set subscription QoS (default is 0) if (this->subscribe_qos_ != 0) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_QOS] = this->subscribe_qos_; } diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 0bb0ea661a..4f5ff408a4 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -45,6 +45,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray color_modes = root["supported_color_modes"].to(); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 1217dd56a7..a44632ff30 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -38,9 +38,9 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index c1c958b9e7..b851348306 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -33,9 +33,9 @@ std::string MQTTSelectComponent::component_type() const { return "select"; } const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray options = root[MQTT_OPTIONS].to(); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 01643858ba..1579ccd730 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1324,6 +1324,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); @@ -1331,31 +1332,37 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf char buf[16]; if (start_config == DETAIL_ALL) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["modes"].to(); for (climate::ClimateMode m : traits.get_supported_modes()) opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); if (!traits.get_supported_custom_fan_modes().empty()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["fan_modes"].to(); for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["custom_fan_modes"].to(); for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) opt.add(custom_fan_mode); } if (traits.get_supports_swing_modes()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["swing_modes"].to(); for (auto swing_mode : traits.get_supported_swing_modes()) opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); } if (traits.get_supports_presets() && obj->preset.has_value()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["presets"].to(); for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["custom_presets"].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); @@ -1685,6 +1692,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); root["value"] = obj->update_info.latest_version; switch (obj->state) { From 1778776b7369f2bae3a201deaad6068ce0672c6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 11:30:43 -1000 Subject: [PATCH 57/64] use NOLINTBEGIN/NOLINTEND for the multi occ cases --- esphome/components/json/json_util.cpp | 11 ++++------- esphome/components/mqtt/mqtt_client.cpp | 4 ++-- esphome/components/mqtt/mqtt_component.cpp | 5 ++--- esphome/components/web_server/web_server.cpp | 14 ++++---------- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 29e2a65bbd..1ff5105137 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,18 +26,15 @@ struct SpiRamAllocator : ArduinoJson::Allocator { }; std::string build_json(const json_build_t &f) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; } - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonObject root = json_document.to(); f(root); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return "{}"; @@ -45,18 +42,17 @@ std::string build_json(const json_build_t &f) { std::string output; serializeJson(json_document, output); return output; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } bool parse_json(const std::string &data, const json_parse_t &f) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto doc_allocator = SpiRamAllocator(); JsonDocument json_document(&doc_allocator); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (json_document.overflowed()) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return false; } - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson DeserializationError err = deserializeJson(json_document, data); JsonObject root = json_document.as(); @@ -69,6 +65,7 @@ bool parse_json(const std::string &data, const json_parse_t &f) { } ESP_LOGE(TAG, "Parse error: %s", err.c_str()); return false; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } } // namespace json diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 9ab903a2d9..5b93789447 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -92,7 +92,7 @@ void MQTTClientComponent::send_device_info_() { std::string topic = "esphome/discover/"; topic.append(App.get_name()); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson this->publish_json( topic, [](JsonObject root) { @@ -103,7 +103,6 @@ void MQTTClientComponent::send_device_info_() { index++; } } - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["name"] = App.get_name(); if (!App.get_friendly_name().empty()) { root["friendly_name"] = App.get_friendly_name(); @@ -149,6 +148,7 @@ void MQTTClientComponent::send_device_info_() { #endif }, 2, this->discovery_info_.retain); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } void MQTTClientComponent::dump_config() { diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index adb24517a7..b51f4d903e 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -70,7 +70,7 @@ bool MQTTComponent::send_discovery_() { ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str()); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return global_mqtt_client->publish_json( this->get_discovery_topic_(discovery_info), [this](JsonObject root) { @@ -81,7 +81,6 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Set subscription QoS (default is 0) if (this->subscribe_qos_ != 0) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_QOS] = this->subscribe_qos_; } @@ -190,11 +189,11 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area; } - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson device_info[MQTT_DEVICE_CONNECTIONS][0][0] = "mac"; device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac; }, this->qos_, discovery_info.retain); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } uint8_t MQTTComponent::get_qos() const { return this->qos_; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1579ccd730..9ec667dbc5 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1322,9 +1322,8 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); } std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); @@ -1332,37 +1331,31 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf char buf[16]; if (start_config == DETAIL_ALL) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["modes"].to(); for (climate::ClimateMode m : traits.get_supported_modes()) opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); if (!traits.get_supported_custom_fan_modes().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["fan_modes"].to(); for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["custom_fan_modes"].to(); for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) opt.add(custom_fan_mode); } if (traits.get_supports_swing_modes()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["swing_modes"].to(); for (auto swing_mode : traits.get_supported_swing_modes()) opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); } if (traits.get_supports_presets() && obj->preset.has_value()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["presets"].to(); for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray opt = root["custom_presets"].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); @@ -1415,6 +1408,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf root["state"] = root["target_temperature"]; } }); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } #endif @@ -1690,9 +1684,8 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); } std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); root["value"] = obj->update_info.latest_version; switch (obj->state) { @@ -1717,6 +1710,7 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c this->add_sorting_info_(root, obj); } }); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } #endif From a714e8da0b75bdab2b54e0eeff89617882d76147 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 11:52:11 -1000 Subject: [PATCH 58/64] last ones --- esphome/components/mqtt/mqtt_button.cpp | 3 ++- esphome/components/mqtt/mqtt_climate.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index bf96f2343b..c619a02344 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -31,11 +31,12 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson config.state_topic = false; if (!this->button_->get_device_class().empty()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } std::string MQTTButtonComponent::component_type() const { return "button"; } diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 4477e6afe3..e16f097812 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -14,10 +14,10 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } // current_humidity_topic @@ -29,7 +29,6 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // mode_state_topic root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) @@ -165,6 +164,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo config.state_topic = false; config.command_topic = false; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } void MQTTClimateComponent::setup() { auto traits = this->device_->get_traits(); From 52d680161871d007e1c6c8575a452b765fe24b8b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 12:29:40 -1000 Subject: [PATCH 59/64] address bot comments --- .../http_request/update/http_request_update.cpp | 3 +-- esphome/components/json/json_util.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 5e6e7b75d9..06aa6da6a4 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -83,8 +83,7 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root["name"].is() || !root["version"].is() || - !root["builds"].is()) { + if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 1ff5105137..5f47444582 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,7 +1,7 @@ #include "json_util.h" #include "esphome/core/log.h" -#include +// ArduinoJson::Allocator is included via ArduinoJson.h in json_util.h namespace esphome { namespace json { @@ -13,7 +13,11 @@ struct SpiRamAllocator : ArduinoJson::Allocator { void *allocate(size_t size) override { return this->allocator_.allocate(size); } void deallocate(void *pointer) override { - // RAMAllocator requires passing the size of the allocated space which don't know, so use free directly + // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate. + // RAMAllocator::deallocate() requires the size, which we don't have access to here. + // Since RAMAllocator internally uses malloc/free for NONE mode (PSRAM allocation), + // it's safe to use free() directly. The memory was allocated via malloc in + // RAMAllocator::allocate() when using NONE mode. free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) } From 3cca7a61614368785aac4f92903f39d0d75d8f34 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 12:31:41 -1000 Subject: [PATCH 60/64] fix incorrect comment --- esphome/components/json/json_util.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 5f47444582..94c531222a 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -15,9 +15,10 @@ struct SpiRamAllocator : ArduinoJson::Allocator { void deallocate(void *pointer) override { // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate. // RAMAllocator::deallocate() requires the size, which we don't have access to here. - // Since RAMAllocator internally uses malloc/free for NONE mode (PSRAM allocation), - // it's safe to use free() directly. The memory was allocated via malloc in - // RAMAllocator::allocate() when using NONE mode. + // RAMAllocator::deallocate implementation just calls free() regardless of whether + // the memory was allocated with heap_caps_malloc or malloc. + // This is safe because ESP-IDF's heap implementation internally tracks the memory region + // and routes free() to the appropriate heap. free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) } From 619e2d69c03a9252aa7ec80421a3db2075c1b6d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 14:20:05 -1000 Subject: [PATCH 61/64] Remove redundant pyupgrade CI job (follow-up to #9484) (#9493) --- .github/workflows/ci.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e2e114fdb..16943861ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,29 +84,6 @@ jobs: run: script/ci-suggest-changes if: always() - pyupgrade: - name: Check pyupgrade - runs-on: ubuntu-24.04 - needs: - - common - - determine-jobs - if: needs.determine-jobs.outputs.python-linters == 'true' - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4.2.2 - - name: Restore Python - uses: ./.github/actions/restore-python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - cache-key: ${{ needs.common.outputs.cache-key }} - - name: Run pyupgrade - run: | - . venv/bin/activate - pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f` - - name: Suggested changes - run: script/ci-suggest-changes - if: always() - ci-custom: name: Run script/ci-custom runs-on: ubuntu-24.04 @@ -525,7 +502,6 @@ jobs: - pylint - pytest - integration-tests - - pyupgrade - clang-tidy-deps - clang-tidy - determine-jobs From d268c14f7ef69bcee455a60fecea4d2af9f105e7 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 14 Jul 2025 20:20:46 -0400 Subject: [PATCH 62/64] Apply suggestions from code review Fixes unsigned integer wrong type Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mqtt/mqtt_date.cpp | 2 +- esphome/components/mqtt/mqtt_datetime.cpp | 6 +++--- esphome/components/mqtt/mqtt_time.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 5f6923fc3e..0f0a334ae7 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -26,7 +26,7 @@ void MQTTDateComponent::setup() { if (root["month"].is()) { call.set_month(root["month"]); } - if (root["day"].is()) { + if (root["day"].is()) { call.set_day(root["day"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index c74cb159c8..5c56baabe0 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -29,13 +29,13 @@ void MQTTDateTimeComponent::setup() { if (root["day"].is()) { call.set_day(root["day"]); } - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index a19c16bb21..0c95bd8147 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root["hour"].is()) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root["minute"].is()) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root["second"].is()) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); From b2a8b0a22fd548c228d3f71c07b52060f8a6478f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 14:25:18 -1000 Subject: [PATCH 63/64] Add pre-commit hooks to fix common formatting issues causing CI failures (#9494) --- .clang-tidy.hash | 2 +- .coveragerc | 2 +- .github/workflows/ci-clang-tidy-hash.yml | 1 - .pre-commit-config.yaml | 5 ++++- script/clang_tidy_hash.py | 3 ++- script/test_build_components | 4 ++-- tests/components/animation/.gitattributes | 1 - tests/components/animation/test.esp32-ard.yaml | 1 - tests/components/ble_presence/common.yaml | 1 - tests/components/color/common.yaml | 1 - tests/components/const/common.yaml | 1 - tests/components/cst816/common.yaml | 1 - tests/components/debug/common.yaml | 1 - tests/components/ens160_spi/common.yaml | 1 - tests/components/esp32/test.esp32-idf.yaml | 1 - tests/components/esphome/test.esp32-ard.yaml | 1 - tests/components/esphome/test.esp32-c3-ard.yaml | 1 - tests/components/font/.gitattributes | 1 - tests/components/lvgl/.gitattributes | 1 - tests/components/lvgl/test.esp32-ard.yaml | 1 - tests/components/max17043/test.esp32-ard.yaml | 1 - tests/components/mipi_spi/common.yaml | 1 - tests/components/nextion/test.rp2040-ard.yaml | 1 - tests/components/online_image/test.esp32-idf.yaml | 1 - tests/components/openthread/test.esp32-c6-idf.yaml | 1 - tests/components/packages/test.esp32-ard.yaml | 1 - tests/components/packages/test.esp32-idf.yaml | 1 - tests/components/qspi_dbi/common.yaml | 1 - tests/components/spi/test.esp32-p4-idf.yaml | 1 - tests/components/spi/test.esp32-s3-idf.yaml | 1 - tests/components/udp/common.yaml | 1 - tests/components/web_server/test_ota.esp32-idf.yaml | 1 - tests/components/wk2132_i2c/test.esp32-ard.yaml | 1 - tests/integration/fixtures/areas_and_devices.yaml | 1 - tests/integration/fixtures/device_id_in_state.yaml | 1 - tests/integration/fixtures/large_message_batching.yaml | 1 - tests/script/test_clang_tidy_hash.py | 2 +- tests/unit_tests/fixtures/substitutions/.gitignore | 2 +- 38 files changed, 12 insertions(+), 39 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 30c52f5baa..d36ae1f164 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a \ No newline at end of file +a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a diff --git a/.coveragerc b/.coveragerc index 12e48ec395..f23592be24 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,4 @@ [run] -omit = +omit = esphome/components/* tests/integration/* diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 4e89da267c..1c7a62e40b 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -73,4 +73,3 @@ jobs: }); } } - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8db7f21599..5a522d9d36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,13 +27,15 @@ repos: - pydocstyle==5.1.1 files: ^(esphome|tests)/.+\.py$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v5.0.0 hooks: - id: no-commit-to-branch args: - --branch=dev - --branch=release - --branch=beta + - id: end-of-file-fixer + - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v3.20.0 hooks: @@ -43,6 +45,7 @@ repos: rev: v1.37.1 hooks: - id: yamllint + exclude: ^(\.clang-format|\.clang-tidy)$ - repo: https://github.com/pre-commit/mirrors-clang-format rev: v13.0.1 hooks: diff --git a/script/clang_tidy_hash.py b/script/clang_tidy_hash.py index 86f4c4e158..8cc135f5d0 100755 --- a/script/clang_tidy_hash.py +++ b/script/clang_tidy_hash.py @@ -126,7 +126,8 @@ def write_file_content(path: Path, content: str) -> None: def write_hash(hash_value: str) -> None: """Write hash to file""" hash_file = Path(__file__).parent.parent / ".clang-tidy.hash" - write_file_content(hash_file, hash_value) + # Strip any trailing newlines to ensure consistent formatting + write_file_content(hash_file, hash_value.strip() + "\n") def main() -> None: diff --git a/script/test_build_components b/script/test_build_components index 83ab947fc1..3796280176 100755 --- a/script/test_build_components +++ b/script/test_build_components @@ -72,7 +72,7 @@ for f in ./tests/components/$target_component/*.*.yaml; do if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then # Test has *not* defined a specific target platform. Need to run tests for all possible target platforms. - + for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do IFS='/' read -r -a folder_name <<< "$target_platform_file" IFS='.' read -r -a file_name <<< "${folder_name[3]}" @@ -83,7 +83,7 @@ for f in ./tests/components/$target_component/*.*.yaml; do else # Test has defined a specific target platform. - + # Validate we have a base test yaml for selected platform. # The target_platform is sourced from the following location. # 1. `./tests/test_build_components/build_components_base.[target_platform].yaml` diff --git a/tests/components/animation/.gitattributes b/tests/components/animation/.gitattributes index ff9fc6f1f1..766b86f19e 100644 --- a/tests/components/animation/.gitattributes +++ b/tests/components/animation/.gitattributes @@ -1,4 +1,3 @@ *.apng -text *.webp -text *.gif -text - diff --git a/tests/components/animation/test.esp32-ard.yaml b/tests/components/animation/test.esp32-ard.yaml index 5d330900df..7d9fe45bff 100644 --- a/tests/components/animation/test.esp32-ard.yaml +++ b/tests/components/animation/test.esp32-ard.yaml @@ -15,4 +15,3 @@ display: packages: animation: !include common.yaml - diff --git a/tests/components/ble_presence/common.yaml b/tests/components/ble_presence/common.yaml index 6e5173eed8..2ba6aa0754 100644 --- a/tests/components/ble_presence/common.yaml +++ b/tests/components/ble_presence/common.yaml @@ -21,4 +21,3 @@ binary_sensor: - platform: ble_presence irk: 1234567890abcdef1234567890abcdef name: "ESP32 BLE Tracker with Identity Resolving Key" - diff --git a/tests/components/color/common.yaml b/tests/components/color/common.yaml index 88524e6a5f..69a77326f7 100644 --- a/tests/components/color/common.yaml +++ b/tests/components/color/common.yaml @@ -17,4 +17,3 @@ color: hex: 008000 - id: cps_blue hex: 000080 - diff --git a/tests/components/const/common.yaml b/tests/components/const/common.yaml index 655af304af..f4b15f2b90 100644 --- a/tests/components/const/common.yaml +++ b/tests/components/const/common.yaml @@ -41,4 +41,3 @@ display: - delay 120ms - [0x29] - delay 20ms - diff --git a/tests/components/cst816/common.yaml b/tests/components/cst816/common.yaml index 9750de15db..9400e4eef0 100644 --- a/tests/components/cst816/common.yaml +++ b/tests/components/cst816/common.yaml @@ -42,4 +42,3 @@ binary_sensor: x_max: 480 y_min: 320 y_max: 360 - diff --git a/tests/components/debug/common.yaml b/tests/components/debug/common.yaml index a9d74e6865..d9a61f8df0 100644 --- a/tests/components/debug/common.yaml +++ b/tests/components/debug/common.yaml @@ -15,4 +15,3 @@ sensor: name: "Loop Time" cpu_frequency: name: "CPU Frequency" - diff --git a/tests/components/ens160_spi/common.yaml b/tests/components/ens160_spi/common.yaml index 7250ead228..c8b663272f 100644 --- a/tests/components/ens160_spi/common.yaml +++ b/tests/components/ens160_spi/common.yaml @@ -14,4 +14,3 @@ sensor: name: "ENS160 Total Volatile Organic Compounds" aqi: name: "ENS160 Air Quality Index" - diff --git a/tests/components/esp32/test.esp32-idf.yaml b/tests/components/esp32/test.esp32-idf.yaml index 582b2c22ce..ccf0b7cbd5 100644 --- a/tests/components/esp32/test.esp32-idf.yaml +++ b/tests/components/esp32/test.esp32-idf.yaml @@ -9,4 +9,3 @@ esp32: wifi: ssid: MySSID password: password1 - diff --git a/tests/components/esphome/test.esp32-ard.yaml b/tests/components/esphome/test.esp32-ard.yaml index a991f7e15a..dade44d145 100644 --- a/tests/components/esphome/test.esp32-ard.yaml +++ b/tests/components/esphome/test.esp32-ard.yaml @@ -1,2 +1 @@ <<: !include common.yaml - diff --git a/tests/components/esphome/test.esp32-c3-ard.yaml b/tests/components/esphome/test.esp32-c3-ard.yaml index a991f7e15a..dade44d145 100644 --- a/tests/components/esphome/test.esp32-c3-ard.yaml +++ b/tests/components/esphome/test.esp32-c3-ard.yaml @@ -1,2 +1 @@ <<: !include common.yaml - diff --git a/tests/components/font/.gitattributes b/tests/components/font/.gitattributes index 18d9a389e8..63ab00e9f2 100644 --- a/tests/components/font/.gitattributes +++ b/tests/components/font/.gitattributes @@ -1,2 +1 @@ *.pcf -text - diff --git a/tests/components/lvgl/.gitattributes b/tests/components/lvgl/.gitattributes index 75e7a44254..9d74867fcf 100644 --- a/tests/components/lvgl/.gitattributes +++ b/tests/components/lvgl/.gitattributes @@ -1,2 +1 @@ *.ttf -text - diff --git a/tests/components/lvgl/test.esp32-ard.yaml b/tests/components/lvgl/test.esp32-ard.yaml index 5b09147de7..f85bedbde6 100644 --- a/tests/components/lvgl/test.esp32-ard.yaml +++ b/tests/components/lvgl/test.esp32-ard.yaml @@ -56,4 +56,3 @@ lvgl: packages: lvgl: !include lvgl-package.yaml xvgl: !include common.yaml - diff --git a/tests/components/max17043/test.esp32-ard.yaml b/tests/components/max17043/test.esp32-ard.yaml index c84e0a5c2e..c6615f51cd 100644 --- a/tests/components/max17043/test.esp32-ard.yaml +++ b/tests/components/max17043/test.esp32-ard.yaml @@ -3,4 +3,3 @@ substitutions: scl_pin: GPIO22 <<: !include common.yaml - diff --git a/tests/components/mipi_spi/common.yaml b/tests/components/mipi_spi/common.yaml index e4b1e2b30c..2c84489ec7 100644 --- a/tests/components/mipi_spi/common.yaml +++ b/tests/components/mipi_spi/common.yaml @@ -35,4 +35,3 @@ display: allow_other_uses: true - number: ${enable_pin} bus_mode: single - diff --git a/tests/components/nextion/test.rp2040-ard.yaml b/tests/components/nextion/test.rp2040-ard.yaml index 20347c6eff..44534b97a8 100644 --- a/tests/components/nextion/test.rp2040-ard.yaml +++ b/tests/components/nextion/test.rp2040-ard.yaml @@ -4,4 +4,3 @@ substitutions: packages: base: !include common.yaml - diff --git a/tests/components/online_image/test.esp32-idf.yaml b/tests/components/online_image/test.esp32-idf.yaml index 3f01009812..d4af284151 100644 --- a/tests/components/online_image/test.esp32-idf.yaml +++ b/tests/components/online_image/test.esp32-idf.yaml @@ -1,4 +1,3 @@ <<: !include common-esp32.yaml http_request: - diff --git a/tests/components/openthread/test.esp32-c6-idf.yaml b/tests/components/openthread/test.esp32-c6-idf.yaml index bbcf48efa5..da5339fb39 100644 --- a/tests/components/openthread/test.esp32-c6-idf.yaml +++ b/tests/components/openthread/test.esp32-c6-idf.yaml @@ -11,4 +11,3 @@ openthread: pskc: 0xc23a76e98f1a6483639b1ac1271e2e27 mesh_local_prefix: fd53:145f:ed22:ad81::/64 force_dataset: true - diff --git a/tests/components/packages/test.esp32-ard.yaml b/tests/components/packages/test.esp32-ard.yaml index 7d0ab2b905..9e4ceb09d6 100644 --- a/tests/components/packages/test.esp32-ard.yaml +++ b/tests/components/packages/test.esp32-ard.yaml @@ -9,4 +9,3 @@ packages: file: common.yaml ref: dev refresh: 1d - diff --git a/tests/components/packages/test.esp32-idf.yaml b/tests/components/packages/test.esp32-idf.yaml index 8c0a34bb1a..5bf40822dc 100644 --- a/tests/components/packages/test.esp32-idf.yaml +++ b/tests/components/packages/test.esp32-idf.yaml @@ -11,4 +11,3 @@ packages: file: common.yaml ref: dev refresh: 1d - diff --git a/tests/components/qspi_dbi/common.yaml b/tests/components/qspi_dbi/common.yaml index 655af304af..f4b15f2b90 100644 --- a/tests/components/qspi_dbi/common.yaml +++ b/tests/components/qspi_dbi/common.yaml @@ -41,4 +41,3 @@ display: - delay 120ms - [0x29] - delay 20ms - diff --git a/tests/components/spi/test.esp32-p4-idf.yaml b/tests/components/spi/test.esp32-p4-idf.yaml index 061e3dd44a..93626dc1d5 100644 --- a/tests/components/spi/test.esp32-p4-idf.yaml +++ b/tests/components/spi/test.esp32-p4-idf.yaml @@ -35,4 +35,3 @@ spi: interface: any clk_pin: 8 mosi_pin: 9 - diff --git a/tests/components/spi/test.esp32-s3-idf.yaml b/tests/components/spi/test.esp32-s3-idf.yaml index 061e3dd44a..93626dc1d5 100644 --- a/tests/components/spi/test.esp32-s3-idf.yaml +++ b/tests/components/spi/test.esp32-s3-idf.yaml @@ -35,4 +35,3 @@ spi: interface: any clk_pin: 8 mosi_pin: 9 - diff --git a/tests/components/udp/common.yaml b/tests/components/udp/common.yaml index 79da02a692..96224d0d1f 100644 --- a/tests/components/udp/common.yaml +++ b/tests/components/udp/common.yaml @@ -17,4 +17,3 @@ udp: id: my_udp data: !lambda |- return std::vector{1,3,4,5,6}; - diff --git a/tests/components/web_server/test_ota.esp32-idf.yaml b/tests/components/web_server/test_ota.esp32-idf.yaml index 37838b3d34..99873aa27b 100644 --- a/tests/components/web_server/test_ota.esp32-idf.yaml +++ b/tests/components/web_server/test_ota.esp32-idf.yaml @@ -27,4 +27,3 @@ logger: logs: web_server: VERBOSE web_server_idf: VERBOSE - diff --git a/tests/components/wk2132_i2c/test.esp32-ard.yaml b/tests/components/wk2132_i2c/test.esp32-ard.yaml index 94552c5a40..3b761d3fc1 100644 --- a/tests/components/wk2132_i2c/test.esp32-ard.yaml +++ b/tests/components/wk2132_i2c/test.esp32-ard.yaml @@ -3,4 +3,3 @@ substitutions: sda_pin: GPIO21 <<: !include common.yaml - diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml index 4a327b73a1..6bf1519c79 100644 --- a/tests/integration/fixtures/areas_and_devices.yaml +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -54,4 +54,3 @@ sensor: device_id: smart_switch_device lambda: return 4.0; update_interval: 0.1s - diff --git a/tests/integration/fixtures/device_id_in_state.yaml b/tests/integration/fixtures/device_id_in_state.yaml index f2e320a2e2..c8548617b8 100644 --- a/tests/integration/fixtures/device_id_in_state.yaml +++ b/tests/integration/fixtures/device_id_in_state.yaml @@ -82,4 +82,3 @@ output: write_action: - lambda: |- ESP_LOGD("test", "Light output: %d", state); - diff --git a/tests/integration/fixtures/large_message_batching.yaml b/tests/integration/fixtures/large_message_batching.yaml index 1b2d817cd4..e491da0486 100644 --- a/tests/integration/fixtures/large_message_batching.yaml +++ b/tests/integration/fixtures/large_message_batching.yaml @@ -134,4 +134,3 @@ switch: name: "Test Switch" id: test_switch optimistic: true - diff --git a/tests/script/test_clang_tidy_hash.py b/tests/script/test_clang_tidy_hash.py index dbcb477a4f..e4d4b40473 100644 --- a/tests/script/test_clang_tidy_hash.py +++ b/tests/script/test_clang_tidy_hash.py @@ -158,7 +158,7 @@ def test_write_hash() -> None: mock_write.assert_called_once() args = mock_write.call_args[0] assert str(args[0]).endswith(".clang-tidy.hash") - assert args[1] == hash_value + assert args[1] == hash_value.strip() + "\n" @pytest.mark.parametrize( diff --git a/tests/unit_tests/fixtures/substitutions/.gitignore b/tests/unit_tests/fixtures/substitutions/.gitignore index 0b15cdb2b7..897c9da86c 100644 --- a/tests/unit_tests/fixtures/substitutions/.gitignore +++ b/tests/unit_tests/fixtures/substitutions/.gitignore @@ -1 +1 @@ -*.received.yaml \ No newline at end of file +*.received.yaml From e3da197adfb986ca44a652e48192b821e10c63e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Jul 2025 14:52:03 -1000 Subject: [PATCH 64/64] Remove yamllint job from CI since its now handled by pre-commit job (#9500) --- .github/workflows/ci.yml | 2 +- .github/workflows/yaml-lint.yml | 25 ------------------------- .pre-commit-config.yaml | 2 +- 3 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/yaml-lint.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16943861ea..94e490a4c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -489,7 +489,7 @@ jobs: cache-key: ${{ needs.common.outputs.cache-key }} - uses: pre-commit/action@v3.0.1 env: - SKIP: pylint,clang-tidy-hash,yamllint + SKIP: pylint,clang-tidy-hash - uses: pre-commit-ci/lite-action@v1.1.0 if: always() diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml deleted file mode 100644 index ed9b4407a2..0000000000 --- a/.github/workflows/yaml-lint.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: YAML lint - -on: - push: - branches: [dev, beta, release] - paths: - - "**.yaml" - - "**.yml" - pull_request: - paths: - - "**.yaml" - - "**.yml" - -jobs: - yamllint: - name: yamllint - runs-on: ubuntu-latest - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4.2.2 - - name: Run yamllint - uses: frenck/action-yamllint@v1.5.0 - with: - strict: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a522d9d36..118253861d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: autoupdate_commit_msg: 'pre-commit: autoupdate' autoupdate_schedule: off # Disabled until ruff versions are synced between deps and pre-commit # Skip hooks that have issues in pre-commit CI environment - skip: [pylint, clang-tidy-hash, yamllint] + skip: [pylint, clang-tidy-hash] repos: - repo: https://github.com/astral-sh/ruff-pre-commit