From 49a46883ed5895cb76641128aae2bd66a7f247b4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:24:24 +1200 Subject: [PATCH 1/9] [core] Update core component codeowners to ``@esphome/core`` (#10082) --- CODEOWNERS | 14 +++++++------- esphome/components/api/__init__.py | 2 +- esphome/components/async_tcp/__init__.py | 2 +- esphome/components/captive_portal/__init__.py | 2 +- esphome/components/debug/__init__.py | 2 +- esphome/components/json/__init__.py | 2 +- esphome/components/time/__init__.py | 2 +- esphome/components/web_server_base/__init__.py | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index e40be9a737..708bfaa0e8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,11 +40,11 @@ esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/apds9306/* @aodrenah -esphome/components/api/* @OttoWinter +esphome/components/api/* @esphome/core esphome/components/as5600/* @ammmze esphome/components/as5600/sensor/* @ammmze esphome/components/as7341/* @mrgnr -esphome/components/async_tcp/* @OttoWinter +esphome/components/async_tcp/* @esphome/core esphome/components/at581x/* @X-Ryl669 esphome/components/atc_mithermometer/* @ahpohl esphome/components/atm90e26/* @danieltwagner @@ -91,7 +91,7 @@ esphome/components/bytebuffer/* @clydebarrow esphome/components/camera/* @DT-art1 @bdraco esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 -esphome/components/captive_portal/* @OttoWinter +esphome/components/captive_portal/* @esphome/core esphome/components/ccs811/* @habbie esphome/components/cd74hc4067/* @asoehlke esphome/components/ch422g/* @clydebarrow @jesterret @@ -118,7 +118,7 @@ esphome/components/dallas_temp/* @ssieb esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/datetime/* @jesserockz @rfdarter -esphome/components/debug/* @OttoWinter +esphome/components/debug/* @esphome/core esphome/components/delonghi/* @grob6000 esphome/components/dfplayer/* @glmnet esphome/components/dfrobot_sen0395/* @niklasweber @@ -237,7 +237,7 @@ esphome/components/integration/* @OttoWinter esphome/components/internal_temperature/* @Mat931 esphome/components/interval/* @esphome/core esphome/components/jsn_sr04t/* @Mafus1 -esphome/components/json/* @OttoWinter +esphome/components/json/* @esphome/core esphome/components/kamstrup_kmp/* @cfeenstra1024 esphome/components/key_collector/* @ssieb esphome/components/key_provider/* @ssieb @@ -467,7 +467,7 @@ esphome/components/template/event/* @nohat esphome/components/template/fan/* @ssieb esphome/components/text/* @mauritskorse esphome/components/thermostat/* @kbx81 -esphome/components/time/* @OttoWinter +esphome/components/time/* @esphome/core esphome/components/tlc5947/* @rnauber esphome/components/tlc5971/* @IJIJI esphome/components/tm1621/* @Philippe12 @@ -511,7 +511,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/watchdog/* @oarcher esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server/ota/* @esphome/core -esphome/components/web_server_base/* @OttoWinter +esphome/components/web_server_base/* @esphome/core esphome/components/web_server_idf/* @dentra esphome/components/weikai/* @DrCoolZic esphome/components/weikai_i2c/* @DrCoolZic diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 5d398a4e23..ba5f994e9a 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -29,7 +29,7 @@ from esphome.core import CORE, coroutine_with_priority DOMAIN = "api" DEPENDENCIES = ["network"] AUTO_LOAD = ["socket"] -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] api_ns = cg.esphome_ns.namespace("api") APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 4a469fa0e0..942d5bc8e5 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -10,7 +10,7 @@ from esphome.const import ( ) from esphome.core import CORE, coroutine_with_priority -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] CONFIG_SCHEMA = cv.All( cv.Schema({}), diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 7e8afd8fab..cd69b67c78 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -14,7 +14,7 @@ from esphome.core import CORE, coroutine_with_priority AUTO_LOAD = ["web_server_base", "ota.web_server"] DEPENDENCIES = ["wifi"] -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] captive_portal_ns = cg.esphome_ns.namespace("captive_portal") CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index b8dabc3374..9dffe00bf4 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -13,7 +13,7 @@ from esphome.const import ( ) from esphome.core import CORE -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger"] CONF_DEBUG_ID = "debug_id" diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 9773bf67ce..87aa823c0d 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import coroutine_with_priority -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] json_ns = cg.esphome_ns.namespace("json") CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 63d4ba17f2..a38ad4eae3 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -30,7 +30,7 @@ from esphome.core import CORE, coroutine_with_priority _LOGGER = logging.getLogger(__name__) -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True time_ns = cg.esphome_ns.namespace("time") diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 9f3371c233..50ae6b92fa 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.const import CONF_ID from esphome.core import CORE, coroutine_with_priority -CODEOWNERS = ["@OttoWinter"] +CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] From 58a088e06bd1343ff997cd3055da2e70e1f68d2c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Aug 2025 23:00:04 -1000 Subject: [PATCH 2/9] Add myself to multiple bluetooth codeowners (#10083) --- CODEOWNERS | 7 ++++--- esphome/components/bluetooth_proxy/__init__.py | 2 +- esphome/components/esp32_ble/__init__.py | 2 +- esphome/components/esp32_ble_client/__init__.py | 2 +- esphome/components/esp32_ble_tracker/__init__.py | 1 + 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 708bfaa0e8..509b0c0b9e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -69,7 +69,7 @@ esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/bl0942/* @dbuezas @dwmw2 esphome/components/ble_client/* @buxtronix @clydebarrow -esphome/components/bluetooth_proxy/* @jesserockz +esphome/components/bluetooth_proxy/* @bdraco @jesserockz esphome/components/bme280_base/* @esphome/core esphome/components/bme280_spi/* @apbodrov esphome/components/bme680_bsec/* @trvrnrth @@ -144,9 +144,10 @@ esphome/components/es8156/* @kbx81 esphome/components/es8311/* @kahrendt @kroimon esphome/components/es8388/* @P4uLT esphome/components/esp32/* @esphome/core -esphome/components/esp32_ble/* @Rapsssito @jesserockz -esphome/components/esp32_ble_client/* @jesserockz +esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz +esphome/components/esp32_ble_client/* @bdraco @jesserockz esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz +esphome/components/esp32_ble_tracker/* @bdraco esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron esphome/components/esp32_hosted/* @swoboda1337 diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index 4087255410..fb7f7a37c0 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -11,7 +11,7 @@ from esphome.log import AnsiFore, color AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] DEPENDENCIES = ["api", "esp32"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@bdraco"] _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 1c7c075cfa..f208fda34c 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -11,7 +11,7 @@ from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX import esphome.final_validate as fv DEPENDENCIES = ["esp32"] -CODEOWNERS = ["@jesserockz", "@Rapsssito"] +CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] class BTLoggers(Enum): diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py index 25957ed0da..55619f1fc0 100644 --- a/esphome/components/esp32_ble_client/__init__.py +++ b/esphome/components/esp32_ble_client/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components import esp32_ble_tracker AUTO_LOAD = ["esp32_ble_tracker"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@bdraco"] DEPENDENCIES = ["esp32"] esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client") diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 9daa6ee34e..e1abdd8490 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -36,6 +36,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] +CODEOWNERS = ["@bdraco"] KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker" KEY_USED_CONNECTION_SLOTS = "used_connection_slots" From d443a97dd8dbe0fc79d363cd9bee8d8c42484388 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 5 Aug 2025 19:55:40 +0100 Subject: [PATCH 3/9] [speaker] Media player fixes for IDF5.4 (#10088) --- .../speaker/media_player/__init__.py | 38 +++++++------------ .../speaker/media_player/audio_pipeline.cpp | 11 ++---- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 1c2e7dc0e1..3ae7b980d3 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -315,31 +315,19 @@ async def to_code(config): cg.add_define("USE_AUDIO_FLAC_SUPPORT", True) cg.add_define("USE_AUDIO_MP3_SUPPORT", True) - # Wifi settings based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702 - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM", 16) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM", 512) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_STATIC_TX_BUFFER", True) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_TX_BUFFER_TYPE", 0) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM", 8) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM", 32) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED", True) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_TX_BA_WIN", 16) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED", True) - esp32.add_idf_sdkconfig_option("CONFIG_ESP32_WIFI_RX_BA_WIN", 32) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_MAX_ACTIVE_TCP", 16) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_MAX_LISTENING_TCP", 16) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_MAXRTX", 12) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_SYNMAXRTX", 6) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_MSS", 1436) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_MSL", 60000) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_SND_BUF_DEFAULT", 65535) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_WND_DEFAULT", 512000) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_RECVMBOX_SIZE", 512) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_QUEUE_OOSEQ", True) - esp32.add_idf_sdkconfig_option("CONFIG_TCP_OVERSIZE_MSS", True) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_WND_SCALE", True) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RCV_SCALE", 3) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 512) + # Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32 + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32) + + esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534) + esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534) + esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64) + esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64) # Allocate wifi buffers in PSRAM esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index 8811ea1644..dc8572ae43 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -259,13 +259,10 @@ esp_err_t AudioPipeline::allocate_communications_() { esp_err_t AudioPipeline::start_tasks_() { if (this->read_task_handle_ == nullptr) { if (this->read_task_stack_buffer_ == nullptr) { - if (this->task_stack_in_psram_) { - RAMAllocator stack_allocator(RAMAllocator::ALLOC_EXTERNAL); - this->read_task_stack_buffer_ = stack_allocator.allocate(READ_TASK_STACK_SIZE); - } else { - RAMAllocator stack_allocator(RAMAllocator::ALLOC_INTERNAL); - this->read_task_stack_buffer_ = stack_allocator.allocate(READ_TASK_STACK_SIZE); - } + // Reader task uses the AudioReader class which uses esp_http_client. This crashes on IDF 5.4 if the task stack is + // in PSRAM. As a workaround, always allocate the read task in internal memory. + RAMAllocator stack_allocator(RAMAllocator::ALLOC_INTERNAL); + this->read_task_stack_buffer_ = stack_allocator.allocate(READ_TASK_STACK_SIZE); } if (this->read_task_stack_buffer_ == nullptr) { From bd2b3b9da52923141cb9521239125d54b7c34b57 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Tue, 5 Aug 2025 21:46:40 +0200 Subject: [PATCH 4/9] [espnow] Small changes and fixes (#10014) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/espnow/__init__.py | 11 ------ esphome/components/espnow/automation.h | 12 +++---- .../components/espnow/espnow_component.cpp | 34 +++++++++---------- esphome/components/espnow/espnow_component.h | 1 + 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index d15817cf92..9d2f17440c 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -65,15 +65,6 @@ CONF_WAIT_FOR_SENT = "wait_for_sent" MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes -def _validate_unknown_peer(config): - if config[CONF_AUTO_ADD_PEER] and config.get(CONF_ON_UNKNOWN_PEER): - raise cv.Invalid( - f"'{CONF_ON_UNKNOWN_PEER}' cannot be used when '{CONF_AUTO_ADD_PEER}' is enabled.", - path=[CONF_ON_UNKNOWN_PEER], - ) - return config - - CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -103,7 +94,6 @@ CONFIG_SCHEMA = cv.All( }, ).extend(cv.COMPONENT_SCHEMA), cv.only_on_esp32, - _validate_unknown_peer, ) @@ -124,7 +114,6 @@ async def _trigger_to_code(config): async def to_code(config): - print(config) var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/espnow/automation.h b/esphome/components/espnow/automation.h index ad534b279a..2416377859 100644 --- a/esphome/components/espnow/automation.h +++ b/esphome/components/espnow/automation.h @@ -40,20 +40,20 @@ template class SendAction : public Action, public Parente this->num_running_++; send_callback_t send_callback = [this, x...](esp_err_t status) { if (status == ESP_OK) { - if (this->sent_.empty() && this->flags_.wait_for_sent) { - this->play_next_(x...); - } else if (!this->sent_.empty()) { + if (!this->sent_.empty()) { this->sent_.play(x...); + } else if (this->flags_.wait_for_sent) { + this->play_next_(x...); } } else { - if (this->error_.empty() && this->flags_.wait_for_sent) { + if (!this->error_.empty()) { + this->error_.play(x...); + } else if (this->flags_.wait_for_sent) { if (this->flags_.continue_on_error) { this->play_next_(x...); } else { this->stop_complex(); } - } else if (!this->error_.empty()) { - this->error_.play(x...); } } }; diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index dab8e2b726..82f8e3230e 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -154,7 +154,7 @@ void ESPNowComponent::setup() { } void ESPNowComponent::enable() { - if (this->state_ != ESPNOW_STATE_ENABLED) + if (this->state_ == ESPNOW_STATE_ENABLED) return; ESP_LOGD(TAG, "Enabling"); @@ -178,11 +178,7 @@ void ESPNowComponent::enable_() { this->apply_wifi_channel(); } -#ifdef USE_WIFI - else { - this->wifi_channel_ = wifi::global_wifi_component->get_wifi_channel(); - } -#endif + this->get_wifi_channel(); esp_err_t err = esp_now_init(); if (err != ESP_OK) { @@ -215,6 +211,7 @@ void ESPNowComponent::enable_() { for (auto peer : this->peers_) { this->add_peer(peer.address); } + this->state_ = ESPNOW_STATE_ENABLED; } @@ -228,10 +225,6 @@ void ESPNowComponent::disable() { esp_now_unregister_recv_cb(); esp_now_unregister_send_cb(); - for (auto peer : this->peers_) { - this->del_peer(peer.address); - } - esp_err_t err = esp_now_deinit(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err); @@ -267,7 +260,6 @@ void ESPNowComponent::loop() { } } #endif - // Process received packets ESPNowPacket *packet = this->receive_packet_queue_.pop(); while (packet != nullptr) { @@ -275,14 +267,16 @@ void ESPNowComponent::loop() { case ESPNowPacket::RECEIVED: { const ESPNowRecvInfo info = packet->get_receive_info(); if (!esp_now_is_peer_exist(info.src_addr)) { - if (this->auto_add_peer_) { - this->add_peer(info.src_addr); - } else { - for (auto *handler : this->unknown_peer_handlers_) { - if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) - break; // If a handler returns true, stop processing further handlers + bool handled = false; + for (auto *handler : this->unknown_peer_handlers_) { + if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) { + handled = true; + break; // If a handler returns true, stop processing further handlers } } + if (!handled && this->auto_add_peer_) { + this->add_peer(info.src_addr); + } } // Intentionally left as if instead of else in case the peer is added above if (esp_now_is_peer_exist(info.src_addr)) { @@ -343,6 +337,12 @@ void ESPNowComponent::loop() { } } +uint8_t ESPNowComponent::get_wifi_channel() { + wifi_second_chan_t dummy; + esp_wifi_get_channel(&this->wifi_channel_, &dummy); + return this->wifi_channel_; +} + esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size, const send_callback_t &callback) { if (this->state_ != ESPNOW_STATE_ENABLED) { diff --git a/esphome/components/espnow/espnow_component.h b/esphome/components/espnow/espnow_component.h index 3a523d1f7e..9941e97227 100644 --- a/esphome/components/espnow/espnow_component.h +++ b/esphome/components/espnow/espnow_component.h @@ -110,6 +110,7 @@ class ESPNowComponent : public Component { void set_wifi_channel(uint8_t channel) { this->wifi_channel_ = channel; } void apply_wifi_channel(); + uint8_t get_wifi_channel(); void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; } From c308e03e921f7a7dbbb98cdb763f94f50d5e78e8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:09:36 +1200 Subject: [PATCH 5/9] [select] Fix new_select() not forwarding constructor args while preserving keyword-only options parameter (#10036) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com> --- esphome/components/select/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index dd3feccab5..756e98c906 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -118,8 +118,8 @@ async def register_select(var, config, *, options: list[str]): await setup_select_core_(var, config, options=options) -async def new_select(config, *, options: list[str]): - var = cg.new_Pvariable(config[CONF_ID]) +async def new_select(config, *args, options: list[str]): + var = cg.new_Pvariable(config[CONF_ID], *args) await register_select(var, config, options=options) return var From 3edd746c6c6a089daf3b1fe04d9fde1acf44c698 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:01:57 +1200 Subject: [PATCH 6/9] [mcp23xxx] Use CachedGpioExpander (#10078) --- esphome/components/mcp23008/__init__.py | 2 +- esphome/components/mcp23017/__init__.py | 2 +- esphome/components/mcp23s08/__init__.py | 2 +- esphome/components/mcp23s17/__init__.py | 2 +- esphome/components/mcp23x08_base/__init__.py | 2 ++ .../mcp23x08_base/mcp23x08_base.cpp | 16 ++++++----- .../components/mcp23x08_base/mcp23x08_base.h | 13 ++++++--- esphome/components/mcp23x17_base/__init__.py | 2 ++ .../mcp23x17_base/mcp23x17_base.cpp | 27 ++++++++++++++----- .../components/mcp23x17_base/mcp23x17_base.h | 13 ++++++--- esphome/components/mcp23xxx_base/__init__.py | 16 +++++++---- .../mcp23xxx_base/mcp23xxx_base.cpp | 27 ++++++++++--------- .../components/mcp23xxx_base/mcp23xxx_base.h | 15 ++++++----- 13 files changed, 89 insertions(+), 50 deletions(-) diff --git a/esphome/components/mcp23008/__init__.py b/esphome/components/mcp23008/__init__.py index ed48eb06a6..8ff938114a 100644 --- a/esphome/components/mcp23008/__init__.py +++ b/esphome/components/mcp23008/__init__.py @@ -24,5 +24,5 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = await mcp23xxx_base.register_mcp23xxx(config) + var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x08_base.NUM_PINS) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp23017/__init__.py b/esphome/components/mcp23017/__init__.py index 33b8a680cf..e5cc1856eb 100644 --- a/esphome/components/mcp23017/__init__.py +++ b/esphome/components/mcp23017/__init__.py @@ -24,5 +24,5 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = await mcp23xxx_base.register_mcp23xxx(config) + var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x17_base.NUM_PINS) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp23s08/__init__.py b/esphome/components/mcp23s08/__init__.py index c6152d58c0..3d4e304f9b 100644 --- a/esphome/components/mcp23s08/__init__.py +++ b/esphome/components/mcp23s08/__init__.py @@ -27,6 +27,6 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = await mcp23xxx_base.register_mcp23xxx(config) + var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x08_base.NUM_PINS) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp23s17/__init__.py b/esphome/components/mcp23s17/__init__.py index 9a763d09b0..ea8433af2e 100644 --- a/esphome/components/mcp23s17/__init__.py +++ b/esphome/components/mcp23s17/__init__.py @@ -27,6 +27,6 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = await mcp23xxx_base.register_mcp23xxx(config) + var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x17_base.NUM_PINS) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp23x08_base/__init__.py b/esphome/components/mcp23x08_base/__init__.py index ba44917202..a3c12165f0 100644 --- a/esphome/components/mcp23x08_base/__init__.py +++ b/esphome/components/mcp23x08_base/__init__.py @@ -4,5 +4,7 @@ from esphome.components import mcp23xxx_base AUTO_LOAD = ["mcp23xxx_base"] CODEOWNERS = ["@jesserockz"] +NUM_PINS = 8 + mcp23x08_base_ns = cg.esphome_ns.namespace("mcp23x08_base") MCP23X08Base = mcp23x08_base_ns.class_("MCP23X08Base", mcp23xxx_base.MCP23XXXBase) diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 0c20e902c4..e4fb51174b 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -6,19 +6,21 @@ namespace mcp23x08_base { static const char *const TAG = "mcp23x08_base"; -bool MCP23X08Base::digital_read(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = mcp23x08_base::MCP23X08_GPIO; - uint8_t value = 0; - this->read_reg(reg_addr, &value); - return value & (1 << bit); +bool MCP23X08Base::digital_read_hw(uint8_t pin) { + if (!this->read_reg(mcp23x08_base::MCP23X08_GPIO, &this->input_mask_)) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return false; + } + return true; } -void MCP23X08Base::digital_write(uint8_t pin, bool value) { +void MCP23X08Base::digital_write_hw(uint8_t pin, bool value) { uint8_t reg_addr = mcp23x08_base::MCP23X08_OLAT; this->update_reg(pin, value, reg_addr); } +bool MCP23X08Base::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); } + void MCP23X08Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = mcp23x08_base::MCP23X08_IODIR; uint8_t gppu = mcp23x08_base::MCP23X08_GPPU; diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.h b/esphome/components/mcp23x08_base/mcp23x08_base.h index 910519119b..6eee8274b1 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.h +++ b/esphome/components/mcp23x08_base/mcp23x08_base.h @@ -1,7 +1,7 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" +#include "esphome/core/component.h" #include "esphome/core/hal.h" namespace esphome { @@ -22,10 +22,12 @@ enum MCP23S08GPIORegisters { MCP23X08_OLAT = 0x0A, }; -class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase { +class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase<8> { public: - bool digital_read(uint8_t pin) override; - void digital_write(uint8_t pin, bool value) override; + bool digital_read_hw(uint8_t pin) override; + void digital_write_hw(uint8_t pin, bool value) override; + bool digital_read_cache(uint8_t pin) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; @@ -33,6 +35,9 @@ class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase { void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a) override; uint8_t olat_{0x00}; + + /// State read in digital_read_hw + uint8_t input_mask_{0x00}; }; } // namespace mcp23x08_base diff --git a/esphome/components/mcp23x17_base/__init__.py b/esphome/components/mcp23x17_base/__init__.py index 97e0b3823d..1b93d16ff3 100644 --- a/esphome/components/mcp23x17_base/__init__.py +++ b/esphome/components/mcp23x17_base/__init__.py @@ -4,5 +4,7 @@ from esphome.components import mcp23xxx_base AUTO_LOAD = ["mcp23xxx_base"] CODEOWNERS = ["@jesserockz"] +NUM_PINS = 16 + mcp23x17_base_ns = cg.esphome_ns.namespace("mcp23x17_base") MCP23X17Base = mcp23x17_base_ns.class_("MCP23X17Base", mcp23xxx_base.MCP23XXXBase) diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 99064f8880..020b8a5ddf 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -1,4 +1,5 @@ #include "mcp23x17_base.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -6,19 +7,31 @@ namespace mcp23x17_base { static const char *const TAG = "mcp23x17_base"; -bool MCP23X17Base::digital_read(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = pin < 8 ? mcp23x17_base::MCP23X17_GPIOA : mcp23x17_base::MCP23X17_GPIOB; - uint8_t value = 0; - this->read_reg(reg_addr, &value); - return value & (1 << bit); +bool MCP23X17Base::digital_read_hw(uint8_t pin) { + uint8_t data; + if (pin < 8) { + if (!this->read_reg(mcp23x17_base::MCP23X17_GPIOA, &data)) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return false; + } + this->input_mask_ = encode_uint16(this->input_mask_ >> 8, data); + } else { + if (!this->read_reg(mcp23x17_base::MCP23X17_GPIOB, &data)) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return false; + } + this->input_mask_ = encode_uint16(data, this->input_mask_ & 0xFF); + } + return true; } -void MCP23X17Base::digital_write(uint8_t pin, bool value) { +void MCP23X17Base::digital_write_hw(uint8_t pin, bool value) { uint8_t reg_addr = pin < 8 ? mcp23x17_base::MCP23X17_OLATA : mcp23x17_base::MCP23X17_OLATB; this->update_reg(pin, value, reg_addr); } +bool MCP23X17Base::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); } + void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = pin < 8 ? mcp23x17_base::MCP23X17_IODIRA : mcp23x17_base::MCP23X17_IODIRB; uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.h b/esphome/components/mcp23x17_base/mcp23x17_base.h index 3d50ee8c03..bdd66503e2 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.h +++ b/esphome/components/mcp23x17_base/mcp23x17_base.h @@ -1,7 +1,7 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" +#include "esphome/core/component.h" #include "esphome/core/hal.h" namespace esphome { @@ -34,10 +34,12 @@ enum MCP23X17GPIORegisters { MCP23X17_OLATB = 0x15, }; -class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase { +class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase<16> { public: - bool digital_read(uint8_t pin) override; - void digital_write(uint8_t pin, bool value) override; + bool digital_read_hw(uint8_t pin) override; + void digital_write_hw(uint8_t pin, bool value) override; + bool digital_read_cache(uint8_t pin) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; @@ -46,6 +48,9 @@ class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase { uint8_t olat_a_{0x00}; uint8_t olat_b_{0x00}; + + /// State read in digital_read_hw + uint16_t input_mask_{0x00}; }; } // namespace mcp23x17_base diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index 8cf0ebcd44..d6e82101ad 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -12,8 +12,9 @@ from esphome.const import ( CONF_OUTPUT, CONF_PULLUP, ) -from esphome.core import coroutine +from esphome.core import CORE, ID, coroutine +AUTO_LOAD = ["gpio_expander"] CODEOWNERS = ["@jesserockz"] mcp23xxx_base_ns = cg.esphome_ns.namespace("mcp23xxx_base") @@ -36,9 +37,11 @@ MCP23XXX_CONFIG_SCHEMA = cv.Schema( @coroutine -async def register_mcp23xxx(config): - var = cg.new_Pvariable(config[CONF_ID]) +async def register_mcp23xxx(config, num_pins): + id: ID = config[CONF_ID] + var = cg.new_Pvariable(id) await cg.register_component(var, config) + CORE.data.setdefault(CONF_MCP23XXX, {})[id.id] = num_pins cg.add(var.set_open_drain_ints(config[CONF_OPEN_DRAIN_INTERRUPT])) return var @@ -73,9 +76,12 @@ MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23XXX, MCP23XXX_PIN_SCHEMA) async def mcp23xxx_pin_to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_MCP23XXX]) + parent_id: ID = config[CONF_MCP23XXX] + parent = await cg.get_variable(parent_id) + num_pins = cg.TemplateArguments(CORE.data[CONF_MCP23XXX][parent_id.id]) + + var = cg.new_Pvariable(config[CONF_ID], num_pins) cg.add(var.set_parent(parent)) num = config[CONF_NUMBER] diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index fc49f216ee..81324e794f 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -1,24 +1,27 @@ #include "mcp23xxx_base.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace mcp23xxx_base { -float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } - -void MCP23XXXGPIOPin::setup() { - pin_mode(flags_); +template void MCP23XXXGPIOPin::setup() { + this->pin_mode(flags_); this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); } - -void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string MCP23XXXGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via MCP23XXX", pin_); - return buffer; +template void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } +template bool MCP23XXXGPIOPin::digital_read() { + return this->parent_->digital_read(this->pin_) != this->inverted_; } +template void MCP23XXXGPIOPin::digital_write(bool value) { + this->parent_->digital_write(this->pin_, value != this->inverted_); +} +template std::string MCP23XXXGPIOPin::dump_summary() const { + return str_snprintf("%u via MCP23XXX", 15, pin_); +} + +template class MCP23XXXGPIOPin<8>; +template class MCP23XXXGPIOPin<16>; } // namespace mcp23xxx_base } // namespace esphome diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index 9686c9fd33..ab7f8ec398 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/components/gpio_expander/cached_gpio.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -8,15 +9,15 @@ namespace mcp23xxx_base { enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING }; -class MCP23XXXBase : public Component { +template class MCP23XXXBase : public Component, public gpio_expander::CachedGpioExpander { public: - virtual bool digital_read(uint8_t pin); - virtual void digital_write(uint8_t pin, bool value); virtual void pin_mode(uint8_t pin, gpio::Flags flags); virtual void pin_interrupt_mode(uint8_t pin, MCP23XXXInterruptMode interrupt_mode); void set_open_drain_ints(const bool value) { this->open_drain_ints_ = value; } - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::IO; } + + void loop() override { this->reset_pin_cache_(); } protected: // read a given register @@ -29,7 +30,7 @@ class MCP23XXXBase : public Component { bool open_drain_ints_; }; -class MCP23XXXGPIOPin : public GPIOPin { +template class MCP23XXXGPIOPin : public GPIOPin { public: void setup() override; void pin_mode(gpio::Flags flags) override; @@ -37,7 +38,7 @@ class MCP23XXXGPIOPin : public GPIOPin { void digital_write(bool value) override; std::string dump_summary() const override; - void set_parent(MCP23XXXBase *parent) { parent_ = parent; } + void set_parent(MCP23XXXBase *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } void set_flags(gpio::Flags flags) { flags_ = flags; } @@ -46,7 +47,7 @@ class MCP23XXXGPIOPin : public GPIOPin { gpio::Flags get_flags() const override { return this->flags_; } protected: - MCP23XXXBase *parent_; + MCP23XXXBase *parent_; uint8_t pin_; bool inverted_; gpio::Flags flags_; From 96bbb58f342c9253724b4ad0e2afe4405743f1e8 Mon Sep 17 00:00:00 2001 From: "@RubenKelevra" Date: Wed, 6 Aug 2025 02:33:15 +0200 Subject: [PATCH 7/9] update espressif's esp32-camera library to 2.1.1 (#10090) --- esphome/components/esp32_camera/__init__.py | 2 +- esphome/idf_component.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index bfb66ff83a..6206fe4682 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -345,7 +345,7 @@ async def to_code(config): cg.add_define("USE_CAMERA") if CORE.using_esp_idf: - add_idf_component(name="espressif/esp32-camera", ref="2.1.0") + add_idf_component(name="espressif/esp32-camera", ref="2.1.1") for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 419a9797e3..687efd2b49 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -2,7 +2,7 @@ dependencies: espressif/esp-tflite-micro: version: 1.3.3~1 espressif/esp32-camera: - version: 2.1.0 + version: 2.1.1 espressif/mdns: version: 1.8.2 espressif/esp_wifi_remote: From cfb22e33c948d57be518a4ecb0bd43f4f2705ad8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Aug 2025 16:22:32 -1000 Subject: [PATCH 8/9] [esp32_ble_tracker] Add missing USE_ESP32_BLE_DEVICE guard for already_discovered_ member (#10085) --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 4 ++++ esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 856ae82dca..6180c53ad8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -241,7 +241,9 @@ void ESP32BLETracker::start_scan_(bool first) { for (auto *listener : this->listeners_) listener->on_scan_end(); } +#ifdef USE_ESP32_BLE_DEVICE this->already_discovered_.clear(); +#endif this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; @@ -839,7 +841,9 @@ bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) { void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); +#ifdef USE_ESP32_BLE_DEVICE this->already_discovered_.clear(); +#endif this->cancel_timeout("scan"); for (auto *listener : this->listeners_) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 1c28bc7a7d..b46c88b4de 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -282,8 +282,10 @@ class ESP32BLETracker : public Component, uint8_t app_id_{0}; +#ifdef USE_ESP32_BLE_DEVICE /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; +#endif std::vector listeners_; /// Client parameters. std::vector clients_; From b01f03cc24c5b20006d8956f61d86745c5463909 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Aug 2025 16:26:11 -1000 Subject: [PATCH 9/9] [esp32_ble_tracker] Refactor loop() method for improved readability and performance (#10074) --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 157 +++++++++--------- .../esp32_ble_tracker/esp32_ble_tracker.h | 51 +++++- 2 files changed, 122 insertions(+), 86 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 6180c53ad8..02f24e9286 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -76,58 +76,17 @@ void ESP32BLETracker::loop() { this->start_scan(); } } - int connecting = 0; - int discovered = 0; - int searching = 0; - int disconnecting = 0; - for (auto *client : this->clients_) { - switch (client->state()) { - case ClientState::DISCONNECTING: - disconnecting++; - break; - case ClientState::DISCOVERED: - discovered++; - break; - case ClientState::SEARCHING: - searching++; - break; - case ClientState::CONNECTING: - case ClientState::READY_TO_CONNECT: - connecting++; - break; - default: - break; - } + ClientStateCounts counts = this->count_client_states_(); + if (counts != this->client_state_counts_) { + this->client_state_counts_ = counts; + ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", + this->client_state_counts_.connecting, this->client_state_counts_.discovered, + this->client_state_counts_.searching, this->client_state_counts_.disconnecting); } - if (connecting != connecting_ || discovered != discovered_ || searching != searching_ || - disconnecting != disconnecting_) { - connecting_ = connecting; - discovered_ = discovered; - searching_ = searching; - disconnecting_ = disconnecting; - ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, - searching_, disconnecting_); - } - bool promote_to_connecting = discovered && !searching && !connecting; - // All scan result processing is now done immediately in gap_scan_event_handler - // No ring buffer processing needed here if (this->scanner_state_ == ScannerState::FAILED || (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) { - this->stop_scan_(); - if (this->scan_start_fail_count_ == std::numeric_limits::max()) { - ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)", - std::numeric_limits::max()); - App.reboot(); - } - if (this->scan_start_failed_) { - ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_); - this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS; - } - if (this->scan_set_param_failed_) { - ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_); - this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; - } + this->handle_scanner_failure_(); } /* @@ -142,13 +101,12 @@ void ESP32BLETracker::loop() { https://github.com/espressif/esp-idf/issues/6688 */ - if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) { + bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting; + + if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && + !promote_to_connecting) { #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE - if (this->coex_prefer_ble_) { - this->coex_prefer_ble_ = false; - ESP_LOGD(TAG, "Setting coexistence preference to balanced."); - esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default - } + this->update_coex_preference_(false); #endif if (this->scan_continuous_) { this->start_scan_(false); // first = false @@ -157,34 +115,12 @@ void ESP32BLETracker::loop() { // If there is a discovered client and no connecting // clients and no clients using the scanner to search for // devices, then promote the discovered client to ready to connect. - // Note: Scanning is already stopped by gap_scan_event_handler when - // a discovered client is found, so we only need to handle promotion - // when the scanner is IDLE. + // We check both RUNNING and IDLE states because: + // - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately + // - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler) if (promote_to_connecting && (this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) { - for (auto *client : this->clients_) { - if (client->state() == ClientState::DISCOVERED) { - if (this->scanner_state_ == ScannerState::RUNNING) { - ESP_LOGD(TAG, "Stopping scan to make connection"); - this->stop_scan_(); - // Don't wait for scan stop complete - promote immediately. - // This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue. - // This guarantees that the stop scan command will be fully processed before any subsequent connect command, - // preventing race conditions or overlapping operations. - } - - ESP_LOGD(TAG, "Promoting client to connect"); -#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE - ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection."); - if (!this->coex_prefer_ble_) { - this->coex_prefer_ble_ = true; - esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth - } -#endif - client->set_state(ClientState::READY_TO_CONNECT); - break; - } - } + this->try_promote_discovered_clients_(); } } @@ -701,8 +637,9 @@ void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, " Scanner State: FAILED"); break; } - ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, - searching_, disconnecting_); + ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", + this->client_state_counts_.connecting, this->client_state_counts_.discovered, + this->client_state_counts_.searching, this->client_state_counts_.disconnecting); if (this->scan_start_fail_count_) { ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_); } @@ -852,6 +789,62 @@ void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { this->set_scanner_state_(ScannerState::IDLE); } +void ESP32BLETracker::handle_scanner_failure_() { + this->stop_scan_(); + if (this->scan_start_fail_count_ == std::numeric_limits::max()) { + ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)", + std::numeric_limits::max()); + App.reboot(); + } + if (this->scan_start_failed_) { + ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_); + this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS; + } + if (this->scan_set_param_failed_) { + ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_); + this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; + } +} + +void ESP32BLETracker::try_promote_discovered_clients_() { + // Only promote the first discovered client to avoid multiple simultaneous connections + for (auto *client : this->clients_) { + if (client->state() != ClientState::DISCOVERED) { + continue; + } + + if (this->scanner_state_ == ScannerState::RUNNING) { + ESP_LOGD(TAG, "Stopping scan to make connection"); + this->stop_scan_(); + // Don't wait for scan stop complete - promote immediately. + // This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue. + // This guarantees that the stop scan command will be fully processed before any subsequent connect command, + // preventing race conditions or overlapping operations. + } + + ESP_LOGD(TAG, "Promoting client to connect"); +#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE + this->update_coex_preference_(true); +#endif + client->set_state(ClientState::READY_TO_CONNECT); + break; + } +} + +#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE +void ESP32BLETracker::update_coex_preference_(bool force_ble) { + if (force_ble && !this->coex_prefer_ble_) { + ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection."); + this->coex_prefer_ble_ = true; + esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth + } else if (!force_ble && this->coex_prefer_ble_) { + ESP_LOGD(TAG, "Setting coexistence preference to balanced."); + this->coex_prefer_ble_ = false; + esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default + } +} +#endif + } // namespace esphome::esp32_ble_tracker #endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index b46c88b4de..4d318b4cf6 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -136,6 +136,20 @@ class ESPBTDeviceListener { ESP32BLETracker *parent_{nullptr}; }; +struct ClientStateCounts { + uint8_t connecting = 0; + uint8_t discovered = 0; + uint8_t searching = 0; + uint8_t disconnecting = 0; + + bool operator==(const ClientStateCounts &other) const { + return connecting == other.connecting && discovered == other.discovered && searching == other.searching && + disconnecting == other.disconnecting; + } + + bool operator!=(const ClientStateCounts &other) const { return !(*this == other); } +}; + enum class ClientState : uint8_t { // Connection is allocated INIT, @@ -279,6 +293,38 @@ class ESP32BLETracker : public Component, /// Check if any clients are in connecting or ready to connect state bool has_connecting_clients_() const; #endif + /// Handle scanner failure states + void handle_scanner_failure_(); + /// Try to promote discovered clients to ready to connect + void try_promote_discovered_clients_(); +#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE + /// Update BLE coexistence preference + void update_coex_preference_(bool force_ble); +#endif + /// Count clients in each state + ClientStateCounts count_client_states_() const { + ClientStateCounts counts; + for (auto *client : this->clients_) { + switch (client->state()) { + case ClientState::DISCONNECTING: + counts.disconnecting++; + break; + case ClientState::DISCOVERED: + counts.discovered++; + break; + case ClientState::SEARCHING: + counts.searching++; + break; + case ClientState::CONNECTING: + case ClientState::READY_TO_CONNECT: + counts.connecting++; + break; + default: + break; + } + } + return counts; + } uint8_t app_id_{0}; @@ -306,10 +352,7 @@ class ESP32BLETracker : public Component, esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; - int connecting_{0}; - int discovered_{0}; - int searching_{0}; - int disconnecting_{0}; + ClientStateCounts client_state_counts_; #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE bool coex_prefer_ble_{false}; #endif