From 8014cbc71e8f5c1f47a1d04d0c7b1aee9024e047 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 30 Jun 2025 19:25:54 +0100 Subject: [PATCH 01/35] Fixes for async MQTT (#9273) --- esphome/components/mqtt/mqtt_backend_esp32.cpp | 8 ++++++++ esphome/components/mqtt/mqtt_backend_esp32.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 62b153e676..4648e66e1d 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -163,12 +163,20 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { case MQTT_EVENT_CONNECTED: ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED"); this->is_connected_ = true; +#if defined(USE_MQTT_IDF_ENQUEUE) + this->last_dropped_log_time_ = 0; + xTaskNotifyGive(this->task_handle_); +#endif this->on_connect_.call(event.session_present); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED"); // TODO is there a way to get the disconnect reason? this->is_connected_ = false; +#if defined(USE_MQTT_IDF_ENQUEUE) + this->last_dropped_log_time_ = 0; + xTaskNotifyGive(this->task_handle_); +#endif this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED); break; diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index 57286a24b2..3611caf554 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -116,7 +116,7 @@ struct QueueElement { class MQTTBackendESP32 final : public MQTTBackend { public: static const size_t MQTT_BUFFER_SIZE = 4096; - static const size_t TASK_STACK_SIZE = 2048; + static const size_t TASK_STACK_SIZE = 3072; static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations static const ssize_t TASK_PRIORITY = 5; static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360 From 0cbb5e6c1c67a436dcf9606417a18387c025e14d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 15:02:43 -0500 Subject: [PATCH 02/35] Fix flaky test_api_conditional_memory by waiting for all required states (#9271) --- .../integration/test_api_conditional_memory.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/integration/test_api_conditional_memory.py b/tests/integration/test_api_conditional_memory.py index b85e8d91af..8048624f70 100644 --- a/tests/integration/test_api_conditional_memory.py +++ b/tests/integration/test_api_conditional_memory.py @@ -177,19 +177,22 @@ async def test_api_conditional_memory( async with api_client_connected() as client2: # Subscribe to states with new client states2: dict[int, EntityState] = {} - connected_future: asyncio.Future[None] = loop.create_future() + states_ready_future: asyncio.Future[None] = loop.create_future() def on_state2(state: EntityState) -> None: states2[state.key] = state - # Check for reconnection - if state.key == client_connected.key and state.state is True: - if not connected_future.done(): - connected_future.set_result(None) + # Check if we have received both required states + if ( + client_connected.key in states2 + and client_disconnected_event.key in states2 + and not states_ready_future.done() + ): + states_ready_future.set_result(None) client2.subscribe_states(on_state2) - # Wait for connected state - await asyncio.wait_for(connected_future, timeout=5.0) + # Wait for both connected and disconnected event states + await asyncio.wait_for(states_ready_future, timeout=5.0) # Verify client is connected again (on_client_connected fired) assert states2[client_connected.key].state is True, ( From 98e106e0ae9b30937ff87b5285d61c03fd2ce61a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:09:11 +1200 Subject: [PATCH 03/35] [pins] Update ``internal_gpio_pin_number`` to work directly like ``internal_gpio_output_pin_number`` (#9270) --- esphome/components/i2c/__init__.py | 11 ++--------- esphome/pins.py | 4 +++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index fae1fa1d22..6adb9b71aa 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -9,8 +9,6 @@ from esphome.const import ( CONF_FREQUENCY, CONF_I2C_ID, CONF_ID, - CONF_INPUT, - CONF_OUTPUT, CONF_SCAN, CONF_SCL, CONF_SDA, @@ -73,20 +71,15 @@ def validate_config(config): return config -pin_with_input_and_output_support = pins.internal_gpio_pin_number( - {CONF_OUTPUT: True, CONF_INPUT: True} -) - - CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): _bus_declare_type, - cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support, + cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number, cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( cv.only_with_esp_idf, cv.boolean ), - cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support, + cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number, cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( cv.only_with_esp_idf, cv.boolean ), diff --git a/esphome/pins.py b/esphome/pins.py index 0dfd5a245b..4f9b4859a1 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -220,7 +220,9 @@ def gpio_flags_expr(mode): gpio_pin_schema = _schema_creator -internal_gpio_pin_number = _internal_number_creator +internal_gpio_pin_number = _internal_number_creator( + {CONF_OUTPUT: True, CONF_INPUT: True} +) gpio_output_pin_schema = _schema_creator( { CONF_OUTPUT: True, From 78c8cd4c4e4f08c886a3faf44e1e5fa017e5755f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:50:19 +1200 Subject: [PATCH 04/35] [http_request.update] Fix ``size_t`` printing (#9144) --- 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 828fb5bd8b..6bc88ae49a 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -57,7 +57,7 @@ void HttpRequestUpdate::update_task(void *params) { RAMAllocator allocator; uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { - std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); + std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length); this_update->status_set_error(msg.c_str()); container->end(); UPDATE_RETURN; From 08c88ba0f26d5b5558b4679ca9beba73acc20da3 Mon Sep 17 00:00:00 2001 From: piechade Date: Mon, 30 Jun 2025 22:54:23 +0200 Subject: [PATCH 05/35] [smt100] Rename ``dielectric_constant`` to ``permittivity`` (#9175) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/smt100/sensor.py | 12 ++++++++---- esphome/components/smt100/smt100.cpp | 10 +++++----- esphome/components/smt100/smt100.h | 6 +++--- esphome/const.py | 1 + tests/components/smt100/common.yaml | 4 ++-- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/esphome/components/smt100/sensor.py b/esphome/components/smt100/sensor.py index ea42499379..f877ce2af0 100644 --- a/esphome/components/smt100/sensor.py +++ b/esphome/components/smt100/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_DIELECTRIC_CONSTANT, CONF_ID, CONF_MOISTURE, + CONF_PERMITTIVITY, CONF_TEMPERATURE, CONF_VOLTAGE, DEVICE_CLASS_TEMPERATURE, @@ -33,7 +34,10 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_DIELECTRIC_CONSTANT): sensor.sensor_schema( + cv.Optional(CONF_DIELECTRIC_CONSTANT): cv.invalid( + "Use 'permittivity' instead" + ), + cv.Optional(CONF_PERMITTIVITY): sensor.sensor_schema( unit_of_measurement=UNIT_EMPTY, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, @@ -76,9 +80,9 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_COUNTS]) cg.add(var.set_counts_sensor(sens)) - if CONF_DIELECTRIC_CONSTANT in config: - sens = await sensor.new_sensor(config[CONF_DIELECTRIC_CONSTANT]) - cg.add(var.set_dielectric_constant_sensor(sens)) + if CONF_PERMITTIVITY in config: + sens = await sensor.new_sensor(config[CONF_PERMITTIVITY]) + cg.add(var.set_permittivity_sensor(sens)) if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) diff --git a/esphome/components/smt100/smt100.cpp b/esphome/components/smt100/smt100.cpp index 24ba05b894..c8dfb4c7bd 100644 --- a/esphome/components/smt100/smt100.cpp +++ b/esphome/components/smt100/smt100.cpp @@ -16,7 +16,7 @@ void SMT100Component::loop() { while (this->available() != 0) { if (readline_(read(), buffer, MAX_LINE_LENGTH) > 0) { int counts = (int) strtol((strtok(buffer, ",")), nullptr, 10); - float dielectric_constant = (float) strtod((strtok(nullptr, ",")), nullptr); + float permittivity = (float) strtod((strtok(nullptr, ",")), nullptr); float moisture = (float) strtod((strtok(nullptr, ",")), nullptr); float temperature = (float) strtod((strtok(nullptr, ",")), nullptr); float voltage = (float) strtod((strtok(nullptr, ",")), nullptr); @@ -25,8 +25,8 @@ void SMT100Component::loop() { counts_sensor_->publish_state(counts); } - if (this->dielectric_constant_sensor_ != nullptr) { - dielectric_constant_sensor_->publish_state(dielectric_constant); + if (this->permittivity_sensor_ != nullptr) { + permittivity_sensor_->publish_state(permittivity); } if (this->moisture_sensor_ != nullptr) { @@ -49,8 +49,8 @@ float SMT100Component::get_setup_priority() const { return setup_priority::DATA; void SMT100Component::dump_config() { ESP_LOGCONFIG(TAG, "SMT100:"); - LOG_SENSOR(TAG, "Counts", this->temperature_sensor_); - LOG_SENSOR(TAG, "Dielectric Constant", this->temperature_sensor_); + LOG_SENSOR(TAG, "Counts", this->counts_sensor_); + LOG_SENSOR(TAG, "Permittivity", this->permittivity_sensor_); LOG_SENSOR(TAG, "Temperature", this->temperature_sensor_); LOG_SENSOR(TAG, "Moisture", this->moisture_sensor_); LOG_UPDATE_INTERVAL(this); diff --git a/esphome/components/smt100/smt100.h b/esphome/components/smt100/smt100.h index 017818bdcf..86827607dc 100644 --- a/esphome/components/smt100/smt100.h +++ b/esphome/components/smt100/smt100.h @@ -20,8 +20,8 @@ class SMT100Component : public PollingComponent, public uart::UARTDevice { float get_setup_priority() const override; void set_counts_sensor(sensor::Sensor *counts_sensor) { this->counts_sensor_ = counts_sensor; } - void set_dielectric_constant_sensor(sensor::Sensor *dielectric_constant_sensor) { - this->dielectric_constant_sensor_ = dielectric_constant_sensor; + void set_permittivity_sensor(sensor::Sensor *permittivity_sensor) { + this->permittivity_sensor_ = permittivity_sensor; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } void set_moisture_sensor(sensor::Sensor *moisture_sensor) { this->moisture_sensor_ = moisture_sensor; } @@ -31,7 +31,7 @@ class SMT100Component : public PollingComponent, public uart::UARTDevice { int readline_(int readch, char *buffer, int len); sensor::Sensor *counts_sensor_{nullptr}; - sensor::Sensor *dielectric_constant_sensor_{nullptr}; + sensor::Sensor *permittivity_sensor_{nullptr}; sensor::Sensor *moisture_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *voltage_sensor_{nullptr}; diff --git a/esphome/const.py b/esphome/const.py index b167935d12..4aeb5179e6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -654,6 +654,7 @@ CONF_PAYLOAD = "payload" CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PERIOD = "period" +CONF_PERMITTIVITY = "permittivity" CONF_PH = "ph" CONF_PHASE_A = "phase_a" CONF_PHASE_ANGLE = "phase_angle" diff --git a/tests/components/smt100/common.yaml b/tests/components/smt100/common.yaml index f86bd762e7..b12d7198fd 100644 --- a/tests/components/smt100/common.yaml +++ b/tests/components/smt100/common.yaml @@ -8,8 +8,8 @@ sensor: - platform: smt100 counts: name: Counts - dielectric_constant: - name: Dielectric Constant + permittivity: + name: Permittivity temperature: name: Temperature moisture: From e58baab563ed9a93dd3f55edfaac2b6b7be4af03 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:06:59 -0400 Subject: [PATCH 06/35] [ethernet] P4 changes and 5.3.0 deprecated warnings (#8457) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ethernet/__init__.py | 55 ++++++++++++------- .../ethernet/ethernet_component.cpp | 41 +++++++++----- .../components/ethernet/ethernet_component.h | 5 +- tests/components/ethernet/common-dp83848.yaml | 4 +- tests/components/ethernet/common-ip101.yaml | 4 +- tests/components/ethernet/common-jl1101.yaml | 4 +- tests/components/ethernet/common-ksz8081.yaml | 4 +- .../ethernet/common-ksz8081rna.yaml | 4 +- tests/components/ethernet/common-lan8720.yaml | 4 +- tests/components/ethernet/common-rtl8201.yaml | 4 +- tests/components/ethernet_info/common.yaml | 4 +- 11 files changed, 88 insertions(+), 45 deletions(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 8eec9510cc..ac07d02e37 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -23,8 +23,10 @@ from esphome.const import ( CONF_INTERRUPT_PIN, CONF_MANUAL_IP, CONF_MISO_PIN, + CONF_MODE, CONF_MOSI_PIN, CONF_PAGE_ID, + CONF_PIN, CONF_POLLING_INTERVAL, CONF_RESET_PIN, CONF_SPI, @@ -49,6 +51,7 @@ PHYRegister = ethernet_ns.struct("PHYRegister") CONF_PHY_ADDR = "phy_addr" CONF_MDC_PIN = "mdc_pin" CONF_MDIO_PIN = "mdio_pin" +CONF_CLK = "clk" CONF_CLK_MODE = "clk_mode" CONF_POWER_PIN = "power_pin" CONF_PHY_REGISTERS = "phy_registers" @@ -73,26 +76,18 @@ SPI_ETHERNET_TYPES = ["W5500", "DM9051"] SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") -emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") + CLK_MODES = { - "GPIO0_IN": ( - emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN, - emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO, - ), - "GPIO0_OUT": ( - emac_rmii_clock_mode_t.EMAC_CLK_OUT, - emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO, - ), - "GPIO16_OUT": ( - emac_rmii_clock_mode_t.EMAC_CLK_OUT, - emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO, - ), - "GPIO17_OUT": ( - emac_rmii_clock_mode_t.EMAC_CLK_OUT, - emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO, - ), + "CLK_EXT_IN": emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN, + "CLK_OUT": emac_rmii_clock_mode_t.EMAC_CLK_OUT, } +CLK_MODES_DEPRECATED = { + "GPIO0_IN": ("CLK_EXT_IN", 0), + "GPIO0_OUT": ("CLK_OUT", 0), + "GPIO16_OUT": ("CLK_OUT", 16), + "GPIO17_OUT": ("CLK_OUT", 17), +} MANUAL_IP_SCHEMA = cv.Schema( { @@ -154,6 +149,18 @@ def _validate(config): f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]." ) + elif config[CONF_TYPE] != "OPENETH": + if CONF_CLK_MODE in config: + LOGGER.warning( + "[ethernet] The 'clk_mode' option is deprecated and will be removed in ESPHome 2026.1. " + "Please update your configuration to use 'clk' instead." + ) + mode = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]] + config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode[0], CONF_PIN: mode[1]}) + del config[CONF_CLK_MODE] + elif CONF_CLK not in config: + raise cv.Invalid("'clk' is a required option for [ethernet].") + return config @@ -177,14 +184,21 @@ PHY_REGISTER_SCHEMA = cv.Schema( cv.Optional(CONF_PAGE_ID): cv.hex_int, } ) +CLK_SCHEMA = cv.Schema( + { + cv.Required(CONF_MODE): cv.enum(CLK_MODES, upper=True, space="_"), + cv.Required(CONF_PIN): pins.internal_gpio_pin_number, + } +) RMII_SCHEMA = BASE_SCHEMA.extend( cv.Schema( { cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( - CLK_MODES, upper=True, space="_" + cv.Optional(CONF_CLK_MODE): cv.enum( + CLK_MODES_DEPRECATED, upper=True, space="_" ), + cv.Optional(CONF_CLK): CLK_SCHEMA, cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), @@ -308,7 +322,8 @@ async def to_code(config): cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) - cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) + cg.add(var.set_clk_mode(config[CONF_CLK][CONF_MODE])) + cg.add(var.set_clk_pin(config[CONF_CLK][CONF_PIN])) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) for register_value in config.get(CONF_PHY_REGISTERS, []): diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 8739269f4a..19a11c6945 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -17,6 +17,22 @@ namespace esphome { namespace ethernet { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) +// work around IDF compile issue on P4 https://github.com/espressif/esp-idf/pull/15637 +#ifdef USE_ESP32_VARIANT_ESP32P4 +#undef ETH_ESP32_EMAC_DEFAULT_CONFIG +#define ETH_ESP32_EMAC_DEFAULT_CONFIG() \ + { \ + .smi_gpio = {.mdc_num = 31, .mdio_num = 52}, .interface = EMAC_DATA_INTERFACE_RMII, \ + .clock_config = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, .clock_gpio = (emac_rmii_clock_gpio_t) 50}}, \ + .dma_burst_len = ETH_DMA_BURST_LEN_32, .intr_priority = 0, \ + .emac_dataif_gpio = \ + {.rmii = {.tx_en_num = 49, .txd0_num = 34, .txd1_num = 35, .crs_dv_num = 28, .rxd0_num = 29, .rxd1_num = 30}}, \ + .clock_config_out_in = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, .clock_gpio = (emac_rmii_clock_gpio_t) -1}}, \ + } +#endif +#endif + static const char *const TAG = "ethernet"; EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -150,22 +166,18 @@ void EthernetComponent::setup() { phy_config.phy_addr = this->phy_addr_; phy_config.reset_gpio_num = this->power_pin_; -#if ESP_IDF_VERSION_MAJOR >= 5 eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_; + esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_; +#else esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_; +#endif esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_; - esp32_emac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; + esp32_emac_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t) this->clk_pin_; esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); -#else - mac_config.smi_mdc_gpio_num = this->mdc_pin_; - mac_config.smi_mdio_gpio_num = this->mdio_pin_; - mac_config.clock_config.rmii.clock_mode = this->clk_mode_; - mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; - - esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); -#endif #endif switch (this->type_) { @@ -387,10 +399,11 @@ void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); } ESP_LOGCONFIG(TAG, + " CLK Pin: %u\n" " MDC Pin: %u\n" " MDIO Pin: %u\n" " PHY addr: %u", - this->mdc_pin_, this->mdio_pin_, this->phy_addr_); + this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_); #endif ESP_LOGCONFIG(TAG, " Type: %s", eth_type); } @@ -611,10 +624,8 @@ void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_a void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } -void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { - this->clk_mode_ = clk_mode; - this->clk_gpio_ = clk_gpio; -} +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } #endif void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 6cdc113aa8..1b347946f5 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -76,7 +76,8 @@ class EthernetComponent : public Component { void set_power_pin(int power_pin); void set_mdc_pin(uint8_t mdc_pin); void set_mdio_pin(uint8_t mdio_pin); - void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); + void set_clk_pin(uint8_t clk_pin); + void set_clk_mode(emac_rmii_clock_mode_t clk_mode); void add_phy_register(PHYRegister register_value); #endif void set_type(EthernetType type); @@ -123,10 +124,10 @@ class EthernetComponent : public Component { // Group all 32-bit members first int power_pin_{-1}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; - emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; std::vector phy_registers_{}; // Group all 8-bit members together + uint8_t clk_pin_{0}; uint8_t phy_addr_{0}; uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; diff --git a/tests/components/ethernet/common-dp83848.yaml b/tests/components/ethernet/common-dp83848.yaml index 5b6ed3e8d0..140c7d0d1b 100644 --- a/tests/components/ethernet/common-dp83848.yaml +++ b/tests/components/ethernet/common-dp83848.yaml @@ -2,7 +2,9 @@ ethernet: type: DP83848 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-ip101.yaml b/tests/components/ethernet/common-ip101.yaml index 5ca369cce1..b5589220de 100644 --- a/tests/components/ethernet/common-ip101.yaml +++ b/tests/components/ethernet/common-ip101.yaml @@ -2,7 +2,9 @@ ethernet: type: IP101 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-jl1101.yaml b/tests/components/ethernet/common-jl1101.yaml index 639542d807..2ada9495a0 100644 --- a/tests/components/ethernet/common-jl1101.yaml +++ b/tests/components/ethernet/common-jl1101.yaml @@ -2,7 +2,9 @@ ethernet: type: JL1101 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-ksz8081.yaml b/tests/components/ethernet/common-ksz8081.yaml index 167606a1eb..7da8adb09a 100644 --- a/tests/components/ethernet/common-ksz8081.yaml +++ b/tests/components/ethernet/common-ksz8081.yaml @@ -2,7 +2,9 @@ ethernet: type: KSZ8081 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-ksz8081rna.yaml b/tests/components/ethernet/common-ksz8081rna.yaml index f506906b1b..df04f06132 100644 --- a/tests/components/ethernet/common-ksz8081rna.yaml +++ b/tests/components/ethernet/common-ksz8081rna.yaml @@ -2,7 +2,9 @@ ethernet: type: KSZ8081RNA mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-lan8720.yaml b/tests/components/ethernet/common-lan8720.yaml index b9ed9cb036..f227752f42 100644 --- a/tests/components/ethernet/common-lan8720.yaml +++ b/tests/components/ethernet/common-lan8720.yaml @@ -2,7 +2,9 @@ ethernet: type: LAN8720 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet/common-rtl8201.yaml b/tests/components/ethernet/common-rtl8201.yaml index 43842e7c9f..7c9c9d913c 100644 --- a/tests/components/ethernet/common-rtl8201.yaml +++ b/tests/components/ethernet/common-rtl8201.yaml @@ -2,7 +2,9 @@ ethernet: type: RTL8201 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: diff --git a/tests/components/ethernet_info/common.yaml b/tests/components/ethernet_info/common.yaml index d9a6f515b1..f45f345316 100644 --- a/tests/components/ethernet_info/common.yaml +++ b/tests/components/ethernet_info/common.yaml @@ -2,7 +2,9 @@ ethernet: type: LAN8720 mdc_pin: 23 mdio_pin: 25 - clk_mode: GPIO0_IN + clk: + pin: 0 + mode: CLK_EXT_IN phy_addr: 0 power_pin: 26 manual_ip: From db7a420e54059217458be57ce3be40a220fd204b Mon Sep 17 00:00:00 2001 From: Mathieu Rene Date: Mon, 30 Jun 2025 18:07:30 -0400 Subject: [PATCH 07/35] Fix - Pass thread TLVs down to openthread if they are defined (#9182) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/openthread/__init__.py | 72 +++++++++---------- .../components/openthread/openthread_esp.cpp | 32 +++++++-- esphome/components/openthread/tlv.py | 65 ----------------- 3 files changed, 62 insertions(+), 107 deletions(-) delete mode 100644 esphome/components/openthread/tlv.py diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 393c47e720..65138e28c7 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -22,7 +22,6 @@ from .const import ( CONF_SRP_ID, CONF_TLV, ) -from .tlv import parse_tlv CODEOWNERS = ["@mrene"] @@ -43,29 +42,40 @@ def set_sdkconfig_options(config): add_idf_sdkconfig_option("CONFIG_OPENTHREAD_CLI", False) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_ENABLED", True) - add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID]) - add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL]) - add_idf_sdkconfig_option( - "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower() - ) - if network_name := config.get(CONF_NETWORK_NAME): - add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_NAME", network_name) + if tlv := config.get(CONF_TLV): + cg.add_define("USE_OPENTHREAD_TLVS", tlv) + else: + if pan_id := config.get(CONF_PAN_ID): + add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", pan_id) - if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None: - add_idf_sdkconfig_option( - "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower() - ) - if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None: - add_idf_sdkconfig_option( - "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower() - ) - if (pskc := config.get(CONF_PSKC)) is not None: - add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower()) + if channel := config.get(CONF_CHANNEL): + add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", channel) - if CONF_FORCE_DATASET in config: - if config[CONF_FORCE_DATASET]: - cg.add_define("CONFIG_OPENTHREAD_FORCE_DATASET") + if network_key := config.get(CONF_NETWORK_KEY): + add_idf_sdkconfig_option( + "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{network_key:X}".lower() + ) + + if network_name := config.get(CONF_NETWORK_NAME): + add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_NAME", network_name) + + if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None: + add_idf_sdkconfig_option( + "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower() + ) + if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None: + add_idf_sdkconfig_option( + "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower() + ) + if (pskc := config.get(CONF_PSKC)) is not None: + add_idf_sdkconfig_option( + "CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower() + ) + + if force_dataset := config.get(CONF_FORCE_DATASET): + if force_dataset: + cg.add_define("USE_OPENTHREAD_FORCE_DATASET") add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True) @@ -79,22 +89,11 @@ openthread_ns = cg.esphome_ns.namespace("openthread") OpenThreadComponent = openthread_ns.class_("OpenThreadComponent", cg.Component) OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Component) - -def _convert_tlv(config): - if tlv := config.get(CONF_TLV): - config = config.copy() - parsed_tlv = parse_tlv(tlv) - validated = _CONNECTION_SCHEMA(parsed_tlv) - config.update(validated) - del config[CONF_TLV] - return config - - _CONNECTION_SCHEMA = cv.Schema( { - cv.Inclusive(CONF_PAN_ID, "manual"): cv.hex_int, - cv.Inclusive(CONF_CHANNEL, "manual"): cv.int_, - cv.Inclusive(CONF_NETWORK_KEY, "manual"): cv.hex_int, + cv.Optional(CONF_PAN_ID): cv.hex_int, + cv.Optional(CONF_CHANNEL): cv.int_, + cv.Optional(CONF_NETWORK_KEY): cv.hex_int, cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, cv.Optional(CONF_NETWORK_NAME): cv.string_strict, cv.Optional(CONF_PSKC): cv.hex_int, @@ -112,8 +111,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TLV): cv.string_strict, } ).extend(_CONNECTION_SCHEMA), - cv.has_exactly_one_key(CONF_PAN_ID, CONF_TLV), - _convert_tlv, + cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]), ) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index c5c817382f..dc303cef17 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -111,14 +111,36 @@ void OpenThreadComponent::ot_main() { esp_openthread_cli_create_task(); #endif ESP_LOGI(TAG, "Activating dataset..."); - otOperationalDatasetTlvs dataset; + otOperationalDatasetTlvs dataset = {}; -#ifdef CONFIG_OPENTHREAD_FORCE_DATASET - ESP_ERROR_CHECK(esp_openthread_auto_start(NULL)); -#else +#ifndef USE_OPENTHREAD_FORCE_DATASET + // Check if openthread has a valid dataset from a previous execution otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset); - ESP_ERROR_CHECK(esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL)); + if (error != OT_ERROR_NONE) { + // Make sure the length is 0 so we fallback to the configuration + dataset.mLength = 0; + } else { + ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration"); + ESP_LOGI(TAG, "(set force_dataset: true to override)"); + } #endif + +#ifdef USE_OPENTHREAD_TLVS + if (dataset.mLength == 0) { + // If we didn't have an active dataset, and we have tlvs, parse it and pass it to esp_openthread_auto_start + size_t len = (sizeof(USE_OPENTHREAD_TLVS) - 1) / 2; + if (len > sizeof(dataset.mTlvs)) { + ESP_LOGW(TAG, "TLV buffer too small, truncating"); + len = sizeof(dataset.mTlvs); + } + parse_hex(USE_OPENTHREAD_TLVS, sizeof(USE_OPENTHREAD_TLVS) - 1, dataset.mTlvs, len); + dataset.mLength = len; + } +#endif + + // Pass the existing dataset, or NULL which will use the preprocessor definitions + ESP_ERROR_CHECK(esp_openthread_auto_start(dataset.mLength > 0 ? &dataset : nullptr)); + esp_openthread_launch_mainloop(); // Clean up diff --git a/esphome/components/openthread/tlv.py b/esphome/components/openthread/tlv.py deleted file mode 100644 index 4a7d21c47d..0000000000 --- a/esphome/components/openthread/tlv.py +++ /dev/null @@ -1,65 +0,0 @@ -# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9 -import binascii -import ipaddress - -from esphome.const import CONF_CHANNEL - -from . import ( - CONF_EXT_PAN_ID, - CONF_MESH_LOCAL_PREFIX, - CONF_NETWORK_KEY, - CONF_NETWORK_NAME, - CONF_PAN_ID, - CONF_PSKC, -) - -TLV_TYPES = { - 0: CONF_CHANNEL, - 1: CONF_PAN_ID, - 2: CONF_EXT_PAN_ID, - 3: CONF_NETWORK_NAME, - 4: CONF_PSKC, - 5: CONF_NETWORK_KEY, - 7: CONF_MESH_LOCAL_PREFIX, -} - - -def parse_tlv(tlv) -> dict: - data = binascii.a2b_hex(tlv) - output = {} - pos = 0 - while pos < len(data): - tag = data[pos] - pos += 1 - _len = data[pos] - pos += 1 - val = data[pos : pos + _len] - pos += _len - if tag in TLV_TYPES: - if tag == 3: - output[TLV_TYPES[tag]] = val.decode("utf-8") - elif tag == 7: - mesh_local_prefix = binascii.hexlify(val).decode("utf-8") - mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000" - ipv6_bytes = bytes.fromhex(mesh_local_prefix_str) - ipv6_address = ipaddress.IPv6Address(ipv6_bytes) - output[TLV_TYPES[tag]] = f"{ipv6_address}/64" - else: - output[TLV_TYPES[tag]] = int.from_bytes(val) - return output - - -def main(): - import sys - - args = sys.argv[1:] - parsed = parse_tlv(args[0]) - # print the parsed TLV data - for key, value in parsed.items(): - if isinstance(value, bytes): - value = value.hex() - print(f"{key}: {value}") - - -if __name__ == "__main__": - main() From 9a0ba1657e0140b90a03aa9b1dcc1af4a4c61ff2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 21:38:19 -0500 Subject: [PATCH 08/35] Fix entity hash collisions by enforcing unique names across devices per platform (#9276) --- esphome/core/entity_helpers.py | 22 +-- ...ies_not_allowed_on_different_devices.yaml} | 80 ++++++---- tests/integration/test_duplicate_entities.py | 137 +++++++++++------- tests/unit_tests/core/test_entity_helpers.py | 40 +++-- 4 files changed, 175 insertions(+), 104 deletions(-) rename tests/integration/fixtures/{duplicate_entities_on_different_devices.yaml => duplicate_entities_not_allowed_on_different_devices.yaml} (61%) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index c95acebbf9..2442fbca4b 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -184,25 +184,27 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy # No name to validate return config - # Get the entity name and device info + # Get the entity name entity_name = config[CONF_NAME] - device_id = "" # Empty string for main device + # Get device name if entity is on a sub-device + device_name = None if CONF_DEVICE_ID in config: device_id_obj = config[CONF_DEVICE_ID] - # Use the device ID string directly for uniqueness - device_id = device_id_obj.id + device_name = device_id_obj.id - # For duplicate detection, just use the sanitized name - name_key = sanitize(snake_case(entity_name)) + # Calculate what object_id will actually be used + # This handles empty names correctly by using device/friendly names + name_key = get_base_entity_object_id( + entity_name, CORE.friendly_name, device_name + ) # Check for duplicates - unique_key = (device_id, platform, name_key) + unique_key = (platform, name_key) if unique_key in CORE.unique_ids: - device_prefix = f" on device '{device_id}'" if device_id else "" raise cv.Invalid( - f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " - f"Each entity on a device must have a unique name within its platform." + f"Duplicate {platform} entity with name '{entity_name}' found. " + f"Each entity must have a unique name within its platform across all devices." ) # Add to tracking set diff --git a/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml b/tests/integration/fixtures/duplicate_entities_not_allowed_on_different_devices.yaml similarity index 61% rename from tests/integration/fixtures/duplicate_entities_on_different_devices.yaml rename to tests/integration/fixtures/duplicate_entities_not_allowed_on_different_devices.yaml index ecc502ad28..f7d017a0ae 100644 --- a/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml +++ b/tests/integration/fixtures/duplicate_entities_not_allowed_on_different_devices.yaml @@ -1,6 +1,6 @@ esphome: name: duplicate-entities-test - # Define devices to test multi-device duplicate handling + # Define devices to test multi-device unique name validation devices: - id: controller_1 name: Controller 1 @@ -13,31 +13,31 @@ host: api: # Port will be automatically injected logger: -# Test that duplicate entity names are allowed on different devices +# Test that duplicate entity names are NOT allowed on different devices -# Scenario 1: Same sensor name on different devices (allowed) +# Scenario 1: Different sensor names on different devices (allowed) sensor: - platform: template - name: Temperature + name: Temperature Controller 1 device_id: controller_1 lambda: return 21.0; update_interval: 0.1s - platform: template - name: Temperature + name: Temperature Controller 2 device_id: controller_2 lambda: return 22.0; update_interval: 0.1s - platform: template - name: Temperature + name: Temperature Controller 3 device_id: controller_3 lambda: return 23.0; update_interval: 0.1s # Main device sensor (no device_id) - platform: template - name: Temperature + name: Temperature Main lambda: return 20.0; update_interval: 0.1s @@ -47,20 +47,20 @@ sensor: lambda: return 60.0; update_interval: 0.1s -# Scenario 2: Same binary sensor name on different devices (allowed) +# Scenario 2: Different binary sensor names on different devices binary_sensor: - platform: template - name: Status + name: Status Controller 1 device_id: controller_1 lambda: return true; - platform: template - name: Status + name: Status Controller 2 device_id: controller_2 lambda: return false; - platform: template - name: Status + name: Status Main lambda: return true; # Main device # Different platform can have same name as sensor @@ -68,43 +68,43 @@ binary_sensor: name: Temperature lambda: return true; -# Scenario 3: Same text sensor name on different devices +# Scenario 3: Different text sensor names on different devices text_sensor: - platform: template - name: Device Info + name: Device Info Controller 1 device_id: controller_1 lambda: return {"Controller 1 Active"}; update_interval: 0.1s - platform: template - name: Device Info + name: Device Info Controller 2 device_id: controller_2 lambda: return {"Controller 2 Active"}; update_interval: 0.1s - platform: template - name: Device Info + name: Device Info Main lambda: return {"Main Device Active"}; update_interval: 0.1s -# Scenario 4: Same switch name on different devices +# Scenario 4: Different switch names on different devices switch: - platform: template - name: Power + name: Power Controller 1 device_id: controller_1 lambda: return false; turn_on_action: [] turn_off_action: [] - platform: template - name: Power + name: Power Controller 2 device_id: controller_2 lambda: return true; turn_on_action: [] turn_off_action: [] - platform: template - name: Power + name: Power Controller 3 device_id: controller_3 lambda: return false; turn_on_action: [] @@ -117,26 +117,54 @@ switch: turn_on_action: [] turn_off_action: [] -# Scenario 5: Empty names on different devices (should use device name) +# Scenario 5: Buttons with unique names button: - platform: template - name: "" + name: "Reset Controller 1" device_id: controller_1 on_press: [] - platform: template - name: "" + name: "Reset Controller 2" device_id: controller_2 on_press: [] - platform: template - name: "" + name: "Reset Main" on_press: [] # Main device -# Scenario 6: Special characters in names +# Scenario 6: Empty names (should use device names) +select: + - platform: template + name: "" + device_id: controller_1 + options: + - "Option 1" + - "Option 2" + lambda: return {"Option 1"}; + set_action: [] + + - platform: template + name: "" + device_id: controller_2 + options: + - "Option 1" + - "Option 2" + lambda: return {"Option 1"}; + set_action: [] + + - platform: template + name: "" # Main device + options: + - "Option 1" + - "Option 2" + lambda: return {"Option 1"}; + set_action: [] + +# Scenario 7: Special characters in names - now with unique names number: - platform: template - name: "Temperature Setpoint!" + name: "Temperature Setpoint! Controller 1" device_id: controller_1 min_value: 10.0 max_value: 30.0 @@ -145,7 +173,7 @@ number: set_action: [] - platform: template - name: "Temperature Setpoint!" + name: "Temperature Setpoint! Controller 2" device_id: controller_2 min_value: 10.0 max_value: 30.0 diff --git a/tests/integration/test_duplicate_entities.py b/tests/integration/test_duplicate_entities.py index 99968204d4..b7ee8dd478 100644 --- a/tests/integration/test_duplicate_entities.py +++ b/tests/integration/test_duplicate_entities.py @@ -11,12 +11,12 @@ from .types import APIClientConnectedFactory, RunCompiledFunction @pytest.mark.asyncio -async def test_duplicate_entities_on_different_devices( +async def test_duplicate_entities_not_allowed_on_different_devices( yaml_config: str, run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, ) -> None: - """Test that duplicate entity names are allowed on different devices.""" + """Test that duplicate entity names are NOT allowed on different devices.""" async with run_compiled(yaml_config), api_client_connected() as client: # Get device info device_info = await client.device_info() @@ -52,42 +52,46 @@ async def test_duplicate_entities_on_different_devices( switches = [e for e in all_entities if e.__class__.__name__ == "SwitchInfo"] buttons = [e for e in all_entities if e.__class__.__name__ == "ButtonInfo"] numbers = [e for e in all_entities if e.__class__.__name__ == "NumberInfo"] + selects = [e for e in all_entities if e.__class__.__name__ == "SelectInfo"] - # Scenario 1: Check sensors with same "Temperature" name on different devices - temp_sensors = [s for s in sensors if s.name == "Temperature"] + # Scenario 1: Check that temperature sensors have unique names per device + temp_sensors = [s for s in sensors if "Temperature" in s.name] assert len(temp_sensors) == 4, ( f"Expected exactly 4 temperature sensors, got {len(temp_sensors)}" ) - # Verify each sensor is on a different device - temp_device_ids = set() + # Verify each sensor has a unique name + temp_names = set() temp_object_ids = set() for sensor in temp_sensors: - temp_device_ids.add(sensor.device_id) + temp_names.add(sensor.name) temp_object_ids.add(sensor.object_id) - # All should have object_id "temperature" (no suffix) - assert sensor.object_id == "temperature", ( - f"Expected object_id 'temperature', got '{sensor.object_id}'" - ) - - # Should have 4 different device IDs (including None for main device) - assert len(temp_device_ids) == 4, ( - f"Temperature sensors should be on different devices, got {temp_device_ids}" + # Should have 4 unique names + assert len(temp_names) == 4, ( + f"Temperature sensors should have unique names, got {temp_names}" ) - # Scenario 2: Check binary sensors "Status" on different devices - status_binary = [b for b in binary_sensors if b.name == "Status"] + # Object IDs should also be unique + assert len(temp_object_ids) == 4, ( + f"Temperature sensors should have unique object_ids, got {temp_object_ids}" + ) + + # Scenario 2: Check binary sensors have unique names + status_binary = [b for b in binary_sensors if "Status" in b.name] assert len(status_binary) == 3, ( f"Expected exactly 3 status binary sensors, got {len(status_binary)}" ) - # All should have object_id "status" + # All should have unique object_ids + status_names = set() for binary in status_binary: - assert binary.object_id == "status", ( - f"Expected object_id 'status', got '{binary.object_id}'" - ) + status_names.add(binary.name) + + assert len(status_names) == 3, ( + f"Status binary sensors should have unique names, got {status_names}" + ) # Scenario 3: Check that sensor and binary_sensor can have same name temp_binary = [b for b in binary_sensors if b.name == "Temperature"] @@ -96,62 +100,86 @@ async def test_duplicate_entities_on_different_devices( ) assert temp_binary[0].object_id == "temperature" - # Scenario 4: Check text sensors "Device Info" on different devices - info_text = [t for t in text_sensors if t.name == "Device Info"] + # Scenario 4: Check text sensors have unique names + info_text = [t for t in text_sensors if "Device Info" in t.name] assert len(info_text) == 3, ( f"Expected exactly 3 device info text sensors, got {len(info_text)}" ) - # All should have object_id "device_info" + # All should have unique names and object_ids + info_names = set() for text in info_text: - assert text.object_id == "device_info", ( - f"Expected object_id 'device_info', got '{text.object_id}'" - ) + info_names.add(text.name) - # Scenario 5: Check switches "Power" on different devices - power_switches = [s for s in switches if s.name == "Power"] - assert len(power_switches) == 3, ( - f"Expected exactly 3 power switches, got {len(power_switches)}" + assert len(info_names) == 3, ( + f"Device info text sensors should have unique names, got {info_names}" ) - # All should have object_id "power" - for switch in power_switches: - assert switch.object_id == "power", ( - f"Expected object_id 'power', got '{switch.object_id}'" - ) + # Scenario 5: Check switches have unique names + power_switches = [s for s in switches if "Power" in s.name] + assert len(power_switches) == 4, ( + f"Expected exactly 4 power switches, got {len(power_switches)}" + ) - # Scenario 6: Check empty name buttons (should use device name) - empty_buttons = [b for b in buttons if b.name == ""] - assert len(empty_buttons) == 3, ( - f"Expected exactly 3 empty name buttons, got {len(empty_buttons)}" + # All should have unique names + power_names = set() + for switch in power_switches: + power_names.add(switch.name) + + assert len(power_names) == 4, ( + f"Power switches should have unique names, got {power_names}" + ) + + # Scenario 6: Check reset buttons have unique names + reset_buttons = [b for b in buttons if "Reset" in b.name] + assert len(reset_buttons) == 3, ( + f"Expected exactly 3 reset buttons, got {len(reset_buttons)}" + ) + + # All should have unique names + reset_names = set() + for button in reset_buttons: + reset_names.add(button.name) + + assert len(reset_names) == 3, ( + f"Reset buttons should have unique names, got {reset_names}" + ) + + # Scenario 7: Check empty name selects (should use device names) + empty_selects = [s for s in selects if s.name == ""] + assert len(empty_selects) == 3, ( + f"Expected exactly 3 empty name selects, got {len(empty_selects)}" ) # Group by device - c1_buttons = [b for b in empty_buttons if b.device_id == controller_1.device_id] - c2_buttons = [b for b in empty_buttons if b.device_id == controller_2.device_id] + c1_selects = [s for s in empty_selects if s.device_id == controller_1.device_id] + c2_selects = [s for s in empty_selects if s.device_id == controller_2.device_id] # For main device, device_id is 0 - main_buttons = [b for b in empty_buttons if b.device_id == 0] + main_selects = [s for s in empty_selects if s.device_id == 0] - # Check object IDs for empty name entities - assert len(c1_buttons) == 1 and c1_buttons[0].object_id == "controller_1" - assert len(c2_buttons) == 1 and c2_buttons[0].object_id == "controller_2" + # Check object IDs for empty name entities - they should use device names + assert len(c1_selects) == 1 and c1_selects[0].object_id == "controller_1" + assert len(c2_selects) == 1 and c2_selects[0].object_id == "controller_2" assert ( - len(main_buttons) == 1 - and main_buttons[0].object_id == "duplicate-entities-test" + len(main_selects) == 1 + and main_selects[0].object_id == "duplicate-entities-test" ) - # Scenario 7: Check special characters in number names - temp_numbers = [n for n in numbers if n.name == "Temperature Setpoint!"] + # Scenario 8: Check special characters in number names - now unique + temp_numbers = [n for n in numbers if "Temperature Setpoint!" in n.name] assert len(temp_numbers) == 2, ( f"Expected exactly 2 temperature setpoint numbers, got {len(temp_numbers)}" ) - # Special characters should be sanitized to _ in object_id + # Should have unique names + setpoint_names = set() for number in temp_numbers: - assert number.object_id == "temperature_setpoint_", ( - f"Expected object_id 'temperature_setpoint_', got '{number.object_id}'" - ) + setpoint_names.add(number.name) + + assert len(setpoint_names) == 2, ( + f"Temperature setpoint numbers should have unique names, got {setpoint_names}" + ) # Verify we can get states for all entities (ensures they're functional) loop = asyncio.get_running_loop() @@ -164,6 +192,7 @@ async def test_duplicate_entities_on_different_devices( + len(switches) + len(buttons) + len(numbers) + + len(selects) ) def on_state(state) -> None: diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index e166eeedee..0dcdd84507 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -505,13 +505,13 @@ def test_entity_duplicate_validator() -> None: config1 = {CONF_NAME: "Temperature"} validated1 = validator(config1) assert validated1 == config1 - assert ("", "sensor", "temperature") in CORE.unique_ids + assert ("sensor", "temperature") in CORE.unique_ids # Second entity with different name should pass config2 = {CONF_NAME: "Humidity"} validated2 = validator(config2) assert validated2 == config2 - assert ("", "sensor", "humidity") in CORE.unique_ids + assert ("sensor", "humidity") in CORE.unique_ids # Duplicate entity should fail config3 = {CONF_NAME: "Temperature"} @@ -535,24 +535,36 @@ def test_entity_duplicate_validator_with_devices() -> None: device1 = ID("device1", type="Device") device2 = ID("device2", type="Device") - # Same name on different devices should pass + # First entity on device1 should pass config1 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1} validated1 = validator(config1) assert validated1 == config1 - assert ("device1", "sensor", "temperature") in CORE.unique_ids + assert ("sensor", "temperature") in CORE.unique_ids + # Same name on different device should now fail config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device2} - validated2 = validator(config2) - assert validated2 == config2 - assert ("device2", "sensor", "temperature") in CORE.unique_ids - - # Duplicate on same device should fail - config3 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1} with pytest.raises( Invalid, - match=r"Duplicate sensor entity with name 'Temperature' found on device 'device1'", + match=r"Duplicate sensor entity with name 'Temperature' found. Each entity must have a unique name within its platform across all devices.", ): - validator(config3) + validator(config2) + + # Different name on device2 should pass + config3 = {CONF_NAME: "Humidity", CONF_DEVICE_ID: device2} + validated3 = validator(config3) + assert validated3 == config3 + assert ("sensor", "humidity") in CORE.unique_ids + + # Empty names should use device names and be allowed + config4 = {CONF_NAME: "", CONF_DEVICE_ID: device1} + validated4 = validator(config4) + assert validated4 == config4 + assert ("sensor", "device1") in CORE.unique_ids + + config5 = {CONF_NAME: "", CONF_DEVICE_ID: device2} + validated5 = validator(config5) + assert validated5 == config5 + assert ("sensor", "device2") in CORE.unique_ids def test_duplicate_entity_yaml_validation( @@ -576,10 +588,10 @@ def test_duplicate_entity_with_devices_yaml_validation( ) assert result is None - # Check for the duplicate entity error message with device + # Check for the duplicate entity error message captured = capsys.readouterr() assert ( - "Duplicate sensor entity with name 'Temperature' found on device 'device1'" + "Duplicate sensor entity with name 'Temperature' found. Each entity must have a unique name within its platform across all devices." in captured.out ) From 27c745d5a10d7b7768619ceb9143bf61ac06ae5f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:38:39 +1200 Subject: [PATCH 09/35] [host] Disable platformio ldf (#9277) --- esphome/components/host/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index da75873eaf..d3dbcba6ed 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -44,3 +44,4 @@ async def to_code(config): cg.add_build_flag("-std=gnu++20") cg.add_define("ESPHOME_BOARD", "host") cg.add_platformio_option("platform", "platformio/native") + cg.add_platformio_option("lib_ldf_mode", "off") From 8c34b72b62f69d850a54b0ec511f8b7638352776 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 1 Jul 2025 04:57:00 +0200 Subject: [PATCH 10/35] Jinja expressions in configs (Take #3) (#8955) --- esphome/components/packages/__init__.py | 3 +- esphome/components/substitutions/__init__.py | 95 ++++++++++--- esphome/components/substitutions/jinja.py | 99 ++++++++++++++ esphome/config.py | 1 - esphome/yaml_util.py | 2 - requirements.txt | 1 + .../fixtures/substitutions/.gitignore | 1 + .../substitutions/00-simple_var.approved.yaml | 19 +++ .../substitutions/00-simple_var.input.yaml | 21 +++ .../substitutions/01-include.approved.yaml | 15 +++ .../substitutions/01-include.input.yaml | 15 +++ .../02-expressions.approved.yaml | 24 ++++ .../substitutions/02-expressions.input.yaml | 22 +++ .../substitutions/03-closures.approved.yaml | 17 +++ .../substitutions/03-closures.input.yaml | 16 +++ .../04-display_example.approved.yaml | 5 + .../04-display_example.input.yaml | 7 + .../substitutions/closures_package.yaml | 3 + .../fixtures/substitutions/display.yaml | 11 ++ .../fixtures/substitutions/inc1.yaml | 8 ++ tests/unit_tests/test_substitutions.py | 125 ++++++++++++++++++ 21 files changed, 486 insertions(+), 24 deletions(-) create mode 100644 esphome/components/substitutions/jinja.py create mode 100644 tests/unit_tests/fixtures/substitutions/.gitignore create mode 100644 tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/01-include.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/01-include.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/02-expressions.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/02-expressions.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/03-closures.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/04-display_example.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/04-display_example.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/closures_package.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/display.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/inc1.yaml create mode 100644 tests/unit_tests/test_substitutions.py diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 08ae798282..6eb746ec63 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -74,7 +74,7 @@ BASE_SCHEMA = cv.All( { cv.Required(CONF_PATH): validate_yaml_filename, cv.Optional(CONF_VARS, default={}): cv.Schema( - {cv.string: cv.string} + {cv.string: object} ), } ), @@ -148,7 +148,6 @@ def _process_base_package(config: dict) -> dict: raise cv.Invalid( f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}" ) - vars = {k: str(v) for k, v in vars.items()} new_yaml = yaml_util.substitute_vars(new_yaml, vars) packages[f"{filename}{idx}"] = new_yaml except EsphomeError as e: diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 41e49f70db..5878af43b2 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -5,6 +5,13 @@ from esphome.config_helpers import Extend, Remove, merge_config import esphome.config_validation as cv from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS from esphome.yaml_util import ESPHomeDataBase, make_data_base +from .jinja import ( + Jinja, + JinjaStr, + has_jinja, + TemplateError, + TemplateRuntimeError, +) CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) @@ -28,7 +35,7 @@ def validate_substitution_key(value): CONFIG_SCHEMA = cv.Schema( { - validate_substitution_key: cv.string_strict, + validate_substitution_key: object, } ) @@ -37,7 +44,42 @@ async def to_code(config): pass -def _expand_substitutions(substitutions, value, path, ignore_missing): +def _expand_jinja(value, orig_value, path, jinja, ignore_missing): + if has_jinja(value): + # If the original value passed in to this function is a JinjaStr, it means it contains an unresolved + # Jinja expression from a previous pass. + if isinstance(orig_value, JinjaStr): + # Rebuild the JinjaStr in case it was lost while replacing substitutions. + value = JinjaStr(value, orig_value.upvalues) + try: + # Invoke the jinja engine to evaluate the expression. + value, err = jinja.expand(value) + if err is not None: + if not ignore_missing and "password" not in path: + _LOGGER.warning( + "Found '%s' (see %s) which looks like an expression," + " but could not resolve all the variables: %s", + value, + "->".join(str(x) for x in path), + err.message, + ) + except ( + TemplateError, + TemplateRuntimeError, + RuntimeError, + ArithmeticError, + AttributeError, + TypeError, + ) as err: + raise cv.Invalid( + f"{type(err).__name__} Error evaluating jinja expression '{value}': {str(err)}." + f" See {'->'.join(str(x) for x in path)}", + path, + ) + return value + + +def _expand_substitutions(substitutions, value, path, jinja, ignore_missing): if "$" not in value: return value @@ -47,7 +89,8 @@ def _expand_substitutions(substitutions, value, path, ignore_missing): while True: m = cv.VARIABLE_PROG.search(value, i) if not m: - # Nothing more to match. Done + # No more variable substitutions found. See if the remainder looks like a jinja template + value = _expand_jinja(value, orig_value, path, jinja, ignore_missing) break i, j = m.span(0) @@ -67,8 +110,15 @@ def _expand_substitutions(substitutions, value, path, ignore_missing): continue sub = substitutions[name] + + if i == 0 and j == len(value): + # The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly + # to conserve its type. + value = sub + break + tail = value[j:] - value = value[:i] + sub + value = value[:i] + str(sub) i = len(value) value += tail @@ -77,36 +127,40 @@ def _expand_substitutions(substitutions, value, path, ignore_missing): if isinstance(orig_value, ESPHomeDataBase): # even though string can get larger or smaller, the range should point # to original document marks - return make_data_base(value, orig_value) + value = make_data_base(value, orig_value) return value -def _substitute_item(substitutions, item, path, ignore_missing): +def _substitute_item(substitutions, item, path, jinja, ignore_missing): if isinstance(item, list): for i, it in enumerate(item): - sub = _substitute_item(substitutions, it, path + [i], ignore_missing) + sub = _substitute_item(substitutions, it, path + [i], jinja, ignore_missing) if sub is not None: item[i] = sub elif isinstance(item, dict): replace_keys = [] for k, v in item.items(): if path or k != CONF_SUBSTITUTIONS: - sub = _substitute_item(substitutions, k, path + [k], ignore_missing) + sub = _substitute_item( + substitutions, k, path + [k], jinja, ignore_missing + ) if sub is not None: replace_keys.append((k, sub)) - sub = _substitute_item(substitutions, v, path + [k], ignore_missing) + sub = _substitute_item(substitutions, v, path + [k], 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] elif isinstance(item, str): - sub = _expand_substitutions(substitutions, item, path, ignore_missing) - if sub != item: + sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing) + if isinstance(sub, JinjaStr) or sub != item: return sub elif isinstance(item, (core.Lambda, Extend, Remove)): - sub = _expand_substitutions(substitutions, item.value, path, ignore_missing) + sub = _expand_substitutions( + substitutions, item.value, path, jinja, ignore_missing + ) if sub != item: item.value = sub return None @@ -116,11 +170,11 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals if CONF_SUBSTITUTIONS not in config and not command_line_substitutions: return - substitutions = config.get(CONF_SUBSTITUTIONS) - if substitutions is None: - substitutions = command_line_substitutions - elif command_line_substitutions: - substitutions = {**substitutions, **command_line_substitutions} + # Merge substitutions in config, overriding with substitutions coming from command line: + substitutions = { + **config.get(CONF_SUBSTITUTIONS, {}), + **(command_line_substitutions or {}), + } with cv.prepend_path("substitutions"): if not isinstance(substitutions, dict): raise cv.Invalid( @@ -133,7 +187,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals sub = validate_substitution_key(key) if sub != key: replace_keys.append((key, sub)) - substitutions[key] = cv.string_strict(value) + substitutions[key] = value for old, new in replace_keys: substitutions[new] = substitutions[old] del substitutions[old] @@ -141,4 +195,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals config[CONF_SUBSTITUTIONS] = substitutions # Move substitutions to the first place to replace substitutions in them correctly config.move_to_end(CONF_SUBSTITUTIONS, False) - _substitute_item(substitutions, config, [], ignore_missing) + + # Create a Jinja environment that will consider substitutions in scope: + jinja = Jinja(substitutions) + _substitute_item(substitutions, config, [], jinja, ignore_missing) diff --git a/esphome/components/substitutions/jinja.py b/esphome/components/substitutions/jinja.py new file mode 100644 index 0000000000..9ecdbab844 --- /dev/null +++ b/esphome/components/substitutions/jinja.py @@ -0,0 +1,99 @@ +import logging +import math +import re +import jinja2 as jinja +from jinja2.nativetypes import NativeEnvironment + +TemplateError = jinja.TemplateError +TemplateSyntaxError = jinja.TemplateSyntaxError +TemplateRuntimeError = jinja.TemplateRuntimeError +UndefinedError = jinja.UndefinedError +Undefined = jinja.Undefined + +_LOGGER = logging.getLogger(__name__) + +DETECT_JINJA = r"(\$\{)" +detect_jinja_re = re.compile( + r"<%.+?%>" # Block form expression: <% ... %> + r"|\$\{[^}]+\}", # Braced form expression: ${ ... } + flags=re.MULTILINE, +) + + +def has_jinja(st): + return detect_jinja_re.search(st) is not None + + +class JinjaStr(str): + """ + Wraps a string containing an unresolved Jinja expression, + storing the variables visible to it when it failed to resolve. + For example, an expression inside a package, `${ A * B }` may fail + to resolve at package parsing time if `A` is a local package var + but `B` is a substitution defined in the root yaml. + Therefore, we store the value of `A` as an upvalue bound + to the original string so we may be able to resolve `${ A * B }` + later in the main substitutions pass. + """ + + def __new__(cls, value: str, upvalues=None): + obj = super().__new__(cls, value) + obj.upvalues = upvalues or {} + return obj + + def __init__(self, value: str, upvalues=None): + self.upvalues = upvalues or {} + + +class Jinja: + """ + Wraps a Jinja environment + """ + + def __init__(self, context_vars): + self.env = NativeEnvironment( + trim_blocks=True, + lstrip_blocks=True, + block_start_string="<%", + block_end_string="%>", + line_statement_prefix="#", + line_comment_prefix="##", + variable_start_string="${", + variable_end_string="}", + undefined=jinja.StrictUndefined, + ) + self.env.add_extension("jinja2.ext.do") + self.env.globals["math"] = math # Inject entire math module + self.context_vars = {**context_vars} + self.env.globals = {**self.env.globals, **self.context_vars} + + def expand(self, content_str): + """ + Renders a string that may contain Jinja expressions or statements + Returns the resulting processed string if all values could be resolved. + Otherwise, it returns a tagged (JinjaStr) string that captures variables + in scope (upvalues), like a closure for later evaluation. + """ + result = None + override_vars = {} + if isinstance(content_str, JinjaStr): + # If `value` is already a JinjaStr, it means we are trying to evaluate it again + # in a parent pass. + # Hopefully, all required variables are visible now. + override_vars = content_str.upvalues + try: + template = self.env.from_string(content_str) + result = template.render(override_vars) + if isinstance(result, Undefined): + # This happens when the expression is simply an undefined variable. Jinja does not + # raise an exception, instead we get "Undefined". + # Trigger an UndefinedError exception so we skip to below. + print("" + result) + except (TemplateSyntaxError, UndefinedError) as err: + # `content_str` contains a Jinja expression that refers to a variable that is undefined + # in this scope. Perhaps it refers to a root substitution that is not visible yet. + # Therefore, return the original `content_str` as a JinjaStr, which contains the variables + # that are actually visible to it at this point to postpone evaluation. + return JinjaStr(content_str, {**self.context_vars, **override_vars}), err + + return result, None diff --git a/esphome/config.py b/esphome/config.py index ca3686a0e6..73cc7657cc 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -789,7 +789,6 @@ def validate_config( result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS) try: substitutions.do_substitution_pass(config, command_line_substitutions) - substitutions.do_substitution_pass(config, command_line_substitutions) except vol.Invalid as err: result.add_error(err) return result diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index bd1806affc..e52fc9e788 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -292,8 +292,6 @@ class ESPHomeLoaderMixin: if file is None: raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark) vars = fields.get(CONF_VARS) - if vars: - vars = {k: str(v) for k, v in vars.items()} return file, vars if isinstance(node, yaml.nodes.MappingNode): diff --git a/requirements.txt b/requirements.txt index 12f3b84359..1010a311d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ esphome-glyphsets==0.2.0 pillow==10.4.0 cairosvg==2.8.2 freetype-py==2.5.1 +jinja2==3.1.6 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 diff --git a/tests/unit_tests/fixtures/substitutions/.gitignore b/tests/unit_tests/fixtures/substitutions/.gitignore new file mode 100644 index 0000000000..0b15cdb2b7 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/.gitignore @@ -0,0 +1 @@ +*.received.yaml \ No newline at end of file diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml new file mode 100644 index 0000000000..c031399c37 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml @@ -0,0 +1,19 @@ +substitutions: + var1: '1' + var2: '2' + var21: '79' +esphome: + name: test +test_list: + - '1' + - '1' + - '1' + - '1' + - 'Values: 1 2' + - 'Value: 79' + - 1 + 2 + - 1 * 2 + - 'Undefined var: ${undefined_var}' + - ${undefined_var} + - $undefined_var + - ${ undefined_var } diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml new file mode 100644 index 0000000000..88a4ffb991 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + +substitutions: + var1: "1" + var2: "2" + var21: "79" + +test_list: + - "$var1" + - "${var1}" + - $var1 + - ${var1} + - "Values: $var1 ${var2}" + - "Value: ${var2${var1}}" + - "$var1 + $var2" + - "${ var1 } * ${ var2 }" + - "Undefined var: ${undefined_var}" + - ${undefined_var} + - $undefined_var + - ${ undefined_var } diff --git a/tests/unit_tests/fixtures/substitutions/01-include.approved.yaml b/tests/unit_tests/fixtures/substitutions/01-include.approved.yaml new file mode 100644 index 0000000000..a812fedcfd --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/01-include.approved.yaml @@ -0,0 +1,15 @@ +substitutions: + var1: '1' + var2: '2' + a: alpha +test_list: + - values: + - var1: '1' + - a: A + - b: B-default + - c: The value of C is C + - values: + - var1: '1' + - a: alpha + - b: beta + - c: The value of C is $c diff --git a/tests/unit_tests/fixtures/substitutions/01-include.input.yaml b/tests/unit_tests/fixtures/substitutions/01-include.input.yaml new file mode 100644 index 0000000000..d3daa681a4 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/01-include.input.yaml @@ -0,0 +1,15 @@ +substitutions: + var1: "1" + var2: "2" + a: "alpha" + +test_list: + - !include + file: inc1.yaml + vars: + a: "A" + c: "C" + - !include + file: inc1.yaml + vars: + b: "beta" diff --git a/tests/unit_tests/fixtures/substitutions/02-expressions.approved.yaml b/tests/unit_tests/fixtures/substitutions/02-expressions.approved.yaml new file mode 100644 index 0000000000..9e401ec5d6 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/02-expressions.approved.yaml @@ -0,0 +1,24 @@ +substitutions: + width: 7 + height: 8 + enabled: true + pin: &id001 + number: 18 + inverted: true + area: 25 + numberOne: 1 + var1: 79 +test_list: + - The area is 56 + - 56 + - 56 + 1 + - ENABLED + - list: + - 7 + - 8 + - width: 7 + height: 8 + - *id001 + - The pin number is 18 + - The square root is: 5.0 + - The number is 80 diff --git a/tests/unit_tests/fixtures/substitutions/02-expressions.input.yaml b/tests/unit_tests/fixtures/substitutions/02-expressions.input.yaml new file mode 100644 index 0000000000..1777b46f67 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/02-expressions.input.yaml @@ -0,0 +1,22 @@ +substitutions: + width: 7 + height: 8 + enabled: true + pin: + number: 18 + inverted: true + area: 25 + numberOne: 1 + var1: 79 + +test_list: + - "The area is ${width * height}" + - ${width * height} + - ${width * height} + 1 + - ${enabled and "ENABLED" or "DISABLED"} + - list: ${ [width, height] } + - "${ {'width': width, 'height': height} }" + - ${pin} + - The pin number is ${pin.number} + - The square root is: ${math.sqrt(area)} + - The number is ${var${numberOne} + 1} diff --git a/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml b/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml new file mode 100644 index 0000000000..c8f7d9976c --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/03-closures.approved.yaml @@ -0,0 +1,17 @@ +substitutions: + B: 5 + var7: 79 +package_result: + - The value of A*B is 35, where A is a package var and B is a substitution in the + root file + - Double substitution also works; the value of var7 is 79, where A is a package + var +local_results: + - The value of B is 5 + - 'You will see, however, that + + ${A} is not substituted here, since + + it is out of scope. + + ' diff --git a/tests/unit_tests/fixtures/substitutions/03-closures.input.yaml b/tests/unit_tests/fixtures/substitutions/03-closures.input.yaml new file mode 100644 index 0000000000..e0b2c39e52 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/03-closures.input.yaml @@ -0,0 +1,16 @@ +substitutions: + B: 5 + var7: 79 + +packages: + closures_package: !include + file: closures_package.yaml + vars: + A: 7 + +local_results: + - The value of B is ${B} + - | + You will see, however, that + ${A} is not substituted here, since + it is out of scope. diff --git a/tests/unit_tests/fixtures/substitutions/04-display_example.approved.yaml b/tests/unit_tests/fixtures/substitutions/04-display_example.approved.yaml new file mode 100644 index 0000000000..f559181b45 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/04-display_example.approved.yaml @@ -0,0 +1,5 @@ +display: + - platform: ili9xxx + dimensions: + width: 960 + height: 544 diff --git a/tests/unit_tests/fixtures/substitutions/04-display_example.input.yaml b/tests/unit_tests/fixtures/substitutions/04-display_example.input.yaml new file mode 100644 index 0000000000..9d8f64a253 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/04-display_example.input.yaml @@ -0,0 +1,7 @@ +# main.yaml +packages: + my_display: !include + file: display.yaml + vars: + high_dpi: true + native_height: 272 diff --git a/tests/unit_tests/fixtures/substitutions/closures_package.yaml b/tests/unit_tests/fixtures/substitutions/closures_package.yaml new file mode 100644 index 0000000000..e87908814d --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/closures_package.yaml @@ -0,0 +1,3 @@ +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 diff --git a/tests/unit_tests/fixtures/substitutions/display.yaml b/tests/unit_tests/fixtures/substitutions/display.yaml new file mode 100644 index 0000000000..1e2249dddb --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/display.yaml @@ -0,0 +1,11 @@ +# display.yaml + +defaults: + native_width: 480 + native_height: 480 + +display: + - platform: ili9xxx + dimensions: + width: ${high_dpi and native_width * 2 or native_width} + height: ${high_dpi and native_height * 2 or native_height} diff --git a/tests/unit_tests/fixtures/substitutions/inc1.yaml b/tests/unit_tests/fixtures/substitutions/inc1.yaml new file mode 100644 index 0000000000..65b91a5e16 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/inc1.yaml @@ -0,0 +1,8 @@ +defaults: + b: "B-default" + +values: + - var1: $var1 + - a: $a + - b: ${b} + - c: The value of C is $c diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py new file mode 100644 index 0000000000..b377499d29 --- /dev/null +++ b/tests/unit_tests/test_substitutions.py @@ -0,0 +1,125 @@ +import glob +import logging +import os + +from esphome import yaml_util +from esphome.components import substitutions +from esphome.const import CONF_PACKAGES + +_LOGGER = logging.getLogger(__name__) + +# Set to True for dev mode behavior +# This will generate the expected version of the test files. + +DEV_MODE = False + + +def sort_dicts(obj): + """Recursively sort dictionaries for order-insensitive comparison.""" + if isinstance(obj, dict): + return {k: sort_dicts(obj[k]) for k in sorted(obj)} + elif isinstance(obj, list): + # Lists are not sorted; we preserve order + return [sort_dicts(i) for i in obj] + else: + return obj + + +def dict_diff(a, b, path=""): + """Recursively find differences between two dict/list structures.""" + diffs = [] + if isinstance(a, dict) and isinstance(b, dict): + a_keys = set(a) + b_keys = set(b) + for key in a_keys - b_keys: + diffs.append(f"{path}/{key} only in actual") + for key in b_keys - a_keys: + diffs.append(f"{path}/{key} only in expected") + for key in a_keys & b_keys: + diffs.extend(dict_diff(a[key], b[key], f"{path}/{key}")) + elif isinstance(a, list) and isinstance(b, list): + min_len = min(len(a), len(b)) + for i in range(min_len): + diffs.extend(dict_diff(a[i], b[i], f"{path}[{i}]")) + if len(a) > len(b): + for i in range(min_len, len(a)): + diffs.append(f"{path}[{i}] only in actual: {a[i]!r}") + elif len(b) > len(a): + for i in range(min_len, len(b)): + diffs.append(f"{path}[{i}] only in expected: {b[i]!r}") + else: + if a != b: + diffs.append(f"\t{path}: actual={a!r} expected={b!r}") + return diffs + + +def write_yaml(path, data): + with open(path, "w", encoding="utf-8") as f: + f.write(yaml_util.dump(data)) + + +def test_substitutions_fixtures(fixture_path): + base_dir = fixture_path / "substitutions" + sources = sorted(glob.glob(str(base_dir / "*.input.yaml"))) + assert sources, f"No input YAML files found in {base_dir}" + + failures = [] + for source_path in sources: + try: + expected_path = source_path.replace(".input.yaml", ".approved.yaml") + test_case = os.path.splitext(os.path.basename(source_path))[0].replace( + ".input", "" + ) + + # Load using ESPHome's YAML loader + config = yaml_util.load_yaml(source_path) + + if CONF_PACKAGES in config: + from esphome.components.packages import do_packages_pass + + config = do_packages_pass(config) + + substitutions.do_substitution_pass(config, None) + + # Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE + if os.path.isfile(expected_path): + expected = yaml_util.load_yaml(expected_path) + elif DEV_MODE: + expected = {} + else: + assert os.path.isfile(expected_path), ( + f"Expected file missing: {expected_path}" + ) + + # Sort dicts only (not lists) for comparison + got_sorted = sort_dicts(config) + expected_sorted = sort_dicts(expected) + + if got_sorted != expected_sorted: + diff = "\n".join(dict_diff(got_sorted, expected_sorted)) + msg = ( + f"Substitution result mismatch for {os.path.basename(source_path)}\n" + f"Diff:\n{diff}\n\n" + f"Got: {got_sorted}\n" + f"Expected: {expected_sorted}" + ) + # Write out the received file when test fails + if DEV_MODE: + received_path = os.path.join( + os.path.dirname(source_path), f"{test_case}.received.yaml" + ) + write_yaml(received_path, config) + print(msg) + failures.append(msg) + else: + raise AssertionError(msg) + except Exception as err: + _LOGGER.error("Error in test file %s", source_path) + raise err + + if DEV_MODE and failures: + print(f"\n{len(failures)} substitution test case(s) failed.") + + if DEV_MODE: + _LOGGER.error("Tests passed, but Dev mode is enabled.") + assert not DEV_MODE # make sure DEV_MODE is disabled after you are finished. From e3ccb9b46c9b0c5535d9d3c5bbdefe03b0c1d7b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 22:04:50 -0500 Subject: [PATCH 11/35] Use interrupt based approach for esp32_touch (#9059) Co-authored-by: Keith Burzinski --- .../components/esp32_touch/esp32_touch.cpp | 355 ---------------- esphome/components/esp32_touch/esp32_touch.h | 209 +++++++-- .../esp32_touch/esp32_touch_common.cpp | 159 +++++++ .../components/esp32_touch/esp32_touch_v1.cpp | 240 +++++++++++ .../components/esp32_touch/esp32_touch_v2.cpp | 398 ++++++++++++++++++ 5 files changed, 975 insertions(+), 386 deletions(-) delete mode 100644 esphome/components/esp32_touch/esp32_touch.cpp create mode 100644 esphome/components/esp32_touch/esp32_touch_common.cpp create mode 100644 esphome/components/esp32_touch/esp32_touch_v1.cpp create mode 100644 esphome/components/esp32_touch/esp32_touch_v2.cpp diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp deleted file mode 100644 index 366aa10697..0000000000 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#ifdef USE_ESP32 - -#include "esp32_touch.h" -#include "esphome/core/application.h" -#include "esphome/core/log.h" -#include "esphome/core/hal.h" - -#include - -namespace esphome { -namespace esp32_touch { - -static const char *const TAG = "esp32_touch"; - -void ESP32TouchComponent::setup() { - ESP_LOGCONFIG(TAG, "Running setup"); - touch_pad_init(); -// set up and enable/start filtering based on ESP32 variant -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - if (this->filter_configured_()) { - touch_filter_config_t filter_info = { - .mode = this->filter_mode_, - .debounce_cnt = this->debounce_count_, - .noise_thr = this->noise_threshold_, - .jitter_step = this->jitter_step_, - .smh_lvl = this->smooth_level_, - }; - touch_pad_filter_set_config(&filter_info); - touch_pad_filter_enable(); - } - - if (this->denoise_configured_()) { - touch_pad_denoise_t denoise = { - .grade = this->grade_, - .cap_level = this->cap_level_, - }; - touch_pad_denoise_set_config(&denoise); - touch_pad_denoise_enable(); - } - - if (this->waterproof_configured_()) { - touch_pad_waterproof_t waterproof = { - .guard_ring_pad = this->waterproof_guard_ring_pad_, - .shield_driver = this->waterproof_shield_driver_, - }; - touch_pad_waterproof_set_config(&waterproof); - touch_pad_waterproof_enable(); - } -#else - if (this->iir_filter_enabled_()) { - touch_pad_filter_start(this->iir_filter_); - } -#endif - -#if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) - touch_pad_set_measurement_clock_cycles(this->meas_cycle_); - touch_pad_set_measurement_interval(this->sleep_cycle_); -#else - touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); -#endif - touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); - - for (auto *child : this->children_) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_config(child->get_touch_pad()); -#else - // Disable interrupt threshold - touch_pad_config(child->get_touch_pad(), 0); -#endif - } -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - touch_pad_fsm_start(); -#endif -} - -void ESP32TouchComponent::dump_config() { - ESP_LOGCONFIG(TAG, - "Config for ESP32 Touch Hub:\n" - " Meas cycle: %.2fms\n" - " Sleep cycle: %.2fms", - this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f)); - - const char *lv_s; - switch (this->low_voltage_reference_) { - case TOUCH_LVOLT_0V5: - lv_s = "0.5V"; - break; - case TOUCH_LVOLT_0V6: - lv_s = "0.6V"; - break; - case TOUCH_LVOLT_0V7: - lv_s = "0.7V"; - break; - case TOUCH_LVOLT_0V8: - lv_s = "0.8V"; - break; - default: - lv_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Low Voltage Reference: %s", lv_s); - - const char *hv_s; - switch (this->high_voltage_reference_) { - case TOUCH_HVOLT_2V4: - hv_s = "2.4V"; - break; - case TOUCH_HVOLT_2V5: - hv_s = "2.5V"; - break; - case TOUCH_HVOLT_2V6: - hv_s = "2.6V"; - break; - case TOUCH_HVOLT_2V7: - hv_s = "2.7V"; - break; - default: - hv_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " High Voltage Reference: %s", hv_s); - - const char *atten_s; - switch (this->voltage_attenuation_) { - case TOUCH_HVOLT_ATTEN_1V5: - atten_s = "1.5V"; - break; - case TOUCH_HVOLT_ATTEN_1V: - atten_s = "1V"; - break; - case TOUCH_HVOLT_ATTEN_0V5: - atten_s = "0.5V"; - break; - case TOUCH_HVOLT_ATTEN_0V: - atten_s = "0V"; - break; - default: - atten_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Voltage Attenuation: %s", atten_s); - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - if (this->filter_configured_()) { - const char *filter_mode_s; - switch (this->filter_mode_) { - case TOUCH_PAD_FILTER_IIR_4: - filter_mode_s = "IIR_4"; - break; - case TOUCH_PAD_FILTER_IIR_8: - filter_mode_s = "IIR_8"; - break; - case TOUCH_PAD_FILTER_IIR_16: - filter_mode_s = "IIR_16"; - break; - case TOUCH_PAD_FILTER_IIR_32: - filter_mode_s = "IIR_32"; - break; - case TOUCH_PAD_FILTER_IIR_64: - filter_mode_s = "IIR_64"; - break; - case TOUCH_PAD_FILTER_IIR_128: - filter_mode_s = "IIR_128"; - break; - case TOUCH_PAD_FILTER_IIR_256: - filter_mode_s = "IIR_256"; - break; - case TOUCH_PAD_FILTER_JITTER: - filter_mode_s = "JITTER"; - break; - default: - filter_mode_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, - " Filter mode: %s\n" - " Debounce count: %" PRIu32 "\n" - " Noise threshold coefficient: %" PRIu32 "\n" - " Jitter filter step size: %" PRIu32, - filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_); - const char *smooth_level_s; - switch (this->smooth_level_) { - case TOUCH_PAD_SMOOTH_OFF: - smooth_level_s = "OFF"; - break; - case TOUCH_PAD_SMOOTH_IIR_2: - smooth_level_s = "IIR_2"; - break; - case TOUCH_PAD_SMOOTH_IIR_4: - smooth_level_s = "IIR_4"; - break; - case TOUCH_PAD_SMOOTH_IIR_8: - smooth_level_s = "IIR_8"; - break; - default: - smooth_level_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s); - } - - if (this->denoise_configured_()) { - const char *grade_s; - switch (this->grade_) { - case TOUCH_PAD_DENOISE_BIT12: - grade_s = "BIT12"; - break; - case TOUCH_PAD_DENOISE_BIT10: - grade_s = "BIT10"; - break; - case TOUCH_PAD_DENOISE_BIT8: - grade_s = "BIT8"; - break; - case TOUCH_PAD_DENOISE_BIT4: - grade_s = "BIT4"; - break; - default: - grade_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s); - - const char *cap_level_s; - switch (this->cap_level_) { - case TOUCH_PAD_DENOISE_CAP_L0: - cap_level_s = "L0"; - break; - case TOUCH_PAD_DENOISE_CAP_L1: - cap_level_s = "L1"; - break; - case TOUCH_PAD_DENOISE_CAP_L2: - cap_level_s = "L2"; - break; - case TOUCH_PAD_DENOISE_CAP_L3: - cap_level_s = "L3"; - break; - case TOUCH_PAD_DENOISE_CAP_L4: - cap_level_s = "L4"; - break; - case TOUCH_PAD_DENOISE_CAP_L5: - cap_level_s = "L5"; - break; - case TOUCH_PAD_DENOISE_CAP_L6: - cap_level_s = "L6"; - break; - case TOUCH_PAD_DENOISE_CAP_L7: - cap_level_s = "L7"; - break; - default: - cap_level_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s); - } -#else - if (this->iir_filter_enabled_()) { - ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_); - } else { - ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); - } -#endif - - if (this->setup_mode_) { - ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); - } - - for (auto *child : this->children_) { - LOG_BINARY_SENSOR(" ", "Touch Pad", child); - ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); - ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); - } -} - -uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(tp, &value); - } else { - touch_pad_read_raw_data(tp, &value); - } -#else - uint16_t value = 0; - if (this->iir_filter_enabled_()) { - touch_pad_read_filtered(tp, &value); - } else { - touch_pad_read(tp, &value); - } -#endif - return value; -} - -void ESP32TouchComponent::loop() { - const uint32_t now = App.get_loop_component_start_time(); - bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; - for (auto *child : this->children_) { - child->value_ = this->component_touch_pad_read(child->get_touch_pad()); -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - child->publish_state(child->value_ < child->get_threshold()); -#else - child->publish_state(child->value_ > child->get_threshold()); -#endif - - if (should_print) { - ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), - (uint32_t) child->get_touch_pad(), child->value_); - } - - App.feed_wdt(); - } - - if (should_print) { - // Avoid spamming logs - this->setup_mode_last_log_print_ = now; - } -} - -void ESP32TouchComponent::on_shutdown() { - bool is_wakeup_source = false; - -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - if (this->iir_filter_enabled_()) { - touch_pad_filter_stop(); - touch_pad_filter_delete(); - } -#endif - - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - } - -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - // No filter available when using as wake-up source. - touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); -#endif - } - } - - if (!is_wakeup_source) { - touch_pad_deinit(); - } -} - -ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold) - : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} - -} // namespace esp32_touch -} // namespace esphome - -#endif diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 3fce8a7e18..576c1a5649 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -9,10 +9,26 @@ #include #include +#include +#include namespace esphome { namespace esp32_touch { +// IMPORTANT: Touch detection logic differs between ESP32 variants: +// - ESP32 v1 (original): Touch detected when value < threshold (capacitance increase causes value decrease) +// - ESP32-S2/S3 v2: Touch detected when value > threshold (capacitance increase causes value increase) +// This inversion is due to different hardware implementations between chip generations. +// +// INTERRUPT BEHAVIOR: +// - ESP32 v1: Interrupts fire when ANY pad is touched and continue while touched. +// Releases are detected by timeout since hardware doesn't generate release interrupts. +// - ESP32-S2/S3 v2: Hardware supports both touch and release interrupts, but release +// interrupts are unreliable and sometimes don't fire. We now only use touch interrupts +// and detect releases via timeout, similar to v1. + +static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; + class ESP32TouchBinarySensor; class ESP32TouchComponent : public Component { @@ -31,6 +47,14 @@ class ESP32TouchComponent : public Component { void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) { this->voltage_attenuation_ = voltage_attenuation; } + + void setup() override; + void dump_config() override; + void loop() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void on_shutdown() override; + #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) void set_filter_mode(touch_filter_mode_t filter_mode) { this->filter_mode_ = filter_mode; } void set_debounce_count(uint32_t debounce_count) { this->debounce_count_ = debounce_count; } @@ -47,16 +71,101 @@ class ESP32TouchComponent : public Component { void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; } #endif - uint32_t component_touch_pad_read(touch_pad_t tp); + protected: + // Common helper methods + void dump_config_base_(); + void dump_config_sensors_(); + bool create_touch_queue_(); + void cleanup_touch_queue_(); + void configure_wakeup_pads_(); - void setup() override; - void dump_config() override; - void loop() override; + // Helper methods for loop() logic + void process_setup_mode_logging_(uint32_t now); + bool should_check_for_releases_(uint32_t now); + void publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now); + void check_and_disable_loop_if_all_released_(size_t pads_off); + void calculate_release_timeout_(); - void on_shutdown() override; + // Common members + std::vector children_; + bool setup_mode_{false}; + uint32_t setup_mode_last_log_print_{0}; + uint32_t last_release_check_{0}; + uint32_t release_timeout_ms_{1500}; + uint32_t release_check_interval_ms_{50}; + bool initial_state_published_[TOUCH_PAD_MAX] = {false}; + + // Common configuration parameters + uint16_t sleep_cycle_{4095}; + uint16_t meas_cycle_{65535}; + touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5}; + touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7}; + touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V}; + + // Common constants + static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100; + + // ==================== PLATFORM SPECIFIC ==================== + +#ifdef USE_ESP32_VARIANT_ESP32 + // ESP32 v1 specific + + static void touch_isr_handler(void *arg); + QueueHandle_t touch_queue_{nullptr}; + + private: + // Touch event structure for ESP32 v1 + // Contains touch pad info, value, and touch state for queue communication + struct TouchPadEventV1 { + touch_pad_t pad; + uint32_t value; + bool is_touched; + }; protected: -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // Design note: last_touch_time_ does not require synchronization primitives because: + // 1. ESP32 guarantees atomic 32-bit aligned reads/writes + // 2. ISR only writes timestamps, main loop only reads + // 3. Timing tolerance allows for occasional stale reads (50ms check interval) + // 4. Queue operations provide implicit memory barriers + // Using atomic/critical sections would add overhead without meaningful benefit + uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; + uint32_t iir_filter_{0}; + + bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } + +#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // ESP32-S2/S3 v2 specific + static void touch_isr_handler(void *arg); + QueueHandle_t touch_queue_{nullptr}; + + private: + // Touch event structure for ESP32 v2 (S2/S3) + // Contains touch pad and interrupt mask for queue communication + struct TouchPadEventV2 { + touch_pad_t pad; + uint32_t intr_mask; + }; + + // Track last touch time for timeout-based release detection + uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; + + protected: + // Filter configuration + touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; + uint32_t debounce_count_{0}; + uint32_t noise_threshold_{0}; + uint32_t jitter_step_{0}; + touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX}; + + // Denoise configuration + touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX}; + touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX}; + + // Waterproof configuration + touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX}; + touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX}; + bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); } @@ -67,43 +176,78 @@ class ESP32TouchComponent : public Component { return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) && (this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX); } -#else - bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } + + // Helper method to read touch values - non-blocking operation + // Returns the current touch pad value using either filtered or raw reading + // based on the filter configuration + uint32_t read_touch_value(touch_pad_t pad) const; + + // Helper to update touch state with a known state + void update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched); + + // Helper to read touch value and update state for a given child + bool check_and_update_touch_state_(ESP32TouchBinarySensor *child); #endif - std::vector children_; - bool setup_mode_{false}; - uint32_t setup_mode_last_log_print_{0}; - // common parameters - uint16_t sleep_cycle_{4095}; - uint16_t meas_cycle_{65535}; - touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5}; - touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7}; - touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V}; -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; - uint32_t debounce_count_{0}; - uint32_t noise_threshold_{0}; - uint32_t jitter_step_{0}; - touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX}; - touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX}; - touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX}; - touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX}; - touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX}; -#else - uint32_t iir_filter_{0}; -#endif + // Helper functions for dump_config - common to both implementations + static const char *get_low_voltage_reference_str(touch_low_volt_t ref) { + switch (ref) { + case TOUCH_LVOLT_0V5: + return "0.5V"; + case TOUCH_LVOLT_0V6: + return "0.6V"; + case TOUCH_LVOLT_0V7: + return "0.7V"; + case TOUCH_LVOLT_0V8: + return "0.8V"; + default: + return "UNKNOWN"; + } + } + + static const char *get_high_voltage_reference_str(touch_high_volt_t ref) { + switch (ref) { + case TOUCH_HVOLT_2V4: + return "2.4V"; + case TOUCH_HVOLT_2V5: + return "2.5V"; + case TOUCH_HVOLT_2V6: + return "2.6V"; + case TOUCH_HVOLT_2V7: + return "2.7V"; + default: + return "UNKNOWN"; + } + } + + static const char *get_voltage_attenuation_str(touch_volt_atten_t atten) { + switch (atten) { + case TOUCH_HVOLT_ATTEN_1V5: + return "1.5V"; + case TOUCH_HVOLT_ATTEN_1V: + return "1V"; + case TOUCH_HVOLT_ATTEN_0V5: + return "0.5V"; + case TOUCH_HVOLT_ATTEN_0V: + return "0V"; + default: + return "UNKNOWN"; + } + } }; /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold); + ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold) + : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} touch_pad_t get_touch_pad() const { return this->touch_pad_; } uint32_t get_threshold() const { return this->threshold_; } void set_threshold(uint32_t threshold) { this->threshold_ = threshold; } +#ifdef USE_ESP32_VARIANT_ESP32 uint32_t get_value() const { return this->value_; } +#endif uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; } protected: @@ -111,7 +255,10 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t touch_pad_{TOUCH_PAD_MAX}; uint32_t threshold_{0}; +#ifdef USE_ESP32_VARIANT_ESP32 uint32_t value_{0}; +#endif + bool last_state_{false}; const uint32_t wakeup_threshold_{0}; }; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp new file mode 100644 index 0000000000..fd2cdfcbad --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -0,0 +1,159 @@ +#ifdef USE_ESP32 + +#include "esp32_touch.h" +#include "esphome/core/log.h" +#include + +#include "soc/rtc.h" + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +void ESP32TouchComponent::dump_config_base_() { + const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); + const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); + const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); + + ESP_LOGCONFIG(TAG, + "Config for ESP32 Touch Hub:\n" + " Meas cycle: %.2fms\n" + " Sleep cycle: %.2fms\n" + " Low Voltage Reference: %s\n" + " High Voltage Reference: %s\n" + " Voltage Attenuation: %s", + this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, + atten_s); +} + +void ESP32TouchComponent::dump_config_sensors_() { + for (auto *child : this->children_) { + LOG_BINARY_SENSOR(" ", "Touch Pad", child); + ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); + ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); + } +} + +bool ESP32TouchComponent::create_touch_queue_() { + // Queue size calculation: children * 4 allows for burst scenarios where ISR + // fires multiple times before main loop processes. + size_t queue_size = this->children_.size() * 4; + if (queue_size < 8) + queue_size = 8; + +#ifdef USE_ESP32_VARIANT_ESP32 + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); +#else + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV2)); +#endif + + if (this->touch_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create touch event queue of size %" PRIu32, (uint32_t) queue_size); + this->mark_failed(); + return false; + } + return true; +} + +void ESP32TouchComponent::cleanup_touch_queue_() { + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + this->touch_queue_ = nullptr; + } +} + +void ESP32TouchComponent::configure_wakeup_pads_() { + bool is_wakeup_source = false; + + // Check if any pad is configured for wakeup + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + is_wakeup_source = true; + +#ifdef USE_ESP32_VARIANT_ESP32 + // ESP32 v1: No filter available when using as wake-up source. + touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); +#else + // ESP32-S2/S3 v2: Set threshold for wakeup + touch_pad_set_thresh(child->get_touch_pad(), child->get_wakeup_threshold()); +#endif + } + } + + if (!is_wakeup_source) { + // If no pad is configured for wakeup, deinitialize touch pad + touch_pad_deinit(); + } +} + +void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) { + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { + for (auto *child : this->children_) { +#ifdef USE_ESP32_VARIANT_ESP32 + ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), + (uint32_t) child->get_touch_pad(), child->value_); +#else + // Read the value being used for touch detection + uint32_t value = this->read_touch_value(child->get_touch_pad()); + ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); +#endif + } + this->setup_mode_last_log_print_ = now; + } +} + +bool ESP32TouchComponent::should_check_for_releases_(uint32_t now) { + if (now - this->last_release_check_ < this->release_check_interval_ms_) { + return false; + } + this->last_release_check_ = now; + return true; +} + +void ESP32TouchComponent::publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now) { + touch_pad_t pad = child->get_touch_pad(); + if (!this->initial_state_published_[pad]) { + // Check if enough time has passed since startup + if (now > this->release_timeout_ms_) { + child->publish_initial_state(false); + this->initial_state_published_[pad] = true; + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + } + } +} + +void ESP32TouchComponent::check_and_disable_loop_if_all_released_(size_t pads_off) { + // Disable the loop to save CPU cycles when all pads are off and not in setup mode. + if (pads_off == this->children_.size() && !this->setup_mode_) { + this->disable_loop(); + } +} + +void ESP32TouchComponent::calculate_release_timeout_() { + // Calculate release timeout based on sleep cycle + // Design note: Hardware limitation - interrupts only fire reliably on touch (not release) + // We must use timeout-based detection for release events + // Formula: 3 sleep cycles converted to ms, with MINIMUM_RELEASE_TIME_MS minimum + // Per ESP-IDF docs: t_sleep = sleep_cycle / SOC_CLK_RC_SLOW_FREQ_APPROX + + uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); + + // Calculate timeout as 3 sleep cycles + this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / rtc_freq; + + if (this->release_timeout_ms_ < MINIMUM_RELEASE_TIME_MS) { + this->release_timeout_ms_ = MINIMUM_RELEASE_TIME_MS; + } + + // Check for releases at 1/4 the timeout interval + // Since hardware doesn't generate reliable release interrupts, we must poll + // for releases in the main loop. Checking at 1/4 the timeout interval provides + // a good balance between responsiveness and efficiency. + this->release_check_interval_ms_ = this->release_timeout_ms_ / 4; +} + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp new file mode 100644 index 0000000000..a6d499e9fa --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -0,0 +1,240 @@ +#ifdef USE_ESP32_VARIANT_ESP32 + +#include "esp32_touch.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#include +#include + +// Include HAL for ISR-safe touch reading +#include "hal/touch_sensor_ll.h" + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +void ESP32TouchComponent::setup() { + // Create queue for touch events + // Queue size calculation: children * 4 allows for burst scenarios where ISR + // fires multiple times before main loop processes. This is important because + // ESP32 v1 scans all pads on each interrupt, potentially sending multiple events. + if (!this->create_touch_queue_()) { + return; + } + + touch_pad_init(); + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Set up IIR filter if enabled + if (this->iir_filter_enabled_()) { + touch_pad_filter_start(this->iir_filter_); + } + + // Configure measurement parameters +#if ESP_IDF_VERSION_MAJOR >= 5 + touch_pad_set_measurement_clock_cycles(this->meas_cycle_); + touch_pad_set_measurement_interval(this->sleep_cycle_); +#else + touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); +#endif + touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); + + // Configure each touch pad + for (auto *child : this->children_) { + touch_pad_config(child->get_touch_pad(), child->get_threshold()); + } + + // Register ISR handler + esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); + this->cleanup_touch_queue_(); + this->mark_failed(); + return; + } + + // Calculate release timeout based on sleep cycle + this->calculate_release_timeout_(); + + // Enable touch pad interrupt + touch_pad_intr_enable(); +} + +void ESP32TouchComponent::dump_config() { + this->dump_config_base_(); + + if (this->iir_filter_enabled_()) { + ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_); + } else { + ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); + } + + if (this->setup_mode_) { + ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); + } + + this->dump_config_sensors_(); +} + +void ESP32TouchComponent::loop() { + const uint32_t now = App.get_loop_component_start_time(); + + // Print debug info for all pads in setup mode + this->process_setup_mode_logging_(now); + + // Process any queued touch events from interrupts + // Note: Events are only sent by ISR for pads that were measured in that cycle (value != 0) + // This is more efficient than sending all pad states every interrupt + TouchPadEventV1 event; + while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + // Find the corresponding sensor - O(n) search is acceptable since events are infrequent + for (auto *child : this->children_) { + if (child->get_touch_pad() != event.pad) { + continue; + } + + // Found matching pad - process it + child->value_ = event.value; + + // The interrupt gives us the touch state directly + bool new_state = event.is_touched; + + // Track when we last saw this pad as touched + if (new_state) { + this->last_touch_time_[event.pad] = now; + } + + // Only publish if state changed - this filters out repeated events + if (new_state != child->last_state_) { + child->last_state_ = new_state; + child->publish_state(new_state); + // Original ESP32: ISR only fires when touched, release is detected by timeout + // Note: ESP32 v1 uses inverted logic - touched when value < threshold + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); + } + break; // Exit inner loop after processing matching pad + } + } + + // Check for released pads periodically + if (!this->should_check_for_releases_(now)) { + return; + } + + size_t pads_off = 0; + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); + + // Handle initial state publication after startup + this->publish_initial_state_if_needed_(child, now); + + if (child->last_state_) { + // Pad is currently in touched state - check for release timeout + // Using subtraction handles 32-bit rollover correctly + uint32_t time_diff = now - this->last_touch_time_[pad]; + + // Check if we haven't seen this pad recently + if (time_diff > this->release_timeout_ms_) { + // Haven't seen this pad recently, assume it's released + child->last_state_ = false; + child->publish_state(false); + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); + pads_off++; + } + } else { + // Pad is already off + pads_off++; + } + } + + // Disable the loop to save CPU cycles when all pads are off and not in setup mode. + // The loop will be re-enabled by the ISR when any touch pad is touched. + // v1 hardware limitations require us to check all pads are off because: + // - v1 only generates interrupts on touch events (not releases) + // - We must poll for release timeouts in the main loop + // - We can only safely disable when no pads need timeout monitoring + this->check_and_disable_loop_if_all_released_(pads_off); +} + +void ESP32TouchComponent::on_shutdown() { + touch_pad_intr_disable(); + touch_pad_isr_deregister(touch_isr_handler, this); + this->cleanup_touch_queue_(); + + if (this->iir_filter_enabled_()) { + touch_pad_filter_stop(); + touch_pad_filter_delete(); + } + + // Configure wakeup pads if any are set + this->configure_wakeup_pads_(); +} + +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + + touch_pad_clear_status(); + + // INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured + // touch pad detects a touch (value goes below threshold). The hardware does NOT + // generate interrupts on release - only on touch events. + // The interrupt will continue to fire periodically (based on sleep_cycle) as long + // as any pad remains touched. This allows us to detect both new touches and + // continued touches, but releases must be detected by timeout in the main loop. + + // Process all configured pads to check their current state + // Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt, + // so we must scan all configured pads to find which ones were touched + for (auto *child : component->children_) { + touch_pad_t pad = child->get_touch_pad(); + + // Read current value using ISR-safe API + uint32_t value; + if (component->iir_filter_enabled_()) { + uint16_t temp_value = 0; + touch_pad_read_filtered(pad, &temp_value); + value = temp_value; + } else { + // Use low-level HAL function when filter is not enabled + value = touch_ll_read_raw_data(pad); + } + + // Skip pads with 0 value - they haven't been measured in this cycle + // This is important: not all pads are measured every interrupt cycle, + // only those that the hardware has updated + if (value == 0) { + continue; + } + + // IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2! + // ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE + // Therefore: touched = (value < threshold) + // This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold) + bool is_touched = value < child->get_threshold(); + + // Always send the current state - the main loop will filter for changes + // We send both touched and untouched states because the ISR doesn't + // track previous state (to keep ISR fast and simple) + TouchPadEventV1 event; + event.pad = pad; + event.value = value; + event.is_touched = is_touched; + + // Send to queue from ISR - non-blocking, drops if queue full + BaseType_t x_higher_priority_task_woken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); + component->enable_loop_soon_any_context(); + if (x_higher_priority_task_woken) { + portYIELD_FROM_ISR(); + } + } +} + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32 diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp new file mode 100644 index 0000000000..ad77881724 --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -0,0 +1,398 @@ +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + +#include "esp32_touch.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +// Helper to update touch state with a known state +void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched) { + // Always update timer when touched + if (is_touched) { + this->last_touch_time_[child->get_touch_pad()] = App.get_loop_component_start_time(); + } + + if (child->last_state_ != is_touched) { + // Read value for logging + uint32_t value = this->read_touch_value(child->get_touch_pad()); + + child->last_state_ = is_touched; + child->publish_state(is_touched); + if (is_touched) { + // ESP32-S2/S3 v2: touched when value > threshold + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " > threshold: %" PRIu32 ")", child->get_name().c_str(), + value, child->get_threshold()); + } else { + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", child->get_name().c_str()); + } + } +} + +// Helper to read touch value and update state for a given child (used for timeout events) +bool ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *child) { + // Read current touch value + uint32_t value = this->read_touch_value(child->get_touch_pad()); + + // ESP32-S2/S3 v2: Touch is detected when value > threshold + bool is_touched = value > child->get_threshold(); + + this->update_touch_state_(child, is_touched); + return is_touched; +} + +void ESP32TouchComponent::setup() { + // Create queue for touch events first + if (!this->create_touch_queue_()) { + return; + } + + // Initialize touch pad peripheral + esp_err_t init_err = touch_pad_init(); + if (init_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize touch pad: %s", esp_err_to_name(init_err)); + this->mark_failed(); + return; + } + + // Configure each touch pad first + for (auto *child : this->children_) { + esp_err_t config_err = touch_pad_config(child->get_touch_pad()); + if (config_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->get_touch_pad(), esp_err_to_name(config_err)); + } + } + + // Set up filtering if configured + if (this->filter_configured_()) { + touch_filter_config_t filter_info = { + .mode = this->filter_mode_, + .debounce_cnt = this->debounce_count_, + .noise_thr = this->noise_threshold_, + .jitter_step = this->jitter_step_, + .smh_lvl = this->smooth_level_, + }; + touch_pad_filter_set_config(&filter_info); + touch_pad_filter_enable(); + } + + if (this->denoise_configured_()) { + touch_pad_denoise_t denoise = { + .grade = this->grade_, + .cap_level = this->cap_level_, + }; + touch_pad_denoise_set_config(&denoise); + touch_pad_denoise_enable(); + } + + if (this->waterproof_configured_()) { + touch_pad_waterproof_t waterproof = { + .guard_ring_pad = this->waterproof_guard_ring_pad_, + .shield_driver = this->waterproof_shield_driver_, + }; + touch_pad_waterproof_set_config(&waterproof); + touch_pad_waterproof_enable(); + } + + // Configure measurement parameters + touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); + // ESP32-S2/S3 always use the older API + touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); + + // Configure timeout if needed + touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); + + // Register ISR handler with interrupt mask + esp_err_t err = + touch_pad_isr_register(touch_isr_handler, this, static_cast(TOUCH_PAD_INTR_MASK_ALL)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); + this->cleanup_touch_queue_(); + this->mark_failed(); + return; + } + + // Set thresholds for each pad BEFORE starting FSM + for (auto *child : this->children_) { + if (child->get_threshold() != 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } + } + + // Enable interrupts - only ACTIVE and TIMEOUT + // NOTE: We intentionally don't enable INACTIVE interrupts because they are unreliable + // on ESP32-S2/S3 hardware and sometimes don't fire. Instead, we use timeout-based + // release detection with the ability to verify the actual state. + touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); + + // Set FSM mode before starting + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Start FSM + touch_pad_fsm_start(); + + // Calculate release timeout based on sleep cycle + this->calculate_release_timeout_(); +} + +void ESP32TouchComponent::dump_config() { + this->dump_config_base_(); + + if (this->filter_configured_()) { + const char *filter_mode_s; + switch (this->filter_mode_) { + case TOUCH_PAD_FILTER_IIR_4: + filter_mode_s = "IIR_4"; + break; + case TOUCH_PAD_FILTER_IIR_8: + filter_mode_s = "IIR_8"; + break; + case TOUCH_PAD_FILTER_IIR_16: + filter_mode_s = "IIR_16"; + break; + case TOUCH_PAD_FILTER_IIR_32: + filter_mode_s = "IIR_32"; + break; + case TOUCH_PAD_FILTER_IIR_64: + filter_mode_s = "IIR_64"; + break; + case TOUCH_PAD_FILTER_IIR_128: + filter_mode_s = "IIR_128"; + break; + case TOUCH_PAD_FILTER_IIR_256: + filter_mode_s = "IIR_256"; + break; + case TOUCH_PAD_FILTER_JITTER: + filter_mode_s = "JITTER"; + break; + default: + filter_mode_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, + " Filter mode: %s\n" + " Debounce count: %" PRIu32 "\n" + " Noise threshold coefficient: %" PRIu32 "\n" + " Jitter filter step size: %" PRIu32, + filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_); + const char *smooth_level_s; + switch (this->smooth_level_) { + case TOUCH_PAD_SMOOTH_OFF: + smooth_level_s = "OFF"; + break; + case TOUCH_PAD_SMOOTH_IIR_2: + smooth_level_s = "IIR_2"; + break; + case TOUCH_PAD_SMOOTH_IIR_4: + smooth_level_s = "IIR_4"; + break; + case TOUCH_PAD_SMOOTH_IIR_8: + smooth_level_s = "IIR_8"; + break; + default: + smooth_level_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s); + } + + if (this->denoise_configured_()) { + const char *grade_s; + switch (this->grade_) { + case TOUCH_PAD_DENOISE_BIT12: + grade_s = "BIT12"; + break; + case TOUCH_PAD_DENOISE_BIT10: + grade_s = "BIT10"; + break; + case TOUCH_PAD_DENOISE_BIT8: + grade_s = "BIT8"; + break; + case TOUCH_PAD_DENOISE_BIT4: + grade_s = "BIT4"; + break; + default: + grade_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s); + + const char *cap_level_s; + switch (this->cap_level_) { + case TOUCH_PAD_DENOISE_CAP_L0: + cap_level_s = "L0"; + break; + case TOUCH_PAD_DENOISE_CAP_L1: + cap_level_s = "L1"; + break; + case TOUCH_PAD_DENOISE_CAP_L2: + cap_level_s = "L2"; + break; + case TOUCH_PAD_DENOISE_CAP_L3: + cap_level_s = "L3"; + break; + case TOUCH_PAD_DENOISE_CAP_L4: + cap_level_s = "L4"; + break; + case TOUCH_PAD_DENOISE_CAP_L5: + cap_level_s = "L5"; + break; + case TOUCH_PAD_DENOISE_CAP_L6: + cap_level_s = "L6"; + break; + case TOUCH_PAD_DENOISE_CAP_L7: + cap_level_s = "L7"; + break; + default: + cap_level_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s); + } + + if (this->setup_mode_) { + ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); + } + + this->dump_config_sensors_(); +} + +void ESP32TouchComponent::loop() { + const uint32_t now = App.get_loop_component_start_time(); + + // V2 TOUCH HANDLING: + // Due to unreliable INACTIVE interrupts on ESP32-S2/S3, we use a hybrid approach: + // 1. Process ACTIVE interrupts when pads are touched + // 2. Use timeout-based release detection (like v1) + // 3. But smarter than v1: verify actual state before releasing on timeout + // This prevents false releases if we missed interrupts + + // In setup mode, periodically log all pad values + this->process_setup_mode_logging_(now); + + // Process any queued touch events from interrupts + TouchPadEventV2 event; + while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + // Handle timeout events + if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { + // Resume measurement after timeout + touch_pad_timeout_resume(); + // For timeout events, always check the current state + } else if (!(event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE)) { + // Skip if not an active/timeout event + continue; + } + + // Find the child for the pad that triggered the interrupt + for (auto *child : this->children_) { + if (child->get_touch_pad() != event.pad) { + continue; + } + + if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { + // For timeout events, we need to read the value to determine state + this->check_and_update_touch_state_(child); + } else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) { + // We only get ACTIVE interrupts now, releases are detected by timeout + this->update_touch_state_(child, true); // Always touched for ACTIVE interrupts + } + break; + } + } + + // Check for released pads periodically (like v1) + if (!this->should_check_for_releases_(now)) { + return; + } + + size_t pads_off = 0; + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); + + // Handle initial state publication after startup + this->publish_initial_state_if_needed_(child, now); + + if (child->last_state_) { + // Pad is currently in touched state - check for release timeout + // Using subtraction handles 32-bit rollover correctly + uint32_t time_diff = now - this->last_touch_time_[pad]; + + // Check if we haven't seen this pad recently + if (time_diff > this->release_timeout_ms_) { + // Haven't seen this pad recently - verify actual state + // Unlike v1, v2 hardware allows us to read the current state anytime + // This makes v2 smarter: we can verify if it's actually released before + // declaring a timeout, preventing false releases if interrupts were missed + bool still_touched = this->check_and_update_touch_state_(child); + + if (still_touched) { + // Still touched! Timer was reset in update_touch_state_ + ESP_LOGVV(TAG, "Touch Pad '%s' still touched after %" PRIu32 "ms timeout, resetting timer", + child->get_name().c_str(), this->release_timeout_ms_); + } else { + // Actually released - already handled by check_and_update_touch_state_ + pads_off++; + } + } + } else { + // Pad is already off + pads_off++; + } + } + + // Disable the loop when all pads are off and not in setup mode (like v1) + // We need to keep checking for timeouts, so only disable when all pads are confirmed off + this->check_and_disable_loop_if_all_released_(pads_off); +} + +void ESP32TouchComponent::on_shutdown() { + // Disable interrupts + touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_isr_deregister(touch_isr_handler, this); + this->cleanup_touch_queue_(); + + // Configure wakeup pads if any are set + this->configure_wakeup_pads_(); +} + +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + BaseType_t x_higher_priority_task_woken = pdFALSE; + + // Read interrupt status + TouchPadEventV2 event; + event.intr_mask = touch_pad_read_intr_status_mask(); + event.pad = touch_pad_get_current_meas_channel(); + + // Send event to queue for processing in main loop + xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); + component->enable_loop_soon_any_context(); + + if (x_higher_priority_task_woken) { + portYIELD_FROM_ISR(); + } +} + +uint32_t ESP32TouchComponent::read_touch_value(touch_pad_t pad) const { + // Unlike ESP32 v1, touch reads on ESP32-S2/S3 v2 are non-blocking operations. + // The hardware continuously samples in the background and we can read the + // latest value at any time without waiting. + uint32_t value = 0; + if (this->filter_configured_()) { + // Read filtered/smoothed value when filter is enabled + touch_pad_filter_read_smooth(pad, &value); + } else { + // Read raw value when filter is not configured + touch_pad_read_raw_data(pad, &value); + } + return value; +} + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 From 16ef5a93774476bcbbb53bc3bcb7fc2ffef61454 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 22:21:11 -0500 Subject: [PATCH 12/35] Add OTA support to ESP-IDF webserver (#9264) --- .../captive_portal/captive_portal.cpp | 2 + esphome/components/web_server/__init__.py | 22 +- esphome/components/web_server/web_server.cpp | 6 + .../web_server_base/web_server_base.cpp | 210 +++++++++++++-- .../web_server_base/web_server_base.h | 16 ++ esphome/components/web_server_idf/__init__.py | 8 +- .../components/web_server_idf/multipart.cpp | 254 ++++++++++++++++++ esphome/components/web_server_idf/multipart.h | 86 ++++++ esphome/components/web_server_idf/utils.cpp | 32 +++ esphome/components/web_server_idf/utils.h | 10 + .../web_server_idf/web_server_idf.cpp | 128 ++++++++- .../web_server_idf/web_server_idf.h | 3 + esphome/core/defines.h | 1 + esphome/idf_component.yml | 2 + .../web_server/test_no_ota.esp32-idf.yaml | 9 + .../web_server/test_ota.esp32-idf.yaml | 32 +++ .../test_ota_disabled.esp32-idf.yaml | 11 + 17 files changed, 788 insertions(+), 44 deletions(-) create mode 100644 esphome/components/web_server_idf/multipart.cpp create mode 100644 esphome/components/web_server_idf/multipart.h create mode 100644 tests/components/web_server/test_no_ota.esp32-idf.yaml create mode 100644 tests/components/web_server/test_ota.esp32-idf.yaml create mode 100644 tests/components/web_server/test_ota_disabled.esp32-idf.yaml diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 51e5cfc8ff..ba392bb0f2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -47,7 +47,9 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); +#ifdef USE_WEBSERVER_OTA this->base_->add_ota_handler(); +#endif } #ifdef USE_ARDUINO diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index f2c1824028..ca145c732b 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -40,6 +40,7 @@ CONF_SORTING_GROUP_ID = "sorting_group_id" CONF_SORTING_GROUPS = "sorting_groups" CONF_SORTING_WEIGHT = "sorting_weight" + web_server_ns = cg.esphome_ns.namespace("web_server") WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) @@ -72,12 +73,6 @@ def validate_local(config): return config -def validate_ota(config): - if CORE.using_esp_idf and config[CONF_OTA]: - raise cv.Invalid("Enabling 'ota' is not supported for IDF framework yet") - return config - - def validate_sorting_groups(config): if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3: raise cv.Invalid( @@ -175,15 +170,7 @@ CONFIG_SCHEMA = cv.All( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.SplitDefault( - CONF_OTA, - esp8266=True, - esp32_arduino=True, - esp32_idf=False, - bk72xx=True, - ln882x=True, - rtl87xx=True, - ): cv.boolean, + cv.Optional(CONF_OTA, default=True): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), @@ -200,7 +187,6 @@ CONFIG_SCHEMA = cv.All( ), default_url, validate_local, - validate_ota, validate_sorting_groups, ) @@ -286,6 +272,10 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) + if config[CONF_OTA]: + # Define USE_WEBSERVER_OTA based only on web_server OTA config + # This allows web server OTA to work without loading the OTA component + cg.add_define("USE_WEBSERVER_OTA") cg.add(var.set_expose_log(config[CONF_LOG])) if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 669bfbf279..e0027d0b27 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -299,8 +299,10 @@ void WebServer::setup() { #endif this->base_->add_handler(this); +#ifdef USE_WEBSERVER_OTA if (this->allow_ota_) this->base_->add_ota_handler(); +#endif // doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly // getting a lot of events @@ -2030,6 +2032,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } #endif + + // No matching handler found - send 404 + ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str()); + request->send(404, "text/plain", "Not Found"); } bool WebServer::isRequestHandlerTrivial() const { return false; } diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 2835585387..9ad88e09f4 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -14,11 +14,114 @@ #endif #endif +#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +#include +#include +#endif + namespace esphome { namespace web_server_base { static const char *const TAG = "web_server_base"; +#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +// Minimal OTA backend implementation for web server +// This allows OTA updates via web server without requiring the OTA component +// TODO: In the future, this should be refactored into a common ota_base component +// that both web_server and ota components can depend on, avoiding code duplication +// while keeping the components independent. This would allow both ESP-IDF and Arduino +// implementations to share the base OTA functionality without requiring the full OTA component. +// The IDFWebServerOTABackend class is intentionally designed with the same interface +// as OTABackend to make it easy to swap to using OTABackend when the ota component +// is split into ota and ota_base in the future. +class IDFWebServerOTABackend { + public: + bool begin() { + this->partition_ = esp_ota_get_next_update_partition(nullptr); + if (this->partition_ == nullptr) { + ESP_LOGE(TAG, "No OTA partition available"); + return false; + } + +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // The following function takes longer than the default timeout of WDT due to flash erase +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_task_wdt_config_t wdtc; + wdtc.idle_core_mask = 0; +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 + wdtc.idle_core_mask |= (1 << 0); +#endif +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 + wdtc.idle_core_mask |= (1 << 1); +#endif + wdtc.timeout_ms = 15000; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(15, false); +#endif +#endif + + esp_err_t err = esp_ota_begin(this->partition_, 0, &this->update_handle_); + +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // Set the WDT back to the configured timeout +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); +#endif +#endif + + if (err != ESP_OK) { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; + ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); + return false; + } + return true; + } + + bool write(uint8_t *data, size_t len) { + esp_err_t err = esp_ota_write(this->update_handle_, data, len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(err)); + return false; + } + return true; + } + + bool end() { + esp_err_t err = esp_ota_end(this->update_handle_); + this->update_handle_ = 0; + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err)); + return false; + } + + err = esp_ota_set_boot_partition(this->partition_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err)); + return false; + } + + return true; + } + + void abort() { + if (this->update_handle_ != 0) { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; + } + } + + private: + esp_ota_handle_t update_handle_{0}; + const esp_partition_t *partition_{nullptr}; +}; +#endif + void WebServerBase::add_handler(AsyncWebHandler *handler) { // remove all handlers @@ -31,6 +134,33 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) { } } +#ifdef USE_WEBSERVER_OTA +void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { + const uint32_t now = millis(); + if (now - this->last_ota_progress_ > 1000) { + if (request->contentLength() != 0) { + float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); + ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); + } else { + ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); + } + this->last_ota_progress_ = now; + } +} + +void OTARequestHandler::schedule_ota_reboot_() { + ESP_LOGI(TAG, "OTA update successful!"); + this->parent_->set_timeout(100, []() { + ESP_LOGI(TAG, "Performing OTA reboot now"); + App.safe_reboot(); + }); +} + +void OTARequestHandler::ota_init_(const char *filename) { + ESP_LOGI(TAG, "OTA Update Start: %s", filename); + this->ota_read_length_ = 0; +} + void report_ota_error() { #ifdef USE_ARDUINO StreamString ss; @@ -44,8 +174,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin #ifdef USE_ARDUINO bool success; if (index == 0) { - ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); - this->ota_read_length_ = 0; + this->ota_init_(filename.c_str()); #ifdef USE_ESP8266 Update.runAsync(true); // NOLINTNEXTLINE(readability-static-accessed-through-instance) @@ -72,31 +201,68 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin return; } this->ota_read_length_ += len; - - const uint32_t now = millis(); - if (now - this->last_ota_progress_ > 1000) { - if (request->contentLength() != 0) { - float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); - ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); - } else { - ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); - } - this->last_ota_progress_ = now; - } + this->report_ota_progress_(request); if (final) { if (Update.end(true)) { - ESP_LOGI(TAG, "OTA update successful!"); - this->parent_->set_timeout(100, []() { App.safe_reboot(); }); + this->schedule_ota_reboot_(); } else { report_ota_error(); } } -#endif +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF + // ESP-IDF implementation + if (index == 0 && !this->ota_backend_) { + // Initialize OTA on first call + this->ota_init_(filename.c_str()); + this->ota_success_ = false; + + auto *backend = new IDFWebServerOTABackend(); + if (!backend->begin()) { + ESP_LOGE(TAG, "OTA begin failed"); + delete backend; + return; + } + this->ota_backend_ = backend; + } + + auto *backend = static_cast(this->ota_backend_); + if (!backend) { + return; + } + + // Process data + if (len > 0) { + if (!backend->write(data, len)) { + ESP_LOGE(TAG, "OTA write failed"); + backend->abort(); + delete backend; + this->ota_backend_ = nullptr; + return; + } + this->ota_read_length_ += len; + this->report_ota_progress_(request); + } + + // Finalize + if (final) { + this->ota_success_ = backend->end(); + if (this->ota_success_) { + this->schedule_ota_reboot_(); + } else { + ESP_LOGE(TAG, "OTA end failed"); + } + delete backend; + this->ota_backend_ = nullptr; + } +#endif // USE_ESP_IDF } + void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { -#ifdef USE_ARDUINO AsyncWebServerResponse *response; +#ifdef USE_ARDUINO if (!Update.hasError()) { response = request->beginResponse(200, "text/plain", "Update Successful!"); } else { @@ -105,16 +271,20 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { Update.printError(ss); response = request->beginResponse(200, "text/plain", ss); } +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + // Send response based on the OTA result + response = request->beginResponse(200, "text/plain", this->ota_success_ ? "Update Successful!" : "Update Failed!"); +#endif // USE_ESP_IDF response->addHeader("Connection", "close"); request->send(response); -#endif } void WebServerBase::add_ota_handler() { -#ifdef USE_ARDUINO this->add_handler(new OTARequestHandler(this)); // NOLINT -#endif } +#endif + float WebServerBase::get_setup_priority() const { // Before WiFi (captive portal) return setup_priority::WIFI + 2.0f; diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 641006cb99..09a41956c9 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -110,13 +110,17 @@ class WebServerBase : public Component { void add_handler(AsyncWebHandler *handler); +#ifdef USE_WEBSERVER_OTA void add_ota_handler(); +#endif void set_port(uint16_t port) { port_ = port; } uint16_t get_port() const { return port_; } protected: +#ifdef USE_WEBSERVER_OTA friend class OTARequestHandler; +#endif int initialized_{0}; uint16_t port_{80}; @@ -125,6 +129,7 @@ class WebServerBase : public Component { internal::Credentials credentials_; }; +#ifdef USE_WEBSERVER_OTA class OTARequestHandler : public AsyncWebHandler { public: OTARequestHandler(WebServerBase *parent) : parent_(parent) {} @@ -139,10 +144,21 @@ class OTARequestHandler : public AsyncWebHandler { bool isRequestHandlerTrivial() const override { return false; } protected: + void report_ota_progress_(AsyncWebServerRequest *request); + void schedule_ota_reboot_(); + void ota_init_(const char *filename); + uint32_t last_ota_progress_{0}; uint32_t ota_read_length_{0}; WebServerBase *parent_; + + private: +#ifdef USE_ESP_IDF + void *ota_backend_{nullptr}; + bool ota_success_{false}; +#endif }; +#endif // USE_WEBSERVER_OTA } // namespace web_server_base } // namespace esphome diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py index 506e1c5c13..fe1c6f2640 100644 --- a/esphome/components/web_server_idf/__init__.py +++ b/esphome/components/web_server_idf/__init__.py @@ -1,5 +1,7 @@ -from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option import esphome.config_validation as cv +from esphome.const import CONF_OTA, CONF_WEB_SERVER +from esphome.core import CORE CODEOWNERS = ["@dentra"] @@ -12,3 +14,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024) + # Check if web_server component has OTA enabled + if CORE.config.get(CONF_WEB_SERVER, {}).get(CONF_OTA, True): + # Add multipart parser component for ESP-IDF OTA support + add_idf_component(name="zorxx/multipart-parser", ref="1.0.1") diff --git a/esphome/components/web_server_idf/multipart.cpp b/esphome/components/web_server_idf/multipart.cpp new file mode 100644 index 0000000000..8655226ab9 --- /dev/null +++ b/esphome/components/web_server_idf/multipart.cpp @@ -0,0 +1,254 @@ +#include "esphome/core/defines.h" +#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +#include "multipart.h" +#include "utils.h" +#include "esphome/core/log.h" +#include +#include "multipart_parser.h" + +namespace esphome { +namespace web_server_idf { + +static const char *const TAG = "multipart"; + +// ========== MultipartReader Implementation ========== + +MultipartReader::MultipartReader(const std::string &boundary) { + // Initialize settings with callbacks + memset(&settings_, 0, sizeof(settings_)); + settings_.on_header_field = on_header_field; + settings_.on_header_value = on_header_value; + settings_.on_part_data = on_part_data; + settings_.on_part_data_end = on_part_data_end; + + ESP_LOGV(TAG, "Initializing multipart parser with boundary: '%s' (len: %zu)", boundary.c_str(), boundary.length()); + + // Create parser with boundary + parser_ = multipart_parser_init(boundary.c_str(), &settings_); + if (parser_) { + multipart_parser_set_data(parser_, this); + } else { + ESP_LOGE(TAG, "Failed to initialize multipart parser"); + } +} + +MultipartReader::~MultipartReader() { + if (parser_) { + multipart_parser_free(parser_); + } +} + +size_t MultipartReader::parse(const char *data, size_t len) { + if (!parser_) { + ESP_LOGE(TAG, "Parser not initialized"); + return 0; + } + + size_t parsed = multipart_parser_execute(parser_, data, len); + + if (parsed != len) { + ESP_LOGW(TAG, "Parser consumed %zu of %zu bytes - possible error", parsed, len); + } + + return parsed; +} + +void MultipartReader::process_header_(const char *value, size_t length) { + // Process the completed header (field + value pair) + std::string value_str(value, length); + + if (str_startswith_case_insensitive(current_header_field_, "content-disposition")) { + // Parse name and filename from Content-Disposition + current_part_.name = extract_header_param(value_str, "name"); + current_part_.filename = extract_header_param(value_str, "filename"); + } else if (str_startswith_case_insensitive(current_header_field_, "content-type")) { + current_part_.content_type = str_trim(value_str); + } + + // Clear field for next header + current_header_field_.clear(); +} + +int MultipartReader::on_header_field(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + reader->current_header_field_.assign(at, length); + return 0; +} + +int MultipartReader::on_header_value(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + reader->process_header_(at, length); + return 0; +} + +int MultipartReader::on_part_data(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + // Only process file uploads + if (reader->has_file() && reader->data_callback_) { + // IMPORTANT: The 'at' pointer points to data within the parser's input buffer. + // This data is only valid during this callback. The callback handler MUST + // process or copy the data immediately - it cannot store the pointer for + // later use as the buffer will be overwritten. + reader->data_callback_(reinterpret_cast(at), length); + } + return 0; +} + +int MultipartReader::on_part_data_end(multipart_parser *parser) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + ESP_LOGV(TAG, "Part data end"); + if (reader->part_complete_callback_) { + reader->part_complete_callback_(); + } + // Clear part info for next part + reader->current_part_ = Part{}; + return 0; +} + +// ========== Utility Functions ========== + +// Case-insensitive string prefix check +bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix) { + if (str.length() < prefix.length()) { + return false; + } + return str_ncmp_ci(str.c_str(), prefix.c_str(), prefix.length()); +} + +// Extract a parameter value from a header line +// Handles both quoted and unquoted values +std::string extract_header_param(const std::string &header, const std::string ¶m) { + size_t search_pos = 0; + + while (search_pos < header.length()) { + // Look for param name + const char *found = stristr(header.c_str() + search_pos, param.c_str()); + if (!found) { + return ""; + } + size_t pos = found - header.c_str(); + + // Check if this is a word boundary (not part of another parameter) + if (pos > 0 && header[pos - 1] != ' ' && header[pos - 1] != ';' && header[pos - 1] != '\t') { + search_pos = pos + 1; + continue; + } + + // Move past param name + pos += param.length(); + + // Skip whitespace and find '=' + while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + if (pos >= header.length() || header[pos] != '=') { + search_pos = pos; + continue; + } + + pos++; // Skip '=' + + // Skip whitespace after '=' + while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + if (pos >= header.length()) { + return ""; + } + + // Check if value is quoted + if (header[pos] == '"') { + pos++; + size_t end = header.find('"', pos); + if (end != std::string::npos) { + return header.substr(pos, end - pos); + } + // Malformed - no closing quote + return ""; + } + + // Unquoted value - find the end (semicolon, comma, or end of string) + size_t end = pos; + while (end < header.length() && header[end] != ';' && header[end] != ',' && header[end] != ' ' && + header[end] != '\t') { + end++; + } + + return header.substr(pos, end - pos); + } + + return ""; +} + +// Parse boundary from Content-Type header +// Returns true if boundary found, false otherwise +// boundary_start and boundary_len will point to the boundary value +bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len) { + if (!content_type) { + return false; + } + + // Check for multipart/form-data (case-insensitive) + if (!stristr(content_type, "multipart/form-data")) { + return false; + } + + // Look for boundary parameter + const char *b = stristr(content_type, "boundary="); + if (!b) { + return false; + } + + const char *start = b + 9; // Skip "boundary=" + + // Skip whitespace + while (*start == ' ' || *start == '\t') { + start++; + } + + if (!*start) { + return false; + } + + // Find end of boundary + const char *end = start; + if (*end == '"') { + // Quoted boundary + start++; + end++; + while (*end && *end != '"') { + end++; + } + *boundary_len = end - start; + } else { + // Unquoted boundary + while (*end && *end != ' ' && *end != ';' && *end != '\r' && *end != '\n' && *end != '\t') { + end++; + } + *boundary_len = end - start; + } + + if (*boundary_len == 0) { + return false; + } + + *boundary_start = start; + + return true; +} + +// Trim whitespace from both ends of a string +std::string str_trim(const std::string &str) { + size_t start = str.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(" \t\r\n"); + return str.substr(start, end - start + 1); +} + +} // namespace web_server_idf +} // namespace esphome +#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) diff --git a/esphome/components/web_server_idf/multipart.h b/esphome/components/web_server_idf/multipart.h new file mode 100644 index 0000000000..967c72ffa5 --- /dev/null +++ b/esphome/components/web_server_idf/multipart.h @@ -0,0 +1,86 @@ +#pragma once +#include "esphome/core/defines.h" +#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) + +#include +#include +#include +#include +#include +#include +#include + +namespace esphome { +namespace web_server_idf { + +// Wrapper around zorxx/multipart-parser for ESP-IDF OTA uploads +class MultipartReader { + public: + struct Part { + std::string name; + std::string filename; + std::string content_type; + }; + + // IMPORTANT: The data pointer in DataCallback is only valid during the callback! + // The multipart parser passes pointers to its internal buffer which will be + // overwritten after the callback returns. Callbacks MUST process or copy the + // data immediately - storing the pointer for deferred processing will result + // in use-after-free bugs. + using DataCallback = std::function; + using PartCompleteCallback = std::function; + + explicit MultipartReader(const std::string &boundary); + ~MultipartReader(); + + // Set callbacks for handling data + void set_data_callback(DataCallback callback) { data_callback_ = std::move(callback); } + void set_part_complete_callback(PartCompleteCallback callback) { part_complete_callback_ = std::move(callback); } + + // Parse incoming data + size_t parse(const char *data, size_t len); + + // Get current part info + const Part &get_current_part() const { return current_part_; } + + // Check if we found a file upload + bool has_file() const { return !current_part_.filename.empty(); } + + private: + static int on_header_field(multipart_parser *parser, const char *at, size_t length); + static int on_header_value(multipart_parser *parser, const char *at, size_t length); + static int on_part_data(multipart_parser *parser, const char *at, size_t length); + static int on_part_data_end(multipart_parser *parser); + + multipart_parser *parser_{nullptr}; + multipart_parser_settings settings_{}; + + Part current_part_; + std::string current_header_field_; + + DataCallback data_callback_; + PartCompleteCallback part_complete_callback_; + + void process_header_(const char *value, size_t length); +}; + +// ========== Utility Functions ========== + +// Case-insensitive string prefix check +bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix); + +// Extract a parameter value from a header line +// Handles both quoted and unquoted values +std::string extract_header_param(const std::string &header, const std::string ¶m); + +// Parse boundary from Content-Type header +// Returns true if boundary found, false otherwise +// boundary_start and boundary_len will point to the boundary value +bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len); + +// Trim whitespace from both ends of a string +std::string str_trim(const std::string &str); + +} // namespace web_server_idf +} // namespace esphome +#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) diff --git a/esphome/components/web_server_idf/utils.cpp b/esphome/components/web_server_idf/utils.cpp index 349acce50d..ac5df90bb8 100644 --- a/esphome/components/web_server_idf/utils.cpp +++ b/esphome/components/web_server_idf/utils.cpp @@ -1,5 +1,7 @@ #ifdef USE_ESP_IDF #include +#include +#include #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "http_parser.h" @@ -88,6 +90,36 @@ optional query_key_value(const std::string &query_url, const std::s return {val.get()}; } +// Helper function for case-insensitive string region comparison +bool str_ncmp_ci(const char *s1, const char *s2, size_t n) { + for (size_t i = 0; i < n; i++) { + if (!char_equals_ci(s1[i], s2[i])) { + return false; + } + } + return true; +} + +// Case-insensitive string search (like strstr but case-insensitive) +const char *stristr(const char *haystack, const char *needle) { + if (!haystack) { + return nullptr; + } + + size_t needle_len = strlen(needle); + if (needle_len == 0) { + return haystack; + } + + for (const char *p = haystack; *p; p++) { + if (str_ncmp_ci(p, needle, needle_len)) { + return p; + } + } + + return nullptr; +} + } // namespace web_server_idf } // namespace esphome #endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/utils.h b/esphome/components/web_server_idf/utils.h index 9ed17c1d50..988b962d72 100644 --- a/esphome/components/web_server_idf/utils.h +++ b/esphome/components/web_server_idf/utils.h @@ -2,6 +2,7 @@ #ifdef USE_ESP_IDF #include +#include #include "esphome/core/helpers.h" namespace esphome { @@ -12,6 +13,15 @@ optional request_get_header(httpd_req_t *req, const char *name); optional request_get_url_query(httpd_req_t *req); optional query_key_value(const std::string &query_url, const std::string &key); +// Helper function for case-insensitive character comparison +inline bool char_equals_ci(char a, char b) { return ::tolower(a) == ::tolower(b); } + +// Helper function for case-insensitive string region comparison +bool str_ncmp_ci(const char *s1, const char *s2, size_t n); + +// Case-insensitive string search (like strstr but case-insensitive) +const char *stristr(const char *haystack, const char *needle); + } // namespace web_server_idf } // namespace esphome #endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 409230806c..9478e4748c 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -1,16 +1,25 @@ #ifdef USE_ESP_IDF #include +#include +#include +#include #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esp_tls_crypto.h" +#include +#include #include "utils.h" - #include "web_server_idf.h" +#ifdef USE_WEBSERVER_OTA +#include +#include "multipart.h" // For parse_multipart_boundary and other utils +#endif + #ifdef USE_WEBSERVER #include "esphome/components/web_server/web_server.h" #include "esphome/components/web_server/list_entities.h" @@ -72,18 +81,32 @@ void AsyncWebServer::begin() { esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) { ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri); auto content_type = request_get_header(r, "Content-Type"); - if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") { - ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request"); - // fallback to get handler to support backward compatibility - return AsyncWebServer::request_handler(r); - } if (!request_has_header(r, "Content-Length")) { - ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri); + ESP_LOGW(TAG, "Content length is required for post: %s", r->uri); httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr); return ESP_OK; } + if (content_type.has_value()) { + const char *content_type_char = content_type.value().c_str(); + + // Check most common case first + if (stristr(content_type_char, "application/x-www-form-urlencoded") != nullptr) { + // Normal form data - proceed with regular handling +#ifdef USE_WEBSERVER_OTA + } else if (stristr(content_type_char, "multipart/form-data") != nullptr) { + auto *server = static_cast(r->user_ctx); + return server->handle_multipart_upload_(r, content_type_char); +#endif + } else { + ESP_LOGW(TAG, "Unsupported content type for POST: %s", content_type_char); + // fallback to get handler to support backward compatibility + return AsyncWebServer::request_handler(r); + } + } + + // Handle regular form data if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) { ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len); httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); @@ -539,6 +562,97 @@ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *e } #endif +#ifdef USE_WEBSERVER_OTA +esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *content_type) { + static constexpr size_t MULTIPART_CHUNK_SIZE = 1460; // Match Arduino AsyncWebServer buffer size + static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024; // Yield every 16KB to prevent watchdog + + // Parse boundary and create reader + const char *boundary_start; + size_t boundary_len; + if (!parse_multipart_boundary(content_type, &boundary_start, &boundary_len)) { + ESP_LOGE(TAG, "Failed to parse multipart boundary"); + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; + } + + AsyncWebServerRequest req(r); + AsyncWebHandler *handler = nullptr; + for (auto *h : this->handlers_) { + if (h->canHandle(&req)) { + handler = h; + break; + } + } + + if (!handler) { + ESP_LOGW(TAG, "No handler found for OTA request"); + httpd_resp_send_err(r, HTTPD_404_NOT_FOUND, nullptr); + return ESP_OK; + } + + // Upload state + std::string filename; + size_t index = 0; + // Create reader on heap to reduce stack usage + auto reader = std::make_unique("--" + std::string(boundary_start, boundary_len)); + + // Configure callbacks + reader->set_data_callback([&](const uint8_t *data, size_t len) { + if (!reader->has_file() || !len) + return; + + if (filename.empty()) { + filename = reader->get_current_part().filename; + ESP_LOGV(TAG, "Processing file: '%s'", filename.c_str()); + handler->handleUpload(&req, filename, 0, nullptr, 0, false); // Start + } + + handler->handleUpload(&req, filename, index, const_cast(data), len, false); + index += len; + }); + + reader->set_part_complete_callback([&]() { + if (index > 0) { + handler->handleUpload(&req, filename, index, nullptr, 0, true); // End + filename.clear(); + index = 0; + } + }); + + // Process data + std::unique_ptr buffer(new char[MULTIPART_CHUNK_SIZE]); + size_t bytes_since_yield = 0; + + for (size_t remaining = r->content_len; remaining > 0;) { + int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE)); + + if (recv_len <= 0) { + httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST, + nullptr); + return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL; + } + + if (reader->parse(buffer.get(), recv_len) != static_cast(recv_len)) { + ESP_LOGW(TAG, "Multipart parser error"); + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; + } + + remaining -= recv_len; + bytes_since_yield += recv_len; + + if (bytes_since_yield > YIELD_INTERVAL_BYTES) { + vTaskDelay(1); + bytes_since_yield = 0; + } + } + + handler->handleRequest(&req); + return ESP_OK; +} +#endif // USE_WEBSERVER_OTA + } // namespace web_server_idf } // namespace esphome diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 7547117224..8de25c8e96 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -204,6 +204,9 @@ class AsyncWebServer { static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; +#ifdef USE_WEBSERVER_OTA + esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type); +#endif std::vector handlers_; std::function on_not_found_{}; }; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ea3c8bdc17..cfaed6fdb7 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -153,6 +153,7 @@ #define USE_SPI #define USE_VOICE_ASSISTANT #define USE_WEBSERVER +#define USE_WEBSERVER_OTA #define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WEBSERVER_SORTING #define USE_WIFI_11KV_SUPPORT diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 6299909033..c43b622684 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -17,3 +17,5 @@ dependencies: version: 2.0.11 rules: - if: "target in [esp32h2, esp32p4]" + zorxx/multipart-parser: + version: 1.0.1 diff --git a/tests/components/web_server/test_no_ota.esp32-idf.yaml b/tests/components/web_server/test_no_ota.esp32-idf.yaml new file mode 100644 index 0000000000..1f677fb948 --- /dev/null +++ b/tests/components/web_server/test_no_ota.esp32-idf.yaml @@ -0,0 +1,9 @@ +packages: + device_base: !include common.yaml + +# No OTA component defined for this test + +web_server: + port: 8080 + version: 2 + ota: false diff --git a/tests/components/web_server/test_ota.esp32-idf.yaml b/tests/components/web_server/test_ota.esp32-idf.yaml new file mode 100644 index 0000000000..294e7f862e --- /dev/null +++ b/tests/components/web_server/test_ota.esp32-idf.yaml @@ -0,0 +1,32 @@ +# Test configuration for ESP-IDF web server with OTA enabled +esphome: + name: test-web-server-ota-idf + +# Force ESP-IDF framework +esp32: + board: esp32dev + framework: + type: esp-idf + +packages: + device_base: !include common.yaml + +# Enable OTA for multipart upload testing +ota: + - platform: esphome + password: "test_ota_password" + +# Web server with OTA enabled +web_server: + port: 8080 + version: 2 + ota: true + include_internal: true + +# Enable debug logging for OTA +logger: + level: DEBUG + logs: + web_server: VERBOSE + web_server_idf: VERBOSE + diff --git a/tests/components/web_server/test_ota_disabled.esp32-idf.yaml b/tests/components/web_server/test_ota_disabled.esp32-idf.yaml new file mode 100644 index 0000000000..c7c7574e3b --- /dev/null +++ b/tests/components/web_server/test_ota_disabled.esp32-idf.yaml @@ -0,0 +1,11 @@ +packages: + device_base: !include common.yaml + +# OTA is configured but web_server OTA is disabled +ota: + - platform: esphome + +web_server: + port: 8080 + version: 2 + ota: false From 35de36d690188623cbd11062aeb59fdc35a4ee1e Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 1 Jul 2025 05:39:06 +0200 Subject: [PATCH 13/35] [modbus] Modbus server role: write holding registers (#9156) --- esphome/components/modbus/modbus.cpp | 48 +++++++---- esphome/components/modbus/modbus.h | 1 + .../components/modbus_controller/__init__.py | 13 +++ .../modbus_controller/modbus_controller.cpp | 80 +++++++++++++++++++ .../modbus_controller/modbus_controller.h | 15 ++++ .../components/modbus_controller/common.yaml | 13 ++- 6 files changed, 154 insertions(+), 16 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index c2efa93fae..6350f43ef6 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -90,15 +90,24 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { } else { // data starts at 2 and length is 4 for read registers commands - if (this->role == ModbusRole::SERVER && (function_code == 0x1 || function_code == 0x3 || function_code == 0x4)) { - data_offset = 2; - data_len = 4; - } - - // the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands - if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) { - data_offset = 2; - data_len = 4; + if (this->role == ModbusRole::SERVER) { + if (function_code == 0x1 || function_code == 0x3 || function_code == 0x4 || function_code == 0x6) { + data_offset = 2; + data_len = 4; + } else if (function_code == 0x10) { + if (at < 6) { + return true; + } + data_offset = 2; + // starting address (2 bytes) + quantity of registers (2 bytes) + byte count itself (1 byte) + actual byte count + data_len = 2 + 2 + 1 + raw[6]; + } + } else { + // the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands + if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) { + data_offset = 2; + data_len = 4; + } } // Error ( msb indicates error ) @@ -132,6 +141,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { + found = true; // Is it an error response? if ((function_code & 0x80) == 0x80) { ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); @@ -141,13 +151,21 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { // Ignore modbus exception not related to a pending command ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response"); } - } else if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) { - device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8), - uint16_t(data[3]) | (uint16_t(data[2]) << 8)); - } else { - device->on_modbus_data(data); + continue; } - found = true; + if (this->role == ModbusRole::SERVER) { + if (function_code == 0x3 || function_code == 0x4) { + device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8), + uint16_t(data[3]) | (uint16_t(data[2]) << 8)); + continue; + } + if (function_code == 0x6 || function_code == 0x10) { + device->on_modbus_write_registers(function_code, data); + continue; + } + } + // fallthrough for other function codes + device->on_modbus_data(data); } } waiting_for_response = 0; diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index aebdbccc78..ec35612690 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -59,6 +59,7 @@ class ModbusDevice { virtual void on_modbus_data(const std::vector &data) = 0; virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {} virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){}; + virtual void on_modbus_write_registers(uint8_t function_code, const std::vector &data){}; void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0, const uint8_t *payload = nullptr) { this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 8079b824b0..5ab82f5e17 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -39,6 +39,7 @@ CODEOWNERS = ["@martgras"] AUTO_LOAD = ["modbus"] CONF_READ_LAMBDA = "read_lambda" +CONF_WRITE_LAMBDA = "write_lambda" CONF_SERVER_REGISTERS = "server_registers" MULTI_CONF = True @@ -148,6 +149,7 @@ ModbusServerRegisterSchema = cv.Schema( cv.Required(CONF_ADDRESS): cv.positive_int, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Required(CONF_READ_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, } ) @@ -318,6 +320,17 @@ async def to_code(config): ), ) ) + if CONF_WRITE_LAMBDA in server_register: + cg.add( + server_register_var.set_write_lambda( + cg.TemplateArguments(cpp_type), + await cg.process_lambda( + server_register[CONF_WRITE_LAMBDA], + parameters=[(cg.uint16, "address"), (cpp_type, "x")], + return_type=cg.bool_, + ), + ) + ) cg.add(var.add_server_register(server_register_var)) await register_modbus_device(var, config) for conf in config.get(CONF_ON_COMMAND_SENT, []): diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 81e9ccf0a6..0f3ddf920d 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,6 +152,86 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t this->send(function_code, start_address, number_of_registers, response.size(), response.data()); } +void ModbusController::on_modbus_write_registers(uint8_t function_code, const std::vector &data) { + uint16_t number_of_registers; + uint16_t payload_offset; + + if (function_code == 0x10) { + number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8); + if (number_of_registers == 0 || number_of_registers > 0x7B) { + ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers); + send_error(function_code, 3); + return; + } + uint16_t payload_size = data[4]; + if (payload_size != number_of_registers * 2) { + ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.", + payload_size, number_of_registers); + send_error(function_code, 3); + return; + } + payload_offset = 5; + } else if (function_code == 0x06) { + number_of_registers = 1; + payload_offset = 2; + } else { + ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code); + send_error(function_code, 1); + return; + } + + uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8); + ESP_LOGD(TAG, + "Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: " + "0x%X.", + this->address_, function_code, start_address, number_of_registers); + + auto for_each_register = [this, start_address, number_of_registers, payload_offset]( + const std::function &callback) -> bool { + uint16_t offset = payload_offset; + for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) { + bool ok = false; + for (auto *server_register : this->server_registers_) { + if (server_register->address == current_address) { + ok = callback(server_register, offset); + current_address += server_register->register_count; + offset += server_register->register_count * sizeof(uint16_t); + break; + } + } + + if (!ok) { + return false; + } + } + return true; + }; + + // check all registers are writable before writing to any of them: + if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool { + return server_register->write_lambda != nullptr; + })) { + send_error(function_code, 1); + return; + } + + // Actually write to the registers: + if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) { + int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF); + return server_register->write_lambda(number); + })) { + send_error(function_code, 4); + return; + } + + std::vector response; + response.reserve(6); + response.push_back(this->address_); + response.push_back(function_code); + response.insert(response.end(), data.begin(), data.begin() + 4); + this->send_raw(response); +} + SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { auto reg_it = std::find_if( std::begin(this->register_ranges_), std::end(this->register_ranges_), diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 11d27c4025..a86ad1ccb5 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -258,6 +258,7 @@ class SensorItem { class ServerRegister { using ReadLambda = std::function; + using WriteLambda = std::function; public: ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) { @@ -277,6 +278,17 @@ class ServerRegister { }; } + template + void set_write_lambda(const std::function &&user_write_lambda) { + this->write_lambda = [this, user_write_lambda](int64_t number) { + if constexpr (std::is_same_v) { + float float_value = bit_cast(static_cast(number)); + return user_write_lambda(this->address, float_value); + } + return user_write_lambda(this->address, static_cast(number)); + }; + } + // Formats a raw value into a string representation based on the value type for debugging std::string format_value(int64_t value) const { switch (this->value_type) { @@ -304,6 +316,7 @@ class ServerRegister { SensorValueType value_type{SensorValueType::RAW}; uint8_t register_count{0}; ReadLambda read_lambda; + WriteLambda write_lambda; }; // ModbusController::create_register_ranges_ tries to optimize register range @@ -485,6 +498,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; /// called when a modbus request (function code 0x03 or 0x04) was parsed without errors void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; + /// called when a modbus request (function code 0x06 or 0x10) was parsed without errors + void on_modbus_write_registers(uint8_t function_code, const std::vector &data) final; /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data); /// default delegate called by process_modbus_data when a response for a write response has retrieved from the diff --git a/tests/components/modbus_controller/common.yaml b/tests/components/modbus_controller/common.yaml index 7fa9f8dae3..7d342ee353 100644 --- a/tests/components/modbus_controller/common.yaml +++ b/tests/components/modbus_controller/common.yaml @@ -33,7 +33,18 @@ modbus_controller: read_lambda: |- return 42.3; max_cmd_retries: 0 - + - id: modbus_controller3 + address: 0x3 + modbus_id: mod_bus2 + server_registers: + - address: 0x0009 + value_type: S_DWORD + read_lambda: |- + return 31; + write_lambda: |- + printf("address=%d, value=%d", x); + return true; + max_cmd_retries: 0 binary_sensor: - platform: modbus_controller modbus_controller_id: modbus_controller1 From 3470305d9d41837a8412eee0b4f6fdf247e6beed Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:22:41 -0400 Subject: [PATCH 14/35] [esp32] Remove IDF 4 support and clean up code (#9145) --- esphome/components/adc/adc_sensor.h | 7 +-- esphome/components/esp32/__init__.py | 28 ++++-------- esphome/components/esp32/core.cpp | 4 -- .../components/esp32_ble_tracker/__init__.py | 10 +---- .../components/ethernet/esp_eth_phy_jl1101.c | 16 ------- .../ethernet/ethernet_component.cpp | 17 ------- esphome/components/i2s_audio/__init__.py | 12 +---- esphome/components/i2s_audio/i2s_audio.cpp | 6 +-- .../improv_serial/improv_serial_component.cpp | 4 -- .../internal_temperature.cpp | 35 ++++----------- .../components/internal_temperature/sensor.py | 26 ----------- esphome/components/logger/logger_esp32.cpp | 2 - esphome/components/mdns/__init__.py | 6 +-- .../components/mqtt/mqtt_backend_esp32.cpp | 45 +------------------ esphome/components/opentherm/opentherm.cpp | 19 +++----- .../components/ota/ota_backend_esp_idf.cpp | 11 ----- esphome/components/psram/psram.cpp | 20 --------- .../display/pvvx_display.cpp | 4 -- .../components/rpi_dpi_rgb/rpi_dpi_rgb.cpp | 4 -- esphome/components/st7701s/st7701s.cpp | 4 -- .../uart/uart_component_esp_idf.cpp | 4 -- esphome/components/watchdog/watchdog.cpp | 4 -- esphome/components/wireguard/__init__.py | 13 +----- 23 files changed, 34 insertions(+), 267 deletions(-) diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 9ffb6cf856..28dfd2262c 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -15,8 +15,7 @@ namespace adc { #ifdef USE_ESP32 // clang-format off -#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \ - (ESP_IDF_VERSION_MAJOR == 5 && \ +#if (ESP_IDF_VERSION_MAJOR == 5 && \ ((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \ (ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \ (ESP_IDF_VERSION_MINOR >= 2)) \ @@ -100,11 +99,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage adc1_channel_t channel1_{ADC1_CHANNEL_MAX}; adc2_channel_t channel2_{ADC2_CHANNEL_MAX}; bool autorange_{false}; -#if ESP_IDF_VERSION_MAJOR >= 5 esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; -#else - esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; -#endif // ESP_IDF_VERSION_MAJOR #endif // USE_ESP32 }; diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 32323b7504..b4c7a4e05b 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -411,8 +411,8 @@ def _esp_idf_check_versions(value): version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) source = value.get(CONF_SOURCE, None) - if version < cv.Version(4, 0, 0): - raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") + if version < cv.Version(5, 0, 0): + raise cv.Invalid("Only ESP-IDF 5.0+ is supported.") # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below has_platform_ver = CONF_PLATFORM_VERSION in value @@ -422,20 +422,15 @@ def _esp_idf_check_versions(value): ) if ( - (is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])) - and version.major >= 5 - and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X - ): + is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]) + ) and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X: raise cv.Invalid( f"ESP-IDF {str(version)} not supported by platformio/espressif32" ) if ( - version.major < 5 - or ( - version in SUPPORTED_PLATFORMIO_ESP_IDF_5X - and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X - ) + version in SUPPORTED_PLATFORMIO_ESP_IDF_5X + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X ) and not has_platform_ver: raise cv.Invalid( f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'" @@ -801,14 +796,9 @@ async def to_code(config): if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC): add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) - if (framework_ver.major, framework_ver.minor) >= (4, 4): - add_idf_sdkconfig_option( - "CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False - ) - else: - add_idf_sdkconfig_option( - "CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False - ) + add_idf_sdkconfig_option( + "CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False + ) if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): _LOGGER.warning( "Using experimental features in ESP-IDF may result in unexpected failures." diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 562bcba3c2..f3bdfea2a0 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -56,11 +56,7 @@ void arch_init() { void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -#if ESP_IDF_VERSION_MAJOR >= 5 uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } -#else -uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); } -#endif uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; #ifdef USE_ESP_IDF diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 2242d709a4..547cf84ed1 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -29,8 +29,6 @@ from esphome.const import ( CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_SERVICE_UUID, CONF_TRIGGER_ID, - KEY_CORE, - KEY_FRAMEWORK_VERSION, ) from esphome.core import CORE @@ -323,10 +321,7 @@ async def to_code(config): # https://github.com/espressif/esp-idf/issues/2503 # Match arduino CONFIG_BTU_TASK_STACK_SIZE # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 - if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(4, 4, 6): - add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) - else: - add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192) + add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) add_idf_sdkconfig_option( "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS] @@ -335,8 +330,7 @@ async def to_code(config): # max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS # is enough in 4.x # https://github.com/esphome/issues/issues/6808 - if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 0, 0): - add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9) + add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9) cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") diff --git a/esphome/components/ethernet/esp_eth_phy_jl1101.c b/esphome/components/ethernet/esp_eth_phy_jl1101.c index de2a6f4f35..4f31e0a9fd 100644 --- a/esphome/components/ethernet/esp_eth_phy_jl1101.c +++ b/esphome/components/ethernet/esp_eth_phy_jl1101.c @@ -19,11 +19,7 @@ #include #include "esp_log.h" #include "esp_eth.h" -#if ESP_IDF_VERSION_MAJOR >= 5 #include "esp_eth_phy_802_3.h" -#else -#include "eth_phy_regs_struct.h" -#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" @@ -174,11 +170,7 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) { return ESP_OK; } -#if ESP_IDF_VERSION_MAJOR >= 5 static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) { -#else -static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) { -#endif phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent); esp_eth_mediator_t *eth = jl1101->eth; /* in case any link status has changed, let's assume we're in link down status */ @@ -293,11 +285,7 @@ static esp_err_t jl1101_init(esp_eth_phy_t *phy) { esp_eth_mediator_t *eth = jl1101->eth; // Detect PHY address if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) { -#if ESP_IDF_VERSION_MAJOR >= 5 PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err); -#else - PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err); -#endif } /* Power on Ethernet PHY */ PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err); @@ -336,11 +324,7 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) { jl1101->parent.init = jl1101_init; jl1101->parent.deinit = jl1101_deinit; jl1101->parent.set_mediator = jl1101_set_mediator; -#if ESP_IDF_VERSION_MAJOR >= 5 jl1101->parent.autonego_ctrl = jl1101_negotiate; -#else - jl1101->parent.negotiate = jl1101_negotiate; -#endif jl1101->parent.get_link = jl1101_get_link; jl1101->parent.pwrctl = jl1101_pwrctl; jl1101->parent.get_addr = jl1101_get_addr; diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 19a11c6945..f8c2f3a72e 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -122,25 +122,12 @@ void EthernetComponent::setup() { .post_cb = nullptr, }; -#if ESP_IDF_VERSION_MAJOR >= 5 #if CONFIG_ETH_SPI_ETHERNET_W5500 eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); #endif #if CONFIG_ETH_SPI_ETHERNET_DM9051 eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg); #endif -#else - spi_device_handle_t spi_handle = nullptr; - err = spi_bus_add_device(host, &devcfg, &spi_handle); - ESPHL_ERROR_CHECK(err, "SPI bus add device error"); - -#if CONFIG_ETH_SPI_ETHERNET_W5500 - eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); -#endif -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle); -#endif -#endif // ESP_IDF_VERSION_MAJOR >= 5 #if CONFIG_ETH_SPI_ETHERNET_W5500 w5500_config.int_gpio_num = this->interrupt_pin_; @@ -211,11 +198,7 @@ void EthernetComponent::setup() { } case ETHERNET_TYPE_KSZ8081: case ETHERNET_TYPE_KSZ8081RNA: { -#if ESP_IDF_VERSION_MAJOR >= 5 this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); -#else - this->phy_ = esp_eth_phy_new_ksz8081(&phy_config); -#endif break; } #endif diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index ef95fd0b41..9a2aa0362f 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -9,14 +9,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S3, ) import esphome.config_validation as cv -from esphome.const import ( - CONF_BITS_PER_SAMPLE, - CONF_CHANNEL, - CONF_ID, - CONF_SAMPLE_RATE, - KEY_CORE, - KEY_FRAMEWORK_VERSION, -) +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE from esphome.core import CORE from esphome.cpp_generator import MockObjClass import esphome.final_validate as fv @@ -250,8 +243,7 @@ def _final_validate(_): def use_legacy(): - framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] - if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0): + if CORE.using_esp_idf: if not _use_legacy_driver: return False return True diff --git a/esphome/components/i2s_audio/i2s_audio.cpp b/esphome/components/i2s_audio/i2s_audio.cpp index 7ff21bba57..7f233516e6 100644 --- a/esphome/components/i2s_audio/i2s_audio.cpp +++ b/esphome/components/i2s_audio/i2s_audio.cpp @@ -9,15 +9,11 @@ namespace i2s_audio { static const char *const TAG = "i2s_audio"; -#if ESP_IDF_VERSION_MAJOR >= 5 -static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :( -#endif - void I2SAudioComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); static i2s_port_t next_port_num = I2S_NUM_0; - if (next_port_num >= I2S_NUM_MAX) { + if (next_port_num >= SOC_I2S_NUM) { ESP_LOGE(TAG, "Too many components"); this->mark_failed(); return; diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index c3a0f2eacc..ae4927828b 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -59,11 +59,7 @@ optional ImprovSerialComponent::read_byte_() { break; #if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC) case logger::UART_SELECTION_USB_CDC: -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) if (esp_usb_console_available_for_read()) { -#else - if (esp_usb_console_read_available()) { -#endif esp_usb_console_read_buf((char *) &data, 1); byte = data; } diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index f503927d8e..85844647f2 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -10,11 +10,7 @@ uint8_t temprature_sens_read(); #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) -#include "driver/temp_sensor.h" -#else #include "driver/temperature_sensor.h" -#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32_VARIANT #endif // USE_ESP32 #ifdef USE_RP2040 @@ -31,12 +27,11 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \ - (defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4)) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) static temperature_sensor_handle_t tsensNew = NULL; -#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT +#endif // USE_ESP32_VARIANT #endif // USE_ESP32 void InternalTemperatureSensor::update() { @@ -51,24 +46,11 @@ void InternalTemperatureSensor::update() { #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); - temp_sensor_set_config(tsens); - temp_sensor_start(); -#if defined(USE_ESP32_VARIANT_ESP32S3) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 3)) -#error \ - "ESP32-S3 internal temperature sensor requires ESP IDF V4.4.3 or higher. See https://github.com/esphome/issues/issues/4271" -#endif - esp_err_t result = temp_sensor_read_celsius(&temperature); - temp_sensor_stop(); - success = (result == ESP_OK); -#else esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { ESP_LOGE(TAG, "Reading failed (%d)", result); } -#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32_VARIANT #endif // USE_ESP32 #ifdef USE_RP2040 @@ -99,10 +81,9 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \ - (defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4)) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) ESP_LOGCONFIG(TAG, "Running setup"); temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); @@ -120,7 +101,7 @@ void InternalTemperatureSensor::setup() { this->mark_failed(); return; } -#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT +#endif // USE_ESP32_VARIANT #endif // USE_ESP32 } diff --git a/esphome/components/internal_temperature/sensor.py b/esphome/components/internal_temperature/sensor.py index 9bfa3739c8..93b98a30f4 100644 --- a/esphome/components/internal_temperature/sensor.py +++ b/esphome/components/internal_temperature/sensor.py @@ -1,46 +1,21 @@ import esphome.codegen as cg from esphome.components import sensor -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import VARIANT_ESP32S3 import esphome.config_validation as cv from esphome.const import ( DEVICE_CLASS_TEMPERATURE, ENTITY_CATEGORY_DIAGNOSTIC, - KEY_CORE, - KEY_FRAMEWORK_VERSION, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_RP2040, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) -from esphome.core import CORE internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature") InternalTemperatureSensor = internal_temperature_ns.class_( "InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent ) - -def validate_config(config): - if CORE.is_esp32: - variant = get_esp32_variant() - if variant == VARIANT_ESP32S3: - if CORE.using_arduino and CORE.data[KEY_CORE][ - KEY_FRAMEWORK_VERSION - ] < cv.Version(2, 0, 6): - raise cv.Invalid( - "ESP32-S3 Internal Temperature Sensor requires framework version 2.0.6 or higher. See ." - ) - if CORE.using_esp_idf and CORE.data[KEY_CORE][ - KEY_FRAMEWORK_VERSION - ] < cv.Version(4, 4, 3): - raise cv.Invalid( - "ESP32-S3 Internal Temperature Sensor requires framework version 4.4.3 or higher. See ." - ) - return config - - CONFIG_SCHEMA = cv.All( sensor.sensor_schema( InternalTemperatureSensor, @@ -51,7 +26,6 @@ CONFIG_SCHEMA = cv.All( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ).extend(cv.polling_component_schema("60s")), cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]), - validate_config, ) diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index b5ac84a665..41445fa3b4 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -83,9 +83,7 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) { uart_config.parity = UART_PARITY_DISABLE; uart_config.stop_bits = UART_STOP_BITS_1; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) uart_config.source_clk = UART_SCLK_DEFAULT; -#endif uart_param_config(uart_num, &uart_config); const int uart_buffer_size = tx_buffer_size; // Install UART driver using an event queue here diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 2f81068e8a..ed230d43aa 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -8,8 +8,6 @@ from esphome.const import ( CONF_PROTOCOL, CONF_SERVICE, CONF_SERVICES, - KEY_CORE, - KEY_FRAMEWORK_VERSION, ) from esphome.core import CORE, coroutine_with_priority @@ -85,9 +83,7 @@ async def to_code(config): elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) - if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( - 5, 0, 0 - ): + if CORE.using_esp_idf: add_idf_component(name="espressif/mdns", ref="1.8.2") cg.add_define("USE_MDNS") diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 4648e66e1d..a096408aa5 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -14,49 +14,6 @@ namespace mqtt { static const char *const TAG = "mqtt.idf"; bool MQTTBackendESP32::initialize_() { -#if ESP_IDF_VERSION_MAJOR < 5 - mqtt_cfg_.user_context = (void *) this; - mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE; - - mqtt_cfg_.host = this->host_.c_str(); - mqtt_cfg_.port = this->port_; - mqtt_cfg_.keepalive = this->keep_alive_; - mqtt_cfg_.disable_clean_session = !this->clean_session_; - - if (!this->username_.empty()) { - mqtt_cfg_.username = this->username_.c_str(); - if (!this->password_.empty()) { - mqtt_cfg_.password = this->password_.c_str(); - } - } - - if (!this->lwt_topic_.empty()) { - mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str(); - this->mqtt_cfg_.lwt_qos = this->lwt_qos_; - this->mqtt_cfg_.lwt_retain = this->lwt_retain_; - - if (!this->lwt_message_.empty()) { - mqtt_cfg_.lwt_msg = this->lwt_message_.c_str(); - mqtt_cfg_.lwt_msg_len = this->lwt_message_.size(); - } - } - - if (!this->client_id_.empty()) { - mqtt_cfg_.client_id = this->client_id_.c_str(); - } - if (ca_certificate_.has_value()) { - mqtt_cfg_.cert_pem = ca_certificate_.value().c_str(); - mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_; - mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL; - - if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) { - mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str(); - mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str(); - } - } else { - mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; - } -#else mqtt_cfg_.broker.address.hostname = this->host_.c_str(); mqtt_cfg_.broker.address.port = this->port_; mqtt_cfg_.session.keepalive = this->keep_alive_; @@ -95,7 +52,7 @@ bool MQTTBackendESP32::initialize_() { } else { mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; } -#endif + auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_); if (mqtt_client) { handler_.reset(mqtt_client); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 49482316ee..b2751470b2 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -272,18 +272,13 @@ bool OpenTherm::init_esp32_timer_() { this->timer_idx_ = timer_idx; timer_config_t const config = { - .alarm_en = TIMER_ALARM_EN, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL, - .counter_dir = TIMER_COUNT_UP, - .auto_reload = TIMER_AUTORELOAD_EN, -#if ESP_IDF_VERSION_MAJOR >= 5 - .clk_src = TIMER_SRC_CLK_DEFAULT, -#endif - .divider = 80, -#if defined(SOC_TIMER_GROUP_SUPPORT_XTAL) && ESP_IDF_VERSION_MAJOR < 5 - .clk_src = TIMER_SRC_CLK_APB -#endif + .alarm_en = TIMER_ALARM_EN, + .counter_en = TIMER_PAUSE, + .intr_type = TIMER_INTR_LEVEL, + .counter_dir = TIMER_COUNT_UP, + .auto_reload = TIMER_AUTORELOAD_EN, + .clk_src = TIMER_SRC_CLK_DEFAULT, + .divider = 80, }; esp_err_t result; diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 6f45fb75e4..cad44a5795 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -6,10 +6,7 @@ #include #include - -#if ESP_IDF_VERSION_MAJOR >= 5 #include -#endif namespace esphome { namespace ota { @@ -24,7 +21,6 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { #if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 // The following function takes longer than the 5 seconds timeout of WDT -#if ESP_IDF_VERSION_MAJOR >= 5 esp_task_wdt_config_t wdtc; wdtc.idle_core_mask = 0; #if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 @@ -36,21 +32,14 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { wdtc.timeout_ms = 15000; wdtc.trigger_panic = false; esp_task_wdt_reconfigure(&wdtc); -#else - esp_task_wdt_init(15, false); -#endif #endif esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_); #if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 // Set the WDT back to the configured timeout -#if ESP_IDF_VERSION_MAJOR >= 5 wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; esp_task_wdt_reconfigure(&wdtc); -#else - esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); -#endif #endif if (err != ESP_OK) { diff --git a/esphome/components/psram/psram.cpp b/esphome/components/psram/psram.cpp index 162543545e..6c110a577d 100644 --- a/esphome/components/psram/psram.cpp +++ b/esphome/components/psram/psram.cpp @@ -2,9 +2,7 @@ #ifdef USE_ESP32 #include "psram.h" #include -#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5 #include -#endif // USE_ESP_IDF #include "esphome/core/log.h" @@ -16,7 +14,6 @@ static const char *const TAG = "psram"; void PsramComponent::dump_config() { ESP_LOGCONFIG(TAG, "PSRAM:"); -#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5 bool available = esp_psram_is_initialized(); ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available)); @@ -26,23 +23,6 @@ void PsramComponent::dump_config() { ESP_LOGCONFIG(TAG, " ECC enabled: YES"); #endif } -#else - // Technically this can be false if the PSRAM is full, but heap_caps_get_total_size() isn't always available, and it's - // very unlikely for the PSRAM to be full. - bool available = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0; - ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available)); - - if (available) { - const size_t psram_total_size_bytes = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); - const float psram_total_size_kb = psram_total_size_bytes / 1024.0f; - - if (abs(std::round(psram_total_size_kb) - psram_total_size_kb) < 0.05f) { - ESP_LOGCONFIG(TAG, " Size: %.0f KB", psram_total_size_kb); - } else { - ESP_LOGCONFIG(TAG, " Size: %zu bytes", psram_total_size_bytes); - } - } -#endif // USE_ESP_IDF } } // namespace psram diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 74f63a9640..4b6c11b332 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -146,11 +146,7 @@ void PVVXDisplay::sync_time_() { } time.recalc_timestamp_utc(true); // calculate timestamp of local time uint8_t blk[5] = {}; -#if ESP_IDF_VERSION_MAJOR >= 5 ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str().c_str(), time.timestamp); -#else - ESP_LOGD(TAG, "[%s] Sync time with timestamp %lu.", this->parent_->address_str().c_str(), time.timestamp); -#endif blk[0] = 0x23; blk[1] = time.timestamp & 0xff; blk[2] = (time.timestamp >> 8) & 0xff; diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 666bac354d..91eb947a3e 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -10,10 +10,8 @@ void RpiDpiRgb::setup() { this->reset_display_(); esp_lcd_rgb_panel_config_t config{}; config.flags.fb_in_psram = 1; -#if ESP_IDF_VERSION_MAJOR >= 5 config.bounce_buffer_size_px = this->width_ * 10; config.num_fbs = 1; -#endif // ESP_IDF_VERSION_MAJOR config.timings.h_res = this->width_; config.timings.v_res = this->height_; config.timings.hsync_pulse_width = this->hsync_pulse_width_; @@ -47,10 +45,8 @@ void RpiDpiRgb::setup() { ESP_LOGCONFIG(TAG, "RPI_DPI_RGB setup complete"); } void RpiDpiRgb::loop() { -#if ESP_IDF_VERSION_MAJOR >= 5 if (this->handle_ != nullptr) esp_lcd_rgb_panel_restart(this->handle_); -#endif // ESP_IDF_VERSION_MAJOR } void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp index 6261f33b77..46509a7f9f 100644 --- a/esphome/components/st7701s/st7701s.cpp +++ b/esphome/components/st7701s/st7701s.cpp @@ -12,10 +12,8 @@ void ST7701S::setup() { esp_lcd_rgb_panel_config_t config{}; config.flags.fb_in_psram = 1; -#if ESP_IDF_VERSION_MAJOR >= 5 config.bounce_buffer_size_px = this->width_ * 10; config.num_fbs = 1; -#endif // ESP_IDF_VERSION_MAJOR config.timings.h_res = this->width_; config.timings.v_res = this->height_; config.timings.hsync_pulse_width = this->hsync_pulse_width_; @@ -48,10 +46,8 @@ void ST7701S::setup() { } void ST7701S::loop() { -#if ESP_IDF_VERSION_MAJOR >= 5 if (this->handle_ != nullptr) esp_lcd_rgb_panel_restart(this->handle_); -#endif // ESP_IDF_VERSION_MAJOR } void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 8fae63a603..63b2579c3f 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -48,11 +48,7 @@ uart_config_t IDFUARTComponent::get_config_() { uart_config.parity = parity; uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) uart_config.source_clk = UART_SCLK_DEFAULT; -#else - uart_config.source_clk = UART_SCLK_APB; -#endif uart_config.rx_flow_ctrl_thresh = 122; return uart_config; diff --git a/esphome/components/watchdog/watchdog.cpp b/esphome/components/watchdog/watchdog.cpp index f6f2992a11..2ce46756e4 100644 --- a/esphome/components/watchdog/watchdog.cpp +++ b/esphome/components/watchdog/watchdog.cpp @@ -38,16 +38,12 @@ WatchdogManager::~WatchdogManager() { void WatchdogManager::set_timeout_(uint32_t timeout_ms) { ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms); #ifdef USE_ESP32 -#if ESP_IDF_VERSION_MAJOR >= 5 esp_task_wdt_config_t wdt_config = { .timeout_ms = timeout_ms, .idle_core_mask = (1 << SOC_CPU_CORES_NUM) - 1, .trigger_panic = true, }; esp_task_wdt_reconfigure(&wdt_config); -#else - esp_task_wdt_init(timeout_ms / 1000, true); -#endif // ESP_IDF_VERSION_MAJOR #endif // USE_ESP32 #ifdef USE_RP2040 diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index fc0e4e0538..8eff8e7b2a 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -6,14 +6,7 @@ import esphome.codegen as cg from esphome.components import time from esphome.components.esp32 import CORE, add_idf_sdkconfig_option import esphome.config_validation as cv -from esphome.const import ( - CONF_ADDRESS, - CONF_ID, - CONF_REBOOT_TIMEOUT, - CONF_TIME_ID, - KEY_CORE, - KEY_FRAMEWORK_VERSION, -) +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_REBOOT_TIMEOUT, CONF_TIME_ID from esphome.core import TimePeriod CONF_NETMASK = "netmask" @@ -125,9 +118,7 @@ async def to_code(config): # Workaround for crash on IDF 5+ # See https://github.com/trombik/esp_wireguard/issues/33#issuecomment-1568503651 - if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( - 5, 0, 0 - ): + if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True) # This flag is added here because the esp_wireguard library statically From 0083abe3b5ad93192d2ab33bf16660dc286f982e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Jul 2025 18:30:03 -0500 Subject: [PATCH 15/35] Fix regression: BK7231N devices not returning entities via API (#9283) --- esphome/components/api/api_connection.cpp | 47 ++++----- esphome/components/api/api_connection.h | 119 ++++++++++------------ 2 files changed, 72 insertions(+), 94 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b7624221c9..e83d508c50 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1687,7 +1687,9 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c // O(n) but optimized for RAM and not performance. for (auto &item : items) { if (item.entity == entity && item.message_type == message_type) { - // Update the existing item with the new creator + // Clean up old creator before replacing + item.creator.cleanup(message_type); + // Move assign the new creator item.creator = std::move(creator); return; } @@ -1730,11 +1732,11 @@ void APIConnection::process_batch_() { return; } - size_t num_items = this->deferred_batch_.items.size(); + size_t num_items = this->deferred_batch_.size(); // Fast path for single message - allocate exact size needed if (num_items == 1) { - const auto &item = this->deferred_batch_.items[0]; + const auto &item = this->deferred_batch_[0]; // Let the creator calculate size and encode if it fits uint16_t payload_size = @@ -1764,7 +1766,8 @@ void APIConnection::process_batch_() { // Pre-calculate exact buffer size needed based on message types uint32_t total_estimated_size = 0; - for (const auto &item : this->deferred_batch_.items) { + for (size_t i = 0; i < this->deferred_batch_.size(); i++) { + const auto &item = this->deferred_batch_[i]; total_estimated_size += get_estimated_message_size(item.message_type); } @@ -1785,7 +1788,8 @@ void APIConnection::process_batch_() { uint32_t current_offset = 0; // Process items and encode directly to buffer - for (const auto &item : this->deferred_batch_.items) { + for (size_t i = 0; i < this->deferred_batch_.size(); i++) { + const auto &item = this->deferred_batch_[i]; // Try to encode message // The creator will calculate overhead to determine if the message fits uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); @@ -1840,17 +1844,15 @@ void APIConnection::process_batch_() { // Log messages after send attempt for VV debugging // It's safe to use the buffer for logging at this point regardless of send result for (size_t i = 0; i < items_processed; i++) { - const auto &item = this->deferred_batch_.items[i]; + const auto &item = this->deferred_batch_[i]; this->log_batch_item_(item); } #endif // Handle remaining items more efficiently - if (items_processed < this->deferred_batch_.items.size()) { - // Remove processed items from the beginning - this->deferred_batch_.items.erase(this->deferred_batch_.items.begin(), - this->deferred_batch_.items.begin() + items_processed); - + if (items_processed < this->deferred_batch_.size()) { + // Remove processed items from the beginning with proper cleanup + this->deferred_batch_.remove_front(items_processed); // Reschedule for remaining items this->schedule_batch_(); } else { @@ -1861,23 +1863,16 @@ void APIConnection::process_batch_() { uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t message_type) const { - if (has_tagged_string_ptr_()) { - // Handle string-based messages - switch (message_type) { #ifdef USE_EVENT - case EventResponse::MESSAGE_TYPE: { - auto *e = static_cast(entity); - return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); - } -#endif - default: - // Should not happen, return 0 to indicate no message - return 0; - } - } else { - // Function pointer case - return data_.ptr(entity, conn, remaining_size, is_single); + // Special case: EventResponse uses string pointer + if (message_type == EventResponse::MESSAGE_TYPE) { + auto *e = static_cast(entity); + return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); } +#endif + + // All other message types use function pointers + return data_.function_ptr(entity, conn, remaining_size, is_single); } uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 410a9ad3a5..151369aa70 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -451,96 +451,53 @@ class APIConnection : public APIServerConnection { // Function pointer type for message encoding using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); - // Optimized MessageCreator class using tagged pointer class MessageCreator { - // Ensure pointer alignment allows LSB tagging - static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); - public: // Constructor for function pointer - MessageCreator(MessageCreatorPtr ptr) { - // Function pointers are always aligned, so LSB is 0 - data_.ptr = ptr; - } + MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } // Constructor for string state capture - explicit MessageCreator(const std::string &str_value) { - // Allocate string and tag the pointer - auto *str = new std::string(str_value); - // Set LSB to 1 to indicate string pointer - data_.tagged = reinterpret_cast(str) | 1; - } + explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); } - // Destructor - ~MessageCreator() { - if (has_tagged_string_ptr_()) { - delete get_string_ptr_(); - } - } + // No destructor - cleanup must be called explicitly with message_type - // Copy constructor - MessageCreator(const MessageCreator &other) { - if (other.has_tagged_string_ptr_()) { - auto *str = new std::string(*other.get_string_ptr_()); - data_.tagged = reinterpret_cast(str) | 1; - } else { - data_ = other.data_; - } - } + // Delete copy operations - MessageCreator should only be moved + MessageCreator(const MessageCreator &other) = delete; + MessageCreator &operator=(const MessageCreator &other) = delete; // Move constructor - MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } - - // Assignment operators (needed for batch deduplication) - MessageCreator &operator=(const MessageCreator &other) { - if (this != &other) { - // Clean up current string data if needed - if (has_tagged_string_ptr_()) { - delete get_string_ptr_(); - } - // Copy new data - if (other.has_tagged_string_ptr_()) { - auto *str = new std::string(*other.get_string_ptr_()); - data_.tagged = reinterpret_cast(str) | 1; - } else { - data_ = other.data_; - } - } - return *this; - } + MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; } + // Move assignment MessageCreator &operator=(MessageCreator &&other) noexcept { if (this != &other) { - // Clean up current string data if needed - if (has_tagged_string_ptr_()) { - delete get_string_ptr_(); - } - // Move data + // IMPORTANT: Caller must ensure cleanup() was called if this contains a string! + // In our usage, this happens in add_item() deduplication and vector::erase() data_ = other.data_; - // Reset other to safe state - other.data_.ptr = nullptr; + other.data_.function_ptr = nullptr; } return *this; } - // Call operator - now accepts message_type as parameter + // Call operator - uses message_type to determine union type uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t message_type) const; - private: - // Check if this contains a string pointer - bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } - - // Get the actual string pointer (clears the tag bit) - std::string *get_string_ptr_() const { - // NOLINTNEXTLINE(performance-no-int-to-ptr) - return reinterpret_cast(data_.tagged & ~uintptr_t(1)); + // Manual cleanup method - must be called before destruction for string types + void cleanup(uint16_t message_type) { +#ifdef USE_EVENT + if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { + delete data_.string_ptr; + data_.string_ptr = nullptr; + } +#endif } - union { - MessageCreatorPtr ptr; - uintptr_t tagged; - } data_; // 4 bytes on 32-bit + private: + union Data { + MessageCreatorPtr function_ptr; + std::string *string_ptr; + } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before }; // Generic batching mechanism for both state updates and entity info @@ -558,20 +515,46 @@ class APIConnection : public APIServerConnection { std::vector items; uint32_t batch_start_time{0}; + private: + // Helper to cleanup items from the beginning + void cleanup_items_(size_t count) { + for (size_t i = 0; i < count; i++) { + items[i].creator.cleanup(items[i].message_type); + } + } + + public: DeferredBatch() { // Pre-allocate capacity for typical batch sizes to avoid reallocation items.reserve(8); } + ~DeferredBatch() { + // Ensure cleanup of any remaining items + clear(); + } + // Add item to the batch void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); // Add item to the front of the batch (for high priority messages like ping) void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); + + // Clear all items with proper cleanup void clear() { + cleanup_items_(items.size()); items.clear(); batch_start_time = 0; } + + // Remove processed items from the front with proper cleanup + void remove_front(size_t count) { + cleanup_items_(count); + items.erase(items.begin(), items.begin() + count); + } + bool empty() const { return items.empty(); } + size_t size() const { return items.size(); } + const BatchItem &operator[](size_t index) const { return items[index]; } }; // DeferredBatch here (16 bytes, 4-byte aligned) From 04a46de23794bffe17c8094cdc8534c01cd61906 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:40:39 -0400 Subject: [PATCH 16/35] [esp32_rmt_led_strip] Reduce memory usage by 32x with IDF 5.3 (#8388) --- .../esp32_rmt_led_strip/led_strip.cpp | 95 +++++++++++++++---- .../esp32_rmt_led_strip/led_strip.h | 13 ++- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index dfdf50aa66..389c32882b 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -21,6 +21,43 @@ static const uint32_t RMT_CLK_FREQ = 80000000; static const uint8_t RMT_CLK_DIV = 2; #endif +static const size_t RMT_SYMBOLS_PER_BYTE = 8; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t symbols_written, size_t symbols_free, + rmt_symbol_word_t *symbols, bool *done, void *arg) { + auto *params = static_cast(arg); + const auto *bytes = static_cast(data); + size_t index = symbols_written / RMT_SYMBOLS_PER_BYTE; + + // convert byte to symbols + if (index < size) { + if (symbols_free < RMT_SYMBOLS_PER_BYTE) { + return 0; + } + for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) { + if (bytes[index] & (1 << (7 - i))) { + symbols[i] = params->bit1; + } else { + symbols[i] = params->bit0; + } + } + if ((index + 1) >= size && params->reset.duration0 == 0 && params->reset.duration1 == 0) { + *done = true; + } + return RMT_SYMBOLS_PER_BYTE; + } + + // send reset + if (symbols_free < 1) { + return 0; + } + symbols[0] = params->reset; + *done = true; + return 1; +} +#endif + void ESP32RMTLEDStripLightOutput::setup() { ESP_LOGCONFIG(TAG, "Running setup"); @@ -42,10 +79,15 @@ void ESP32RMTLEDStripLightOutput::setup() { return; } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + // copy of the led buffer + this->rmt_buf_ = allocator.allocate(buffer_size); +#else RAMAllocator rmt_allocator(this->use_psram_ ? 0 : RAMAllocator::ALLOC_INTERNAL); // 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1); +#endif rmt_tx_channel_config_t channel; memset(&channel, 0, sizeof(channel)); @@ -65,6 +107,18 @@ void ESP32RMTLEDStripLightOutput::setup() { return; } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + rmt_simple_encoder_config_t encoder; + memset(&encoder, 0, sizeof(encoder)); + encoder.callback = encoder_callback; + encoder.arg = &this->params_; + encoder.min_chunk_size = 8; + if (rmt_new_simple_encoder(&encoder, &this->encoder_) != ESP_OK) { + ESP_LOGE(TAG, "Encoder creation failed"); + this->mark_failed(); + return; + } +#else rmt_copy_encoder_config_t encoder; memset(&encoder, 0, sizeof(encoder)); if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) { @@ -72,6 +126,7 @@ void ESP32RMTLEDStripLightOutput::setup() { this->mark_failed(); return; } +#endif if (rmt_enable(this->channel_) != ESP_OK) { ESP_LOGE(TAG, "Enabling channel failed"); @@ -85,20 +140,20 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; // 0-bit - this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); - this->bit0_.level0 = 1; - this->bit0_.duration1 = (uint32_t) (ratio * bit0_low); - this->bit0_.level1 = 0; + this->params_.bit0.duration0 = (uint32_t) (ratio * bit0_high); + this->params_.bit0.level0 = 1; + this->params_.bit0.duration1 = (uint32_t) (ratio * bit0_low); + this->params_.bit0.level1 = 0; // 1-bit - this->bit1_.duration0 = (uint32_t) (ratio * bit1_high); - this->bit1_.level0 = 1; - this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); - this->bit1_.level1 = 0; + this->params_.bit1.duration0 = (uint32_t) (ratio * bit1_high); + this->params_.bit1.level0 = 1; + this->params_.bit1.duration1 = (uint32_t) (ratio * bit1_low); + this->params_.bit1.level1 = 0; // reset - this->reset_.duration0 = (uint32_t) (ratio * reset_time_high); - this->reset_.level0 = 1; - this->reset_.duration1 = (uint32_t) (ratio * reset_time_low); - this->reset_.level1 = 0; + this->params_.reset.duration0 = (uint32_t) (ratio * reset_time_high); + this->params_.reset.level0 = 1; + this->params_.reset.duration1 = (uint32_t) (ratio * reset_time_low); + this->params_.reset.level1 = 0; } void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { @@ -122,6 +177,9 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { } delayMicroseconds(50); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + memcpy(this->rmt_buf_, this->buf_, this->get_buffer_size_()); +#else size_t buffer_size = this->get_buffer_size_(); size_t size = 0; @@ -131,7 +189,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { while (size < buffer_size) { uint8_t b = *psrc; for (int i = 0; i < 8; i++) { - pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val; + pdest->val = b & (1 << (7 - i)) ? this->params_.bit1.val : this->params_.bit0.val; pdest++; len++; } @@ -139,17 +197,20 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { psrc++; } - if (this->reset_.duration0 > 0 || this->reset_.duration1 > 0) { - pdest->val = this->reset_.val; + if (this->params_.reset.duration0 > 0 || this->params_.reset.duration1 > 0) { + pdest->val = this->params_.reset.val; pdest++; len++; } +#endif rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); - config.loop_count = 0; - config.flags.eot_level = 0; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, this->get_buffer_size_(), &config); +#else error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config); +#endif if (error != ESP_OK) { ESP_LOGE(TAG, "RMT TX error"); this->status_set_warning(); diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index c6a2b4bc9f..72ce659b4f 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -25,6 +25,12 @@ enum RGBOrder : uint8_t { ORDER_BRG, }; +struct LedParams { + rmt_symbol_word_t bit0; + rmt_symbol_word_t bit1; + rmt_symbol_word_t reset; +}; + class ESP32RMTLEDStripLightOutput : public light::AddressableLight { public: void setup() override; @@ -72,12 +78,15 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { uint8_t *buf_{nullptr}; uint8_t *effect_data_{nullptr}; + LedParams params_; rmt_channel_handle_t channel_{nullptr}; rmt_encoder_handle_t encoder_{nullptr}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + uint8_t *rmt_buf_{nullptr}; +#else rmt_symbol_word_t *rmt_buf_{nullptr}; - rmt_symbol_word_t bit0_, bit1_, reset_; +#endif uint32_t rmt_symbols_{48}; - uint8_t pin_; uint16_t num_leds_; bool is_rgbw_{false}; From 6a096c1d5a734ed3f9db5f70c3fecdaec55dc777 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:36:15 +1200 Subject: [PATCH 17/35] [api] Dump bytes fields as hex instead of unreadable string (#9288) --- esphome/components/api/api_pb2.cpp | 23 ++++++++++++----------- esphome/core/helpers.cpp | 21 ++++++++++++++++++--- esphome/core/helpers.h | 2 ++ script/api_protobuf/api_protobuf.py | 3 ++- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 9793565ee5..8bce14c9cc 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3,6 +3,7 @@ #include "api_pb2.h" #include "api_pb2_size.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include @@ -3510,7 +3511,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" message: "); - out.append("'").append(this->message).append("'"); + out.append(format_hex_pretty(this->message)); out.append("\n"); out.append(" send_failed: "); @@ -3538,7 +3539,7 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NoiseEncryptionSetKeyRequest {\n"); out.append(" key: "); - out.append("'").append(this->key).append("'"); + out.append(format_hex_pretty(this->key)); out.append("\n"); out.append("}"); } @@ -4284,7 +4285,7 @@ void CameraImageResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append(" done: "); @@ -6811,7 +6812,7 @@ void BluetoothServiceData::dump_to(std::string &out) const { } out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -6894,7 +6895,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" name: "); - out.append("'").append(this->name).append("'"); + out.append(format_hex_pretty(this->name)); out.append("\n"); out.append(" rssi: "); @@ -6987,7 +6988,7 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -7514,7 +7515,7 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -7578,7 +7579,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -7670,7 +7671,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -7772,7 +7773,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append("}"); } @@ -8492,7 +8493,7 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantAudio {\n"); out.append(" data: "); - out.append("'").append(this->data).append("'"); + out.append(format_hex_pretty(this->data)); out.append("\n"); out.append(" end: "); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index fc91d83972..b4923c7af0 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -4,13 +4,13 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include #include #include #include #include #include #include -#include #ifdef USE_HOST #ifndef _WIN32 @@ -43,10 +43,10 @@ #include #endif #ifdef USE_ESP32 -#include "rom/crc.h" -#include "esp_mac.h" #include "esp_efuse.h" #include "esp_efuse_table.h" +#include "esp_mac.h" +#include "rom/crc.h" #endif #ifdef USE_LIBRETINY @@ -393,6 +393,21 @@ std::string format_hex_pretty(const uint16_t *data, size_t length) { return ret; } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const std::string &data) { + if (data.empty()) + return ""; + std::string ret; + ret.resize(3 * data.length() - 1); + for (size_t i = 0; i < data.length(); i++) { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != data.length() - 1) + ret[3 * i + 2] = '.'; + } + if (data.length() > 4) + return ret + " (" + std::to_string(data.length()) + ")"; + return ret; +} std::string format_bin(const uint8_t *data, size_t length) { std::string result; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 7d5366f323..362f3d1fa4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -348,6 +348,8 @@ std::string format_hex_pretty(const uint16_t *data, size_t length); std::string format_hex_pretty(const std::vector &data); /// Format the vector \p data in pretty-printed, human-readable hex. std::string format_hex_pretty(const std::vector &data); +/// Format the string \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(const std::string &data); /// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. template::value, int> = 0> std::string format_hex_pretty(T val) { val = convert_big_endian(val); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index ad8e41ba5e..615f5bbfda 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -530,7 +530,7 @@ class BytesType(TypeInfo): wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 def dump(self, name: str) -> str: - o = f'out.append("\'").append({name}).append("\'");' + o = f"out.append(format_hex_pretty({name}));" return o def get_size_calculation(self, name: str, force: bool = False) -> str: @@ -1255,6 +1255,7 @@ def main() -> None: #include "api_pb2.h" #include "api_pb2_size.h" #include "esphome/core/log.h" + #include "esphome/core/helpers.h" #include From 03566c34ed014d6babad37dfc3cbe6fb4772e214 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Jul 2025 20:43:40 -0500 Subject: [PATCH 18/35] Reduce Component memory usage by 40% (8 bytes per component) (#9278) --- esphome/core/application.cpp | 4 ++ esphome/core/component.cpp | 94 +++++++++++++++++++++++++++++++++--- esphome/core/component.h | 5 +- 3 files changed, 93 insertions(+), 10 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1599c648e7..d6fab018cc 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -84,6 +84,10 @@ void Application::setup() { } ESP_LOGI(TAG, "setup() finished successfully!"); + + // Clear setup priority overrides to free memory + clear_setup_priority_overrides(); + this->schedule_dump_config(); this->calculate_looping_components_(); } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 6661223e35..aba5dc729c 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -12,6 +14,30 @@ namespace esphome { static const char *const TAG = "component"; +// Global vectors for component data that doesn't belong in every instance. +// Using vector instead of unordered_map for both because: +// - Much lower memory overhead (8 bytes per entry vs 20+ for unordered_map) +// - Linear search is fine for small n (typically < 5 entries) +// - These are rarely accessed (setup only or error cases only) + +// Component error messages - only stores messages for failed components +// Lazy allocated since most configs have zero failures +// Note: We don't clear this vector because: +// 1. Components are never destroyed in ESPHome +// 2. Failed components remain failed (no recovery mechanism) +// 3. Memory usage is minimal (only failures with custom messages are stored) +static std::unique_ptr>> &get_component_error_messages() { + static std::unique_ptr>> instance; + return instance; +} + +// Setup priority overrides - freed after setup completes +// Typically < 5 entries, lazy allocated +static std::unique_ptr>> &get_setup_priority_overrides() { + static std::unique_ptr>> instance; + return instance; +} + namespace setup_priority { const float BUS = 1000.0f; @@ -102,8 +128,17 @@ void Component::call_setup() { this->setup(); } void Component::call_dump_config() { this->dump_config(); if (this->is_failed()) { - ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), - this->error_message_ ? this->error_message_ : "unspecified"); + // Look up error message from global vector + const char *error_msg = "unspecified"; + if (get_component_error_messages()) { + for (const auto &pair : *get_component_error_messages()) { + if (pair.first == this) { + error_msg = pair.second; + break; + } + } + } + ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg); } } @@ -245,8 +280,21 @@ void Component::status_set_error(const char *message) { 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); - if (strcmp(message, "unspecified") != 0) - this->error_message_ = message; + if (strcmp(message, "unspecified") != 0) { + // Lazy allocate the error messages vector if needed + if (!get_component_error_messages()) { + get_component_error_messages() = std::make_unique>>(); + } + // Check if this component already has an error message + for (auto &pair : *get_component_error_messages()) { + if (pair.first == this) { + pair.second = message; + return; + } + } + // Add new error message + get_component_error_messages()->emplace_back(this, message); + } } void Component::status_clear_warning() { if ((this->component_state_ & STATUS_LED_WARNING) == 0) @@ -270,11 +318,36 @@ void Component::status_momentary_error(const std::string &name, uint32_t length) } void Component::dump_config() {} float Component::get_actual_setup_priority() const { - if (std::isnan(this->setup_priority_override_)) - return this->get_setup_priority(); - return this->setup_priority_override_; + // Check if there's an override in the global vector + if (get_setup_priority_overrides()) { + // Linear search is fine for small n (typically < 5 overrides) + for (const auto &pair : *get_setup_priority_overrides()) { + if (pair.first == this) { + return pair.second; + } + } + } + return this->get_setup_priority(); +} +void Component::set_setup_priority(float priority) { + // Lazy allocate the vector if needed + if (!get_setup_priority_overrides()) { + get_setup_priority_overrides() = std::make_unique>>(); + // Reserve some space to avoid reallocations (most configs have < 10 overrides) + get_setup_priority_overrides()->reserve(10); + } + + // Check if this component already has an override + for (auto &pair : *get_setup_priority_overrides()) { + if (pair.first == this) { + pair.second = priority; + return; + } + } + + // Add new override + get_setup_priority_overrides()->emplace_back(this, priority); } -void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; } bool Component::has_overridden_loop() const { #if defined(USE_HOST) || defined(CLANG_TIDY) @@ -336,4 +409,9 @@ uint32_t WarnIfComponentBlockingGuard::finish() { WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {} +void clear_setup_priority_overrides() { + // Free the setup priority map completely + get_setup_priority_overrides().reset(); +} + } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index 5b37deeb68..ab30466e2d 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -387,9 +387,7 @@ class Component { bool cancel_defer(const std::string &name); // NOLINT // Ordered for optimal packing on 32-bit systems - float setup_priority_override_{NAN}; const char *component_source_{nullptr}; - const char *error_message_{nullptr}; uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s) /// State of this component - each bit has a purpose: /// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED) @@ -459,4 +457,7 @@ class WarnIfComponentBlockingGuard { Component *component_; }; +// Function to clear setup priority overrides after all components are set up +void clear_setup_priority_overrides(); + } // namespace esphome From 84ab758b227c014d001a1736b7ed7745f892fc96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Jul 2025 20:50:45 -0500 Subject: [PATCH 19/35] Replace custom OTA implementation in web_server_base (#9274) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/captive_portal/__init__.py | 2 +- .../captive_portal/captive_portal.cpp | 3 - esphome/components/ota/ota_backend.h | 28 +- .../ota/ota_backend_arduino_esp32.cpp | 14 +- .../ota/ota_backend_arduino_esp32.h | 3 + .../ota/ota_backend_arduino_esp8266.cpp | 22 +- .../ota/ota_backend_arduino_esp8266.h | 3 + .../ota/ota_backend_arduino_libretiny.cpp | 14 +- .../ota/ota_backend_arduino_libretiny.h | 3 + .../ota/ota_backend_arduino_rp2040.cpp | 11 +- .../ota/ota_backend_arduino_rp2040.h | 3 + .../components/ota/ota_backend_esp_idf.cpp | 15 +- esphome/components/ota/ota_backend_esp_idf.h | 1 + esphome/components/web_server/__init__.py | 35 ++- esphome/components/web_server/ota/__init__.py | 32 +++ .../web_server/ota/ota_web_server.cpp | 210 ++++++++++++++ .../web_server/ota/ota_web_server.h | 26 ++ esphome/components/web_server/web_server.cpp | 11 +- esphome/components/web_server/web_server.h | 6 - .../components/web_server/web_server_v1.cpp | 9 +- .../components/web_server_base/__init__.py | 1 + .../web_server_base/web_server_base.cpp | 264 +----------------- .../web_server_base/web_server_base.h | 42 +-- esphome/components/web_server_idf/__init__.py | 8 +- esphome/config.py | 109 ++++++-- .../ota/test_web_server_ota.py | 102 +++++++ .../ota/test_web_server_ota.yaml | 15 + .../ota/test_web_server_ota_arduino.yaml | 18 ++ .../ota/test_web_server_ota_callbacks.yaml | 31 ++ .../ota/test_web_server_ota_esp8266.yaml | 15 + .../ota/test_web_server_ota_idf.yaml | 17 ++ .../ota/test_web_server_ota_multi.yaml | 21 ++ .../web_server/test_ota_migration.py | 38 +++ .../web_server/test_no_ota.esp32-idf.yaml | 9 +- .../web_server/test_ota.esp32-idf.yaml | 8 +- .../test_ota_disabled.esp32-idf.yaml | 11 +- 37 files changed, 776 insertions(+), 385 deletions(-) create mode 100644 esphome/components/web_server/ota/__init__.py create mode 100644 esphome/components/web_server/ota/ota_web_server.cpp create mode 100644 esphome/components/web_server/ota/ota_web_server.h create mode 100644 tests/component_tests/ota/test_web_server_ota.py create mode 100644 tests/component_tests/ota/test_web_server_ota.yaml create mode 100644 tests/component_tests/ota/test_web_server_ota_arduino.yaml create mode 100644 tests/component_tests/ota/test_web_server_ota_callbacks.yaml create mode 100644 tests/component_tests/ota/test_web_server_ota_esp8266.yaml create mode 100644 tests/component_tests/ota/test_web_server_ota_idf.yaml create mode 100644 tests/component_tests/ota/test_web_server_ota_multi.yaml create mode 100644 tests/component_tests/web_server/test_ota_migration.py diff --git a/CODEOWNERS b/CODEOWNERS index 68c8684024..16f38da725 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -498,6 +498,7 @@ esphome/components/voice_assistant/* @jesserockz @kahrendt esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/watchdog/* @oarcher esphome/components/waveshare_epaper/* @clydebarrow +esphome/components/web_server/ota/* @esphome/core esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_idf/* @dentra esphome/components/weikai/* @DrCoolZic diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index cba3b4921a..7e8afd8fab 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -12,7 +12,7 @@ from esphome.const import ( ) from esphome.core import CORE, coroutine_with_priority -AUTO_LOAD = ["web_server_base"] +AUTO_LOAD = ["web_server_base", "ota.web_server"] DEPENDENCIES = ["wifi"] CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ba392bb0f2..25179fdacc 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -47,9 +47,6 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); -#ifdef USE_WEBSERVER_OTA - this->base_->add_ota_handler(); -#endif } #ifdef USE_ARDUINO diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index bc8ab46643..372f24df5e 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -67,7 +67,28 @@ class OTAComponent : public Component { } protected: - CallbackManager state_callback_{}; + /** Extended callback manager with deferred call support. + * + * This adds a call_deferred() method for thread-safe execution from other tasks. + */ + class StateCallbackManager : public CallbackManager { + public: + StateCallbackManager(OTAComponent *component) : component_(component) {} + + /** Call callbacks with deferral to main loop (for thread safety). + * + * This should be used by OTA implementations that run in separate tasks + * (like web_server OTA) to ensure callbacks execute in the main loop. + */ + void call_deferred(ota::OTAState state, float progress, uint8_t error) { + component_->defer([this, state, progress, error]() { this->call(state, progress, error); }); + } + + private: + OTAComponent *component_; + }; + + StateCallbackManager state_callback_{this}; #endif }; @@ -89,6 +110,11 @@ class OTAGlobalCallback { OTAGlobalCallback *get_global_ota_callback(); void register_ota_platform(OTAComponent *ota_caller); + +// OTA implementations should use: +// - state_callback_.call() when already in main loop (e.g., esphome OTA) +// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA) +// This ensures proper callback execution in all contexts. #endif std::unique_ptr make_ota_backend(); diff --git a/esphome/components/ota/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp index 15dfc98a6c..5c6230f2ce 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp32.cpp @@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_esp32"; std::unique_ptr make_ota_backend() { return make_unique(); } OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA + // where the exact firmware size is unknown due to multipart encoding + if (image_size == 0) { + image_size = UPDATE_SIZE_UNKNOWN; + } bool ret = Update.begin(image_size, U_FLASH); if (ret) { return OTA_RESPONSE_OK; @@ -29,7 +34,10 @@ OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoESP32OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -44,7 +52,9 @@ OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoESP32OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index ac7fe9f14f..6615cf3dc0 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -16,6 +16,9 @@ class ArduinoESP32OTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp index 42edbf5d2b..375c4e7200 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -17,6 +17,11 @@ static const char *const TAG = "ota.arduino_esp8266"; std::unique_ptr make_ota_backend() { return make_unique(); } OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space + if (image_size == 0) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + } bool ret = Update.begin(image_size, U_FLASH); if (ret) { esp8266::preferences_prevent_write(true); @@ -38,7 +43,10 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -53,13 +61,19 @@ OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoESP8266OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + bool success = Update.end(!this->md5_set_); + + // On ESP8266, Update.end() might return false even with error code 0 + // Check the actual error code to determine success + uint8_t error = Update.getError(); + + if (success || error == UPDATE_ERROR_OK) { return OTA_RESPONSE_OK; } - uint8_t error = Update.getError(); ESP_LOGE(TAG, "End error: %d", error); - return OTA_RESPONSE_ERROR_UPDATE_END; } diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index 7f44d7c965..e1b9015cc7 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -21,6 +21,9 @@ class ArduinoESP8266OTABackend : public OTABackend { #else bool supports_compression() override { return false; } #endif + + private: + bool md5_set_{false}; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp index 6b2cf80684..b4ecad1227 100644 --- a/esphome/components/ota/ota_backend_arduino_libretiny.cpp +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_libretiny"; std::unique_ptr make_ota_backend() { return make_unique(); } OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA + // where the exact firmware size is unknown due to multipart encoding + if (image_size == 0) { + image_size = UPDATE_SIZE_UNKNOWN; + } bool ret = Update.begin(image_size, U_FLASH); if (ret) { return OTA_RESPONSE_OK; @@ -29,7 +34,10 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -44,7 +52,9 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoLibreTinyOTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.h b/esphome/components/ota/ota_backend_arduino_libretiny.h index 11deb6e2f2..6d9b7a96d5 100644 --- a/esphome/components/ota/ota_backend_arduino_libretiny.h +++ b/esphome/components/ota/ota_backend_arduino_libretiny.h @@ -15,6 +15,9 @@ class ArduinoLibreTinyOTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp index ffeab2e93f..ee1ba48d50 100644 --- a/esphome/components/ota/ota_backend_arduino_rp2040.cpp +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -17,6 +17,8 @@ static const char *const TAG = "ota.arduino_rp2040"; std::unique_ptr make_ota_backend() { return make_unique(); } OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { + // OTA size of 0 is not currently handled, but + // web_server is not supported for RP2040, so this is not an issue. bool ret = Update.begin(image_size, U_FLASH); if (ret) { rp2040::preferences_prevent_write(true); @@ -38,7 +40,10 @@ OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -53,7 +58,9 @@ OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoRP2040OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.h b/esphome/components/ota/ota_backend_arduino_rp2040.h index b189964ab3..b9e10d506c 100644 --- a/esphome/components/ota/ota_backend_arduino_rp2040.h +++ b/esphome/components/ota/ota_backend_arduino_rp2040.h @@ -17,6 +17,9 @@ class ArduinoRP2040OTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index cad44a5795..97aae09bd9 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -56,7 +56,10 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { return OTA_RESPONSE_OK; } -void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); } +void IDFOTABackend::set_update_md5(const char *expected_md5) { + memcpy(this->expected_bin_md5_, expected_md5, 32); + this->md5_set_ = true; +} OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { esp_err_t err = esp_ota_write(this->update_handle_, data, len); @@ -73,10 +76,12 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes IDFOTABackend::end() { - this->md5_.calculate(); - if (!this->md5_.equals_hex(this->expected_bin_md5_)) { - this->abort(); - return OTA_RESPONSE_ERROR_MD5_MISMATCH; + if (this->md5_set_) { + this->md5_.calculate(); + if (!this->md5_.equals_hex(this->expected_bin_md5_)) { + this->abort(); + return OTA_RESPONSE_ERROR_MD5_MISMATCH; + } } esp_err_t err = esp_ota_end(this->update_handle_); this->update_handle_ = 0; diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index ed66d9b970..6e93982131 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -24,6 +24,7 @@ class IDFOTABackend : public OTABackend { const esp_partition_t *partition_; md5::MD5Digest md5_{}; char expected_bin_md5_[32]; + bool md5_set_{false}; }; } // namespace ota diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ca145c732b..6890f60014 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -33,6 +33,7 @@ from esphome.const import ( ) from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv +from esphome.types import ConfigType AUTO_LOAD = ["json", "web_server_base"] @@ -47,7 +48,7 @@ WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) sorting_groups = {} -def default_url(config): +def default_url(config: ConfigType) -> ConfigType: config = config.copy() if config[CONF_VERSION] == 1: if CONF_CSS_URL not in config: @@ -67,13 +68,27 @@ def default_url(config): return config -def validate_local(config): +def validate_local(config: ConfigType) -> ConfigType: if CONF_LOCAL in config and config[CONF_VERSION] == 1: raise cv.Invalid("'local' is not supported in version 1") return config -def validate_sorting_groups(config): +def validate_ota_removed(config: ConfigType) -> ConfigType: + # Only raise error if OTA is explicitly enabled (True) + # If it's False or not specified, we can safely ignore it + if config.get(CONF_OTA): + raise cv.Invalid( + f"The '{CONF_OTA}' option has been removed from 'web_server'. " + f"Please use the new OTA platform structure instead:\n\n" + f"ota:\n" + f" - platform: web_server\n\n" + f"See https://esphome.io/components/ota for more information." + ) + return config + + +def validate_sorting_groups(config: ConfigType) -> ConfigType: if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3: raise cv.Invalid( f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3" @@ -84,7 +99,7 @@ def validate_sorting_groups(config): def _validate_no_sorting_component( sorting_component: str, webserver_version: int, - config: dict, + config: ConfigType, path: list[str] | None = None, ) -> None: if path is None: @@ -107,7 +122,7 @@ def _validate_no_sorting_component( ) -def _final_validate_sorting(config): +def _final_validate_sorting(config: ConfigType) -> ConfigType: if (webserver_version := config.get(CONF_VERSION)) != 3: _validate_no_sorting_component( CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get() @@ -170,7 +185,7 @@ CONFIG_SCHEMA = cv.All( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.Optional(CONF_OTA, default=True): cv.boolean, + cv.Optional(CONF_OTA, default=False): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), @@ -188,6 +203,7 @@ CONFIG_SCHEMA = cv.All( default_url, validate_local, validate_sorting_groups, + validate_ota_removed, ) @@ -271,11 +287,8 @@ async def to_code(config): else: cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) - cg.add(var.set_allow_ota(config[CONF_OTA])) - if config[CONF_OTA]: - # Define USE_WEBSERVER_OTA based only on web_server OTA config - # This allows web server OTA to work without loading the OTA component - cg.add_define("USE_WEBSERVER_OTA") + # OTA is now handled by the web_server OTA platform + # The CONF_OTA option is kept only for backwards compatibility validation cg.add(var.set_expose_log(config[CONF_LOG])) if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py new file mode 100644 index 0000000000..3af14fd453 --- /dev/null +++ b/esphome/components/web_server/ota/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components.esp32 import add_idf_component +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.core import CORE, coroutine_with_priority + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network", "web_server_base"] + +web_server_ns = cg.esphome_ns.namespace("web_server") +WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServerOTAComponent), + } + ) + .extend(BASE_OTA_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +@coroutine_with_priority(52.0) +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await ota_to_code(var, config) + await cg.register_component(var, config) + cg.add_define("USE_WEBSERVER_OTA") + if CORE.using_esp_idf: + add_idf_component(name="zorxx/multipart-parser", ref="1.0.1") diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp new file mode 100644 index 0000000000..4f8f6fda17 --- /dev/null +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -0,0 +1,210 @@ +#include "ota_web_server.h" +#ifdef USE_WEBSERVER_OTA + +#include "esphome/components/ota/ota_backend.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 +#include +#elif defined(USE_ESP32) || defined(USE_LIBRETINY) +#include +#endif +#endif // USE_ARDUINO + +namespace esphome { +namespace web_server { + +static const char *const TAG = "web_server.ota"; + +class OTARequestHandler : public AsyncWebHandler { + public: + OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {} + void handleRequest(AsyncWebServerRequest *request) override; + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override; + bool canHandle(AsyncWebServerRequest *request) const override { + return request->url() == "/update" && request->method() == HTTP_POST; + } + + // NOLINTNEXTLINE(readability-identifier-naming) + bool isRequestHandlerTrivial() const override { return false; } + + protected: + void report_ota_progress_(AsyncWebServerRequest *request); + void schedule_ota_reboot_(); + void ota_init_(const char *filename); + + uint32_t last_ota_progress_{0}; + uint32_t ota_read_length_{0}; + WebServerOTAComponent *parent_; + bool ota_success_{false}; + + private: + std::unique_ptr ota_backend_{nullptr}; +}; + +void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { + const uint32_t now = millis(); + if (now - this->last_ota_progress_ > 1000) { + float percentage = 0.0f; + if (request->contentLength() != 0) { + // Note: Using contentLength() for progress calculation is technically wrong as it includes + // multipart headers/boundaries, but it's only off by a small amount and we don't have + // access to the actual firmware size until the upload is complete. This is intentional + // as it still gives the user a reasonable progress indication. + percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); + ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); + } else { + ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); + } +#ifdef USE_OTA_STATE_CALLBACK + // Report progress - use call_deferred since we're in web server task + this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0); +#endif + this->last_ota_progress_ = now; + } +} + +void OTARequestHandler::schedule_ota_reboot_() { + ESP_LOGI(TAG, "OTA update successful!"); + this->parent_->set_timeout(100, []() { + ESP_LOGI(TAG, "Performing OTA reboot now"); + App.safe_reboot(); + }); +} + +void OTARequestHandler::ota_init_(const char *filename) { + ESP_LOGI(TAG, "OTA Update Start: %s", filename); + this->ota_read_length_ = 0; + this->ota_success_ = false; +} + +void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, + uint8_t *data, size_t len, bool final) { + ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK; + + if (index == 0 && !this->ota_backend_) { + // Initialize OTA on first call + this->ota_init_(filename.c_str()); + +#ifdef USE_OTA_STATE_CALLBACK + // Notify OTA started - use call_deferred since we're in web server task + this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0); +#endif + + // Platform-specific pre-initialization +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + Update.runAsync(true); +#endif +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + if (Update.isRunning()) { + Update.abort(); + } +#endif +#endif // USE_ARDUINO + + this->ota_backend_ = ota::make_ota_backend(); + if (!this->ota_backend_) { + ESP_LOGE(TAG, "Failed to create OTA backend"); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, + static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); +#endif + return; + } + + // Web server OTA uses multipart uploads where the actual firmware size + // is unknown (contentLength includes multipart overhead) + // Pass 0 to indicate unknown size + error_code = this->ota_backend_->begin(0); + if (error_code != ota::OTA_RESPONSE_OK) { + ESP_LOGE(TAG, "OTA begin failed: %d", error_code); + this->ota_backend_.reset(); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + return; + } + } + + if (!this->ota_backend_) { + return; + } + + // Process data + if (len > 0) { + error_code = this->ota_backend_->write(data, len); + if (error_code != ota::OTA_RESPONSE_OK) { + ESP_LOGE(TAG, "OTA write failed: %d", error_code); + this->ota_backend_->abort(); + this->ota_backend_.reset(); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + return; + } + this->ota_read_length_ += len; + this->report_ota_progress_(request); + } + + // Finalize + if (final) { + ESP_LOGD(TAG, "OTA final chunk: index=%u, len=%u, total_read=%u, contentLength=%u", index, len, + this->ota_read_length_, request->contentLength()); + + // For Arduino framework, the Update library tracks expected size from firmware header + // If we haven't received enough data, calling end() will fail + // This can happen if the upload is interrupted or the client disconnects + error_code = this->ota_backend_->end(); + if (error_code == ota::OTA_RESPONSE_OK) { + this->ota_success_ = true; +#ifdef USE_OTA_STATE_CALLBACK + // Report completion before reboot - use call_deferred since we're in web server task + this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0); +#endif + this->schedule_ota_reboot_(); + } else { + ESP_LOGE(TAG, "OTA end failed: %d", error_code); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + } + this->ota_backend_.reset(); + } +} + +void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response; + // Use the ota_success_ flag to determine the actual result + const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!"; + response = request->beginResponse(200, "text/plain", msg); + response->addHeader("Connection", "close"); + request->send(response); +} + +void WebServerOTAComponent::setup() { + // Get the global web server base instance and register our handler + auto *base = web_server_base::global_web_server_base; + if (base == nullptr) { + ESP_LOGE(TAG, "WebServerBase not found"); + this->mark_failed(); + return; + } + + // AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed + base->add_handler(new OTARequestHandler(this)); // NOLINT +#ifdef USE_OTA_STATE_CALLBACK + // Register with global OTA callback system + ota::register_ota_platform(this); +#endif +} + +void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); } + +} // namespace web_server +} // namespace esphome + +#endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/ota/ota_web_server.h b/esphome/components/web_server/ota/ota_web_server.h new file mode 100644 index 0000000000..a7170c0e34 --- /dev/null +++ b/esphome/components/web_server/ota/ota_web_server.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_WEBSERVER_OTA + +#include "esphome/components/ota/ota_backend.h" +#include "esphome/components/web_server_base/web_server_base.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace web_server { + +class WebServerOTAComponent : public ota::OTAComponent { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + protected: + friend class OTARequestHandler; +}; + +} // namespace web_server +} // namespace esphome + +#endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e0027d0b27..d5ded2a02c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -273,7 +273,11 @@ std::string WebServer::get_config_json() { return json::build_json([this](JsonObject root) { root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); root["comment"] = App.get_comment(); - root["ota"] = this->allow_ota_; +#ifdef USE_WEBSERVER_OTA + root["ota"] = true; // web_server OTA platform is configured +#else + root["ota"] = false; +#endif root["log"] = this->expose_log_; root["lang"] = "en"; }); @@ -299,10 +303,7 @@ void WebServer::setup() { #endif this->base_->add_handler(this); -#ifdef USE_WEBSERVER_OTA - if (this->allow_ota_) - this->base_->add_ota_handler(); -#endif + // OTA is now handled by the web_server OTA platform // doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly // getting a lot of events diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 991bca6fa7..5f175b6bdd 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -212,11 +212,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { * @param include_internal Whether internal components should be displayed. */ void set_include_internal(bool include_internal) { include_internal_ = include_internal; } - /** Set whether or not the webserver should expose the OTA form and handler. - * - * @param allow_ota. - */ - void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; } /** Set whether or not the webserver should expose the Log. * * @param expose_log. @@ -525,7 +520,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #ifdef USE_WEBSERVER_JS_INCLUDE const char *js_include_{nullptr}; #endif - bool allow_ota_{true}; bool expose_log_{true}; #ifdef USE_ESP32 std::deque> to_schedule_; diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index c9b38a2dc4..5db0f1cae9 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -192,11 +192,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("

See ESPHome Web API for " "REST API documentation.

")); - if (this->allow_ota_) { - stream->print( - F("

OTA Update

")); - } +#ifdef USE_WEBSERVER_OTA + stream->print(F("

OTA Update

")); +#endif stream->print(F("

Debug Log

"));
 #ifdef USE_WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py
index c17bab2128..754bf7d433 100644
--- a/esphome/components/web_server_base/__init__.py
+++ b/esphome/components/web_server_base/__init__.py
@@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID])
     await cg.register_component(var, config)
+    cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}"))
 
     if CORE.using_arduino:
         if CORE.is_esp32:
diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp
index 9ad88e09f4..e1c2bc0b25 100644
--- a/esphome/components/web_server_base/web_server_base.cpp
+++ b/esphome/components/web_server_base/web_server_base.cpp
@@ -4,123 +4,12 @@
 #include "esphome/core/helpers.h"
 #include "esphome/core/log.h"
 
-#ifdef USE_ARDUINO
-#include 
-#if defined(USE_ESP32) || defined(USE_LIBRETINY)
-#include 
-#endif
-#ifdef USE_ESP8266
-#include 
-#endif
-#endif
-
-#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA)
-#include 
-#include 
-#endif
-
 namespace esphome {
 namespace web_server_base {
 
 static const char *const TAG = "web_server_base";
 
-#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA)
-// Minimal OTA backend implementation for web server
-// This allows OTA updates via web server without requiring the OTA component
-// TODO: In the future, this should be refactored into a common ota_base component
-// that both web_server and ota components can depend on, avoiding code duplication
-// while keeping the components independent. This would allow both ESP-IDF and Arduino
-// implementations to share the base OTA functionality without requiring the full OTA component.
-// The IDFWebServerOTABackend class is intentionally designed with the same interface
-// as OTABackend to make it easy to swap to using OTABackend when the ota component
-// is split into ota and ota_base in the future.
-class IDFWebServerOTABackend {
- public:
-  bool begin() {
-    this->partition_ = esp_ota_get_next_update_partition(nullptr);
-    if (this->partition_ == nullptr) {
-      ESP_LOGE(TAG, "No OTA partition available");
-      return false;
-    }
-
-#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
-    // The following function takes longer than the default timeout of WDT due to flash erase
-#if ESP_IDF_VERSION_MAJOR >= 5
-    esp_task_wdt_config_t wdtc;
-    wdtc.idle_core_mask = 0;
-#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
-    wdtc.idle_core_mask |= (1 << 0);
-#endif
-#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
-    wdtc.idle_core_mask |= (1 << 1);
-#endif
-    wdtc.timeout_ms = 15000;
-    wdtc.trigger_panic = false;
-    esp_task_wdt_reconfigure(&wdtc);
-#else
-    esp_task_wdt_init(15, false);
-#endif
-#endif
-
-    esp_err_t err = esp_ota_begin(this->partition_, 0, &this->update_handle_);
-
-#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
-    // Set the WDT back to the configured timeout
-#if ESP_IDF_VERSION_MAJOR >= 5
-    wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
-    esp_task_wdt_reconfigure(&wdtc);
-#else
-    esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
-#endif
-#endif
-
-    if (err != ESP_OK) {
-      esp_ota_abort(this->update_handle_);
-      this->update_handle_ = 0;
-      ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
-      return false;
-    }
-    return true;
-  }
-
-  bool write(uint8_t *data, size_t len) {
-    esp_err_t err = esp_ota_write(this->update_handle_, data, len);
-    if (err != ESP_OK) {
-      ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(err));
-      return false;
-    }
-    return true;
-  }
-
-  bool end() {
-    esp_err_t err = esp_ota_end(this->update_handle_);
-    this->update_handle_ = 0;
-    if (err != ESP_OK) {
-      ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
-      return false;
-    }
-
-    err = esp_ota_set_boot_partition(this->partition_);
-    if (err != ESP_OK) {
-      ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
-      return false;
-    }
-
-    return true;
-  }
-
-  void abort() {
-    if (this->update_handle_ != 0) {
-      esp_ota_abort(this->update_handle_);
-      this->update_handle_ = 0;
-    }
-  }
-
- private:
-  esp_ota_handle_t update_handle_{0};
-  const esp_partition_t *partition_{nullptr};
-};
-#endif
+WebServerBase *global_web_server_base = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 
 void WebServerBase::add_handler(AsyncWebHandler *handler) {
   // remove all handlers
@@ -134,157 +23,6 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
   }
 }
 
-#ifdef USE_WEBSERVER_OTA
-void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
-  const uint32_t now = millis();
-  if (now - this->last_ota_progress_ > 1000) {
-    if (request->contentLength() != 0) {
-      float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
-      ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
-    } else {
-      ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
-    }
-    this->last_ota_progress_ = now;
-  }
-}
-
-void OTARequestHandler::schedule_ota_reboot_() {
-  ESP_LOGI(TAG, "OTA update successful!");
-  this->parent_->set_timeout(100, []() {
-    ESP_LOGI(TAG, "Performing OTA reboot now");
-    App.safe_reboot();
-  });
-}
-
-void OTARequestHandler::ota_init_(const char *filename) {
-  ESP_LOGI(TAG, "OTA Update Start: %s", filename);
-  this->ota_read_length_ = 0;
-}
-
-void report_ota_error() {
-#ifdef USE_ARDUINO
-  StreamString ss;
-  Update.printError(ss);
-  ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
-#endif
-}
-
-void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
-                                     uint8_t *data, size_t len, bool final) {
-#ifdef USE_ARDUINO
-  bool success;
-  if (index == 0) {
-    this->ota_init_(filename.c_str());
-#ifdef USE_ESP8266
-    Update.runAsync(true);
-    // NOLINTNEXTLINE(readability-static-accessed-through-instance)
-    success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
-#endif
-#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
-    if (Update.isRunning()) {
-      Update.abort();
-    }
-    success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
-#endif
-    if (!success) {
-      report_ota_error();
-      return;
-    }
-  } else if (Update.hasError()) {
-    // don't spam logs with errors if something failed at start
-    return;
-  }
-
-  success = Update.write(data, len) == len;
-  if (!success) {
-    report_ota_error();
-    return;
-  }
-  this->ota_read_length_ += len;
-  this->report_ota_progress_(request);
-
-  if (final) {
-    if (Update.end(true)) {
-      this->schedule_ota_reboot_();
-    } else {
-      report_ota_error();
-    }
-  }
-#endif  // USE_ARDUINO
-
-#ifdef USE_ESP_IDF
-  // ESP-IDF implementation
-  if (index == 0 && !this->ota_backend_) {
-    // Initialize OTA on first call
-    this->ota_init_(filename.c_str());
-    this->ota_success_ = false;
-
-    auto *backend = new IDFWebServerOTABackend();
-    if (!backend->begin()) {
-      ESP_LOGE(TAG, "OTA begin failed");
-      delete backend;
-      return;
-    }
-    this->ota_backend_ = backend;
-  }
-
-  auto *backend = static_cast(this->ota_backend_);
-  if (!backend) {
-    return;
-  }
-
-  // Process data
-  if (len > 0) {
-    if (!backend->write(data, len)) {
-      ESP_LOGE(TAG, "OTA write failed");
-      backend->abort();
-      delete backend;
-      this->ota_backend_ = nullptr;
-      return;
-    }
-    this->ota_read_length_ += len;
-    this->report_ota_progress_(request);
-  }
-
-  // Finalize
-  if (final) {
-    this->ota_success_ = backend->end();
-    if (this->ota_success_) {
-      this->schedule_ota_reboot_();
-    } else {
-      ESP_LOGE(TAG, "OTA end failed");
-    }
-    delete backend;
-    this->ota_backend_ = nullptr;
-  }
-#endif  // USE_ESP_IDF
-}
-
-void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
-  AsyncWebServerResponse *response;
-#ifdef USE_ARDUINO
-  if (!Update.hasError()) {
-    response = request->beginResponse(200, "text/plain", "Update Successful!");
-  } else {
-    StreamString ss;
-    ss.print("Update Failed: ");
-    Update.printError(ss);
-    response = request->beginResponse(200, "text/plain", ss);
-  }
-#endif  // USE_ARDUINO
-#ifdef USE_ESP_IDF
-  // Send response based on the OTA result
-  response = request->beginResponse(200, "text/plain", this->ota_success_ ? "Update Successful!" : "Update Failed!");
-#endif  // USE_ESP_IDF
-  response->addHeader("Connection", "close");
-  request->send(response);
-}
-
-void WebServerBase::add_ota_handler() {
-  this->add_handler(new OTARequestHandler(this));  // NOLINT
-}
-#endif
-
 float WebServerBase::get_setup_priority() const {
   // Before WiFi (captive portal)
   return setup_priority::WIFI + 2.0f;
diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h
index 09a41956c9..a475238a37 100644
--- a/esphome/components/web_server_base/web_server_base.h
+++ b/esphome/components/web_server_base/web_server_base.h
@@ -17,6 +17,9 @@
 namespace esphome {
 namespace web_server_base {
 
+class WebServerBase;
+extern WebServerBase *global_web_server_base;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
 namespace internal {
 
 class MiddlewareHandler : public AsyncWebHandler {
@@ -110,18 +113,10 @@ class WebServerBase : public Component {
 
   void add_handler(AsyncWebHandler *handler);
 
-#ifdef USE_WEBSERVER_OTA
-  void add_ota_handler();
-#endif
-
   void set_port(uint16_t port) { port_ = port; }
   uint16_t get_port() const { return port_; }
 
  protected:
-#ifdef USE_WEBSERVER_OTA
-  friend class OTARequestHandler;
-#endif
-
   int initialized_{0};
   uint16_t port_{80};
   std::shared_ptr server_{nullptr};
@@ -129,37 +124,6 @@ class WebServerBase : public Component {
   internal::Credentials credentials_;
 };
 
-#ifdef USE_WEBSERVER_OTA
-class OTARequestHandler : public AsyncWebHandler {
- public:
-  OTARequestHandler(WebServerBase *parent) : parent_(parent) {}
-  void handleRequest(AsyncWebServerRequest *request) override;
-  void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
-                    bool final) override;
-  bool canHandle(AsyncWebServerRequest *request) const override {
-    return request->url() == "/update" && request->method() == HTTP_POST;
-  }
-
-  // NOLINTNEXTLINE(readability-identifier-naming)
-  bool isRequestHandlerTrivial() const override { return false; }
-
- protected:
-  void report_ota_progress_(AsyncWebServerRequest *request);
-  void schedule_ota_reboot_();
-  void ota_init_(const char *filename);
-
-  uint32_t last_ota_progress_{0};
-  uint32_t ota_read_length_{0};
-  WebServerBase *parent_;
-
- private:
-#ifdef USE_ESP_IDF
-  void *ota_backend_{nullptr};
-  bool ota_success_{false};
-#endif
-};
-#endif  // USE_WEBSERVER_OTA
-
 }  // namespace web_server_base
 }  // namespace esphome
 #endif
diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py
index fe1c6f2640..506e1c5c13 100644
--- a/esphome/components/web_server_idf/__init__.py
+++ b/esphome/components/web_server_idf/__init__.py
@@ -1,7 +1,5 @@
-from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
+from esphome.components.esp32 import add_idf_sdkconfig_option
 import esphome.config_validation as cv
-from esphome.const import CONF_OTA, CONF_WEB_SERVER
-from esphome.core import CORE
 
 CODEOWNERS = ["@dentra"]
 
@@ -14,7 +12,3 @@ CONFIG_SCHEMA = cv.All(
 async def to_code(config):
     # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
     add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024)
-    # Check if web_server component has OTA enabled
-    if CORE.config.get(CONF_WEB_SERVER, {}).get(CONF_OTA, True):
-        # Add multipart parser component for ESP-IDF OTA support
-        add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
diff --git a/esphome/config.py b/esphome/config.py
index 73cc7657cc..c4aa9aea24 100644
--- a/esphome/config.py
+++ b/esphome/config.py
@@ -67,6 +67,42 @@ ConfigPath = list[str | int]
 path_context = contextvars.ContextVar("Config path")
 
 
+def _process_platform_config(
+    result: Config,
+    component_name: str,
+    platform_name: str,
+    platform_config: ConfigType,
+    path: ConfigPath,
+) -> None:
+    """Process a platform configuration and add necessary validation steps.
+
+    This is shared between LoadValidationStep and AutoLoadValidationStep to avoid duplication.
+    """
+    # Get the platform manifest
+    platform = get_platform(component_name, platform_name)
+    if platform is None:
+        result.add_str_error(
+            f"Platform not found: '{component_name}.{platform_name}'", path
+        )
+        return
+
+    # Add platform to loaded integrations
+    CORE.loaded_integrations.add(platform_name)
+    CORE.loaded_platforms.add(f"{component_name}/{platform_name}")
+
+    # Process platform's AUTO_LOAD
+    for load in platform.auto_load:
+        if load not in result:
+            result.add_validation_step(AutoLoadValidationStep(load))
+
+    # Add validation steps for the platform
+    p_domain = f"{component_name}.{platform_name}"
+    result.add_output_path(path, p_domain)
+    result.add_validation_step(
+        MetadataValidationStep(path, p_domain, platform_config, platform)
+    )
+
+
 def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool:
     if len(path) < len(other):
         return False
@@ -379,26 +415,11 @@ class LoadValidationStep(ConfigValidationStep):
                     path,
                 )
                 continue
-            # Remove temp output path and construct new one
+            # Remove temp output path
             result.remove_output_path(path, p_domain)
-            p_domain = f"{self.domain}.{p_name}"
-            result.add_output_path(path, p_domain)
-            # Try Load platform
-            platform = get_platform(self.domain, p_name)
-            if platform is None:
-                result.add_str_error(f"Platform not found: '{p_domain}'", path)
-                continue
-            CORE.loaded_integrations.add(p_name)
-            CORE.loaded_platforms.add(f"{self.domain}/{p_name}")
 
-            # Process AUTO_LOAD
-            for load in platform.auto_load:
-                if load not in result:
-                    result.add_validation_step(AutoLoadValidationStep(load))
-
-            result.add_validation_step(
-                MetadataValidationStep(path, p_domain, p_config, platform)
-            )
+            # Process the platform configuration
+            _process_platform_config(result, self.domain, p_name, p_config, path)
 
 
 class AutoLoadValidationStep(ConfigValidationStep):
@@ -413,10 +434,56 @@ class AutoLoadValidationStep(ConfigValidationStep):
         self.domain = domain
 
     def run(self, result: Config) -> None:
-        if self.domain in result:
-            # already loaded
+        # Regular component auto-load (no platform)
+        if "." not in self.domain:
+            if self.domain in result:
+                # already loaded
+                return
+            result.add_validation_step(LoadValidationStep(self.domain, core.AutoLoad()))
             return
-        result.add_validation_step(LoadValidationStep(self.domain, core.AutoLoad()))
+
+        # Platform-specific auto-load (e.g., "ota.web_server")
+        component_name, _, platform_name = self.domain.partition(".")
+
+        # Check if component exists
+        if component_name not in result:
+            # Component doesn't exist, load it first
+            result.add_validation_step(LoadValidationStep(component_name, []))
+            # Re-run this step after the component is loaded
+            result.add_validation_step(AutoLoadValidationStep(self.domain))
+            return
+
+        # Component exists, check if it's a platform component
+        component = get_component(component_name)
+        if component is None or not component.is_platform_component:
+            result.add_str_error(
+                f"Component {component_name} is not a platform component, "
+                f"cannot auto-load platform {platform_name}",
+                [component_name],
+            )
+            return
+
+        # Ensure the component config is a list
+        component_conf = result.get(component_name)
+        if not isinstance(component_conf, list):
+            component_conf = result[component_name] = []
+
+        # Check if platform already exists
+        if any(
+            isinstance(conf, dict) and conf.get(CONF_PLATFORM) == platform_name
+            for conf in component_conf
+        ):
+            return
+
+        # Add and process the platform configuration
+        platform_conf = core.AutoLoad()
+        platform_conf[CONF_PLATFORM] = platform_name
+        component_conf.append(platform_conf)
+
+        path = [component_name, len(component_conf) - 1]
+        _process_platform_config(
+            result, component_name, platform_name, platform_conf, path
+        )
 
 
 class MetadataValidationStep(ConfigValidationStep):
diff --git a/tests/component_tests/ota/test_web_server_ota.py b/tests/component_tests/ota/test_web_server_ota.py
new file mode 100644
index 0000000000..0d8ff6f134
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota.py
@@ -0,0 +1,102 @@
+"""Tests for the web_server OTA platform."""
+
+from collections.abc import Callable
+
+
+def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None:
+    """Test that web_server OTA platform generates correct code."""
+    main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
+
+    # Check that the web server OTA component is included
+    assert "WebServerOTAComponent" in main_cpp
+    assert "web_server::WebServerOTAComponent" in main_cpp
+
+    # Check that global web server base is referenced
+    assert "global_web_server_base" in main_cpp
+
+    # Check component is registered
+    assert "App.register_component(web_server_webserverotacomponent_id)" in main_cpp
+
+
+def test_web_server_ota_with_callbacks(generate_main: Callable[[str], str]) -> None:
+    """Test web_server OTA with state callbacks."""
+    main_cpp = generate_main(
+        "tests/component_tests/ota/test_web_server_ota_callbacks.yaml"
+    )
+
+    # Check that web server OTA component is present
+    assert "WebServerOTAComponent" in main_cpp
+
+    # Check that callbacks are configured
+    # The actual callback code is in the component implementation, not main.cpp
+    # But we can check that logger.log statements are present from the callbacks
+    assert "logger.log" in main_cpp
+    assert "OTA started" in main_cpp
+    assert "OTA completed" in main_cpp
+    assert "OTA error" in main_cpp
+
+
+def test_web_server_ota_idf_multipart(generate_main: Callable[[str], str]) -> None:
+    """Test that ESP-IDF builds include multipart parser dependency."""
+    main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_idf.yaml")
+
+    # Check that web server OTA component is present
+    assert "WebServerOTAComponent" in main_cpp
+
+    # For ESP-IDF builds, the framework type is esp-idf
+    # The multipart parser dependency is added by web_server_idf
+    assert "web_server::WebServerOTAComponent" in main_cpp
+
+
+def test_web_server_ota_without_web_server_fails(
+    generate_main: Callable[[str], str],
+) -> None:
+    """Test that web_server OTA requires web_server component."""
+    # This should fail during validation since web_server_base is required
+    # but we can't test validation failures with generate_main
+    # Instead, verify that both components are needed in valid config
+    main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
+
+    # Both web server and OTA components should be present
+    assert "WebServer" in main_cpp
+    assert "WebServerOTAComponent" in main_cpp
+
+
+def test_multiple_ota_platforms(generate_main: Callable[[str], str]) -> None:
+    """Test multiple OTA platforms can coexist."""
+    main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_multi.yaml")
+
+    # Check all OTA platforms are included
+    assert "WebServerOTAComponent" in main_cpp
+    assert "ESPHomeOTAComponent" in main_cpp
+    assert "OtaHttpRequestComponent" in main_cpp
+
+    # Check components are from correct namespaces
+    assert "web_server::WebServerOTAComponent" in main_cpp
+    assert "esphome::ESPHomeOTAComponent" in main_cpp
+    assert "http_request::OtaHttpRequestComponent" in main_cpp
+
+
+def test_web_server_ota_arduino_with_auth(generate_main: Callable[[str], str]) -> None:
+    """Test web_server OTA with Arduino framework and authentication."""
+    main_cpp = generate_main(
+        "tests/component_tests/ota/test_web_server_ota_arduino.yaml"
+    )
+
+    # Check web server OTA component is present
+    assert "WebServerOTAComponent" in main_cpp
+
+    # Check authentication is set up for web server
+    assert "set_auth_username" in main_cpp
+    assert "set_auth_password" in main_cpp
+
+
+def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None:
+    """Test web_server OTA on ESP8266 platform."""
+    main_cpp = generate_main(
+        "tests/component_tests/ota/test_web_server_ota_esp8266.yaml"
+    )
+
+    # Check web server OTA component is present
+    assert "WebServerOTAComponent" in main_cpp
+    assert "web_server::WebServerOTAComponent" in main_cpp
diff --git a/tests/component_tests/ota/test_web_server_ota.yaml b/tests/component_tests/ota/test_web_server_ota.yaml
new file mode 100644
index 0000000000..e0fda3d0b5
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota.yaml
@@ -0,0 +1,15 @@
+esphome:
+  name: test_web_server_ota
+
+esp32:
+  board: esp32dev
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+web_server:
+  port: 80
+
+ota:
+  - platform: web_server
diff --git a/tests/component_tests/ota/test_web_server_ota_arduino.yaml b/tests/component_tests/ota/test_web_server_ota_arduino.yaml
new file mode 100644
index 0000000000..9462548cc8
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota_arduino.yaml
@@ -0,0 +1,18 @@
+esphome:
+  name: test_web_server_ota_arduino
+
+esp32:
+  board: esp32dev
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+web_server:
+  port: 80
+  auth:
+    username: admin
+    password: admin
+
+ota:
+  - platform: web_server
diff --git a/tests/component_tests/ota/test_web_server_ota_callbacks.yaml b/tests/component_tests/ota/test_web_server_ota_callbacks.yaml
new file mode 100644
index 0000000000..c2fd9e0f19
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota_callbacks.yaml
@@ -0,0 +1,31 @@
+esphome:
+  name: test_web_server_ota_callbacks
+
+esp32:
+  board: esp32dev
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+logger:
+
+web_server:
+  port: 80
+
+ota:
+  - platform: web_server
+    on_begin:
+      - logger.log: "OTA started"
+    on_progress:
+      - logger.log:
+          format: "OTA progress: %.1f%%"
+          args: ["x"]
+    on_end:
+      - logger.log: "OTA completed"
+    on_error:
+      - logger.log:
+          format: "OTA error: %d"
+          args: ["x"]
+    on_state_change:
+      - logger.log: "OTA state changed"
diff --git a/tests/component_tests/ota/test_web_server_ota_esp8266.yaml b/tests/component_tests/ota/test_web_server_ota_esp8266.yaml
new file mode 100644
index 0000000000..a1b66a5b53
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota_esp8266.yaml
@@ -0,0 +1,15 @@
+esphome:
+  name: test_web_server_ota_esp8266
+
+esp8266:
+  board: nodemcuv2
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+web_server:
+  port: 80
+
+ota:
+  - platform: web_server
diff --git a/tests/component_tests/ota/test_web_server_ota_idf.yaml b/tests/component_tests/ota/test_web_server_ota_idf.yaml
new file mode 100644
index 0000000000..18b639347c
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota_idf.yaml
@@ -0,0 +1,17 @@
+esphome:
+  name: test_web_server_ota_idf
+
+esp32:
+  board: esp32dev
+  framework:
+    type: esp-idf
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+web_server:
+  port: 80
+
+ota:
+  - platform: web_server
diff --git a/tests/component_tests/ota/test_web_server_ota_multi.yaml b/tests/component_tests/ota/test_web_server_ota_multi.yaml
new file mode 100644
index 0000000000..7926b09c71
--- /dev/null
+++ b/tests/component_tests/ota/test_web_server_ota_multi.yaml
@@ -0,0 +1,21 @@
+esphome:
+  name: test_web_server_ota_multi
+
+esp32:
+  board: esp32dev
+
+wifi:
+  ssid: MySSID
+  password: password1
+
+web_server:
+  port: 80
+
+http_request:
+  verify_ssl: false
+
+ota:
+  - platform: esphome
+    password: "test_password"
+  - platform: web_server
+  - platform: http_request
diff --git a/tests/component_tests/web_server/test_ota_migration.py b/tests/component_tests/web_server/test_ota_migration.py
new file mode 100644
index 0000000000..7f34ec75f6
--- /dev/null
+++ b/tests/component_tests/web_server/test_ota_migration.py
@@ -0,0 +1,38 @@
+"""Tests for web_server OTA migration validation."""
+
+import pytest
+
+from esphome import config_validation as cv
+from esphome.types import ConfigType
+
+
+def test_web_server_ota_true_fails_validation() -> None:
+    """Test that web_server with ota: true fails validation with helpful message."""
+    from esphome.components.web_server import validate_ota_removed
+
+    # Config with ota: true should fail
+    config: ConfigType = {"ota": True}
+
+    with pytest.raises(cv.Invalid) as exc_info:
+        validate_ota_removed(config)
+
+    # Check error message contains migration instructions
+    error_msg = str(exc_info.value)
+    assert "has been removed from 'web_server'" in error_msg
+    assert "platform: web_server" in error_msg
+    assert "ota:" in error_msg
+
+
+def test_web_server_ota_false_passes_validation() -> None:
+    """Test that web_server with ota: false passes validation."""
+    from esphome.components.web_server import validate_ota_removed
+
+    # Config with ota: false should pass
+    config: ConfigType = {"ota": False}
+    result = validate_ota_removed(config)
+    assert result == config
+
+    # Config without ota should also pass
+    config: ConfigType = {}
+    result = validate_ota_removed(config)
+    assert result == config
diff --git a/tests/components/web_server/test_no_ota.esp32-idf.yaml b/tests/components/web_server/test_no_ota.esp32-idf.yaml
index 1f677fb948..4064f518cf 100644
--- a/tests/components/web_server/test_no_ota.esp32-idf.yaml
+++ b/tests/components/web_server/test_no_ota.esp32-idf.yaml
@@ -1,3 +1,11 @@
+esphome:
+  name: test-web-server-no-ota-idf
+
+esp32:
+  board: esp32dev
+  framework:
+    type: esp-idf
+
 packages:
   device_base: !include common.yaml
 
@@ -6,4 +14,3 @@ packages:
 web_server:
   port: 8080
   version: 2
-  ota: false
diff --git a/tests/components/web_server/test_ota.esp32-idf.yaml b/tests/components/web_server/test_ota.esp32-idf.yaml
index 294e7f862e..37838b3d34 100644
--- a/tests/components/web_server/test_ota.esp32-idf.yaml
+++ b/tests/components/web_server/test_ota.esp32-idf.yaml
@@ -1,8 +1,6 @@
-# Test configuration for ESP-IDF web server with OTA enabled
 esphome:
   name: test-web-server-ota-idf
 
-# Force ESP-IDF framework
 esp32:
   board: esp32dev
   framework:
@@ -15,17 +13,17 @@ packages:
 ota:
   - platform: esphome
     password: "test_ota_password"
+  - platform: web_server
 
-# Web server with OTA enabled
+# Web server configuration
 web_server:
   port: 8080
   version: 2
-  ota: true
   include_internal: true
 
 # Enable debug logging for OTA
 logger:
-  level: DEBUG
+  level: VERBOSE
   logs:
     web_server: VERBOSE
     web_server_idf: VERBOSE
diff --git a/tests/components/web_server/test_ota_disabled.esp32-idf.yaml b/tests/components/web_server/test_ota_disabled.esp32-idf.yaml
index c7c7574e3b..b88b845db7 100644
--- a/tests/components/web_server/test_ota_disabled.esp32-idf.yaml
+++ b/tests/components/web_server/test_ota_disabled.esp32-idf.yaml
@@ -1,11 +1,18 @@
+esphome:
+  name: test-ws-ota-disabled-idf
+
+esp32:
+  board: esp32dev
+  framework:
+    type: esp-idf
+
 packages:
   device_base: !include common.yaml
 
-# OTA is configured but web_server OTA is disabled
+# OTA is configured but web_server OTA is NOT included
 ota:
   - platform: esphome
 
 web_server:
   port: 8080
   version: 2
-  ota: false

From 785b14ac84fa0cacc88af0923c36170ea3a07518 Mon Sep 17 00:00:00 2001
From: George 
Date: Wed, 2 Jul 2025 04:14:16 +0200
Subject: [PATCH 20/35] pulse_meter total (#9282)

---
 esphome/components/pulse_meter/pulse_meter_sensor.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp
index 81ecf22c71..9a7630a7be 100644
--- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp
+++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp
@@ -31,6 +31,10 @@ void PulseMeterSensor::setup() {
     this->pulse_state_.latched_ = this->last_pin_val_;
     this->pin_->attach_interrupt(PulseMeterSensor::pulse_intr, this, gpio::INTERRUPT_ANY_EDGE);
   }
+
+  if (this->total_sensor_ != nullptr) {
+    this->total_sensor_->publish_state(this->total_pulses_);
+  }
 }
 
 void PulseMeterSensor::loop() {

From 5fa9d22c5dd925bd92eaf3297c25b33e8d3abff6 Mon Sep 17 00:00:00 2001
From: Craig Andrews 
Date: Tue, 1 Jul 2025 22:17:34 -0400
Subject: [PATCH 21/35] [http_request] allow retrieval of more than just the
 first header (#9242)

---
 esphome/components/http_request/http_request_arduino.cpp | 1 -
 esphome/components/http_request/http_request_idf.cpp     | 1 -
 2 files changed, 2 deletions(-)

diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp
index b4378cdce6..c009b33c2d 100644
--- a/esphome/components/http_request/http_request_arduino.cpp
+++ b/esphome/components/http_request/http_request_arduino.cpp
@@ -133,7 +133,6 @@ std::shared_ptr HttpRequestArduino::perform(std::string url, std:
       std::string header_value = container->client_.header(i).c_str();
       ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
       container->response_headers_[header_name].push_back(header_value);
-      break;
     }
   }
 
diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp
index 6a779ba03a..68c06d28f2 100644
--- a/esphome/components/http_request/http_request_idf.cpp
+++ b/esphome/components/http_request/http_request_idf.cpp
@@ -42,7 +42,6 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
         const std::string header_value = evt->header_value;
         ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
         user_data->response_headers[header_name].push_back(header_value);
-        break;
       }
       break;
     }

From 095acce3e28ccec918968d5af44af1edd85b4a48 Mon Sep 17 00:00:00 2001
From: Jeremy Brown 
Date: Tue, 1 Jul 2025 22:48:42 -0400
Subject: [PATCH 22/35] Mmc5603 fix for devices that don't retrieve chip_id
 (#8959)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/mmc5603/mmc5603.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp
index 86b1b23c15..7f78f9592a 100644
--- a/esphome/components/mmc5603/mmc5603.cpp
+++ b/esphome/components/mmc5603/mmc5603.cpp
@@ -39,7 +39,7 @@ void MMC5603Component::setup() {
     return;
   }
 
-  if (id != MMC56X3_CHIP_ID) {
+  if (id != 0 && id != MMC56X3_CHIP_ID) {  // ID is not reported correctly by all chips, 0 on some chips
     ESP_LOGCONFIG(TAG, "Chip Wrong");
     this->error_code_ = ID_REGISTERS;
     this->mark_failed();

From 2fb23becec8726cbcace94951814debf7f872775 Mon Sep 17 00:00:00 2001
From: JonasB2497 <45214989+JonasB2497@users.noreply.github.com>
Date: Wed, 2 Jul 2025 04:56:48 +0200
Subject: [PATCH 23/35] made qr_code elements optional (#8896)

---
 esphome/components/qr_code/__init__.py | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/esphome/components/qr_code/__init__.py b/esphome/components/qr_code/__init__.py
index 1c5e0471b0..6ff92b8a7f 100644
--- a/esphome/components/qr_code/__init__.py
+++ b/esphome/components/qr_code/__init__.py
@@ -21,21 +21,24 @@ ECC = {
     "HIGH": qrcodegen_Ecc.qrcodegen_Ecc_HIGH,
 }
 
-CONFIG_SCHEMA = cv.Schema(
-    {
-        cv.Required(CONF_ID): cv.declare_id(QRCode),
-        cv.Required(CONF_VALUE): cv.string,
-        cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True),
-    }
+CONFIG_SCHEMA = cv.ensure_list(
+    cv.Schema(
+        {
+            cv.Required(CONF_ID): cv.declare_id(QRCode),
+            cv.Required(CONF_VALUE): cv.string,
+            cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True),
+        }
+    )
 )
 
 
 async def to_code(config):
     cg.add_library("wjtje/qr-code-generator-library", "^1.7.0")
 
-    var = cg.new_Pvariable(config[CONF_ID])
-    cg.add(var.set_value(config[CONF_VALUE]))
-    cg.add(var.set_ecc(ECC[config[CONF_ECC]]))
-    await cg.register_component(var, config)
+    for entry in config:
+        var = cg.new_Pvariable(entry[CONF_ID])
+        cg.add(var.set_value(entry[CONF_VALUE]))
+        cg.add(var.set_ecc(ECC[entry[CONF_ECC]]))
+        await cg.register_component(var, entry)
 
     cg.add_define("USE_QR_CODE")

From fae96e279c810401cc3e751dca062d30399ac71d Mon Sep 17 00:00:00 2001
From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com>
Date: Wed, 2 Jul 2025 05:25:06 +0200
Subject: [PATCH 24/35] [nextion] memory optimization (#9164)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/nextion/nextion.cpp        | 25 ++++++++-----------
 esphome/components/nextion/nextion.h          | 16 ++++++------
 .../components/nextion/nextion_commands.cpp   |  4 +--
 3 files changed, 20 insertions(+), 25 deletions(-)

diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp
index 042a595ff8..bb75385d8c 100644
--- a/esphome/components/nextion/nextion.cpp
+++ b/esphome/components/nextion/nextion.cpp
@@ -164,7 +164,7 @@ void Nextion::dump_config() {
 #endif  // USE_NEXTION_MAX_COMMANDS_PER_LOOP
 
   if (this->touch_sleep_timeout_ != 0) {
-    ESP_LOGCONFIG(TAG, "  Touch Timeout:  %" PRIu32, this->touch_sleep_timeout_);
+    ESP_LOGCONFIG(TAG, "  Touch Timeout:  %" PRIu16, this->touch_sleep_timeout_);
   }
 
   if (this->wake_up_page_ != -1) {
@@ -302,11 +302,11 @@ void Nextion::loop() {
     }
 
     // Check if a startup page has been set and send the command
-    if (this->start_up_page_ != -1) {
+    if (this->start_up_page_ >= 0) {
       this->goto_page(this->start_up_page_);
     }
 
-    if (this->wake_up_page_ != -1) {
+    if (this->wake_up_page_ >= 0) {
       this->set_wake_up_page(this->wake_up_page_);
     }
 
@@ -418,12 +418,12 @@ void Nextion::process_nextion_commands_() {
       ESP_LOGN(TAG, "Add 0xFF");
     }
 
-    this->nextion_event_ = this->command_data_[0];
+    const uint8_t nextion_event = this->command_data_[0];
 
     to_process_length -= 1;
     to_process = this->command_data_.substr(1, to_process_length);
 
-    switch (this->nextion_event_) {
+    switch (nextion_event) {
       case 0x00:  // instruction sent by user has failed
         ESP_LOGW(TAG, "Invalid instruction");
         this->remove_from_q_();
@@ -562,9 +562,9 @@ void Nextion::process_nextion_commands_() {
           break;
         }
 
-        uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
-        uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
-        uint8_t touch_event = to_process[4];  // 0 -> release, 1 -> press
+        const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
+        const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
+        const uint8_t touch_event = to_process[4];  // 0 -> release, 1 -> press
         ESP_LOGD(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
         break;
       }
@@ -820,15 +820,14 @@ void Nextion::process_nextion_commands_() {
         break;
       }
       default:
-        ESP_LOGW(TAG, "Unknown event: 0x%02X", this->nextion_event_);
+        ESP_LOGW(TAG, "Unknown event: 0x%02X", nextion_event);
         break;
     }
 
-    // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1);
     this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
   }
 
-  uint32_t ms = App.get_loop_component_start_time();
+  const uint32_t ms = App.get_loop_component_start_time();
 
   if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
     for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
@@ -960,7 +959,6 @@ void Nextion::update_components_by_prefix(const std::string &prefix) {
 }
 
 uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
-  uint16_t ret = 0;
   uint8_t c = 0;
   uint8_t nr_of_ff_bytes = 0;
   bool exit_flag = false;
@@ -1003,8 +1001,7 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
   if (ff_flag)
     response = response.substr(0, response.length() - 3);  // Remove last 3 0xFF
 
-  ret = response.length();
-  return ret;
+  return response.length();
 }
 
 /**
diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h
index 0cd559d251..0b77d234f5 100644
--- a/esphome/components/nextion/nextion.h
+++ b/esphome/components/nextion/nextion.h
@@ -1190,11 +1190,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
    * After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
    * `thup`.
    */
-  void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
+  void set_touch_sleep_timeout(uint16_t touch_sleep_timeout);
 
   /**
    * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
-   * @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
+   * @param wake_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to
    * wakes up to current page.
    *
    * Example:
@@ -1204,11 +1204,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
    *
    * The display will wake up to page 2.
    */
-  void set_wake_up_page(uint8_t wake_up_page = 255);
+  void set_wake_up_page(int16_t wake_up_page = -1);
 
   /**
    * Sets which page Nextion loads when connecting to ESPHome.
-   * @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
+   * @param start_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to
    * wakes up to current page.
    *
    * Example:
@@ -1218,7 +1218,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
    *
    * The display will go to page 2 when it establishes a connection to ESPHome.
    */
-  void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
+  void set_start_up_page(int16_t start_up_page = -1) { this->start_up_page_ = start_up_page; }
 
   /**
    * Sets if Nextion should auto-wake from sleep when touch press occurs.
@@ -1330,7 +1330,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
   std::deque waveform_queue_;
   uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
   void all_components_send_state_(bool force_update = false);
-  uint64_t comok_sent_ = 0;
+  uint32_t comok_sent_ = 0;
   bool remove_from_q_(bool report_empty = true);
 
   /**
@@ -1340,12 +1340,10 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
   bool ignore_is_setup_ = false;
 
   bool nextion_reports_is_setup_ = false;
-  uint8_t nextion_event_;
-
   void process_nextion_commands_();
   void process_serial_();
   bool is_updating_ = false;
-  uint32_t touch_sleep_timeout_ = 0;
+  uint16_t touch_sleep_timeout_ = 0;
   int16_t wake_up_page_ = -1;
   int16_t start_up_page_ = -1;
   bool auto_wake_on_touch_ = true;
diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp
index 0226e0a13c..84aacd1868 100644
--- a/esphome/components/nextion/nextion_commands.cpp
+++ b/esphome/components/nextion/nextion_commands.cpp
@@ -10,12 +10,12 @@ static const char *const TAG = "nextion";
 // Sleep safe commands
 void Nextion::soft_reset() { this->send_command_("rest"); }
 
-void Nextion::set_wake_up_page(uint8_t wake_up_page) {
+void Nextion::set_wake_up_page(int16_t wake_up_page) {
   this->wake_up_page_ = wake_up_page;
   this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
 }
 
-void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
+void Nextion::set_touch_sleep_timeout(uint16_t touch_sleep_timeout) {
   if (touch_sleep_timeout < 3) {
     ESP_LOGD(TAG, "Sleep timeout out of bounds (3-65535)");
     return;

From eba2c82fec60181327afa35a3be48613d18cc35b Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" 
Date: Tue, 1 Jul 2025 23:36:09 -0500
Subject: [PATCH 25/35] Use encode_bytes() for protobuf bytes fields (#9289)

---
 esphome/components/api/api_pb2.cpp  | 24 +++++++++++++-----------
 script/api_protobuf/api_protobuf.py |  6 +++++-
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp
index 8bce14c9cc..7d16e43ce6 100644
--- a/esphome/components/api/api_pb2.cpp
+++ b/esphome/components/api/api_pb2.cpp
@@ -3494,7 +3494,7 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite
 }
 void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_enum(1, this->level);
-  buffer.encode_string(3, this->message);
+  buffer.encode_bytes(3, reinterpret_cast(this->message.data()), this->message.size());
   buffer.encode_bool(4, this->send_failed);
 }
 void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const {
@@ -3530,7 +3530,9 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD
       return false;
   }
 }
-void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key); }
+void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const {
+  buffer.encode_bytes(1, reinterpret_cast(this->key.data()), this->key.size());
+}
 void NoiseEncryptionSetKeyRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->key, false);
 }
@@ -4267,7 +4269,7 @@ bool CameraImageResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 }
 void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_fixed32(1, this->key);
-  buffer.encode_string(2, this->data);
+  buffer.encode_bytes(2, reinterpret_cast(this->data.data()), this->data.size());
   buffer.encode_bool(3, this->done);
 }
 void CameraImageResponse::calculate_size(uint32_t &total_size) const {
@@ -6785,7 +6787,7 @@ void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
   for (auto &it : this->legacy_data) {
     buffer.encode_uint32(2, it, true);
   }
-  buffer.encode_string(3, this->data);
+  buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothServiceData::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->uuid, false);
@@ -6859,7 +6861,7 @@ bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLen
 }
 void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
-  buffer.encode_string(2, this->name);
+  buffer.encode_bytes(2, reinterpret_cast(this->name.data()), this->name.size());
   buffer.encode_sint32(3, this->rssi);
   for (auto &it : this->service_uuids) {
     buffer.encode_string(4, it, true);
@@ -6960,7 +6962,7 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
   buffer.encode_sint32(2, this->rssi);
   buffer.encode_uint32(3, this->address_type);
-  buffer.encode_string(4, this->data);
+  buffer.encode_bytes(4, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothLERawAdvertisement::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
@@ -7493,7 +7495,7 @@ bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDeli
 void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
   buffer.encode_uint32(2, this->handle);
-  buffer.encode_string(3, this->data);
+  buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
@@ -7552,7 +7554,7 @@ void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
   buffer.encode_uint32(2, this->handle);
   buffer.encode_bool(3, this->response);
-  buffer.encode_string(4, this->data);
+  buffer.encode_bytes(4, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothGATTWriteRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
@@ -7649,7 +7651,7 @@ bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, Proto
 void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
   buffer.encode_uint32(2, this->handle);
-  buffer.encode_string(3, this->data);
+  buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothGATTWriteDescriptorRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
@@ -7751,7 +7753,7 @@ bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLeng
 void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
   buffer.encode_uint64(1, this->address);
   buffer.encode_uint32(2, this->handle);
-  buffer.encode_string(3, this->data);
+  buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size());
 }
 void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
@@ -8481,7 +8483,7 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited
   }
 }
 void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
-  buffer.encode_string(1, this->data);
+  buffer.encode_bytes(1, reinterpret_cast(this->data.data()), this->data.size());
   buffer.encode_bool(2, this->end);
 }
 void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const {
diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py
index 615f5bbfda..56a46a7701 100755
--- a/script/api_protobuf/api_protobuf.py
+++ b/script/api_protobuf/api_protobuf.py
@@ -526,9 +526,13 @@ class BytesType(TypeInfo):
     reference_type = "std::string &"
     const_reference_type = "const std::string &"
     decode_length = "value.as_string()"
-    encode_func = "encode_string"
+    encode_func = "encode_bytes"
     wire_type = WireType.LENGTH_DELIMITED  # Uses wire type 2
 
+    @property
+    def encode_content(self) -> str:
+        return f"buffer.encode_bytes({self.number}, reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size());"
+
     def dump(self, name: str) -> str:
         o = f"out.append(format_hex_pretty({name}));"
         return o

From f6f0e52d5e7bdef9e2b36990307779d6624a5f36 Mon Sep 17 00:00:00 2001
From: Aleksey Zinchenko 
Date: Wed, 2 Jul 2025 10:37:31 +0300
Subject: [PATCH 26/35] [core] Deleting CMakeCache.txt for fast recompilation
 with ESP-IDF (#8750)

---
 esphome/writer.py | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/esphome/writer.py b/esphome/writer.py
index 7a5089e384..943dfa78cc 100644
--- a/esphome/writer.py
+++ b/esphome/writer.py
@@ -107,6 +107,11 @@ def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool:
         return True
     if old.build_path != new.build_path:
         return True
+
+    return False
+
+
+def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool:
     if (
         old.loaded_integrations != new.loaded_integrations
         or old.loaded_platforms != new.loaded_platforms
@@ -126,10 +131,11 @@ def update_storage_json():
         return
 
     if storage_should_clean(old, new):
-        _LOGGER.info(
-            "Core config, version or integrations changed, cleaning build files..."
-        )
+        _LOGGER.info("Core config, version changed, cleaning build files...")
         clean_build()
+    elif storage_should_update_cmake_cache(old, new):
+        _LOGGER.info("Integrations changed, cleaning cmake cache...")
+        clean_cmake_cache()
 
     new.save(path)
 
@@ -353,6 +359,15 @@ def write_cpp(code_s):
     write_file_if_changed(path, full_file)
 
 
+def clean_cmake_cache():
+    pioenvs = CORE.relative_pioenvs_path()
+    if os.path.isdir(pioenvs):
+        pioenvs_cmake_path = CORE.relative_pioenvs_path(CORE.name, "CMakeCache.txt")
+        if os.path.isfile(pioenvs_cmake_path):
+            _LOGGER.info("Deleting %s", pioenvs_cmake_path)
+            os.remove(pioenvs_cmake_path)
+
+
 def clean_build():
     import shutil
 

From 56a963dfe68e251acedc25ce598ca4c32bc9783a Mon Sep 17 00:00:00 2001
From: mrtntome <21003287+mrtntome@users.noreply.github.com>
Date: Wed, 2 Jul 2025 09:05:54 -0300
Subject: [PATCH 27/35] [heatpumpir] Add Support for PHS32 HeatPump (#7378)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/heatpumpir/climate.py     | 1 +
 esphome/components/heatpumpir/heatpumpir.cpp | 1 +
 esphome/components/heatpumpir/heatpumpir.h   | 1 +
 3 files changed, 3 insertions(+)

diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py
index 9e5a2bf45c..0f9f146ae9 100644
--- a/esphome/components/heatpumpir/climate.py
+++ b/esphome/components/heatpumpir/climate.py
@@ -70,6 +70,7 @@ PROTOCOLS = {
     "airway": Protocol.PROTOCOL_AIRWAY,
     "bgh_aud": Protocol.PROTOCOL_BGH_AUD,
     "panasonic_altdke": Protocol.PROTOCOL_PANASONIC_ALTDKE,
+    "philco_phs32": Protocol.PROTOCOL_PHILCO_PHS32,
     "vaillantvai8": Protocol.PROTOCOL_VAILLANTVAI8,
     "r51m": Protocol.PROTOCOL_R51M,
 }
diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp
index d3476c6a71..f4d2ca6c1d 100644
--- a/esphome/components/heatpumpir/heatpumpir.cpp
+++ b/esphome/components/heatpumpir/heatpumpir.cpp
@@ -65,6 +65,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP
     {PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }},                              // NOLINT
     {PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }},                                // NOLINT
     {PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }},           // NOLINT
+    {PROTOCOL_PHILCO_PHS32, []() { return new PhilcoPHS32HeatpumpIR(); }},                   // NOLINT
     {PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }},                      // NOLINT
     {PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }},                                  // NOLINT
 };
diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h
index b740d27af7..3e14c11861 100644
--- a/esphome/components/heatpumpir/heatpumpir.h
+++ b/esphome/components/heatpumpir/heatpumpir.h
@@ -65,6 +65,7 @@ enum Protocol {
   PROTOCOL_AIRWAY,
   PROTOCOL_BGH_AUD,
   PROTOCOL_PANASONIC_ALTDKE,
+  PROTOCOL_PHILCO_PHS32,
   PROTOCOL_VAILLANTVAI8,
   PROTOCOL_R51M,
 };

From 4cdc804c178088ee221943accca1398f2c3e1923 Mon Sep 17 00:00:00 2001
From: rwrozelle 
Date: Wed, 2 Jul 2025 08:16:28 -0400
Subject: [PATCH 28/35] OpenThread - add Device Type (#9272)

Co-authored-by: mc 
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/openthread/__init__.py          | 11 ++++++++++-
 esphome/components/openthread/const.py             |  1 +
 tests/components/openthread/test.esp32-c6-idf.yaml |  1 +
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py
index 65138e28c7..25e3153d1b 100644
--- a/esphome/components/openthread/__init__.py
+++ b/esphome/components/openthread/__init__.py
@@ -11,6 +11,7 @@ from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID
 import esphome.final_validate as fv
 
 from .const import (
+    CONF_DEVICE_TYPE,
     CONF_EXT_PAN_ID,
     CONF_FORCE_DATASET,
     CONF_MDNS_ID,
@@ -32,6 +33,11 @@ AUTO_LOAD = ["network"]
 CONFLICTS_WITH = ["wifi"]
 DEPENDENCIES = ["esp32"]
 
+CONF_DEVICE_TYPES = [
+    "FTD",
+    "MTD",
+]
+
 
 def set_sdkconfig_options(config):
     # and expose options for using SPI/UART RCPs
@@ -82,7 +88,7 @@ def set_sdkconfig_options(config):
     add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
 
     # TODO: Add suport for sleepy end devices
-    add_idf_sdkconfig_option("CONFIG_OPENTHREAD_FTD", True)  # Full Thread Device
+    add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
 
 
 openthread_ns = cg.esphome_ns.namespace("openthread")
@@ -107,6 +113,9 @@ CONFIG_SCHEMA = cv.All(
             cv.GenerateID(): cv.declare_id(OpenThreadComponent),
             cv.GenerateID(CONF_SRP_ID): cv.declare_id(OpenThreadSrpComponent),
             cv.GenerateID(CONF_MDNS_ID): cv.use_id(MDNSComponent),
+            cv.Optional(CONF_DEVICE_TYPE, default="FTD"): cv.one_of(
+                *CONF_DEVICE_TYPES, upper=True
+            ),
             cv.Optional(CONF_FORCE_DATASET): cv.boolean,
             cv.Optional(CONF_TLV): cv.string_strict,
         }
diff --git a/esphome/components/openthread/const.py b/esphome/components/openthread/const.py
index 7837e69eea..7a6ffb2df4 100644
--- a/esphome/components/openthread/const.py
+++ b/esphome/components/openthread/const.py
@@ -1,3 +1,4 @@
+CONF_DEVICE_TYPE = "device_type"
 CONF_EXT_PAN_ID = "ext_pan_id"
 CONF_FORCE_DATASET = "force_dataset"
 CONF_MDNS_ID = "mdns_id"
diff --git a/tests/components/openthread/test.esp32-c6-idf.yaml b/tests/components/openthread/test.esp32-c6-idf.yaml
index f53b323bec..bbcf48efa5 100644
--- a/tests/components/openthread/test.esp32-c6-idf.yaml
+++ b/tests/components/openthread/test.esp32-c6-idf.yaml
@@ -2,6 +2,7 @@ network:
   enable_ipv6: true
 
 openthread:
+  device_type: FTD
   channel: 13
   network_name: OpenThread-8f28
   network_key: 0xdfd34f0f05cad978ec4e32b0413038ff

From 289aedcfe21c54352ab8da8858d74d5828620287 Mon Sep 17 00:00:00 2001
From: Colm 
Date: Wed, 2 Jul 2025 05:23:37 -0700
Subject: [PATCH 29/35] Don't compile `state_to_string()` unless debugging.
 (#7473)

---
 esphome/components/rtttl/rtttl.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp
index 2c4a0f917f..65a3af1bbc 100644
--- a/esphome/components/rtttl/rtttl.cpp
+++ b/esphome/components/rtttl/rtttl.cpp
@@ -371,6 +371,7 @@ void Rtttl::finish_() {
   ESP_LOGD(TAG, "Playback finished");
 }
 
+#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
 static const LogString *state_to_string(State state) {
   switch (state) {
     case STATE_STOPPED:
@@ -387,6 +388,7 @@ static const LogString *state_to_string(State state) {
       return LOG_STR("UNKNOWN");
   }
 };
+#endif
 
 void Rtttl::set_state_(State state) {
   State old_state = this->state_;

From 9b3ece4caf0bdaf0633cd91c42966af09d309cce Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 3 Jul 2025 01:51:25 +1200
Subject: [PATCH 30/35] [time] Add ``USE_TIME_TIMEZONE`` define (#9290)

---
 esphome/components/time/__init__.py         | 18 ++++++++++++++++--
 esphome/components/time/real_time_clock.cpp |  4 ++++
 esphome/components/time/real_time_clock.h   |  4 ++++
 esphome/core/defines.h                      |  1 +
 4 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py
index 6b3ff6f4d3..ab821d457b 100644
--- a/esphome/components/time/__init__.py
+++ b/esphome/components/time/__init__.py
@@ -268,7 +268,19 @@ def validate_tz(value: str) -> str:
 
 TIME_SCHEMA = cv.Schema(
     {
-        cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz,
+        cv.SplitDefault(
+            CONF_TIMEZONE,
+            esp8266=detect_tz,
+            esp32=detect_tz,
+            rp2040=detect_tz,
+            bk72xx=detect_tz,
+            rtl87xx=detect_tz,
+            ln882x=detect_tz,
+            host=detect_tz,
+        ): cv.All(
+            cv.only_with_framework(["arduino", "esp-idf", "host"]),
+            validate_tz,
+        ),
         cv.Optional(CONF_ON_TIME): automation.validate_automation(
             {
                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger),
@@ -293,7 +305,9 @@ TIME_SCHEMA = cv.Schema(
 
 
 async def setup_time_core_(time_var, config):
-    cg.add(time_var.set_timezone(config[CONF_TIMEZONE]))
+    if timezone := config.get(CONF_TIMEZONE):
+        cg.add(time_var.set_timezone(timezone))
+        cg.add_define("USE_TIME_TIMEZONE")
 
     for conf in config.get(CONF_ON_TIME, []):
         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], time_var)
diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp
index 11e39e8f67..61391d2c6b 100644
--- a/esphome/components/time/real_time_clock.cpp
+++ b/esphome/components/time/real_time_clock.cpp
@@ -35,8 +35,10 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
     ret = settimeofday(&timev, nullptr);
   }
 
+#ifdef USE_TIME_TIMEZONE
   // Move timezone back to local timezone.
   this->apply_timezone_();
+#endif
 
   if (ret != 0) {
     ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
@@ -49,10 +51,12 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
   this->time_sync_callback_.call();
 }
 
+#ifdef USE_TIME_TIMEZONE
 void RealTimeClock::apply_timezone_() {
   setenv("TZ", this->timezone_.c_str(), 1);
   tzset();
 }
+#endif
 
 }  // namespace time
 }  // namespace esphome
diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h
index 401798a568..9fad148885 100644
--- a/esphome/components/time/real_time_clock.h
+++ b/esphome/components/time/real_time_clock.h
@@ -20,6 +20,7 @@ class RealTimeClock : public PollingComponent {
  public:
   explicit RealTimeClock();
 
+#ifdef USE_TIME_TIMEZONE
   /// Set the time zone.
   void set_timezone(const std::string &tz) {
     this->timezone_ = tz;
@@ -28,6 +29,7 @@ class RealTimeClock : public PollingComponent {
 
   /// Get the time zone currently in use.
   std::string get_timezone() { return this->timezone_; }
+#endif
 
   /// Get the time in the currently defined timezone.
   ESPTime now() { return ESPTime::from_epoch_local(this->timestamp_now()); }
@@ -46,8 +48,10 @@ class RealTimeClock : public PollingComponent {
   /// Report a unix epoch as current time.
   void synchronize_epoch_(uint32_t epoch);
 
+#ifdef USE_TIME_TIMEZONE
   std::string timezone_{};
   void apply_timezone_();
+#endif
 
   CallbackManager time_sync_callback_;
 };
diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index cfaed6fdb7..be872689f3 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -116,6 +116,7 @@
 #define USE_OTA_PASSWORD
 #define USE_OTA_STATE_CALLBACK
 #define USE_OTA_VERSION 2
+#define USE_TIME_TIMEZONE
 #define USE_WIFI
 #define USE_WIFI_AP
 #define USE_WIREGUARD

From 60eac6ea0707e0f4dd7d1e506edf1e4c504dbe67 Mon Sep 17 00:00:00 2001
From: tomaszduda23 
Date: Wed, 2 Jul 2025 16:02:56 +0200
Subject: [PATCH 31/35] [time] fix clang-tidy (#9292)

---
 esphome/components/time/real_time_clock.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h
index 9fad148885..4b98a88975 100644
--- a/esphome/components/time/real_time_clock.h
+++ b/esphome/components/time/real_time_clock.h
@@ -40,7 +40,7 @@ class RealTimeClock : public PollingComponent {
   /// Get the current time as the UTC epoch since January 1st 1970.
   time_t timestamp_now() { return ::time(nullptr); }
 
-  void add_on_time_sync_callback(std::function callback) {
+  void add_on_time_sync_callback(std::function &&callback) {
     this->time_sync_callback_.add(std::move(callback));
   };
 

From 00eb56d8db27af8001ba93fbb3552778eab750a2 Mon Sep 17 00:00:00 2001
From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Date: Thu, 3 Jul 2025 00:08:10 +1000
Subject: [PATCH 32/35] [esp32_touch] Fix threshold (#9291)

Co-authored-by: Keith Burzinski 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 esphome/components/esp32_touch/esp32_touch.h  | 22 ++++----
 .../esp32_touch/esp32_touch_common.cpp        | 17 +++---
 .../components/esp32_touch/esp32_touch_v1.cpp |  6 +-
 .../components/esp32_touch/esp32_touch_v2.cpp | 55 +++++++++----------
 4 files changed, 50 insertions(+), 50 deletions(-)

diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h
index 576c1a5649..5a91b1c750 100644
--- a/esphome/components/esp32_touch/esp32_touch.h
+++ b/esphome/components/esp32_touch/esp32_touch.h
@@ -93,7 +93,6 @@ class ESP32TouchComponent : public Component {
   uint32_t last_release_check_{0};
   uint32_t release_timeout_ms_{1500};
   uint32_t release_check_interval_ms_{50};
-  bool initial_state_published_[TOUCH_PAD_MAX] = {false};
 
   // Common configuration parameters
   uint16_t sleep_cycle_{4095};
@@ -123,13 +122,6 @@ class ESP32TouchComponent : public Component {
   };
 
  protected:
-  // Design note: last_touch_time_ does not require synchronization primitives because:
-  // 1. ESP32 guarantees atomic 32-bit aligned reads/writes
-  // 2. ISR only writes timestamps, main loop only reads
-  // 3. Timing tolerance allows for occasional stale reads (50ms check interval)
-  // 4. Queue operations provide implicit memory barriers
-  // Using atomic/critical sections would add overhead without meaningful benefit
-  uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0};
   uint32_t iir_filter_{0};
 
   bool iir_filter_enabled_() const { return this->iir_filter_ > 0; }
@@ -147,9 +139,6 @@ class ESP32TouchComponent : public Component {
     uint32_t intr_mask;
   };
 
-  // Track last touch time for timeout-based release detection
-  uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0};
-
  protected:
   // Filter configuration
   touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX};
@@ -255,11 +244,22 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
 
   touch_pad_t touch_pad_{TOUCH_PAD_MAX};
   uint32_t threshold_{0};
+  uint32_t benchmark_{};
 #ifdef USE_ESP32_VARIANT_ESP32
   uint32_t value_{0};
 #endif
   bool last_state_{false};
   const uint32_t wakeup_threshold_{0};
+
+  // Track last touch time for timeout-based release detection
+  // Design note: last_touch_time_ does not require synchronization primitives because:
+  // 1. ESP32 guarantees atomic 32-bit aligned reads/writes
+  // 2. ISR only writes timestamps, main loop only reads
+  // 3. Timing tolerance allows for occasional stale reads (50ms check interval)
+  // 4. Queue operations provide implicit memory barriers
+  // Using atomic/critical sections would add overhead without meaningful benefit
+  uint32_t last_touch_time_{};
+  bool initial_state_published_{};
 };
 
 }  // namespace esp32_touch
diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp
index fd2cdfcbad..2d93de077e 100644
--- a/esphome/components/esp32_touch/esp32_touch_common.cpp
+++ b/esphome/components/esp32_touch/esp32_touch_common.cpp
@@ -22,16 +22,20 @@ void ESP32TouchComponent::dump_config_base_() {
                 "  Sleep cycle: %.2fms\n"
                 "  Low Voltage Reference: %s\n"
                 "  High Voltage Reference: %s\n"
-                "  Voltage Attenuation: %s",
+                "  Voltage Attenuation: %s\n"
+                "  Release Timeout: %" PRIu32 "ms\n",
                 this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s,
-                atten_s);
+                atten_s, this->release_timeout_ms_);
 }
 
 void ESP32TouchComponent::dump_config_sensors_() {
   for (auto *child : this->children_) {
     LOG_BINARY_SENSOR("  ", "Touch Pad", child);
-    ESP_LOGCONFIG(TAG, "    Pad: T%" PRIu32, (uint32_t) child->get_touch_pad());
-    ESP_LOGCONFIG(TAG, "    Threshold: %" PRIu32, child->get_threshold());
+    ESP_LOGCONFIG(TAG,
+                  "    Pad: T%u\n"
+                  "    Threshold: %" PRIu32 "\n"
+                  "    Benchmark: %" PRIu32,
+                  (unsigned) child->touch_pad_, child->threshold_, child->benchmark_);
   }
 }
 
@@ -112,12 +116,11 @@ bool ESP32TouchComponent::should_check_for_releases_(uint32_t now) {
 }
 
 void ESP32TouchComponent::publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now) {
-  touch_pad_t pad = child->get_touch_pad();
-  if (!this->initial_state_published_[pad]) {
+  if (!child->initial_state_published_) {
     // Check if enough time has passed since startup
     if (now > this->release_timeout_ms_) {
       child->publish_initial_state(false);
-      this->initial_state_published_[pad] = true;
+      child->initial_state_published_ = true;
       ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str());
     }
   }
diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp
index a6d499e9fa..6f05610ed6 100644
--- a/esphome/components/esp32_touch/esp32_touch_v1.cpp
+++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp
@@ -104,7 +104,7 @@ void ESP32TouchComponent::loop() {
 
       // Track when we last saw this pad as touched
       if (new_state) {
-        this->last_touch_time_[event.pad] = now;
+        child->last_touch_time_ = now;
       }
 
       // Only publish if state changed - this filters out repeated events
@@ -127,15 +127,13 @@ void ESP32TouchComponent::loop() {
 
   size_t pads_off = 0;
   for (auto *child : this->children_) {
-    touch_pad_t pad = child->get_touch_pad();
-
     // Handle initial state publication after startup
     this->publish_initial_state_if_needed_(child, now);
 
     if (child->last_state_) {
       // Pad is currently in touched state - check for release timeout
       // Using subtraction handles 32-bit rollover correctly
-      uint32_t time_diff = now - this->last_touch_time_[pad];
+      uint32_t time_diff = now - child->last_touch_time_;
 
       // Check if we haven't seen this pad recently
       if (time_diff > this->release_timeout_ms_) {
diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp
index ad77881724..afd2655fd7 100644
--- a/esphome/components/esp32_touch/esp32_touch_v2.cpp
+++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp
@@ -14,19 +14,16 @@ static const char *const TAG = "esp32_touch";
 void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched) {
   // Always update timer when touched
   if (is_touched) {
-    this->last_touch_time_[child->get_touch_pad()] = App.get_loop_component_start_time();
+    child->last_touch_time_ = App.get_loop_component_start_time();
   }
 
   if (child->last_state_ != is_touched) {
-    // Read value for logging
-    uint32_t value = this->read_touch_value(child->get_touch_pad());
-
     child->last_state_ = is_touched;
     child->publish_state(is_touched);
     if (is_touched) {
       // ESP32-S2/S3 v2: touched when value > threshold
       ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " > threshold: %" PRIu32 ")", child->get_name().c_str(),
-               value, child->get_threshold());
+               this->read_touch_value(child->touch_pad_), child->threshold_ + child->benchmark_);
     } else {
       ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", child->get_name().c_str());
     }
@@ -36,10 +33,13 @@ void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, boo
 // Helper to read touch value and update state for a given child (used for timeout events)
 bool ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *child) {
   // Read current touch value
-  uint32_t value = this->read_touch_value(child->get_touch_pad());
+  uint32_t value = this->read_touch_value(child->touch_pad_);
 
-  // ESP32-S2/S3 v2: Touch is detected when value > threshold
-  bool is_touched = value > child->get_threshold();
+  // ESP32-S2/S3 v2: Touch is detected when value > threshold + benchmark
+  ESP_LOGV(TAG,
+           "Checking touch state for '%s' (T%d): value = %" PRIu32 ", threshold = %" PRIu32 ", benchmark = %" PRIu32,
+           child->get_name().c_str(), child->touch_pad_, value, child->threshold_, child->benchmark_);
+  bool is_touched = value > child->benchmark_ + child->threshold_;
 
   this->update_touch_state_(child, is_touched);
   return is_touched;
@@ -61,9 +61,9 @@ void ESP32TouchComponent::setup() {
 
   // Configure each touch pad first
   for (auto *child : this->children_) {
-    esp_err_t config_err = touch_pad_config(child->get_touch_pad());
+    esp_err_t config_err = touch_pad_config(child->touch_pad_);
     if (config_err != ESP_OK) {
-      ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->get_touch_pad(), esp_err_to_name(config_err));
+      ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->touch_pad_, esp_err_to_name(config_err));
     }
   }
 
@@ -100,8 +100,8 @@ void ESP32TouchComponent::setup() {
 
   // Configure measurement parameters
   touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_);
-  // ESP32-S2/S3 always use the older API
-  touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_);
+  touch_pad_set_charge_discharge_times(this->meas_cycle_);
+  touch_pad_set_measurement_interval(this->sleep_cycle_);
 
   // Configure timeout if needed
   touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX);
@@ -118,8 +118,8 @@ void ESP32TouchComponent::setup() {
 
   // Set thresholds for each pad BEFORE starting FSM
   for (auto *child : this->children_) {
-    if (child->get_threshold() != 0) {
-      touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold());
+    if (child->threshold_ != 0) {
+      touch_pad_set_thresh(child->touch_pad_, child->threshold_);
     }
   }
 
@@ -277,6 +277,7 @@ void ESP32TouchComponent::loop() {
   // Process any queued touch events from interrupts
   TouchPadEventV2 event;
   while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) {
+    ESP_LOGD(TAG, "Event received, mask = 0x%" PRIx32 ", pad = %d", event.intr_mask, event.pad);
     // Handle timeout events
     if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) {
       // Resume measurement after timeout
@@ -289,18 +290,16 @@ void ESP32TouchComponent::loop() {
 
     // Find the child for the pad that triggered the interrupt
     for (auto *child : this->children_) {
-      if (child->get_touch_pad() != event.pad) {
-        continue;
+      if (child->touch_pad_ == event.pad) {
+        if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) {
+          // For timeout events, we need to read the value to determine state
+          this->check_and_update_touch_state_(child);
+        } else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) {
+          // We only get ACTIVE interrupts now, releases are detected by timeout
+          this->update_touch_state_(child, true);  // Always touched for ACTIVE interrupts
+        }
+        break;
       }
-
-      if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) {
-        // For timeout events, we need to read the value to determine state
-        this->check_and_update_touch_state_(child);
-      } else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) {
-        // We only get ACTIVE interrupts now, releases are detected by timeout
-        this->update_touch_state_(child, true);  // Always touched for ACTIVE interrupts
-      }
-      break;
     }
   }
 
@@ -311,15 +310,15 @@ void ESP32TouchComponent::loop() {
 
   size_t pads_off = 0;
   for (auto *child : this->children_) {
-    touch_pad_t pad = child->get_touch_pad();
-
+    if (child->benchmark_ == 0)
+      touch_pad_read_benchmark(child->touch_pad_, &child->benchmark_);
     // Handle initial state publication after startup
     this->publish_initial_state_if_needed_(child, now);
 
     if (child->last_state_) {
       // Pad is currently in touched state - check for release timeout
       // Using subtraction handles 32-bit rollover correctly
-      uint32_t time_diff = now - this->last_touch_time_[pad];
+      uint32_t time_diff = now - child->last_touch_time_;
 
       // Check if we haven't seen this pad recently
       if (time_diff > this->release_timeout_ms_) {

From b9391f2cd415172b3ac697fd55ae322094d41006 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= 
Date: Wed, 2 Jul 2025 23:15:37 +0200
Subject: [PATCH 33/35] [ds2484] New component (#9147)

---
 CODEOWNERS                                    |   1 +
 esphome/components/ds2484/__init__.py         |   1 +
 esphome/components/ds2484/ds2484.cpp          | 209 ++++++++++++++++++
 esphome/components/ds2484/ds2484.h            |  43 ++++
 esphome/components/ds2484/one_wire.py         |  37 ++++
 tests/components/ds2484/common.yaml           |  11 +
 tests/components/ds2484/test.esp32-ard.yaml   |   5 +
 .../components/ds2484/test.esp32-c3-ard.yaml  |   5 +
 .../components/ds2484/test.esp32-c3-idf.yaml  |   5 +
 tests/components/ds2484/test.esp32-idf.yaml   |   5 +
 tests/components/ds2484/test.esp8266-ard.yaml |   5 +
 tests/components/ds2484/test.rp2040-ard.yaml  |   5 +
 12 files changed, 332 insertions(+)
 create mode 100644 esphome/components/ds2484/__init__.py
 create mode 100644 esphome/components/ds2484/ds2484.cpp
 create mode 100644 esphome/components/ds2484/ds2484.h
 create mode 100644 esphome/components/ds2484/one_wire.py
 create mode 100644 tests/components/ds2484/common.yaml
 create mode 100644 tests/components/ds2484/test.esp32-ard.yaml
 create mode 100644 tests/components/ds2484/test.esp32-c3-ard.yaml
 create mode 100644 tests/components/ds2484/test.esp32-c3-idf.yaml
 create mode 100644 tests/components/ds2484/test.esp32-idf.yaml
 create mode 100644 tests/components/ds2484/test.esp8266-ard.yaml
 create mode 100644 tests/components/ds2484/test.rp2040-ard.yaml

diff --git a/CODEOWNERS b/CODEOWNERS
index 16f38da725..295dd9b1b2 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -124,6 +124,7 @@ esphome/components/dht/* @OttoWinter
 esphome/components/display_menu_base/* @numo68
 esphome/components/dps310/* @kbx81
 esphome/components/ds1307/* @badbadc0ffee
+esphome/components/ds2484/* @mrk-its
 esphome/components/dsmr/* @glmnet @zuidwijk
 esphome/components/duty_time/* @dudanov
 esphome/components/ee895/* @Stock-M
diff --git a/esphome/components/ds2484/__init__.py b/esphome/components/ds2484/__init__.py
new file mode 100644
index 0000000000..3d9f24ff19
--- /dev/null
+++ b/esphome/components/ds2484/__init__.py
@@ -0,0 +1 @@
+CODEOWNERS = ["@mrk-its"]
diff --git a/esphome/components/ds2484/ds2484.cpp b/esphome/components/ds2484/ds2484.cpp
new file mode 100644
index 0000000000..c3df9786b6
--- /dev/null
+++ b/esphome/components/ds2484/ds2484.cpp
@@ -0,0 +1,209 @@
+#include "ds2484.h"
+
+namespace esphome {
+namespace ds2484 {
+static const char *const TAG = "ds2484.onewire";
+
+void DS2484OneWireBus::setup() {
+  ESP_LOGCONFIG(TAG, "Running setup");
+  this->reset_device();
+  this->search();
+}
+
+void DS2484OneWireBus::dump_config() {
+  ESP_LOGCONFIG(TAG, "1-wire bus:");
+  this->dump_devices_(TAG);
+}
+
+bool DS2484OneWireBus::read_status_(uint8_t *status) {
+  for (uint8_t retry_nr = 0; retry_nr < 10; retry_nr++) {
+    if (this->read(status, 1) != i2c::ERROR_OK) {
+      ESP_LOGE(TAG, "read status error");
+      return false;
+    }
+    ESP_LOGVV(TAG, "status: %02x", *status);
+    if (!(*status & 1)) {
+      return true;
+    }
+  }
+  ESP_LOGE(TAG, "read status error: too many retries");
+  return false;
+}
+
+bool DS2484OneWireBus::wait_for_completion_() {
+  uint8_t status;
+  return this->read_status_(&status);
+}
+
+bool DS2484OneWireBus::reset_device() {
+  ESP_LOGVV(TAG, "reset_device");
+  uint8_t device_reset_cmd = 0xf0;
+  uint8_t response;
+  if (this->write(&device_reset_cmd, 1) != i2c::ERROR_OK) {
+    return false;
+  }
+  if (!this->wait_for_completion_()) {
+    ESP_LOGE(TAG, "reset_device: can't complete");
+    return false;
+  }
+  uint8_t config = (this->active_pullup_ ? 1 : 0) | (this->strong_pullup_ ? 4 : 0);
+  uint8_t write_config[2] = {0xd2, (uint8_t) (config | (~config << 4))};
+  if (this->write(write_config, 2) != i2c::ERROR_OK) {
+    ESP_LOGE(TAG, "reset_device: can't write config");
+    return false;
+  }
+  if (this->read(&response, 1) != i2c::ERROR_OK) {
+    ESP_LOGE(TAG, "can't read read8 response");
+    return false;
+  }
+  if (response != (write_config[1] & 0xf)) {
+    ESP_LOGE(TAG, "configuration didn't update");
+    return false;
+  }
+  return true;
+};
+
+int DS2484OneWireBus::reset_int() {
+  ESP_LOGVV(TAG, "reset");
+  uint8_t reset_cmd = 0xb4;
+  if (this->write(&reset_cmd, 1) != i2c::ERROR_OK) {
+    return -1;
+  }
+  return this->wait_for_completion_() ? 1 : 0;
+};
+
+void DS2484OneWireBus::write8_(uint8_t value) {
+  uint8_t buffer[2] = {0xa5, value};
+  this->write(buffer, 2);
+  this->wait_for_completion_();
+};
+
+void DS2484OneWireBus::write8(uint8_t value) {
+  ESP_LOGVV(TAG, "write8: %02x", value);
+  this->write8_(value);
+};
+
+void DS2484OneWireBus::write64(uint64_t value) {
+  ESP_LOGVV(TAG, "write64: %llx", value);
+  for (uint8_t i = 0; i < 8; i++) {
+    this->write8_((value >> (i * 8)) & 0xff);
+  }
+}
+
+uint8_t DS2484OneWireBus::read8() {
+  uint8_t read8_cmd = 0x96;
+  uint8_t set_read_reg_cmd[2] = {0xe1, 0xe1};
+  uint8_t response = 0;
+  if (this->write(&read8_cmd, 1) != i2c::ERROR_OK) {
+    ESP_LOGE(TAG, "can't write read8 cmd");
+    return 0;
+  }
+  this->wait_for_completion_();
+  if (this->write(set_read_reg_cmd, 2) != i2c::ERROR_OK) {
+    ESP_LOGE(TAG, "can't set read data reg");
+    return 0;
+  }
+  if (this->read(&response, 1) != i2c::ERROR_OK) {
+    ESP_LOGE(TAG, "can't read read8 response");
+    return 0;
+  }
+  return response;
+}
+
+uint64_t DS2484OneWireBus::read64() {
+  uint8_t response = 0;
+  for (uint8_t i = 0; i < 8; i++) {
+    response |= (this->read8() << (i * 8));
+  }
+  return response;
+}
+
+void DS2484OneWireBus::reset_search() {
+  this->last_discrepancy_ = 0;
+  this->last_device_flag_ = false;
+  this->address_ = 0;
+}
+
+bool DS2484OneWireBus::one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit) {
+  uint8_t buffer[2] = {(uint8_t) 0x78, (uint8_t) (*branch ? 0x80u : 0)};
+  uint8_t status;
+  if (!this->read_status_(&status)) {
+    ESP_LOGE(TAG, "one_wire_triple start: read status error");
+    return false;
+  }
+  if (this->write(buffer, 2) != i2c::ERROR_OK) {
+    ESP_LOGV(TAG, "one_wire_triple: can't write cmd");
+    return false;
+  }
+  if (!this->read_status_(&status)) {
+    ESP_LOGE(TAG, "one_wire_triple: read status error");
+    return false;
+  }
+  *id_bit = bool(status & 0x20);
+  *cmp_id_bit = bool(status & 0x40);
+  *branch = bool(status & 0x80);
+  return true;
+}
+
+uint64_t IRAM_ATTR DS2484OneWireBus::search_int() {
+  ESP_LOGVV(TAG, "search_int");
+  if (this->last_device_flag_) {
+    ESP_LOGVV(TAG, "last device flag set, quitting");
+    return 0u;
+  }
+
+  uint8_t last_zero = 0;
+  uint64_t bit_mask = 1;
+  uint64_t address = this->address_;
+
+  // Initiate search
+  for (uint8_t bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
+    bool branch;
+
+    // compute branch value for the case when there is a discrepancy
+    // (there are devices with both 0s and 1s at this bit)
+    if (bit_number < this->last_discrepancy_) {
+      branch = (address & bit_mask) > 0;
+    } else {
+      branch = bit_number == this->last_discrepancy_;
+    }
+
+    bool id_bit, cmp_id_bit;
+    bool branch_before = branch;
+    if (!this->one_wire_triple_(&branch, &id_bit, &cmp_id_bit)) {
+      ESP_LOGW(TAG, "one wire triple error, quitting");
+      return 0;
+    }
+
+    if (id_bit && cmp_id_bit) {
+      ESP_LOGW(TAG, "no devices on the bus, quitting");
+      // No devices participating in search
+      return 0;
+    }
+
+    if (!id_bit && !cmp_id_bit && !branch) {
+      last_zero = bit_number;
+    }
+
+    ESP_LOGVV(TAG, "%d %d branch: %d %d", id_bit, cmp_id_bit, branch_before, branch);
+
+    if (branch) {
+      address |= bit_mask;
+    } else {
+      address &= ~bit_mask;
+    }
+  }
+  ESP_LOGVV(TAG, "last_discepancy: %d", last_zero);
+  ESP_LOGVV(TAG, "address: %llx", address);
+  this->last_discrepancy_ = last_zero;
+  if (this->last_discrepancy_ == 0) {
+    // we're at root and have no choices left, so this was the last one.
+    this->last_device_flag_ = true;
+  }
+
+  this->address_ = address;
+  return address;
+}
+
+}  // namespace ds2484
+}  // namespace esphome
diff --git a/esphome/components/ds2484/ds2484.h b/esphome/components/ds2484/ds2484.h
new file mode 100644
index 0000000000..223227c0a2
--- /dev/null
+++ b/esphome/components/ds2484/ds2484.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/hal.h"
+#include "esphome/core/preferences.h"
+#include "esphome/components/i2c/i2c.h"
+#include "esphome/components/one_wire/one_wire.h"
+
+namespace esphome {
+namespace ds2484 {
+
+class DS2484OneWireBus : public one_wire::OneWireBus, public i2c::I2CDevice, public Component {
+ public:
+  void setup() override;
+  void dump_config() override;
+  float get_setup_priority() const override { return setup_priority::BUS - 1.0; }
+
+  bool reset_device();
+  int reset_int() override;
+  void write8(uint8_t) override;
+  void write64(uint64_t) override;
+  uint8_t read8() override;
+  uint64_t read64() override;
+
+  void set_active_pullup(bool value) { this->active_pullup_ = value; }
+  void set_strong_pullup(bool value) { this->strong_pullup_ = value; }
+
+ protected:
+  void reset_search() override;
+  uint64_t search_int() override;
+  bool read_status_(uint8_t *);
+  bool wait_for_completion_();
+  void write8_(uint8_t);
+  bool one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit);
+
+  uint64_t address_;
+  uint8_t last_discrepancy_{0};
+  bool last_device_flag_{false};
+  bool active_pullup_{false};
+  bool strong_pullup_{false};
+};
+}  // namespace ds2484
+}  // namespace esphome
diff --git a/esphome/components/ds2484/one_wire.py b/esphome/components/ds2484/one_wire.py
new file mode 100644
index 0000000000..384b2d01e6
--- /dev/null
+++ b/esphome/components/ds2484/one_wire.py
@@ -0,0 +1,37 @@
+import esphome.codegen as cg
+from esphome.components import i2c
+from esphome.components.one_wire import OneWireBus
+import esphome.config_validation as cv
+from esphome.const import CONF_ID
+
+ds2484_ns = cg.esphome_ns.namespace("ds2484")
+
+CONF_ACTIVE_PULLUP = "active_pullup"
+CONF_STRONG_PULLUP = "strong_pullup"
+
+CODEOWNERS = ["@mrk-its"]
+DEPENDENCIES = ["i2c"]
+
+DS2484OneWireBus = ds2484_ns.class_(
+    "DS2484OneWireBus", OneWireBus, i2c.I2CDevice, cg.Component
+)
+
+CONFIG_SCHEMA = (
+    cv.Schema(
+        {
+            cv.GenerateID(): cv.declare_id(DS2484OneWireBus),
+            cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
+            cv.Optional(CONF_STRONG_PULLUP, default=False): cv.boolean,
+        }
+    )
+    .extend(cv.COMPONENT_SCHEMA)
+    .extend(i2c.i2c_device_schema(0x18))
+)
+
+
+async def to_code(config):
+    var = cg.new_Pvariable(config[CONF_ID])
+    await i2c.register_i2c_device(var, config)
+    await cg.register_component(var, config)
+    cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
+    cg.add(var.set_strong_pullup(config[CONF_STRONG_PULLUP]))
diff --git a/tests/components/ds2484/common.yaml b/tests/components/ds2484/common.yaml
new file mode 100644
index 0000000000..9d2882a3c0
--- /dev/null
+++ b/tests/components/ds2484/common.yaml
@@ -0,0 +1,11 @@
+i2c:
+  - id: i2c_ds2484
+    scl: ${scl_pin}
+    sda: ${sda_pin}
+
+one_wire:
+  platform: ds2484
+  i2c_id: i2c_ds2484
+  address: 0x18
+  active_pullup: true
+  strong_pullup: false
diff --git a/tests/components/ds2484/test.esp32-ard.yaml b/tests/components/ds2484/test.esp32-ard.yaml
new file mode 100644
index 0000000000..63c3bd6afd
--- /dev/null
+++ b/tests/components/ds2484/test.esp32-ard.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO16
+  sda_pin: GPIO17
+
+<<: !include common.yaml
diff --git a/tests/components/ds2484/test.esp32-c3-ard.yaml b/tests/components/ds2484/test.esp32-c3-ard.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/ds2484/test.esp32-c3-ard.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml
diff --git a/tests/components/ds2484/test.esp32-c3-idf.yaml b/tests/components/ds2484/test.esp32-c3-idf.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/ds2484/test.esp32-c3-idf.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml
diff --git a/tests/components/ds2484/test.esp32-idf.yaml b/tests/components/ds2484/test.esp32-idf.yaml
new file mode 100644
index 0000000000..63c3bd6afd
--- /dev/null
+++ b/tests/components/ds2484/test.esp32-idf.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO16
+  sda_pin: GPIO17
+
+<<: !include common.yaml
diff --git a/tests/components/ds2484/test.esp8266-ard.yaml b/tests/components/ds2484/test.esp8266-ard.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/ds2484/test.esp8266-ard.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml
diff --git a/tests/components/ds2484/test.rp2040-ard.yaml b/tests/components/ds2484/test.rp2040-ard.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/ds2484/test.rp2040-ard.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml

From 4ef5c941c935a9e3248203ad3a03730818440cdf Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" 
Date: Wed, 2 Jul 2025 16:39:20 -0500
Subject: [PATCH 34/35] Fix missing ifdef guards in API protobuf generator
 (#9296)

---
 esphome/components/api/api_pb2.cpp       | 4483 +---------------------
 esphome/components/api/api_pb2.h         |   82 +
 esphome/components/api/api_pb2_dump.cpp  | 4228 ++++++++++++++++++++
 esphome/components/api/api_pb2_service.h |    3 +-
 script/api_protobuf/api_protobuf.py      |  359 +-
 5 files changed, 4616 insertions(+), 4539 deletions(-)
 create mode 100644 esphome/components/api/api_pb2_dump.cpp

diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp
index 7d16e43ce6..01140fbfc8 100644
--- a/esphome/components/api/api_pb2.cpp
+++ b/esphome/components/api/api_pb2.cpp
@@ -5,633 +5,9 @@
 #include "esphome/core/log.h"
 #include "esphome/core/helpers.h"
 
-#include 
-
 namespace esphome {
 namespace api {
 
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::EntityCategory value) {
-  switch (value) {
-    case enums::ENTITY_CATEGORY_NONE:
-      return "ENTITY_CATEGORY_NONE";
-    case enums::ENTITY_CATEGORY_CONFIG:
-      return "ENTITY_CATEGORY_CONFIG";
-    case enums::ENTITY_CATEGORY_DIAGNOSTIC:
-      return "ENTITY_CATEGORY_DIAGNOSTIC";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::LegacyCoverState value) {
-  switch (value) {
-    case enums::LEGACY_COVER_STATE_OPEN:
-      return "LEGACY_COVER_STATE_OPEN";
-    case enums::LEGACY_COVER_STATE_CLOSED:
-      return "LEGACY_COVER_STATE_CLOSED";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::CoverOperation value) {
-  switch (value) {
-    case enums::COVER_OPERATION_IDLE:
-      return "COVER_OPERATION_IDLE";
-    case enums::COVER_OPERATION_IS_OPENING:
-      return "COVER_OPERATION_IS_OPENING";
-    case enums::COVER_OPERATION_IS_CLOSING:
-      return "COVER_OPERATION_IS_CLOSING";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::LegacyCoverCommand value) {
-  switch (value) {
-    case enums::LEGACY_COVER_COMMAND_OPEN:
-      return "LEGACY_COVER_COMMAND_OPEN";
-    case enums::LEGACY_COVER_COMMAND_CLOSE:
-      return "LEGACY_COVER_COMMAND_CLOSE";
-    case enums::LEGACY_COVER_COMMAND_STOP:
-      return "LEGACY_COVER_COMMAND_STOP";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::FanSpeed value) {
-  switch (value) {
-    case enums::FAN_SPEED_LOW:
-      return "FAN_SPEED_LOW";
-    case enums::FAN_SPEED_MEDIUM:
-      return "FAN_SPEED_MEDIUM";
-    case enums::FAN_SPEED_HIGH:
-      return "FAN_SPEED_HIGH";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::FanDirection value) {
-  switch (value) {
-    case enums::FAN_DIRECTION_FORWARD:
-      return "FAN_DIRECTION_FORWARD";
-    case enums::FAN_DIRECTION_REVERSE:
-      return "FAN_DIRECTION_REVERSE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ColorMode value) {
-  switch (value) {
-    case enums::COLOR_MODE_UNKNOWN:
-      return "COLOR_MODE_UNKNOWN";
-    case enums::COLOR_MODE_ON_OFF:
-      return "COLOR_MODE_ON_OFF";
-    case enums::COLOR_MODE_LEGACY_BRIGHTNESS:
-      return "COLOR_MODE_LEGACY_BRIGHTNESS";
-    case enums::COLOR_MODE_BRIGHTNESS:
-      return "COLOR_MODE_BRIGHTNESS";
-    case enums::COLOR_MODE_WHITE:
-      return "COLOR_MODE_WHITE";
-    case enums::COLOR_MODE_COLOR_TEMPERATURE:
-      return "COLOR_MODE_COLOR_TEMPERATURE";
-    case enums::COLOR_MODE_COLD_WARM_WHITE:
-      return "COLOR_MODE_COLD_WARM_WHITE";
-    case enums::COLOR_MODE_RGB:
-      return "COLOR_MODE_RGB";
-    case enums::COLOR_MODE_RGB_WHITE:
-      return "COLOR_MODE_RGB_WHITE";
-    case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE:
-      return "COLOR_MODE_RGB_COLOR_TEMPERATURE";
-    case enums::COLOR_MODE_RGB_COLD_WARM_WHITE:
-      return "COLOR_MODE_RGB_COLD_WARM_WHITE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::SensorStateClass value) {
-  switch (value) {
-    case enums::STATE_CLASS_NONE:
-      return "STATE_CLASS_NONE";
-    case enums::STATE_CLASS_MEASUREMENT:
-      return "STATE_CLASS_MEASUREMENT";
-    case enums::STATE_CLASS_TOTAL_INCREASING:
-      return "STATE_CLASS_TOTAL_INCREASING";
-    case enums::STATE_CLASS_TOTAL:
-      return "STATE_CLASS_TOTAL";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::SensorLastResetType value) {
-  switch (value) {
-    case enums::LAST_RESET_NONE:
-      return "LAST_RESET_NONE";
-    case enums::LAST_RESET_NEVER:
-      return "LAST_RESET_NEVER";
-    case enums::LAST_RESET_AUTO:
-      return "LAST_RESET_AUTO";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::LogLevel value) {
-  switch (value) {
-    case enums::LOG_LEVEL_NONE:
-      return "LOG_LEVEL_NONE";
-    case enums::LOG_LEVEL_ERROR:
-      return "LOG_LEVEL_ERROR";
-    case enums::LOG_LEVEL_WARN:
-      return "LOG_LEVEL_WARN";
-    case enums::LOG_LEVEL_INFO:
-      return "LOG_LEVEL_INFO";
-    case enums::LOG_LEVEL_CONFIG:
-      return "LOG_LEVEL_CONFIG";
-    case enums::LOG_LEVEL_DEBUG:
-      return "LOG_LEVEL_DEBUG";
-    case enums::LOG_LEVEL_VERBOSE:
-      return "LOG_LEVEL_VERBOSE";
-    case enums::LOG_LEVEL_VERY_VERBOSE:
-      return "LOG_LEVEL_VERY_VERBOSE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ServiceArgType value) {
-  switch (value) {
-    case enums::SERVICE_ARG_TYPE_BOOL:
-      return "SERVICE_ARG_TYPE_BOOL";
-    case enums::SERVICE_ARG_TYPE_INT:
-      return "SERVICE_ARG_TYPE_INT";
-    case enums::SERVICE_ARG_TYPE_FLOAT:
-      return "SERVICE_ARG_TYPE_FLOAT";
-    case enums::SERVICE_ARG_TYPE_STRING:
-      return "SERVICE_ARG_TYPE_STRING";
-    case enums::SERVICE_ARG_TYPE_BOOL_ARRAY:
-      return "SERVICE_ARG_TYPE_BOOL_ARRAY";
-    case enums::SERVICE_ARG_TYPE_INT_ARRAY:
-      return "SERVICE_ARG_TYPE_INT_ARRAY";
-    case enums::SERVICE_ARG_TYPE_FLOAT_ARRAY:
-      return "SERVICE_ARG_TYPE_FLOAT_ARRAY";
-    case enums::SERVICE_ARG_TYPE_STRING_ARRAY:
-      return "SERVICE_ARG_TYPE_STRING_ARRAY";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ClimateMode value) {
-  switch (value) {
-    case enums::CLIMATE_MODE_OFF:
-      return "CLIMATE_MODE_OFF";
-    case enums::CLIMATE_MODE_HEAT_COOL:
-      return "CLIMATE_MODE_HEAT_COOL";
-    case enums::CLIMATE_MODE_COOL:
-      return "CLIMATE_MODE_COOL";
-    case enums::CLIMATE_MODE_HEAT:
-      return "CLIMATE_MODE_HEAT";
-    case enums::CLIMATE_MODE_FAN_ONLY:
-      return "CLIMATE_MODE_FAN_ONLY";
-    case enums::CLIMATE_MODE_DRY:
-      return "CLIMATE_MODE_DRY";
-    case enums::CLIMATE_MODE_AUTO:
-      return "CLIMATE_MODE_AUTO";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ClimateFanMode value) {
-  switch (value) {
-    case enums::CLIMATE_FAN_ON:
-      return "CLIMATE_FAN_ON";
-    case enums::CLIMATE_FAN_OFF:
-      return "CLIMATE_FAN_OFF";
-    case enums::CLIMATE_FAN_AUTO:
-      return "CLIMATE_FAN_AUTO";
-    case enums::CLIMATE_FAN_LOW:
-      return "CLIMATE_FAN_LOW";
-    case enums::CLIMATE_FAN_MEDIUM:
-      return "CLIMATE_FAN_MEDIUM";
-    case enums::CLIMATE_FAN_HIGH:
-      return "CLIMATE_FAN_HIGH";
-    case enums::CLIMATE_FAN_MIDDLE:
-      return "CLIMATE_FAN_MIDDLE";
-    case enums::CLIMATE_FAN_FOCUS:
-      return "CLIMATE_FAN_FOCUS";
-    case enums::CLIMATE_FAN_DIFFUSE:
-      return "CLIMATE_FAN_DIFFUSE";
-    case enums::CLIMATE_FAN_QUIET:
-      return "CLIMATE_FAN_QUIET";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ClimateSwingMode value) {
-  switch (value) {
-    case enums::CLIMATE_SWING_OFF:
-      return "CLIMATE_SWING_OFF";
-    case enums::CLIMATE_SWING_BOTH:
-      return "CLIMATE_SWING_BOTH";
-    case enums::CLIMATE_SWING_VERTICAL:
-      return "CLIMATE_SWING_VERTICAL";
-    case enums::CLIMATE_SWING_HORIZONTAL:
-      return "CLIMATE_SWING_HORIZONTAL";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ClimateAction value) {
-  switch (value) {
-    case enums::CLIMATE_ACTION_OFF:
-      return "CLIMATE_ACTION_OFF";
-    case enums::CLIMATE_ACTION_COOLING:
-      return "CLIMATE_ACTION_COOLING";
-    case enums::CLIMATE_ACTION_HEATING:
-      return "CLIMATE_ACTION_HEATING";
-    case enums::CLIMATE_ACTION_IDLE:
-      return "CLIMATE_ACTION_IDLE";
-    case enums::CLIMATE_ACTION_DRYING:
-      return "CLIMATE_ACTION_DRYING";
-    case enums::CLIMATE_ACTION_FAN:
-      return "CLIMATE_ACTION_FAN";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ClimatePreset value) {
-  switch (value) {
-    case enums::CLIMATE_PRESET_NONE:
-      return "CLIMATE_PRESET_NONE";
-    case enums::CLIMATE_PRESET_HOME:
-      return "CLIMATE_PRESET_HOME";
-    case enums::CLIMATE_PRESET_AWAY:
-      return "CLIMATE_PRESET_AWAY";
-    case enums::CLIMATE_PRESET_BOOST:
-      return "CLIMATE_PRESET_BOOST";
-    case enums::CLIMATE_PRESET_COMFORT:
-      return "CLIMATE_PRESET_COMFORT";
-    case enums::CLIMATE_PRESET_ECO:
-      return "CLIMATE_PRESET_ECO";
-    case enums::CLIMATE_PRESET_SLEEP:
-      return "CLIMATE_PRESET_SLEEP";
-    case enums::CLIMATE_PRESET_ACTIVITY:
-      return "CLIMATE_PRESET_ACTIVITY";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::NumberMode value) {
-  switch (value) {
-    case enums::NUMBER_MODE_AUTO:
-      return "NUMBER_MODE_AUTO";
-    case enums::NUMBER_MODE_BOX:
-      return "NUMBER_MODE_BOX";
-    case enums::NUMBER_MODE_SLIDER:
-      return "NUMBER_MODE_SLIDER";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::LockState value) {
-  switch (value) {
-    case enums::LOCK_STATE_NONE:
-      return "LOCK_STATE_NONE";
-    case enums::LOCK_STATE_LOCKED:
-      return "LOCK_STATE_LOCKED";
-    case enums::LOCK_STATE_UNLOCKED:
-      return "LOCK_STATE_UNLOCKED";
-    case enums::LOCK_STATE_JAMMED:
-      return "LOCK_STATE_JAMMED";
-    case enums::LOCK_STATE_LOCKING:
-      return "LOCK_STATE_LOCKING";
-    case enums::LOCK_STATE_UNLOCKING:
-      return "LOCK_STATE_UNLOCKING";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::LockCommand value) {
-  switch (value) {
-    case enums::LOCK_UNLOCK:
-      return "LOCK_UNLOCK";
-    case enums::LOCK_LOCK:
-      return "LOCK_LOCK";
-    case enums::LOCK_OPEN:
-      return "LOCK_OPEN";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::MediaPlayerState value) {
-  switch (value) {
-    case enums::MEDIA_PLAYER_STATE_NONE:
-      return "MEDIA_PLAYER_STATE_NONE";
-    case enums::MEDIA_PLAYER_STATE_IDLE:
-      return "MEDIA_PLAYER_STATE_IDLE";
-    case enums::MEDIA_PLAYER_STATE_PLAYING:
-      return "MEDIA_PLAYER_STATE_PLAYING";
-    case enums::MEDIA_PLAYER_STATE_PAUSED:
-      return "MEDIA_PLAYER_STATE_PAUSED";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::MediaPlayerCommand value) {
-  switch (value) {
-    case enums::MEDIA_PLAYER_COMMAND_PLAY:
-      return "MEDIA_PLAYER_COMMAND_PLAY";
-    case enums::MEDIA_PLAYER_COMMAND_PAUSE:
-      return "MEDIA_PLAYER_COMMAND_PAUSE";
-    case enums::MEDIA_PLAYER_COMMAND_STOP:
-      return "MEDIA_PLAYER_COMMAND_STOP";
-    case enums::MEDIA_PLAYER_COMMAND_MUTE:
-      return "MEDIA_PLAYER_COMMAND_MUTE";
-    case enums::MEDIA_PLAYER_COMMAND_UNMUTE:
-      return "MEDIA_PLAYER_COMMAND_UNMUTE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::MediaPlayerFormatPurpose value) {
-  switch (value) {
-    case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT:
-      return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT";
-    case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT:
-      return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<>
-const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) {
-  switch (value) {
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
-    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
-      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::BluetoothScannerState value) {
-  switch (value) {
-    case enums::BLUETOOTH_SCANNER_STATE_IDLE:
-      return "BLUETOOTH_SCANNER_STATE_IDLE";
-    case enums::BLUETOOTH_SCANNER_STATE_STARTING:
-      return "BLUETOOTH_SCANNER_STATE_STARTING";
-    case enums::BLUETOOTH_SCANNER_STATE_RUNNING:
-      return "BLUETOOTH_SCANNER_STATE_RUNNING";
-    case enums::BLUETOOTH_SCANNER_STATE_FAILED:
-      return "BLUETOOTH_SCANNER_STATE_FAILED";
-    case enums::BLUETOOTH_SCANNER_STATE_STOPPING:
-      return "BLUETOOTH_SCANNER_STATE_STOPPING";
-    case enums::BLUETOOTH_SCANNER_STATE_STOPPED:
-      return "BLUETOOTH_SCANNER_STATE_STOPPED";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::BluetoothScannerMode value) {
-  switch (value) {
-    case enums::BLUETOOTH_SCANNER_MODE_PASSIVE:
-      return "BLUETOOTH_SCANNER_MODE_PASSIVE";
-    case enums::BLUETOOTH_SCANNER_MODE_ACTIVE:
-      return "BLUETOOTH_SCANNER_MODE_ACTIVE";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<>
-const char *proto_enum_to_string(enums::VoiceAssistantSubscribeFlag value) {
-  switch (value) {
-    case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE:
-      return "VOICE_ASSISTANT_SUBSCRIBE_NONE";
-    case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO:
-      return "VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::VoiceAssistantRequestFlag value) {
-  switch (value) {
-    case enums::VOICE_ASSISTANT_REQUEST_NONE:
-      return "VOICE_ASSISTANT_REQUEST_NONE";
-    case enums::VOICE_ASSISTANT_REQUEST_USE_VAD:
-      return "VOICE_ASSISTANT_REQUEST_USE_VAD";
-    case enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD:
-      return "VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::VoiceAssistantEvent value) {
-  switch (value) {
-    case enums::VOICE_ASSISTANT_ERROR:
-      return "VOICE_ASSISTANT_ERROR";
-    case enums::VOICE_ASSISTANT_RUN_START:
-      return "VOICE_ASSISTANT_RUN_START";
-    case enums::VOICE_ASSISTANT_RUN_END:
-      return "VOICE_ASSISTANT_RUN_END";
-    case enums::VOICE_ASSISTANT_STT_START:
-      return "VOICE_ASSISTANT_STT_START";
-    case enums::VOICE_ASSISTANT_STT_END:
-      return "VOICE_ASSISTANT_STT_END";
-    case enums::VOICE_ASSISTANT_INTENT_START:
-      return "VOICE_ASSISTANT_INTENT_START";
-    case enums::VOICE_ASSISTANT_INTENT_END:
-      return "VOICE_ASSISTANT_INTENT_END";
-    case enums::VOICE_ASSISTANT_TTS_START:
-      return "VOICE_ASSISTANT_TTS_START";
-    case enums::VOICE_ASSISTANT_TTS_END:
-      return "VOICE_ASSISTANT_TTS_END";
-    case enums::VOICE_ASSISTANT_WAKE_WORD_START:
-      return "VOICE_ASSISTANT_WAKE_WORD_START";
-    case enums::VOICE_ASSISTANT_WAKE_WORD_END:
-      return "VOICE_ASSISTANT_WAKE_WORD_END";
-    case enums::VOICE_ASSISTANT_STT_VAD_START:
-      return "VOICE_ASSISTANT_STT_VAD_START";
-    case enums::VOICE_ASSISTANT_STT_VAD_END:
-      return "VOICE_ASSISTANT_STT_VAD_END";
-    case enums::VOICE_ASSISTANT_TTS_STREAM_START:
-      return "VOICE_ASSISTANT_TTS_STREAM_START";
-    case enums::VOICE_ASSISTANT_TTS_STREAM_END:
-      return "VOICE_ASSISTANT_TTS_STREAM_END";
-    case enums::VOICE_ASSISTANT_INTENT_PROGRESS:
-      return "VOICE_ASSISTANT_INTENT_PROGRESS";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::VoiceAssistantTimerEvent value) {
-  switch (value) {
-    case enums::VOICE_ASSISTANT_TIMER_STARTED:
-      return "VOICE_ASSISTANT_TIMER_STARTED";
-    case enums::VOICE_ASSISTANT_TIMER_UPDATED:
-      return "VOICE_ASSISTANT_TIMER_UPDATED";
-    case enums::VOICE_ASSISTANT_TIMER_CANCELLED:
-      return "VOICE_ASSISTANT_TIMER_CANCELLED";
-    case enums::VOICE_ASSISTANT_TIMER_FINISHED:
-      return "VOICE_ASSISTANT_TIMER_FINISHED";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::AlarmControlPanelState value) {
-  switch (value) {
-    case enums::ALARM_STATE_DISARMED:
-      return "ALARM_STATE_DISARMED";
-    case enums::ALARM_STATE_ARMED_HOME:
-      return "ALARM_STATE_ARMED_HOME";
-    case enums::ALARM_STATE_ARMED_AWAY:
-      return "ALARM_STATE_ARMED_AWAY";
-    case enums::ALARM_STATE_ARMED_NIGHT:
-      return "ALARM_STATE_ARMED_NIGHT";
-    case enums::ALARM_STATE_ARMED_VACATION:
-      return "ALARM_STATE_ARMED_VACATION";
-    case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
-      return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
-    case enums::ALARM_STATE_PENDING:
-      return "ALARM_STATE_PENDING";
-    case enums::ALARM_STATE_ARMING:
-      return "ALARM_STATE_ARMING";
-    case enums::ALARM_STATE_DISARMING:
-      return "ALARM_STATE_DISARMING";
-    case enums::ALARM_STATE_TRIGGERED:
-      return "ALARM_STATE_TRIGGERED";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<>
-const char *proto_enum_to_string(enums::AlarmControlPanelStateCommand value) {
-  switch (value) {
-    case enums::ALARM_CONTROL_PANEL_DISARM:
-      return "ALARM_CONTROL_PANEL_DISARM";
-    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
-      return "ALARM_CONTROL_PANEL_ARM_AWAY";
-    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
-      return "ALARM_CONTROL_PANEL_ARM_HOME";
-    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
-      return "ALARM_CONTROL_PANEL_ARM_NIGHT";
-    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
-      return "ALARM_CONTROL_PANEL_ARM_VACATION";
-    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
-      return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
-    case enums::ALARM_CONTROL_PANEL_TRIGGER:
-      return "ALARM_CONTROL_PANEL_TRIGGER";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::TextMode value) {
-  switch (value) {
-    case enums::TEXT_MODE_TEXT:
-      return "TEXT_MODE_TEXT";
-    case enums::TEXT_MODE_PASSWORD:
-      return "TEXT_MODE_PASSWORD";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::ValveOperation value) {
-  switch (value) {
-    case enums::VALVE_OPERATION_IDLE:
-      return "VALVE_OPERATION_IDLE";
-    case enums::VALVE_OPERATION_IS_OPENING:
-      return "VALVE_OPERATION_IS_OPENING";
-    case enums::VALVE_OPERATION_IS_CLOSING:
-      return "VALVE_OPERATION_IS_CLOSING";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-template<> const char *proto_enum_to_string(enums::UpdateCommand value) {
-  switch (value) {
-    case enums::UPDATE_COMMAND_NONE:
-      return "UPDATE_COMMAND_NONE";
-    case enums::UPDATE_COMMAND_UPDATE:
-      return "UPDATE_COMMAND_UPDATE";
-    case enums::UPDATE_COMMAND_CHECK:
-      return "UPDATE_COMMAND_CHECK";
-    default:
-      return "UNKNOWN";
-  }
-}
-#endif
-
 bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -666,26 +42,6 @@ void HelloRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->api_version_major, false);
   ProtoSize::add_uint32_field(total_size, 1, this->api_version_minor, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void HelloRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("HelloRequest {\n");
-  out.append("  client_info: ");
-  out.append("'").append(this->client_info).append("'");
-  out.append("\n");
-
-  out.append("  api_version_major: ");
-  sprintf(buffer, "%" PRIu32, this->api_version_major);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  api_version_minor: ");
-  sprintf(buffer, "%" PRIu32, this->api_version_minor);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool HelloResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -726,30 +82,6 @@ void HelloResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->server_info, false);
   ProtoSize::add_string_field(total_size, 1, this->name, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void HelloResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("HelloResponse {\n");
-  out.append("  api_version_major: ");
-  sprintf(buffer, "%" PRIu32, this->api_version_major);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  api_version_minor: ");
-  sprintf(buffer, "%" PRIu32, this->api_version_minor);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  server_info: ");
-  out.append("'").append(this->server_info).append("'");
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -764,16 +96,6 @@ void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_strin
 void ConnectRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->password, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ConnectRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ConnectRequest {\n");
-  out.append("  password: ");
-  out.append("'").append(this->password).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -788,31 +110,6 @@ void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool
 void ConnectResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->invalid_password, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ConnectResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ConnectResponse {\n");
-  out.append("  invalid_password: ");
-  out.append(YESNO(this->invalid_password));
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
-#endif
 bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -841,21 +138,6 @@ void AreaInfo::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
   ProtoSize::add_string_field(total_size, 1, this->name, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void AreaInfo::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("AreaInfo {\n");
-  out.append("  area_id: ");
-  sprintf(buffer, "%" PRIu32, this->area_id);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -890,26 +172,6 @@ void DeviceInfo::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->name, false);
   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DeviceInfo::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DeviceInfo {\n");
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  area_id: ");
-  sprintf(buffer, "%" PRIu32, this->area_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -1062,118 +324,7 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_repeated_message(total_size, 2, this->areas);
   ProtoSize::add_message_object(total_size, 2, this->area, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DeviceInfoResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DeviceInfoResponse {\n");
-  out.append("  uses_password: ");
-  out.append(YESNO(this->uses_password));
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  mac_address: ");
-  out.append("'").append(this->mac_address).append("'");
-  out.append("\n");
-
-  out.append("  esphome_version: ");
-  out.append("'").append(this->esphome_version).append("'");
-  out.append("\n");
-
-  out.append("  compilation_time: ");
-  out.append("'").append(this->compilation_time).append("'");
-  out.append("\n");
-
-  out.append("  model: ");
-  out.append("'").append(this->model).append("'");
-  out.append("\n");
-
-  out.append("  has_deep_sleep: ");
-  out.append(YESNO(this->has_deep_sleep));
-  out.append("\n");
-
-  out.append("  project_name: ");
-  out.append("'").append(this->project_name).append("'");
-  out.append("\n");
-
-  out.append("  project_version: ");
-  out.append("'").append(this->project_version).append("'");
-  out.append("\n");
-
-  out.append("  webserver_port: ");
-  sprintf(buffer, "%" PRIu32, this->webserver_port);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  legacy_bluetooth_proxy_version: ");
-  sprintf(buffer, "%" PRIu32, this->legacy_bluetooth_proxy_version);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  bluetooth_proxy_feature_flags: ");
-  sprintf(buffer, "%" PRIu32, this->bluetooth_proxy_feature_flags);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  manufacturer: ");
-  out.append("'").append(this->manufacturer).append("'");
-  out.append("\n");
-
-  out.append("  friendly_name: ");
-  out.append("'").append(this->friendly_name).append("'");
-  out.append("\n");
-
-  out.append("  legacy_voice_assistant_version: ");
-  sprintf(buffer, "%" PRIu32, this->legacy_voice_assistant_version);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  voice_assistant_feature_flags: ");
-  sprintf(buffer, "%" PRIu32, this->voice_assistant_feature_flags);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  suggested_area: ");
-  out.append("'").append(this->suggested_area).append("'");
-  out.append("\n");
-
-  out.append("  bluetooth_mac_address: ");
-  out.append("'").append(this->bluetooth_mac_address).append("'");
-  out.append("\n");
-
-  out.append("  api_encryption_supported: ");
-  out.append(YESNO(this->api_encryption_supported));
-  out.append("\n");
-
-  for (const auto &it : this->devices) {
-    out.append("  devices: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->areas) {
-    out.append("  areas: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  out.append("  area: ");
-  this->area.dump_to(out);
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); }
-#endif
+#ifdef USE_BINARY_SENSOR
 bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -1256,54 +407,6 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesBinarySensorResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  is_status_binary_sensor: ");
-  out.append(YESNO(this->is_status_binary_sensor));
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -1338,25 +441,8 @@ void BinarySensorStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->state, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BinarySensorStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BinarySensorStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_COVER
 bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 5: {
@@ -1457,66 +543,6 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesCoverResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesCoverResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  assumed_state: ");
-  out.append(YESNO(this->assumed_state));
-  out.append("\n");
-
-  out.append("  supports_position: ");
-  out.append(YESNO(this->supports_position));
-  out.append("\n");
-
-  out.append("  supports_tilt: ");
-  out.append(YESNO(this->supports_tilt));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  supports_stop: ");
-  out.append(YESNO(this->supports_stop));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -1563,35 +589,6 @@ void CoverStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void CoverStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("CoverStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  legacy_state: ");
-  out.append(proto_enum_to_string(this->legacy_state));
-  out.append("\n");
-
-  out.append("  position: ");
-  sprintf(buffer, "%g", this->position);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  tilt: ");
-  sprintf(buffer, "%g", this->tilt);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  current_operation: ");
-  out.append(proto_enum_to_string(this->current_operation));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -1656,47 +653,8 @@ void CoverCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f, false);
   ProtoSize::add_bool_field(total_size, 1, this->stop, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void CoverCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("CoverCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_legacy_command: ");
-  out.append(YESNO(this->has_legacy_command));
-  out.append("\n");
-
-  out.append("  legacy_command: ");
-  out.append(proto_enum_to_string(this->legacy_command));
-  out.append("\n");
-
-  out.append("  has_position: ");
-  out.append(YESNO(this->has_position));
-  out.append("\n");
-
-  out.append("  position: ");
-  sprintf(buffer, "%g", this->position);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_tilt: ");
-  out.append(YESNO(this->has_tilt));
-  out.append("\n");
-
-  out.append("  tilt: ");
-  sprintf(buffer, "%g", this->tilt);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  stop: ");
-  out.append(YESNO(this->stop));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_FAN
 bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 5: {
@@ -1803,69 +761,6 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
   }
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesFanResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesFanResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  supports_oscillation: ");
-  out.append(YESNO(this->supports_oscillation));
-  out.append("\n");
-
-  out.append("  supports_speed: ");
-  out.append(YESNO(this->supports_speed));
-  out.append("\n");
-
-  out.append("  supports_direction: ");
-  out.append(YESNO(this->supports_direction));
-  out.append("\n");
-
-  out.append("  supported_speed_count: ");
-  sprintf(buffer, "%" PRId32, this->supported_speed_count);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  for (const auto &it : this->supported_preset_modes) {
-    out.append("  supported_preset_modes: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -1930,42 +825,6 @@ void FanStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_int32_field(total_size, 1, this->speed_level, false);
   ProtoSize::add_string_field(total_size, 1, this->preset_mode, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void FanStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("FanStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  oscillating: ");
-  out.append(YESNO(this->oscillating));
-  out.append("\n");
-
-  out.append("  speed: ");
-  out.append(proto_enum_to_string(this->speed));
-  out.append("\n");
-
-  out.append("  direction: ");
-  out.append(proto_enum_to_string(this->direction));
-  out.append("\n");
-
-  out.append("  speed_level: ");
-  sprintf(buffer, "%" PRId32, this->speed_level);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  preset_mode: ");
-  out.append("'").append(this->preset_mode).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -2066,66 +925,8 @@ void FanCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->has_preset_mode, false);
   ProtoSize::add_string_field(total_size, 1, this->preset_mode, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void FanCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("FanCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_state: ");
-  out.append(YESNO(this->has_state));
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  has_speed: ");
-  out.append(YESNO(this->has_speed));
-  out.append("\n");
-
-  out.append("  speed: ");
-  out.append(proto_enum_to_string(this->speed));
-  out.append("\n");
-
-  out.append("  has_oscillating: ");
-  out.append(YESNO(this->has_oscillating));
-  out.append("\n");
-
-  out.append("  oscillating: ");
-  out.append(YESNO(this->oscillating));
-  out.append("\n");
-
-  out.append("  has_direction: ");
-  out.append(YESNO(this->has_direction));
-  out.append("\n");
-
-  out.append("  direction: ");
-  out.append(proto_enum_to_string(this->direction));
-  out.append("\n");
-
-  out.append("  has_speed_level: ");
-  out.append(YESNO(this->has_speed_level));
-  out.append("\n");
-
-  out.append("  speed_level: ");
-  sprintf(buffer, "%" PRId32, this->speed_level);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_preset_mode: ");
-  out.append(YESNO(this->has_preset_mode));
-  out.append("\n");
-
-  out.append("  preset_mode: ");
-  out.append("'").append(this->preset_mode).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_LIGHT
 bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 12: {
@@ -2256,84 +1057,6 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesLightResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesLightResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->supported_color_modes) {
-    out.append("  supported_color_modes: ");
-    out.append(proto_enum_to_string(it));
-    out.append("\n");
-  }
-
-  out.append("  legacy_supports_brightness: ");
-  out.append(YESNO(this->legacy_supports_brightness));
-  out.append("\n");
-
-  out.append("  legacy_supports_rgb: ");
-  out.append(YESNO(this->legacy_supports_rgb));
-  out.append("\n");
-
-  out.append("  legacy_supports_white_value: ");
-  out.append(YESNO(this->legacy_supports_white_value));
-  out.append("\n");
-
-  out.append("  legacy_supports_color_temperature: ");
-  out.append(YESNO(this->legacy_supports_color_temperature));
-  out.append("\n");
-
-  out.append("  min_mireds: ");
-  sprintf(buffer, "%g", this->min_mireds);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  max_mireds: ");
-  sprintf(buffer, "%g", this->max_mireds);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->effects) {
-    out.append("  effects: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -2434,74 +1157,6 @@ void LightStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->warm_white != 0.0f, false);
   ProtoSize::add_string_field(total_size, 1, this->effect, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void LightStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("LightStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  brightness: ");
-  sprintf(buffer, "%g", this->brightness);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  color_mode: ");
-  out.append(proto_enum_to_string(this->color_mode));
-  out.append("\n");
-
-  out.append("  color_brightness: ");
-  sprintf(buffer, "%g", this->color_brightness);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  red: ");
-  sprintf(buffer, "%g", this->red);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  green: ");
-  sprintf(buffer, "%g", this->green);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  blue: ");
-  sprintf(buffer, "%g", this->blue);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  white: ");
-  sprintf(buffer, "%g", this->white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  color_temperature: ");
-  sprintf(buffer, "%g", this->color_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  cold_white: ");
-  sprintf(buffer, "%g", this->cold_white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  warm_white: ");
-  sprintf(buffer, "%g", this->warm_white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  effect: ");
-  out.append("'").append(this->effect).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -2686,132 +1341,8 @@ void LightCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 2, this->has_effect, false);
   ProtoSize::add_string_field(total_size, 2, this->effect, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void LightCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("LightCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_state: ");
-  out.append(YESNO(this->has_state));
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  has_brightness: ");
-  out.append(YESNO(this->has_brightness));
-  out.append("\n");
-
-  out.append("  brightness: ");
-  sprintf(buffer, "%g", this->brightness);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_color_mode: ");
-  out.append(YESNO(this->has_color_mode));
-  out.append("\n");
-
-  out.append("  color_mode: ");
-  out.append(proto_enum_to_string(this->color_mode));
-  out.append("\n");
-
-  out.append("  has_color_brightness: ");
-  out.append(YESNO(this->has_color_brightness));
-  out.append("\n");
-
-  out.append("  color_brightness: ");
-  sprintf(buffer, "%g", this->color_brightness);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_rgb: ");
-  out.append(YESNO(this->has_rgb));
-  out.append("\n");
-
-  out.append("  red: ");
-  sprintf(buffer, "%g", this->red);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  green: ");
-  sprintf(buffer, "%g", this->green);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  blue: ");
-  sprintf(buffer, "%g", this->blue);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_white: ");
-  out.append(YESNO(this->has_white));
-  out.append("\n");
-
-  out.append("  white: ");
-  sprintf(buffer, "%g", this->white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_color_temperature: ");
-  out.append(YESNO(this->has_color_temperature));
-  out.append("\n");
-
-  out.append("  color_temperature: ");
-  sprintf(buffer, "%g", this->color_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_cold_white: ");
-  out.append(YESNO(this->has_cold_white));
-  out.append("\n");
-
-  out.append("  cold_white: ");
-  sprintf(buffer, "%g", this->cold_white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_warm_white: ");
-  out.append(YESNO(this->has_warm_white));
-  out.append("\n");
-
-  out.append("  warm_white: ");
-  sprintf(buffer, "%g", this->warm_white);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_transition_length: ");
-  out.append(YESNO(this->has_transition_length));
-  out.append("\n");
-
-  out.append("  transition_length: ");
-  sprintf(buffer, "%" PRIu32, this->transition_length);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_flash_length: ");
-  out.append(YESNO(this->has_flash_length));
-  out.append("\n");
-
-  out.append("  flash_length: ");
-  sprintf(buffer, "%" PRIu32, this->flash_length);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_effect: ");
-  out.append(YESNO(this->has_effect));
-  out.append("\n");
-
-  out.append("  effect: ");
-  out.append("'").append(this->effect).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_SENSOR
 bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 7: {
@@ -2918,71 +1449,6 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesSensorResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesSensorResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  unit_of_measurement: ");
-  out.append("'").append(this->unit_of_measurement).append("'");
-  out.append("\n");
-
-  out.append("  accuracy_decimals: ");
-  sprintf(buffer, "%" PRId32, this->accuracy_decimals);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  force_update: ");
-  out.append(YESNO(this->force_update));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  state_class: ");
-  out.append(proto_enum_to_string(this->state_class));
-  out.append("\n");
-
-  out.append("  legacy_last_reset_type: ");
-  out.append(proto_enum_to_string(this->legacy_last_reset_type));
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -3017,26 +1483,8 @@ void SensorStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SensorStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SensorStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  sprintf(buffer, "%g", this->state);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_SWITCH
 bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -3119,54 +1567,6 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->device_class, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesSwitchResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  assumed_state: ");
-  out.append(YESNO(this->assumed_state));
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -3195,21 +1595,6 @@ void SwitchStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_bool_field(total_size, 1, this->state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SwitchStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SwitchStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -3238,21 +1623,8 @@ void SwitchCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_bool_field(total_size, 1, this->state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SwitchCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SwitchCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_TEXT_SENSOR
 bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -3329,50 +1701,6 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const
   ProtoSize::add_string_field(total_size, 1, this->device_class, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesTextSensorResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -3413,24 +1741,6 @@ void TextSensorStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->state, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void TextSensorStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("TextSensorStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
 #endif
 bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
@@ -3454,20 +1764,6 @@ void SubscribeLogsRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->level), false);
   ProtoSize::add_bool_field(total_size, 1, this->dump_config, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeLogsRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SubscribeLogsRequest {\n");
-  out.append("  level: ");
-  out.append(proto_enum_to_string(this->level));
-  out.append("\n");
-
-  out.append("  dump_config: ");
-  out.append(YESNO(this->dump_config));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -3502,24 +1798,7 @@ void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->message, false);
   ProtoSize::add_bool_field(total_size, 1, this->send_failed, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeLogsResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SubscribeLogsResponse {\n");
-  out.append("  level: ");
-  out.append(proto_enum_to_string(this->level));
-  out.append("\n");
-
-  out.append("  message: ");
-  out.append(format_hex_pretty(this->message));
-  out.append("\n");
-
-  out.append("  send_failed: ");
-  out.append(YESNO(this->send_failed));
-  out.append("\n");
-  out.append("}");
-}
-#endif
+#ifdef USE_API_NOISE
 bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -3536,16 +1815,6 @@ void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const {
 void NoiseEncryptionSetKeyRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->key, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("NoiseEncryptionSetKeyRequest {\n");
-  out.append("  key: ");
-  out.append(format_hex_pretty(this->key));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool NoiseEncryptionSetKeyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -3560,20 +1829,6 @@ void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buff
 void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->success, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("NoiseEncryptionSetKeyResponse {\n");
-  out.append("  success: ");
-  out.append(YESNO(this->success));
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
-  out.append("SubscribeHomeassistantServicesRequest {}");
-}
 #endif
 bool HomeassistantServiceMap::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
@@ -3597,20 +1852,6 @@ void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->key, false);
   ProtoSize::add_string_field(total_size, 1, this->value, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void HomeassistantServiceMap::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("HomeassistantServiceMap {\n");
-  out.append("  key: ");
-  out.append("'").append(this->key).append("'");
-  out.append("\n");
-
-  out.append("  value: ");
-  out.append("'").append(this->value).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool HomeassistantServiceResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 5: {
@@ -3663,43 +1904,6 @@ void HomeassistantServiceResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_repeated_message(total_size, 1, this->variables);
   ProtoSize::add_bool_field(total_size, 1, this->is_event, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void HomeassistantServiceResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("HomeassistantServiceResponse {\n");
-  out.append("  service: ");
-  out.append("'").append(this->service).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->data) {
-    out.append("  data: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->data_template) {
-    out.append("  data_template: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->variables) {
-    out.append("  variables: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  out.append("  is_event: ");
-  out.append(YESNO(this->is_event));
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
-  out.append("SubscribeHomeAssistantStatesRequest {}");
-}
-#endif
 bool SubscribeHomeAssistantStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -3734,24 +1938,6 @@ void SubscribeHomeAssistantStateResponse::calculate_size(uint32_t &total_size) c
   ProtoSize::add_string_field(total_size, 1, this->attribute, false);
   ProtoSize::add_bool_field(total_size, 1, this->once, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SubscribeHomeAssistantStateResponse {\n");
-  out.append("  entity_id: ");
-  out.append("'").append(this->entity_id).append("'");
-  out.append("\n");
-
-  out.append("  attribute: ");
-  out.append("'").append(this->attribute).append("'");
-  out.append("\n");
-
-  out.append("  once: ");
-  out.append(YESNO(this->once));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -3780,27 +1966,6 @@ void HomeAssistantStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->state, false);
   ProtoSize::add_string_field(total_size, 1, this->attribute, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void HomeAssistantStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("HomeAssistantStateResponse {\n");
-  out.append("  entity_id: ");
-  out.append("'").append(this->entity_id).append("'");
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-
-  out.append("  attribute: ");
-  out.append("'").append(this->attribute).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
-#endif
 bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
   switch (field_id) {
     case 1: {
@@ -3815,17 +1980,6 @@ void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixe
 void GetTimeResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void GetTimeResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("GetTimeResponse {\n");
-  out.append("  epoch_seconds: ");
-  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -3854,20 +2008,6 @@ void ListEntitiesServicesArgument::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->name, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->type), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesServicesArgument::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesServicesArgument {\n");
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  type: ");
-  out.append(proto_enum_to_string(this->type));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ListEntitiesServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -3904,27 +2044,6 @@ void ListEntitiesServicesResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_repeated_message(total_size, 1, this->args);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesServicesResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesServicesResponse {\n");
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->args) {
-    out.append("  args: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -4025,61 +2144,6 @@ void ExecuteServiceArgument::calculate_size(uint32_t &total_size) const {
     }
   }
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ExecuteServiceArgument::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ExecuteServiceArgument {\n");
-  out.append("  bool_: ");
-  out.append(YESNO(this->bool_));
-  out.append("\n");
-
-  out.append("  legacy_int: ");
-  sprintf(buffer, "%" PRId32, this->legacy_int);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  float_: ");
-  sprintf(buffer, "%g", this->float_);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  string_: ");
-  out.append("'").append(this->string_).append("'");
-  out.append("\n");
-
-  out.append("  int_: ");
-  sprintf(buffer, "%" PRId32, this->int_);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto it : this->bool_array) {
-    out.append("  bool_array: ");
-    out.append(YESNO(it));
-    out.append("\n");
-  }
-
-  for (const auto &it : this->int_array) {
-    out.append("  int_array: ");
-    sprintf(buffer, "%" PRId32, it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->float_array) {
-    out.append("  float_array: ");
-    sprintf(buffer, "%g", it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->string_array) {
-    out.append("  string_array: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 2: {
@@ -4110,23 +2174,7 @@ void ExecuteServiceRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_repeated_message(total_size, 1, this->args);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ExecuteServiceRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ExecuteServiceRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->args) {
-    out.append("  args: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
+#ifdef USE_ESP32_CAMERA
 bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 5: {
@@ -4197,46 +2245,6 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesCameraResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesCameraResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -4277,25 +2285,6 @@ void CameraImageResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->data, false);
   ProtoSize::add_bool_field(total_size, 1, this->done, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void CameraImageResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("CameraImageResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-
-  out.append("  done: ");
-  out.append(YESNO(this->done));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -4318,20 +2307,8 @@ void CameraImageRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->single, false);
   ProtoSize::add_bool_field(total_size, 1, this->stream, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void CameraImageRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("CameraImageRequest {\n");
-  out.append("  single: ");
-  out.append(YESNO(this->single));
-  out.append("\n");
-
-  out.append("  stream: ");
-  out.append(YESNO(this->stream));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_CLIMATE
 bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 5: {
@@ -4546,136 +2523,6 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false);
   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesClimateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesClimateResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  supports_current_temperature: ");
-  out.append(YESNO(this->supports_current_temperature));
-  out.append("\n");
-
-  out.append("  supports_two_point_target_temperature: ");
-  out.append(YESNO(this->supports_two_point_target_temperature));
-  out.append("\n");
-
-  for (const auto &it : this->supported_modes) {
-    out.append("  supported_modes: ");
-    out.append(proto_enum_to_string(it));
-    out.append("\n");
-  }
-
-  out.append("  visual_min_temperature: ");
-  sprintf(buffer, "%g", this->visual_min_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  visual_max_temperature: ");
-  sprintf(buffer, "%g", this->visual_max_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  visual_target_temperature_step: ");
-  sprintf(buffer, "%g", this->visual_target_temperature_step);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  legacy_supports_away: ");
-  out.append(YESNO(this->legacy_supports_away));
-  out.append("\n");
-
-  out.append("  supports_action: ");
-  out.append(YESNO(this->supports_action));
-  out.append("\n");
-
-  for (const auto &it : this->supported_fan_modes) {
-    out.append("  supported_fan_modes: ");
-    out.append(proto_enum_to_string(it));
-    out.append("\n");
-  }
-
-  for (const auto &it : this->supported_swing_modes) {
-    out.append("  supported_swing_modes: ");
-    out.append(proto_enum_to_string(it));
-    out.append("\n");
-  }
-
-  for (const auto &it : this->supported_custom_fan_modes) {
-    out.append("  supported_custom_fan_modes: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  for (const auto &it : this->supported_presets) {
-    out.append("  supported_presets: ");
-    out.append(proto_enum_to_string(it));
-    out.append("\n");
-  }
-
-  for (const auto &it : this->supported_custom_presets) {
-    out.append("  supported_custom_presets: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  visual_current_temperature_step: ");
-  sprintf(buffer, "%g", this->visual_current_temperature_step);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  supports_current_humidity: ");
-  out.append(YESNO(this->supports_current_humidity));
-  out.append("\n");
-
-  out.append("  supports_target_humidity: ");
-  out.append(YESNO(this->supports_target_humidity));
-  out.append("\n");
-
-  out.append("  visual_min_humidity: ");
-  sprintf(buffer, "%g", this->visual_min_humidity);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  visual_max_humidity: ");
-  sprintf(buffer, "%g", this->visual_max_humidity);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -4788,79 +2635,6 @@ void ClimateStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->current_humidity != 0.0f, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->target_humidity != 0.0f, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ClimateStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ClimateStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-
-  out.append("  current_temperature: ");
-  sprintf(buffer, "%g", this->current_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  target_temperature: ");
-  sprintf(buffer, "%g", this->target_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  target_temperature_low: ");
-  sprintf(buffer, "%g", this->target_temperature_low);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  target_temperature_high: ");
-  sprintf(buffer, "%g", this->target_temperature_high);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  unused_legacy_away: ");
-  out.append(YESNO(this->unused_legacy_away));
-  out.append("\n");
-
-  out.append("  action: ");
-  out.append(proto_enum_to_string(this->action));
-  out.append("\n");
-
-  out.append("  fan_mode: ");
-  out.append(proto_enum_to_string(this->fan_mode));
-  out.append("\n");
-
-  out.append("  swing_mode: ");
-  out.append(proto_enum_to_string(this->swing_mode));
-  out.append("\n");
-
-  out.append("  custom_fan_mode: ");
-  out.append("'").append(this->custom_fan_mode).append("'");
-  out.append("\n");
-
-  out.append("  preset: ");
-  out.append(proto_enum_to_string(this->preset));
-  out.append("\n");
-
-  out.append("  custom_preset: ");
-  out.append("'").append(this->custom_preset).append("'");
-  out.append("\n");
-
-  out.append("  current_humidity: ");
-  sprintf(buffer, "%g", this->current_humidity);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  target_humidity: ");
-  sprintf(buffer, "%g", this->target_humidity);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -5021,109 +2795,8 @@ void ClimateCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 2, this->has_target_humidity, false);
   ProtoSize::add_fixed_field<4>(total_size, 2, this->target_humidity != 0.0f, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ClimateCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ClimateCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_mode: ");
-  out.append(YESNO(this->has_mode));
-  out.append("\n");
-
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-
-  out.append("  has_target_temperature: ");
-  out.append(YESNO(this->has_target_temperature));
-  out.append("\n");
-
-  out.append("  target_temperature: ");
-  sprintf(buffer, "%g", this->target_temperature);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_target_temperature_low: ");
-  out.append(YESNO(this->has_target_temperature_low));
-  out.append("\n");
-
-  out.append("  target_temperature_low: ");
-  sprintf(buffer, "%g", this->target_temperature_low);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_target_temperature_high: ");
-  out.append(YESNO(this->has_target_temperature_high));
-  out.append("\n");
-
-  out.append("  target_temperature_high: ");
-  sprintf(buffer, "%g", this->target_temperature_high);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  unused_has_legacy_away: ");
-  out.append(YESNO(this->unused_has_legacy_away));
-  out.append("\n");
-
-  out.append("  unused_legacy_away: ");
-  out.append(YESNO(this->unused_legacy_away));
-  out.append("\n");
-
-  out.append("  has_fan_mode: ");
-  out.append(YESNO(this->has_fan_mode));
-  out.append("\n");
-
-  out.append("  fan_mode: ");
-  out.append(proto_enum_to_string(this->fan_mode));
-  out.append("\n");
-
-  out.append("  has_swing_mode: ");
-  out.append(YESNO(this->has_swing_mode));
-  out.append("\n");
-
-  out.append("  swing_mode: ");
-  out.append(proto_enum_to_string(this->swing_mode));
-  out.append("\n");
-
-  out.append("  has_custom_fan_mode: ");
-  out.append(YESNO(this->has_custom_fan_mode));
-  out.append("\n");
-
-  out.append("  custom_fan_mode: ");
-  out.append("'").append(this->custom_fan_mode).append("'");
-  out.append("\n");
-
-  out.append("  has_preset: ");
-  out.append(YESNO(this->has_preset));
-  out.append("\n");
-
-  out.append("  preset: ");
-  out.append(proto_enum_to_string(this->preset));
-  out.append("\n");
-
-  out.append("  has_custom_preset: ");
-  out.append(YESNO(this->has_custom_preset));
-  out.append("\n");
-
-  out.append("  custom_preset: ");
-  out.append("'").append(this->custom_preset).append("'");
-  out.append("\n");
-
-  out.append("  has_target_humidity: ");
-  out.append(YESNO(this->has_target_humidity));
-  out.append("\n");
-
-  out.append("  target_humidity: ");
-  sprintf(buffer, "%g", this->target_humidity);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_NUMBER
 bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 9: {
@@ -5230,73 +2903,6 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->device_class, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesNumberResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesNumberResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  min_value: ");
-  sprintf(buffer, "%g", this->min_value);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  max_value: ");
-  sprintf(buffer, "%g", this->max_value);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  step: ");
-  sprintf(buffer, "%g", this->step);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  unit_of_measurement: ");
-  out.append("'").append(this->unit_of_measurement).append("'");
-  out.append("\n");
-
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -5331,26 +2937,6 @@ void NumberStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void NumberStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("NumberStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  sprintf(buffer, "%g", this->state);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
   switch (field_id) {
     case 1: {
@@ -5373,22 +2959,8 @@ void NumberCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void NumberCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("NumberCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  sprintf(buffer, "%g", this->state);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_SELECT
 bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 7: {
@@ -5471,52 +3043,6 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesSelectResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesSelectResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->options) {
-    out.append("  options: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -5557,25 +3083,6 @@ void SelectStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->state, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SelectStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SelectStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 2: {
@@ -5604,21 +3111,8 @@ void SelectCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_string_field(total_size, 1, this->state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SelectCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SelectCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_SIREN
 bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -5713,60 +3207,6 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesSirenResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesSirenResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  for (const auto &it : this->tones) {
-    out.append("  tones: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  supports_duration: ");
-  out.append(YESNO(this->supports_duration));
-  out.append("\n");
-
-  out.append("  supports_volume: ");
-  out.append(YESNO(this->supports_volume));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SirenStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -5795,21 +3235,6 @@ void SirenStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_bool_field(total_size, 1, this->state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SirenStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SirenStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -5886,51 +3311,8 @@ void SirenCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->has_volume, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SirenCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SirenCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_state: ");
-  out.append(YESNO(this->has_state));
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(YESNO(this->state));
-  out.append("\n");
-
-  out.append("  has_tone: ");
-  out.append(YESNO(this->has_tone));
-  out.append("\n");
-
-  out.append("  tone: ");
-  out.append("'").append(this->tone).append("'");
-  out.append("\n");
-
-  out.append("  has_duration: ");
-  out.append(YESNO(this->has_duration));
-  out.append("\n");
-
-  out.append("  duration: ");
-  sprintf(buffer, "%" PRIu32, this->duration);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_volume: ");
-  out.append(YESNO(this->has_volume));
-  out.append("\n");
-
-  out.append("  volume: ");
-  sprintf(buffer, "%g", this->volume);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_LOCK
 bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -6025,62 +3407,6 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->code_format, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesLockResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesLockResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  assumed_state: ");
-  out.append(YESNO(this->assumed_state));
-  out.append("\n");
-
-  out.append("  supports_open: ");
-  out.append(YESNO(this->supports_open));
-  out.append("\n");
-
-  out.append("  requires_code: ");
-  out.append(YESNO(this->requires_code));
-  out.append("\n");
-
-  out.append("  code_format: ");
-  out.append("'").append(this->code_format).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6109,21 +3435,6 @@ void LockStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void LockStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("LockStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(proto_enum_to_string(this->state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6170,29 +3481,8 @@ void LockCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->has_code, false);
   ProtoSize::add_string_field(total_size, 1, this->code, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void LockCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("LockCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  command: ");
-  out.append(proto_enum_to_string(this->command));
-  out.append("\n");
-
-  out.append("  has_code: ");
-  out.append(YESNO(this->has_code));
-  out.append("\n");
-
-  out.append("  code: ");
-  out.append("'").append(this->code).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_BUTTON
 bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -6269,50 +3559,6 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->device_class, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesButtonResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesButtonResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
   switch (field_id) {
     case 1: {
@@ -6327,17 +3573,8 @@ void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode
 void ButtonCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ButtonCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ButtonCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_MEDIA_PLAYER
 bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6384,35 +3621,6 @@ void MediaPlayerSupportedFormat::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->purpose), false);
   ProtoSize::add_uint32_field(total_size, 1, this->sample_bytes, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("MediaPlayerSupportedFormat {\n");
-  out.append("  format: ");
-  out.append("'").append(this->format).append("'");
-  out.append("\n");
-
-  out.append("  sample_rate: ");
-  sprintf(buffer, "%" PRIu32, this->sample_rate);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  num_channels: ");
-  sprintf(buffer, "%" PRIu32, this->num_channels);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  purpose: ");
-  out.append(proto_enum_to_string(this->purpose));
-  out.append("\n");
-
-  out.append("  sample_bytes: ");
-  sprintf(buffer, "%" PRIu32, this->sample_bytes);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -6497,56 +3705,6 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const
   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesMediaPlayerResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  supports_pause: ");
-  out.append(YESNO(this->supports_pause));
-  out.append("\n");
-
-  for (const auto &it : this->supported_formats) {
-    out.append("  supported_formats: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool MediaPlayerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6587,30 +3745,6 @@ void MediaPlayerStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false);
   ProtoSize::add_bool_field(total_size, 1, this->muted, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void MediaPlayerStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("MediaPlayerStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(proto_enum_to_string(this->state));
-  out.append("\n");
-
-  out.append("  volume: ");
-  sprintf(buffer, "%g", this->volume);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  muted: ");
-  out.append(YESNO(this->muted));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6687,50 +3821,8 @@ void MediaPlayerCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->has_announcement, false);
   ProtoSize::add_bool_field(total_size, 1, this->announcement, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void MediaPlayerCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("MediaPlayerCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_command: ");
-  out.append(YESNO(this->has_command));
-  out.append("\n");
-
-  out.append("  command: ");
-  out.append(proto_enum_to_string(this->command));
-  out.append("\n");
-
-  out.append("  has_volume: ");
-  out.append(YESNO(this->has_volume));
-  out.append("\n");
-
-  out.append("  volume: ");
-  sprintf(buffer, "%g", this->volume);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_media_url: ");
-  out.append(YESNO(this->has_media_url));
-  out.append("\n");
-
-  out.append("  media_url: ");
-  out.append("'").append(this->media_url).append("'");
-  out.append("\n");
-
-  out.append("  has_announcement: ");
-  out.append(YESNO(this->has_announcement));
-  out.append("\n");
-
-  out.append("  announcement: ");
-  out.append(YESNO(this->announcement));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_BLUETOOTH_PROXY
 bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -6747,17 +3839,6 @@ void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer)
 void SubscribeBluetoothLEAdvertisementsRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->flags, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
-  out.append("  flags: ");
-  sprintf(buffer, "%" PRIu32, this->flags);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -6798,27 +3879,6 @@ void BluetoothServiceData::calculate_size(uint32_t &total_size) const {
   }
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothServiceData::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothServiceData {\n");
-  out.append("  uuid: ");
-  out.append("'").append(this->uuid).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->legacy_data) {
-    out.append("  legacy_data: ");
-    sprintf(buffer, "%" PRIu32, it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -6887,49 +3947,6 @@ void BluetoothLEAdvertisementResponse::calculate_size(uint32_t &total_size) cons
   ProtoSize::add_repeated_message(total_size, 1, this->manufacturer_data);
   ProtoSize::add_uint32_field(total_size, 1, this->address_type, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothLEAdvertisementResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append(format_hex_pretty(this->name));
-  out.append("\n");
-
-  out.append("  rssi: ");
-  sprintf(buffer, "%" PRId32, this->rssi);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->service_uuids) {
-    out.append("  service_uuids: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  for (const auto &it : this->service_data) {
-    out.append("  service_data: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->manufacturer_data) {
-    out.append("  manufacturer_data: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  out.append("  address_type: ");
-  sprintf(buffer, "%" PRIu32, this->address_type);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -6970,31 +3987,6 @@ void BluetoothLERawAdvertisement::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->address_type, false);
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothLERawAdvertisement {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  rssi: ");
-  sprintf(buffer, "%" PRId32, this->rssi);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  address_type: ");
-  sprintf(buffer, "%" PRIu32, this->address_type);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -7013,18 +4005,6 @@ void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const
 void BluetoothLERawAdvertisementsResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_repeated_message(total_size, 1, this->advertisements);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothLERawAdvertisementsResponse {\n");
-  for (const auto &it : this->advertisements) {
-    out.append("  advertisements: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7059,30 +4039,6 @@ void BluetoothDeviceRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->has_address_type, false);
   ProtoSize::add_uint32_field(total_size, 1, this->address_type, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothDeviceRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothDeviceRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  request_type: ");
-  out.append(proto_enum_to_string(this->request_type));
-  out.append("\n");
-
-  out.append("  has_address_type: ");
-  out.append(YESNO(this->has_address_type));
-  out.append("\n");
-
-  out.append("  address_type: ");
-  sprintf(buffer, "%" PRIu32, this->address_type);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7117,31 +4073,6 @@ void BluetoothDeviceConnectionResponse::calculate_size(uint32_t &total_size) con
   ProtoSize::add_uint32_field(total_size, 1, this->mtu, false);
   ProtoSize::add_int32_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothDeviceConnectionResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  connected: ");
-  out.append(YESNO(this->connected));
-  out.append("\n");
-
-  out.append("  mtu: ");
-  sprintf(buffer, "%" PRIu32, this->mtu);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  error: ");
-  sprintf(buffer, "%" PRId32, this->error);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7156,17 +4087,6 @@ void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { bu
 void BluetoothGATTGetServicesRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTGetServicesRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7195,24 +4115,6 @@ void BluetoothGATTDescriptor::calculate_size(uint32_t &total_size) const {
   }
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTDescriptor::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTDescriptor {\n");
-  for (const auto &it : this->uuid) {
-    out.append("  uuid: ");
-    sprintf(buffer, "%llu", it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7261,35 +4163,6 @@ void BluetoothGATTCharacteristic::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->properties, false);
   ProtoSize::add_repeated_message(total_size, 1, this->descriptors);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTCharacteristic {\n");
-  for (const auto &it : this->uuid) {
-    out.append("  uuid: ");
-    sprintf(buffer, "%llu", it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  properties: ");
-  sprintf(buffer, "%" PRIu32, this->properties);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->descriptors) {
-    out.append("  descriptors: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7332,30 +4205,6 @@ void BluetoothGATTService::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_repeated_message(total_size, 1, this->characteristics);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTService::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTService {\n");
-  for (const auto &it : this->uuid) {
-    out.append("  uuid: ");
-    sprintf(buffer, "%llu", it);
-    out.append(buffer);
-    out.append("\n");
-  }
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->characteristics) {
-    out.append("  characteristics: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7386,23 +4235,6 @@ void BluetoothGATTGetServicesResponse::calculate_size(uint32_t &total_size) cons
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
   ProtoSize::add_repeated_message(total_size, 1, this->services);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTGetServicesResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->services) {
-    out.append("  services: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7419,17 +4251,6 @@ void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const
 void BluetoothGATTGetServicesDoneResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTGetServicesDoneResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7452,22 +4273,6 @@ void BluetoothGATTReadRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTReadRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTReadRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7502,26 +4307,6 @@ void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTReadResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTReadResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7562,30 +4347,6 @@ void BluetoothGATTWriteRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->response, false);
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTWriteRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  response: ");
-  out.append(YESNO(this->response));
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7608,22 +4369,6 @@ void BluetoothGATTReadDescriptorRequest::calculate_size(uint32_t &total_size) co
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTReadDescriptorRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7658,26 +4403,6 @@ void BluetoothGATTWriteDescriptorRequest::calculate_size(uint32_t &total_size) c
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTWriteDescriptorRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7706,26 +4431,6 @@ void BluetoothGATTNotifyRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_bool_field(total_size, 1, this->enable, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTNotifyRequest {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  enable: ");
-  out.append(YESNO(this->enable));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7760,31 +4465,6 @@ void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_string_field(total_size, 1, this->data, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTNotifyDataResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
-  out.append("SubscribeBluetoothConnectionsFreeRequest {}");
-}
-#endif
 bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7819,29 +4499,6 @@ void BluetoothConnectionsFreeResponse::calculate_size(uint32_t &total_size) cons
     }
   }
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothConnectionsFreeResponse {\n");
-  out.append("  free: ");
-  sprintf(buffer, "%" PRIu32, this->free);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  limit: ");
-  sprintf(buffer, "%" PRIu32, this->limit);
-  out.append(buffer);
-  out.append("\n");
-
-  for (const auto &it : this->allocated) {
-    out.append("  allocated: ");
-    sprintf(buffer, "%llu", it);
-    out.append(buffer);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7870,27 +4527,6 @@ void BluetoothGATTErrorResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
   ProtoSize::add_int32_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTErrorResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  error: ");
-  sprintf(buffer, "%" PRId32, this->error);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7913,22 +4549,6 @@ void BluetoothGATTWriteResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTWriteResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7951,22 +4571,6 @@ void BluetoothGATTNotifyResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint64_field(total_size, 1, this->address, false);
   ProtoSize::add_uint32_field(total_size, 1, this->handle, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothGATTNotifyResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  handle: ");
-  sprintf(buffer, "%" PRIu32, this->handle);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -7995,26 +4599,6 @@ void BluetoothDevicePairingResponse::calculate_size(uint32_t &total_size) const
   ProtoSize::add_bool_field(total_size, 1, this->paired, false);
   ProtoSize::add_int32_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothDevicePairingResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  paired: ");
-  out.append(YESNO(this->paired));
-  out.append("\n");
-
-  out.append("  error: ");
-  sprintf(buffer, "%" PRId32, this->error);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8043,31 +4627,6 @@ void BluetoothDeviceUnpairingResponse::calculate_size(uint32_t &total_size) cons
   ProtoSize::add_bool_field(total_size, 1, this->success, false);
   ProtoSize::add_int32_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothDeviceUnpairingResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  success: ");
-  out.append(YESNO(this->success));
-  out.append("\n");
-
-  out.append("  error: ");
-  sprintf(buffer, "%" PRId32, this->error);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
-  out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
-}
-#endif
 bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8096,26 +4655,6 @@ void BluetoothDeviceClearCacheResponse::calculate_size(uint32_t &total_size) con
   ProtoSize::add_bool_field(total_size, 1, this->success, false);
   ProtoSize::add_int32_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothDeviceClearCacheResponse {\n");
-  out.append("  address: ");
-  sprintf(buffer, "%llu", this->address);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  success: ");
-  out.append(YESNO(this->success));
-  out.append("\n");
-
-  out.append("  error: ");
-  sprintf(buffer, "%" PRId32, this->error);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothScannerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8138,20 +4677,6 @@ void BluetoothScannerStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothScannerStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothScannerStateResponse {\n");
-  out.append("  state: ");
-  out.append(proto_enum_to_string(this->state));
-  out.append("\n");
-
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8168,16 +4693,8 @@ void BluetoothScannerSetModeRequest::encode(ProtoWriteBuffer buffer) const {
 void BluetoothScannerSetModeRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("BluetoothScannerSetModeRequest {\n");
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_VOICE_ASSISTANT
 bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8200,21 +4717,6 @@ void SubscribeVoiceAssistantRequest::calculate_size(uint32_t &total_size) const
   ProtoSize::add_bool_field(total_size, 1, this->subscribe, false);
   ProtoSize::add_uint32_field(total_size, 1, this->flags, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("SubscribeVoiceAssistantRequest {\n");
-  out.append("  subscribe: ");
-  out.append(YESNO(this->subscribe));
-  out.append("\n");
-
-  out.append("  flags: ");
-  sprintf(buffer, "%" PRIu32, this->flags);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantAudioSettings::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8249,27 +4751,6 @@ void VoiceAssistantAudioSettings::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->auto_gain, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->volume_multiplier != 0.0f, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantAudioSettings {\n");
-  out.append("  noise_suppression_level: ");
-  sprintf(buffer, "%" PRIu32, this->noise_suppression_level);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  auto_gain: ");
-  sprintf(buffer, "%" PRIu32, this->auto_gain);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  volume_multiplier: ");
-  sprintf(buffer, "%g", this->volume_multiplier);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8316,33 +4797,6 @@ void VoiceAssistantRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_message_object(total_size, 1, this->audio_settings, false);
   ProtoSize::add_string_field(total_size, 1, this->wake_word_phrase, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantRequest {\n");
-  out.append("  start: ");
-  out.append(YESNO(this->start));
-  out.append("\n");
-
-  out.append("  conversation_id: ");
-  out.append("'").append(this->conversation_id).append("'");
-  out.append("\n");
-
-  out.append("  flags: ");
-  sprintf(buffer, "%" PRIu32, this->flags);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  audio_settings: ");
-  this->audio_settings.dump_to(out);
-  out.append("\n");
-
-  out.append("  wake_word_phrase: ");
-  out.append("'").append(this->wake_word_phrase).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8365,21 +4819,6 @@ void VoiceAssistantResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->port, false);
   ProtoSize::add_bool_field(total_size, 1, this->error, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantResponse {\n");
-  out.append("  port: ");
-  sprintf(buffer, "%" PRIu32, this->port);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  error: ");
-  out.append(YESNO(this->error));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -8402,20 +4841,6 @@ void VoiceAssistantEventData::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->name, false);
   ProtoSize::add_string_field(total_size, 1, this->value, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantEventData::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantEventData {\n");
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  value: ");
-  out.append("'").append(this->value).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8446,22 +4871,6 @@ void VoiceAssistantEventResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->event_type), false);
   ProtoSize::add_repeated_message(total_size, 1, this->data);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantEventResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantEventResponse {\n");
-  out.append("  event_type: ");
-  out.append(proto_enum_to_string(this->event_type));
-  out.append("\n");
-
-  for (const auto &it : this->data) {
-    out.append("  data: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
 bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -8490,20 +4899,6 @@ void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->data, false);
   ProtoSize::add_bool_field(total_size, 1, this->end, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantAudio::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantAudio {\n");
-  out.append("  data: ");
-  out.append(format_hex_pretty(this->data));
-  out.append("\n");
-
-  out.append("  end: ");
-  out.append(YESNO(this->end));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8556,38 +4951,6 @@ void VoiceAssistantTimerEventResponse::calculate_size(uint32_t &total_size) cons
   ProtoSize::add_uint32_field(total_size, 1, this->seconds_left, false);
   ProtoSize::add_bool_field(total_size, 1, this->is_active, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantTimerEventResponse {\n");
-  out.append("  event_type: ");
-  out.append(proto_enum_to_string(this->event_type));
-  out.append("\n");
-
-  out.append("  timer_id: ");
-  out.append("'").append(this->timer_id).append("'");
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  total_seconds: ");
-  sprintf(buffer, "%" PRIu32, this->total_seconds);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  seconds_left: ");
-  sprintf(buffer, "%" PRIu32, this->seconds_left);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  is_active: ");
-  out.append(YESNO(this->is_active));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 4: {
@@ -8628,28 +4991,6 @@ void VoiceAssistantAnnounceRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->preannounce_media_id, false);
   ProtoSize::add_bool_field(total_size, 1, this->start_conversation, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantAnnounceRequest {\n");
-  out.append("  media_id: ");
-  out.append("'").append(this->media_id).append("'");
-  out.append("\n");
-
-  out.append("  text: ");
-  out.append("'").append(this->text).append("'");
-  out.append("\n");
-
-  out.append("  preannounce_media_id: ");
-  out.append("'").append(this->preannounce_media_id).append("'");
-  out.append("\n");
-
-  out.append("  start_conversation: ");
-  out.append(YESNO(this->start_conversation));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantAnnounceFinished::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 1: {
@@ -8664,16 +5005,6 @@ void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buf
 void VoiceAssistantAnnounceFinished::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->success, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantAnnounceFinished {\n");
-  out.append("  success: ");
-  out.append(YESNO(this->success));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -8708,31 +5039,6 @@ void VoiceAssistantWakeWord::calculate_size(uint32_t &total_size) const {
     }
   }
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantWakeWord::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantWakeWord {\n");
-  out.append("  id: ");
-  out.append("'").append(this->id).append("'");
-  out.append("\n");
-
-  out.append("  wake_word: ");
-  out.append("'").append(this->wake_word).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->trained_languages) {
-    out.append("  trained_languages: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-  out.append("}");
-}
-#endif
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
-  out.append("VoiceAssistantConfigurationRequest {}");
-}
-#endif
 bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -8775,29 +5081,6 @@ void VoiceAssistantConfigurationResponse::calculate_size(uint32_t &total_size) c
   }
   ProtoSize::add_uint32_field(total_size, 1, this->max_active_wake_words, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantConfigurationResponse {\n");
-  for (const auto &it : this->available_wake_words) {
-    out.append("  available_wake_words: ");
-    it.dump_to(out);
-    out.append("\n");
-  }
-
-  for (const auto &it : this->active_wake_words) {
-    out.append("  active_wake_words: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  max_active_wake_words: ");
-  sprintf(buffer, "%" PRIu32, this->max_active_wake_words);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 1: {
@@ -8820,18 +5103,8 @@ void VoiceAssistantSetConfiguration::calculate_size(uint32_t &total_size) const
     }
   }
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("VoiceAssistantSetConfiguration {\n");
-  for (const auto &it : this->active_wake_words) {
-    out.append("  active_wake_words: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-  out.append("}");
-}
 #endif
+#ifdef USE_ALARM_CONTROL_PANEL
 bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -8920,59 +5193,6 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size)
   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesAlarmControlPanelResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  supported_features: ");
-  sprintf(buffer, "%" PRIu32, this->supported_features);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  requires_code: ");
-  out.append(YESNO(this->requires_code));
-  out.append("\n");
-
-  out.append("  requires_code_to_arm: ");
-  out.append(YESNO(this->requires_code_to_arm));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9001,21 +5221,6 @@ void AlarmControlPanelStateResponse::calculate_size(uint32_t &total_size) const
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("AlarmControlPanelStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append(proto_enum_to_string(this->state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9056,25 +5261,8 @@ void AlarmControlPanelCommandRequest::calculate_size(uint32_t &total_size) const
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->command), false);
   ProtoSize::add_string_field(total_size, 1, this->code, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("AlarmControlPanelCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  command: ");
-  out.append(proto_enum_to_string(this->command));
-  out.append("\n");
-
-  out.append("  code: ");
-  out.append("'").append(this->code).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_TEXT
 bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -9169,64 +5357,6 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesTextResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesTextResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  min_length: ");
-  sprintf(buffer, "%" PRIu32, this->min_length);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  max_length: ");
-  sprintf(buffer, "%" PRIu32, this->max_length);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  pattern: ");
-  out.append("'").append(this->pattern).append("'");
-  out.append("\n");
-
-  out.append("  mode: ");
-  out.append(proto_enum_to_string(this->mode));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool TextStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -9267,25 +5397,6 @@ void TextStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->state, false);
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void TextStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("TextStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 2: {
@@ -9314,21 +5425,8 @@ void TextCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_string_field(total_size, 1, this->state, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void TextCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("TextCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  state: ");
-  out.append("'").append(this->state).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_DATETIME_DATE
 bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -9399,46 +5497,6 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesDateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesDateResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9485,36 +5543,6 @@ void DateStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->month, false);
   ProtoSize::add_uint32_field(total_size, 1, this->day, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DateStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DateStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-
-  out.append("  year: ");
-  sprintf(buffer, "%" PRIu32, this->year);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  month: ");
-  sprintf(buffer, "%" PRIu32, this->month);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  day: ");
-  sprintf(buffer, "%" PRIu32, this->day);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9555,32 +5583,8 @@ void DateCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->month, false);
   ProtoSize::add_uint32_field(total_size, 1, this->day, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DateCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DateCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  year: ");
-  sprintf(buffer, "%" PRIu32, this->year);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  month: ");
-  sprintf(buffer, "%" PRIu32, this->month);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  day: ");
-  sprintf(buffer, "%" PRIu32, this->day);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_DATETIME_TIME
 bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -9651,46 +5655,6 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesTimeResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesTimeResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool TimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9737,36 +5701,6 @@ void TimeStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->minute, false);
   ProtoSize::add_uint32_field(total_size, 1, this->second, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void TimeStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("TimeStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-
-  out.append("  hour: ");
-  sprintf(buffer, "%" PRIu32, this->hour);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  minute: ");
-  sprintf(buffer, "%" PRIu32, this->minute);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  second: ");
-  sprintf(buffer, "%" PRIu32, this->second);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -9807,32 +5741,8 @@ void TimeCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_uint32_field(total_size, 1, this->minute, false);
   ProtoSize::add_uint32_field(total_size, 1, this->second, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void TimeCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("TimeCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  hour: ");
-  sprintf(buffer, "%" PRIu32, this->hour);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  minute: ");
-  sprintf(buffer, "%" PRIu32, this->minute);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  second: ");
-  sprintf(buffer, "%" PRIu32, this->second);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_EVENT
 bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -9921,56 +5831,6 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
   }
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesEventResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesEventResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  for (const auto &it : this->event_types) {
-    out.append("  event_types: ");
-    out.append("'").append(it).append("'");
-    out.append("\n");
-  }
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool EventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
   switch (field_id) {
     case 2: {
@@ -9999,21 +5859,8 @@ void EventResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_string_field(total_size, 1, this->event_type, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void EventResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("EventResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  event_type: ");
-  out.append("'").append(this->event_type).append("'");
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_VALVE
 bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -10108,62 +5955,6 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesValveResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesValveResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  assumed_state: ");
-  out.append(YESNO(this->assumed_state));
-  out.append("\n");
-
-  out.append("  supports_position: ");
-  out.append(YESNO(this->supports_position));
-  out.append("\n");
-
-  out.append("  supports_stop: ");
-  out.append(YESNO(this->supports_stop));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ValveStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 3: {
@@ -10198,26 +5989,6 @@ void ValveStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ValveStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ValveStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  position: ");
-  sprintf(buffer, "%g", this->position);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  current_operation: ");
-  out.append(proto_enum_to_string(this->current_operation));
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -10258,30 +6029,8 @@ void ValveCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false);
   ProtoSize::add_bool_field(total_size, 1, this->stop, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ValveCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ValveCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  has_position: ");
-  out.append(YESNO(this->has_position));
-  out.append("\n");
-
-  out.append("  position: ");
-  sprintf(buffer, "%g", this->position);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  stop: ");
-  out.append(YESNO(this->stop));
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_DATETIME_DATETIME
 bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -10352,46 +6101,6 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesDateTimeResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DateTimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -10426,26 +6135,6 @@ void DateTimeStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DateTimeStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DateTimeStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-
-  out.append("  epoch_seconds: ");
-  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
   switch (field_id) {
     case 1: {
@@ -10468,22 +6157,8 @@ void DateTimeCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void DateTimeCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("DateTimeCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  epoch_seconds: ");
-  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
 #endif
+#ifdef USE_UPDATE
 bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 6: {
@@ -10560,50 +6235,6 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->device_class, false);
   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("ListEntitiesUpdateResponse {\n");
-  out.append("  object_id: ");
-  out.append("'").append(this->object_id).append("'");
-  out.append("\n");
-
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  name: ");
-  out.append("'").append(this->name).append("'");
-  out.append("\n");
-
-  out.append("  unique_id: ");
-  out.append("'").append(this->unique_id).append("'");
-  out.append("\n");
-
-  out.append("  icon: ");
-  out.append("'").append(this->icon).append("'");
-  out.append("\n");
-
-  out.append("  disabled_by_default: ");
-  out.append(YESNO(this->disabled_by_default));
-  out.append("\n");
-
-  out.append("  entity_category: ");
-  out.append(proto_enum_to_string(this->entity_category));
-  out.append("\n");
-
-  out.append("  device_class: ");
-  out.append("'").append(this->device_class).append("'");
-  out.append("\n");
-
-  out.append("  device_id: ");
-  sprintf(buffer, "%" PRIu32, this->device_id);
-  out.append(buffer);
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -10686,54 +6317,6 @@ void UpdateStateResponse::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_string_field(total_size, 1, this->release_summary, false);
   ProtoSize::add_string_field(total_size, 1, this->release_url, false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void UpdateStateResponse::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("UpdateStateResponse {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  missing_state: ");
-  out.append(YESNO(this->missing_state));
-  out.append("\n");
-
-  out.append("  in_progress: ");
-  out.append(YESNO(this->in_progress));
-  out.append("\n");
-
-  out.append("  has_progress: ");
-  out.append(YESNO(this->has_progress));
-  out.append("\n");
-
-  out.append("  progress: ");
-  sprintf(buffer, "%g", this->progress);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  current_version: ");
-  out.append("'").append(this->current_version).append("'");
-  out.append("\n");
-
-  out.append("  latest_version: ");
-  out.append("'").append(this->latest_version).append("'");
-  out.append("\n");
-
-  out.append("  title: ");
-  out.append("'").append(this->title).append("'");
-  out.append("\n");
-
-  out.append("  release_summary: ");
-  out.append("'").append(this->release_summary).append("'");
-  out.append("\n");
-
-  out.append("  release_url: ");
-  out.append("'").append(this->release_url).append("'");
-  out.append("\n");
-  out.append("}");
-}
-#endif
 bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
   switch (field_id) {
     case 2: {
@@ -10762,20 +6345,6 @@ void UpdateCommandRequest::calculate_size(uint32_t &total_size) const {
   ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
   ProtoSize::add_enum_field(total_size, 1, static_cast(this->command), false);
 }
-#ifdef HAS_PROTO_MESSAGE_DUMP
-void UpdateCommandRequest::dump_to(std::string &out) const {
-  __attribute__((unused)) char buffer[64];
-  out.append("UpdateCommandRequest {\n");
-  out.append("  key: ");
-  sprintf(buffer, "%" PRIu32, this->key);
-  out.append(buffer);
-  out.append("\n");
-
-  out.append("  command: ");
-  out.append(proto_enum_to_string(this->command));
-  out.append("\n");
-  out.append("}");
-}
 #endif
 
 }  // namespace api
diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h
index 2f0444c2cd..24b0e891c9 100644
--- a/esphome/components/api/api_pb2.h
+++ b/esphome/components/api/api_pb2.h
@@ -2,6 +2,8 @@
 // See script/api_protobuf/api_protobuf.py
 #pragma once
 
+#include "esphome/core/defines.h"
+
 #include "proto.h"
 #include "api_pb2_size.h"
 
@@ -15,6 +17,7 @@ enum EntityCategory : uint32_t {
   ENTITY_CATEGORY_CONFIG = 1,
   ENTITY_CATEGORY_DIAGNOSTIC = 2,
 };
+#ifdef USE_COVER
 enum LegacyCoverState : uint32_t {
   LEGACY_COVER_STATE_OPEN = 0,
   LEGACY_COVER_STATE_CLOSED = 1,
@@ -29,6 +32,8 @@ enum LegacyCoverCommand : uint32_t {
   LEGACY_COVER_COMMAND_CLOSE = 1,
   LEGACY_COVER_COMMAND_STOP = 2,
 };
+#endif
+#ifdef USE_FAN
 enum FanSpeed : uint32_t {
   FAN_SPEED_LOW = 0,
   FAN_SPEED_MEDIUM = 1,
@@ -38,6 +43,8 @@ enum FanDirection : uint32_t {
   FAN_DIRECTION_FORWARD = 0,
   FAN_DIRECTION_REVERSE = 1,
 };
+#endif
+#ifdef USE_LIGHT
 enum ColorMode : uint32_t {
   COLOR_MODE_UNKNOWN = 0,
   COLOR_MODE_ON_OFF = 1,
@@ -51,6 +58,8 @@ enum ColorMode : uint32_t {
   COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
   COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
 };
+#endif
+#ifdef USE_SENSOR
 enum SensorStateClass : uint32_t {
   STATE_CLASS_NONE = 0,
   STATE_CLASS_MEASUREMENT = 1,
@@ -62,6 +71,7 @@ enum SensorLastResetType : uint32_t {
   LAST_RESET_NEVER = 1,
   LAST_RESET_AUTO = 2,
 };
+#endif
 enum LogLevel : uint32_t {
   LOG_LEVEL_NONE = 0,
   LOG_LEVEL_ERROR = 1,
@@ -82,6 +92,7 @@ enum ServiceArgType : uint32_t {
   SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
   SERVICE_ARG_TYPE_STRING_ARRAY = 7,
 };
+#ifdef USE_CLIMATE
 enum ClimateMode : uint32_t {
   CLIMATE_MODE_OFF = 0,
   CLIMATE_MODE_HEAT_COOL = 1,
@@ -127,11 +138,15 @@ enum ClimatePreset : uint32_t {
   CLIMATE_PRESET_SLEEP = 6,
   CLIMATE_PRESET_ACTIVITY = 7,
 };
+#endif
+#ifdef USE_NUMBER
 enum NumberMode : uint32_t {
   NUMBER_MODE_AUTO = 0,
   NUMBER_MODE_BOX = 1,
   NUMBER_MODE_SLIDER = 2,
 };
+#endif
+#ifdef USE_LOCK
 enum LockState : uint32_t {
   LOCK_STATE_NONE = 0,
   LOCK_STATE_LOCKED = 1,
@@ -145,6 +160,8 @@ enum LockCommand : uint32_t {
   LOCK_LOCK = 1,
   LOCK_OPEN = 2,
 };
+#endif
+#ifdef USE_MEDIA_PLAYER
 enum MediaPlayerState : uint32_t {
   MEDIA_PLAYER_STATE_NONE = 0,
   MEDIA_PLAYER_STATE_IDLE = 1,
@@ -162,6 +179,8 @@ enum MediaPlayerFormatPurpose : uint32_t {
   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
 };
+#endif
+#ifdef USE_BLUETOOTH_PROXY
 enum BluetoothDeviceRequestType : uint32_t {
   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
@@ -183,6 +202,7 @@ enum BluetoothScannerMode : uint32_t {
   BLUETOOTH_SCANNER_MODE_PASSIVE = 0,
   BLUETOOTH_SCANNER_MODE_ACTIVE = 1,
 };
+#endif
 enum VoiceAssistantSubscribeFlag : uint32_t {
   VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
   VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
@@ -192,6 +212,7 @@ enum VoiceAssistantRequestFlag : uint32_t {
   VOICE_ASSISTANT_REQUEST_USE_VAD = 1,
   VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2,
 };
+#ifdef USE_VOICE_ASSISTANT
 enum VoiceAssistantEvent : uint32_t {
   VOICE_ASSISTANT_ERROR = 0,
   VOICE_ASSISTANT_RUN_START = 1,
@@ -216,6 +237,8 @@ enum VoiceAssistantTimerEvent : uint32_t {
   VOICE_ASSISTANT_TIMER_CANCELLED = 2,
   VOICE_ASSISTANT_TIMER_FINISHED = 3,
 };
+#endif
+#ifdef USE_ALARM_CONTROL_PANEL
 enum AlarmControlPanelState : uint32_t {
   ALARM_STATE_DISARMED = 0,
   ALARM_STATE_ARMED_HOME = 1,
@@ -237,20 +260,27 @@ enum AlarmControlPanelStateCommand : uint32_t {
   ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
   ALARM_CONTROL_PANEL_TRIGGER = 6,
 };
+#endif
+#ifdef USE_TEXT
 enum TextMode : uint32_t {
   TEXT_MODE_TEXT = 0,
   TEXT_MODE_PASSWORD = 1,
 };
+#endif
+#ifdef USE_VALVE
 enum ValveOperation : uint32_t {
   VALVE_OPERATION_IDLE = 0,
   VALVE_OPERATION_IS_OPENING = 1,
   VALVE_OPERATION_IS_CLOSING = 2,
 };
+#endif
+#ifdef USE_UPDATE
 enum UpdateCommand : uint32_t {
   UPDATE_COMMAND_NONE = 0,
   UPDATE_COMMAND_UPDATE = 1,
   UPDATE_COMMAND_CHECK = 2,
 };
+#endif
 
 }  // namespace enums
 
@@ -523,6 +553,7 @@ class SubscribeStatesRequest : public ProtoMessage {
 
  protected:
 };
+#ifdef USE_BINARY_SENSOR
 class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 12;
@@ -562,6 +593,8 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_COVER
 class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 13;
@@ -631,6 +664,8 @@ class CoverCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_FAN
 class ListEntitiesFanResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 14;
@@ -709,6 +744,8 @@ class FanCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_LIGHT
 class ListEntitiesLightResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 15;
@@ -810,6 +847,8 @@ class LightCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_SENSOR
 class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 16;
@@ -853,6 +892,8 @@ class SensorStateResponse : public StateResponseProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_SWITCH
 class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 17;
@@ -910,6 +951,8 @@ class SwitchCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_TEXT_SENSOR
 class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 18;
@@ -949,6 +992,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
 class SubscribeLogsRequest : public ProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 28;
@@ -987,6 +1031,7 @@ class SubscribeLogsResponse : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#ifdef USE_API_NOISE
 class NoiseEncryptionSetKeyRequest : public ProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 124;
@@ -1021,6 +1066,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
  protected:
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
 class SubscribeHomeassistantServicesRequest : public ProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 34;
@@ -1226,6 +1272,7 @@ class ExecuteServiceRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 };
+#ifdef USE_ESP32_CAMERA
 class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 43;
@@ -1283,6 +1330,8 @@ class CameraImageRequest : public ProtoMessage {
  protected:
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_CLIMATE
 class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 46;
@@ -1392,6 +1441,8 @@ class ClimateCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_NUMBER
 class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 49;
@@ -1453,6 +1504,8 @@ class NumberCommandRequest : public ProtoMessage {
  protected:
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 };
+#endif
+#ifdef USE_SELECT
 class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 52;
@@ -1511,6 +1564,8 @@ class SelectCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 };
+#endif
+#ifdef USE_SIREN
 class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 55;
@@ -1577,6 +1632,8 @@ class SirenCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_LOCK
 class ListEntitiesLockResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 58;
@@ -1639,6 +1696,8 @@ class LockCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_BUTTON
 class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 61;
@@ -1675,6 +1734,8 @@ class ButtonCommandRequest : public ProtoMessage {
  protected:
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 };
+#endif
+#ifdef USE_MEDIA_PLAYER
 class MediaPlayerSupportedFormat : public ProtoMessage {
  public:
   std::string format{};
@@ -1759,6 +1820,8 @@ class MediaPlayerCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_BLUETOOTH_PROXY
 class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 66;
@@ -2313,6 +2376,8 @@ class BluetoothScannerSetModeRequest : public ProtoMessage {
  protected:
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_VOICE_ASSISTANT
 class SubscribeVoiceAssistantRequest : public ProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 89;
@@ -2562,6 +2627,8 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
  protected:
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 };
+#endif
+#ifdef USE_ALARM_CONTROL_PANEL
 class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 94;
@@ -2622,6 +2689,8 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_TEXT
 class ListEntitiesTextResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 97;
@@ -2683,6 +2752,8 @@ class TextCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 };
+#endif
+#ifdef USE_DATETIME_DATE
 class ListEntitiesDateResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 100;
@@ -2743,6 +2814,8 @@ class DateCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_DATETIME_TIME
 class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 103;
@@ -2803,6 +2876,8 @@ class TimeCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_EVENT
 class ListEntitiesEventResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 107;
@@ -2841,6 +2916,8 @@ class EventResponse : public StateResponseProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 };
+#endif
+#ifdef USE_VALVE
 class ListEntitiesValveResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 109;
@@ -2903,6 +2980,8 @@ class ValveCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
+#ifdef USE_DATETIME_DATETIME
 class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 112;
@@ -2958,6 +3037,8 @@ class DateTimeCommandRequest : public ProtoMessage {
  protected:
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 };
+#endif
+#ifdef USE_UPDATE
 class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
  public:
   static constexpr uint16_t MESSAGE_TYPE = 116;
@@ -3023,6 +3104,7 @@ class UpdateCommandRequest : public ProtoMessage {
   bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
   bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 };
+#endif
 
 }  // namespace api
 }  // namespace esphome
diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp
new file mode 100644
index 0000000000..6658fd754b
--- /dev/null
+++ b/esphome/components/api/api_pb2_dump.cpp
@@ -0,0 +1,4228 @@
+// This file was automatically generated with a tool.
+// See script/api_protobuf/api_protobuf.py
+#include "api_pb2.h"
+#include "esphome/core/helpers.h"
+
+#include 
+
+#ifdef HAS_PROTO_MESSAGE_DUMP
+
+namespace esphome {
+namespace api {
+
+template<> const char *proto_enum_to_string(enums::EntityCategory value) {
+  switch (value) {
+    case enums::ENTITY_CATEGORY_NONE:
+      return "ENTITY_CATEGORY_NONE";
+    case enums::ENTITY_CATEGORY_CONFIG:
+      return "ENTITY_CATEGORY_CONFIG";
+    case enums::ENTITY_CATEGORY_DIAGNOSTIC:
+      return "ENTITY_CATEGORY_DIAGNOSTIC";
+    default:
+      return "UNKNOWN";
+  }
+}
+#ifdef USE_COVER
+template<> const char *proto_enum_to_string(enums::LegacyCoverState value) {
+  switch (value) {
+    case enums::LEGACY_COVER_STATE_OPEN:
+      return "LEGACY_COVER_STATE_OPEN";
+    case enums::LEGACY_COVER_STATE_CLOSED:
+      return "LEGACY_COVER_STATE_CLOSED";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::CoverOperation value) {
+  switch (value) {
+    case enums::COVER_OPERATION_IDLE:
+      return "COVER_OPERATION_IDLE";
+    case enums::COVER_OPERATION_IS_OPENING:
+      return "COVER_OPERATION_IS_OPENING";
+    case enums::COVER_OPERATION_IS_CLOSING:
+      return "COVER_OPERATION_IS_CLOSING";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::LegacyCoverCommand value) {
+  switch (value) {
+    case enums::LEGACY_COVER_COMMAND_OPEN:
+      return "LEGACY_COVER_COMMAND_OPEN";
+    case enums::LEGACY_COVER_COMMAND_CLOSE:
+      return "LEGACY_COVER_COMMAND_CLOSE";
+    case enums::LEGACY_COVER_COMMAND_STOP:
+      return "LEGACY_COVER_COMMAND_STOP";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_FAN
+template<> const char *proto_enum_to_string(enums::FanSpeed value) {
+  switch (value) {
+    case enums::FAN_SPEED_LOW:
+      return "FAN_SPEED_LOW";
+    case enums::FAN_SPEED_MEDIUM:
+      return "FAN_SPEED_MEDIUM";
+    case enums::FAN_SPEED_HIGH:
+      return "FAN_SPEED_HIGH";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::FanDirection value) {
+  switch (value) {
+    case enums::FAN_DIRECTION_FORWARD:
+      return "FAN_DIRECTION_FORWARD";
+    case enums::FAN_DIRECTION_REVERSE:
+      return "FAN_DIRECTION_REVERSE";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_LIGHT
+template<> const char *proto_enum_to_string(enums::ColorMode value) {
+  switch (value) {
+    case enums::COLOR_MODE_UNKNOWN:
+      return "COLOR_MODE_UNKNOWN";
+    case enums::COLOR_MODE_ON_OFF:
+      return "COLOR_MODE_ON_OFF";
+    case enums::COLOR_MODE_LEGACY_BRIGHTNESS:
+      return "COLOR_MODE_LEGACY_BRIGHTNESS";
+    case enums::COLOR_MODE_BRIGHTNESS:
+      return "COLOR_MODE_BRIGHTNESS";
+    case enums::COLOR_MODE_WHITE:
+      return "COLOR_MODE_WHITE";
+    case enums::COLOR_MODE_COLOR_TEMPERATURE:
+      return "COLOR_MODE_COLOR_TEMPERATURE";
+    case enums::COLOR_MODE_COLD_WARM_WHITE:
+      return "COLOR_MODE_COLD_WARM_WHITE";
+    case enums::COLOR_MODE_RGB:
+      return "COLOR_MODE_RGB";
+    case enums::COLOR_MODE_RGB_WHITE:
+      return "COLOR_MODE_RGB_WHITE";
+    case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE:
+      return "COLOR_MODE_RGB_COLOR_TEMPERATURE";
+    case enums::COLOR_MODE_RGB_COLD_WARM_WHITE:
+      return "COLOR_MODE_RGB_COLD_WARM_WHITE";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_SENSOR
+template<> const char *proto_enum_to_string(enums::SensorStateClass value) {
+  switch (value) {
+    case enums::STATE_CLASS_NONE:
+      return "STATE_CLASS_NONE";
+    case enums::STATE_CLASS_MEASUREMENT:
+      return "STATE_CLASS_MEASUREMENT";
+    case enums::STATE_CLASS_TOTAL_INCREASING:
+      return "STATE_CLASS_TOTAL_INCREASING";
+    case enums::STATE_CLASS_TOTAL:
+      return "STATE_CLASS_TOTAL";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::SensorLastResetType value) {
+  switch (value) {
+    case enums::LAST_RESET_NONE:
+      return "LAST_RESET_NONE";
+    case enums::LAST_RESET_NEVER:
+      return "LAST_RESET_NEVER";
+    case enums::LAST_RESET_AUTO:
+      return "LAST_RESET_AUTO";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+template<> const char *proto_enum_to_string(enums::LogLevel value) {
+  switch (value) {
+    case enums::LOG_LEVEL_NONE:
+      return "LOG_LEVEL_NONE";
+    case enums::LOG_LEVEL_ERROR:
+      return "LOG_LEVEL_ERROR";
+    case enums::LOG_LEVEL_WARN:
+      return "LOG_LEVEL_WARN";
+    case enums::LOG_LEVEL_INFO:
+      return "LOG_LEVEL_INFO";
+    case enums::LOG_LEVEL_CONFIG:
+      return "LOG_LEVEL_CONFIG";
+    case enums::LOG_LEVEL_DEBUG:
+      return "LOG_LEVEL_DEBUG";
+    case enums::LOG_LEVEL_VERBOSE:
+      return "LOG_LEVEL_VERBOSE";
+    case enums::LOG_LEVEL_VERY_VERBOSE:
+      return "LOG_LEVEL_VERY_VERBOSE";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::ServiceArgType value) {
+  switch (value) {
+    case enums::SERVICE_ARG_TYPE_BOOL:
+      return "SERVICE_ARG_TYPE_BOOL";
+    case enums::SERVICE_ARG_TYPE_INT:
+      return "SERVICE_ARG_TYPE_INT";
+    case enums::SERVICE_ARG_TYPE_FLOAT:
+      return "SERVICE_ARG_TYPE_FLOAT";
+    case enums::SERVICE_ARG_TYPE_STRING:
+      return "SERVICE_ARG_TYPE_STRING";
+    case enums::SERVICE_ARG_TYPE_BOOL_ARRAY:
+      return "SERVICE_ARG_TYPE_BOOL_ARRAY";
+    case enums::SERVICE_ARG_TYPE_INT_ARRAY:
+      return "SERVICE_ARG_TYPE_INT_ARRAY";
+    case enums::SERVICE_ARG_TYPE_FLOAT_ARRAY:
+      return "SERVICE_ARG_TYPE_FLOAT_ARRAY";
+    case enums::SERVICE_ARG_TYPE_STRING_ARRAY:
+      return "SERVICE_ARG_TYPE_STRING_ARRAY";
+    default:
+      return "UNKNOWN";
+  }
+}
+#ifdef USE_CLIMATE
+template<> const char *proto_enum_to_string(enums::ClimateMode value) {
+  switch (value) {
+    case enums::CLIMATE_MODE_OFF:
+      return "CLIMATE_MODE_OFF";
+    case enums::CLIMATE_MODE_HEAT_COOL:
+      return "CLIMATE_MODE_HEAT_COOL";
+    case enums::CLIMATE_MODE_COOL:
+      return "CLIMATE_MODE_COOL";
+    case enums::CLIMATE_MODE_HEAT:
+      return "CLIMATE_MODE_HEAT";
+    case enums::CLIMATE_MODE_FAN_ONLY:
+      return "CLIMATE_MODE_FAN_ONLY";
+    case enums::CLIMATE_MODE_DRY:
+      return "CLIMATE_MODE_DRY";
+    case enums::CLIMATE_MODE_AUTO:
+      return "CLIMATE_MODE_AUTO";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::ClimateFanMode value) {
+  switch (value) {
+    case enums::CLIMATE_FAN_ON:
+      return "CLIMATE_FAN_ON";
+    case enums::CLIMATE_FAN_OFF:
+      return "CLIMATE_FAN_OFF";
+    case enums::CLIMATE_FAN_AUTO:
+      return "CLIMATE_FAN_AUTO";
+    case enums::CLIMATE_FAN_LOW:
+      return "CLIMATE_FAN_LOW";
+    case enums::CLIMATE_FAN_MEDIUM:
+      return "CLIMATE_FAN_MEDIUM";
+    case enums::CLIMATE_FAN_HIGH:
+      return "CLIMATE_FAN_HIGH";
+    case enums::CLIMATE_FAN_MIDDLE:
+      return "CLIMATE_FAN_MIDDLE";
+    case enums::CLIMATE_FAN_FOCUS:
+      return "CLIMATE_FAN_FOCUS";
+    case enums::CLIMATE_FAN_DIFFUSE:
+      return "CLIMATE_FAN_DIFFUSE";
+    case enums::CLIMATE_FAN_QUIET:
+      return "CLIMATE_FAN_QUIET";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::ClimateSwingMode value) {
+  switch (value) {
+    case enums::CLIMATE_SWING_OFF:
+      return "CLIMATE_SWING_OFF";
+    case enums::CLIMATE_SWING_BOTH:
+      return "CLIMATE_SWING_BOTH";
+    case enums::CLIMATE_SWING_VERTICAL:
+      return "CLIMATE_SWING_VERTICAL";
+    case enums::CLIMATE_SWING_HORIZONTAL:
+      return "CLIMATE_SWING_HORIZONTAL";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::ClimateAction value) {
+  switch (value) {
+    case enums::CLIMATE_ACTION_OFF:
+      return "CLIMATE_ACTION_OFF";
+    case enums::CLIMATE_ACTION_COOLING:
+      return "CLIMATE_ACTION_COOLING";
+    case enums::CLIMATE_ACTION_HEATING:
+      return "CLIMATE_ACTION_HEATING";
+    case enums::CLIMATE_ACTION_IDLE:
+      return "CLIMATE_ACTION_IDLE";
+    case enums::CLIMATE_ACTION_DRYING:
+      return "CLIMATE_ACTION_DRYING";
+    case enums::CLIMATE_ACTION_FAN:
+      return "CLIMATE_ACTION_FAN";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::ClimatePreset value) {
+  switch (value) {
+    case enums::CLIMATE_PRESET_NONE:
+      return "CLIMATE_PRESET_NONE";
+    case enums::CLIMATE_PRESET_HOME:
+      return "CLIMATE_PRESET_HOME";
+    case enums::CLIMATE_PRESET_AWAY:
+      return "CLIMATE_PRESET_AWAY";
+    case enums::CLIMATE_PRESET_BOOST:
+      return "CLIMATE_PRESET_BOOST";
+    case enums::CLIMATE_PRESET_COMFORT:
+      return "CLIMATE_PRESET_COMFORT";
+    case enums::CLIMATE_PRESET_ECO:
+      return "CLIMATE_PRESET_ECO";
+    case enums::CLIMATE_PRESET_SLEEP:
+      return "CLIMATE_PRESET_SLEEP";
+    case enums::CLIMATE_PRESET_ACTIVITY:
+      return "CLIMATE_PRESET_ACTIVITY";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_NUMBER
+template<> const char *proto_enum_to_string(enums::NumberMode value) {
+  switch (value) {
+    case enums::NUMBER_MODE_AUTO:
+      return "NUMBER_MODE_AUTO";
+    case enums::NUMBER_MODE_BOX:
+      return "NUMBER_MODE_BOX";
+    case enums::NUMBER_MODE_SLIDER:
+      return "NUMBER_MODE_SLIDER";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_LOCK
+template<> const char *proto_enum_to_string(enums::LockState value) {
+  switch (value) {
+    case enums::LOCK_STATE_NONE:
+      return "LOCK_STATE_NONE";
+    case enums::LOCK_STATE_LOCKED:
+      return "LOCK_STATE_LOCKED";
+    case enums::LOCK_STATE_UNLOCKED:
+      return "LOCK_STATE_UNLOCKED";
+    case enums::LOCK_STATE_JAMMED:
+      return "LOCK_STATE_JAMMED";
+    case enums::LOCK_STATE_LOCKING:
+      return "LOCK_STATE_LOCKING";
+    case enums::LOCK_STATE_UNLOCKING:
+      return "LOCK_STATE_UNLOCKING";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::LockCommand value) {
+  switch (value) {
+    case enums::LOCK_UNLOCK:
+      return "LOCK_UNLOCK";
+    case enums::LOCK_LOCK:
+      return "LOCK_LOCK";
+    case enums::LOCK_OPEN:
+      return "LOCK_OPEN";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_MEDIA_PLAYER
+template<> const char *proto_enum_to_string(enums::MediaPlayerState value) {
+  switch (value) {
+    case enums::MEDIA_PLAYER_STATE_NONE:
+      return "MEDIA_PLAYER_STATE_NONE";
+    case enums::MEDIA_PLAYER_STATE_IDLE:
+      return "MEDIA_PLAYER_STATE_IDLE";
+    case enums::MEDIA_PLAYER_STATE_PLAYING:
+      return "MEDIA_PLAYER_STATE_PLAYING";
+    case enums::MEDIA_PLAYER_STATE_PAUSED:
+      return "MEDIA_PLAYER_STATE_PAUSED";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::MediaPlayerCommand value) {
+  switch (value) {
+    case enums::MEDIA_PLAYER_COMMAND_PLAY:
+      return "MEDIA_PLAYER_COMMAND_PLAY";
+    case enums::MEDIA_PLAYER_COMMAND_PAUSE:
+      return "MEDIA_PLAYER_COMMAND_PAUSE";
+    case enums::MEDIA_PLAYER_COMMAND_STOP:
+      return "MEDIA_PLAYER_COMMAND_STOP";
+    case enums::MEDIA_PLAYER_COMMAND_MUTE:
+      return "MEDIA_PLAYER_COMMAND_MUTE";
+    case enums::MEDIA_PLAYER_COMMAND_UNMUTE:
+      return "MEDIA_PLAYER_COMMAND_UNMUTE";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::MediaPlayerFormatPurpose value) {
+  switch (value) {
+    case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT:
+      return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT";
+    case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT:
+      return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_BLUETOOTH_PROXY
+template<>
+const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) {
+  switch (value) {
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
+    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
+      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::BluetoothScannerState value) {
+  switch (value) {
+    case enums::BLUETOOTH_SCANNER_STATE_IDLE:
+      return "BLUETOOTH_SCANNER_STATE_IDLE";
+    case enums::BLUETOOTH_SCANNER_STATE_STARTING:
+      return "BLUETOOTH_SCANNER_STATE_STARTING";
+    case enums::BLUETOOTH_SCANNER_STATE_RUNNING:
+      return "BLUETOOTH_SCANNER_STATE_RUNNING";
+    case enums::BLUETOOTH_SCANNER_STATE_FAILED:
+      return "BLUETOOTH_SCANNER_STATE_FAILED";
+    case enums::BLUETOOTH_SCANNER_STATE_STOPPING:
+      return "BLUETOOTH_SCANNER_STATE_STOPPING";
+    case enums::BLUETOOTH_SCANNER_STATE_STOPPED:
+      return "BLUETOOTH_SCANNER_STATE_STOPPED";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::BluetoothScannerMode value) {
+  switch (value) {
+    case enums::BLUETOOTH_SCANNER_MODE_PASSIVE:
+      return "BLUETOOTH_SCANNER_MODE_PASSIVE";
+    case enums::BLUETOOTH_SCANNER_MODE_ACTIVE:
+      return "BLUETOOTH_SCANNER_MODE_ACTIVE";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+template<>
+const char *proto_enum_to_string(enums::VoiceAssistantSubscribeFlag value) {
+  switch (value) {
+    case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE:
+      return "VOICE_ASSISTANT_SUBSCRIBE_NONE";
+    case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO:
+      return "VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::VoiceAssistantRequestFlag value) {
+  switch (value) {
+    case enums::VOICE_ASSISTANT_REQUEST_NONE:
+      return "VOICE_ASSISTANT_REQUEST_NONE";
+    case enums::VOICE_ASSISTANT_REQUEST_USE_VAD:
+      return "VOICE_ASSISTANT_REQUEST_USE_VAD";
+    case enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD:
+      return "VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD";
+    default:
+      return "UNKNOWN";
+  }
+}
+#ifdef USE_VOICE_ASSISTANT
+template<> const char *proto_enum_to_string(enums::VoiceAssistantEvent value) {
+  switch (value) {
+    case enums::VOICE_ASSISTANT_ERROR:
+      return "VOICE_ASSISTANT_ERROR";
+    case enums::VOICE_ASSISTANT_RUN_START:
+      return "VOICE_ASSISTANT_RUN_START";
+    case enums::VOICE_ASSISTANT_RUN_END:
+      return "VOICE_ASSISTANT_RUN_END";
+    case enums::VOICE_ASSISTANT_STT_START:
+      return "VOICE_ASSISTANT_STT_START";
+    case enums::VOICE_ASSISTANT_STT_END:
+      return "VOICE_ASSISTANT_STT_END";
+    case enums::VOICE_ASSISTANT_INTENT_START:
+      return "VOICE_ASSISTANT_INTENT_START";
+    case enums::VOICE_ASSISTANT_INTENT_END:
+      return "VOICE_ASSISTANT_INTENT_END";
+    case enums::VOICE_ASSISTANT_TTS_START:
+      return "VOICE_ASSISTANT_TTS_START";
+    case enums::VOICE_ASSISTANT_TTS_END:
+      return "VOICE_ASSISTANT_TTS_END";
+    case enums::VOICE_ASSISTANT_WAKE_WORD_START:
+      return "VOICE_ASSISTANT_WAKE_WORD_START";
+    case enums::VOICE_ASSISTANT_WAKE_WORD_END:
+      return "VOICE_ASSISTANT_WAKE_WORD_END";
+    case enums::VOICE_ASSISTANT_STT_VAD_START:
+      return "VOICE_ASSISTANT_STT_VAD_START";
+    case enums::VOICE_ASSISTANT_STT_VAD_END:
+      return "VOICE_ASSISTANT_STT_VAD_END";
+    case enums::VOICE_ASSISTANT_TTS_STREAM_START:
+      return "VOICE_ASSISTANT_TTS_STREAM_START";
+    case enums::VOICE_ASSISTANT_TTS_STREAM_END:
+      return "VOICE_ASSISTANT_TTS_STREAM_END";
+    case enums::VOICE_ASSISTANT_INTENT_PROGRESS:
+      return "VOICE_ASSISTANT_INTENT_PROGRESS";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<> const char *proto_enum_to_string(enums::VoiceAssistantTimerEvent value) {
+  switch (value) {
+    case enums::VOICE_ASSISTANT_TIMER_STARTED:
+      return "VOICE_ASSISTANT_TIMER_STARTED";
+    case enums::VOICE_ASSISTANT_TIMER_UPDATED:
+      return "VOICE_ASSISTANT_TIMER_UPDATED";
+    case enums::VOICE_ASSISTANT_TIMER_CANCELLED:
+      return "VOICE_ASSISTANT_TIMER_CANCELLED";
+    case enums::VOICE_ASSISTANT_TIMER_FINISHED:
+      return "VOICE_ASSISTANT_TIMER_FINISHED";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_ALARM_CONTROL_PANEL
+template<> const char *proto_enum_to_string(enums::AlarmControlPanelState value) {
+  switch (value) {
+    case enums::ALARM_STATE_DISARMED:
+      return "ALARM_STATE_DISARMED";
+    case enums::ALARM_STATE_ARMED_HOME:
+      return "ALARM_STATE_ARMED_HOME";
+    case enums::ALARM_STATE_ARMED_AWAY:
+      return "ALARM_STATE_ARMED_AWAY";
+    case enums::ALARM_STATE_ARMED_NIGHT:
+      return "ALARM_STATE_ARMED_NIGHT";
+    case enums::ALARM_STATE_ARMED_VACATION:
+      return "ALARM_STATE_ARMED_VACATION";
+    case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
+      return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
+    case enums::ALARM_STATE_PENDING:
+      return "ALARM_STATE_PENDING";
+    case enums::ALARM_STATE_ARMING:
+      return "ALARM_STATE_ARMING";
+    case enums::ALARM_STATE_DISARMING:
+      return "ALARM_STATE_DISARMING";
+    case enums::ALARM_STATE_TRIGGERED:
+      return "ALARM_STATE_TRIGGERED";
+    default:
+      return "UNKNOWN";
+  }
+}
+template<>
+const char *proto_enum_to_string(enums::AlarmControlPanelStateCommand value) {
+  switch (value) {
+    case enums::ALARM_CONTROL_PANEL_DISARM:
+      return "ALARM_CONTROL_PANEL_DISARM";
+    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
+      return "ALARM_CONTROL_PANEL_ARM_AWAY";
+    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
+      return "ALARM_CONTROL_PANEL_ARM_HOME";
+    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
+      return "ALARM_CONTROL_PANEL_ARM_NIGHT";
+    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
+      return "ALARM_CONTROL_PANEL_ARM_VACATION";
+    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
+      return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
+    case enums::ALARM_CONTROL_PANEL_TRIGGER:
+      return "ALARM_CONTROL_PANEL_TRIGGER";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_TEXT
+template<> const char *proto_enum_to_string(enums::TextMode value) {
+  switch (value) {
+    case enums::TEXT_MODE_TEXT:
+      return "TEXT_MODE_TEXT";
+    case enums::TEXT_MODE_PASSWORD:
+      return "TEXT_MODE_PASSWORD";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_VALVE
+template<> const char *proto_enum_to_string(enums::ValveOperation value) {
+  switch (value) {
+    case enums::VALVE_OPERATION_IDLE:
+      return "VALVE_OPERATION_IDLE";
+    case enums::VALVE_OPERATION_IS_OPENING:
+      return "VALVE_OPERATION_IS_OPENING";
+    case enums::VALVE_OPERATION_IS_CLOSING:
+      return "VALVE_OPERATION_IS_CLOSING";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+#ifdef USE_UPDATE
+template<> const char *proto_enum_to_string(enums::UpdateCommand value) {
+  switch (value) {
+    case enums::UPDATE_COMMAND_NONE:
+      return "UPDATE_COMMAND_NONE";
+    case enums::UPDATE_COMMAND_UPDATE:
+      return "UPDATE_COMMAND_UPDATE";
+    case enums::UPDATE_COMMAND_CHECK:
+      return "UPDATE_COMMAND_CHECK";
+    default:
+      return "UNKNOWN";
+  }
+}
+#endif
+
+void HelloRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("HelloRequest {\n");
+  out.append("  client_info: ");
+  out.append("'").append(this->client_info).append("'");
+  out.append("\n");
+
+  out.append("  api_version_major: ");
+  sprintf(buffer, "%" PRIu32, this->api_version_major);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  api_version_minor: ");
+  sprintf(buffer, "%" PRIu32, this->api_version_minor);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void HelloResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("HelloResponse {\n");
+  out.append("  api_version_major: ");
+  sprintf(buffer, "%" PRIu32, this->api_version_major);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  api_version_minor: ");
+  sprintf(buffer, "%" PRIu32, this->api_version_minor);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  server_info: ");
+  out.append("'").append(this->server_info).append("'");
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void ConnectRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ConnectRequest {\n");
+  out.append("  password: ");
+  out.append("'").append(this->password).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void ConnectResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ConnectResponse {\n");
+  out.append("  invalid_password: ");
+  out.append(YESNO(this->invalid_password));
+  out.append("\n");
+  out.append("}");
+}
+void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
+void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
+void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
+void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); }
+void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
+void AreaInfo::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("AreaInfo {\n");
+  out.append("  area_id: ");
+  sprintf(buffer, "%" PRIu32, this->area_id);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void DeviceInfo::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DeviceInfo {\n");
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  area_id: ");
+  sprintf(buffer, "%" PRIu32, this->area_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void DeviceInfoResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DeviceInfoResponse {\n");
+  out.append("  uses_password: ");
+  out.append(YESNO(this->uses_password));
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  mac_address: ");
+  out.append("'").append(this->mac_address).append("'");
+  out.append("\n");
+
+  out.append("  esphome_version: ");
+  out.append("'").append(this->esphome_version).append("'");
+  out.append("\n");
+
+  out.append("  compilation_time: ");
+  out.append("'").append(this->compilation_time).append("'");
+  out.append("\n");
+
+  out.append("  model: ");
+  out.append("'").append(this->model).append("'");
+  out.append("\n");
+
+  out.append("  has_deep_sleep: ");
+  out.append(YESNO(this->has_deep_sleep));
+  out.append("\n");
+
+  out.append("  project_name: ");
+  out.append("'").append(this->project_name).append("'");
+  out.append("\n");
+
+  out.append("  project_version: ");
+  out.append("'").append(this->project_version).append("'");
+  out.append("\n");
+
+  out.append("  webserver_port: ");
+  sprintf(buffer, "%" PRIu32, this->webserver_port);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  legacy_bluetooth_proxy_version: ");
+  sprintf(buffer, "%" PRIu32, this->legacy_bluetooth_proxy_version);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  bluetooth_proxy_feature_flags: ");
+  sprintf(buffer, "%" PRIu32, this->bluetooth_proxy_feature_flags);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  manufacturer: ");
+  out.append("'").append(this->manufacturer).append("'");
+  out.append("\n");
+
+  out.append("  friendly_name: ");
+  out.append("'").append(this->friendly_name).append("'");
+  out.append("\n");
+
+  out.append("  legacy_voice_assistant_version: ");
+  sprintf(buffer, "%" PRIu32, this->legacy_voice_assistant_version);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  voice_assistant_feature_flags: ");
+  sprintf(buffer, "%" PRIu32, this->voice_assistant_feature_flags);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  suggested_area: ");
+  out.append("'").append(this->suggested_area).append("'");
+  out.append("\n");
+
+  out.append("  bluetooth_mac_address: ");
+  out.append("'").append(this->bluetooth_mac_address).append("'");
+  out.append("\n");
+
+  out.append("  api_encryption_supported: ");
+  out.append(YESNO(this->api_encryption_supported));
+  out.append("\n");
+
+  for (const auto &it : this->devices) {
+    out.append("  devices: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->areas) {
+    out.append("  areas: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  out.append("  area: ");
+  this->area.dump_to(out);
+  out.append("\n");
+  out.append("}");
+}
+void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
+void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
+void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); }
+#ifdef USE_BINARY_SENSOR
+void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesBinarySensorResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  is_status_binary_sensor: ");
+  out.append(YESNO(this->is_status_binary_sensor));
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BinarySensorStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BinarySensorStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_COVER
+void ListEntitiesCoverResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesCoverResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  assumed_state: ");
+  out.append(YESNO(this->assumed_state));
+  out.append("\n");
+
+  out.append("  supports_position: ");
+  out.append(YESNO(this->supports_position));
+  out.append("\n");
+
+  out.append("  supports_tilt: ");
+  out.append(YESNO(this->supports_tilt));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  supports_stop: ");
+  out.append(YESNO(this->supports_stop));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void CoverStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("CoverStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  legacy_state: ");
+  out.append(proto_enum_to_string(this->legacy_state));
+  out.append("\n");
+
+  out.append("  position: ");
+  sprintf(buffer, "%g", this->position);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  tilt: ");
+  sprintf(buffer, "%g", this->tilt);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  current_operation: ");
+  out.append(proto_enum_to_string(this->current_operation));
+  out.append("\n");
+  out.append("}");
+}
+void CoverCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("CoverCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_legacy_command: ");
+  out.append(YESNO(this->has_legacy_command));
+  out.append("\n");
+
+  out.append("  legacy_command: ");
+  out.append(proto_enum_to_string(this->legacy_command));
+  out.append("\n");
+
+  out.append("  has_position: ");
+  out.append(YESNO(this->has_position));
+  out.append("\n");
+
+  out.append("  position: ");
+  sprintf(buffer, "%g", this->position);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_tilt: ");
+  out.append(YESNO(this->has_tilt));
+  out.append("\n");
+
+  out.append("  tilt: ");
+  sprintf(buffer, "%g", this->tilt);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  stop: ");
+  out.append(YESNO(this->stop));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_FAN
+void ListEntitiesFanResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesFanResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  supports_oscillation: ");
+  out.append(YESNO(this->supports_oscillation));
+  out.append("\n");
+
+  out.append("  supports_speed: ");
+  out.append(YESNO(this->supports_speed));
+  out.append("\n");
+
+  out.append("  supports_direction: ");
+  out.append(YESNO(this->supports_direction));
+  out.append("\n");
+
+  out.append("  supported_speed_count: ");
+  sprintf(buffer, "%" PRId32, this->supported_speed_count);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  for (const auto &it : this->supported_preset_modes) {
+    out.append("  supported_preset_modes: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void FanStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("FanStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  oscillating: ");
+  out.append(YESNO(this->oscillating));
+  out.append("\n");
+
+  out.append("  speed: ");
+  out.append(proto_enum_to_string(this->speed));
+  out.append("\n");
+
+  out.append("  direction: ");
+  out.append(proto_enum_to_string(this->direction));
+  out.append("\n");
+
+  out.append("  speed_level: ");
+  sprintf(buffer, "%" PRId32, this->speed_level);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  preset_mode: ");
+  out.append("'").append(this->preset_mode).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void FanCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("FanCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_state: ");
+  out.append(YESNO(this->has_state));
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  has_speed: ");
+  out.append(YESNO(this->has_speed));
+  out.append("\n");
+
+  out.append("  speed: ");
+  out.append(proto_enum_to_string(this->speed));
+  out.append("\n");
+
+  out.append("  has_oscillating: ");
+  out.append(YESNO(this->has_oscillating));
+  out.append("\n");
+
+  out.append("  oscillating: ");
+  out.append(YESNO(this->oscillating));
+  out.append("\n");
+
+  out.append("  has_direction: ");
+  out.append(YESNO(this->has_direction));
+  out.append("\n");
+
+  out.append("  direction: ");
+  out.append(proto_enum_to_string(this->direction));
+  out.append("\n");
+
+  out.append("  has_speed_level: ");
+  out.append(YESNO(this->has_speed_level));
+  out.append("\n");
+
+  out.append("  speed_level: ");
+  sprintf(buffer, "%" PRId32, this->speed_level);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_preset_mode: ");
+  out.append(YESNO(this->has_preset_mode));
+  out.append("\n");
+
+  out.append("  preset_mode: ");
+  out.append("'").append(this->preset_mode).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_LIGHT
+void ListEntitiesLightResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesLightResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->supported_color_modes) {
+    out.append("  supported_color_modes: ");
+    out.append(proto_enum_to_string(it));
+    out.append("\n");
+  }
+
+  out.append("  legacy_supports_brightness: ");
+  out.append(YESNO(this->legacy_supports_brightness));
+  out.append("\n");
+
+  out.append("  legacy_supports_rgb: ");
+  out.append(YESNO(this->legacy_supports_rgb));
+  out.append("\n");
+
+  out.append("  legacy_supports_white_value: ");
+  out.append(YESNO(this->legacy_supports_white_value));
+  out.append("\n");
+
+  out.append("  legacy_supports_color_temperature: ");
+  out.append(YESNO(this->legacy_supports_color_temperature));
+  out.append("\n");
+
+  out.append("  min_mireds: ");
+  sprintf(buffer, "%g", this->min_mireds);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  max_mireds: ");
+  sprintf(buffer, "%g", this->max_mireds);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->effects) {
+    out.append("  effects: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void LightStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("LightStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  brightness: ");
+  sprintf(buffer, "%g", this->brightness);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  color_mode: ");
+  out.append(proto_enum_to_string(this->color_mode));
+  out.append("\n");
+
+  out.append("  color_brightness: ");
+  sprintf(buffer, "%g", this->color_brightness);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  red: ");
+  sprintf(buffer, "%g", this->red);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  green: ");
+  sprintf(buffer, "%g", this->green);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  blue: ");
+  sprintf(buffer, "%g", this->blue);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  white: ");
+  sprintf(buffer, "%g", this->white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  color_temperature: ");
+  sprintf(buffer, "%g", this->color_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  cold_white: ");
+  sprintf(buffer, "%g", this->cold_white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  warm_white: ");
+  sprintf(buffer, "%g", this->warm_white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  effect: ");
+  out.append("'").append(this->effect).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void LightCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("LightCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_state: ");
+  out.append(YESNO(this->has_state));
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  has_brightness: ");
+  out.append(YESNO(this->has_brightness));
+  out.append("\n");
+
+  out.append("  brightness: ");
+  sprintf(buffer, "%g", this->brightness);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_color_mode: ");
+  out.append(YESNO(this->has_color_mode));
+  out.append("\n");
+
+  out.append("  color_mode: ");
+  out.append(proto_enum_to_string(this->color_mode));
+  out.append("\n");
+
+  out.append("  has_color_brightness: ");
+  out.append(YESNO(this->has_color_brightness));
+  out.append("\n");
+
+  out.append("  color_brightness: ");
+  sprintf(buffer, "%g", this->color_brightness);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_rgb: ");
+  out.append(YESNO(this->has_rgb));
+  out.append("\n");
+
+  out.append("  red: ");
+  sprintf(buffer, "%g", this->red);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  green: ");
+  sprintf(buffer, "%g", this->green);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  blue: ");
+  sprintf(buffer, "%g", this->blue);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_white: ");
+  out.append(YESNO(this->has_white));
+  out.append("\n");
+
+  out.append("  white: ");
+  sprintf(buffer, "%g", this->white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_color_temperature: ");
+  out.append(YESNO(this->has_color_temperature));
+  out.append("\n");
+
+  out.append("  color_temperature: ");
+  sprintf(buffer, "%g", this->color_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_cold_white: ");
+  out.append(YESNO(this->has_cold_white));
+  out.append("\n");
+
+  out.append("  cold_white: ");
+  sprintf(buffer, "%g", this->cold_white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_warm_white: ");
+  out.append(YESNO(this->has_warm_white));
+  out.append("\n");
+
+  out.append("  warm_white: ");
+  sprintf(buffer, "%g", this->warm_white);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_transition_length: ");
+  out.append(YESNO(this->has_transition_length));
+  out.append("\n");
+
+  out.append("  transition_length: ");
+  sprintf(buffer, "%" PRIu32, this->transition_length);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_flash_length: ");
+  out.append(YESNO(this->has_flash_length));
+  out.append("\n");
+
+  out.append("  flash_length: ");
+  sprintf(buffer, "%" PRIu32, this->flash_length);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_effect: ");
+  out.append(YESNO(this->has_effect));
+  out.append("\n");
+
+  out.append("  effect: ");
+  out.append("'").append(this->effect).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_SENSOR
+void ListEntitiesSensorResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesSensorResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  unit_of_measurement: ");
+  out.append("'").append(this->unit_of_measurement).append("'");
+  out.append("\n");
+
+  out.append("  accuracy_decimals: ");
+  sprintf(buffer, "%" PRId32, this->accuracy_decimals);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  force_update: ");
+  out.append(YESNO(this->force_update));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  state_class: ");
+  out.append(proto_enum_to_string(this->state_class));
+  out.append("\n");
+
+  out.append("  legacy_last_reset_type: ");
+  out.append(proto_enum_to_string(this->legacy_last_reset_type));
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void SensorStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SensorStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  sprintf(buffer, "%g", this->state);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_SWITCH
+void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesSwitchResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  assumed_state: ");
+  out.append(YESNO(this->assumed_state));
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void SwitchStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SwitchStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+  out.append("}");
+}
+void SwitchCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SwitchCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_TEXT_SENSOR
+void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesTextSensorResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void TextSensorStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("TextSensorStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+void SubscribeLogsRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SubscribeLogsRequest {\n");
+  out.append("  level: ");
+  out.append(proto_enum_to_string(this->level));
+  out.append("\n");
+
+  out.append("  dump_config: ");
+  out.append(YESNO(this->dump_config));
+  out.append("\n");
+  out.append("}");
+}
+void SubscribeLogsResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SubscribeLogsResponse {\n");
+  out.append("  level: ");
+  out.append(proto_enum_to_string(this->level));
+  out.append("\n");
+
+  out.append("  message: ");
+  out.append(format_hex_pretty(this->message));
+  out.append("\n");
+
+  out.append("  send_failed: ");
+  out.append(YESNO(this->send_failed));
+  out.append("\n");
+  out.append("}");
+}
+#ifdef USE_API_NOISE
+void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("NoiseEncryptionSetKeyRequest {\n");
+  out.append("  key: ");
+  out.append(format_hex_pretty(this->key));
+  out.append("\n");
+  out.append("}");
+}
+void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("NoiseEncryptionSetKeyResponse {\n");
+  out.append("  success: ");
+  out.append(YESNO(this->success));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
+  out.append("SubscribeHomeassistantServicesRequest {}");
+}
+void HomeassistantServiceMap::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("HomeassistantServiceMap {\n");
+  out.append("  key: ");
+  out.append("'").append(this->key).append("'");
+  out.append("\n");
+
+  out.append("  value: ");
+  out.append("'").append(this->value).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void HomeassistantServiceResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("HomeassistantServiceResponse {\n");
+  out.append("  service: ");
+  out.append("'").append(this->service).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->data) {
+    out.append("  data: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->data_template) {
+    out.append("  data_template: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->variables) {
+    out.append("  variables: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  out.append("  is_event: ");
+  out.append(YESNO(this->is_event));
+  out.append("\n");
+  out.append("}");
+}
+void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
+  out.append("SubscribeHomeAssistantStatesRequest {}");
+}
+void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SubscribeHomeAssistantStateResponse {\n");
+  out.append("  entity_id: ");
+  out.append("'").append(this->entity_id).append("'");
+  out.append("\n");
+
+  out.append("  attribute: ");
+  out.append("'").append(this->attribute).append("'");
+  out.append("\n");
+
+  out.append("  once: ");
+  out.append(YESNO(this->once));
+  out.append("\n");
+  out.append("}");
+}
+void HomeAssistantStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("HomeAssistantStateResponse {\n");
+  out.append("  entity_id: ");
+  out.append("'").append(this->entity_id).append("'");
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+
+  out.append("  attribute: ");
+  out.append("'").append(this->attribute).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
+void GetTimeResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("GetTimeResponse {\n");
+  out.append("  epoch_seconds: ");
+  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ListEntitiesServicesArgument::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesServicesArgument {\n");
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  type: ");
+  out.append(proto_enum_to_string(this->type));
+  out.append("\n");
+  out.append("}");
+}
+void ListEntitiesServicesResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesServicesResponse {\n");
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->args) {
+    out.append("  args: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void ExecuteServiceArgument::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ExecuteServiceArgument {\n");
+  out.append("  bool_: ");
+  out.append(YESNO(this->bool_));
+  out.append("\n");
+
+  out.append("  legacy_int: ");
+  sprintf(buffer, "%" PRId32, this->legacy_int);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  float_: ");
+  sprintf(buffer, "%g", this->float_);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  string_: ");
+  out.append("'").append(this->string_).append("'");
+  out.append("\n");
+
+  out.append("  int_: ");
+  sprintf(buffer, "%" PRId32, this->int_);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto it : this->bool_array) {
+    out.append("  bool_array: ");
+    out.append(YESNO(it));
+    out.append("\n");
+  }
+
+  for (const auto &it : this->int_array) {
+    out.append("  int_array: ");
+    sprintf(buffer, "%" PRId32, it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->float_array) {
+    out.append("  float_array: ");
+    sprintf(buffer, "%g", it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->string_array) {
+    out.append("  string_array: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+  out.append("}");
+}
+void ExecuteServiceRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ExecuteServiceRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->args) {
+    out.append("  args: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+#ifdef USE_ESP32_CAMERA
+void ListEntitiesCameraResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesCameraResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void CameraImageResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("CameraImageResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+
+  out.append("  done: ");
+  out.append(YESNO(this->done));
+  out.append("\n");
+  out.append("}");
+}
+void CameraImageRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("CameraImageRequest {\n");
+  out.append("  single: ");
+  out.append(YESNO(this->single));
+  out.append("\n");
+
+  out.append("  stream: ");
+  out.append(YESNO(this->stream));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_CLIMATE
+void ListEntitiesClimateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesClimateResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  supports_current_temperature: ");
+  out.append(YESNO(this->supports_current_temperature));
+  out.append("\n");
+
+  out.append("  supports_two_point_target_temperature: ");
+  out.append(YESNO(this->supports_two_point_target_temperature));
+  out.append("\n");
+
+  for (const auto &it : this->supported_modes) {
+    out.append("  supported_modes: ");
+    out.append(proto_enum_to_string(it));
+    out.append("\n");
+  }
+
+  out.append("  visual_min_temperature: ");
+  sprintf(buffer, "%g", this->visual_min_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  visual_max_temperature: ");
+  sprintf(buffer, "%g", this->visual_max_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  visual_target_temperature_step: ");
+  sprintf(buffer, "%g", this->visual_target_temperature_step);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  legacy_supports_away: ");
+  out.append(YESNO(this->legacy_supports_away));
+  out.append("\n");
+
+  out.append("  supports_action: ");
+  out.append(YESNO(this->supports_action));
+  out.append("\n");
+
+  for (const auto &it : this->supported_fan_modes) {
+    out.append("  supported_fan_modes: ");
+    out.append(proto_enum_to_string(it));
+    out.append("\n");
+  }
+
+  for (const auto &it : this->supported_swing_modes) {
+    out.append("  supported_swing_modes: ");
+    out.append(proto_enum_to_string(it));
+    out.append("\n");
+  }
+
+  for (const auto &it : this->supported_custom_fan_modes) {
+    out.append("  supported_custom_fan_modes: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  for (const auto &it : this->supported_presets) {
+    out.append("  supported_presets: ");
+    out.append(proto_enum_to_string(it));
+    out.append("\n");
+  }
+
+  for (const auto &it : this->supported_custom_presets) {
+    out.append("  supported_custom_presets: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  visual_current_temperature_step: ");
+  sprintf(buffer, "%g", this->visual_current_temperature_step);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  supports_current_humidity: ");
+  out.append(YESNO(this->supports_current_humidity));
+  out.append("\n");
+
+  out.append("  supports_target_humidity: ");
+  out.append(YESNO(this->supports_target_humidity));
+  out.append("\n");
+
+  out.append("  visual_min_humidity: ");
+  sprintf(buffer, "%g", this->visual_min_humidity);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  visual_max_humidity: ");
+  sprintf(buffer, "%g", this->visual_max_humidity);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ClimateStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ClimateStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+
+  out.append("  current_temperature: ");
+  sprintf(buffer, "%g", this->current_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  target_temperature: ");
+  sprintf(buffer, "%g", this->target_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  target_temperature_low: ");
+  sprintf(buffer, "%g", this->target_temperature_low);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  target_temperature_high: ");
+  sprintf(buffer, "%g", this->target_temperature_high);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  unused_legacy_away: ");
+  out.append(YESNO(this->unused_legacy_away));
+  out.append("\n");
+
+  out.append("  action: ");
+  out.append(proto_enum_to_string(this->action));
+  out.append("\n");
+
+  out.append("  fan_mode: ");
+  out.append(proto_enum_to_string(this->fan_mode));
+  out.append("\n");
+
+  out.append("  swing_mode: ");
+  out.append(proto_enum_to_string(this->swing_mode));
+  out.append("\n");
+
+  out.append("  custom_fan_mode: ");
+  out.append("'").append(this->custom_fan_mode).append("'");
+  out.append("\n");
+
+  out.append("  preset: ");
+  out.append(proto_enum_to_string(this->preset));
+  out.append("\n");
+
+  out.append("  custom_preset: ");
+  out.append("'").append(this->custom_preset).append("'");
+  out.append("\n");
+
+  out.append("  current_humidity: ");
+  sprintf(buffer, "%g", this->current_humidity);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  target_humidity: ");
+  sprintf(buffer, "%g", this->target_humidity);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ClimateCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ClimateCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_mode: ");
+  out.append(YESNO(this->has_mode));
+  out.append("\n");
+
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+
+  out.append("  has_target_temperature: ");
+  out.append(YESNO(this->has_target_temperature));
+  out.append("\n");
+
+  out.append("  target_temperature: ");
+  sprintf(buffer, "%g", this->target_temperature);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_target_temperature_low: ");
+  out.append(YESNO(this->has_target_temperature_low));
+  out.append("\n");
+
+  out.append("  target_temperature_low: ");
+  sprintf(buffer, "%g", this->target_temperature_low);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_target_temperature_high: ");
+  out.append(YESNO(this->has_target_temperature_high));
+  out.append("\n");
+
+  out.append("  target_temperature_high: ");
+  sprintf(buffer, "%g", this->target_temperature_high);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  unused_has_legacy_away: ");
+  out.append(YESNO(this->unused_has_legacy_away));
+  out.append("\n");
+
+  out.append("  unused_legacy_away: ");
+  out.append(YESNO(this->unused_legacy_away));
+  out.append("\n");
+
+  out.append("  has_fan_mode: ");
+  out.append(YESNO(this->has_fan_mode));
+  out.append("\n");
+
+  out.append("  fan_mode: ");
+  out.append(proto_enum_to_string(this->fan_mode));
+  out.append("\n");
+
+  out.append("  has_swing_mode: ");
+  out.append(YESNO(this->has_swing_mode));
+  out.append("\n");
+
+  out.append("  swing_mode: ");
+  out.append(proto_enum_to_string(this->swing_mode));
+  out.append("\n");
+
+  out.append("  has_custom_fan_mode: ");
+  out.append(YESNO(this->has_custom_fan_mode));
+  out.append("\n");
+
+  out.append("  custom_fan_mode: ");
+  out.append("'").append(this->custom_fan_mode).append("'");
+  out.append("\n");
+
+  out.append("  has_preset: ");
+  out.append(YESNO(this->has_preset));
+  out.append("\n");
+
+  out.append("  preset: ");
+  out.append(proto_enum_to_string(this->preset));
+  out.append("\n");
+
+  out.append("  has_custom_preset: ");
+  out.append(YESNO(this->has_custom_preset));
+  out.append("\n");
+
+  out.append("  custom_preset: ");
+  out.append("'").append(this->custom_preset).append("'");
+  out.append("\n");
+
+  out.append("  has_target_humidity: ");
+  out.append(YESNO(this->has_target_humidity));
+  out.append("\n");
+
+  out.append("  target_humidity: ");
+  sprintf(buffer, "%g", this->target_humidity);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_NUMBER
+void ListEntitiesNumberResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesNumberResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  min_value: ");
+  sprintf(buffer, "%g", this->min_value);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  max_value: ");
+  sprintf(buffer, "%g", this->max_value);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  step: ");
+  sprintf(buffer, "%g", this->step);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  unit_of_measurement: ");
+  out.append("'").append(this->unit_of_measurement).append("'");
+  out.append("\n");
+
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void NumberStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("NumberStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  sprintf(buffer, "%g", this->state);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+void NumberCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("NumberCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  sprintf(buffer, "%g", this->state);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_SELECT
+void ListEntitiesSelectResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesSelectResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->options) {
+    out.append("  options: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void SelectStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SelectStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+void SelectCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SelectCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_SIREN
+void ListEntitiesSirenResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesSirenResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  for (const auto &it : this->tones) {
+    out.append("  tones: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  supports_duration: ");
+  out.append(YESNO(this->supports_duration));
+  out.append("\n");
+
+  out.append("  supports_volume: ");
+  out.append(YESNO(this->supports_volume));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void SirenStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SirenStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+  out.append("}");
+}
+void SirenCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SirenCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_state: ");
+  out.append(YESNO(this->has_state));
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(YESNO(this->state));
+  out.append("\n");
+
+  out.append("  has_tone: ");
+  out.append(YESNO(this->has_tone));
+  out.append("\n");
+
+  out.append("  tone: ");
+  out.append("'").append(this->tone).append("'");
+  out.append("\n");
+
+  out.append("  has_duration: ");
+  out.append(YESNO(this->has_duration));
+  out.append("\n");
+
+  out.append("  duration: ");
+  sprintf(buffer, "%" PRIu32, this->duration);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_volume: ");
+  out.append(YESNO(this->has_volume));
+  out.append("\n");
+
+  out.append("  volume: ");
+  sprintf(buffer, "%g", this->volume);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_LOCK
+void ListEntitiesLockResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesLockResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  assumed_state: ");
+  out.append(YESNO(this->assumed_state));
+  out.append("\n");
+
+  out.append("  supports_open: ");
+  out.append(YESNO(this->supports_open));
+  out.append("\n");
+
+  out.append("  requires_code: ");
+  out.append(YESNO(this->requires_code));
+  out.append("\n");
+
+  out.append("  code_format: ");
+  out.append("'").append(this->code_format).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void LockStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("LockStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(proto_enum_to_string(this->state));
+  out.append("\n");
+  out.append("}");
+}
+void LockCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("LockCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  command: ");
+  out.append(proto_enum_to_string(this->command));
+  out.append("\n");
+
+  out.append("  has_code: ");
+  out.append(YESNO(this->has_code));
+  out.append("\n");
+
+  out.append("  code: ");
+  out.append("'").append(this->code).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_BUTTON
+void ListEntitiesButtonResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesButtonResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ButtonCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ButtonCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_MEDIA_PLAYER
+void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("MediaPlayerSupportedFormat {\n");
+  out.append("  format: ");
+  out.append("'").append(this->format).append("'");
+  out.append("\n");
+
+  out.append("  sample_rate: ");
+  sprintf(buffer, "%" PRIu32, this->sample_rate);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  num_channels: ");
+  sprintf(buffer, "%" PRIu32, this->num_channels);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  purpose: ");
+  out.append(proto_enum_to_string(this->purpose));
+  out.append("\n");
+
+  out.append("  sample_bytes: ");
+  sprintf(buffer, "%" PRIu32, this->sample_bytes);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesMediaPlayerResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  supports_pause: ");
+  out.append(YESNO(this->supports_pause));
+  out.append("\n");
+
+  for (const auto &it : this->supported_formats) {
+    out.append("  supported_formats: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void MediaPlayerStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("MediaPlayerStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(proto_enum_to_string(this->state));
+  out.append("\n");
+
+  out.append("  volume: ");
+  sprintf(buffer, "%g", this->volume);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  muted: ");
+  out.append(YESNO(this->muted));
+  out.append("\n");
+  out.append("}");
+}
+void MediaPlayerCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("MediaPlayerCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_command: ");
+  out.append(YESNO(this->has_command));
+  out.append("\n");
+
+  out.append("  command: ");
+  out.append(proto_enum_to_string(this->command));
+  out.append("\n");
+
+  out.append("  has_volume: ");
+  out.append(YESNO(this->has_volume));
+  out.append("\n");
+
+  out.append("  volume: ");
+  sprintf(buffer, "%g", this->volume);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_media_url: ");
+  out.append(YESNO(this->has_media_url));
+  out.append("\n");
+
+  out.append("  media_url: ");
+  out.append("'").append(this->media_url).append("'");
+  out.append("\n");
+
+  out.append("  has_announcement: ");
+  out.append(YESNO(this->has_announcement));
+  out.append("\n");
+
+  out.append("  announcement: ");
+  out.append(YESNO(this->announcement));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_BLUETOOTH_PROXY
+void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
+  out.append("  flags: ");
+  sprintf(buffer, "%" PRIu32, this->flags);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothServiceData::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothServiceData {\n");
+  out.append("  uuid: ");
+  out.append("'").append(this->uuid).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->legacy_data) {
+    out.append("  legacy_data: ");
+    sprintf(buffer, "%" PRIu32, it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothLEAdvertisementResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append(format_hex_pretty(this->name));
+  out.append("\n");
+
+  out.append("  rssi: ");
+  sprintf(buffer, "%" PRId32, this->rssi);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->service_uuids) {
+    out.append("  service_uuids: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  for (const auto &it : this->service_data) {
+    out.append("  service_data: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->manufacturer_data) {
+    out.append("  manufacturer_data: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  out.append("  address_type: ");
+  sprintf(buffer, "%" PRIu32, this->address_type);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothLERawAdvertisement {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  rssi: ");
+  sprintf(buffer, "%" PRId32, this->rssi);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  address_type: ");
+  sprintf(buffer, "%" PRIu32, this->address_type);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothLERawAdvertisementsResponse {\n");
+  for (const auto &it : this->advertisements) {
+    out.append("  advertisements: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void BluetoothDeviceRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothDeviceRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  request_type: ");
+  out.append(proto_enum_to_string(this->request_type));
+  out.append("\n");
+
+  out.append("  has_address_type: ");
+  out.append(YESNO(this->has_address_type));
+  out.append("\n");
+
+  out.append("  address_type: ");
+  sprintf(buffer, "%" PRIu32, this->address_type);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothDeviceConnectionResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  connected: ");
+  out.append(YESNO(this->connected));
+  out.append("\n");
+
+  out.append("  mtu: ");
+  sprintf(buffer, "%" PRIu32, this->mtu);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  error: ");
+  sprintf(buffer, "%" PRId32, this->error);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTGetServicesRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTDescriptor::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTDescriptor {\n");
+  for (const auto &it : this->uuid) {
+    out.append("  uuid: ");
+    sprintf(buffer, "%llu", it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTCharacteristic {\n");
+  for (const auto &it : this->uuid) {
+    out.append("  uuid: ");
+    sprintf(buffer, "%llu", it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  properties: ");
+  sprintf(buffer, "%" PRIu32, this->properties);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->descriptors) {
+    out.append("  descriptors: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void BluetoothGATTService::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTService {\n");
+  for (const auto &it : this->uuid) {
+    out.append("  uuid: ");
+    sprintf(buffer, "%llu", it);
+    out.append(buffer);
+    out.append("\n");
+  }
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->characteristics) {
+    out.append("  characteristics: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTGetServicesResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->services) {
+    out.append("  services: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTGetServicesDoneResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTReadRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTReadRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTReadResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTReadResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTWriteRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  response: ");
+  out.append(YESNO(this->response));
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTReadDescriptorRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTWriteDescriptorRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTNotifyRequest {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  enable: ");
+  out.append(YESNO(this->enable));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTNotifyDataResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+  out.append("}");
+}
+void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
+  out.append("SubscribeBluetoothConnectionsFreeRequest {}");
+}
+void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothConnectionsFreeResponse {\n");
+  out.append("  free: ");
+  sprintf(buffer, "%" PRIu32, this->free);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  limit: ");
+  sprintf(buffer, "%" PRIu32, this->limit);
+  out.append(buffer);
+  out.append("\n");
+
+  for (const auto &it : this->allocated) {
+    out.append("  allocated: ");
+    sprintf(buffer, "%llu", it);
+    out.append(buffer);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTErrorResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  error: ");
+  sprintf(buffer, "%" PRId32, this->error);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTWriteResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothGATTNotifyResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  handle: ");
+  sprintf(buffer, "%" PRIu32, this->handle);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothDevicePairingResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  paired: ");
+  out.append(YESNO(this->paired));
+  out.append("\n");
+
+  out.append("  error: ");
+  sprintf(buffer, "%" PRId32, this->error);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothDeviceUnpairingResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  success: ");
+  out.append(YESNO(this->success));
+  out.append("\n");
+
+  out.append("  error: ");
+  sprintf(buffer, "%" PRId32, this->error);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
+  out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
+}
+void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothDeviceClearCacheResponse {\n");
+  out.append("  address: ");
+  sprintf(buffer, "%llu", this->address);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  success: ");
+  out.append(YESNO(this->success));
+  out.append("\n");
+
+  out.append("  error: ");
+  sprintf(buffer, "%" PRId32, this->error);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothScannerStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothScannerStateResponse {\n");
+  out.append("  state: ");
+  out.append(proto_enum_to_string(this->state));
+  out.append("\n");
+
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+  out.append("}");
+}
+void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("BluetoothScannerSetModeRequest {\n");
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_VOICE_ASSISTANT
+void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("SubscribeVoiceAssistantRequest {\n");
+  out.append("  subscribe: ");
+  out.append(YESNO(this->subscribe));
+  out.append("\n");
+
+  out.append("  flags: ");
+  sprintf(buffer, "%" PRIu32, this->flags);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantAudioSettings {\n");
+  out.append("  noise_suppression_level: ");
+  sprintf(buffer, "%" PRIu32, this->noise_suppression_level);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  auto_gain: ");
+  sprintf(buffer, "%" PRIu32, this->auto_gain);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  volume_multiplier: ");
+  sprintf(buffer, "%g", this->volume_multiplier);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantRequest {\n");
+  out.append("  start: ");
+  out.append(YESNO(this->start));
+  out.append("\n");
+
+  out.append("  conversation_id: ");
+  out.append("'").append(this->conversation_id).append("'");
+  out.append("\n");
+
+  out.append("  flags: ");
+  sprintf(buffer, "%" PRIu32, this->flags);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  audio_settings: ");
+  this->audio_settings.dump_to(out);
+  out.append("\n");
+
+  out.append("  wake_word_phrase: ");
+  out.append("'").append(this->wake_word_phrase).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantResponse {\n");
+  out.append("  port: ");
+  sprintf(buffer, "%" PRIu32, this->port);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  error: ");
+  out.append(YESNO(this->error));
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantEventData::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantEventData {\n");
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  value: ");
+  out.append("'").append(this->value).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantEventResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantEventResponse {\n");
+  out.append("  event_type: ");
+  out.append(proto_enum_to_string(this->event_type));
+  out.append("\n");
+
+  for (const auto &it : this->data) {
+    out.append("  data: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+  out.append("}");
+}
+void VoiceAssistantAudio::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantAudio {\n");
+  out.append("  data: ");
+  out.append(format_hex_pretty(this->data));
+  out.append("\n");
+
+  out.append("  end: ");
+  out.append(YESNO(this->end));
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantTimerEventResponse {\n");
+  out.append("  event_type: ");
+  out.append(proto_enum_to_string(this->event_type));
+  out.append("\n");
+
+  out.append("  timer_id: ");
+  out.append("'").append(this->timer_id).append("'");
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  total_seconds: ");
+  sprintf(buffer, "%" PRIu32, this->total_seconds);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  seconds_left: ");
+  sprintf(buffer, "%" PRIu32, this->seconds_left);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  is_active: ");
+  out.append(YESNO(this->is_active));
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantAnnounceRequest {\n");
+  out.append("  media_id: ");
+  out.append("'").append(this->media_id).append("'");
+  out.append("\n");
+
+  out.append("  text: ");
+  out.append("'").append(this->text).append("'");
+  out.append("\n");
+
+  out.append("  preannounce_media_id: ");
+  out.append("'").append(this->preannounce_media_id).append("'");
+  out.append("\n");
+
+  out.append("  start_conversation: ");
+  out.append(YESNO(this->start_conversation));
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantAnnounceFinished {\n");
+  out.append("  success: ");
+  out.append(YESNO(this->success));
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantWakeWord::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantWakeWord {\n");
+  out.append("  id: ");
+  out.append("'").append(this->id).append("'");
+  out.append("\n");
+
+  out.append("  wake_word: ");
+  out.append("'").append(this->wake_word).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->trained_languages) {
+    out.append("  trained_languages: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+  out.append("}");
+}
+void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
+  out.append("VoiceAssistantConfigurationRequest {}");
+}
+void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantConfigurationResponse {\n");
+  for (const auto &it : this->available_wake_words) {
+    out.append("  available_wake_words: ");
+    it.dump_to(out);
+    out.append("\n");
+  }
+
+  for (const auto &it : this->active_wake_words) {
+    out.append("  active_wake_words: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  max_active_wake_words: ");
+  sprintf(buffer, "%" PRIu32, this->max_active_wake_words);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("VoiceAssistantSetConfiguration {\n");
+  for (const auto &it : this->active_wake_words) {
+    out.append("  active_wake_words: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+  out.append("}");
+}
+#endif
+#ifdef USE_ALARM_CONTROL_PANEL
+void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesAlarmControlPanelResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  supported_features: ");
+  sprintf(buffer, "%" PRIu32, this->supported_features);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  requires_code: ");
+  out.append(YESNO(this->requires_code));
+  out.append("\n");
+
+  out.append("  requires_code_to_arm: ");
+  out.append(YESNO(this->requires_code_to_arm));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("AlarmControlPanelStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append(proto_enum_to_string(this->state));
+  out.append("\n");
+  out.append("}");
+}
+void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("AlarmControlPanelCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  command: ");
+  out.append(proto_enum_to_string(this->command));
+  out.append("\n");
+
+  out.append("  code: ");
+  out.append("'").append(this->code).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_TEXT
+void ListEntitiesTextResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesTextResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  min_length: ");
+  sprintf(buffer, "%" PRIu32, this->min_length);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  max_length: ");
+  sprintf(buffer, "%" PRIu32, this->max_length);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  pattern: ");
+  out.append("'").append(this->pattern).append("'");
+  out.append("\n");
+
+  out.append("  mode: ");
+  out.append(proto_enum_to_string(this->mode));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void TextStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("TextStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+  out.append("}");
+}
+void TextCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("TextCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  state: ");
+  out.append("'").append(this->state).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_DATETIME_DATE
+void ListEntitiesDateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesDateResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void DateStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DateStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+
+  out.append("  year: ");
+  sprintf(buffer, "%" PRIu32, this->year);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  month: ");
+  sprintf(buffer, "%" PRIu32, this->month);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  day: ");
+  sprintf(buffer, "%" PRIu32, this->day);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void DateCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DateCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  year: ");
+  sprintf(buffer, "%" PRIu32, this->year);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  month: ");
+  sprintf(buffer, "%" PRIu32, this->month);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  day: ");
+  sprintf(buffer, "%" PRIu32, this->day);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_DATETIME_TIME
+void ListEntitiesTimeResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesTimeResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void TimeStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("TimeStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+
+  out.append("  hour: ");
+  sprintf(buffer, "%" PRIu32, this->hour);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  minute: ");
+  sprintf(buffer, "%" PRIu32, this->minute);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  second: ");
+  sprintf(buffer, "%" PRIu32, this->second);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void TimeCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("TimeCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  hour: ");
+  sprintf(buffer, "%" PRIu32, this->hour);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  minute: ");
+  sprintf(buffer, "%" PRIu32, this->minute);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  second: ");
+  sprintf(buffer, "%" PRIu32, this->second);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_EVENT
+void ListEntitiesEventResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesEventResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  for (const auto &it : this->event_types) {
+    out.append("  event_types: ");
+    out.append("'").append(it).append("'");
+    out.append("\n");
+  }
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void EventResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("EventResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  event_type: ");
+  out.append("'").append(this->event_type).append("'");
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_VALVE
+void ListEntitiesValveResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesValveResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  assumed_state: ");
+  out.append(YESNO(this->assumed_state));
+  out.append("\n");
+
+  out.append("  supports_position: ");
+  out.append(YESNO(this->supports_position));
+  out.append("\n");
+
+  out.append("  supports_stop: ");
+  out.append(YESNO(this->supports_stop));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void ValveStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ValveStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  position: ");
+  sprintf(buffer, "%g", this->position);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  current_operation: ");
+  out.append(proto_enum_to_string(this->current_operation));
+  out.append("\n");
+  out.append("}");
+}
+void ValveCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ValveCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  has_position: ");
+  out.append(YESNO(this->has_position));
+  out.append("\n");
+
+  out.append("  position: ");
+  sprintf(buffer, "%g", this->position);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  stop: ");
+  out.append(YESNO(this->stop));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_DATETIME_DATETIME
+void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesDateTimeResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void DateTimeStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DateTimeStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+
+  out.append("  epoch_seconds: ");
+  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void DateTimeCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("DateTimeCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  epoch_seconds: ");
+  sprintf(buffer, "%" PRIu32, this->epoch_seconds);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+#endif
+#ifdef USE_UPDATE
+void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("ListEntitiesUpdateResponse {\n");
+  out.append("  object_id: ");
+  out.append("'").append(this->object_id).append("'");
+  out.append("\n");
+
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  name: ");
+  out.append("'").append(this->name).append("'");
+  out.append("\n");
+
+  out.append("  unique_id: ");
+  out.append("'").append(this->unique_id).append("'");
+  out.append("\n");
+
+  out.append("  icon: ");
+  out.append("'").append(this->icon).append("'");
+  out.append("\n");
+
+  out.append("  disabled_by_default: ");
+  out.append(YESNO(this->disabled_by_default));
+  out.append("\n");
+
+  out.append("  entity_category: ");
+  out.append(proto_enum_to_string(this->entity_category));
+  out.append("\n");
+
+  out.append("  device_class: ");
+  out.append("'").append(this->device_class).append("'");
+  out.append("\n");
+
+  out.append("  device_id: ");
+  sprintf(buffer, "%" PRIu32, this->device_id);
+  out.append(buffer);
+  out.append("\n");
+  out.append("}");
+}
+void UpdateStateResponse::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("UpdateStateResponse {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  missing_state: ");
+  out.append(YESNO(this->missing_state));
+  out.append("\n");
+
+  out.append("  in_progress: ");
+  out.append(YESNO(this->in_progress));
+  out.append("\n");
+
+  out.append("  has_progress: ");
+  out.append(YESNO(this->has_progress));
+  out.append("\n");
+
+  out.append("  progress: ");
+  sprintf(buffer, "%g", this->progress);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  current_version: ");
+  out.append("'").append(this->current_version).append("'");
+  out.append("\n");
+
+  out.append("  latest_version: ");
+  out.append("'").append(this->latest_version).append("'");
+  out.append("\n");
+
+  out.append("  title: ");
+  out.append("'").append(this->title).append("'");
+  out.append("\n");
+
+  out.append("  release_summary: ");
+  out.append("'").append(this->release_summary).append("'");
+  out.append("\n");
+
+  out.append("  release_url: ");
+  out.append("'").append(this->release_url).append("'");
+  out.append("\n");
+  out.append("}");
+}
+void UpdateCommandRequest::dump_to(std::string &out) const {
+  __attribute__((unused)) char buffer[64];
+  out.append("UpdateCommandRequest {\n");
+  out.append("  key: ");
+  sprintf(buffer, "%" PRIu32, this->key);
+  out.append(buffer);
+  out.append("\n");
+
+  out.append("  command: ");
+  out.append(proto_enum_to_string(this->command));
+  out.append("\n");
+  out.append("}");
+}
+#endif
+
+}  // namespace api
+}  // namespace esphome
+
+#endif  // HAS_PROTO_MESSAGE_DUMP
diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h
index 3cc774f91c..8c870e5e1c 100644
--- a/esphome/components/api/api_pb2_service.h
+++ b/esphome/components/api/api_pb2_service.h
@@ -2,9 +2,10 @@
 // See script/api_protobuf/api_protobuf.py
 #pragma once
 
-#include "api_pb2.h"
 #include "esphome/core/defines.h"
 
+#include "api_pb2.h"
+
 namespace esphome {
 namespace api {
 
diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py
index 56a46a7701..2266dda81c 100755
--- a/script/api_protobuf/api_protobuf.py
+++ b/script/api_protobuf/api_protobuf.py
@@ -813,27 +813,137 @@ class RepeatedTypeInfo(TypeInfo):
         return underlying_size * 2
 
 
-def build_enum_type(desc) -> tuple[str, str]:
-    """Builds the enum type."""
+def build_type_usage_map(
+    file_desc: descriptor.FileDescriptorProto,
+) -> tuple[dict[str, str | None], dict[str, str | None]]:
+    """Build mappings for both enums and messages to their ifdefs based on usage.
+
+    Returns:
+        tuple: (enum_ifdef_map, message_ifdef_map)
+    """
+    enum_ifdef_map: dict[str, str | None] = {}
+    message_ifdef_map: dict[str, str | None] = {}
+
+    # Build maps of which types are used by which messages
+    enum_usage: dict[
+        str, set[str]
+    ] = {}  # enum_name -> set of message names that use it
+    message_usage: dict[
+        str, set[str]
+    ] = {}  # message_name -> set of message names that use it
+
+    # Build message name to ifdef mapping for quick lookup
+    message_to_ifdef: dict[str, str | None] = {
+        msg.name: get_opt(msg, pb.ifdef) for msg in file_desc.message_type
+    }
+
+    # Analyze field usage
+    for message in file_desc.message_type:
+        for field in message.field:
+            type_name = field.type_name.split(".")[-1] if field.type_name else None
+            if not type_name:
+                continue
+
+            # Track enum usage
+            if field.type == 14:  # TYPE_ENUM
+                enum_usage.setdefault(type_name, set()).add(message.name)
+            # Track message usage
+            elif field.type == 11:  # TYPE_MESSAGE
+                message_usage.setdefault(type_name, set()).add(message.name)
+
+    # Helper to get unique ifdef from a set of messages
+    def get_unique_ifdef(message_names: set[str]) -> str | None:
+        ifdefs: set[str] = {
+            message_to_ifdef[name]
+            for name in message_names
+            if message_to_ifdef.get(name)
+        }
+        return ifdefs.pop() if len(ifdefs) == 1 else None
+
+    # Build enum ifdef map
+    for enum in file_desc.enum_type:
+        if enum.name in enum_usage:
+            enum_ifdef_map[enum.name] = get_unique_ifdef(enum_usage[enum.name])
+        else:
+            enum_ifdef_map[enum.name] = None
+
+    # Build message ifdef map
+    for message in file_desc.message_type:
+        # Explicit ifdef takes precedence
+        explicit_ifdef = message_to_ifdef.get(message.name)
+        if explicit_ifdef:
+            message_ifdef_map[message.name] = explicit_ifdef
+        elif message.name in message_usage:
+            # Inherit ifdef if all parent messages have the same one
+            message_ifdef_map[message.name] = get_unique_ifdef(
+                message_usage[message.name]
+            )
+        else:
+            message_ifdef_map[message.name] = None
+
+    # Second pass: propagate ifdefs recursively
+    # Keep iterating until no more changes are made
+    changed = True
+    iterations = 0
+    while changed and iterations < 10:  # Add safety limit
+        changed = False
+        iterations += 1
+        for message in file_desc.message_type:
+            # Skip if already has an ifdef
+            if message_ifdef_map.get(message.name):
+                continue
+
+            # Check if this message is used by other messages
+            if message.name not in message_usage:
+                continue
+
+            # Get ifdefs from all messages that use this one
+            parent_ifdefs: set[str] = {
+                message_ifdef_map.get(parent)
+                for parent in message_usage[message.name]
+                if message_ifdef_map.get(parent)
+            }
+
+            # If all parents have the same ifdef, inherit it
+            if len(parent_ifdefs) == 1 and None not in parent_ifdefs:
+                message_ifdef_map[message.name] = parent_ifdefs.pop()
+                changed = True
+
+    return enum_ifdef_map, message_ifdef_map
+
+
+def build_enum_type(desc, enum_ifdef_map) -> tuple[str, str, str]:
+    """Builds the enum type.
+
+    Args:
+        desc: The enum descriptor
+        enum_ifdef_map: Mapping of enum names to their ifdefs
+
+    Returns:
+        tuple: (header_content, cpp_content, dump_cpp_content)
+    """
     name = desc.name
+
     out = f"enum {name} : uint32_t {{\n"
     for v in desc.value:
         out += f"  {v.name} = {v.number},\n"
     out += "};\n"
 
-    cpp = "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
-    cpp += f"template<> const char *proto_enum_to_string(enums::{name} value) {{\n"
-    cpp += "  switch (value) {\n"
-    for v in desc.value:
-        cpp += f"    case enums::{v.name}:\n"
-        cpp += f'      return "{v.name}";\n'
-    cpp += "    default:\n"
-    cpp += '      return "UNKNOWN";\n'
-    cpp += "  }\n"
-    cpp += "}\n"
-    cpp += "#endif\n"
+    # Regular cpp file has no enum content anymore
+    cpp = ""
 
-    return out, cpp
+    # Dump cpp content for enum string conversion
+    dump_cpp = f"template<> const char *proto_enum_to_string(enums::{name} value) {{\n"
+    dump_cpp += "  switch (value) {\n"
+    for v in desc.value:
+        dump_cpp += f"    case enums::{v.name}:\n"
+        dump_cpp += f'      return "{v.name}";\n'
+    dump_cpp += "    default:\n"
+    dump_cpp += '      return "UNKNOWN";\n'
+    dump_cpp += "  }\n"
+    dump_cpp += "}\n"
+
+    return out, cpp, dump_cpp
 
 
 def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int:
@@ -855,7 +965,7 @@ def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int:
 def build_message_type(
     desc: descriptor.DescriptorProto,
     base_class_fields: dict[str, list[descriptor.FieldDescriptorProto]] = None,
-) -> tuple[str, str]:
+) -> tuple[str, str, str]:
     public_content: list[str] = []
     protected_content: list[str] = []
     decode_varint: list[str] = []
@@ -886,7 +996,7 @@ def build_message_type(
             f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};"
         )
 
-        # Add message_name method for debugging
+        # Add message_name method inline in header
         public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP")
         snake_name = camel_to_snake(desc.name)
         public_content.append(
@@ -993,32 +1103,32 @@ def build_message_type(
         public_content.append(prot)
     # If no fields to calculate size for, the default implementation in ProtoMessage will be used
 
-    o = f"void {desc.name}::dump_to(std::string &out) const {{"
-    if dump:
-        if len(dump) == 1 and len(dump[0]) + len(o) + 3 < 120:
-            o += f" {dump[0]} "
-        else:
-            o += "\n"
-            o += "  __attribute__((unused)) char buffer[64];\n"
-            o += f'  out.append("{desc.name} {{\\n");\n'
-            o += indent("\n".join(dump)) + "\n"
-            o += '  out.append("}");\n'
-    else:
-        o2 = f'out.append("{desc.name} {{}}");'
-        if len(o) + len(o2) + 3 < 120:
-            o += f" {o2} "
-        else:
-            o += "\n"
-            o += f"  {o2}\n"
-    o += "}\n"
-    cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
-    cpp += o
-    cpp += "#endif\n"
+    # dump_to method declaration in header
     prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
     prot += "void dump_to(std::string &out) const override;\n"
     prot += "#endif\n"
     public_content.append(prot)
 
+    # dump_to implementation will go in dump_cpp
+    dump_impl = f"void {desc.name}::dump_to(std::string &out) const {{"
+    if dump:
+        if len(dump) == 1 and len(dump[0]) + len(dump_impl) + 3 < 120:
+            dump_impl += f" {dump[0]} "
+        else:
+            dump_impl += "\n"
+            dump_impl += "  __attribute__((unused)) char buffer[64];\n"
+            dump_impl += f'  out.append("{desc.name} {{\\n");\n'
+            dump_impl += indent("\n".join(dump)) + "\n"
+            dump_impl += '  out.append("}");\n'
+    else:
+        o2 = f'out.append("{desc.name} {{}}");'
+        if len(dump_impl) + len(o2) + 3 < 120:
+            dump_impl += f" {o2} "
+        else:
+            dump_impl += "\n"
+            dump_impl += f"  {o2}\n"
+    dump_impl += "}\n"
+
     if base_class:
         out = f"class {desc.name} : public {base_class} {{\n"
     else:
@@ -1031,7 +1141,11 @@ def build_message_type(
     if len(protected_content) > 0:
         out += "\n"
     out += "};\n"
-    return out, cpp
+
+    # Build dump_cpp content with dump_to implementation
+    dump_cpp = dump_impl
+
+    return out, cpp, dump_cpp
 
 
 SOURCE_BOTH = 0
@@ -1119,7 +1233,7 @@ def find_common_fields(
 def build_base_class(
     base_class_name: str,
     common_fields: list[descriptor.FieldDescriptorProto],
-) -> tuple[str, str]:
+) -> tuple[str, str, str]:
     """Build the base class definition and implementation."""
     public_content = []
     protected_content = []
@@ -1156,16 +1270,18 @@ def build_base_class(
     out += "};\n"
 
     # No implementation needed for base classes
+    dump_cpp = ""
 
-    return out, cpp
+    return out, cpp, dump_cpp
 
 
 def generate_base_classes(
     base_class_groups: dict[str, list[descriptor.DescriptorProto]],
-) -> tuple[str, str]:
+) -> tuple[str, str, str]:
     """Generate all base classes."""
     all_headers = []
     all_cpp = []
+    all_dump_cpp = []
 
     for base_class_name, messages in base_class_groups.items():
         # Find common fields
@@ -1173,11 +1289,12 @@ def generate_base_classes(
 
         if common_fields:
             # Generate base class
-            header, cpp = build_base_class(base_class_name, common_fields)
+            header, cpp, dump_cpp = build_base_class(base_class_name, common_fields)
             all_headers.append(header)
             all_cpp.append(cpp)
+            all_dump_cpp.append(dump_cpp)
 
-    return "\n".join(all_headers), "\n".join(all_cpp)
+    return "\n".join(all_headers), "\n".join(all_cpp), "\n".join(all_dump_cpp)
 
 
 def build_service_message_type(
@@ -1244,15 +1361,17 @@ def main() -> None:
     file = d.file[0]
     content = FILE_HEADER
     content += """\
-    #pragma once
+#pragma once
 
-    #include "proto.h"
-    #include "api_pb2_size.h"
+#include "esphome/core/defines.h"
 
-    namespace esphome {
-    namespace api {
+#include "proto.h"
+#include "api_pb2_size.h"
 
-    """
+namespace esphome {
+namespace api {
+
+"""
 
     cpp = FILE_HEADER
     cpp += """\
@@ -1261,19 +1380,56 @@ def main() -> None:
     #include "esphome/core/log.h"
     #include "esphome/core/helpers.h"
 
-    #include 
+namespace esphome {
+namespace api {
 
-    namespace esphome {
-    namespace api {
+"""
 
-    """
+    # Initialize dump cpp content
+    dump_cpp = FILE_HEADER
+    dump_cpp += """\
+#include "api_pb2.h"
+#include "esphome/core/helpers.h"
+
+#include 
+
+#ifdef HAS_PROTO_MESSAGE_DUMP
+
+namespace esphome {
+namespace api {
+
+"""
 
     content += "namespace enums {\n\n"
 
+    # Build dynamic ifdef mappings for both enums and messages
+    enum_ifdef_map, message_ifdef_map = build_type_usage_map(file)
+
+    # Simple grouping of enums by ifdef
+    current_ifdef = None
+
     for enum in file.enum_type:
-        s, c = build_enum_type(enum)
+        s, c, dc = build_enum_type(enum, enum_ifdef_map)
+        enum_ifdef = enum_ifdef_map.get(enum.name)
+
+        # Handle ifdef changes
+        if enum_ifdef != current_ifdef:
+            if current_ifdef is not None:
+                content += "#endif\n"
+                dump_cpp += "#endif\n"
+            if enum_ifdef is not None:
+                content += f"#ifdef {enum_ifdef}\n"
+                dump_cpp += f"#ifdef {enum_ifdef}\n"
+            current_ifdef = enum_ifdef
+
         content += s
         cpp += c
+        dump_cpp += dc
+
+    # Close last ifdef
+    if current_ifdef is not None:
+        content += "#endif\n"
+        dump_cpp += "#endif\n"
 
     content += "\n}  // namespace enums\n\n"
 
@@ -1291,26 +1447,61 @@ def main() -> None:
 
     # Generate base classes
     if base_class_fields:
-        base_headers, base_cpp = generate_base_classes(base_class_groups)
+        base_headers, base_cpp, base_dump_cpp = generate_base_classes(base_class_groups)
         content += base_headers
         cpp += base_cpp
+        dump_cpp += base_dump_cpp
 
     # Generate message types with base class information
+    # Simple grouping by ifdef
+    current_ifdef = None
+
     for m in mt:
-        s, c = build_message_type(m, base_class_fields)
+        s, c, dc = build_message_type(m, base_class_fields)
+        msg_ifdef = message_ifdef_map.get(m.name)
+
+        # Handle ifdef changes
+        if msg_ifdef != current_ifdef:
+            if current_ifdef is not None:
+                content += "#endif\n"
+                if cpp:
+                    cpp += "#endif\n"
+                if dump_cpp:
+                    dump_cpp += "#endif\n"
+            if msg_ifdef is not None:
+                content += f"#ifdef {msg_ifdef}\n"
+                cpp += f"#ifdef {msg_ifdef}\n"
+                dump_cpp += f"#ifdef {msg_ifdef}\n"
+            current_ifdef = msg_ifdef
+
         content += s
         cpp += c
+        dump_cpp += dc
+
+    # Close last ifdef
+    if current_ifdef is not None:
+        content += "#endif\n"
+        cpp += "#endif\n"
+        dump_cpp += "#endif\n"
 
     content += """\
 
-    }  // namespace api
-    }  // namespace esphome
-    """
+}  // namespace api
+}  // namespace esphome
+"""
     cpp += """\
 
-    }  // namespace api
-    }  // namespace esphome
-    """
+}  // namespace api
+}  // namespace esphome
+"""
+
+    dump_cpp += """\
+
+}  // namespace api
+}  // namespace esphome
+
+#endif  // HAS_PROTO_MESSAGE_DUMP
+"""
 
     with open(root / "api_pb2.h", "w", encoding="utf-8") as f:
         f.write(content)
@@ -1318,29 +1509,33 @@ def main() -> None:
     with open(root / "api_pb2.cpp", "w", encoding="utf-8") as f:
         f.write(cpp)
 
+    with open(root / "api_pb2_dump.cpp", "w", encoding="utf-8") as f:
+        f.write(dump_cpp)
+
     hpp = FILE_HEADER
     hpp += """\
-    #pragma once
+#pragma once
 
-    #include "api_pb2.h"
-    #include "esphome/core/defines.h"
+#include "esphome/core/defines.h"
 
-    namespace esphome {
-    namespace api {
+#include "api_pb2.h"
 
-    """
+namespace esphome {
+namespace api {
+
+"""
 
     cpp = FILE_HEADER
     cpp += """\
-    #include "api_pb2_service.h"
-    #include "esphome/core/log.h"
+#include "api_pb2_service.h"
+#include "esphome/core/log.h"
 
-    namespace esphome {
-    namespace api {
+namespace esphome {
+namespace api {
 
-    static const char *const TAG = "api.service";
+static const char *const TAG = "api.service";
 
-    """
+"""
 
     class_name = "APIServerConnectionBase"
 
@@ -1419,7 +1614,7 @@ def main() -> None:
         needs_conn = get_opt(m, pb.needs_setup_connection, True)
         needs_auth = get_opt(m, pb.needs_authentication, True)
 
-        ifdef = ifdefs.get(inp, None)
+        ifdef = message_ifdef_map.get(inp, ifdefs.get(inp, None))
 
         if ifdef is not None:
             hpp += f"#ifdef {ifdef}\n"
@@ -1476,14 +1671,14 @@ def main() -> None:
 
     hpp += """\
 
-    }  // namespace api
-    }  // namespace esphome
-    """
+}  // namespace api
+}  // namespace esphome
+"""
     cpp += """\
 
-    }  // namespace api
-    }  // namespace esphome
-    """
+}  // namespace api
+}  // namespace esphome
+"""
 
     with open(root / "api_pb2_service.h", "w", encoding="utf-8") as f:
         f.write(hpp)
@@ -1506,6 +1701,8 @@ def main() -> None:
         exec_clang_format(root / "api_pb2_service.cpp")
         exec_clang_format(root / "api_pb2.h")
         exec_clang_format(root / "api_pb2.cpp")
+        exec_clang_format(root / "api_pb2_dump.h")
+        exec_clang_format(root / "api_pb2_dump.cpp")
     except ImportError:
         pass
 

From 5b55e205efb9ddf25dc69a28ca3f56a73ed9fe31 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" 
Date: Wed, 2 Jul 2025 16:42:08 -0500
Subject: [PATCH 35/35] Save flash and RAM by conditionally compiling unused
 API password code (#9297)

---
 esphome/components/api/__init__.py        | 4 +++-
 esphome/components/api/api_connection.cpp | 9 ++++++++-
 esphome/components/api/api_server.cpp     | 4 ++++
 esphome/components/api/api_server.h       | 6 +++++-
 4 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py
index b02a875d72..2f1be28293 100644
--- a/esphome/components/api/__init__.py
+++ b/esphome/components/api/__init__.py
@@ -132,7 +132,9 @@ async def to_code(config):
     await cg.register_component(var, config)
 
     cg.add(var.set_port(config[CONF_PORT]))
-    cg.add(var.set_password(config[CONF_PASSWORD]))
+    if config[CONF_PASSWORD]:
+        cg.add_define("USE_API_PASSWORD")
+        cg.add(var.set_password(config[CONF_PASSWORD]))
     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
     cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
 
diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp
index e83d508c50..49ad9706bc 100644
--- a/esphome/components/api/api_connection.cpp
+++ b/esphome/components/api/api_connection.cpp
@@ -1503,7 +1503,10 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
   return resp;
 }
 ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
-  bool correct = this->parent_->check_password(msg.password);
+  bool correct = true;
+#ifdef USE_API_PASSWORD
+  correct = this->parent_->check_password(msg.password);
+#endif
 
   ConnectResponse resp;
   // bool invalid_password = 1;
@@ -1524,7 +1527,11 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
 }
 DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
   DeviceInfoResponse resp{};
+#ifdef USE_API_PASSWORD
   resp.uses_password = this->parent_->uses_password();
+#else
+  resp.uses_password = false;
+#endif
   resp.name = App.get_name();
   resp.friendly_name = App.get_friendly_name();
   resp.suggested_area = App.get_area();
diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp
index ebe80604dc..0fd9c1a228 100644
--- a/esphome/components/api/api_server.cpp
+++ b/esphome/components/api/api_server.cpp
@@ -218,6 +218,7 @@ void APIServer::dump_config() {
 #endif
 }
 
+#ifdef USE_API_PASSWORD
 bool APIServer::uses_password() const { return !this->password_.empty(); }
 
 bool APIServer::check_password(const std::string &password) const {
@@ -248,6 +249,7 @@ bool APIServer::check_password(const std::string &password) const {
 
   return result == 0;
 }
+#endif
 
 void APIServer::handle_disconnect(APIConnection *conn) {}
 
@@ -431,7 +433,9 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
 
 void APIServer::set_port(uint16_t port) { this->port_ = port; }
 
+#ifdef USE_API_PASSWORD
 void APIServer::set_password(const std::string &password) { this->password_ = password; }
+#endif
 
 void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
 
diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h
index 5a9b0677bc..9dc2b4b7d6 100644
--- a/esphome/components/api/api_server.h
+++ b/esphome/components/api/api_server.h
@@ -35,10 +35,12 @@ class APIServer : public Component, public Controller {
   void dump_config() override;
   void on_shutdown() override;
   bool teardown() override;
+#ifdef USE_API_PASSWORD
   bool check_password(const std::string &password) const;
   bool uses_password() const;
-  void set_port(uint16_t port);
   void set_password(const std::string &password);
+#endif
+  void set_port(uint16_t port);
   void set_reboot_timeout(uint32_t reboot_timeout);
   void set_batch_delay(uint16_t batch_delay);
   uint16_t get_batch_delay() const { return batch_delay_; }
@@ -179,7 +181,9 @@ class APIServer : public Component, public Controller {
 
   // Vectors and strings (12 bytes each on 32-bit)
   std::vector> clients_;
+#ifdef USE_API_PASSWORD
   std::string password_;
+#endif
   std::vector shared_write_buffer_;  // Shared proto write buffer for all connections
   std::vector state_subs_;
 #ifdef USE_API_YAML_SERVICES