From 484f4b3aadbcf9e97c21bbdaf35265946b7fda9c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:34:28 -0500 Subject: [PATCH 01/32] [cc1101] Add PLL lock verification and retry support (#13006) --- esphome/components/cc1101/cc1101.cpp | 67 +++++++++++++++++++------- esphome/components/cc1101/cc1101.h | 5 +- esphome/components/cc1101/cc1101defs.h | 3 ++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 5727d485f6..c4507a54e5 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -140,7 +140,10 @@ void CC1101Component::setup() { this->write_(static_cast(i)); } this->set_output_power(this->output_power_requested_); - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + this->mark_failed(); + return; + } // Defer pin mode setup until after all components have completed setup() // This handles the case where remote_transmitter runs after CC1101 and changes pin mode @@ -163,8 +166,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "RX FIFO overflow, flushing"); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } @@ -181,8 +183,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } this->packet_.resize(payload_length); @@ -203,8 +204,7 @@ void CC1101Component::loop() { // Return to rx this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); } void CC1101Component::dump_config() { @@ -235,9 +235,8 @@ void CC1101Component::begin_tx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); } - this->strobe_(Command::TX); - if (!this->wait_for_state_(State::TX, 50)) { - ESP_LOGW(TAG, "Timed out waiting for TX state!"); + if (!this->enter_tx_()) { + ESP_LOGW(TAG, "Failed to enter TX state!"); } } @@ -246,7 +245,9 @@ void CC1101Component::begin_rx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); } - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + ESP_LOGW(TAG, "Failed to enter RX state!"); + } } void CC1101Component::reset() { @@ -272,11 +273,33 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { return false; } +bool CC1101Component::enter_calibrated_(State target_state, Command cmd) { + // The PLL must be recalibrated until PLL lock is achieved + for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) { + this->strobe_(cmd); + if (!this->wait_for_state_(target_state)) { + return false; + } + this->read_(Register::FSCAL1); + if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) { + return true; + } + ESP_LOGW(TAG, "PLL lock failed, retrying calibration"); + this->enter_idle_(); + } + ESP_LOGE(TAG, "PLL lock failed after retries"); + return false; +} + void CC1101Component::enter_idle_() { this->strobe_(Command::IDLE); this->wait_for_state_(State::IDLE); } +bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); } + +bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); } + uint8_t CC1101Component::strobe_(Command cmd) { uint8_t index = static_cast(cmd); if (cmd < Command::RES || cmd > Command::NOP) { @@ -338,18 +361,26 @@ CC1101Error CC1101Component::transmit_packet(const std::vector &packet) this->write_(Register::FIFO, static_cast(packet.size())); } this->write_(Register::FIFO, packet.data(), packet.size()); + + // Calibrate PLL + if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) { + ESP_LOGW(TAG, "PLL lock failed during TX"); + this->enter_idle_(); + this->enter_rx_(); + return CC1101Error::PLL_LOCK; + } + + // Transmit packet this->strobe_(Command::TX); if (!this->wait_for_state_(State::IDLE, 1000)) { ESP_LOGW(TAG, "TX timeout"); this->enter_idle_(); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::TIMEOUT; } // Return to rx - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::NONE; } @@ -406,7 +437,7 @@ void CC1101Component::set_frequency(float value) { this->write_(Register::FREQ2); this->write_(Register::FREQ1); this->write_(Register::FREQ0); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -433,7 +464,7 @@ void CC1101Component::set_channel(uint8_t value) { if (this->initialized_) { this->enter_idle_(); this->write_(Register::CHANNR); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -502,7 +533,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); - this->strobe_(Command::RX); + this->enter_rx_(); } } diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 9b8d4e56a8..43ae5b3612 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -9,7 +9,7 @@ namespace esphome::cc1101 { -enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK }; class CC1101Component : public Component, public spi::SPIDevice Date: Thu, 1 Jan 2026 13:44:21 +1000 Subject: [PATCH 02/32] [lvgl] Fix arc background angles (#12773) --- esphome/components/lvgl/widgets/arc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/widgets/arc.py b/esphome/components/lvgl/widgets/arc.py index 21530441f8..34ac9c51f7 100644 --- a/esphome/components/lvgl/widgets/arc.py +++ b/esphome/components/lvgl/widgets/arc.py @@ -85,11 +85,11 @@ class ArcType(NumberType): lv.arc_set_range(w.obj, min_value, max_value) await w.set_property( - CONF_START_ANGLE, + "bg_start_angle", await lv_angle_degrees.process(config.get(CONF_START_ANGLE)), ) await w.set_property( - CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) + "bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) ) await w.set_property( CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION)) From 178a61b6fd8fc298914bc6b8dd3d3b8d1e0e6f81 Mon Sep 17 00:00:00 2001 From: Artur <130101347+aanikei@users.noreply.github.com> Date: Fri, 2 Jan 2026 04:28:10 +0000 Subject: [PATCH 03/32] [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) --- esphome/components/sn74hc595/sn74hc595.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index fc47a6dc5e..a9ada432e4 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -70,7 +70,7 @@ void SN74HC595GPIOComponent::write_gpio() { void SN74HC595SPIComponent::write_gpio() { for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) { this->enable(); - this->transfer_byte(output_byte); + this->write_byte(output_byte); this->disable(); } SN74HC595Component::write_gpio(); From 8b4ba8dfe66fe73957e272c9ea23ac8e1f44a315 Mon Sep 17 00:00:00 2001 From: Conrad Juhl Andersen Date: Sat, 3 Jan 2026 23:06:33 +0100 Subject: [PATCH 04/32] [wts01] Fix negative values for WTS01 sensor (#12835) --- esphome/components/wts01/wts01.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/wts01/wts01.cpp b/esphome/components/wts01/wts01.cpp index cb910d89cf..a7948c805a 100644 --- a/esphome/components/wts01/wts01.cpp +++ b/esphome/components/wts01/wts01.cpp @@ -71,17 +71,20 @@ void WTS01Sensor::process_packet_() { } // Extract temperature value - int8_t temp = this->buffer_[6]; - int32_t sign = 1; + const uint8_t raw = this->buffer_[6]; - // Handle negative temperatures - if (temp < 0) { - sign = -1; + // WTS01 encodes sign in bit 7, magnitude in bits 0-6 + const bool negative = (raw & 0x80) != 0; + const uint8_t magnitude = raw & 0x7F; + + const float decimal = static_cast(this->buffer_[7]) / 100.0f; + + float temperature = static_cast(magnitude) + decimal; + + if (negative) { + temperature = -temperature; } - // Calculate temperature (temp + decimal/100) - float temperature = static_cast(temp) + (sign * static_cast(this->buffer_[7]) / 100.0f); - ESP_LOGV(TAG, "Received new temperature: %.2f°C", temperature); this->publish_state(temperature); From 8255c02d5d4c619e0e7ab1ec6278bbde5f9b2102 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 5 Jan 2026 08:37:44 +1000 Subject: [PATCH 05/32] [esp32_ble] Remove requirement for configured network (#12891) --- esphome/components/esp32_ble/__init__.py | 1 - esphome/components/socket/__init__.py | 7 ++- esphome/core/__init__.py | 19 ++++++ .../socket/test_wake_loop_threadsafe.py | 35 +++++++++++ tests/unit_tests/test_core.py | 62 +++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ced7e3fec9..dcc3ce71cf 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,7 +22,6 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["socket"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] DOMAIN = "esp32_ble" diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 49e074a6ee..e364da78f8 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -47,6 +47,8 @@ def require_wake_loop_threadsafe() -> None: This enables the shared UDP loopback socket mechanism (~208 bytes RAM). The socket is shared across all components that use this feature. + This call is a no-op if networking is not enabled in the configuration. + IMPORTANT: This is for background thread context only, NOT ISR context. Socket operations are not safe to call from ISR handlers. @@ -56,8 +58,11 @@ def require_wake_loop_threadsafe() -> None: async def to_code(config): socket.require_wake_loop_threadsafe() """ + # Only set up once (idempotent - multiple components can call this) - if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False): + if CORE.has_networking and not CORE.data.get( + KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False + ): CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True cg.add_define("USE_WAKE_LOOP_THREADSAFE") # Consume 1 socket for the shared wake notification socket diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 721cd5787d..0e09d97fed 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -703,6 +703,25 @@ class EsphomeCore: def config_filename(self) -> str: return self.config_path.name + def has_at_least_one_component(self, *components: str) -> bool: + """ + Are any of the given components configured? + :param components: component names + :return: true if so + """ + if self.config is None: + raise ValueError("Config has not been loaded yet") + + return any(component in self.config for component in components) + + @property + def has_networking(self) -> bool: + """ + Is a network component configured? + :return: true if so + """ + return self.has_at_least_one_component("wifi", "ethernet", "openthread") + def relative_config_path(self, *path: str | Path) -> Path: path_ = Path(*path).expanduser() return self.config_dir / path_ diff --git a/tests/components/socket/test_wake_loop_threadsafe.py b/tests/components/socket/test_wake_loop_threadsafe.py index 45e5ea2211..b4bc95176d 100644 --- a/tests/components/socket/test_wake_loop_threadsafe.py +++ b/tests/components/socket/test_wake_loop_threadsafe.py @@ -4,6 +4,7 @@ from esphome.core import CORE def test_require_wake_loop_threadsafe__first_call() -> None: """Test that first call sets up define and consumes socket.""" + CORE.config = {"wifi": True} socket.require_wake_loop_threadsafe() # Verify CORE.data was updated @@ -17,6 +18,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: """Test that subsequent calls are idempotent.""" # Set up initial state as if already called CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True + CORE.config = {"ethernet": True} # Call again - should not raise or fail socket.require_wake_loop_threadsafe() @@ -31,6 +33,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: def test_require_wake_loop_threadsafe__multiple_calls() -> None: """Test that multiple calls only set up once.""" # Call three times + CORE.config = {"openthread": True} socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() @@ -40,3 +43,35 @@ def test_require_wake_loop_threadsafe__multiple_calls() -> None: # Verify the define was added (only once, but we can just check it exists) assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking() -> None: + """Test that wake loop is NOT configured when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"esphome": {"name": "test"}, "logger": {}} + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify CORE.data flag was NOT set (since has_networking returns False) + assert socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED not in CORE.data + + # Verify the define was NOT added + assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -> None: + """Test that no socket is consumed when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"logger": {}} + + # Track initial socket consumer state + initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify no socket was consumed + consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + assert "socket.wake_loop_threadsafe" not in consumers + assert consumers == initial_consumers diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index e52cb24831..1fc8dab358 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -718,3 +718,65 @@ class TestEsphomeCore: # Even though "web_server" is in loaded_integrations due to the platform, # web_port must return None because the full web_server component is not configured assert target.web_port is None + + def test_has_at_least_one_component__none_configured(self, target): + """Test has_at_least_one_component returns False when none of the components are configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is False + + def test_has_at_least_one_component__one_configured(self, target): + """Test has_at_least_one_component returns True when one component is configured.""" + target.config = {const.CONF_WIFI: {}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is True + + def test_has_at_least_one_component__multiple_configured(self, target): + """Test has_at_least_one_component returns True when multiple components are configured.""" + target.config = { + const.CONF_WIFI: {}, + const.CONF_ETHERNET: {}, + "logger": {}, + } + + assert ( + target.has_at_least_one_component("wifi", "ethernet", "bluetooth") is True + ) + + def test_has_at_least_one_component__single_component(self, target): + """Test has_at_least_one_component works with a single component.""" + target.config = {const.CONF_MQTT: {}} + + assert target.has_at_least_one_component("mqtt") is True + assert target.has_at_least_one_component("wifi") is False + + def test_has_at_least_one_component__config_not_loaded(self, target): + """Test has_at_least_one_component raises ValueError when config is not loaded.""" + target.config = None + + with pytest.raises(ValueError, match="Config has not been loaded yet"): + target.has_at_least_one_component("wifi") + + def test_has_networking__with_wifi(self, target): + """Test has_networking returns True when wifi is configured.""" + target.config = {const.CONF_WIFI: {}} + + assert target.has_networking is True + + def test_has_networking__with_ethernet(self, target): + """Test has_networking returns True when ethernet is configured.""" + target.config = {const.CONF_ETHERNET: {}} + + assert target.has_networking is True + + def test_has_networking__with_openthread(self, target): + """Test has_networking returns True when openthread is configured.""" + target.config = {const.CONF_OPENTHREAD: {}} + + assert target.has_networking is True + + def test_has_networking__without_networking(self, target): + """Test has_networking returns False when no networking component is configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_networking is False From 47d0d3cfeb1c447c133950ceb90ef6834e9c1a27 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:34:28 -0500 Subject: [PATCH 06/32] [cc1101] Add PLL lock verification and retry support (#13006) --- esphome/components/cc1101/cc1101.cpp | 67 +++++++++++++++++++------- esphome/components/cc1101/cc1101.h | 5 +- esphome/components/cc1101/cc1101defs.h | 3 ++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 7e5309e165..fe9238b141 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -140,7 +140,10 @@ void CC1101Component::setup() { this->write_(static_cast(i)); } this->set_output_power(this->output_power_requested_); - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + this->mark_failed(); + return; + } // Defer pin mode setup until after all components have completed setup() // This handles the case where remote_transmitter runs after CC1101 and changes pin mode @@ -163,8 +166,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "RX FIFO overflow, flushing"); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } @@ -181,8 +183,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } this->packet_.resize(payload_length); @@ -201,8 +202,7 @@ void CC1101Component::loop() { // Return to rx this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); } void CC1101Component::dump_config() { @@ -233,9 +233,8 @@ void CC1101Component::begin_tx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); } - this->strobe_(Command::TX); - if (!this->wait_for_state_(State::TX, 50)) { - ESP_LOGW(TAG, "Timed out waiting for TX state!"); + if (!this->enter_tx_()) { + ESP_LOGW(TAG, "Failed to enter TX state!"); } } @@ -244,7 +243,9 @@ void CC1101Component::begin_rx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); } - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + ESP_LOGW(TAG, "Failed to enter RX state!"); + } } void CC1101Component::reset() { @@ -270,11 +271,33 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { return false; } +bool CC1101Component::enter_calibrated_(State target_state, Command cmd) { + // The PLL must be recalibrated until PLL lock is achieved + for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) { + this->strobe_(cmd); + if (!this->wait_for_state_(target_state)) { + return false; + } + this->read_(Register::FSCAL1); + if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) { + return true; + } + ESP_LOGW(TAG, "PLL lock failed, retrying calibration"); + this->enter_idle_(); + } + ESP_LOGE(TAG, "PLL lock failed after retries"); + return false; +} + void CC1101Component::enter_idle_() { this->strobe_(Command::IDLE); this->wait_for_state_(State::IDLE); } +bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); } + +bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); } + uint8_t CC1101Component::strobe_(Command cmd) { uint8_t index = static_cast(cmd); if (cmd < Command::RES || cmd > Command::NOP) { @@ -336,18 +359,26 @@ CC1101Error CC1101Component::transmit_packet(const std::vector &packet) this->write_(Register::FIFO, static_cast(packet.size())); } this->write_(Register::FIFO, packet.data(), packet.size()); + + // Calibrate PLL + if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) { + ESP_LOGW(TAG, "PLL lock failed during TX"); + this->enter_idle_(); + this->enter_rx_(); + return CC1101Error::PLL_LOCK; + } + + // Transmit packet this->strobe_(Command::TX); if (!this->wait_for_state_(State::IDLE, 1000)) { ESP_LOGW(TAG, "TX timeout"); this->enter_idle_(); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::TIMEOUT; } // Return to rx - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::NONE; } @@ -404,7 +435,7 @@ void CC1101Component::set_frequency(float value) { this->write_(Register::FREQ2); this->write_(Register::FREQ1); this->write_(Register::FREQ0); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -431,7 +462,7 @@ void CC1101Component::set_channel(uint8_t value) { if (this->initialized_) { this->enter_idle_(); this->write_(Register::CHANNR); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -500,7 +531,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); - this->strobe_(Command::RX); + this->enter_rx_(); } } diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index b896f7e974..fe4898660e 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -9,7 +9,7 @@ namespace esphome::cc1101 { -enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK }; class CC1101Component : public Component, public spi::SPIDevice Date: Tue, 6 Jan 2026 09:35:38 -0500 Subject: [PATCH 07/32] Bump version to 2025.12.5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index ff74757639..079606c501 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.4 +PROJECT_NUMBER = 2025.12.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 3fbdb69215..d1a7104ca4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.4" +__version__ = "2025.12.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From a8a26f4ea8428ec86bbe5b26b7f1b55925120b88 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:32:43 -1000 Subject: [PATCH 08/32] [opentherm][nau7802] Use direct format specifiers instead of to_string().c_str() (#13019) --- esphome/components/nau7802/nau7802.cpp | 6 +++--- esphome/components/opentherm/opentherm.cpp | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/esphome/components/nau7802/nau7802.cpp b/esphome/components/nau7802/nau7802.cpp index 11f63a9a33..5edbc79862 100644 --- a/esphome/components/nau7802/nau7802.cpp +++ b/esphome/components/nau7802/nau7802.cpp @@ -131,9 +131,9 @@ void NAU7802Sensor::dump_config() { } // Note these may differ from the values on the device if calbration has been run ESP_LOGCONFIG(TAG, - " Offset Calibration: %s\n" + " Offset Calibration: %" PRId32 "\n" " Gain Calibration: %f", - to_string(this->offset_calibration_).c_str(), this->gain_calibration_); + this->offset_calibration_, this->gain_calibration_); std::string voltage = "unknown"; switch (this->ldo_) { @@ -289,7 +289,7 @@ void NAU7802Sensor::loop() { this->status_clear_error(); int32_t ocal = this->read_value_(OCAL1_B2_REG, 3); - ESP_LOGI(TAG, "New Offset: %s", to_string(ocal).c_str()); + ESP_LOGI(TAG, "New Offset: %" PRId32, ocal); uint32_t gcal = this->read_value_(GCAL1_B3_REG, 4); float gcal_f = ((float) gcal / (float) (1 << GCAL1_FRACTIONAL)); ESP_LOGI(TAG, "New Gain: %f", gcal_f); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 750ef08b33..c6443f1282 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -21,7 +21,6 @@ namespace esphome { namespace opentherm { using std::string; -using std::to_string; static const char *const TAG = "opentherm"; @@ -564,10 +563,9 @@ const char *OpenTherm::message_id_to_str(MessageId id) { void OpenTherm::debug_data(OpenthermData &data) { ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); - ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", - this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), - to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), - to_string(data.f88()).c_str()); + ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f", + this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(), + data.f88()); } void OpenTherm::debug_error(OpenThermError &error) const { ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_, From 1e56325b33a9301ceed9d6e607364d6715367762 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:33:32 -1000 Subject: [PATCH 09/32] [improv_base] Optimize next_url to avoid STL string operations (#13015) --- .../esp32_improv/esp32_improv_component.cpp | 9 ++- .../components/improv_base/improv_base.cpp | 56 ++++++++++++------- esphome/components/improv_base/improv_base.h | 9 +-- .../improv_serial/improv_serial_component.cpp | 8 ++- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 4a6aec1892..1a19472c87 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -398,9 +398,12 @@ void ESP32ImprovComponent::check_wifi_connection_() { #ifdef USE_ESP32_IMPROV_NEXT_URL // Add next_url if configured (should be first per Improv BLE spec) - std::string next_url = this->get_formatted_next_url_(); - if (!next_url.empty()) { - url_strings[url_count++] = std::move(next_url); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + url_strings[url_count++] = std::string(url_buffer, len); + } } #endif diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index 2091390f95..d0340344a6 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -1,5 +1,6 @@ #include "improv_base.h" +#include #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" @@ -13,37 +14,54 @@ static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHO static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}"; static constexpr size_t IP_ADDRESS_PLACEHOLDER_LEN = sizeof(IP_ADDRESS_PLACEHOLDER) - 1; -static void replace_all_in_place(std::string &str, const char *placeholder, size_t placeholder_len, - const std::string &replacement) { - size_t pos = 0; - const size_t replacement_len = replacement.length(); - while ((pos = str.find(placeholder, pos)) != std::string::npos) { - str.replace(pos, placeholder_len, replacement); - pos += replacement_len; +/// Copy src to dest, returning pointer past last written char. Stops at end or if src is null. +static char *copy_to_buffer(char *dest, const char *end, const char *src) { + if (src == nullptr) { + return dest; } + while (*src != '\0' && dest < end) { + *dest++ = *src++; + } + return dest; } -std::string ImprovBase::get_formatted_next_url_() { - if (this->next_url_.empty()) { - return ""; +size_t ImprovBase::get_formatted_next_url_(char *buffer, size_t buffer_size) { + if (this->next_url_ == nullptr || buffer_size == 0) { + if (buffer_size > 0) { + buffer[0] = '\0'; + } + return 0; } - std::string formatted_url = this->next_url_; - - // Replace all occurrences of {{device_name}} - replace_all_in_place(formatted_url, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN, App.get_name()); - - // Replace all occurrences of {{ip_address}} + // Get IP address once for replacement + const char *ip_str = nullptr; + char ip_buffer[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : network::get_ip_addresses()) { if (ip.is_ip4()) { - replace_all_in_place(formatted_url, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN, ip.str()); + ip.str_to(ip_buffer); + ip_str = ip_buffer; break; } } - // Note: {{esphome_version}} is replaced at code generation time in Python + const char *device_name = App.get_name().c_str(); + char *out = buffer; + const char *end = buffer + buffer_size - 1; - return formatted_url; + // Note: {{esphome_version}} is replaced at code generation time in Python + for (const char *p = this->next_url_; *p != '\0' && out < end;) { + if (strncmp(p, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, device_name); + p += DEVICE_NAME_PLACEHOLDER_LEN; + } else if (ip_str != nullptr && strncmp(p, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, ip_str); + p += IP_ADDRESS_PLACEHOLDER_LEN; + } else { + *out++ = *p++; + } + } + *out = '\0'; + return out - buffer; } #endif diff --git a/esphome/components/improv_base/improv_base.h b/esphome/components/improv_base/improv_base.h index e4138479df..ebc8f38d60 100644 --- a/esphome/components/improv_base/improv_base.h +++ b/esphome/components/improv_base/improv_base.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "esphome/core/defines.h" namespace esphome { @@ -9,13 +9,14 @@ namespace improv_base { class ImprovBase { public: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - void set_next_url(const std::string &next_url) { this->next_url_ = next_url; } + void set_next_url(const char *next_url) { this->next_url_ = next_url; } #endif protected: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - std::string get_formatted_next_url_(); - std::string next_url_; + /// Format next_url_ into buffer, replacing placeholders. Returns length written. + size_t get_formatted_next_url_(char *buffer, size_t buffer_size); + const char *next_url_{nullptr}; #endif }; diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 281e95d12b..936ff414b1 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -182,8 +182,12 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector urls; #ifdef USE_IMPROV_SERIAL_NEXT_URL - if (!this->next_url_.empty()) { - urls.push_back(this->get_formatted_next_url_()); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + urls.emplace_back(url_buffer, len); + } } #endif #ifdef USE_WEBSERVER From e0981323bd72dfee9043926d9badb3e9bb026355 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:33:56 -1000 Subject: [PATCH 10/32] [mqtt] Move Home Assistant discovery keys to PROGMEM on ESP8266 (#13011) --- .../components/mqtt/custom_mqtt_device.cpp | 6 +- esphome/components/mqtt/custom_mqtt_device.h | 6 +- .../mqtt/mqtt_alarm_control_panel.cpp | 6 +- .../mqtt/mqtt_alarm_control_panel.h | 6 +- esphome/components/mqtt/mqtt_backend.h | 6 +- .../components/mqtt/mqtt_backend_esp32.cpp | 6 +- esphome/components/mqtt/mqtt_backend_esp32.h | 6 +- .../components/mqtt/mqtt_backend_esp8266.h | 6 +- .../components/mqtt/mqtt_backend_libretiny.h | 6 +- .../components/mqtt/mqtt_binary_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_binary_sensor.h | 6 +- esphome/components/mqtt/mqtt_button.cpp | 6 +- esphome/components/mqtt/mqtt_button.h | 6 +- esphome/components/mqtt/mqtt_client.cpp | 6 +- esphome/components/mqtt/mqtt_client.h | 6 +- esphome/components/mqtt/mqtt_climate.cpp | 6 +- esphome/components/mqtt/mqtt_climate.h | 6 +- esphome/components/mqtt/mqtt_component.cpp | 6 +- esphome/components/mqtt/mqtt_component.h | 6 +- esphome/components/mqtt/mqtt_const.h | 851 +++++++----------- esphome/components/mqtt/mqtt_cover.cpp | 6 +- esphome/components/mqtt/mqtt_cover.h | 6 +- esphome/components/mqtt/mqtt_date.cpp | 6 +- esphome/components/mqtt/mqtt_date.h | 6 +- esphome/components/mqtt/mqtt_datetime.cpp | 6 +- esphome/components/mqtt/mqtt_datetime.h | 6 +- esphome/components/mqtt/mqtt_event.cpp | 6 +- esphome/components/mqtt/mqtt_event.h | 6 +- esphome/components/mqtt/mqtt_fan.cpp | 6 +- esphome/components/mqtt/mqtt_fan.h | 6 +- esphome/components/mqtt/mqtt_light.cpp | 6 +- esphome/components/mqtt/mqtt_light.h | 6 +- esphome/components/mqtt/mqtt_lock.cpp | 6 +- esphome/components/mqtt/mqtt_lock.h | 6 +- esphome/components/mqtt/mqtt_number.cpp | 6 +- esphome/components/mqtt/mqtt_number.h | 6 +- esphome/components/mqtt/mqtt_select.cpp | 6 +- esphome/components/mqtt/mqtt_select.h | 6 +- esphome/components/mqtt/mqtt_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_sensor.h | 6 +- esphome/components/mqtt/mqtt_switch.cpp | 6 +- esphome/components/mqtt/mqtt_switch.h | 6 +- esphome/components/mqtt/mqtt_text.cpp | 6 +- esphome/components/mqtt/mqtt_text.h | 6 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_text_sensor.h | 6 +- esphome/components/mqtt/mqtt_time.cpp | 6 +- esphome/components/mqtt/mqtt_time.h | 6 +- esphome/components/mqtt/mqtt_update.cpp | 6 +- esphome/components/mqtt/mqtt_update.h | 6 +- esphome/components/mqtt/mqtt_valve.cpp | 6 +- esphome/components/mqtt/mqtt_valve.h | 6 +- 52 files changed, 420 insertions(+), 737 deletions(-) diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 787cc1153f..25a8a82066 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.custom"; @@ -29,7 +28,6 @@ bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_b } bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && global_mqtt_client->is_connected(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 0852a17cf1..09ed7bd6d1 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** This class is a helper class for custom components that communicate using * MQTT. It has 5 helper functions that you can use (square brackets indicate optional): @@ -214,7 +213,6 @@ void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callba global_mqtt_client->subscribe_json(topic, f, qos); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index dd3df5f8aa..8c570d1472 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_ALARM_CONTROL_PANEL -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.alarm_control_panel"; @@ -123,8 +122,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.h b/esphome/components/mqtt/mqtt_alarm_control_panel.h index 4ad37b7314..cf4fac1511 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.h +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { alarm_control_panel::AlarmControlPanel *alarm_control_panel_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_backend.h b/esphome/components/mqtt/mqtt_backend.h index 0c1720ec34..a7e3f1013d 100644 --- a/esphome/components/mqtt/mqtt_backend.h +++ b/esphome/components/mqtt/mqtt_backend.h @@ -6,8 +6,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { enum class MQTTClientDisconnectReason : int8_t { TCP_DISCONNECTED = 0, @@ -67,6 +66,5 @@ class MQTTBackend { virtual void loop() {} }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 3838d6df26..c12c79499f 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -8,8 +8,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.idf"; @@ -270,7 +269,6 @@ bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, } #endif // USE_MQTT_IDF_ENQUEUE -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_ESP32 #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index a24e75eaf9..bd2d2a67b2 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -15,8 +15,7 @@ #include "esphome/core/lock_free_queue.h" #include "esphome/core/event_pool.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { struct Event { esp_mqtt_event_id_t event_id; @@ -273,8 +272,7 @@ class MQTTBackendESP32 final : public MQTTBackend { #endif }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp8266.h b/esphome/components/mqtt/mqtt_backend_esp8266.h index a979634bf4..470d1e6a8b 100644 --- a/esphome/components/mqtt/mqtt_backend_esp8266.h +++ b/esphome/components/mqtt/mqtt_backend_esp8266.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendESP8266 final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendESP8266 final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_ESP8266) #endif diff --git a/esphome/components/mqtt/mqtt_backend_libretiny.h b/esphome/components/mqtt/mqtt_backend_libretiny.h index 2578ae9941..24bf018a90 100644 --- a/esphome/components/mqtt/mqtt_backend_libretiny.h +++ b/esphome/components/mqtt/mqtt_backend_libretiny.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendLibreTiny final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendLibreTiny final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_LIBRETINY) #endif diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 479cee205a..146ca46f68 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.binary_sensor"; @@ -57,8 +56,7 @@ bool MQTTBinarySensorComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index f6579fcd19..82176ec97b 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -7,8 +7,7 @@ #include "mqtt_component.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBinarySensorComponent : public mqtt::MQTTComponent { public: @@ -36,8 +35,7 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { binary_sensor::BinarySensor *binary_sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index f8eb0eab2d..2b700a4962 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_BUTTON -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.button"; @@ -43,8 +42,7 @@ void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon std::string MQTTButtonComponent::component_type() const { return "button"; } const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index 42389caecc..ec802664df 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -8,8 +8,7 @@ #include "esphome/components/button/button.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTButtonComponent : public mqtt::MQTTComponent { public: @@ -33,8 +32,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { button::Button *button_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index d26548acfc..aecf809c8b 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -22,8 +22,7 @@ #include "esphome/components/dashboard_import/dashboard_import.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt"; @@ -751,7 +750,6 @@ void MQTTMessageTrigger::dump_config() { } float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 8547fe337f..4189e7ae77 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -24,8 +24,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** Callback for MQTT events. */ @@ -462,7 +461,6 @@ template class MQTTDisableAction : public Action { MQTTClientComponent *parent_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index aee2b38942..9d9ca012a8 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_CLIMATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.climate"; @@ -460,8 +459,7 @@ bool MQTTClimateComponent::publish_state_() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 4e54230e68..f561627ac9 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -8,8 +8,7 @@ #include "esphome/components/climate/climate.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTClimateComponent : public mqtt::MQTTComponent { public: @@ -49,8 +48,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { climate::Climate *device_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 9db1b1f7c8..ccbdb2ea91 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -9,8 +9,7 @@ #include "mqtt_const.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.component"; @@ -306,7 +305,6 @@ bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 2f8dfcf64e..e5f9664f77 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -11,8 +11,7 @@ #include "esphome/core/string_ref.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /// Simple Helper struct used for Home Assistant MQTT send_discovery(). struct SendDiscoveryConfig { @@ -205,7 +204,6 @@ class MQTTComponent : public Component { bool resend_state_{false}; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTt diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 3ddd8fc5cc..221af00371 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -1,547 +1,332 @@ #pragma once #include "esphome/core/defines.h" +#include "esphome/core/progmem.h" #ifdef USE_MQTT -namespace esphome { -namespace mqtt { +// MQTT JSON Key Constants for Home Assistant Discovery +// +// This file defines string constants used as JSON keys in MQTT discovery payloads +// for Home Assistant integration. These are used exclusively with ArduinoJson as: +// root[MQTT_DEVICE_CLASS] = "temperature"; +// +// Implementation: +// - ESP8266: Stores strings in PROGMEM (flash) using __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +// - Other platforms: Uses constexpr const char* for compile-time optimization. +// - USE_MQTT_ABBREVIATIONS: When defined, uses shortened key names to reduce message size. +// +// Adding new keys: +// Add a single line to MQTT_KEYS_LIST: X(MQTT_NEW_KEY, "abbr", "full_name") +// The X-macro will generate the appropriate constants for each platform. +// +// Note: Other MQTT_* constants (e.g., MQTT_CLIENT_CONNECTED, MQTT_LEGACY_UNIQUE_ID_GENERATOR) +// are C++ enums defined in mqtt_client.h and mqtt_backend*.h - unrelated to these JSON keys. +// X-macro list: MQTT_KEYS_LIST(X) calls X(name, abbr, full) for each key +// clang-format off +#define MQTT_KEYS_LIST(X) \ + X(MQTT_ACTION_TEMPLATE, "act_tpl", "action_template") \ + X(MQTT_ACTION_TOPIC, "act_t", "action_topic") \ + X(MQTT_AUTOMATION_TYPE, "atype", "automation_type") \ + X(MQTT_AUX_COMMAND_TOPIC, "aux_cmd_t", "aux_command_topic") \ + X(MQTT_AUX_STATE_TEMPLATE, "aux_stat_tpl", "aux_state_template") \ + X(MQTT_AUX_STATE_TOPIC, "aux_stat_t", "aux_state_topic") \ + X(MQTT_AVAILABILITY, "avty", "availability") \ + X(MQTT_AVAILABILITY_MODE, "avty_mode", "availability_mode") \ + X(MQTT_AVAILABILITY_TOPIC, "avty_t", "availability_topic") \ + X(MQTT_AWAY_MODE_COMMAND_TOPIC, "away_mode_cmd_t", "away_mode_command_topic") \ + X(MQTT_AWAY_MODE_STATE_TEMPLATE, "away_mode_stat_tpl", "away_mode_state_template") \ + X(MQTT_AWAY_MODE_STATE_TOPIC, "away_mode_stat_t", "away_mode_state_topic") \ + X(MQTT_BATTERY_LEVEL_TEMPLATE, "bat_lev_tpl", "battery_level_template") \ + X(MQTT_BATTERY_LEVEL_TOPIC, "bat_lev_t", "battery_level_topic") \ + X(MQTT_BLUE_TEMPLATE, "b_tpl", "blue_template") \ + X(MQTT_BRIGHTNESS_COMMAND_TOPIC, "bri_cmd_t", "brightness_command_topic") \ + X(MQTT_BRIGHTNESS_SCALE, "bri_scl", "brightness_scale") \ + X(MQTT_BRIGHTNESS_STATE_TOPIC, "bri_stat_t", "brightness_state_topic") \ + X(MQTT_BRIGHTNESS_TEMPLATE, "bri_tpl", "brightness_template") \ + X(MQTT_BRIGHTNESS_VALUE_TEMPLATE, "bri_val_tpl", "brightness_value_template") \ + X(MQTT_CHARGING_TEMPLATE, "chrg_tpl", "charging_template") \ + X(MQTT_CHARGING_TOPIC, "chrg_t", "charging_topic") \ + X(MQTT_CLEANING_TEMPLATE, "cln_tpl", "cleaning_template") \ + X(MQTT_CLEANING_TOPIC, "cln_t", "cleaning_topic") \ + X(MQTT_CODE_ARM_REQUIRED, "cod_arm_req", "code_arm_required") \ + X(MQTT_CODE_DISARM_REQUIRED, "cod_dis_req", "code_disarm_required") \ + X(MQTT_COLOR_MODE, "clrm", "color_mode") \ + X(MQTT_COLOR_MODE_STATE_TOPIC, "clrm_stat_t", "color_mode_state_topic") \ + X(MQTT_COLOR_MODE_VALUE_TEMPLATE, "clrm_val_tpl", "color_mode_value_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TEMPLATE, "clr_temp_cmd_tpl", "color_temp_command_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TOPIC, "clr_temp_cmd_t", "color_temp_command_topic") \ + X(MQTT_COLOR_TEMP_STATE_TOPIC, "clr_temp_stat_t", "color_temp_state_topic") \ + X(MQTT_COLOR_TEMP_TEMPLATE, "clr_temp_tpl", "color_temp_template") \ + X(MQTT_COLOR_TEMP_VALUE_TEMPLATE, "clr_temp_val_tpl", "color_temp_value_template") \ + X(MQTT_COMMAND_OFF_TEMPLATE, "cmd_off_tpl", "command_off_template") \ + X(MQTT_COMMAND_ON_TEMPLATE, "cmd_on_tpl", "command_on_template") \ + X(MQTT_COMMAND_RETAIN, "ret", "retain") \ + X(MQTT_COMMAND_TEMPLATE, "cmd_tpl", "command_template") \ + X(MQTT_COMMAND_TOPIC, "cmd_t", "command_topic") \ + X(MQTT_CONFIGURATION_URL, "cu", "configuration_url") \ + X(MQTT_CURRENT_HUMIDITY_TEMPLATE, "curr_hum_tpl", "current_humidity_template") \ + X(MQTT_CURRENT_HUMIDITY_TOPIC, "curr_hum_t", "current_humidity_topic") \ + X(MQTT_CURRENT_TEMPERATURE_STEP, "precision", "precision") \ + X(MQTT_CURRENT_TEMPERATURE_TEMPLATE, "curr_temp_tpl", "current_temperature_template") \ + X(MQTT_CURRENT_TEMPERATURE_TOPIC, "curr_temp_t", "current_temperature_topic") \ + X(MQTT_DEVICE, "dev", "device") \ + X(MQTT_DEVICE_CLASS, "dev_cla", "device_class") \ + X(MQTT_DEVICE_CONNECTIONS, "cns", "connections") \ + X(MQTT_DEVICE_IDENTIFIERS, "ids", "identifiers") \ + X(MQTT_DEVICE_MANUFACTURER, "mf", "manufacturer") \ + X(MQTT_DEVICE_MODEL, "mdl", "model") \ + X(MQTT_DEVICE_NAME, "name", "name") \ + X(MQTT_DEVICE_SUGGESTED_AREA, "sa", "suggested_area") \ + X(MQTT_DEVICE_SW_VERSION, "sw", "sw_version") \ + X(MQTT_DEVICE_HW_VERSION, "hw", "hw_version") \ + X(MQTT_DIRECTION_COMMAND_TOPIC, "dir_cmd_t", "direction_command_topic") \ + X(MQTT_DIRECTION_STATE_TOPIC, "dir_stat_t", "direction_state_topic") \ + X(MQTT_DOCKED_TEMPLATE, "dock_tpl", "docked_template") \ + X(MQTT_DOCKED_TOPIC, "dock_t", "docked_topic") \ + X(MQTT_EFFECT_COMMAND_TOPIC, "fx_cmd_t", "effect_command_topic") \ + X(MQTT_EFFECT_LIST, "fx_list", "effect_list") \ + X(MQTT_EFFECT_STATE_TOPIC, "fx_stat_t", "effect_state_topic") \ + X(MQTT_EFFECT_TEMPLATE, "fx_tpl", "effect_template") \ + X(MQTT_EFFECT_VALUE_TEMPLATE, "fx_val_tpl", "effect_value_template") \ + X(MQTT_ENABLED_BY_DEFAULT, "en", "enabled_by_default") \ + X(MQTT_ENTITY_CATEGORY, "ent_cat", "entity_category") \ + X(MQTT_ERROR_TEMPLATE, "err_tpl", "error_template") \ + X(MQTT_ERROR_TOPIC, "err_t", "error_topic") \ + X(MQTT_EVENT_TYPE, "event_type", "event_type") \ + X(MQTT_EVENT_TYPES, "evt_typ", "event_types") \ + X(MQTT_EXPIRE_AFTER, "exp_aft", "expire_after") \ + X(MQTT_FAN_MODE_COMMAND_TEMPLATE, "fan_mode_cmd_tpl", "fan_mode_command_template") \ + X(MQTT_FAN_MODE_COMMAND_TOPIC, "fan_mode_cmd_t", "fan_mode_command_topic") \ + X(MQTT_FAN_MODE_STATE_TEMPLATE, "fan_mode_stat_tpl", "fan_mode_state_template") \ + X(MQTT_FAN_MODE_STATE_TOPIC, "fan_mode_stat_t", "fan_mode_state_topic") \ + X(MQTT_FAN_SPEED_LIST, "fanspd_lst", "fan_speed_list") \ + X(MQTT_FAN_SPEED_TEMPLATE, "fanspd_tpl", "fan_speed_template") \ + X(MQTT_FAN_SPEED_TOPIC, "fanspd_t", "fan_speed_topic") \ + X(MQTT_FLASH_TIME_LONG, "flsh_tlng", "flash_time_long") \ + X(MQTT_FLASH_TIME_SHORT, "flsh_tsht", "flash_time_short") \ + X(MQTT_FORCE_UPDATE, "frc_upd", "force_update") \ + X(MQTT_GREEN_TEMPLATE, "g_tpl", "green_template") \ + X(MQTT_HOLD_COMMAND_TEMPLATE, "hold_cmd_tpl", "hold_command_template") \ + X(MQTT_HOLD_COMMAND_TOPIC, "hold_cmd_t", "hold_command_topic") \ + X(MQTT_HOLD_STATE_TEMPLATE, "hold_stat_tpl", "hold_state_template") \ + X(MQTT_HOLD_STATE_TOPIC, "hold_stat_t", "hold_state_topic") \ + X(MQTT_HS_COMMAND_TOPIC, "hs_cmd_t", "hs_command_topic") \ + X(MQTT_HS_STATE_TOPIC, "hs_stat_t", "hs_state_topic") \ + X(MQTT_HS_VALUE_TEMPLATE, "hs_val_tpl", "hs_value_template") \ + X(MQTT_ICON, "ic", "icon") \ + X(MQTT_INITIAL, "init", "initial") \ + X(MQTT_JSON_ATTRIBUTES, "json_attr", "json_attributes") \ + X(MQTT_JSON_ATTRIBUTES_TEMPLATE, "json_attr_tpl", "json_attributes_template") \ + X(MQTT_JSON_ATTRIBUTES_TOPIC, "json_attr_t", "json_attributes_topic") \ + X(MQTT_LAST_RESET_TOPIC, "lrst_t", "last_reset_topic") \ + X(MQTT_LAST_RESET_VALUE_TEMPLATE, "lrst_val_tpl", "last_reset_value_template") \ + X(MQTT_MAX, "max", "max") \ + X(MQTT_MAX_HUMIDITY, "max_hum", "max_humidity") \ + X(MQTT_MAX_MIREDS, "max_mirs", "max_mireds") \ + X(MQTT_MAX_TEMP, "max_temp", "max_temp") \ + X(MQTT_MIN, "min", "min") \ + X(MQTT_MIN_HUMIDITY, "min_hum", "min_humidity") \ + X(MQTT_MIN_MIREDS, "min_mirs", "min_mireds") \ + X(MQTT_MIN_TEMP, "min_temp", "min_temp") \ + X(MQTT_MODE, "mode", "mode") \ + X(MQTT_MODE_COMMAND_TEMPLATE, "mode_cmd_tpl", "mode_command_template") \ + X(MQTT_MODE_COMMAND_TOPIC, "mode_cmd_t", "mode_command_topic") \ + X(MQTT_MODE_STATE_TEMPLATE, "mode_stat_tpl", "mode_state_template") \ + X(MQTT_MODE_STATE_TOPIC, "mode_stat_t", "mode_state_topic") \ + X(MQTT_MODES, "modes", "modes") \ + X(MQTT_NAME, "name", "name") \ + X(MQTT_OBJECT_ID, "obj_id", "object_id") \ + X(MQTT_OFF_DELAY, "off_dly", "off_delay") \ + X(MQTT_ON_COMMAND_TYPE, "on_cmd_type", "on_command_type") \ + X(MQTT_OPTIMISTIC, "opt", "optimistic") \ + X(MQTT_OPTIONS, "ops", "options") \ + X(MQTT_OSCILLATION_COMMAND_TEMPLATE, "osc_cmd_tpl", "oscillation_command_template") \ + X(MQTT_OSCILLATION_COMMAND_TOPIC, "osc_cmd_t", "oscillation_command_topic") \ + X(MQTT_OSCILLATION_STATE_TOPIC, "osc_stat_t", "oscillation_state_topic") \ + X(MQTT_OSCILLATION_VALUE_TEMPLATE, "osc_val_tpl", "oscillation_value_template") \ + X(MQTT_PAYLOAD, "pl", "payload") \ + X(MQTT_PAYLOAD_ARM_AWAY, "pl_arm_away", "payload_arm_away") \ + X(MQTT_PAYLOAD_ARM_CUSTOM_BYPASS, "pl_arm_custom_b", "payload_arm_custom_bypass") \ + X(MQTT_PAYLOAD_ARM_HOME, "pl_arm_home", "payload_arm_home") \ + X(MQTT_PAYLOAD_ARM_NIGHT, "pl_arm_nite", "payload_arm_night") \ + X(MQTT_PAYLOAD_ARM_VACATION, "pl_arm_vacation", "payload_arm_vacation") \ + X(MQTT_PAYLOAD_AVAILABLE, "pl_avail", "payload_available") \ + X(MQTT_PAYLOAD_CLEAN_SPOT, "pl_cln_sp", "payload_clean_spot") \ + X(MQTT_PAYLOAD_CLOSE, "pl_cls", "payload_close") \ + X(MQTT_PAYLOAD_DISARM, "pl_disarm", "payload_disarm") \ + X(MQTT_PAYLOAD_HIGH_SPEED, "pl_hi_spd", "payload_high_speed") \ + X(MQTT_PAYLOAD_HOME, "pl_home", "payload_home") \ + X(MQTT_PAYLOAD_INSTALL, "pl_inst", "payload_install") \ + X(MQTT_PAYLOAD_LOCATE, "pl_loc", "payload_locate") \ + X(MQTT_PAYLOAD_LOCK, "pl_lock", "payload_lock") \ + X(MQTT_PAYLOAD_LOW_SPEED, "pl_lo_spd", "payload_low_speed") \ + X(MQTT_PAYLOAD_MEDIUM_SPEED, "pl_med_spd", "payload_medium_speed") \ + X(MQTT_PAYLOAD_NOT_AVAILABLE, "pl_not_avail", "payload_not_available") \ + X(MQTT_PAYLOAD_NOT_HOME, "pl_not_home", "payload_not_home") \ + X(MQTT_PAYLOAD_OFF, "pl_off", "payload_off") \ + X(MQTT_PAYLOAD_OFF_SPEED, "pl_off_spd", "payload_off_speed") \ + X(MQTT_PAYLOAD_ON, "pl_on", "payload_on") \ + X(MQTT_PAYLOAD_OPEN, "pl_open", "payload_open") \ + X(MQTT_PAYLOAD_OSCILLATION_OFF, "pl_osc_off", "payload_oscillation_off") \ + X(MQTT_PAYLOAD_OSCILLATION_ON, "pl_osc_on", "payload_oscillation_on") \ + X(MQTT_PAYLOAD_PAUSE, "pl_paus", "payload_pause") \ + X(MQTT_PAYLOAD_RESET, "pl_rst", "payload_reset") \ + X(MQTT_PAYLOAD_RESET_HUMIDITY, "pl_rst_hum", "payload_reset_humidity") \ + X(MQTT_PAYLOAD_RESET_MODE, "pl_rst_mode", "payload_reset_mode") \ + X(MQTT_PAYLOAD_RESET_PERCENTAGE, "pl_rst_pct", "payload_reset_percentage") \ + X(MQTT_PAYLOAD_RESET_PRESET_MODE, "pl_rst_pr_mode", "payload_reset_preset_mode") \ + X(MQTT_PAYLOAD_RETURN_TO_BASE, "pl_ret", "payload_return_to_base") \ + X(MQTT_PAYLOAD_START, "pl_strt", "payload_start") \ + X(MQTT_PAYLOAD_START_PAUSE, "pl_stpa", "payload_start_pause") \ + X(MQTT_PAYLOAD_STOP, "pl_stop", "payload_stop") \ + X(MQTT_PAYLOAD_TURN_OFF, "pl_toff", "payload_turn_off") \ + X(MQTT_PAYLOAD_TURN_ON, "pl_ton", "payload_turn_on") \ + X(MQTT_PAYLOAD_UNLOCK, "pl_unlk", "payload_unlock") \ + X(MQTT_PERCENTAGE_COMMAND_TEMPLATE, "pct_cmd_tpl", "percentage_command_template") \ + X(MQTT_PERCENTAGE_COMMAND_TOPIC, "pct_cmd_t", "percentage_command_topic") \ + X(MQTT_PERCENTAGE_STATE_TOPIC, "pct_stat_t", "percentage_state_topic") \ + X(MQTT_PERCENTAGE_VALUE_TEMPLATE, "pct_val_tpl", "percentage_value_template") \ + X(MQTT_POSITION_CLOSED, "pos_clsd", "position_closed") \ + X(MQTT_POSITION_OPEN, "pos_open", "position_open") \ + X(MQTT_POSITION_TEMPLATE, "pos_tpl", "position_template") \ + X(MQTT_POSITION_TOPIC, "pos_t", "position_topic") \ + X(MQTT_POWER_COMMAND_TOPIC, "pow_cmd_t", "power_command_topic") \ + X(MQTT_POWER_STATE_TEMPLATE, "pow_stat_tpl", "power_state_template") \ + X(MQTT_POWER_STATE_TOPIC, "pow_stat_t", "power_state_topic") \ + X(MQTT_PRESET_MODE_COMMAND_TEMPLATE, "pr_mode_cmd_tpl", "preset_mode_command_template") \ + X(MQTT_PRESET_MODE_COMMAND_TOPIC, "pr_mode_cmd_t", "preset_mode_command_topic") \ + X(MQTT_PRESET_MODE_STATE_TOPIC, "pr_mode_stat_t", "preset_mode_state_topic") \ + X(MQTT_PRESET_MODE_VALUE_TEMPLATE, "pr_mode_val_tpl", "preset_mode_value_template") \ + X(MQTT_PRESET_MODES, "pr_modes", "preset_modes") \ + X(MQTT_QOS, "qos", "qos") \ + X(MQTT_RED_TEMPLATE, "r_tpl", "red_template") \ + X(MQTT_RETAIN, "ret", "retain") \ + X(MQTT_RGB_COMMAND_TEMPLATE, "rgb_cmd_tpl", "rgb_command_template") \ + X(MQTT_RGB_COMMAND_TOPIC, "rgb_cmd_t", "rgb_command_topic") \ + X(MQTT_RGB_STATE_TOPIC, "rgb_stat_t", "rgb_state_topic") \ + X(MQTT_RGB_VALUE_TEMPLATE, "rgb_val_tpl", "rgb_value_template") \ + X(MQTT_RGBW_COMMAND_TEMPLATE, "rgbw_cmd_tpl", "rgbw_command_template") \ + X(MQTT_RGBW_COMMAND_TOPIC, "rgbw_cmd_t", "rgbw_command_topic") \ + X(MQTT_RGBW_STATE_TOPIC, "rgbw_stat_t", "rgbw_state_topic") \ + X(MQTT_RGBW_VALUE_TEMPLATE, "rgbw_val_tpl", "rgbw_value_template") \ + X(MQTT_RGBWW_COMMAND_TEMPLATE, "rgbww_cmd_tpl", "rgbww_command_template") \ + X(MQTT_RGBWW_COMMAND_TOPIC, "rgbww_cmd_t", "rgbww_command_topic") \ + X(MQTT_RGBWW_STATE_TOPIC, "rgbww_stat_t", "rgbww_state_topic") \ + X(MQTT_RGBWW_VALUE_TEMPLATE, "rgbww_val_tpl", "rgbww_value_template") \ + X(MQTT_SEND_COMMAND_TOPIC, "send_cmd_t", "send_command_topic") \ + X(MQTT_SEND_IF_OFF, "send_if_off", "send_if_off") \ + X(MQTT_SET_FAN_SPEED_TOPIC, "set_fan_spd_t", "set_fan_speed_topic") \ + X(MQTT_SET_POSITION_TEMPLATE, "set_pos_tpl", "set_position_template") \ + X(MQTT_SET_POSITION_TOPIC, "set_pos_t", "set_position_topic") \ + X(MQTT_SOURCE_TYPE, "src_type", "source_type") \ + X(MQTT_SPEED_COMMAND_TOPIC, "spd_cmd_t", "speed_command_topic") \ + X(MQTT_SPEED_RANGE_MAX, "spd_rng_max", "speed_range_max") \ + X(MQTT_SPEED_RANGE_MIN, "spd_rng_min", "speed_range_min") \ + X(MQTT_SPEED_STATE_TOPIC, "spd_stat_t", "speed_state_topic") \ + X(MQTT_SPEED_VALUE_TEMPLATE, "spd_val_tpl", "speed_value_template") \ + X(MQTT_SPEEDS, "spds", "speeds") \ + X(MQTT_STATE_CLASS, "stat_cla", "state_class") \ + X(MQTT_STATE_CLOSED, "stat_clsd", "state_closed") \ + X(MQTT_STATE_CLOSING, "stat_closing", "state_closing") \ + X(MQTT_STATE_LOCKED, "stat_locked", "state_locked") \ + X(MQTT_STATE_OFF, "stat_off", "state_off") \ + X(MQTT_STATE_ON, "stat_on", "state_on") \ + X(MQTT_STATE_OPEN, "stat_open", "state_open") \ + X(MQTT_STATE_OPENING, "stat_opening", "state_opening") \ + X(MQTT_STATE_STOPPED, "stat_stopped", "state_stopped") \ + X(MQTT_STATE_TEMPLATE, "stat_tpl", "state_template") \ + X(MQTT_STATE_TOPIC, "stat_t", "state_topic") \ + X(MQTT_STATE_UNLOCKED, "stat_unlocked", "state_unlocked") \ + X(MQTT_STATE_VALUE_TEMPLATE, "stat_val_tpl", "state_value_template") \ + X(MQTT_STEP, "step", "step") \ + X(MQTT_SUBTYPE, "stype", "subtype") \ + X(MQTT_SUPPORTED_COLOR_MODES, "sup_clrm", "supported_color_modes") \ + X(MQTT_SUPPORTED_FEATURES, "sup_feat", "supported_features") \ + X(MQTT_SWING_MODE_COMMAND_TEMPLATE, "swing_mode_cmd_tpl", "swing_mode_command_template") \ + X(MQTT_SWING_MODE_COMMAND_TOPIC, "swing_mode_cmd_t", "swing_mode_command_topic") \ + X(MQTT_SWING_MODE_STATE_TEMPLATE, "swing_mode_stat_tpl", "swing_mode_state_template") \ + X(MQTT_SWING_MODE_STATE_TOPIC, "swing_mode_stat_t", "swing_mode_state_topic") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE, "hum_cmd_tpl", "target_humidity_command_template") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TOPIC, "hum_cmd_t", "target_humidity_command_topic") \ + X(MQTT_TARGET_HUMIDITY_STATE_TEMPLATE, "hum_state_tpl", "target_humidity_state_template") \ + X(MQTT_TARGET_HUMIDITY_STATE_TOPIC, "hum_stat_t", "target_humidity_state_topic") \ + X(MQTT_TARGET_TEMPERATURE_STEP, "temp_step", "temp_step") \ + X(MQTT_TEMPERATURE_COMMAND_TEMPLATE, "temp_cmd_tpl", "temperature_command_template") \ + X(MQTT_TEMPERATURE_COMMAND_TOPIC, "temp_cmd_t", "temperature_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE, "temp_hi_cmd_tpl", "temperature_high_command_template") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC, "temp_hi_cmd_t", "temperature_high_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE, "temp_hi_stat_tpl", "temperature_high_state_template") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TOPIC, "temp_hi_stat_t", "temperature_high_state_topic") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE, "temp_lo_cmd_tpl", "temperature_low_command_template") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TOPIC, "temp_lo_cmd_t", "temperature_low_command_topic") \ + X(MQTT_TEMPERATURE_LOW_STATE_TEMPLATE, "temp_lo_stat_tpl", "temperature_low_state_template") \ + X(MQTT_TEMPERATURE_LOW_STATE_TOPIC, "temp_lo_stat_t", "temperature_low_state_topic") \ + X(MQTT_TEMPERATURE_STATE_TEMPLATE, "temp_stat_tpl", "temperature_state_template") \ + X(MQTT_TEMPERATURE_STATE_TOPIC, "temp_stat_t", "temperature_state_topic") \ + X(MQTT_TEMPERATURE_UNIT, "temp_unit", "temperature_unit") \ + X(MQTT_TILT_CLOSED_VALUE, "tilt_clsd_val", "tilt_closed_value") \ + X(MQTT_TILT_COMMAND_TEMPLATE, "tilt_cmd_tpl", "tilt_command_template") \ + X(MQTT_TILT_COMMAND_TOPIC, "tilt_cmd_t", "tilt_command_topic") \ + X(MQTT_TILT_INVERT_STATE, "tilt_inv_stat", "tilt_invert_state") \ + X(MQTT_TILT_MAX, "tilt_max", "tilt_max") \ + X(MQTT_TILT_MIN, "tilt_min", "tilt_min") \ + X(MQTT_TILT_OPENED_VALUE, "tilt_opnd_val", "tilt_opened_value") \ + X(MQTT_TILT_OPTIMISTIC, "tilt_opt", "tilt_optimistic") \ + X(MQTT_TILT_STATUS_TEMPLATE, "tilt_status_tpl", "tilt_status_template") \ + X(MQTT_TILT_STATUS_TOPIC, "tilt_status_t", "tilt_status_topic") \ + X(MQTT_TOPIC, "t", "topic") \ + X(MQTT_UNIQUE_ID, "uniq_id", "unique_id") \ + X(MQTT_UNIT_OF_MEASUREMENT, "unit_of_meas", "unit_of_measurement") \ + X(MQTT_VALUE_TEMPLATE, "val_tpl", "value_template") \ + X(MQTT_WHITE_COMMAND_TOPIC, "whit_cmd_t", "white_command_topic") \ + X(MQTT_WHITE_SCALE, "whit_scl", "white_scale") \ + X(MQTT_WHITE_VALUE_COMMAND_TOPIC, "whit_val_cmd_t", "white_value_command_topic") \ + X(MQTT_WHITE_VALUE_SCALE, "whit_val_scl", "white_value_scale") \ + X(MQTT_WHITE_VALUE_STATE_TOPIC, "whit_val_stat_t", "white_value_state_topic") \ + X(MQTT_WHITE_VALUE_TEMPLATE, "whit_val_tpl", "white_value_template") \ + X(MQTT_XY_COMMAND_TOPIC, "xy_cmd_t", "xy_command_topic") \ + X(MQTT_XY_STATE_TOPIC, "xy_stat_t", "xy_state_topic") \ + X(MQTT_XY_VALUE_TEMPLATE, "xy_val_tpl", "xy_value_template") +// clang-format on + +#ifdef USE_ESP8266 +// ESP8266: Store strings in PROGMEM (flash) and expose as __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +namespace esphome::mqtt { + +// Generate PROGMEM data arrays #ifdef USE_MQTT_ABBREVIATIONS - -constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; -constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; -constexpr const char *const MQTT_AVAILABILITY = "avty"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; -constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; -constexpr const char *const MQTT_COLOR_MODE = "clrm"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; -constexpr const char *const MQTT_COMMAND_RETAIN = "ret"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; -constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; -constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; -constexpr const char *const MQTT_DEVICE = "dev"; -constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; -constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "dir_cmd_t"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "dir_stat_t"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; -constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; -constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; -constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "evt_typ"; -constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; -constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; -constexpr const char *const MQTT_ICON = "ic"; -constexpr const char *const MQTT_INITIAL = "init"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "obj_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_dly"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; -constexpr const char *const MQTT_OPTIMISTIC = "opt"; -constexpr const char *const MQTT_OPTIONS = "ops"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; -constexpr const char *const MQTT_PAYLOAD = "pl"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; -constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; -constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; -constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; -constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; -constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; -constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; -constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; -constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; -constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; -constexpr const char *const MQTT_RETAIN = "ret"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; -constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; -constexpr const char *const MQTT_SPEEDS = "spds"; -constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; -constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; -constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; -constexpr const char *const MQTT_STATE_OFF = "stat_off"; -constexpr const char *const MQTT_STATE_ON = "stat_on"; -constexpr const char *const MQTT_STATE_OPEN = "stat_open"; -constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; -constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; -constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "stype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; -constexpr const char *const MQTT_TOPIC = "t"; -constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; -constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; - +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = abbr; #else +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = full; +#endif +MQTT_KEYS_LIST(MQTT_DATA) +#undef MQTT_DATA -constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; -constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; -constexpr const char *const MQTT_AVAILABILITY = "availability"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; -constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; -constexpr const char *const MQTT_COLOR_MODE = "color_mode"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; -constexpr const char *const MQTT_COMMAND_RETAIN = "retain"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; -constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; -constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; -constexpr const char *const MQTT_DEVICE = "device"; -constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; -constexpr const char *const MQTT_DEVICE_MODEL = "model"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw_version"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "direction_command_topic"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "direction_state_topic"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; -constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; -constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; -constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "event_types"; -constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; -constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; -constexpr const char *const MQTT_ICON = "icon"; -constexpr const char *const MQTT_INITIAL = "initial"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "object_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_delay"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; -constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; -constexpr const char *const MQTT_OPTIONS = "options"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; -constexpr const char *const MQTT_PAYLOAD = "payload"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; -constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; -constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; -constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; -constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; -constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; -constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; -constexpr const char *const MQTT_POSITION_OPEN = "position_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; -constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; -constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; -constexpr const char *const MQTT_RETAIN = "retain"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; -constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; -constexpr const char *const MQTT_SPEEDS = "speeds"; -constexpr const char *const MQTT_STATE_CLASS = "state_class"; -constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; -constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; -constexpr const char *const MQTT_STATE_OFF = "state_off"; -constexpr const char *const MQTT_STATE_ON = "state_on"; -constexpr const char *const MQTT_STATE_OPEN = "state_open"; -constexpr const char *const MQTT_STATE_OPENING = "state_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; -constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; -constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "subtype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; -constexpr const char *const MQTT_TOPIC = "topic"; -constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; -constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; +// Generate flash string pointers from the PROGMEM data +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_PTR(name, abbr, full) \ + static const __FlashStringHelper *const name = reinterpret_cast(name##_data); +MQTT_KEYS_LIST(MQTT_PTR) +#undef MQTT_PTR +} // namespace esphome::mqtt +#else +// Other platforms: constexpr in namespace +namespace esphome::mqtt { +#ifdef USE_MQTT_ABBREVIATIONS +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = abbr; +#else +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = full; +#endif +MQTT_KEYS_LIST(MQTT_CONST) +#undef MQTT_CONST +} // namespace esphome::mqtt #endif -} // namespace mqtt -} // namespace esphome - #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index b63aa66d29..e628ac37a9 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_COVER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.cover"; @@ -119,8 +118,7 @@ bool MQTTCoverComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index f3e6053d0b..6b874af16a 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -8,8 +8,7 @@ #include "esphome/components/cover/cover.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTCoverComponent : public mqtt::MQTTComponent { public: @@ -36,8 +35,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { cover::Cover *cover_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 0f0a334ae7..c5a17abdfd 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime"; @@ -62,8 +61,7 @@ bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h index 5147afe7e7..380bb69e0e 100644 --- a/esphome/components/mqtt/mqtt_date.h +++ b/esphome/components/mqtt/mqtt_date.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/date_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTDateComponent : public mqtt::MQTTComponent { datetime::DateEntity *date_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 5c56baabe0..d2feddcb00 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATETIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.datetime"; @@ -78,8 +77,7 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.h b/esphome/components/mqtt/mqtt_datetime.h index ba81c06cb3..8706bfcf75 100644 --- a/esphome/components/mqtt/mqtt_datetime.h +++ b/esphome/components/mqtt/mqtt_datetime.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/datetime_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateTimeComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent { datetime::DateTimeEntity *datetime_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index fd095ea041..67a7aab5bd 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_EVENT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.event"; @@ -54,8 +53,7 @@ bool MQTTEventComponent::publish_event_(const std::string &event_type) { std::string MQTTEventComponent::component_type() const { return "event"; } const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.h b/esphome/components/mqtt/mqtt_event.h index 4335820e53..fc6e778d44 100644 --- a/esphome/components/mqtt/mqtt_event.h +++ b/esphome/components/mqtt/mqtt_event.h @@ -8,8 +8,7 @@ #include "esphome/components/event/event.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTEventComponent : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTEventComponent : public mqtt::MQTTComponent { event::Event *event_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 2aefc3a4db..ffecd9c663 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_FAN -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.fan"; @@ -182,8 +181,7 @@ bool MQTTFanComponent::publish_state() { return !failed; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 78641d224f..16ce246853 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -8,8 +8,7 @@ #include "esphome/components/fan/fan.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTFanComponent : public mqtt::MQTTComponent { public: @@ -47,8 +46,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::Fan *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index fe911bfba2..6a040e4b1c 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -8,8 +8,7 @@ #ifdef USE_LIGHT #include "esphome/components/light/light_json_schema.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.light"; @@ -92,8 +91,7 @@ void MQTTJSONLightComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index a105f3d7b8..2cc631c901 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/light/light_state.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRemoteValuesListener { public: @@ -37,8 +36,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRe light::LightState *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 95efbf60e1..58fa675eb7 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_LOCK -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.lock"; @@ -58,8 +57,7 @@ bool MQTTLockComponent::publish_state() { #endif } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h index 789f74c795..6fb4998b25 100644 --- a/esphome/components/mqtt/mqtt_lock.h +++ b/esphome/components/mqtt/mqtt_lock.h @@ -8,8 +8,7 @@ #include "esphome/components/lock/lock.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTLockComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTLockComponent : public mqtt::MQTTComponent { lock::Lock *lock_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index f419eac130..381574ae56 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_NUMBER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.number"; @@ -80,8 +79,7 @@ bool MQTTNumberComponent::publish_state(float value) { return this->publish(this->get_state_topic_(), buffer); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 10500c8333..b89e78a454 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -8,8 +8,7 @@ #include "esphome/components/number/number.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTNumberComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { number::Number *number_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index e48af980c8..5edc5c50dc 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SELECT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.select"; @@ -53,8 +52,7 @@ bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index e0d8ac2417..19aad662e5 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -8,8 +8,7 @@ #include "esphome/components/select/select.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSelectComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { select::Select *select_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 010ac3013e..bd79ae40fe 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -11,8 +11,7 @@ #include "esphome/components/deep_sleep/deep_sleep_component.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.sensor"; @@ -86,8 +85,7 @@ bool MQTTSensorComponent::publish_state(float value) { return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 15ea703ad4..8c60199e1b 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/sensor/sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSensorComponent : public mqtt::MQTTComponent { public: @@ -51,8 +50,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { optional expire_after_; // Override the expire after advertised to Home Assistant }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b3a35420b9..a35ae8f9b6 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SWITCH -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.switch"; @@ -57,8 +56,7 @@ bool MQTTSwitchComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index c4d3f7164c..fb6a13f172 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -8,8 +8,7 @@ #include "esphome/components/switch/switch.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSwitchComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { switch_::Switch *switch_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index 5ab0ca9688..3cb851fd38 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text"; @@ -57,8 +56,7 @@ bool MQTTTextComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.h b/esphome/components/mqtt/mqtt_text.h index d9486fcbf8..0480b89395 100644 --- a/esphome/components/mqtt/mqtt_text.h +++ b/esphome/components/mqtt/mqtt_text.h @@ -8,8 +8,7 @@ #include "esphome/components/text/text.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTTextComponent : public mqtt::MQTTComponent { text::Text *text_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index e6e7cf04e8..c87f22fb8e 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text_sensor"; @@ -43,8 +42,7 @@ bool MQTTTextSensor::send_initial_state() { std::string MQTTTextSensor::component_type() const { return "sensor"; } const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 9a14efdd16..d4d38d7eb2 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextSensor : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent { text_sensor::TextSensor *sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 0c95bd8147..c97a463858 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_TIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.time"; @@ -62,8 +61,7 @@ bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t seco }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_TIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.h b/esphome/components/mqtt/mqtt_time.h index b9dd822a73..60345c37ae 100644 --- a/esphome/components/mqtt/mqtt_time.h +++ b/esphome/components/mqtt/mqtt_time.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/time_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTimeComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTTimeComponent : public mqtt::MQTTComponent { datetime::TimeEntity *time_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 20f3a69a9e..150ddbf745 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_UPDATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.update"; @@ -56,8 +55,7 @@ void MQTTUpdateComponent::dump_config() { std::string MQTTUpdateComponent::component_type() const { return "update"; } const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.h b/esphome/components/mqtt/mqtt_update.h index 6fe04c4ea7..d04d22d25f 100644 --- a/esphome/components/mqtt/mqtt_update.h +++ b/esphome/components/mqtt/mqtt_update.h @@ -8,8 +8,7 @@ #include "esphome/components/update/update_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTUpdateComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTUpdateComponent : public mqtt::MQTTComponent { update::UpdateEntity *update_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index ae60670748..8ee693121b 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_VALVE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.valve"; @@ -89,8 +88,7 @@ bool MQTTValveComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.h b/esphome/components/mqtt/mqtt_valve.h index 63a0462193..9e5221e495 100644 --- a/esphome/components/mqtt/mqtt_valve.h +++ b/esphome/components/mqtt/mqtt_valve.h @@ -8,8 +8,7 @@ #include "esphome/components/valve/valve.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTValveComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTValveComponent : public mqtt::MQTTComponent { valve::Valve *valve_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT From 11aed601b85d6f4e1605fa4cfc783af2e4593f41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:34:38 -1000 Subject: [PATCH 11/32] [ble_scanner] Use stack-based string formatting to reduce heap allocations (#13013) --- esphome/components/ble_scanner/ble_scanner.h | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 8bb51fcff2..7061b6d336 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -1,7 +1,8 @@ #pragma once +#include +#include #include -#include #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" @@ -15,17 +16,13 @@ namespace ble_scanner { class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) + - "," - "\"address\":\"" + - device.address_str() + - "\"," - "\"rssi\":" + - to_string(device.get_rssi()) + - "," - "\"name\":\"" + - device.get_name() + "\"}"); - + // Format JSON using stack buffer to avoid heap allocations from string concatenation + char buf[128]; + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + snprintf(buf, sizeof(buf), "{\"timestamp\":%" PRId64 ",\"address\":\"%s\",\"rssi\":%d,\"name\":\"%s\"}", + static_cast(::time(nullptr)), device.address_str_to(addr_buf), device.get_rssi(), + device.get_name().c_str()); + this->publish_state(buf); return true; } void dump_config() override; From d3e193cd7102c6f48fc6c9bf270aeafb0b4df98c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:34:58 -1000 Subject: [PATCH 12/32] [ota] Fix ESP32-S3 OTA crash with hardware SHA acceleration on IDF 5.5.x (#13021) --- esphome/components/esphome/ota/ota_esphome.cpp | 8 ++++++-- esphome/components/sha256/sha256.cpp | 15 +++++++++------ esphome/components/sha256/sha256.h | 18 ++++++++++++++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index ba25c69fae..f71163f79e 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -560,7 +560,9 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - sha256::SHA256 hasher; + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; const size_t hex_size = hasher.get_size() * 2; const size_t nonce_len = hasher.get_size() / 4; @@ -634,7 +636,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - sha256::SHA256 hasher; + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; hasher.init(); hasher.add(this->password_.c_str(), this->password_.length()); diff --git a/esphome/components/sha256/sha256.cpp b/esphome/components/sha256/sha256.cpp index 32abbd739d..48559d7c73 100644 --- a/esphome/components/sha256/sha256.cpp +++ b/esphome/components/sha256/sha256.cpp @@ -10,23 +10,26 @@ namespace esphome::sha256 { #if defined(USE_ESP32) || defined(USE_LIBRETINY) -// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS: +// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x): // // The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains -// internal state that the DMA engine references. This imposes two critical constraints: +// internal state that the DMA engine references. This imposes three critical constraints: // -// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to +// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment. +// Without this, the DMA engine may crash with an abort in sha_hal_read_digest(). +// +// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to // write to incorrect memory locations. This results in null pointer dereferences and crashes. // ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]). // -// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same +// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same // function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack // frame changes (function call/return), the DMA references become invalid and will produce // truncated hash output (20 bytes instead of 32) or corrupt memory. // // CORRECT USAGE: // void my_function() { -// sha256::SHA256 hasher; // Created locally +// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment // hasher.init(); // hasher.add(data, len); // Any size, no chunking needed // hasher.calculate(); @@ -36,7 +39,7 @@ namespace esphome::sha256 { // // INCORRECT USAGE (WILL FAIL ON ESP32-S3): // void my_function() { -// sha256::SHA256 hasher; +// sha256::SHA256 hasher; // WRONG: Missing alignas(32) // helper(&hasher); // WRONG: Passed to different stack frame // } // void helper(HashBase *h) { diff --git a/esphome/components/sha256/sha256.h b/esphome/components/sha256/sha256.h index a2b62799e1..17d80636f1 100644 --- a/esphome/components/sha256/sha256.h +++ b/esphome/components/sha256/sha256.h @@ -22,6 +22,18 @@ namespace esphome::sha256 { +/// SHA256 hash implementation. +/// +/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration: +/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment +/// 2. The object MUST stay in the same stack frame (no passing to other functions) +/// 3. NO Variable Length Arrays (VLAs) in the same function +/// +/// Example usage: +/// alignas(32) sha256::SHA256 hasher; +/// hasher.init(); +/// hasher.add(data, len); +/// hasher.calculate(); class SHA256 : public esphome::HashBase { public: SHA256() = default; @@ -39,10 +51,8 @@ class SHA256 : public esphome::HashBase { protected: #if defined(USE_ESP32) || defined(USE_LIBRETINY) - // CRITICAL: The mbedtls context MUST be stack-allocated (not a pointer) for ESP32-S3 hardware SHA acceleration. - // The ESP32-S3 DMA engine references this structure's memory addresses. If the context is passed to another - // function (crossing stack frames) or if VLAs are present, the DMA operations will corrupt memory and produce - // truncated/incorrect hash results. + // The mbedtls context for ESP32-S3 hardware SHA requires proper alignment and stack frame constraints. + // See class documentation above for critical requirements. mbedtls_sha256_context ctx_{}; #elif defined(USE_ESP8266) || defined(USE_RP2040) br_sha256_context ctx_{}; From c1ad39a0724baf685de035ffa833375372fa1e98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:35:16 -1000 Subject: [PATCH 13/32] [wifi] Clean up duplicate and empty logging output (#13018) --- esphome/components/wifi/wifi_component.cpp | 39 ++++++++-------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ba25bc9f76..e1dc2d17d6 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -317,7 +317,6 @@ void WiFiComponent::start_initial_connection_() { WiFiAP params = this->build_params_for_current_phase_(); this->start_connecting(params); } else { - ESP_LOGI(TAG, "Starting scan"); this->start_scanning(); } } @@ -369,11 +368,7 @@ void WiFiComponent::setup() { } void WiFiComponent::start() { - char mac_s[18]; - ESP_LOGCONFIG(TAG, - "Starting\n" - " Local MAC: %s", - get_mac_address_pretty_into_buffer(mac_s)); + ESP_LOGCONFIG(TAG, "Starting"); this->last_connected_ = millis(); uint32_t hash = this->has_sta() ? App.get_config_version_hash() : 88491487UL; @@ -857,14 +852,6 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } const LogString *get_signal_bars(int8_t rssi) { - // Check for disconnected sentinel value first - if (rssi == WIFI_RSSI_DISCONNECTED) { - // MULTIPLICATION SIGN - // Unicode: U+00D7, UTF-8: C3 97 - return LOG_STR("\033[0;31m" // red - "\xc3\x97\xc3\x97\xc3\x97\xc3\x97" - "\033[0m"); - } // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -909,15 +896,8 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(bssid.data(), bssid_s); - - char mac_s[18]; - ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty_into_buffer(mac_s)); - if (this->is_disabled()) { - ESP_LOGCONFIG(TAG, " Disabled"); - return; - } // Use stack buffers for IP address formatting to avoid heap allocations char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : wifi_sta_ip_addresses()) { @@ -1189,11 +1169,19 @@ void WiFiComponent::check_scanning_finished() { } void WiFiComponent::dump_config() { + char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "WiFi:\n" + " Local MAC: %s\n" " Connected: %s", - YESNO(this->is_connected())); - this->print_connect_params_(); + get_mac_address_pretty_into_buffer(mac_s), YESNO(this->is_connected())); + if (this->is_disabled()) { + ESP_LOGCONFIG(TAG, " Disabled"); + return; + } + if (this->is_connected()) { + this->print_connect_params_(); + } } void WiFiComponent::check_connecting_finished() { @@ -1223,8 +1211,6 @@ void WiFiComponent::check_connecting_finished() { // the first connection as a failure. this->error_from_callback_ = false; - this->print_connect_params_(); - if (this->has_ap()) { #ifdef USE_CAPTIVE_PORTAL if (this->is_captive_portal_active_()) { @@ -1242,6 +1228,7 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + this->print_connect_params_(); // Clear priority tracking if all priorities are at minimum this->clear_priorities_if_all_min_(); From 2c6584baf50a65b26e6d1b072810bf7a4f5ad2bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:35:40 -1000 Subject: [PATCH 14/32] [xiaomi_ble] Simplify set_bindkey using parse_hex and const char* (#13014) --- esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp | 12 +----------- esphome/components/xiaomi_cgd1/xiaomi_cgd1.h | 2 +- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 12 +----------- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h | 2 +- esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 12 +----------- esphome/components/xiaomi_cgg1/xiaomi_cgg1.h | 2 +- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 13 ++----------- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h | 2 +- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp | 12 +----------- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 12 +----------- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h | 2 +- .../components/xiaomi_mhoc401/xiaomi_mhoc401.cpp | 12 +----------- esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 13 ++----------- .../components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h | 2 +- .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 12 +----------- .../components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h | 2 +- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 12 +----------- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h | 2 +- 20 files changed, 22 insertions(+), 120 deletions(-) diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 1aa542633a..82a04f0d6e 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -63,17 +63,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGD1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGD1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgd1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h index 393795439b..4a34eea32a 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgd1 { class XiaomiCGD1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index a049854935..39ece3e091 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -63,17 +63,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGDK2::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgdk2 } // namespace esphome diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h index 1f5ef89869..ed917e2bbd 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h @@ -13,7 +13,7 @@ namespace xiaomi_cgdk2 { class XiaomiCGDK2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index da4bab6623..448592db16 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -63,17 +63,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGG1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGG1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgg1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index 52904fd75e..c560bddd69 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgg1 { class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 2048c786d3..8813f6479b 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgpr1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -59,17 +60,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGPR1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgpr1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h index 124f9411a1..82bbbfa58d 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -16,7 +16,7 @@ class XiaomiCGPR1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index edd9f67f56..2dd60d4ecb 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -63,17 +63,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD02MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD02MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd02mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h index e1e0fcae40..968604fee6 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd02mmc { class XiaomiLYWSD02MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 2b4b67c92f..b11bbdc40c 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -67,17 +67,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD03MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd03mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h index 3c7907479a..d890e5ed12 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd03mmc { class XiaomiLYWSD03MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index e1b808c54e..10cd15ddbd 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -67,17 +67,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMHOC401::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mhoc401 } // namespace esphome diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h index 1acdaa88af..13547e45d9 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h @@ -13,7 +13,7 @@ namespace xiaomi_mhoc401 { class XiaomiMHOC401 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index eb4862a7e9..ec03c851cd 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -1,4 +1,5 @@ #include "xiaomi_mjyd02yla.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -62,17 +63,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMJYD02YLA::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mjyd02yla } // namespace esphome diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h index e1b4055696..bf9dcaf844 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -16,7 +16,7 @@ class XiaomiMJYD02YLA : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index d5b89507fe..ee3ad316e1 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -79,17 +79,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiRTCGQ02LM::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_rtcgq02lm } // namespace esphome diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h index ae00a28ac9..87dfc0b62b 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h @@ -19,7 +19,7 @@ namespace xiaomi_rtcgq02lm { class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index f126e8bdfd..50cf5f2d76 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -67,17 +67,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic return success; } -void XiaomiXMWSDJ04MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiXMWSDJ04MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_xmwsdj04mmc } // namespace esphome diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h index ed0458ce49..22cac63059 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_xmwsdj04mmc { class XiaomiXMWSDJ04MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; From ac42102320a722fe144bfa57ca6dc4d050dd2e80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:36:01 -1000 Subject: [PATCH 15/32] [core] Auto-replace / in entity names with Unicode fraction slash during deprecation period (#13016) --- esphome/config_validation.py | 21 ++++++++++++--- tests/unit_tests/test_config_validation.py | 31 +++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index b0da88c50d..81a30cb0b7 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1981,16 +1981,31 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( ) +# Unicode FRACTION SLASH (U+2044) - visually similar to '/' but URL-safe +FRACTION_SLASH = "\u2044" + + def _validate_no_slash(value): """Validate that a name does not contain '/' characters. The '/' character is used as a path separator in web server URLs, so it cannot be used in entity or device names. + + During the deprecation period, '/' is automatically replaced with + the visually similar Unicode FRACTION SLASH (U+2044) character. """ if "/" in value: - raise Invalid( - f"Name cannot contain '/' character (used as URL path separator): {value}" + # Remove before 2026.7.0 + new_value = value.replace("/", FRACTION_SLASH) + _LOGGER.warning( + "'%s' contains '/' which is reserved as a URL path separator. " + "Automatically replacing with '%s' (Unicode FRACTION SLASH). " + "Please update your configuration. " + "This will become an error in ESPHome 2026.7.0.", + value, + new_value, ) + return new_value return value @@ -2019,7 +2034,7 @@ def _validate_entity_name(value): f"Maximum length is {NAME_MAX_LENGTH} characters." ) # Validate no '/' in name for web server URL compatibility - _validate_no_slash(value) + value = _validate_no_slash(value) return value diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 94224f2364..9602010ad3 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -510,10 +510,23 @@ def test_string_no_slash__valid(value: str) -> None: assert actual == value -@pytest.mark.parametrize("value", ("has/slash", "a/b/c", "/leading", "trailing/")) -def test_string_no_slash__slash_rejected(value: str) -> None: - with pytest.raises(Invalid, match="cannot contain '/' character"): - config_validation.string_no_slash(value) +@pytest.mark.parametrize( + ("value", "expected"), + ( + ("has/slash", "has⁄slash"), + ("a/b/c", "a⁄b⁄c"), + ("/leading", "⁄leading"), + ("trailing/", "trailing⁄"), + ), +) +def test_string_no_slash__slash_replaced_with_warning( + value: str, expected: str, caplog: pytest.LogCaptureFixture +) -> None: + """Test that '/' is auto-replaced with fraction slash and warning is logged.""" + actual = config_validation.string_no_slash(value) + assert actual == expected + assert "reserved as a URL path separator" in caplog.text + assert "will become an error in ESPHome 2026.7.0" in caplog.text def test_string_no_slash__long_string_allowed() -> None: @@ -532,9 +545,13 @@ def test_validate_entity_name__valid(value: str) -> None: assert actual == value -def test_validate_entity_name__slash_rejected() -> None: - with pytest.raises(Invalid, match="cannot contain '/' character"): - config_validation._validate_entity_name("has/slash") +def test_validate_entity_name__slash_replaced_with_warning( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that '/' in entity names is auto-replaced with fraction slash.""" + actual = config_validation._validate_entity_name("has/slash") + assert actual == "has⁄slash" + assert "reserved as a URL path separator" in caplog.text def test_validate_entity_name__max_length() -> None: From d6c2dd3c26fb26fce9d7dfa495b594e2ba4687c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 08:21:16 -1000 Subject: [PATCH 16/32] [wifi] Eliminate heap allocations in IP address logging (#13017) --- esphome/components/wifi/wifi_component.cpp | 4 +-- .../wifi/wifi_component_esp8266.cpp | 28 ++++++------------- .../wifi/wifi_component_libretiny.cpp | 13 ++------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e1dc2d17d6..2d635d893f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -639,13 +639,13 @@ void WiFiComponent::setup_ap_config_() { } this->ap_setup_ = this->wifi_start_ap_(this->ap_); - auto ip_address = this->wifi_soft_ap_ip().str(); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "Setting up AP:\n" " AP SSID: '%s'\n" " AP Password: '%s'\n" " IP Address: %s", - this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), ip_address.c_str()); + this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), this->wifi_soft_ap_ip().str_to(ip_buf)); #ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 9d99e0b94c..b7d820413c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -371,7 +371,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { while (!connected) { uint8_t ipv6_addr_count = 0; for (auto addr : addrList) { - ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "Address %s", network::IPAddress(addr.ipFromNetifNum()).str_to(ip_buf)); if (addr.isV6()) { ipv6_addr_count++; } @@ -413,21 +414,6 @@ const LogString *get_auth_mode_str(uint8_t mode) { return LOG_STR("UNKNOWN"); } } -#ifdef ipv4_addr -std::string format_ip_addr(struct ipv4_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#else -std::string format_ip_addr(struct ip_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#endif const LogString *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -582,8 +568,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_GOT_IP: { auto it = event->event_info.got_ip; - ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), - format_ip_addr(it.mask).c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE], + mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), + network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); s_sta_got_ip = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->ip_state_listeners_) { @@ -635,8 +623,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.distribute_sta_ip; char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; format_mac_addr_upper(it.mac, mac_buf); - ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, format_ip_addr(it.ip).c_str(), it.aid); + ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, network::IPAddress(&it.ip).str_to(ip_buf), + it.aid); #endif break; } diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index e9ccb86871..68fcc3577d 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -231,14 +231,6 @@ const char *get_auth_mode_str(uint8_t mode) { } } -using esphome_ip4_addr_t = IPAddress; - -std::string format_ip4_addr(const esphome_ip4_addr_t &ip) { - char buf[20]; - uint32_t addr = ip; - sprintf(buf, "%u.%u.%u.%u", uint8_t(addr >> 0), uint8_t(addr >> 8), uint8_t(addr >> 16), uint8_t(addr >> 24)); - return buf; -} const char *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -530,8 +522,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { - ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), - format_ip4_addr(WiFi.gatewayIP()).c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf), + network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf)); s_sta_state = LTWiFiSTAState::CONNECTED; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { From 8eb28a7724798fae4a1b6d0ee5203ee8221fdffb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 08:38:39 -1000 Subject: [PATCH 17/32] [neopixelbus] Fix ESP8266 compilation by enabling Serial/Serial1 for NeoPixelBus library (#13027) --- esphome/components/neopixelbus/light.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index d071059185..c77217243c 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -194,6 +194,14 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): + if CORE.is_esp8266: + # NeoPixelBus library unconditionally includes NeoEsp8266UartMethod.h + # which references Serial and Serial1, so we must enable both + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + enable_serial() + enable_serial1() + has_white = "W" in config[CONF_TYPE] method = config[CONF_METHOD] From 4419bf02b1864976d67fdcc0407f70f80cdbd7fe Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 6 Jan 2026 21:26:27 +0100 Subject: [PATCH 18/32] [async_tcp] Fix build conflicts and use IDF component for ESP32 (#13025) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .clang-tidy.hash | 2 +- esphome/components/async_tcp/__init__.py | 6 +++--- esphome/components/async_tcp/async_tcp.h | 5 ++--- esphome/components/async_tcp/async_tcp_socket.cpp | 5 +++-- esphome/components/async_tcp/async_tcp_socket.h | 6 +++--- esphome/idf_component.yml | 2 ++ platformio.ini | 1 - 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 59caddf59b..0a71b6859f 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -97fb425f1d681a5994ed1cc6187910f5d2c37ee577b6dc07eb3f4d8862a011de +191a0e6ab5842d153dd77a2023bc5742f9d4333c334de8d81b57f2b8d4d4b65e diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 4b6c6a275c..1ff4805f03 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -26,12 +26,12 @@ CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) async def to_code(config): - if CORE.using_esp_idf: - # ESP-IDF needs the IDF component + if CORE.is_esp32: + # https://github.com/ESP32Async/AsyncTCP from esphome.components.esp32 import add_idf_component add_idf_component(name="esp32async/asynctcp", ref="3.4.91") - elif CORE.is_esp32 or CORE.is_libretiny: + elif CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: diff --git a/esphome/components/async_tcp/async_tcp.h b/esphome/components/async_tcp/async_tcp.h index 362f603451..6d9211f023 100644 --- a/esphome/components/async_tcp/async_tcp.h +++ b/esphome/components/async_tcp/async_tcp.h @@ -1,9 +1,8 @@ #pragma once #include "esphome/core/defines.h" -#if (defined(USE_ESP32) || defined(USE_LIBRETINY)) && !defined(CLANG_TIDY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) // Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny -// But not for clang-tidy as the header file isn't present in that case #include #elif defined(USE_ESP8266) // Use ESPAsyncTCP library for ESP8266 (always Arduino) @@ -12,6 +11,6 @@ // Use AsyncTCP_RP2040W library for RP2040 #include #else -// Use socket-based implementation for other platforms and clang-tidy +// Use socket-based implementation for other platforms #include "async_tcp_socket.h" #endif diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp index 6c13f346e9..f64e494f5f 100644 --- a/esphome/components/async_tcp/async_tcp_socket.cpp +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -1,6 +1,7 @@ #include "async_tcp_socket.h" -#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) #include "esphome/components/network/util.h" #include "esphome/core/log.h" @@ -158,4 +159,4 @@ void AsyncClient::loop() { } // namespace esphome::async_tcp -#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.h b/esphome/components/async_tcp/async_tcp_socket.h index ca3bf19d67..28714a7752 100644 --- a/esphome/components/async_tcp/async_tcp_socket.h +++ b/esphome/components/async_tcp/async_tcp_socket.h @@ -2,7 +2,8 @@ #include "esphome/core/defines.h" -#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) #include "esphome/components/socket/socket.h" #include @@ -69,5 +70,4 @@ class AsyncClient { // Expose AsyncClient in global namespace to match library behavior using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers) -#define ESPHOME_ASYNC_TCP_SOCKET_IMPL -#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#endif diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 36aa77c524..2dc5b94847 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -31,3 +31,5 @@ dependencies: version: 0.2.2 rules: - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" + esp32async/asynctcp: + version: 3.4.91 diff --git a/platformio.ini b/platformio.ini index dd9eb566c5..d96e9ad2cc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,7 +146,6 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - ESP32Async/AsyncTCP@3.4.5 ; async_tcp NetworkClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) From 412ab5dbbf681f47f99cad7a69f9b8854dff34cb Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Tue, 6 Jan 2026 13:31:50 -0800 Subject: [PATCH 19/32] [aqi] Implement a sensor that computes AQI (#12958) Co-authored-by: jas --- esphome/components/aqi/aqi_calculator.h | 27 ++++++----- esphome/components/aqi/aqi_sensor.cpp | 52 ++++++++++++++++++++++ esphome/components/aqi/aqi_sensor.h | 31 +++++++++++++ esphome/components/aqi/caqi_calculator.h | 25 +++++------ esphome/components/aqi/sensor.py | 51 +++++++++++++++++++++ esphome/components/hm3301/sensor.py | 9 ++++ esphome/components/pmsx003/pmsx003.cpp | 7 --- esphome/components/pmsx003/pmsx003.h | 11 ----- esphome/components/pmsx003/sensor.py | 26 ----------- tests/components/aqi/common.yaml | 22 +++++++++ tests/components/aqi/test.esp32-idf.yaml | 1 + tests/components/aqi/test.esp8266-ard.yaml | 1 + tests/components/aqi/test.rp2040-ard.yaml | 1 + tests/components/pmsx003/common.yaml | 3 -- 14 files changed, 193 insertions(+), 74 deletions(-) create mode 100644 esphome/components/aqi/aqi_sensor.cpp create mode 100644 esphome/components/aqi/aqi_sensor.h create mode 100644 esphome/components/aqi/sensor.py create mode 100644 tests/components/aqi/common.yaml create mode 100644 tests/components/aqi/test.esp32-idf.yaml create mode 100644 tests/components/aqi/test.esp8266-ard.yaml create mode 100644 tests/components/aqi/test.rp2040-ard.yaml diff --git a/esphome/components/aqi/aqi_calculator.h b/esphome/components/aqi/aqi_calculator.h index 959d6a2438..35dc35a44a 100644 --- a/esphome/components/aqi/aqi_calculator.h +++ b/esphome/components/aqi/aqi_calculator.h @@ -10,38 +10,37 @@ namespace esphome::aqi { class AQICalculator : public AbstractAQICalculator { public: uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); + int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; } protected: - static const int AMOUNT_OF_LEVELS = 6; + static constexpr int NUM_LEVELS = 6; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, - {56, 125}, {126, 225}, {226, INT_MAX}}; + static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}}; - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, - {255, 354}, {355, 424}, {425, INT_MAX}}; + static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, + {255, 354}, {355, 424}, {425, INT_MAX}}; - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); + static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); if (grid_index == -1) { return -1; } - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; + int aqi_lo = INDEX_GRID[grid_index][0]; + int aqi_hi = INDEX_GRID[grid_index][1]; int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { + static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i; } diff --git a/esphome/components/aqi/aqi_sensor.cpp b/esphome/components/aqi/aqi_sensor.cpp new file mode 100644 index 0000000000..cdc9f35ba6 --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.cpp @@ -0,0 +1,52 @@ +#include "aqi_sensor.h" +#include "esphome/core/log.h" + +namespace esphome::aqi { + +static const char *const TAG = "aqi"; + +void AQISensor::setup() { + if (this->pm_2_5_sensor_ != nullptr) { + this->pm_2_5_sensor_->add_on_state_callback([this](float value) { + this->pm_2_5_value_ = value; + // Defer calculation to avoid double-publishing if both sensors update in the same loop + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } + if (this->pm_10_0_sensor_ != nullptr) { + this->pm_10_0_sensor_->add_on_state_callback([this](float value) { + this->pm_10_0_value_ = value; + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } +} + +void AQISensor::dump_config() { + ESP_LOGCONFIG(TAG, "AQI Sensor:"); + ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI"); + if (this->pm_2_5_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str()); + } + if (this->pm_10_0_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str()); + } + LOG_SENSOR(" ", "AQI", this); +} + +void AQISensor::calculate_aqi_() { + if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) { + return; + } + + AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + if (calculator == nullptr) { + ESP_LOGW(TAG, "Unknown AQI calculator type"); + return; + } + + uint16_t aqi = + calculator->get_aqi(static_cast(this->pm_2_5_value_), static_cast(this->pm_10_0_value_)); + this->publish_state(aqi); +} + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_sensor.h b/esphome/components/aqi/aqi_sensor.h new file mode 100644 index 0000000000..a990f815fe --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "aqi_calculator_factory.h" + +namespace esphome::aqi { + +class AQISensor : public sensor::Sensor, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; } + void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; } + void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; } + + protected: + void calculate_aqi_(); + + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + AQICalculatorType aqi_calc_type_{AQI_TYPE}; + AQICalculatorFactory aqi_calculator_factory_; + + float pm_2_5_value_{NAN}; + float pm_10_0_value_{NAN}; +}; + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/caqi_calculator.h b/esphome/components/aqi/caqi_calculator.h index d493dcdf39..9906c179f6 100644 --- a/esphome/components/aqi/caqi_calculator.h +++ b/esphome/components/aqi/caqi_calculator.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/log.h" #include "abstract_aqi_calculator.h" namespace esphome::aqi { @@ -8,37 +7,37 @@ namespace esphome::aqi { class CAQICalculator : public AbstractAQICalculator { public: uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); + int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; } protected: - static const int AMOUNT_OF_LEVELS = 5; + static constexpr int NUM_LEVELS = 5; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; + static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; + static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); + static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); if (grid_index == -1) { return -1; } - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; + int aqi_lo = INDEX_GRID[grid_index][0]; + int aqi_hi = INDEX_GRID[grid_index][1]; int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { + static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i; } diff --git a/esphome/components/aqi/sensor.py b/esphome/components/aqi/sensor.py new file mode 100644 index 0000000000..0b5ee8d75a --- /dev/null +++ b/esphome/components/aqi/sensor.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_PM_2_5, + CONF_PM_10_0, + DEVICE_CLASS_AQI, + STATE_CLASS_MEASUREMENT, +) + +from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["sensor"] + +UNIT_INDEX = "index" + +AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + AQISensor, + unit_of_measurement=UNIT_INDEX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor), + cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor), + cv.Required(CONF_CALCULATION_TYPE): cv.enum( + AQI_CALCULATION_TYPE, upper=True + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor)) + + pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0]) + cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor)) + + cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE])) diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 389da97b1e..9546ae1c3c 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -1,3 +1,5 @@ +import logging + import esphome.codegen as cg from esphome.components import i2c, sensor from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE @@ -16,6 +18,8 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, ) +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ["i2c"] AUTO_LOAD = ["aqi"] CODEOWNERS = ["@freekode"] @@ -99,7 +103,12 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_PM_10_0]) cg.add(var.set_pm_10_0_sensor(sens)) + # Remove before 2026.12.0 if CONF_AQI in config: + _LOGGER.warning( + "The 'aqi' option in hm3301 is deprecated, " + "please use the standalone 'aqi' sensor platform instead." + ) sens = await sensor.new_sensor(config[CONF_AQI]) cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 3bdb5219ed..bb167033d1 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -265,13 +265,6 @@ void PMSX003Component::parse_data_() { if (this->pm_particles_25um_sensor_ != nullptr) this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); - // Calculate and publish AQI if sensor is configured - if (this->aqi_sensor_ != nullptr) { - aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); - int32_t aqi_value = calculator->get_aqi(pm_2_5_concentration, pm_10_0_concentration); - this->aqi_sensor_->publish_state(aqi_value); - } - if (this->type_ == PMSX003_TYPE_5003T) { ESP_LOGD(TAG, "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, " diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index 229972e2e5..f48121800e 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -4,7 +4,6 @@ #include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/aqi/aqi_calculator_factory.h" namespace esphome { namespace pmsx003 { @@ -74,10 +73,6 @@ class PMSX003Component : public uart::UARTDevice, public Component { void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } - void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } - - void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } - protected: optional check_byte_(); void parse_data_(); @@ -121,12 +116,6 @@ class PMSX003Component : public uart::UARTDevice, public Component { // Temperature and Humidity sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; - - // AQI - sensor::Sensor *aqi_sensor_{nullptr}; - - aqi::AQICalculatorType aqi_calc_type_; - aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory(); }; } // namespace pmsx003 diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index b2d6744547..bebd3a01ee 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -1,6 +1,5 @@ import esphome.codegen as cg from esphome.components import sensor, uart -from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE import esphome.config_validation as cv from esphome.const import ( CONF_FORMALDEHYDE, @@ -21,7 +20,6 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_TYPE, CONF_UPDATE_INTERVAL, - DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, @@ -37,13 +35,11 @@ from esphome.const import ( CODEOWNERS = ["@ximex"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["aqi"] pmsx003_ns = cg.esphome_ns.namespace("pmsx003") PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) -UNIT_INDEX = "index" TYPE_PMSX003 = "PMSX003" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" @@ -81,10 +77,6 @@ def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!") - if CONF_AQI in value and CONF_PM_2_5 not in value: - raise cv.Invalid("AQI computation requires PM 2.5 sensor") - if CONF_AQI in value and CONF_PM_10_0 not in value: - raise cv.Invalid("AQI computation requires PM 10 sensor") return value @@ -200,19 +192,6 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_AQI): sensor.sensor_schema( - unit_of_measurement=UNIT_INDEX, - icon=ICON_CHEMICAL_WEAPON, - accuracy_decimals=0, - device_class=DEVICE_CLASS_AQI, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Required(CONF_CALCULATION_TYPE): cv.enum( - AQI_CALCULATION_TYPE, upper=True - ), - } - ), cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval, } ) @@ -299,9 +278,4 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity_sensor(sens)) - if CONF_AQI in config: - sens = await sensor.new_sensor(config[CONF_AQI]) - cg.add(var.set_aqi_sensor(sens)) - cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) - cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) diff --git a/tests/components/aqi/common.yaml b/tests/components/aqi/common.yaml new file mode 100644 index 0000000000..4c8cbbfa3f --- /dev/null +++ b/tests/components/aqi/common.yaml @@ -0,0 +1,22 @@ +sensor: + - platform: template + id: pm25_sensor + name: "PM2.5" + lambda: "return 25.0;" + + - platform: template + id: pm10_sensor + name: "PM10" + lambda: "return 50.0;" + + - platform: aqi + name: "Air Quality Index (AQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: AQI + + - platform: aqi + name: "Air Quality Index (CAQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: CAQI diff --git a/tests/components/aqi/test.esp32-idf.yaml b/tests/components/aqi/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.esp8266-ard.yaml b/tests/components/aqi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.rp2040-ard.yaml b/tests/components/aqi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pmsx003/common.yaml b/tests/components/pmsx003/common.yaml index 9dd79723d1..3c60995804 100644 --- a/tests/components/pmsx003/common.yaml +++ b/tests/components/pmsx003/common.yaml @@ -25,7 +25,4 @@ sensor: name: Particulate Count >5.0um pm_10_0um: name: Particulate Count >10.0um - aqi: - name: AQI - calculation_type: AQI update_interval: 30s From 2147ddf8c7f25fac1a941b1980afdd69ac9349bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 11:32:23 -1000 Subject: [PATCH 20/32] [api] Eliminate std::string from ClientInfo struct (#12566) Co-authored-by: Keith Burzinski --- esphome/components/api/api_connection.cpp | 42 +++++----- esphome/components/api/api_connection.h | 19 ++--- esphome/components/api/api_frame_helper.cpp | 10 ++- esphome/components/api/api_frame_helper.h | 28 ++++--- .../components/api/api_frame_helper_noise.cpp | 7 +- .../components/api/api_frame_helper_noise.h | 4 +- .../api/api_frame_helper_plaintext.cpp | 8 +- .../api/api_frame_helper_plaintext.h | 3 +- esphome/components/api/api_server.cpp | 14 ++-- .../components/esphome/ota/ota_esphome.cpp | 5 +- .../components/socket/bsd_sockets_impl.cpp | 40 +-------- .../components/socket/lwip_raw_tcp_impl.cpp | 57 ++++--------- .../components/socket/lwip_sockets_impl.cpp | 38 +-------- esphome/components/socket/socket.cpp | 81 +++++++++++++++++++ esphome/components/socket/socket.h | 20 ++++- .../voice_assistant/voice_assistant.cpp | 5 +- 16 files changed, 200 insertions(+), 181 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 27344a53ec..30f7b5710c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -101,16 +101,14 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) auto &noise_ctx = parent->get_noise_ctx(); if (noise_ctx.has_psk()) { - this->helper_ = - std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx)}; } else { - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; } #elif defined(USE_API_PLAINTEXT) - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; #elif defined(USE_API_NOISE) - this->helper_ = std::unique_ptr{ - new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; #else #error "No frame helper defined" #endif @@ -131,8 +129,9 @@ void APIConnection::start() { this->fatal_error_with_log_(LOG_STR("Helper init failed"), err); return; } - this->client_info_.peername = helper_->getpeername(); - this->client_info_.name = this->client_info_.peername; + // Initialize client name with peername (IP address) until Hello message provides actual name + const char *peername = this->helper_->get_client_peername(); + this->helper_->set_client_name(peername, strlen(peername)); } APIConnection::~APIConnection() { @@ -252,8 +251,7 @@ void APIConnection::loop() { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { on_fatal_error(); - ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(), - this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting")); } } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { // Only send ping if we're not disconnecting @@ -287,7 +285,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop - ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected")); this->flags_.next_close = true; DisconnectResponse resp; return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); @@ -1504,9 +1502,10 @@ void APIConnection::complete_authentication_() { } this->flags_.connection_state = static_cast(ConnectionState::AUTHENTICATED); - ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected")); #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); + this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()), + std::string(this->helper_->get_client_peername())); #endif #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { @@ -1521,12 +1520,12 @@ void APIConnection::complete_authentication_() { } bool APIConnection::send_hello_response(const HelloRequest &msg) { - this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size()); - this->client_info_.peername = this->helper_->getpeername(); + // Copy client name with truncation if needed (set_client_name handles truncation) + this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size()); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; - ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(), - this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_); + ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(), + this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; @@ -1836,7 +1835,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup")); } void APIConnection::on_fatal_error() { this->helper_->close(); @@ -2084,8 +2083,13 @@ void APIConnection::process_state_subscriptions_() { } #endif // USE_API_HOMEASSISTANT_STATES +void APIConnection::log_client_(int level, const LogString *message) { + esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(), + this->helper_->get_client_peername(), LOG_STR_ARG(message)); +} + void APIConnection::log_warning_(const LogString *message, APIError err) { - ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(), + ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(), LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index cffd52bfdb..802681f32f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -9,18 +9,13 @@ #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/string_ref.h" #include #include namespace esphome::api { -// Client information structure -struct ClientInfo { - std::string name; // Client name from Hello message - std::string peername; // IP:port from socket -}; - // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending @@ -279,8 +274,9 @@ class APIConnection final : public APIServerConnection { bool try_to_clear_buffer(bool log_out_of_space); bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; - const std::string &get_name() const { return this->client_info_.name; } - const std::string &get_peername() const { return this->client_info_.peername; } + const char *get_name() const { return this->helper_->get_client_name(); } + /// Get peer name (IP address) - cached at connection init time + const char *get_peername() const { return this->helper_->get_client_peername(); } protected: // Helper function to handle authentication completion @@ -526,10 +522,7 @@ class APIConnection final : public APIServerConnection { std::unique_ptr image_reader_; #endif - // Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each) - ClientInfo client_info_; - - // Group 4: 4-byte types + // Group 3: 4-byte types uint32_t last_traffic_; #ifdef USE_API_HOMEASSISTANT_STATES int state_subs_at_ = -1; @@ -756,6 +749,8 @@ class APIConnection final : public APIServerConnection { return this->schedule_batch_(); } + // Helper function to log client messages with name and peername + void log_client_(int level, const LogString *message); // Helper function to log API errors with errno void log_warning_(const LogString *message, APIError err); // Helper to handle fatal errors with logging diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 420f42a90a..dd44fe9e17 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -1,6 +1,5 @@ #include "api_frame_helper.h" #ifdef USE_API -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -16,8 +15,11 @@ static const char *const TAG = "api.frame_helper"; // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ @@ -243,6 +245,8 @@ APIError APIFrameHelper::init_common_() { HELPER_LOG("Bad state for init %d", (int) state_); return APIError::BAD_STATE; } + // Cache peername now while socket is valid - needed for error logging after socket failure + this->socket_->getpeername_to(this->client_peername_); int err = this->socket_->setblocking(false); if (err != 0) { state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 383e763e6d..76a93d094e 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -33,11 +33,11 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and oth // Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; -// Forward declaration -struct ClientInfo; - class ProtoWriteBuffer; +// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars) +static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32; + struct ReadPacketBuffer { const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call) uint16_t data_len; @@ -86,14 +86,23 @@ const LogString *api_error_to_logstr(APIError err); class APIFrameHelper { public: APIFrameHelper() = default; - explicit APIFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : socket_(std::move(socket)), client_info_(client_info) {} + explicit APIFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} + + // Get client name (null-terminated) + const char *get_client_name() const { return this->client_name_; } + // Get client peername/IP (null-terminated, cached at init time for availability after socket failure) + const char *get_client_peername() const { return this->client_peername_; } + // Set client name from buffer with length (truncates if needed) + void set_client_name(const char *name, size_t len) { + size_t copy_len = std::min(len, sizeof(this->client_name_) - 1); + memcpy(this->client_name_, name, copy_len); + this->client_name_[copy_len] = '\0'; + } virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop(); virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } - std::string getpeername() { return socket_->getpeername(); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { state_ = State::CLOSED; @@ -186,9 +195,10 @@ class APIFrameHelper { std::array, API_MAX_SEND_QUEUE> tx_buf_; std::vector rx_buf_; - // Pointer to client info (4 bytes on 32-bit) - // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance. - const ClientInfo *client_info_{nullptr}; + // Client name buffer - stores name from Hello message or initial peername + char client_name_[CLIENT_INFO_NAME_MAX_LEN]{}; + // Cached peername/IP address - captured at init time for availability after socket failure + char client_peername_[socket::SOCKADDR_STR_LEN]{}; // Group smaller types together uint16_t rx_buf_len_ = 0; diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index be8d93fbf9..21b0463dfe 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -27,8 +27,11 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 1268086194..183b8c8a51 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -9,8 +9,8 @@ namespace esphome::api { class APINoiseFrameHelper final : public APIFrameHelper { public: - APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info), ctx_(ctx) { + APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) + : APIFrameHelper(std::move(socket)), ctx_(ctx) { // Noise header structure: // Pos 0: indicator (0x01) // Pos 1-2: encrypted payload size (16-bit big-endian) diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index a974a2458e..3dfd683929 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -1,7 +1,6 @@ #include "api_frame_helper_plaintext.h" #ifdef USE_API #ifdef USE_API_PLAINTEXT -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -21,8 +20,11 @@ static const char *const TAG = "api.plaintext"; // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index 7af9fc64b9..96d47e9c7b 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -7,8 +7,7 @@ namespace esphome::api { class APIPlaintextFrameHelper final : public APIFrameHelper { public: - APIPlaintextFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info) { + explicit APIPlaintextFrameHelper(std::unique_ptr socket) : APIFrameHelper(std::move(socket)) { // Plaintext header structure (worst case): // Pos 0: indicator (0x00) // Pos 1-3: payload size varint (up to 3 bytes) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index a7b046447d..4ececfec94 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -125,15 +125,18 @@ void APIServer::loop() { if (!sock) break; + char peername[socket::SOCKADDR_STR_LEN]; + sock->getpeername_to(peername); + // Check if we're at the connection limit if (this->clients_.size() >= this->max_connections_) { - ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str()); + ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername); // Immediately close - socket destructor will handle cleanup sock.reset(); continue; } - ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); + ESP_LOGD(TAG, "Accept %s", peername); auto *conn = new APIConnection(std::move(sock), this); this->clients_.emplace_back(conn); @@ -166,8 +169,7 @@ void APIServer::loop() { // Network is down - disconnect all clients for (auto &client : this->clients_) { client->on_fatal_error(); - ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(), - client->client_info_.peername.c_str()); + client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect")); } // Continue to process and clean up the clients below } @@ -185,12 +187,12 @@ void APIServer::loop() { // Rare case: handle disconnection #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername); + this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername())); #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES this->unregister_active_action_calls_for_connection(client.get()); #endif - ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str()); + ESP_LOGV(TAG, "Remove connection %s", client->get_name()); // Swap with the last element and pop (avoids expensive vector shifts) if (client_index < this->clients_.size() - 1) { diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index f71163f79e..dfa637f701 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -4,6 +4,7 @@ #include "esphome/components/sha256/sha256.h" #endif #include "esphome/components/network/util.h" +#include "esphome/components/socket/socket.h" #include "esphome/components/ota/ota_backend.h" #include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" @@ -443,7 +444,9 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) { void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); } void ESPHomeOTAComponent::log_start_(const LogString *phase) { - ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str()); + char peername[socket::SOCKADDR_STR_LEN]; + this->client_->getpeername_to(peername); + ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername); } void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 09cd81752a..73be025376 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -14,31 +14,7 @@ namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - // Format IPv4-mapped IPv6 addresses as regular IPv4 addresses - if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && - addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && - inet_ntop(AF_INET, &addr->sin6_addr.un.u32_addr[3], buf, sizeof(buf)) != nullptr) { - return std::string{buf}; - } - if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#endif - return {}; -} - -class BSDSocketImpl : public Socket { +class BSDSocketImpl final : public Socket { public: BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT @@ -93,23 +69,9 @@ class BSDSocketImpl : public Socket { int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(this->fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (::getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(this->fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (::getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { return ::getsockopt(this->fd_, level, optname, optval, optlen); } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index cb5d17d5af..429f59ceca 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -71,7 +71,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return nullptr; } - int bind(const struct sockaddr *name, socklen_t addrlen) override { + int bind(const struct sockaddr *name, socklen_t addrlen) final { if (pcb_ == nullptr) { errno = EBADF; return -1; @@ -135,7 +135,7 @@ class LWIPRawImpl : public Socket { } return 0; } - int close() override { + int close() final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -152,7 +152,7 @@ class LWIPRawImpl : public Socket { pcb_ = nullptr; return 0; } - int shutdown(int how) override { + int shutdown(int how) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -178,7 +178,7 @@ class LWIPRawImpl : public Socket { return 0; } - int getpeername(struct sockaddr *name, socklen_t *addrlen) override { + int getpeername(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -189,14 +189,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); } - std::string getpeername() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->remote_ip); - } - int getsockname(struct sockaddr *name, socklen_t *addrlen) override { + int getsockname(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -207,14 +200,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } - std::string getsockname() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->local_ip); - } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -248,7 +234,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -282,7 +268,7 @@ class LWIPRawImpl : public Socket { errno = EOPNOTSUPP; return -1; } - ssize_t read(void *buf, size_t len) override { + ssize_t read(void *buf, size_t len) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -340,7 +326,7 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t readv(const struct iovec *iov, int iovcnt) final { ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -358,7 +344,7 @@ class LWIPRawImpl : public Socket { return ret; } - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) final { errno = ENOTSUP; return -1; } @@ -412,7 +398,7 @@ class LWIPRawImpl : public Socket { } return 0; } - ssize_t write(const void *buf, size_t len) override { + ssize_t write(const void *buf, size_t len) final { ssize_t written = internal_write(buf, len); if (written == -1) return -1; @@ -427,7 +413,7 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t writev(const struct iovec *iov, int iovcnt) final { ssize_t written = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -453,12 +439,12 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) final { // return ::sendto(fd_, buf, len, flags, to, tolen); errno = ENOSYS; return -1; } - int setblocking(bool blocking) override { + int setblocking(bool blocking) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -517,19 +503,6 @@ class LWIPRawImpl : public Socket { } protected: - std::string format_ip_address_(const ip_addr_t &ip) { - char buffer[50] = {}; - if (IP_IS_V4_VAL(ip)) { - inet_ntoa_r(ip, buffer, sizeof(buffer)); - } -#if LWIP_IPV6 - else if (IP_IS_V6_VAL(ip)) { - inet6_ntoa_r(ip, buffer, sizeof(buffer)); - } -#endif - return std::string(buffer); - } - int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { if (family_ == AF_INET) { if (*addrlen < sizeof(struct sockaddr_in)) { @@ -584,7 +557,7 @@ class LWIPRawImpl : public Socket { // Listening socket class - only allocates accept queue when needed (for bind+listen sockets) // This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040 -class LWIPRawListenImpl : public LWIPRawImpl { +class LWIPRawListenImpl final : public LWIPRawImpl { public: LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {} diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index 23fb1a7f6f..a885f243f3 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -9,29 +9,7 @@ namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#endif - return {}; -} - -class LwIPSocketImpl : public Socket { +class LwIPSocketImpl final : public Socket { public: LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT @@ -88,23 +66,9 @@ class LwIPSocketImpl : public Socket { int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(this->fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (lwip_getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(this->fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (lwip_getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { return lwip_getsockopt(this->fd_, level, optname, optval, optlen); } diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index ffe0233abc..c92e33393b 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -10,6 +10,87 @@ namespace esphome::socket { Socket::~Socket() {} +// Platform-specific inet_ntop wrappers +#if defined(USE_SOCKET_IMPL_LWIP_TCP) +// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + inet_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + inet6_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} +#endif +#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +// LWIP sockets (LibreTiny, ESP32 Arduino) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#else +// BSD sockets (host, ESP32-IDF) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#endif + +// Format sockaddr into caller-provided buffer, returns length written (excluding null) +static size_t format_sockaddr_to(const struct sockaddr_storage &storage, std::span buf) { + if (storage.ss_family == AF_INET) { + const auto *addr = reinterpret_cast(&storage); + if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#if USE_NETWORK_IPV6 + else if (storage.ss_family == AF_INET6) { + const auto *addr = reinterpret_cast(&storage); +#ifndef USE_SOCKET_IMPL_LWIP_TCP + // Format IPv4-mapped IPv6 addresses as regular IPv4 (not supported on ESP8266 raw TCP) + if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && + addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && + esphome_inet_ntop4(&addr->sin6_addr.un.u32_addr[3], buf.data(), buf.size()) != nullptr) { + return strlen(buf.data()); + } +#endif + if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#endif + buf[0] = '\0'; + return 0; +} + +size_t Socket::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); +} + +size_t Socket::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); +} + std::unique_ptr socket_ip(int type, int protocol) { #if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 75eb07de4a..9f9f61de85 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "esphome/core/optional.h" @@ -8,6 +9,15 @@ #if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) namespace esphome::socket { +// Maximum length for formatted socket address string (IP address without port) +// IPv4: "255.255.255.255" = 15 chars + null = 16 +// IPv6: full address = 45 chars + null = 46 +#if USE_NETWORK_IPV6 +static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN +#else +static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN +#endif + class Socket { public: Socket() = default; @@ -31,9 +41,15 @@ class Socket { virtual int shutdown(int how) = 0; virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getpeername() = 0; virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getsockname() = 0; + + /// Format peer address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getpeername() - can be optimized away if unused + /// Returns number of characters written (excluding null terminator), or 0 on error + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getsockname() - can be optimized away if unused + size_t getsockname_to(std::span buf); virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 05c356ae4c..0e0616c508 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -3,6 +3,7 @@ #ifdef USE_VOICE_ASSISTANT +#include "esphome/components/socket/socket.h" #include "esphome/core/log.h" #include @@ -433,8 +434,8 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr "Multiple API Clients attempting to connect to Voice Assistant\n" "Current client: %s (%s)\n" "New client: %s (%s)", - this->api_client_->get_name().c_str(), this->api_client_->get_peername().c_str(), - client->get_name().c_str(), client->get_peername().c_str()); + this->api_client_->get_name(), this->api_client_->get_peername(), client->get_name(), + client->get_peername()); return; } From a19597626b714d2c92a94cbb7437ab7748e42e59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 16:16:37 -1000 Subject: [PATCH 21/32] [text_sensor][text] Add const char* overloads to publish_state to eliminate heap churn (#13030) --- esphome/components/text/text.cpp | 18 +++++--- esphome/components/text/text.h | 2 + .../components/text_sensor/text_sensor.cpp | 46 +++++++++++++------ esphome/components/text_sensor/text_sensor.h | 5 ++ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index d06c350832..3824c5004d 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -2,22 +2,26 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text { static const char *const TAG = "text"; -void Text::publish_state(const std::string &state) { - this->set_has_state(true); - this->state = state; - if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { - ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str()); +void Text::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } +void Text::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void Text::publish_state(const char *state, size_t len) { + this->set_has_state(true); + this->state.assign(state, len); + if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { + ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); } else { - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), this->state.c_str()); } - this->state_callback_.call(state); + this->state_callback_.call(this->state); #if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_update(this); #endif diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index b8881c59e6..e4ad64334b 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -27,6 +27,8 @@ class Text : public EntityBase { TextTraits traits; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 8dfb9dad05..174a98054f 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text_sensor { @@ -24,20 +25,26 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } } -void TextSensor::publish_state(const std::string &state) { -// Suppress deprecation warning - we need to populate raw_state for backwards compatibility +void TextSensor::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } + +void TextSensor::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void TextSensor::publish_state(const char *state, size_t len) { + if (this->filter_list_ == nullptr) { + // No filters: raw_state == state, store once and use for both callbacks + this->state.assign(state, len); + this->raw_callback_.call(this->state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); + this->notify_frontend_(); + } else { + // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state = state; + this->raw_state.assign(state, len); + this->raw_callback_.call(this->raw_state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); + this->filter_list_->input(this->raw_state); #pragma GCC diagnostic pop - this->raw_callback_.call(state); - - ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); - - if (this->filter_list_ == nullptr) { - this->internal_send_state_to_frontend(state); - } else { - this->filter_list_->input(state); } } @@ -80,6 +87,9 @@ void TextSensor::add_on_raw_state_callback(std::functionstate; } const std::string &TextSensor::get_raw_state() const { + if (this->filter_list_ == nullptr) { + return this->state; // No filters, raw == filtered + } // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -87,10 +97,18 @@ const std::string &TextSensor::get_raw_state() const { #pragma GCC diagnostic pop } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = state; + this->internal_send_state_to_frontend(state.data(), state.size()); +} + +void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { + this->state.assign(state, len); + this->notify_frontend_(); +} + +void TextSensor::notify_frontend_() { this->set_has_state(true); - ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); - this->callback_.call(state); + ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), this->state.c_str()); + this->callback_.call(this->state); #if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_sensor_update(this); #endif diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 2cd8a65e87..1352a8c1e4 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -42,6 +42,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { const std::string &get_raw_state() const; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -63,8 +65,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { // (In most use cases you won't need these) void internal_send_state_to_frontend(const std::string &state); + void internal_send_state_to_frontend(const char *state, size_t len); protected: + /// Notify frontend that state has changed (assumes this->state is already set) + void notify_frontend_(); LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. LazyCallbackManager callback_; ///< Storage for filtered state callbacks. From b052c9f562cb21fa5996235d814ed6b103b2135e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:22:10 -1000 Subject: [PATCH 22/32] [esp32_camera][uart] Add missing wake_loop_threadsafe() preprocessor guards (#13043) --- esphome/components/esp32_camera/esp32_camera.cpp | 2 ++ esphome/components/uart/uart_component_esp_idf.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 06ba7ff16f..5466d2e7ef 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -429,9 +429,11 @@ void ESP32Camera::framebuffer_task(void *pv) { camera_fb_t *framebuffer = esp_camera_fb_get(); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); // Only wake the main loop if there's a pending request to consume the frame +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) if (that->has_requested_image_()) { App.wake_loop_threadsafe(); } +#endif // return is no-op for config with 1 fb xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); esp_camera_fb_return(framebuffer); diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index b4f6eedf91..90997787aa 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -398,14 +398,18 @@ void IDFUARTComponent::rx_event_task_func(void *param) { case UART_DATA: // Data available in UART RX buffer - wake the main loop ESP_LOGVV(TAG, "Data event: %d bytes", event.size); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; case UART_FIFO_OVF: case UART_BUFFER_FULL: ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); uart_flush_input(self->uart_num_); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; default: From 68b4bc9d9ed321449b93a425f4525964fc0650c0 Mon Sep 17 00:00:00 2001 From: Kyrill <1942093+poolski@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:28:41 +0000 Subject: [PATCH 23/32] Map `HEAT_COOL` to `MODE_AUTO` in HeatpumpIR component (#12058) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/heatpumpir/heatpumpir.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index f4d2ca6c1d..67447a3123 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -181,6 +181,11 @@ void HeatpumpIRClimate::transmit_state() { power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_HEAT; break; + // Map HEAT_COOL to hardware AUTO mode (automatic heat/cool changeover based on temperature). + // In hardware AUTO mode, the device automatically switches between heating and cooling + // based on the current temperature versus the target temperature. + // See https://github.com/esphome/esphome/issues/11161 for further discussion. + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_AUTO: power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_AUTO; From 4391457a9650718cfbfa697ce764bd8132a3f103 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:51:26 -1000 Subject: [PATCH 24/32] [sml] Eliminate heap allocations in text sensor (#13039) --- .../sml/text_sensor/sml_text_sensor.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.cpp b/esphome/components/sml/text_sensor/sml_text_sensor.cpp index 64f10698f0..6ceff26fe5 100644 --- a/esphome/components/sml/text_sensor/sml_text_sensor.cpp +++ b/esphome/components/sml/text_sensor/sml_text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "sml_text_sensor.h" #include "../sml_parser.h" +#include namespace esphome { namespace sml { @@ -21,22 +22,33 @@ void SmlTextSensor::publish_val(const ObisInfo &obis_info) { switch (value_type) { case SML_HEX: { - publish_state("0x" + bytes_repr(obis_info.value)); + // Buffer for "0x" + up to 32 bytes as hex + null + char buf[67]; + buf[0] = '0'; + buf[1] = 'x'; + // Max 32 bytes of data fit in remaining buffer ((65-1)/2) + size_t hex_bytes = std::min(obis_info.value.size(), size_t(32)); + format_hex_to(buf + 2, sizeof(buf) - 2, obis_info.value.begin(), hex_bytes); + publish_state(buf, 2 + hex_bytes * 2); break; } case SML_INT: { - publish_state(to_string(bytes_to_int(obis_info.value))); + char buf[21]; // Enough for int64_t (-9223372036854775808) + int len = snprintf(buf, sizeof(buf), "%" PRId64, bytes_to_int(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_BOOL: publish_state(bytes_to_uint(obis_info.value) ? "True" : "False"); break; case SML_UINT: { - publish_state(to_string(bytes_to_uint(obis_info.value))); + char buf[21]; // Enough for uint64_t (18446744073709551615) + int len = snprintf(buf, sizeof(buf), "%" PRIu64, bytes_to_uint(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_OCTET: { - publish_state(std::string(obis_info.value.begin(), obis_info.value.end())); + publish_state(reinterpret_cast(obis_info.value.begin()), obis_info.value.size()); break; } } From 3a84e4a0b4f44ea15b325ff3ca79bcf1e74436be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:00 -1000 Subject: [PATCH 25/32] [openthread_info] Eliminate heap allocations in text sensors (#13036) --- .../openthread_info_text_sensor.h | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.h b/esphome/components/openthread_info/openthread_info_text_sensor.h index 35e46212cb..ac5623e0c1 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.h +++ b/esphome/components/openthread_info/openthread_info_text_sensor.h @@ -33,13 +33,12 @@ class IPAddressOpenThreadInfo : public PollingComponent, public text_sensor::Tex return; } - char address_as_string[40]; - otIp6AddressToString(&*address, address_as_string, 40); - std::string ip = address_as_string; + char buf[OT_IP6_ADDRESS_STRING_SIZE]; + otIp6AddressToString(&*address, buf, sizeof(buf)); - if (this->last_ip_ != ip) { - this->last_ip_ = ip; - this->publish_state(this->last_ip_); + if (this->last_ip_ != buf) { + this->last_ip_ = buf; + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -89,7 +88,9 @@ class ExtAddrOpenThreadInfo : public OpenThreadInstancePollingComponent, public const auto *extaddr = otLinkGetExtendedAddress(instance); if (!std::equal(this->last_extaddr_.begin(), this->last_extaddr_.end(), extaddr->m8)) { std::copy(extaddr->m8, extaddr->m8 + 8, this->last_extaddr_.begin()); - this->publish_state(format_hex(extaddr->m8, 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, extaddr->m8, 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -107,7 +108,9 @@ class Eui64OpenThreadInfo : public OpenThreadInstancePollingComponent, public te if (!std::equal(this->last_eui64_.begin(), this->last_eui64_.end(), addr.m8)) { std::copy(addr.m8, addr.m8 + 8, this->last_eui64_.begin()); - this->publish_state(format_hex(this->last_eui64_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_eui64_.data(), 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -123,7 +126,9 @@ class ChannelOpenThreadInfo : public OpenThreadInstancePollingComponent, public uint8_t channel = otLinkGetChannel(instance); if (this->last_channel_ != channel) { this->last_channel_ = channel; - this->publish_state(std::to_string(this->last_channel_)); + char buf[4]; // max "255" + null + snprintf(buf, sizeof(buf), "%u", channel); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -168,7 +173,9 @@ class NetworkKeyOpenThreadInfo : public DatasetOpenThreadInfo, public text_senso void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_key_.begin(), this->last_key_.end(), dataset->mNetworkKey.m8)) { std::copy(dataset->mNetworkKey.m8, dataset->mNetworkKey.m8 + 16, this->last_key_.begin()); - this->publish_state(format_hex(dataset->mNetworkKey.m8, 16)); + char buf[format_hex_size(16)]; + format_hex_to(buf, dataset->mNetworkKey.m8, 16); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -201,7 +208,9 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor: void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_extpanid_.begin(), this->last_extpanid_.end(), dataset->mExtendedPanId.m8)) { std::copy(dataset->mExtendedPanId.m8, dataset->mExtendedPanId.m8 + 8, this->last_extpanid_.begin()); - this->publish_state(format_hex(this->last_extpanid_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_extpanid_.data(), 8); + this->publish_state(buf); } } From 498477c5a2a26cc76d227f66f71bdf7781502286 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:14 -1000 Subject: [PATCH 26/32] [homeassistant] Eliminate heap allocation in text sensor state updates (#13035) --- .../homeassistant/text_sensor/homeassistant_text_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 6f77349535..109574e0c8 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -15,7 +15,7 @@ void HomeassistantTextSensor::setup() { } else { ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); } - this->publish_state(state.str()); + this->publish_state(state.c_str(), state.size()); }); } void HomeassistantTextSensor::dump_config() { From 35118da606e3b0dce213675ae04e8d3f5c98e0d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:29 -1000 Subject: [PATCH 27/32] [ethernet_info] Eliminate heap allocations in text sensors (#13034) --- .../ethernet_info/ethernet_info_text_sensor.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index b49ddc263d..5b858b772f 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -14,12 +14,15 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS auto ips = ethernet::global_eth_component->get_ip_addresses(); if (ips != this->last_ips_) { this->last_ips_ = ips; - this->publish_state(ips[0].str()); + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); uint8_t sensor = 0; for (auto &ip : ips) { if (ip.is_set()) { if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); } sensor++; } @@ -64,7 +67,10 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { public: - void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); } + void setup() override { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf)); + } float get_setup_priority() const override { return setup_priority::ETHERNET; } void dump_config() override; }; From f9ed2aa17f1cf0786b69464fc30625f2dd98bf59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:42 -1000 Subject: [PATCH 28/32] [pylontech] Eliminate heap allocations in text sensors (#13033) --- .../pylontech/text_sensor/pylontech_text_sensor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp index 55e02f3e33..8175477cb2 100644 --- a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp @@ -25,16 +25,16 @@ void PylontechTextSensor::on_line_read(PylontechListener::LineContents *line) { return; } if (this->base_state_text_sensor_ != nullptr) { - this->base_state_text_sensor_->publish_state(std::string(line->base_st)); + this->base_state_text_sensor_->publish_state(line->base_st); } if (this->voltage_state_text_sensor_ != nullptr) { - this->voltage_state_text_sensor_->publish_state(std::string(line->volt_st)); + this->voltage_state_text_sensor_->publish_state(line->volt_st); } if (this->current_state_text_sensor_ != nullptr) { - this->current_state_text_sensor_->publish_state(std::string(line->curr_st)); + this->current_state_text_sensor_->publish_state(line->curr_st); } if (this->temperature_state_text_sensor_ != nullptr) { - this->temperature_state_text_sensor_->publish_state(std::string(line->temp_st)); + this->temperature_state_text_sensor_->publish_state(line->temp_st); } } From 6d1f6a1084be5787b148c908eecf083bcd50b8ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:54 -1000 Subject: [PATCH 29/32] [wifi_info] Eliminate heap churn in text sensors (#13031) --- esphome/components/wifi_info/wifi_info_text_sensor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 0cca3e16ef..2c0e66eeaf 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -24,12 +24,15 @@ void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - this->publish_state(ips[0].str()); + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); uint8_t sensor = 0; for (const auto &ip : ips) { if (ip.is_set()) { if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); } sensor++; } @@ -104,7 +107,7 @@ void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_list void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } void SSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { - this->publish_state(ssid.str()); + this->publish_state(ssid.c_str(), ssid.size()); } /**************** From 5b9be7c1695018162462e6b953b6b71db5099174 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:54:04 -1000 Subject: [PATCH 30/32] [ci] Add lint check to prevent usage of deprecated CORE.using_esp_idf (#13029) --- script/ci-custom.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/script/ci-custom.py b/script/ci-custom.py index cf59c3883b..77d2ab287d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -619,6 +619,19 @@ def lint_esphome_h(fname, line, col, content): ) +@lint_content_find_check( + "CORE.using_esp_idf", + include=py_include, + exclude=["esphome/core/__init__.py", "script/ci-custom.py"], +) +def lint_using_esp_idf_deprecated(fname, line, col, content): + return ( + f"{highlight('CORE.using_esp_idf')} is deprecated and will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + f"Please use {highlight('CORE.is_esp32')} and/or {highlight('CORE.using_arduino')} instead." + ) + + @lint_content_check(include=["*.h"]) def lint_pragma_once(fname, content): if "#pragma once" not in content: From fb47bfe92acf70e25bceb4b6bdece129f51d75e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:54:20 -1000 Subject: [PATCH 31/32] [dsmr] Eliminate heap allocation when publishing telegram (#13032) --- esphome/components/dsmr/dsmr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 41fc2f0d85..5c62aa93ab 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -268,7 +268,7 @@ bool Dsmr::parse_telegram() { // publish the telegram, after publishing the sensors so it can also trigger action based on latest values if (this->s_telegram_ != nullptr) { - this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_)); + this->s_telegram_->publish_state(this->telegram_, this->bytes_read_); } return true; } From c387c03944d0d21af655687345da91337eb57c32 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 19:22:04 -1000 Subject: [PATCH 32/32] [text_sensor][text] Avoid heap allocation when state unchanged (#13044) --- esphome/components/text/text.cpp | 5 ++++- esphome/components/text_sensor/text_sensor.cpp | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 3824c5004d..c2ade56f69 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -15,7 +15,10 @@ void Text::publish_state(const char *state) { this->publish_state(state, strlen( void Text::publish_state(const char *state, size_t len) { this->set_has_state(true); - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); } else { diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 174a98054f..66301564a4 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -32,7 +32,10 @@ void TextSensor::publish_state(const char *state) { this->publish_state(state, s void TextSensor::publish_state(const char *state, size_t len) { if (this->filter_list_ == nullptr) { // No filters: raw_state == state, store once and use for both callbacks - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } this->raw_callback_.call(this->state); ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); this->notify_frontend_(); @@ -40,7 +43,10 @@ void TextSensor::publish_state(const char *state, size_t len) { // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->raw_state.size() || memcmp(state, this->raw_state.data(), len) != 0) { + this->raw_state.assign(state, len); + } this->raw_callback_.call(this->raw_state); ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); this->filter_list_->input(this->raw_state); @@ -101,7 +107,10 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { } void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } this->notify_frontend_(); }