From 5f1eacf4ecf8fcc73775311508a92d4d7ff3296b Mon Sep 17 00:00:00 2001 From: Douwe <61123717+dhoeben@users.noreply.github.com> Date: Sun, 4 Jan 2026 03:43:31 +0100 Subject: [PATCH 01/47] [water_heater] (4/4) Implement tests for new water_heater component (#12517) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../water_heater/template_water_heater.cpp | 2 +- tests/components/water_heater/common.yaml | 16 +++ tests/components/web_server/common.yaml | 1 + .../fixtures/water_heater_template.yaml | 23 ++++ .../integration/test_water_heater_template.py | 109 ++++++++++++++++++ 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/components/water_heater/common.yaml create mode 100644 tests/integration/fixtures/water_heater_template.yaml create mode 100644 tests/integration/test_water_heater_template.py diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp index 18ef8d3f06..5ae5c30f36 100644 --- a/esphome/components/template/water_heater/template_water_heater.cpp +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -21,7 +21,7 @@ void TemplateWaterHeater::setup() { } water_heater::WaterHeaterTraits TemplateWaterHeater::traits() { - auto traits = water_heater::WaterHeater::get_traits(); + water_heater::WaterHeaterTraits traits; if (!this->supported_modes_.empty()) { traits.set_supported_modes(this->supported_modes_); diff --git a/tests/components/water_heater/common.yaml b/tests/components/water_heater/common.yaml new file mode 100644 index 0000000000..8ec2b1b297 --- /dev/null +++ b/tests/components/water_heater/common.yaml @@ -0,0 +1,16 @@ +water_heater: + - platform: template + id: my_boiler + name: "Test Boiler" + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 + optimistic: true + current_temperature: 45.0 + mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;" + visual: + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 diff --git a/tests/components/web_server/common.yaml b/tests/components/web_server/common.yaml index eb768eeb91..82307c189c 100644 --- a/tests/components/web_server/common.yaml +++ b/tests/components/web_server/common.yaml @@ -36,3 +36,4 @@ datetime: optimistic: yes event: update: +water_heater: diff --git a/tests/integration/fixtures/water_heater_template.yaml b/tests/integration/fixtures/water_heater_template.yaml new file mode 100644 index 0000000000..b54ebed789 --- /dev/null +++ b/tests/integration/fixtures/water_heater_template.yaml @@ -0,0 +1,23 @@ +esphome: + name: water-heater-template-test +host: +api: +logger: + +water_heater: + - platform: template + id: test_boiler + name: Test Boiler + optimistic: true + current_temperature: !lambda "return 45.0f;" + # Note: No mode lambda - we want optimistic mode changes to stick + # A mode lambda would override mode changes in loop() + supported_modes: + - "off" + - eco + - gas + - performance + visual: + min_temperature: 30.0 + max_temperature: 85.0 + target_temperature_step: 0.5 diff --git a/tests/integration/test_water_heater_template.py b/tests/integration/test_water_heater_template.py new file mode 100644 index 0000000000..b5f1fb64c0 --- /dev/null +++ b/tests/integration/test_water_heater_template.py @@ -0,0 +1,109 @@ +"""Integration test for template water heater component.""" + +from __future__ import annotations + +import asyncio + +import aioesphomeapi +from aioesphomeapi import WaterHeaterInfo, WaterHeaterMode, WaterHeaterState +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_water_heater_template( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test template water heater basic state and mode changes.""" + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + states: dict[int, aioesphomeapi.EntityState] = {} + gas_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + eco_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + + def on_state(state: aioesphomeapi.EntityState) -> None: + states[state.key] = state + if isinstance(state, WaterHeaterState): + # Wait for GAS mode + if state.mode == WaterHeaterMode.GAS and not gas_mode_future.done(): + gas_mode_future.set_result(state) + # Wait for ECO mode (we start at OFF, so test transitioning to ECO) + elif state.mode == WaterHeaterMode.ECO and not eco_mode_future.done(): + eco_mode_future.set_result(state) + + # Get entities and set up state synchronization + entities, services = await client.list_entities_services() + initial_state_helper = InitialStateHelper(entities) + water_heater_infos = [e for e in entities if isinstance(e, WaterHeaterInfo)] + assert len(water_heater_infos) == 1, ( + f"Expected exactly 1 water heater entity, got {len(water_heater_infos)}. Entity types: {[type(e).__name__ for e in entities]}" + ) + + test_water_heater = water_heater_infos[0] + + # Verify water heater entity info + assert test_water_heater.object_id == "test_boiler" + assert test_water_heater.name == "Test Boiler" + assert test_water_heater.min_temperature == 30.0 + assert test_water_heater.max_temperature == 85.0 + assert test_water_heater.target_temperature_step == 0.5 + + # Verify supported modes + supported_modes = test_water_heater.supported_modes + assert WaterHeaterMode.OFF in supported_modes, "Expected OFF in supported modes" + assert WaterHeaterMode.ECO in supported_modes, "Expected ECO in supported modes" + assert WaterHeaterMode.GAS in supported_modes, "Expected GAS in supported modes" + assert WaterHeaterMode.PERFORMANCE in supported_modes, ( + "Expected PERFORMANCE in supported modes" + ) + assert len(supported_modes) == 4, ( + f"Expected 4 supported modes, got {len(supported_modes)}: {supported_modes}" + ) + + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Get initial state and verify + initial_state = initial_state_helper.initial_states.get(test_water_heater.key) + assert initial_state is not None, "Water heater initial state not found" + assert isinstance(initial_state, WaterHeaterState) + # Initial mode is OFF (default) since we don't have a mode lambda + # A mode lambda would override optimistic mode changes + assert initial_state.mode == WaterHeaterMode.OFF, ( + f"Expected initial mode OFF, got {initial_state.mode}" + ) + assert initial_state.current_temperature == 45.0, ( + f"Expected current temp 45.0, got {initial_state.current_temperature}" + ) + + # Test changing to GAS mode + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS) + + try: + gas_state = await asyncio.wait_for(gas_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("GAS mode change not received within 5 seconds") + + assert isinstance(gas_state, WaterHeaterState) + assert gas_state.mode == WaterHeaterMode.GAS + + # Test changing to ECO mode (from GAS) + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO) + + try: + eco_state = await asyncio.wait_for(eco_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("ECO mode change not received within 5 seconds") + + assert isinstance(eco_state, WaterHeaterState) + assert eco_state.mode == WaterHeaterMode.ECO From 12c6f5749e22bbaf4c8bfba02d452b11f1b4219b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:46:29 -1000 Subject: [PATCH 02/47] [cst816] Combine log statements to reduce loop blocking (#12872) --- .../components/cst816/touchscreen/cst816_touchscreen.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 5be93692c0..d18d4e7c94 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -83,14 +83,14 @@ void CST816Touchscreen::update_touches() { } void CST816Touchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CST816 Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CST816 Touchscreen:\n" " X Raw Min: %d, X Raw Max: %d\n" " Y Raw Min: %d, Y Raw Max: %d", this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); const char *name; switch (this->chip_id_) { case CST716_CHIP_ID: From c96d0015a003f0a918bd648059b32e6980709cdf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:48:04 -1000 Subject: [PATCH 03/47] [esp_ldo] Combine log statements to reduce loop blocking (#12886) --- esphome/components/esp_ldo/esp_ldo.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index 5e3d4159f3..2eee855b46 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -21,9 +21,11 @@ void EspLdo::setup() { } } void EspLdo::dump_config() { - ESP_LOGCONFIG(TAG, "ESP LDO Channel %d:", this->channel_); - ESP_LOGCONFIG(TAG, " Voltage: %fV", this->voltage_); - ESP_LOGCONFIG(TAG, " Adjustable: %s", YESNO(this->adjustable_)); + ESP_LOGCONFIG(TAG, + "ESP LDO Channel %d:\n" + " Voltage: %fV\n" + " Adjustable: %s", + this->channel_, this->voltage_, YESNO(this->adjustable_)); } void EspLdo::adjust_voltage(float voltage) { From 16ada4d477146f2ff33caa445f295c4817d1825b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:48:39 -1000 Subject: [PATCH 04/47] [epaper_spi] Combine log statements to reduce loop blocking (#12881) --- esphome/components/epaper_spi/epaper_spi.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index 4e6b4a7fd6..0b600feeae 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -331,20 +331,21 @@ void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { void EPaperBase::dump_config() { LOG_DISPLAY("", "E-Paper SPI", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->name_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + " Model: %s\n" " SPI Data Rate: %uMHz\n" " Full update every: %d\n" " Swap X/Y: %s\n" " Mirror X: %s\n" " Mirror Y: %s", - (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY), - YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y)); + this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, + YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X), + YESNO(this->transform_ & MIRROR_Y)); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_UPDATE_INTERVAL(this); } } // namespace esphome::epaper_spi From cf93b66306a7e7f941a8a204709856be9eebb4fb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:59:55 -1000 Subject: [PATCH 05/47] [chsc6x] Combine log statements to reduce loop blocking (#12871) --- esphome/components/chsc6x/chsc6x_touchscreen.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/chsc6x/chsc6x_touchscreen.cpp b/esphome/components/chsc6x/chsc6x_touchscreen.cpp index 31c9466691..941144e451 100644 --- a/esphome/components/chsc6x/chsc6x_touchscreen.cpp +++ b/esphome/components/chsc6x/chsc6x_touchscreen.cpp @@ -32,14 +32,14 @@ void CHSC6XTouchscreen::update_touches() { } void CHSC6XTouchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); ESP_LOGCONFIG(TAG, + "CHSC6X Touchscreen:\n" " Touch timeout: %d\n" " x_raw_max_: %d\n" " y_raw_max_: %d", this->touch_timeout_, this->x_raw_max_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); } } // namespace chsc6x From bc9093127e5fff67c901b29691c4c4dae000bc2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:00:14 -1000 Subject: [PATCH 06/47] [cap1188] Combine log statements to reduce loop blocking (#12868) --- esphome/components/cap1188/cap1188.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp index 683e5cf487..9e8c87d147 100644 --- a/esphome/components/cap1188/cap1188.cpp +++ b/esphome/components/cap1188/cap1188.cpp @@ -63,14 +63,14 @@ void CAP1188Component::finish_setup_() { } void CAP1188Component::dump_config() { - ESP_LOGCONFIG(TAG, "CAP1188:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CAP1188:\n" " Product ID: 0x%x\n" " Manufacture ID: 0x%x\n" " Revision ID: 0x%x", this->cap1188_product_id_, this->cap1188_manufacture_id_, this->cap1188_revision_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); switch (this->error_code_) { case COMMUNICATION_FAILED: From 44fa6bae95bf3a60962c1752c187532251ebe5c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:57:53 -1000 Subject: [PATCH 07/47] [dht] Combine log statements to reduce loop blocking (#12877) --- esphome/components/dht/dht.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index e0abb7c5f0..6cb204c8de 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -17,11 +17,14 @@ void DHT::setup() { } void DHT::dump_config() { - ESP_LOGCONFIG(TAG, "DHT:"); + ESP_LOGCONFIG(TAG, + "DHT:\n" + " %sModel: %s\n" + " Internal pull-up: %s", + this->is_auto_detect_ ? "Auto-detected " : "", + this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent", + ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); LOG_PIN(" Pin: ", this->t_pin_); - ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "", - this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent"); - ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); From 9f06f046d67f64041eb472bb017e0b03d223b341 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:59:53 -1000 Subject: [PATCH 08/47] [espnow] Combine log statements to reduce loop blocking (#12887) --- .../espnow/packet_transport/espnow_transport.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index c1252acc9d..3d16f28c7d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -21,9 +21,11 @@ void ESPNowTransport::setup() { return; } - ESP_LOGI(TAG, "Registering ESP-NOW handlers"); - ESP_LOGI(TAG, "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", this->peer_address_[0], this->peer_address_[1], - this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]); + ESP_LOGI(TAG, + "Registering ESP-NOW handlers\n" + "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", + this->peer_address_[0], this->peer_address_[1], this->peer_address_[2], this->peer_address_[3], + this->peer_address_[4], this->peer_address_[5]); // Register received handler this->parent_->register_received_handler(this); From 6e8817cbc47f1dcf116eb2bdd06fbbfdb8b8f892 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:11 -1000 Subject: [PATCH 09/47] [esp8266_pwm] Combine log statements to reduce loop blocking (#12885) --- esphome/components/esp8266_pwm/esp8266_pwm.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 0aaef597d3..cc6bfbc8a8 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -18,9 +18,11 @@ void ESP8266PWM::setup() { this->turn_off(); } void ESP8266PWM::dump_config() { - ESP_LOGCONFIG(TAG, "ESP8266 PWM:"); + ESP_LOGCONFIG(TAG, + "ESP8266 PWM:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); LOG_FLOAT_OUTPUT(this); } void HOT ESP8266PWM::write_state(float state) { From cb598c43e891c6887efd1ede490e621041a72a0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:31 -1000 Subject: [PATCH 10/47] [endstop] Combine log statements to reduce loop blocking (#12879) --- esphome/components/endstop/endstop_cover.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index 381f098eb5..2c281ea2e6 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -104,10 +104,12 @@ void EndstopCover::loop() { } void EndstopCover::dump_config() { LOG_COVER("", "Endstop Cover", this); + ESP_LOGCONFIG(TAG, + " Open Duration: %.1fs\n" + " Close Duration: %.1fs", + this->open_duration_ / 1e3f, this->close_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Open Endstop", this->open_endstop_); - ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Close Endstop", this->close_endstop_); - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); } float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } void EndstopCover::stop_prev_trigger_() { From e94158a12f303543bea4e901601373a60d471af6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:52 -1000 Subject: [PATCH 11/47] [fan] Combine log statements to reduce loop blocking (#12889) --- esphome/components/fan/fan.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index bf5506da4b..0ffb60e50d 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -179,8 +179,10 @@ void Fan::add_on_state_callback(std::function &&callback) { this->state_ void Fan::publish_state() { auto traits = this->get_traits(); - ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); - ESP_LOGD(TAG, " State: %s", ONOFF(this->state)); + ESP_LOGD(TAG, + "'%s' - Sending state:\n" + " State: %s", + this->name_.c_str(), ONOFF(this->state)); if (traits.supports_speed()) { ESP_LOGD(TAG, " Speed: %d", this->speed); } From c59314ec09c5e311ca313d9c4baa37c29ccf2e7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:01:28 -1000 Subject: [PATCH 12/47] [debug] Combine log statements to reduce loop blocking (#12875) --- esphome/components/debug/debug_esp8266.cpp | 19 +++++---- esphome/components/debug/debug_libretiny.cpp | 15 ++++--- esphome/components/debug/debug_zephyr.cpp | 43 +++++++++++--------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 3395d9db12..7427b32290 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -47,14 +47,17 @@ void DebugComponent::get_device_info_(std::string &device_info) { #if !defined(CLANG_TIDY) auto reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); - ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); - ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); - ESP_LOGD(TAG, "Boot Version=%u Mode=%u", ESP.getBootVersion(), ESP.getBootMode()); - ESP_LOGD(TAG, "CPU Frequency: %u", ESP.getCpuFreqMHz()); - ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + ESP_LOGD(TAG, + "Chip ID: 0x%08X\n" + "SDK Version: %s\n" + "Core Version: %s\n" + "Boot Version=%u Mode=%u\n" + "CPU Frequency: %u\n" + "Flash Chip ID=0x%08X\n" + "Reset Reason: %s\n" + "Reset Info: %s", + ESP.getChipId(), ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), ESP.getBootVersion(), ESP.getBootMode(), + ESP.getCpuFreqMHz(), ESP.getFlashChipId(), reset_reason.c_str(), ESP.getResetInfo().c_str()); device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); device_info += "|SDK: "; diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index b5e2a5b310..e823ac6c77 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -13,12 +13,15 @@ uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } void DebugComponent::get_device_info_(std::string &device_info) { std::string reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); - ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); - ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); - ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); - ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); + ESP_LOGD(TAG, + "LibreTiny Version: %s\n" + "Chip: %s (%04x) @ %u MHz\n" + "Chip ID: 0x%06X\n" + "Board: %s\n" + "Flash: %u KiB / RAM: %u KiB\n" + "Reset Reason: %s", + lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), lt_cpu_get_mac_id(), + lt_get_board_code(), lt_flash_get_size() / 1024, lt_ram_get_size() / 1024, reset_reason.c_str()); device_info += "|Version: "; device_info += LT_BANNER_STR + 10; diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index c888c41a78..6abf983e9e 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -106,13 +106,13 @@ static void fa_cb(const struct flash_area *fa, void *user_data) { void DebugComponent::log_partition_info_() { #if CONFIG_FLASH_MAP_LABELS ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Label | Offset | Size"); - ESP_LOGCONFIG(TAG, "--------------------------------------------" + "| Label | Offset | Size\n" + "--------------------------------------------" "-----------------------------------------------"); #else ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Offset | Size"); - ESP_LOGCONFIG(TAG, "-----------------------------------------" + "| Offset | Size\n" + "-----------------------------------------" "------------------------------"); #endif flash_area_foreach(fa_cb, nullptr); @@ -300,18 +300,18 @@ void DebugComponent::get_device_info_(std::string &device_info) { return "Unspecified"; }; - ESP_LOGD(TAG, "Code page size: %u, code size: %u, device id: 0x%08x%08x", NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, - NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0]); - ESP_LOGD(TAG, "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x", NRF_FICR->ER[0], + ESP_LOGD(TAG, + "Code page size: %u, code size: %u, device id: 0x%08x%08x\n" + "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n" + "Device address type: %s, address: %s\n" + "Part code: nRF%x, version: %c%c%c%c, package: %s\n" + "RAM: %ukB, Flash: %ukB, production test: %sdone", + NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0], NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2], - NRF_FICR->IR[3]); - ESP_LOGD(TAG, "Device address type: %s, address: %s", (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), - get_mac_address_pretty().c_str()); - ESP_LOGD(TAG, "Part code: nRF%x, version: %c%c%c%c, package: %s", NRF_FICR->INFO.PART, - NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF, - NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE)); - ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, - (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); + NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), get_mac_address_pretty().c_str(), + NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, + NRF_FICR->INFO.VARIANT >> 8 & 0xFF, NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), + NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] && (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos; @@ -329,9 +329,10 @@ void DebugComponent::get_device_info_(std::string &device_info) { #else ESP_LOGD(TAG, "bootloader: Adafruit, version %u.%u.%u", (BOOTLOADER_VERSION_REGISTER >> 16) & 0xFF, (BOOTLOADER_VERSION_REGISTER >> 8) & 0xFF, BOOTLOADER_VERSION_REGISTER & 0xFF); - ESP_LOGD(TAG, "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x", read_mem_u32(MBR_BOOTLOADER_ADDR), - NRF_UICR->NRFFW[0]); - ESP_LOGD(TAG, "MBR param page addr 0x%08x, UICR param page addr 0x%08x", read_mem_u32(MBR_PARAM_PAGE_ADDR), + ESP_LOGD(TAG, + "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x\n" + "MBR param page addr 0x%08x, UICR param page addr 0x%08x", + read_mem_u32(MBR_BOOTLOADER_ADDR), NRF_UICR->NRFFW[0], read_mem_u32(MBR_PARAM_PAGE_ADDR), NRF_UICR->NRFFW[1]); if (is_sd_present()) { uint32_t const sd_id = sd_id_get(); @@ -368,8 +369,10 @@ void DebugComponent::get_device_info_(std::string &device_info) { } return res; }; - ESP_LOGD(TAG, "NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); - ESP_LOGD(TAG, "NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); + ESP_LOGD(TAG, + "NRFFW %s\n" + "NRFHW %s", + uicr(NRF_UICR->NRFFW, 13).c_str(), uicr(NRF_UICR->NRFHW, 12).c_str()); } void DebugComponent::update_platform_() {} From 096de869b6c67cb72126da67ca4d4a6699bc78e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:01:55 -1000 Subject: [PATCH 13/47] [esp32_ble_client] Combine log statements to reduce loop blocking (#12883) --- .../components/esp32_ble_client/ble_client_base.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 8017b577f4..26eb5dd092 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -70,9 +70,9 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B void BLEClientBase::dump_config() { ESP_LOGCONFIG(TAG, " Address: %s\n" - " Auto-Connect: %s", - this->address_str(), TRUEFALSE(this->auto_connect_)); - ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state())); + " Auto-Connect: %s\n" + " State: %s", + this->address_str(), TRUEFALSE(this->auto_connect_), espbt::client_state_to_string(this->state())); if (this->status_ == ESP_GATT_NO_RESOURCES) { ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config."); } else if (this->status_ != ESP_GATT_OK) { @@ -415,8 +415,10 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ for (auto &svc : this->services_) { char uuid_buf[espbt::UUID_STR_LEN]; svc->uuid.to_str(uuid_buf); - ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, uuid_buf); - ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_, + ESP_LOGV(TAG, + "[%d] [%s] Service UUID: %s\n" + "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", + this->connection_index_, this->address_str_, uuid_buf, this->connection_index_, this->address_str_, svc->start_handle, svc->end_handle); } #endif From facf4777a400327b1120e1898425d06a025b9c11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:04:00 -1000 Subject: [PATCH 14/47] [ezo_pmp] Combine log statements to reduce loop blocking (#12888) --- esphome/components/ezo_pmp/ezo_pmp.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp index 9ec41cce30..61b601328a 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.cpp +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -148,10 +148,13 @@ void EzoPMP::read_command_result_() { char current_char = response_buffer[i]; if (current_char == '\0') { - ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer); - ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer); - ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer); - ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer); + ESP_LOGV(TAG, + "Read Response from device: %s\n" + "First Component: %s\n" + "Second Component: %s\n" + "Third Component: %s", + (char *) response_buffer, (char *) first_parameter_buffer, (char *) second_parameter_buffer, + (char *) third_parameter_buffer); break; } From b1f9c08f51682c928a78d7997d279af5e4c7734c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:11:36 -1000 Subject: [PATCH 15/47] [esp32_ble_tracker] Make start_scan action idempotent (#12864) --- esphome/components/esp32_ble_tracker/automation.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index bbf7992fa4..6d26040ccb 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -98,7 +98,13 @@ template class ESP32BLEStartScanAction : public Action { TEMPLATABLE_VALUE(bool, continuous) void play(const Ts &...x) override { this->parent_->set_scan_continuous(this->continuous_.value(x...)); - this->parent_->start_scan(); + // Only call start_scan() if scanner is IDLE + // For other states (STARTING, RUNNING, STOPPING, FAILED), the normal state + // machine flow will eventually transition back to IDLE, at which point + // loop() will see scan_continuous_ and restart scanning if it is true. + if (this->parent_->get_scanner_state() == ScannerState::IDLE) { + this->parent_->start_scan(); + } } protected: From 8a4ee19c0b99fadde31e66ff577378c19121e567 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:26:19 -1000 Subject: [PATCH 16/47] [es8388] Combine log statements to reduce loop blocking (#12882) --- esphome/components/es8388/es8388.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index 5abe7a5e5f..d1834e7043 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -210,9 +210,11 @@ bool ES8388::set_dac_output(DacOutputLine line) { return false; }; - ESP_LOGV(TAG, "Setting ES8388_DACPOWER to 0x%02X", dac_power); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X", reg_out1); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", reg_out2); + ESP_LOGV(TAG, + "Setting ES8388_DACPOWER to 0x%02X\n" + "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X\n" + "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", + dac_power, reg_out1, reg_out2); ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL24, reg_out1)); // LOUT1VOL ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL25, reg_out1)); // ROUT1VOL From 766826cc9cc12732b289edfb8b34e0f226e2ef93 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:28:01 -1000 Subject: [PATCH 17/47] [esp32][libretiny] Reuse preference buffer to avoid heap churn (#12890) --- esphome/components/esp32/preferences.cpp | 6 ++++-- esphome/components/libretiny/preferences.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 5e1e8734e5..08439746b6 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -23,9 +23,11 @@ struct NVSData { size_t len; void set_data(const uint8_t *src, size_t size) { - this->data = std::make_unique(size); + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } memcpy(this->data.get(), src, size); - this->len = size; } }; diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index e47e88c6f3..68bc279767 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -22,9 +22,11 @@ struct NVSData { size_t len; void set_data(const uint8_t *src, size_t size) { - this->data = std::make_unique(size); + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } memcpy(this->data.get(), src, size); - this->len = size; } }; From 1e70091a27f8c6ea45da8cf2586359823d7679e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:28:17 -1000 Subject: [PATCH 18/47] [esp32_hosted] Combine log statements to reduce loop blocking (#12884) --- .../esp32_hosted/update/esp32_hosted_update.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index de130ca71f..626bda3af3 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -41,11 +41,13 @@ void Esp32HostedUpdate::setup() { if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) { esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset); if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) { - ESP_LOGD(TAG, "Firmware version: %s", app_desc->version); - ESP_LOGD(TAG, "Project name: %s", app_desc->project_name); - ESP_LOGD(TAG, "Build date: %s", app_desc->date); - ESP_LOGD(TAG, "Build time: %s", app_desc->time); - ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver); + ESP_LOGD(TAG, + "Firmware version: %s\n" + "Project name: %s\n" + "Build date: %s\n" + "Build time: %s\n" + "IDF version: %s", + app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver); this->update_info_.latest_version = app_desc->version; if (this->update_info_.latest_version != this->update_info_.current_version) { this->state_ = update::UPDATE_STATE_AVAILABLE; From cf513975f3a4ea61c546f8ca5040aeb4a062f9ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:30:45 -1000 Subject: [PATCH 19/47] [ens160_base] Combine log statements to reduce loop blocking (#12880) --- esphome/components/ens160_base/ens160_base.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/ens160_base/ens160_base.cpp b/esphome/components/ens160_base/ens160_base.cpp index 6ffaac9588..785b053f04 100644 --- a/esphome/components/ens160_base/ens160_base.cpp +++ b/esphome/components/ens160_base/ens160_base.cpp @@ -151,14 +151,16 @@ void ENS160Component::update() { } // verbose status logging - ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x", - (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS); - ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x", - (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER); - ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2); - ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x", - (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT); - ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x", + ESP_LOGV(TAG, + "Status: ENS160 STATAS bit 0x%x\n" + "Status: ENS160 STATER bit 0x%x\n" + "Status: ENS160 VALIDITY FLAG 0x%02x\n" + "Status: ENS160 NEWDAT bit 0x%x\n" + "Status: ENS160 NEWGPR bit 0x%x", + (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS, + (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER, + (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2, + (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT, (ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR); data_ready = ENS160_DATA_STATUS_NEWDAT & status_value; From 9e5dbb073a6015f45c7eaf07f15d69f79be27716 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:31:14 -1000 Subject: [PATCH 20/47] [emmeti] Combine log statements to reduce loop blocking (#12878) --- esphome/components/emmeti/emmeti.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/emmeti/emmeti.cpp b/esphome/components/emmeti/emmeti.cpp index 3cb184f868..5286f962b8 100644 --- a/esphome/components/emmeti/emmeti.cpp +++ b/esphome/components/emmeti/emmeti.cpp @@ -153,8 +153,10 @@ void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::Remote bool EmmetiClimate::check_checksum_(uint8_t checksum) { uint8_t expected = this->gen_checksum_(); - ESP_LOGV(TAG, "Expected checksum: %X", expected); - ESP_LOGV(TAG, "Checksum received: %X", checksum); + ESP_LOGV(TAG, + "Expected checksum: %X\n" + "Checksum received: %X", + expected, checksum); return checksum == expected; } @@ -264,8 +266,10 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01); - ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01); + ESP_LOGD(TAG, + "Swing: %d\n" + "Sleep: %d", + (curr_state.bitmap >> 1) & 0x01, (curr_state.bitmap >> 2) & 0x01); for (size_t pos = 0; pos < 4; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { @@ -291,10 +295,13 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01); - ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01); - ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01); - ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01); + ESP_LOGD(TAG, + "Turbo: %d\n" + "Light: %d\n" + "Tree: %d\n" + "Blow: %d", + (curr_state.bitmap >> 3) & 0x01, (curr_state.bitmap >> 4) & 0x01, (curr_state.bitmap >> 5) & 0x01, + (curr_state.bitmap >> 6) & 0x01); uint16_t control_data = 0; for (size_t pos = 0; pos < 11; pos++) { From a6db5a2ed836d643a6e47cb8e12422e5c54e20a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:38 -1000 Subject: [PATCH 21/47] [dfrobot_sen0395] Combine log statements to reduce loop blocking (#12876) --- esphome/components/dfrobot_sen0395/commands.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index 42074c80cf..8bb6ddf942 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -179,8 +179,10 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated detection area config:"); - ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_); + ESP_LOGI(TAG, + "Updated detection area config:\n" + "Detection area 1 from %.02fm to %.02fm.", + this->min1_, this->max1_); if (this->min2_ >= 0 && this->max2_ >= 0) { ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_); } @@ -209,9 +211,11 @@ uint8_t SetLatencyCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated output latency config:"); - ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_); - ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_); + ESP_LOGI(TAG, + "Updated output latency config:\n" + "Signal that someone was detected is delayed by %.03f s.\n" + "Signal that nobody is detected anymore is delayed by %.03f s.", + this->delay_after_detection_, this->delay_after_disappear_); ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); return 1; // Command done } From 25a325da617c4df23999d91ead7f805d2f7a6ffd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:49 -1000 Subject: [PATCH 22/47] [current_based] Combine log statements to reduce loop blocking (#12873) --- esphome/components/current_based/current_based_cover.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index 895b5515cb..cb3f65c9cd 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -146,8 +146,10 @@ void CurrentBasedCover::dump_config() { if (this->close_obstacle_current_threshold_ != FLT_MAX) { ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); } - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); - ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); + ESP_LOGCONFIG(TAG, + " Close Duration: %.1fs\n" + "Obstacle Rollback: %.1f%%", + this->close_duration_ / 1e3f, this->obstacle_rollback_ * 100); if (this->max_duration_ != UINT32_MAX) { ESP_LOGCONFIG(TAG, "Maximum duration: %.1fs", this->max_duration_ / 1e3f); } From 5a8b0f59b88651b2ec32c1bb08417001eea405f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:58 -1000 Subject: [PATCH 23/47] [cd74hc4067] Combine log statements to reduce loop blocking (#12870) --- esphome/components/cd74hc4067/cd74hc4067.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp index 174dc676f9..4293d7af07 100644 --- a/esphome/components/cd74hc4067/cd74hc4067.cpp +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -21,12 +21,14 @@ void CD74HC4067Component::setup() { } void CD74HC4067Component::dump_config() { - ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); + ESP_LOGCONFIG(TAG, + "CD74HC4067 Multiplexer:\n" + " switch delay: %" PRIu32, + this->switch_delay_); LOG_PIN(" S0 Pin: ", this->pin_s0_); LOG_PIN(" S1 Pin: ", this->pin_s1_); LOG_PIN(" S2 Pin: ", this->pin_s2_); LOG_PIN(" S3 Pin: ", this->pin_s3_); - ESP_LOGCONFIG(TAG, "switch delay: %" PRIu32, this->switch_delay_); } void CD74HC4067Component::activate_pin(uint8_t pin) { From dff8dc0ed1e325556d84a08d95aa3778eb248324 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:34:07 -1000 Subject: [PATCH 24/47] [cc1101] Combine log statements to reduce loop blocking (#12869) --- esphome/components/cc1101/cc1101.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 7e5309e165..10f72018f9 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -212,9 +212,8 @@ void CC1101Component::dump_config() { XTAL_FREQUENCY / (1 << 16); float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY; float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); - ESP_LOGCONFIG(TAG, "CC1101:"); - LOG_PIN(" CS Pin: ", this->cs_); ESP_LOGCONFIG(TAG, + "CC1101:\n" " Chip ID: 0x%04X\n" " Frequency: %" PRId32 " Hz\n" " Channel: %u\n" @@ -224,6 +223,7 @@ void CC1101Component::dump_config() { " Output Power: %.1f dBm", this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], symbol_rate, bw, this->output_power_effective_); + LOG_PIN(" CS Pin: ", this->cs_); } void CC1101Component::begin_tx() { From 77b3ffee001305d85cd79ab89268c9757a9db8e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:34:16 -1000 Subject: [PATCH 25/47] [factory_reset] Combine log statements to reduce loop blocking (#12866) --- esphome/components/factory_reset/factory_reset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/factory_reset/factory_reset.cpp b/esphome/components/factory_reset/factory_reset.cpp index bbbe399148..2e3f802343 100644 --- a/esphome/components/factory_reset/factory_reset.cpp +++ b/esphome/components/factory_reset/factory_reset.cpp @@ -30,8 +30,8 @@ static bool was_power_cycled() { void FactoryResetComponent::dump_config() { uint8_t count = 0; this->flash_.load(&count); - ESP_LOGCONFIG(TAG, "Factory Reset by Reset:"); ESP_LOGCONFIG(TAG, + "Factory Reset by Reset:\n" " Max interval between resets: %u seconds\n" " Current count: %u\n" " Factory reset after %u resets", From 9ae19d53dca62ed83f2630672e208480bbe7ab35 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 4 Jan 2026 13:39:56 -0500 Subject: [PATCH 26/47] [ultrasonic] Fix timeout issues and deprecate timeout option (#12897) Co-authored-by: Claude --- esphome/components/ultrasonic/sensor.py | 14 ++++++++++++-- .../components/ultrasonic/ultrasonic_sensor.cpp | 17 +++++------------ .../components/ultrasonic/ultrasonic_sensor.h | 4 ---- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index d341acb9d1..4b04ee7578 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -1,3 +1,5 @@ +import logging + from esphome import pins import esphome.codegen as cg from esphome.components import sensor @@ -11,6 +13,8 @@ from esphome.const import ( UNIT_METER, ) +_LOGGER = logging.getLogger(__name__) + CONF_PULSE_TIME = "pulse_time" ultrasonic_ns = cg.esphome_ns.namespace("ultrasonic") @@ -30,7 +34,7 @@ CONFIG_SCHEMA = ( { cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, + cv.Optional(CONF_TIMEOUT): cv.distance, cv.Optional( CONF_PULSE_TIME, default="10us" ): cv.positive_time_period_microseconds, @@ -49,5 +53,11 @@ async def to_code(config): echo = await cg.gpio_pin_expression(config[CONF_ECHO_PIN]) cg.add(var.set_echo_pin(echo)) - cg.add(var.set_timeout_us(config[CONF_TIMEOUT] / (0.000343 / 2))) + # Remove before 2026.8.0 + if CONF_TIMEOUT in config: + _LOGGER.warning( + "'timeout' option is deprecated and will be removed in 2026.8.0. " + "The option has no effect and can be safely removed." + ) + cg.add(var.set_pulse_time_us(config[CONF_PULSE_TIME])) diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index 184d93f189..369a10edbd 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -6,8 +6,8 @@ namespace esphome::ultrasonic { static const char *const TAG = "ultrasonic.sensor"; -static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) -static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time +static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) +static constexpr uint32_t MEASUREMENT_TIMEOUT_US = 80000; // Maximum time to wait for measurement completion void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) { uint32_t now = micros(); @@ -64,12 +64,8 @@ void UltrasonicSensorComponent::loop() { } uint32_t elapsed = micros() - this->measurement_start_us_; - if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) { - ESP_LOGD(TAG, - "'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32 - ", echo_end=%" PRIu32 ")", - this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us, - this->store_.echo_end_us); + if (elapsed >= MEASUREMENT_TIMEOUT_US) { + ESP_LOGD(TAG, "'%s' - Measurement timed out after %" PRIu32 "us", this->name_.c_str(), elapsed); this->publish_state(NAN); this->measurement_pending_ = false; } @@ -79,10 +75,7 @@ void UltrasonicSensorComponent::dump_config() { LOG_SENSOR("", "Ultrasonic Sensor", this); LOG_PIN(" Echo Pin: ", this->echo_pin_); LOG_PIN(" Trigger Pin: ", this->trigger_pin_); - ESP_LOGCONFIG(TAG, - " Pulse time: %" PRIu32 " us\n" - " Timeout: %" PRIu32 " us", - this->pulse_time_us_, this->timeout_us_); + ESP_LOGCONFIG(TAG, " Pulse time: %" PRIu32 " us", this->pulse_time_us_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index e2266543ce..b0c00e51f0 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -22,9 +22,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; } void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; } - /// Set the timeout for waiting for the echo in µs. - void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } - void setup() override; void loop() override; void dump_config() override; @@ -44,7 +41,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent ISRInternalGPIOPin trigger_pin_isr_; InternalGPIOPin *echo_pin_; UltrasonicSensorStore store_; - uint32_t timeout_us_{}; uint32_t pulse_time_us_{}; uint32_t measurement_start_us_{0}; From 449e478becae951c293991e713c70ab7123db93d Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Sun, 4 Jan 2026 12:50:10 -0800 Subject: [PATCH 27/47] [hub75] Bump esp-hub75 version to 0.2.2 (#12674) --- esphome/components/hub75/display.py | 33 +++++++++++++--- esphome/components/hub75/hub75.cpp | 19 +++++++-- esphome/components/hub75/hub75_component.h | 4 +- esphome/idf_component.yml | 2 +- .../hub75/test.esp32-s3-idf-rotate.yaml | 39 +++++++++++++++++++ 5 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 tests/components/hub75/test.esp32-s3-idf-rotate.yaml diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 7736319330..40202e52ca 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_OE_PIN, + CONF_ROTATION, CONF_UPDATE_INTERVAL, ) from esphome.core import ID @@ -134,6 +135,14 @@ CLOCK_SPEEDS = { "20MHZ": Hub75ClockSpeed.HZ_20M, } +Hub75Rotation = cg.global_ns.enum("Hub75Rotation", is_class=True) +ROTATIONS = { + 0: Hub75Rotation.ROTATE_0, + 90: Hub75Rotation.ROTATE_90, + 180: Hub75Rotation.ROTATE_180, + 270: Hub75Rotation.ROTATE_270, +} + HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display) Hub75Config = cg.global_ns.struct("Hub75Config") Hub75Pins = cg.global_ns.struct("Hub75Pins") @@ -361,6 +370,8 @@ CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(HUB75Display), + # Override rotation - store Hub75Rotation directly (driver handles rotation) + cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS, int=True), # Board preset (optional - provides default pin mappings) cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True), # Panel dimensions @@ -378,7 +389,7 @@ CONFIG_SCHEMA = cv.All( # Display configuration cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean, cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255), - cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12), + cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=4, max=12), cv.Optional(CONF_GAMMA_CORRECT): cv.enum( {"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True ), @@ -490,10 +501,11 @@ def _build_config_struct( Fields must be added in declaration order (see hub75_types.h) to satisfy C++ designated initializer requirements. The order is: 1. fields_before_pins (panel_width through layout) - 2. pins - 3. output_clock_speed - 4. min_refresh_rate - 5. fields_after_min_refresh (latch_blanking through brightness) + 2. rotation + 3. pins + 4. output_clock_speed + 5. min_refresh_rate + 6. fields_after_min_refresh (latch_blanking through brightness) """ fields_before_pins = [ (CONF_PANEL_WIDTH, "panel_width"), @@ -516,6 +528,10 @@ def _build_config_struct( _append_config_fields(config, fields_before_pins, config_fields) + # Rotation - config already contains Hub75Rotation enum from cv.enum + if CONF_ROTATION in config: + config_fields.append(("rotation", config[CONF_ROTATION])) + config_fields.append(("pins", pins_struct)) if CONF_CLOCK_SPEED in config: @@ -531,7 +547,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.7", + ref="0.2.2", ) # Set compile-time configuration via defines @@ -570,6 +586,11 @@ async def to_code(config: ConfigType) -> None: pins_struct = _build_pins_struct(pin_expressions, e_pin_num) hub75_config = _build_config_struct(config, pins_struct, min_refresh) + # Rotation is handled by the hub75 driver (config_.rotation already set above). + # Force rotation to 0 for ESPHome's Display base class to avoid double-rotation. + if CONF_ROTATION in config: + config[CONF_ROTATION] = 0 + # Create display and register var = cg.new_Pvariable(config[CONF_ID], hub75_config) await display.register_display(var, config) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index e29f1a898c..cf8661b2b3 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -92,14 +92,25 @@ void HUB75Display::fill(Color color) { if (!this->enabled_) [[unlikely]] return; - // Special case: black (off) - use fast hardware clear - if (!color.is_on()) { + // Start with full display rect + display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal()); + + // Apply clipping using Rect::shrink() to intersect + display::Rect clip = this->get_clipping(); + if (clip.is_set()) { + fill_rect.shrink(clip); + if (!fill_rect.is_set()) + return; // Completely clipped + } + + // Fast path: black filling entire display + if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() && + fill_rect.h == this->get_height_internal()) { driver_->clear(); return; } - // For non-black colors, fall back to base class (pixel-by-pixel) - Display::fill(color); + driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b); } void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h index f0e7ea10d5..ab7e3fc5b1 100644 --- a/esphome/components/hub75/hub75_component.h +++ b/esphome/components/hub75/hub75_component.h @@ -39,8 +39,8 @@ class HUB75Display : public display::Display { protected: // Display internal methods - int get_width_internal() override { return config_.panel_width * config_.layout_cols; } - int get_height_internal() override { return config_.panel_height * config_.layout_rows; } + int get_width_internal() override { return this->driver_ != nullptr ? this->driver_->get_width() : 0; } + int get_height_internal() override { return this->driver_ != nullptr ? this->driver_->get_height() : 0; } // Member variables Hub75Driver *driver_{nullptr}; diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 4573391bc1..36aa77c524 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -28,6 +28,6 @@ dependencies: rules: - if: "target in [esp32s2, esp32s3, esp32p4]" esphome/esp-hub75: - version: 0.1.7 + version: 0.2.2 rules: - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" diff --git a/tests/components/hub75/test.esp32-s3-idf-rotate.yaml b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml new file mode 100644 index 0000000000..9855fcb4e6 --- /dev/null +++ b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml @@ -0,0 +1,39 @@ +display: + - platform: hub75 + id: my_hub75 + board: apollo-automation-rev6 + panel_width: 64 + panel_height: 64 + layout_rows: 1 + layout_cols: 2 + rotation: 90 + bit_depth: 4 + double_buffer: true + auto_clear_enabled: true + update_interval: 16ms + latch_blanking: 1 + clock_speed: 20MHz + lambda: |- + // Test clipping: 8 columns x 4 rows of 16x16 colored squares + Color colors[32] = { + Color(255, 0, 0), Color(0, 255, 0), Color(0, 0, 255), Color(255, 255, 0), + Color(255, 0, 255), Color(0, 255, 255), Color(255, 128, 0), Color(128, 0, 255), + Color(0, 128, 255), Color(255, 0, 128), Color(128, 255, 0), Color(0, 255, 128), + Color(255, 128, 128), Color(128, 255, 128), Color(128, 128, 255), Color(255, 255, 128), + Color(255, 128, 255), Color(128, 255, 255), Color(192, 64, 0), Color(64, 192, 0), + Color(0, 64, 192), Color(192, 0, 64), Color(64, 0, 192), Color(0, 192, 64), + Color(128, 64, 64), Color(64, 128, 64), Color(64, 64, 128), Color(128, 128, 64), + Color(128, 64, 128), Color(64, 128, 128), Color(255, 255, 255), Color(128, 128, 128) + }; + int idx = 0; + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 8; col++) { + // Clipping mode: clip to square bounds, then fill "entire screen" + it.start_clipping(col * 16, row * 16, (col + 1) * 16, (row + 1) * 16); + it.fill(colors[idx]); + it.end_clipping(); + idx++; + } + } + +<<: !include common.yaml From dd8259b2ce5fa2f45571bb69c2059a38e7637cd2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:24:36 -1000 Subject: [PATCH 28/47] [gcja5] Combine log statements to reduce loop blocking (#12898) --- esphome/components/gcja5/gcja5.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/gcja5/gcja5.cpp b/esphome/components/gcja5/gcja5.cpp index a7342bc828..f7f7f8d02c 100644 --- a/esphome/components/gcja5/gcja5.cpp +++ b/esphome/components/gcja5/gcja5.cpp @@ -95,11 +95,13 @@ void GCJA5Component::parse_data_() { if (!this->first_status_log_) { this->first_status_log_ = true; - ESP_LOGI(TAG, "GCJA5 Status"); - ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03); - ESP_LOGI(TAG, "PD Status : %i", (status >> 4) & 0x03); - ESP_LOGI(TAG, "LD Status : %i", (status >> 2) & 0x03); - ESP_LOGI(TAG, "Fan Status : %i", (status >> 0) & 0x03); + ESP_LOGI(TAG, + "GCJA5 Status\n" + "Overall Status : %i\n" + "PD Status : %i\n" + "LD Status : %i\n" + "Fan Status : %i", + (status >> 6) & 0x03, (status >> 4) & 0x03, (status >> 2) & 0x03, (status >> 0) & 0x03); } } From 8287484a36880b45fdf88e8bbabd931cd1ad5e7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:24:51 -1000 Subject: [PATCH 29/47] [gl_r01_i2c] Combine log statements to reduce loop blocking (#12899) --- esphome/components/gl_r01_i2c/gl_r01_i2c.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp index e2a64b6877..38328c4b03 100644 --- a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp @@ -27,8 +27,10 @@ void GLR01I2CComponent::setup() { } void GLR01I2CComponent::dump_config() { - ESP_LOGCONFIG(TAG, "GL-R01 I2C:"); - ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_); + ESP_LOGCONFIG(TAG, + "GL-R01 I2C:\n" + " Firmware Version: 0x%04X", + this->version_); LOG_I2C_DEVICE(this); LOG_SENSOR(" ", "Distance", this); } From 7e758260647761466a9c357fd726080c6a1df534 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:25:24 -1000 Subject: [PATCH 30/47] [wifi] Fix LibreTiny thread safety with queue-based event handling (#12833) --- esphome/components/wifi/wifi_component.h | 5 + .../wifi/wifi_component_libretiny.cpp | 288 +++++++++++++++--- 2 files changed, 248 insertions(+), 45 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 5bf1f444e8..1906b672b8 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -245,6 +245,10 @@ enum WifiMinAuthMode : uint8_t { struct IDFWiFiEvent; #endif +#ifdef USE_LIBRETINY +struct LTWiFiEvent; +#endif + /** Listener interface for WiFi IP state changes. * * Components can implement this interface to receive IP address updates @@ -583,6 +587,7 @@ class WiFiComponent : public Component { #ifdef USE_LIBRETINY void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); + void wifi_process_event_(LTWiFiEvent *event); void wifi_scan_done_callback_(); #endif diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 9bbd319f33..e9ccb86871 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -3,12 +3,16 @@ #ifdef USE_WIFI #ifdef USE_LIBRETINY +#include #include #include #include "lwip/ip_addr.h" #include "lwip/err.h" #include "lwip/dns.h" +#include +#include + #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -19,7 +23,68 @@ namespace esphome::wifi { static const char *const TAG = "wifi_lt"; -static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Thread-safe event handling for LibreTiny WiFi +// +// LibreTiny's WiFi.onEvent() callback runs in the WiFi driver's thread context, +// not the main ESPHome loop. Without synchronization, modifying shared state +// (like connection status flags) from the callback causes race conditions: +// - The main loop may never see state changes (values cached in registers) +// - State changes may be visible in inconsistent order +// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX) +// +// Solution: Queue events in the callback and process them in the main loop. +// This is the same approach used by ESP32 IDF's wifi_process_event_(). +// All state modifications happen in the main loop context, eliminating races. + +static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow +static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static volatile uint32_t s_event_queue_overflow_count = + 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +// Event structure for queued WiFi events - contains a copy of event data +// to avoid lifetime issues with the original event data from the callback +struct LTWiFiEvent { + arduino_event_id_t event_id; + union { + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t channel; + uint8_t authmode; + } sta_connected; + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t reason; + } sta_disconnected; + struct { + uint8_t old_mode; + uint8_t new_mode; + } sta_authmode_change; + struct { + uint32_t status; + uint8_t number; + uint8_t scan_id; + } scan_done; + struct { + uint8_t mac[6]; + int rssi; + } ap_probe_req; + } data; +}; + +// Connection state machine - only modified from main loop after queue processing +enum class LTWiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; + +static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFi.getMode(); @@ -136,7 +201,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); - s_sta_connecting = true; + // Reset state machine before connecting + s_sta_state = LTWiFiSTAState::CONNECTING; WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), ap.get_channel(), // 0 = auto @@ -271,16 +337,101 @@ const char *get_disconnect_reason_str(uint8_t reason) { using esphome_wifi_event_id_t = arduino_event_id_t; using esphome_wifi_event_info_t = arduino_event_info_t; +// Event callback - runs in WiFi driver thread context +// Only queues events for processing in main loop, no logging or state changes here void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { + if (s_event_queue == nullptr) { + return; + } + + // Allocate on heap and fill directly to avoid extra memcpy + auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory) + to_send->event_id = event; + + // Copy event-specific data switch (event) { + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { + auto &it = info.wifi_sta_connected; + to_send->data.sta_connected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_connected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_connected.ssid) - 1)); + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + to_send->data.sta_connected.channel = it.channel; + to_send->data.sta_connected.authmode = it.authmode; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + to_send->data.sta_disconnected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_disconnected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_disconnected.ssid) - 1)); + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + to_send->data.sta_disconnected.reason = it.reason; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { + auto &it = info.wifi_sta_authmode_change; + to_send->data.sta_authmode_change.old_mode = it.old_mode; + to_send->data.sta_authmode_change.new_mode = it.new_mode; + break; + } + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { + auto &it = info.wifi_scan_done; + to_send->data.scan_done.status = it.status; + to_send->data.scan_done.number = it.number; + to_send->data.scan_done.scan_id = it.scan_id; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { + auto &it = info.wifi_ap_probereqrecved; + memcpy(to_send->data.ap_probe_req.mac, it.mac, 6); + to_send->data.ap_probe_req.rssi = it.rssi; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { + auto &it = info.wifi_sta_connected; + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_READY: + case ESPHOME_EVENT_ID_WIFI_STA_START: + case ESPHOME_EVENT_ID_WIFI_STA_STOP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: + case ESPHOME_EVENT_ID_WIFI_AP_START: + case ESPHOME_EVENT_ID_WIFI_AP_STOP: + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: + // No additional data needed + break; + default: + // Unknown event, don't queue + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + return; + } + + // Queue event (don't block if queue is full) + if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) { + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + s_event_queue_overflow_count++; + } +} + +// Process a single event from the queue - runs in main loop context +void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { + switch (event->event_id) { case ESPHOME_EVENT_ID_WIFI_READY: { ESP_LOGV(TAG, "Ready"); break; } case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { - auto it = info.wifi_scan_done; - ESP_LOGV(TAG, "Scan done: status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); - + auto &it = event->data.scan_done; + ESP_LOGV(TAG, "Scan done: status=%" PRIu32 " number=%u scan_id=%u", it.status, it.number, it.scan_id); this->wifi_scan_done_callback_(); break; } @@ -291,14 +442,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { - auto it = info.wifi_sta_connected; + auto &it = event->data.sta_connected; + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, - (const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel, - get_auth_mode_str(it.authmode)); + (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); + // Note: We don't set CONNECTED state here yet - wait for GOT_IP + // This matches ESP32 IDF behavior where s_sta_connected is set but + // wifi_sta_connect_status_() also checks got_ipv4_address_ #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); @@ -306,6 +461,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_state = LTWiFiSTAState::CONNECTED; for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -315,19 +471,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { - auto it = info.wifi_sta_disconnected; + auto &it = event->data.sta_disconnected; // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. // These are typically "Association Leave" events that don't indicate actual failures: // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [V][wifi_lt]: Connected ssid='WIFI' bssid=... channel=3, authmode=WPA2 PSK - // Without this check, the spurious events set s_sta_connecting=false, causing - // wifi_sta_connect_status_() to return IDLE. The main loop then sees - // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) - // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. - if (it.ssid_len == 0 && s_sta_connecting) { + // Without this check, the spurious events would transition state to ERROR_FAILED, + // causing wifi_sta_connect_status_() to return an error. The main loop would then + // call retry_connect(), aborting a connection that may succeed moments later. + // Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed. + if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) { ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", get_disconnect_reason_str(it.reason)); break; @@ -336,11 +491,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); + s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND; } else { - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } uint8_t reason = it.reason; @@ -351,7 +508,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ this->error_from_callback_ = true; } - s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { @@ -361,24 +517,22 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { - auto it = info.wifi_sta_authmode_change; + auto &it = event->data.sta_authmode_change; ESP_LOGV(TAG, "Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode)); // Mitigate CVE-2020-12638 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) { ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting"); - // we can't call retry_connect() from this context, so disconnect immediately - // and notify main thread with error_from_callback_ WiFi.disconnect(); this->error_from_callback_ = true; + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { - // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), format_ip4_addr(WiFi.gatewayIP()).c_str()); - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::CONNECTED; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); @@ -387,7 +541,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { - // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Got IPv6"); #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { @@ -398,6 +551,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Lost IP"); + // Don't change state to IDLE - let the disconnect event handle that break; } case ESPHOME_EVENT_ID_WIFI_AP_START: { @@ -409,15 +563,21 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { - auto it = info.wifi_sta_connected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_connected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { - auto it = info.wifi_sta_disconnected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_disconnected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { @@ -425,8 +585,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { - auto it = info.wifi_ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + auto &it = event->data.ap_probe_req; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif break; } default: @@ -434,23 +598,35 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } } void WiFiComponent::wifi_pre_setup_() { + // Create event queue for thread-safe event handling + // Events are pushed from WiFi callback thread and processed in main loop + s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *)); + if (s_event_queue == nullptr) { + ESP_LOGE(TAG, "Failed to create event queue"); + return; + } + auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); WiFi.onEvent(f); // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - auto status = WiFi.status(); - if (status == WL_CONNECTED) { - return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { - return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - } else if (status == WL_NO_SSID_AVAIL) { - return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - } else if (s_sta_connecting) { - return WiFiSTAConnectStatus::CONNECTING; + // Use state machine instead of querying WiFi.status() directly + // State is updated in main loop from queued events, ensuring thread safety + switch (s_sta_state) { + case LTWiFiSTAState::CONNECTED: + return WiFiSTAConnectStatus::CONNECTED; + case LTWiFiSTAState::ERROR_NOT_FOUND: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + case LTWiFiSTAState::ERROR_FAILED: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case LTWiFiSTAState::CONNECTING: + return WiFiSTAConnectStatus::CONNECTING; + case LTWiFiSTAState::IDLE: + default: + return WiFiSTAConnectStatus::IDLE; } - return WiFiSTAConnectStatus::IDLE; } bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA @@ -534,9 +710,9 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; #endif // USE_WIFI_AP bool WiFiComponent::wifi_disconnect_() { - // Clear connecting flag first so disconnect events aren't ignored + // Reset state first so disconnect events aren't ignored // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; return WiFi.disconnect(); } @@ -563,7 +739,29 @@ int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } -void WiFiComponent::wifi_loop_() {} +void WiFiComponent::wifi_loop_() { + // Process all pending events from the queue + if (s_event_queue == nullptr) { + return; + } + + // Check for dropped events due to queue overflow + if (s_event_queue_overflow_count > 0) { + ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count); + s_event_queue_overflow_count = 0; + } + + while (true) { + LTWiFiEvent *event; + if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) { + // No more events + break; + } + + wifi_process_event_(event); + delete event; // NOLINT(cppcoreguidelines-owning-memory) + } +} } // namespace esphome::wifi #endif // USE_LIBRETINY From 61ecfb5f2b6421a3824e060ebbad4c35d71c1113 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:25:52 -1000 Subject: [PATCH 31/47] [openthread] Combine log statements to reduce loop blocking (#12917) --- esphome/components/openthread/openthread_esp.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 1f18e51496..a9aff3cce4 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -126,9 +126,12 @@ void OpenThreadComponent::ot_main() { ESP_LOGE(TAG, "Failed to set OpenThread linkmode."); } link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance()); - ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false"); + ESP_LOGD(TAG, + "Link Mode Device Type: %s\n" + "Link Mode Network Data: %s\n" + "Link Mode RX On When Idle: %s", + link_mode_config.mDeviceType ? "true" : "false", link_mode_config.mNetworkData ? "true" : "false", + link_mode_config.mRxOnWhenIdle ? "true" : "false"); // Run the main loop #if CONFIG_OPENTHREAD_CLI @@ -144,8 +147,8 @@ void OpenThreadComponent::ot_main() { // Make sure the length is 0 so we fallback to the configuration dataset.mLength = 0; } else { - ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration"); - ESP_LOGI(TAG, "(set force_dataset: true to override)"); + ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration\n" + "(set force_dataset: true to override)"); } #endif From fc9683f024e69886fd54844e77170222e476af98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:26:13 -1000 Subject: [PATCH 32/47] [opentherm] Combine log statements to reduce loop blocking (#12916) --- esphome/components/opentherm/hub.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index b23792fc7a..7a0cdc7f80 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -395,10 +395,8 @@ void OpenthermHub::dump_config() { this->write_initial_messages_(initial_messages); this->write_repeating_messages_(repeating_messages); - ESP_LOGCONFIG(TAG, "OpenTherm:"); - LOG_PIN(" In: ", this->in_pin_); - LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, + "OpenTherm:\n" " Sync mode: %s\n" " Sensors: %s\n" " Binary sensors: %s\n" @@ -409,6 +407,8 @@ void OpenthermHub::dump_config() { YESNO(this->sync_mode_), SHOW(OPENTHERM_SENSOR_LIST(ID, )), SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )), SHOW(OPENTHERM_SWITCH_LIST(ID, )), SHOW(OPENTHERM_INPUT_SENSOR_LIST(ID, )), SHOW(OPENTHERM_OUTPUT_LIST(ID, )), SHOW(OPENTHERM_NUMBER_LIST(ID, ))); + LOG_PIN(" In: ", this->in_pin_); + LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, " Initial requests:"); for (auto type : initial_messages) { ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type)); From 6d9d593e12c76a799ae3b944b9262c852186e909 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:27:14 -1000 Subject: [PATCH 33/47] [my9231] Combine log statements to reduce loop blocking (#12915) --- esphome/components/my9231/my9231.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/my9231/my9231.cpp b/esphome/components/my9231/my9231.cpp index fba7ac2bf3..5b77a49e72 100644 --- a/esphome/components/my9231/my9231.cpp +++ b/esphome/components/my9231/my9231.cpp @@ -58,14 +58,14 @@ void MY9231OutputComponent::setup() { } } void MY9231OutputComponent::dump_config() { - ESP_LOGCONFIG(TAG, "MY9231:"); - LOG_PIN(" DI Pin: ", this->pin_di_); - LOG_PIN(" DCKI Pin: ", this->pin_dcki_); ESP_LOGCONFIG(TAG, + "MY9231:\n" " Total number of channels: %u\n" " Number of chips: %u\n" " Bit depth: %u", this->num_channels_, this->num_chips_, this->bit_depth_); + LOG_PIN(" DI Pin: ", this->pin_di_); + LOG_PIN(" DCKI Pin: ", this->pin_dcki_); } void MY9231OutputComponent::loop() { if (!this->update_) From ccc9d95c9d8a66384c153d6dd2e543ab0f99214b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:28:14 -1000 Subject: [PATCH 34/47] [mqtt] Combine log statements to reduce loop blocking (#12914) --- esphome/components/mqtt/mqtt_backend_esp32.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index e3105f4860..3838d6df26 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -166,10 +166,12 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { case MQTT_EVENT_ERROR: ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); if (event.error_handle.error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { - ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle.esp_tls_last_esp_err); - ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle.esp_tls_stack_err); - ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle.esp_transport_sock_errno, - strerror(event.error_handle.esp_transport_sock_errno)); + ESP_LOGE(TAG, + "Last error code reported from esp-tls: 0x%x\n" + "Last tls stack error number: 0x%x\n" + "Last captured errno : %d (%s)", + event.error_handle.esp_tls_last_esp_err, event.error_handle.esp_tls_stack_err, + event.error_handle.esp_transport_sock_errno, strerror(event.error_handle.esp_transport_sock_errno)); } else if (event.error_handle.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle.connect_return_code); } else { From d1d5c942ec81a7dc8d2cb1f2566faf1253ce2423 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:01 -1000 Subject: [PATCH 35/47] [mcp9600] Combine log statements to reduce loop blocking (#12913) --- esphome/components/mcp9600/mcp9600.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/mcp9600/mcp9600.cpp b/esphome/components/mcp9600/mcp9600.cpp index e1a88988c4..ff411bef7a 100644 --- a/esphome/components/mcp9600/mcp9600.cpp +++ b/esphome/components/mcp9600/mcp9600.cpp @@ -63,12 +63,12 @@ void MCP9600Component::setup() { } void MCP9600Component::dump_config() { - ESP_LOGCONFIG(TAG, "MCP9600:"); + ESP_LOGCONFIG(TAG, + "MCP9600:\n" + " Device ID: 0x%x", + this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Hot Junction Temperature", this->hot_junction_sensor_); LOG_SENSOR(" ", "Cold Junction Temperature", this->cold_junction_sensor_); From aa4b274b3c69f228463bdefd09c812b488990240 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:18 -1000 Subject: [PATCH 36/47] [mcp3204] Combine log statements to reduce loop blocking (#12912) --- esphome/components/mcp3204/mcp3204.cpp | 6 ++++-- esphome/components/mcp3204/sensor/mcp3204_sensor.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index f0dd171a14..abefcad0eb 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -11,9 +11,11 @@ float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; } void MCP3204::setup() { this->spi_setup(); } void MCP3204::dump_config() { - ESP_LOGCONFIG(TAG, "MCP3204:"); + ESP_LOGCONFIG(TAG, + "MCP3204:\n" + " Reference Voltage: %.2fV", + this->reference_voltage_); LOG_PIN(" CS Pin:", this->cs_); - ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); } float MCP3204::read_data(uint8_t pin, bool differential) { diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp index 4c4abef4a7..e673537be1 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -11,8 +11,10 @@ float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; } void MCP3204Sensor::dump_config() { LOG_SENSOR("", "MCP3204 Sensor", this); - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); - ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_)); + ESP_LOGCONFIG(TAG, + " Pin: %u\n" + " Differential Mode: %s", + this->pin_, YESNO(this->differential_mode_)); LOG_UPDATE_INTERVAL(this); } float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); } From 9b2a36a313be718bb7b6425a14d670d3f5d09dfc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:33 -1000 Subject: [PATCH 37/47] [hc8] Combine log statements to reduce loop blocking (#12900) --- esphome/components/hc8/hc8.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/hc8/hc8.cpp b/esphome/components/hc8/hc8.cpp index 5b649c2735..4d0f77df1b 100644 --- a/esphome/components/hc8/hc8.cpp +++ b/esphome/components/hc8/hc8.cpp @@ -89,11 +89,12 @@ void HC8Component::calibrate(uint16_t baseline) { float HC8Component::get_setup_priority() const { return setup_priority::DATA; } void HC8Component::dump_config() { - ESP_LOGCONFIG(TAG, "HC8:"); + ESP_LOGCONFIG(TAG, + "HC8:\n" + " Warmup time: %" PRIu32 " s", + this->warmup_seconds_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); this->check_uart_settings(9600); - - ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); } } // namespace esphome::hc8 From 8ae1f26b6adefc02e40e0a1033808e68452b6a0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:45 -1000 Subject: [PATCH 38/47] [hlw8012] Combine log statements to reduce loop blocking (#12901) --- esphome/components/hlw8012/hlw8012.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 70a05e4f72..f037ee9d8b 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -33,15 +33,15 @@ void HLW8012Component::setup() { } } void HLW8012Component::dump_config() { - ESP_LOGCONFIG(TAG, "HLW8012:"); - LOG_PIN(" SEL Pin: ", this->sel_pin_); - LOG_PIN(" CF Pin: ", this->cf_pin_); - LOG_PIN(" CF1 Pin: ", this->cf1_pin_); ESP_LOGCONFIG(TAG, + "HLW8012:\n" " Change measurement mode every %" PRIu32 "\n" " Current resistor: %.1f mΩ\n" " Voltage Divider: %.1f", this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_); + LOG_PIN(" SEL Pin: ", this->sel_pin_); + LOG_PIN(" CF Pin: ", this->cf_pin_); + LOG_PIN(" CF1 Pin: ", this->cf1_pin_); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); From 9bbfad4a081f83a257ae7b7ad12182199dbbf204 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:08 -1000 Subject: [PATCH 39/47] [honeywellabp] Combine log statements to reduce loop blocking (#12902) --- esphome/components/honeywellabp/honeywellabp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp index 4c00f034aa..c204325dfc 100644 --- a/esphome/components/honeywellabp/honeywellabp.cpp +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -35,8 +35,10 @@ uint8_t HONEYWELLABPSensor::readsensor_() { pressure_count_ = ((uint16_t) (buf_[0]) << 8 & 0x3F00) | ((uint16_t) (buf_[1]) & 0xFF); // 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3 temperature_count_ = (((uint16_t) (buf_[2]) << 3) & 0x7F8) | (((uint16_t) (buf_[3]) >> 5) & 0x7); - ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_); - ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_); + ESP_LOGV(TAG, + "Sensor pressure_count_ %d\n" + "Sensor temperature_count_ %d", + pressure_count_, temperature_count_); } return status_; } From 548600b47a30783c534abb883cf5ea0aaab65e11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:34 -1000 Subject: [PATCH 40/47] [ina260] Combine log statements to reduce loop blocking (#12903) --- esphome/components/ina260/ina260.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/ina260/ina260.cpp b/esphome/components/ina260/ina260.cpp index 9dd922cec2..4d6acf400c 100644 --- a/esphome/components/ina260/ina260.cpp +++ b/esphome/components/ina260/ina260.cpp @@ -61,13 +61,13 @@ void INA260Component::setup() { } void INA260Component::dump_config() { - ESP_LOGCONFIG(TAG, "INA260:"); + ESP_LOGCONFIG(TAG, + "INA260:\n" + " Manufacture ID: 0x%x\n" + " Device ID: 0x%x", + this->manufacture_id_, this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_); - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); From 1fccddf67f3ad46bdc45c72df1717cada4fcf4d2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:56 -1000 Subject: [PATCH 41/47] [ina2xx_base] Combine log statements to reduce loop blocking (#12904) --- esphome/components/ina2xx_base/ina2xx_base.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ina2xx_base/ina2xx_base.cpp b/esphome/components/ina2xx_base/ina2xx_base.cpp index 4ab02703e8..7185d21810 100644 --- a/esphome/components/ina2xx_base/ina2xx_base.cpp +++ b/esphome/components/ina2xx_base/ina2xx_base.cpp @@ -364,8 +364,10 @@ bool INA2XX::configure_shunt_() { ESP_LOGW(TAG, "Shunt value too high"); } this->shunt_cal_ &= 0x7FFF; - ESP_LOGV(TAG, "Given Rshunt=%f Ohm and Max_current=%.3f", this->shunt_resistance_ohm_, this->max_current_a_); - ESP_LOGV(TAG, "New CURRENT_LSB=%f, SHUNT_CAL=%u", this->current_lsb_, this->shunt_cal_); + ESP_LOGV(TAG, + "Given Rshunt=%f Ohm and Max_current=%.3f\n" + "New CURRENT_LSB=%f, SHUNT_CAL=%u", + this->shunt_resistance_ohm_, this->max_current_a_, this->current_lsb_, this->shunt_cal_); return this->write_unsigned_16_(RegisterMap::REG_SHUNT_CAL, this->shunt_cal_); } From b0855b4a0e68dc1483c733eaffd2714744b8941b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:53:50 -1000 Subject: [PATCH 42/47] [lc709203f] Combine log statements to reduce loop blocking (#12905) --- esphome/components/lc709203f/lc709203f.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/esphome/components/lc709203f/lc709203f.cpp b/esphome/components/lc709203f/lc709203f.cpp index 7e6ac878f8..ad9d6b3098 100644 --- a/esphome/components/lc709203f/lc709203f.cpp +++ b/esphome/components/lc709203f/lc709203f.cpp @@ -146,19 +146,14 @@ void Lc709203f::update() { } void Lc709203f::dump_config() { - ESP_LOGCONFIG(TAG, "LC709203F:"); - LOG_I2C_DEVICE(this); - - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + "LC709203F:\n" " Pack Size: %d mAH\n" - " Pack APA: 0x%02X", - this->pack_size_, this->apa_); - - // This is only true if the pack_voltage_ is either 0x0000 or 0x0001. The config validator - // should have already verified this. - ESP_LOGCONFIG(TAG, " Pack Rated Voltage: 3.%sV", this->pack_voltage_ == 0x0000 ? "8" : "7"); - + " Pack APA: 0x%02X\n" + " Pack Rated Voltage: 3.%sV", + this->pack_size_, this->apa_, this->pack_voltage_ == 0x0000 ? "8" : "7"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Battery Remaining", this->battery_remaining_sensor_); From ca574a15509c83b68ade2c32aabb437532839be0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:14 -1000 Subject: [PATCH 43/47] [ledc] Combine log statements to reduce loop blocking (#12906) --- esphome/components/ledc/ledc_output.cpp | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index aaa4794586..a203dde115 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -130,8 +130,10 @@ void LEDCOutput::setup() { } int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_); - ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint); + ESP_LOGV(TAG, + "Configured frequency %f with a bit depth of %u bits\n" + "Angle of %.1f° results in hpoint %u", + this->frequency_, this->bit_depth_, this->phase_angle_, hpoint); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = this->pin_->get_pin(); @@ -147,25 +149,30 @@ void LEDCOutput::setup() { } void LEDCOutput::dump_config() { - ESP_LOGCONFIG(TAG, "Output:"); - LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, + "Output:\n" " Channel: %u\n" " PWM Frequency: %.1f Hz\n" " Phase angle: %.1f°\n" " Bit depth: %u", this->channel_, this->frequency_, this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_)); - ESP_LOGV(TAG, " Min frequency for bit depth: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1)); - ESP_LOGV(TAG, " Min frequency for bit depth-1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1)); - ESP_LOGV(TAG, " Min frequency for bit depth+1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS); - ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGV(TAG, + " Max frequency for bit depth: %f\n" + " Min frequency for bit depth: %f\n" + " Max frequency for bit depth-1: %f\n" + " Min frequency for bit depth-1: %f\n" + " Max frequency for bit depth+1: %f\n" + " Min frequency for bit depth+1: %f\n" + " Max res bits: %d\n" + " Clock frequency: %f", + ledc_max_frequency_for_bit_depth(this->bit_depth_), + ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)), MAX_RES_BITS, + CLOCK_FREQUENCY); } void LEDCOutput::update_frequency(float frequency) { From b8d93f2150759a9bbb01de105e215ec3652882ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:31 -1000 Subject: [PATCH 44/47] [mopeka_std_check] Combine log statements to reduce loop blocking (#12911) --- .../components/mopeka_std_check/mopeka_std_check.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 986a9a9fdc..231d09b909 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -17,10 +17,12 @@ static const uint16_t MANUFACTURER_ID = 0x000D; static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32; void MopekaStdCheck::dump_config() { - ESP_LOGCONFIG(TAG, "Mopeka Std Check"); - ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100); - ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_); - ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_); + ESP_LOGCONFIG(TAG, + "Mopeka Std Check\n" + " Propane Butane mix: %.0f%%\n" + " Tank distance empty: %" PRIi32 "mm\n" + " Tank distance full: %" PRIi32 "mm", + this->propane_butane_mix_ * 100, this->empty_mm_, this->full_mm_); LOG_SENSOR(" ", "Level", this->level_); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); From a5368d1d95b5dc4dcf612b087cfb885a1fa80f9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:47 -1000 Subject: [PATCH 45/47] [modbus] Combine log statements to reduce loop blocking (#12910) --- esphome/components/modbus/modbus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 457dff4075..5e9387b843 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -196,12 +196,12 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { } void Modbus::dump_config() { - ESP_LOGCONFIG(TAG, "Modbus:"); - LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); ESP_LOGCONFIG(TAG, + "Modbus:\n" " Send Wait Time: %d ms\n" " CRC Disabled: %s", this->send_wait_time_, YESNO(this->disable_crc_)); + LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); } float Modbus::get_setup_priority() const { // After UART bus From f2308c77c67e9b2f0d4c898435542472603ea983 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:55:18 -1000 Subject: [PATCH 46/47] [libretiny_pwm] Combine log statements to reduce loop blocking (#12907) --- esphome/components/libretiny_pwm/libretiny_pwm.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp index 92e4097c0e..4e4a16d761 100644 --- a/esphome/components/libretiny_pwm/libretiny_pwm.cpp +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -31,9 +31,11 @@ void LibreTinyPWM::setup() { } void LibreTinyPWM::dump_config() { - ESP_LOGCONFIG(TAG, "PWM Output:"); + ESP_LOGCONFIG(TAG, + "PWM Output:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); } void LibreTinyPWM::update_frequency(float frequency) { From 05695affff57afb8bbf00da629dca2d042b71365 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:55:31 -1000 Subject: [PATCH 47/47] [m5stack_8angle] Combine log statements to reduce loop blocking (#12908) --- esphome/components/m5stack_8angle/m5stack_8angle.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.cpp b/esphome/components/m5stack_8angle/m5stack_8angle.cpp index c542b4459e..5a9a5e8c9d 100644 --- a/esphome/components/m5stack_8angle/m5stack_8angle.cpp +++ b/esphome/components/m5stack_8angle/m5stack_8angle.cpp @@ -26,9 +26,11 @@ void M5Stack8AngleComponent::setup() { } void M5Stack8AngleComponent::dump_config() { - ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:"); + ESP_LOGCONFIG(TAG, + "M5STACK_8ANGLE:\n" + " Firmware version: %d", + this->fw_version_); LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Firmware version: %d ", this->fw_version_); } float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) {