From 89b70e4352c80847c58d70d81626df10fc412c61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:37:21 +0200 Subject: [PATCH 01/21] Bump docker/setup-buildx-action from 3.11.0 to 3.11.1 in the docker-actions group (#9133) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-docker.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 3bfed87237..f76ebba8e9 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -49,7 +49,7 @@ jobs: with: python-version: "3.10" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.11.0 + uses: docker/setup-buildx-action@v3.11.1 - name: Set TAG run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8239e03a99..b4518b27b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,7 +99,7 @@ jobs: python-version: "3.10" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.11.0 + uses: docker/setup-buildx-action@v3.11.1 - name: Log in to docker hub uses: docker/login-action@v3.4.0 @@ -178,7 +178,7 @@ jobs: merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.11.0 + uses: docker/setup-buildx-action@v3.11.1 - name: Log in to docker hub if: matrix.registry == 'dockerhub' From f16f4e2c4ca72e707c485699f61794b93ade5f18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 19:55:59 +0000 Subject: [PATCH 02/21] Bump aioesphomeapi from 32.2.3 to 32.2.4 (#9132) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 682f9dbe60..76a58bf622 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.8.1 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==32.2.3 +aioesphomeapi==32.2.4 zeroconf==0.147.0 puremagic==1.29 ruamel.yaml==0.18.14 # dashboard_import From 57388254c4ef7abce6a8108eb1d4c9118cb024db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 19:56:26 +0000 Subject: [PATCH 03/21] Bump pytest from 8.4.0 to 8.4.1 (#9131) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8b42b9347c..9263d165ac 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==8.4.0 +pytest==8.4.1 pytest-cov==6.2.1 pytest-mock==3.14.1 pytest-asyncio==1.0.0 From aa180b9581c715c3dce35da25fdd0d3d675336b9 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:16:25 -0400 Subject: [PATCH 04/21] Bump ESP32 Arduino version to 3.1.3 (#8604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kuba Szczodrzyński --- esphome/components/ac_dimmer/ac_dimmer.cpp | 9 +- esphome/components/captive_portal/__init__.py | 1 + esphome/components/esp32/__init__.py | 32 ++++--- .../esp32_camera_web_server/__init__.py | 2 +- esphome/components/esp32_rmt/__init__.py | 58 ------------ .../esp32_rmt_led_strip/led_strip.cpp | 47 ---------- .../esp32_rmt_led_strip/led_strip.h | 15 ---- .../components/esp32_rmt_led_strip/light.py | 90 +++---------------- .../ethernet/ethernet_component.cpp | 2 +- esphome/components/http_request/__init__.py | 2 +- .../http_request/http_request_arduino.h | 1 + esphome/components/i2c/i2c_bus_arduino.cpp | 2 +- esphome/components/i2s_audio/i2s_audio.cpp | 2 +- .../i2s_audio/media_player/__init__.py | 2 +- esphome/components/ledc/ledc_output.cpp | 64 +------------ esphome/components/logger/__init__.py | 5 +- esphome/components/md5/md5.cpp | 6 +- esphome/components/md5/md5.h | 7 +- esphome/components/neopixelbus/light.py | 5 +- .../neopixelbus/neopixelbus_light.h | 2 +- esphome/components/network/__init__.py | 5 +- esphome/components/network/ip_address.h | 1 + esphome/components/nextion/display.py | 2 +- .../components/online_image/online_image.cpp | 2 +- .../components/remote_base/remote_base.cpp | 21 ----- esphome/components/remote_base/remote_base.h | 26 ------ .../components/remote_receiver/__init__.py | 67 +++++--------- .../remote_receiver/remote_receiver.h | 21 +---- .../remote_receiver/remote_receiver_esp32.cpp | 87 ------------------ .../components/remote_transmitter/__init__.py | 67 +++++--------- .../remote_transmitter/remote_transmitter.h | 16 +--- .../remote_transmitter_esp32.cpp | 77 ---------------- esphome/components/sntp/sntp_component.cpp | 6 +- .../wifi/wifi_component_esp32_arduino.cpp | 37 ++++---- esphome/core/defines.h | 2 +- platformio.ini | 11 ++- .../common-ard-esp32_rmt_led_strip.yaml | 1 - .../bluetooth_proxy/test.esp32-c3-ard.yaml | 8 -- ...p32-c3-idf.yaml => test.esp32-c6-idf.yaml} | 0 ....esp32-ard.yaml => test.esp32-s3-ard.yaml} | 0 ....esp32-idf.yaml => test.esp32-s3-idf.yaml} | 0 tests/components/e131/common-ard.yaml | 1 - .../esp32_camera_web_server/common.yaml | 4 + .../esp32_rmt_led_strip/common-ard.yaml | 18 ---- .../{common-idf.yaml => common.yaml} | 0 .../esp32_rmt_led_strip/test.esp32-ard.yaml | 2 +- .../test.esp32-c3-ard.yaml | 2 +- .../test.esp32-c3-idf.yaml | 2 +- .../esp32_rmt_led_strip/test.esp32-idf.yaml | 2 +- .../test.esp32-s3-idf.yaml | 2 +- .../test.esp32-s3-ard.yaml | 4 - tests/components/partition/common-ard.yaml | 1 - .../remote_receiver/esp32-common-ard.yaml | 14 --- ...sp32-common-idf.yaml => esp32-common.yaml} | 0 .../remote_receiver/test.esp32-ard.yaml | 7 +- .../remote_receiver/test.esp32-c3-ard.yaml | 7 +- .../remote_receiver/test.esp32-c3-idf.yaml | 2 +- .../remote_receiver/test.esp32-idf.yaml | 2 +- .../remote_receiver/test.esp32-s3-idf.yaml | 2 +- .../remote_transmitter/esp32-common-ard.yaml | 8 -- ...sp32-common-idf.yaml => esp32-common.yaml} | 0 .../remote_transmitter/test.esp32-ard.yaml | 5 +- .../remote_transmitter/test.esp32-c3-ard.yaml | 5 +- .../remote_transmitter/test.esp32-c3-idf.yaml | 2 +- .../remote_transmitter/test.esp32-idf.yaml | 2 +- .../remote_transmitter/test.esp32-s3-idf.yaml | 2 +- tests/components/wled/test.esp32-ard.yaml | 1 - tests/components/wled/test.esp32-c3-ard.yaml | 1 - 68 files changed, 171 insertions(+), 738 deletions(-) delete mode 100644 tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml rename tests/components/bluetooth_proxy/{test.esp32-c3-idf.yaml => test.esp32-c6-idf.yaml} (100%) rename tests/components/bluetooth_proxy/{test.esp32-ard.yaml => test.esp32-s3-ard.yaml} (100%) rename tests/components/bluetooth_proxy/{test.esp32-idf.yaml => test.esp32-s3-idf.yaml} (100%) delete mode 100644 tests/components/esp32_rmt_led_strip/common-ard.yaml rename tests/components/esp32_rmt_led_strip/{common-idf.yaml => common.yaml} (100%) delete mode 100644 tests/components/remote_receiver/esp32-common-ard.yaml rename tests/components/remote_receiver/{esp32-common-idf.yaml => esp32-common.yaml} (100%) delete mode 100644 tests/components/remote_transmitter/esp32-common-ard.yaml rename tests/components/remote_transmitter/{esp32-common-idf.yaml => esp32-common.yaml} (100%) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index ddaa910db3..276adeebb0 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -193,14 +193,13 @@ void AcDimmer::setup() { setTimer1Callback(&timer_interrupt); #endif #ifdef USE_ESP32 - // 80 Divider -> 1 count=1µs - dimmer_timer = timerBegin(0, 80, true); - timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true); + // timer frequency of 1mhz + dimmer_timer = timerBegin(1000000); + timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr); // For ESP32, we can't use dynamic interval calculation because the timerX functions // are not callable from ISR (placed in flash storage). // Here we just use an interrupt firing every 50 µs. - timerAlarmWrite(dimmer_timer, 50, true); - timerAlarmEnable(dimmer_timer); + timerAlarm(dimmer_timer, 50, true, 0); #endif } void AcDimmer::write_state(float state) { diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index ea11e733ac..a55887948d 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -41,6 +41,7 @@ async def to_code(config): if CORE.using_arduino: if CORE.is_esp32: + cg.add_library("ESP32 Async UDP", None) cg.add_library("DNSServer", None) cg.add_library("WiFi", None) if CORE.is_esp8266: diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 7f2d718d35..f179c315f9 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -132,6 +132,8 @@ def set_core_data(config): choices = CPU_FREQUENCIES[variant] if "160MHZ" in choices: cpu_frequency = "160MHZ" + elif "360MHZ" in choices: + cpu_frequency = "360MHZ" else: cpu_frequency = choices[-1] config[CONF_CPU_FREQUENCY] = cpu_frequency @@ -289,11 +291,8 @@ def add_extra_build_file(filename: str, path: str) -> bool: def _format_framework_arduino_version(ver: cv.Version) -> str: # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to - # a PIO platformio/framework-arduinoespressif32 value - # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 - if ver <= cv.Version(1, 0, 3): - return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + # a PIO pioarduino/framework-arduinoespressif32 value + return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip" def _format_framework_espidf_version( @@ -317,12 +316,10 @@ def _format_framework_espidf_version( # The default/recommended arduino framework version # - https://github.com/espressif/arduino-esp32/releases -# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5) -# The platformio/espressif32 version to use for arduino frameworks -# - https://github.com/platformio/platform-espressif32/releases -# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 3) +# The platform-espressif32 version to use for arduino frameworks +# - https://github.com/pioarduino/platform-espressif32/releases +ARDUINO_PLATFORM_VERSION = cv.Version(53, 3, 13) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases @@ -365,8 +362,8 @@ SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"), - "latest": (cv.Version(2, 0, 9), None), + "dev": (cv.Version(3, 1, 3), "https://github.com/espressif/arduino-esp32.git"), + "latest": (cv.Version(3, 1, 3), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } @@ -388,6 +385,10 @@ def _arduino_check_versions(value): CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) ) + if value[CONF_SOURCE].startswith("http"): + # prefix is necessary or platformio will complain with a cryptic error + value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}" + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( "The selected Arduino framework version is not the recommended one. " @@ -829,10 +830,7 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") - cg.add_platformio_option( - "platform_packages", - [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"], - ) + cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) if CONF_PARTITIONS in config: cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS]) diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py index 363218bbac..df137c8ff2 100644 --- a/esphome/components/esp32_camera_web_server/__init__.py +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODE, CONF_PORT CODEOWNERS = ["@ayufan"] -DEPENDENCIES = ["esp32_camera"] +DEPENDENCIES = ["esp32_camera", "network"] MULTI_CONF = True esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") diff --git a/esphome/components/esp32_rmt/__init__.py b/esphome/components/esp32_rmt/__init__.py index 171c335727..1e72185e3e 100644 --- a/esphome/components/esp32_rmt/__init__.py +++ b/esphome/components/esp32_rmt/__init__.py @@ -1,48 +1,8 @@ -import esphome.codegen as cg from esphome.components import esp32 import esphome.config_validation as cv -from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION -from esphome.core import CORE CODEOWNERS = ["@jesserockz"] -RMT_TX_CHANNELS = { - esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], - esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], - esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], - esp32.const.VARIANT_ESP32C3: [0, 1], - esp32.const.VARIANT_ESP32C6: [0, 1], - esp32.const.VARIANT_ESP32H2: [0, 1], -} - -RMT_RX_CHANNELS = { - esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], - esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], - esp32.const.VARIANT_ESP32S3: [4, 5, 6, 7], - esp32.const.VARIANT_ESP32C3: [2, 3], - esp32.const.VARIANT_ESP32C6: [2, 3], - esp32.const.VARIANT_ESP32H2: [2, 3], -} - -rmt_channel_t = cg.global_ns.enum("rmt_channel_t") -RMT_CHANNEL_ENUMS = { - 0: rmt_channel_t.RMT_CHANNEL_0, - 1: rmt_channel_t.RMT_CHANNEL_1, - 2: rmt_channel_t.RMT_CHANNEL_2, - 3: rmt_channel_t.RMT_CHANNEL_3, - 4: rmt_channel_t.RMT_CHANNEL_4, - 5: rmt_channel_t.RMT_CHANNEL_5, - 6: rmt_channel_t.RMT_CHANNEL_6, - 7: rmt_channel_t.RMT_CHANNEL_7, -} - - -def use_new_rmt_driver(): - framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] - if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0): - return True - return False - def validate_clock_resolution(): def _validator(value): @@ -60,21 +20,3 @@ def validate_clock_resolution(): return value return _validator - - -def validate_rmt_channel(*, tx: bool): - rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS - - def _validator(value): - cv.only_on_esp32(value) - value = cv.int_(value) - variant = esp32.get_esp32_variant() - if variant not in rmt_channels: - raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") - if value not in rmt_channels[variant]: - raise cv.Invalid( - f"RMT channel {value} does not support {'transmitting' if tx else 'receiving'} for ESP32 variant {variant}." - ) - return cv.enum(RMT_CHANNEL_ENUMS)(value) - - return _validator diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index 88ddf24d49..dfdf50aa66 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -42,7 +42,6 @@ void ESP32RMTLEDStripLightOutput::setup() { return; } -#if ESP_IDF_VERSION_MAJOR >= 5 RAMAllocator rmt_allocator(this->use_psram_ ? 0 : RAMAllocator::ALLOC_INTERNAL); // 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset @@ -79,36 +78,6 @@ void ESP32RMTLEDStripLightOutput::setup() { this->mark_failed(); return; } -#else - RAMAllocator rmt_allocator(this->use_psram_ ? 0 : RAMAllocator::ALLOC_INTERNAL); - - // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset - this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1); - - rmt_config_t config; - memset(&config, 0, sizeof(config)); - config.channel = this->channel_; - config.rmt_mode = RMT_MODE_TX; - config.gpio_num = gpio_num_t(this->pin_); - config.mem_block_num = 1; - config.clk_div = RMT_CLK_DIV; - config.tx_config.loop_en = false; - config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; - config.tx_config.carrier_en = false; - config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; - config.tx_config.idle_output_en = true; - - if (rmt_config(&config) != ESP_OK) { - ESP_LOGE(TAG, "Cannot initialize RMT!"); - this->mark_failed(); - return; - } - if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) { - ESP_LOGE(TAG, "Cannot install RMT driver!"); - this->mark_failed(); - return; - } -#endif } void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, @@ -145,11 +114,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { ESP_LOGVV(TAG, "Writing RGB values to bus"); -#if ESP_IDF_VERSION_MAJOR >= 5 esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000); -#else - esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)); -#endif if (error != ESP_OK) { ESP_LOGE(TAG, "RMT TX timeout"); this->status_set_warning(); @@ -162,11 +127,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { size_t size = 0; size_t len = 0; uint8_t *psrc = this->buf_; -#if ESP_IDF_VERSION_MAJOR >= 5 rmt_symbol_word_t *pdest = this->rmt_buf_; -#else - rmt_item32_t *pdest = this->rmt_buf_; -#endif while (size < buffer_size) { uint8_t b = *psrc; for (int i = 0; i < 8; i++) { @@ -184,15 +145,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { len++; } -#if ESP_IDF_VERSION_MAJOR >= 5 rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); config.loop_count = 0; config.flags.eot_level = 0; error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config); -#else - error = rmt_write_items(this->channel_, this->rmt_buf_, len, false); -#endif if (error != ESP_OK) { ESP_LOGE(TAG, "RMT TX error"); this->status_set_warning(); @@ -251,11 +208,7 @@ void ESP32RMTLEDStripLightOutput::dump_config() { "ESP32 RMT LED Strip:\n" " Pin: %u", this->pin_); -#if ESP_IDF_VERSION_MAJOR >= 5 ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_); -#else - ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_); -#endif const char *rgb_order; switch (this->rgb_order_) { case ORDER_RGB: diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index f0cec9b291..c6a2b4bc9f 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -11,12 +11,7 @@ #include #include #include - -#if ESP_IDF_VERSION_MAJOR >= 5 #include -#else -#include -#endif namespace esphome { namespace esp32_rmt_led_strip { @@ -61,11 +56,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { uint32_t reset_time_high, uint32_t reset_time_low); void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } -#if ESP_IDF_VERSION_MAJOR >= 5 void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } -#else - void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } -#endif void clear_effect_data() override { for (int i = 0; i < this->size(); i++) @@ -81,17 +72,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { uint8_t *buf_{nullptr}; uint8_t *effect_data_{nullptr}; -#if ESP_IDF_VERSION_MAJOR >= 5 rmt_channel_handle_t channel_{nullptr}; rmt_encoder_handle_t encoder_{nullptr}; rmt_symbol_word_t *rmt_buf_{nullptr}; rmt_symbol_word_t bit0_, bit1_, reset_; uint32_t rmt_symbols_{48}; -#else - rmt_item32_t *rmt_buf_{nullptr}; - rmt_item32_t bit0_, bit1_, reset_; - rmt_channel_t channel_{RMT_CHANNEL_0}; -#endif uint8_t pin_; uint16_t num_leds_; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 596770b96d..33ae44e435 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -3,7 +3,7 @@ import logging from esphome import pins import esphome.codegen as cg -from esphome.components import esp32, esp32_rmt, light +from esphome.components import esp32, light import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, @@ -13,11 +13,9 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_PIN, CONF_RGB_ORDER, - CONF_RMT_CHANNEL, CONF_RMT_SYMBOLS, CONF_USE_DMA, ) -from esphome.core import CORE _LOGGER = logging.getLogger(__name__) @@ -69,53 +67,6 @@ CONF_RESET_HIGH = "reset_high" CONF_RESET_LOW = "reset_low" -class OptionalForIDF5(cv.SplitDefault): - @property - def default(self): - if not esp32_rmt.use_new_rmt_driver(): - return cv.UNDEFINED - return super().default - - @default.setter - def default(self, value): - # Ignore default set from vol.Optional - pass - - -def only_with_new_rmt_driver(obj): - if not esp32_rmt.use_new_rmt_driver(): - raise cv.Invalid( - "This feature is only available for the IDF framework version 5." - ) - return obj - - -def not_with_new_rmt_driver(obj): - if esp32_rmt.use_new_rmt_driver(): - raise cv.Invalid( - "This feature is not available for the IDF framework version 5." - ) - return obj - - -def final_validation(config): - if not esp32_rmt.use_new_rmt_driver(): - if CONF_RMT_CHANNEL not in config: - if CORE.using_esp_idf: - raise cv.Invalid( - "rmt_channel is a required option for IDF version < 5." - ) - raise cv.Invalid( - "rmt_channel is a required option for the Arduino framework." - ) - _LOGGER.warning( - "RMT_LED_STRIP support for IDF version < 5 is deprecated and will be removed soon." - ) - - -FINAL_VALIDATE_SCHEMA = final_validation - - CONFIG_SCHEMA = cv.All( light.ADDRESSABLE_LIGHT_SCHEMA.extend( { @@ -123,20 +74,17 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), - cv.Optional(CONF_RMT_CHANNEL): cv.All( - not_with_new_rmt_driver, esp32_rmt.validate_rmt_channel(tx=True) - ), - OptionalForIDF5( + cv.SplitDefault( CONF_RMT_SYMBOLS, - esp32_idf=192, - esp32_s2_idf=192, - esp32_s3_idf=192, - esp32_p4_idf=192, - esp32_c3_idf=96, - esp32_c5_idf=96, - esp32_c6_idf=96, - esp32_h2_idf=96, - ): cv.All(only_with_new_rmt_driver, cv.int_range(min=2)), + esp32=192, + esp32_s2=192, + esp32_s3=192, + esp32_p4=192, + esp32_c3=96, + esp32_c5=96, + esp32_c6=96, + esp32_h2=96, + ): cv.int_range(min=2), cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, @@ -145,7 +93,6 @@ CONFIG_SCHEMA = cv.All( esp32.only_on_variant( supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] ), - cv.only_with_esp_idf, cv.boolean, ), cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean, @@ -218,15 +165,6 @@ async def to_code(config): cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) - - if esp32_rmt.use_new_rmt_driver(): - cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) - if CONF_USE_DMA in config: - cg.add(var.set_use_dma(config[CONF_USE_DMA])) - else: - rmt_channel_t = cg.global_ns.enum("rmt_channel_t") - cg.add( - var.set_rmt_channel( - getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") - ) - ) + cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) + if CONF_USE_DMA in config: + cg.add(var.set_use_dma(config[CONF_USE_DMA])) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index fe96973924..0a6ba6470e 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -106,7 +106,7 @@ void EthernetComponent::setup() { .post_cb = nullptr, }; -#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5) +#if ESP_IDF_VERSION_MAJOR >= 5 eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); #else spi_device_handle_t spi_handle = nullptr; diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index ac13334118..18373edb77 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -175,7 +175,7 @@ async def to_code(config): not config.get(CONF_VERIFY_SSL), ) else: - cg.add_library("WiFiClientSecure", None) + cg.add_library("NetworkClientSecure", None) cg.add_library("HTTPClient", None) if CORE.is_esp8266: cg.add_library("ESP8266HTTPClient", None) diff --git a/esphome/components/http_request/http_request_arduino.h b/esphome/components/http_request/http_request_arduino.h index ac9ddffbb0..44744f8c78 100644 --- a/esphome/components/http_request/http_request_arduino.h +++ b/esphome/components/http_request/http_request_arduino.h @@ -6,6 +6,7 @@ #if defined(USE_ESP32) || defined(USE_RP2040) #include +#include #endif #ifdef USE_ESP8266 #include diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index dca77e878d..e9d8c2415c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -125,7 +125,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) size_t to_request = 0; for (size_t i = 0; i < cnt; i++) to_request += buffers[i].len; - size_t ret = wire_->requestFrom((int) address, (int) to_request, 1); + size_t ret = wire_->requestFrom(address, to_request, true); if (ret != to_request) { ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret); return ERROR_TIMEOUT; diff --git a/esphome/components/i2s_audio/i2s_audio.cpp b/esphome/components/i2s_audio/i2s_audio.cpp index 0f2995b4bd..7ff21bba57 100644 --- a/esphome/components/i2s_audio/i2s_audio.cpp +++ b/esphome/components/i2s_audio/i2s_audio.cpp @@ -9,7 +9,7 @@ namespace i2s_audio { static const char *const TAG = "i2s_audio"; -#if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5) +#if ESP_IDF_VERSION_MAJOR >= 5 static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :( #endif diff --git a/esphome/components/i2s_audio/media_player/__init__.py b/esphome/components/i2s_audio/media_player/__init__.py index 8797d13e7c..ad6665a5f5 100644 --- a/esphome/components/i2s_audio/media_player/__init__.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -114,7 +114,7 @@ async def to_code(config): cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb")) - cg.add_library("WiFiClientSecure", None) + cg.add_library("NetworkClientSecure", None) cg.add_library("HTTPClient", None) cg.add_library("esphome/ESP32-audioI2S", "2.3.0") cg.add_build_flag("-DAUDIO_NO_SD_FS") diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index aefe0e63d8..2ae2656f54 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -3,28 +3,16 @@ #ifdef USE_ESP32 -#ifdef USE_ARDUINO -#include -#endif #include - #include #define CLOCK_FREQUENCY 80e6f -#ifdef USE_ARDUINO -#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK -#undef CLOCK_FREQUENCY -// starting with ESP32 Arduino 2.0.2, the 40MHz crystal is used as clock by default if supported -#define CLOCK_FREQUENCY 40e6f -#endif -#else #ifdef SOC_LEDC_SUPPORT_APB_CLOCK #define DEFAULT_CLK LEDC_USE_APB_CLK #else #define DEFAULT_CLK LEDC_AUTO_CLK #endif -#endif static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5; @@ -34,7 +22,6 @@ namespace ledc { static const char *const TAG = "ledc.output"; static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; -#ifdef USE_ESP_IDF #if SOC_LEDC_SUPPORT_HS_MODE // Only ESP32 has LEDC_HIGH_SPEED_MODE inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } @@ -44,7 +31,6 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } #endif -#endif float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return static_cast(CLOCK_FREQUENCY) / static_cast(1 << bit_depth); @@ -68,7 +54,6 @@ optional ledc_bit_depth_for_frequency(float frequency) { return {}; } -#ifdef USE_ESP_IDF esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num, uint8_t channel, uint8_t &bit_depth, float frequency) { bit_depth = *ledc_bit_depth_for_frequency(frequency); @@ -98,13 +83,10 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n return init_result; } -#endif -#ifdef USE_ESP_IDF constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) { return static_cast(angle * ((1U << bit_depth) - 1) / 360.0f); } -#endif // USE_ESP_IDF void LEDCOutput::write_state(float state) { if (!this->initialized_) { @@ -120,10 +102,6 @@ void LEDCOutput::write_state(float state) { const float duty_rounded = roundf(state * max_duty); auto duty = static_cast(duty_rounded); ESP_LOGV(TAG, "Setting duty: %" PRIu32 " on channel %u", duty, this->channel_); -#ifdef USE_ARDUINO - ledcWrite(this->channel_, duty); -#endif -#ifdef USE_ESP_IDF auto speed_mode = get_speed_mode(this->channel_); auto chan_num = static_cast(this->channel_ % 8); int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); @@ -135,18 +113,10 @@ void LEDCOutput::write_state(float state) { ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint); ledc_update_duty(speed_mode, chan_num); } -#endif } void LEDCOutput::setup() { ESP_LOGCONFIG(TAG, "Running setup"); -#ifdef USE_ARDUINO - this->update_frequency(this->frequency_); - this->turn_off(); - // Attach pin after setting default value - ledcAttachPin(this->pin_->get_pin(), this->channel_); -#endif -#ifdef USE_ESP_IDF auto speed_mode = get_speed_mode(this->channel_); auto timer_num = static_cast((this->channel_ % 8) / 2); auto chan_num = static_cast(this->channel_ % 8); @@ -175,7 +145,6 @@ void LEDCOutput::setup() { ledc_channel_config(&chan_conf); this->initialized_ = true; this->status_clear_error(); -#endif } void LEDCOutput::dump_config() { @@ -208,38 +177,7 @@ void LEDCOutput::update_frequency(float frequency) { } this->bit_depth_ = bit_depth_opt.value_or(8); this->frequency_ = frequency; -#ifdef USE_ARDUINO - ESP_LOGV(TAG, "Using Arduino API - Trying to define channel, frequency and bit depth"); - u_int32_t configured_frequency = 0; - // Configure LEDC channel, frequency and bit depth with fallback - int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX; - while (attempt_count_max > 0 && configured_frequency == 0) { - ESP_LOGV(TAG, "Initializing channel %u with frequency %.1f and bit depth of %u", this->channel_, this->frequency_, - this->bit_depth_); - configured_frequency = ledcSetup(this->channel_, frequency, this->bit_depth_); - if (configured_frequency != 0) { - this->initialized_ = true; - this->status_clear_error(); - ESP_LOGV(TAG, "Configured frequency: %u with bit depth: %u", configured_frequency, this->bit_depth_); - } else { - ESP_LOGW(TAG, "Unable to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, - this->frequency_, this->bit_depth_); - // try again with a lower bit depth - this->bit_depth_--; - } - attempt_count_max--; - } - - if (configured_frequency == 0) { - ESP_LOGE(TAG, "Permanently failed to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, - this->frequency_, this->bit_depth_); - this->status_set_error(); - return; - } - -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF if (!this->initialized_) { ESP_LOGW(TAG, "Not yet initialized"); return; @@ -259,7 +197,7 @@ void LEDCOutput::update_frequency(float frequency) { } this->status_clear_error(); -#endif + // re-apply duty this->write_state(this->duty_); } diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 462cae73b6..26516e1506 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -324,7 +324,10 @@ async def to_code(config): if CORE.using_arduino: if config[CONF_HARDWARE_UART] == USB_CDC: cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") - if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3: + if CORE.is_esp32 and get_esp32_variant() in ( + VARIANT_ESP32C3, + VARIANT_ESP32C6, + ): cg.add_build_flag("-DARDUINO_USB_MODE=1") if CORE.using_esp_idf: diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 31f52634be..980cb98699 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace md5 { -#if defined(USE_ARDUINO) && !defined(USE_RP2040) +#if defined(USE_ARDUINO) && !defined(USE_RP2040) && !defined(USE_ESP32) void MD5Digest::init() { memset(this->digest_, 0, 16); MD5Init(&this->ctx_); @@ -18,7 +18,7 @@ void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, da void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } #endif // USE_ARDUINO && !USE_RP2040 -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 void MD5Digest::init() { memset(this->digest_, 0, 16); esp_rom_md5_init(&this->ctx_); @@ -27,7 +27,7 @@ void MD5Digest::init() { void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this->ctx_, data, len); } void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); } -#endif // USE_ESP_IDF +#endif // USE_ESP32 #ifdef USE_RP2040 void MD5Digest::init() { diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index cb6accf46f..be1df40423 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -3,16 +3,11 @@ #include "esphome/core/defines.h" #ifdef USE_MD5 -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esp_rom_md5.h" #define MD5_CTX_TYPE md5_context_t #endif -#if defined(USE_ARDUINO) && defined(USE_ESP32) -#include "rom/md5_hash.h" -#define MD5_CTX_TYPE MD5Context -#endif - #if defined(USE_ARDUINO) && defined(USE_ESP8266) #include #define MD5_CTX_TYPE md5_context_t diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index affeb2de8f..3cd1bfd357 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -215,4 +215,7 @@ async def to_code(config): # https://github.com/Makuna/NeoPixelBus/blob/master/library.json # Version Listed Here: https://registry.platformio.org/libraries/makuna/NeoPixelBus/versions - cg.add_library("makuna/NeoPixelBus", "2.7.3") + if CORE.is_esp32: + cg.add_library("makuna/NeoPixelBus", "2.8.0") + else: + cg.add_library("makuna/NeoPixelBus", "2.7.3") diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index d94a923614..c27244b94d 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(CLANG_TIDY) #include "esphome/core/color.h" #include "esphome/core/component.h" diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 129b1ced06..b04fca7a1c 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option import esphome.config_validation as cv from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] @@ -36,8 +36,11 @@ CONFIG_SCHEMA = cv.Schema( ) +@coroutine_with_priority(201.0) async def to_code(config): cg.add_define("USE_NETWORK") + if CORE.using_arduino and CORE.is_esp32: + cg.add_library("Networking", None) if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None: cg.add_define("USE_NETWORK_IPV6", enable_ipv6) if enable_ipv6: diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index d76da573b5..5e6b0dbd96 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -56,6 +56,7 @@ struct IPAddress { IP_ADDR4(&ip_addr_, first, second, third, fourth); } IPAddress(const ip_addr_t *other_ip) { ip_addr_copy(ip_addr_, *other_ip); } + IPAddress(const char *in_address) { ipaddr_aton(in_address, &ip_addr_); } IPAddress(const std::string &in_address) { ipaddr_aton(in_address.c_str(), &ip_addr_); } IPAddress(ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip4_addr_t)); diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 7f63ca147b..0aa5efeba7 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -150,7 +150,7 @@ async def to_code(config): cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("WiFiClientSecure", None) + cg.add_library("NetworkClientSecure", None) cg.add_library("HTTPClient", None) elif CORE.is_esp32 and CORE.using_esp_idf: esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index 3f1d58fb45..e21b2528d5 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -224,7 +224,7 @@ void OnlineImage::loop() { this->height_ = buffer_height_; ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), this->width_, this->height_); - ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_); + ESP_LOGD(TAG, "Total time: %" PRIu32 "s", (uint32_t) (::time(nullptr) - this->start_time_)); this->etag_ = this->downloader_->get_response_header(ETAG_HEADER_NAME); this->last_modified_ = this->downloader_->get_response_header(LAST_MODIFIED_HEADER_NAME); this->download_finished_callback_.call(false); diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 5dff2c6a38..987286b345 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -8,27 +8,6 @@ namespace remote_base { static const char *const TAG = "remote_base"; -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 -RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { - static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; - this->channel_ = next_rmt_channel; - next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + mem_block_num); -} - -RemoteRMTChannel::RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num) - : channel_(channel), mem_block_num_(mem_block_num) {} - -void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { - if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) { - this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); - ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); - } - rmt.channel = this->channel_; - rmt.clk_div = this->clock_divider_; - rmt.mem_block_num = this->mem_block_num_; -} -#endif - /* RemoteReceiveData */ bool RemoteReceiveData::peek_mark(uint32_t length, uint32_t offset) const { diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 4131d080f5..a18dd0ed7e 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -8,10 +8,6 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 -#include -#endif - namespace esphome { namespace remote_base { @@ -112,43 +108,21 @@ class RemoteComponentBase { #ifdef USE_ESP32 class RemoteRMTChannel { public: -#if ESP_IDF_VERSION_MAJOR >= 5 void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; } void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } -#else - explicit RemoteRMTChannel(uint8_t mem_block_num = 1); - explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1); - - void config_rmt(rmt_config_t &rmt); - void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } -#endif protected: uint32_t from_microseconds_(uint32_t us) { -#if ESP_IDF_VERSION_MAJOR >= 5 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u; -#else - const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; -#endif return us * ticks_per_ten_us / 10; } uint32_t to_microseconds_(uint32_t ticks) { -#if ESP_IDF_VERSION_MAJOR >= 5 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u; -#else - const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; -#endif return (ticks * 10) / ticks_per_ten_us; } RemoteComponentBase *remote_base_; -#if ESP_IDF_VERSION_MAJOR >= 5 uint32_t clock_resolution_{1000000}; uint32_t rmt_symbols_; -#else - rmt_channel_t channel_{RMT_CHANNEL_0}; - uint8_t mem_block_num_; - uint8_t clock_divider_{80}; -#endif }; #endif diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 6994eebd91..321cfc93ff 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -4,15 +4,12 @@ from esphome.components import esp32, esp32_rmt, remote_base import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, - CONF_CLOCK_DIVIDER, CONF_CLOCK_RESOLUTION, CONF_DUMP, CONF_FILTER, CONF_ID, CONF_IDLE, - CONF_MEMORY_BLOCKS, CONF_PIN, - CONF_RMT_CHANNEL, CONF_RMT_SYMBOLS, CONF_TOLERANCE, CONF_TYPE, @@ -103,49 +100,36 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.positive_time_period_microseconds, cv.Range(max=TimePeriod(microseconds=4294967295)), ), - cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32_arduino=80): cv.All( - cv.only_on_esp32, - cv.only_with_arduino, - cv.int_range(min=1, max=255), - ), cv.Optional(CONF_CLOCK_RESOLUTION): cv.All( cv.only_on_esp32, - cv.only_with_esp_idf, esp32_rmt.validate_clock_resolution(), ), cv.Optional(CONF_IDLE, default="10ms"): cv.All( cv.positive_time_period_microseconds, cv.Range(max=TimePeriod(microseconds=4294967295)), ), - cv.SplitDefault(CONF_MEMORY_BLOCKS, esp32_arduino=3): cv.All( - cv.only_with_arduino, cv.int_range(min=1, max=8) - ), - cv.Optional(CONF_RMT_CHANNEL): cv.All( - cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=False) - ), cv.SplitDefault( CONF_RMT_SYMBOLS, - esp32_idf=192, - esp32_s2_idf=192, - esp32_s3_idf=192, - esp32_p4_idf=192, - esp32_c3_idf=96, - esp32_c5_idf=96, - esp32_c6_idf=96, - esp32_h2_idf=96, - ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), + esp32=192, + esp32_s2=192, + esp32_s3=192, + esp32_p4=192, + esp32_c3=96, + esp32_c5=96, + esp32_c6=96, + esp32_h2=96, + ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_FILTER_SYMBOLS): cv.All( - cv.only_with_esp_idf, cv.int_range(min=0) + cv.only_on_esp32, cv.int_range(min=0) ), cv.SplitDefault( CONF_RECEIVE_SYMBOLS, - esp32_idf=192, - ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), + esp32=192, + ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] ), - cv.only_with_esp_idf, cv.boolean, ), } @@ -156,24 +140,15 @@ CONFIG_SCHEMA = remote_base.validate_triggers( async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) if CORE.is_esp32: - if esp32_rmt.use_new_rmt_driver(): - var = cg.new_Pvariable(config[CONF_ID], pin) - cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) - cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS])) - if CONF_USE_DMA in config: - cg.add(var.set_with_dma(config[CONF_USE_DMA])) - if CONF_CLOCK_RESOLUTION in config: - cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) - if CONF_FILTER_SYMBOLS in config: - cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS])) - else: - if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: - var = cg.new_Pvariable( - config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] - ) - else: - var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) - cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) + var = cg.new_Pvariable(config[CONF_ID], pin) + cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) + cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS])) + if CONF_USE_DMA in config: + cg.add(var.set_with_dma(config[CONF_USE_DMA])) + if CONF_CLOCK_RESOLUTION in config: + cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) + if CONF_FILTER_SYMBOLS in config: + cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS])) else: var = cg.new_Pvariable(config[CONF_ID], pin) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 8d19d5490f..9d844eee66 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -5,7 +5,7 @@ #include -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#if defined(USE_ESP32) #include #endif @@ -29,7 +29,7 @@ struct RemoteReceiverComponentStore { uint32_t filter_us{10}; ISRInternalGPIOPin pin; }; -#elif defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#elif defined(USE_ESP32) struct RemoteReceiverComponentStore { /// Stores RMT symbols and rx done event data volatile uint8_t *buffer{nullptr}; @@ -55,21 +55,13 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, { public: -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 - RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) - : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} - - RemoteReceiverComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1) - : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {} -#else RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {} -#endif void setup() override; void dump_config() override; void loop() override; float get_setup_priority() const override { return setup_priority::DATA; } -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#ifdef USE_ESP32 void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; } void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; } void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } @@ -80,21 +72,16 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, protected: #ifdef USE_ESP32 -#if ESP_IDF_VERSION_MAJOR >= 5 void decode_rmt_(rmt_symbol_word_t *item, size_t item_count); rmt_channel_handle_t channel_{NULL}; uint32_t filter_symbols_{0}; uint32_t receive_symbols_{0}; bool with_dma_{false}; -#else - void decode_rmt_(rmt_item32_t *item, size_t item_count); - RingbufHandle_t ringbuf_; -#endif esp_err_t error_code_{ESP_OK}; std::string error_string_{""}; #endif -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || (defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5) +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) RemoteReceiverComponentStore store_; HighFrequencyLoopRequester high_freq_; #endif diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index b78928d857..3d6346baec 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -14,7 +14,6 @@ static const uint32_t RMT_CLK_FREQ = 32000000; static const uint32_t RMT_CLK_FREQ = 80000000; #endif -#if ESP_IDF_VERSION_MAJOR >= 5 static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *event, void *arg) { RemoteReceiverComponentStore *store = (RemoteReceiverComponentStore *) arg; rmt_rx_done_event_data_t *event_buffer = (rmt_rx_done_event_data_t *) (store->buffer + store->buffer_write); @@ -37,11 +36,9 @@ static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_r store->buffer_write = next_write; return false; } -#endif void RemoteReceiverComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); -#if ESP_IDF_VERSION_MAJOR >= 5 rmt_rx_channel_config_t channel; memset(&channel, 0, sizeof(channel)); channel.clk_src = RMT_CLK_SRC_DEFAULT; @@ -105,62 +102,11 @@ void RemoteReceiverComponent::setup() { this->mark_failed(); return; } -#else - this->pin_->setup(); - rmt_config_t rmt{}; - this->config_rmt(rmt); - rmt.gpio_num = gpio_num_t(this->pin_->get_pin()); - rmt.rmt_mode = RMT_MODE_RX; - if (this->filter_us_ == 0) { - rmt.rx_config.filter_en = false; - } else { - rmt.rx_config.filter_en = true; - rmt.rx_config.filter_ticks_thresh = static_cast( - std::min(this->from_microseconds_(this->filter_us_) * this->clock_divider_, (uint32_t) 255)); - } - rmt.rx_config.idle_threshold = - static_cast(std::min(this->from_microseconds_(this->idle_us_), (uint32_t) 65535)); - - esp_err_t error = rmt_config(&rmt); - if (error != ESP_OK) { - this->error_code_ = error; - this->error_string_ = "in rmt_config"; - this->mark_failed(); - return; - } - - error = rmt_driver_install(this->channel_, this->buffer_size_, 0); - if (error != ESP_OK) { - this->error_code_ = error; - if (error == ESP_ERR_INVALID_STATE) { - this->error_string_ = str_sprintf("RMT channel %i is already in use by another component", this->channel_); - } else { - this->error_string_ = "in rmt_driver_install"; - } - this->mark_failed(); - return; - } - error = rmt_get_ringbuf_handle(this->channel_, &this->ringbuf_); - if (error != ESP_OK) { - this->error_code_ = error; - this->error_string_ = "in rmt_get_ringbuf_handle"; - this->mark_failed(); - return; - } - error = rmt_rx_start(this->channel_, true); - if (error != ESP_OK) { - this->error_code_ = error; - this->error_string_ = "in rmt_rx_start"; - this->mark_failed(); - return; - } -#endif } void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); LOG_PIN(" Pin: ", this->pin_); -#if ESP_IDF_VERSION_MAJOR >= 5 ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz\n" " RMT symbols: %" PRIu32 "\n" @@ -172,22 +118,6 @@ void RemoteReceiverComponent::dump_config() { this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_, this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_, this->idle_us_); -#else - if (this->pin_->digital_read()) { - ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " - "invert the signal using 'inverted: True' in the pin schema!"); - } - ESP_LOGCONFIG(TAG, - " Channel: %d\n" - " RMT memory blocks: %d\n" - " Clock divider: %u\n" - " Tolerance: %" PRIu32 "%s\n" - " Filter out pulses shorter than: %" PRIu32 " us\n" - " Signal is done after %" PRIu32 " us of no changes", - this->channel_, this->mem_block_num_, this->clock_divider_, this->tolerance_, - (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_, - this->idle_us_); -#endif if (this->is_failed()) { ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), this->error_string_.c_str()); @@ -195,7 +125,6 @@ void RemoteReceiverComponent::dump_config() { } void RemoteReceiverComponent::loop() { -#if ESP_IDF_VERSION_MAJOR >= 5 if (this->store_.error != ESP_OK) { ESP_LOGE(TAG, "Receive error"); this->error_code_ = this->store_.error; @@ -221,25 +150,9 @@ void RemoteReceiverComponent::loop() { this->call_listeners_dumpers_(); } } -#else - size_t len = 0; - auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0); - if (item != nullptr) { - this->decode_rmt_(item, len / sizeof(rmt_item32_t)); - vRingbufferReturnItem(this->ringbuf_, item); - - if (!this->temp_.empty()) { - this->call_listeners_dumpers_(); - } - } -#endif } -#if ESP_IDF_VERSION_MAJOR >= 5 void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_count) { -#else -void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count) { -#endif bool prev_level = false; bool idle_level = false; uint32_t prev_length = 0; diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index 4db24760d8..713cee0186 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -4,14 +4,12 @@ from esphome.components import esp32, esp32_rmt, remote_base import esphome.config_validation as cv from esphome.const import ( CONF_CARRIER_DUTY_PERCENT, - CONF_CLOCK_DIVIDER, CONF_CLOCK_RESOLUTION, CONF_ID, CONF_INVERTED, CONF_MODE, CONF_OPEN_DRAIN, CONF_PIN, - CONF_RMT_CHANNEL, CONF_RMT_SYMBOLS, CONF_USE_DMA, ) @@ -38,34 +36,26 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_CLOCK_RESOLUTION): cv.All( cv.only_on_esp32, - cv.only_with_esp_idf, esp32_rmt.validate_clock_resolution(), ), - cv.Optional(CONF_CLOCK_DIVIDER): cv.All( - cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255) - ), - cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_with_esp_idf, cv.boolean), + cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] ), - cv.only_with_esp_idf, cv.boolean, ), cv.SplitDefault( CONF_RMT_SYMBOLS, - esp32_idf=64, - esp32_s2_idf=64, - esp32_s3_idf=48, - esp32_p4_idf=48, - esp32_c3_idf=48, - esp32_c5_idf=48, - esp32_c6_idf=48, - esp32_h2_idf=48, - ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), - cv.Optional(CONF_RMT_CHANNEL): cv.All( - cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True) - ), + esp32=64, + esp32_s2=64, + esp32_s3=48, + esp32_p4=48, + esp32_c3=48, + esp32_c5=48, + esp32_c6=48, + esp32_h2=48, + ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), } @@ -75,30 +65,21 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) if CORE.is_esp32: - if esp32_rmt.use_new_rmt_driver(): - var = cg.new_Pvariable(config[CONF_ID], pin) - cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) - if CONF_CLOCK_RESOLUTION in config: - cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) - if CONF_USE_DMA in config: - cg.add(var.set_with_dma(config[CONF_USE_DMA])) - if CONF_EOT_LEVEL in config: - cg.add(var.set_eot_level(config[CONF_EOT_LEVEL])) - else: - cg.add( - var.set_eot_level( - config[CONF_PIN][CONF_MODE][CONF_OPEN_DRAIN] - or config[CONF_PIN][CONF_INVERTED] - ) - ) + var = cg.new_Pvariable(config[CONF_ID], pin) + cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) + if CONF_CLOCK_RESOLUTION in config: + cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) + if CONF_USE_DMA in config: + cg.add(var.set_with_dma(config[CONF_USE_DMA])) + if CONF_EOT_LEVEL in config: + cg.add(var.set_eot_level(config[CONF_EOT_LEVEL])) else: - if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: - var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) - else: - var = cg.new_Pvariable(config[CONF_ID], pin) - if CONF_CLOCK_DIVIDER in config: - cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) - + cg.add( + var.set_eot_level( + config[CONF_PIN][CONF_MODE][CONF_OPEN_DRAIN] + or config[CONF_PIN][CONF_INVERTED] + ) + ) else: var = cg.new_Pvariable(config[CONF_ID], pin) await cg.register_component(var, config) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 0a8f354c72..f0dab2aaf8 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -5,7 +5,7 @@ #include -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#if defined(USE_ESP32) #include #endif @@ -20,15 +20,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif { public: -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 - RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) - : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} - - RemoteTransmitterComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1) - : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {} -#else explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} -#endif void setup() override; void dump_config() override; @@ -38,7 +30,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; } -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#if defined(USE_ESP32) void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } void digital_write(bool value); @@ -65,15 +57,11 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, uint32_t current_carrier_frequency_{38000}; bool initialized_{false}; -#if ESP_IDF_VERSION_MAJOR >= 5 std::vector rmt_temp_; bool with_dma_{false}; bool eot_level_{false}; rmt_channel_handle_t channel_{NULL}; rmt_encoder_handle_t encoder_{NULL}; -#else - std::vector rmt_temp_; -#endif esp_err_t error_code_{ESP_OK}; std::string error_string_{""}; bool inverted_{false}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index d51c45c607..411e380670 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -18,18 +18,10 @@ void RemoteTransmitterComponent::setup() { void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter:"); -#if ESP_IDF_VERSION_MAJOR >= 5 ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz\n" " RMT symbols: %" PRIu32, this->clock_resolution_, this->rmt_symbols_); -#else - ESP_LOGCONFIG(TAG, - " Channel: %d\n" - " RMT memory blocks: %d\n" - " Clock divider: %u", - this->channel_, this->mem_block_num_, this->clock_divider_); -#endif LOG_PIN(" Pin: ", this->pin_); if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) { @@ -42,7 +34,6 @@ void RemoteTransmitterComponent::dump_config() { } } -#if ESP_IDF_VERSION_MAJOR >= 5 void RemoteTransmitterComponent::digital_write(bool value) { rmt_symbol_word_t symbol = { .duration0 = 1, @@ -65,10 +56,8 @@ void RemoteTransmitterComponent::digital_write(bool value) { this->status_set_warning(); } } -#endif void RemoteTransmitterComponent::configure_rmt_() { -#if ESP_IDF_VERSION_MAJOR >= 5 esp_err_t error; if (!this->initialized_) { @@ -140,54 +129,6 @@ void RemoteTransmitterComponent::configure_rmt_() { this->mark_failed(); return; } -#else - rmt_config_t c{}; - - this->config_rmt(c); - c.rmt_mode = RMT_MODE_TX; - c.gpio_num = gpio_num_t(this->pin_->get_pin()); - c.tx_config.loop_en = false; - - if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) { - c.tx_config.carrier_en = false; - } else { - c.tx_config.carrier_en = true; - c.tx_config.carrier_freq_hz = this->current_carrier_frequency_; - c.tx_config.carrier_duty_percent = this->carrier_duty_percent_; - } - - c.tx_config.idle_output_en = true; - if (!this->inverted_) { - c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; - c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; - } else { - c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; - c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; - } - - esp_err_t error = rmt_config(&c); - if (error != ESP_OK) { - this->error_code_ = error; - this->error_string_ = "in rmt_config"; - this->mark_failed(); - return; - } - - if (!this->initialized_) { - error = rmt_driver_install(this->channel_, 0, 0); - if (error != ESP_OK) { - this->error_code_ = error; - if (error == ESP_ERR_INVALID_STATE) { - this->error_string_ = str_sprintf("RMT channel %i is already in use by another component", this->channel_); - } else { - this->error_string_ = "in rmt_driver_install"; - } - this->mark_failed(); - return; - } - this->initialized_ = true; - } -#endif } void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { @@ -202,11 +143,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.clear(); this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2); uint32_t rmt_i = 0; -#if ESP_IDF_VERSION_MAJOR >= 5 rmt_symbol_word_t rmt_item; -#else - rmt_item32_t rmt_item; -#endif for (int32_t val : this->temp_.get_data()) { bool level = val >= 0; @@ -241,7 +178,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen return; } this->transmit_trigger_->trigger(); -#if ESP_IDF_VERSION_MAJOR >= 5 for (uint32_t i = 0; i < send_times; i++) { rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); @@ -263,19 +199,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (i + 1 < send_times) delayMicroseconds(send_wait); } -#else - for (uint32_t i = 0; i < send_times; i++) { - esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); - if (error != ESP_OK) { - ESP_LOGW(TAG, "rmt_write_items failed: %s", esp_err_to_name(error)); - this->status_set_warning(); - } else { - this->status_clear_warning(); - } - if (i + 1 < send_times) - delayMicroseconds(send_wait); - } -#endif this->complete_trigger_->trigger(); } diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index c7642d0637..d5839c1a2b 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -1,7 +1,7 @@ #include "sntp_component.h" #include "esphome/core/log.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esp_sntp.h" #elif USE_ESP8266 #include "sntp.h" @@ -16,7 +16,7 @@ static const char *const TAG = "sntp"; void SNTPComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); -#if defined(USE_ESP_IDF) +#if defined(USE_ESP32) if (esp_sntp_enabled()) { esp_sntp_stop(); } @@ -46,7 +46,7 @@ void SNTPComponent::dump_config() { } } void SNTPComponent::update() { -#if !defined(USE_ESP_IDF) +#if !defined(USE_ESP32) // force resync if (sntp_enabled()) { sntp_stop(); diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 2dc3acda77..d9e45242a8 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -9,7 +9,7 @@ #include #include #ifdef USE_WIFI_WPA2_EAP -#include +#include #endif #ifdef USE_WIFI_AP @@ -228,43 +228,43 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (ap.get_eap().has_value()) { // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. EAPAuth eap = ap.get_eap().value(); - err = esp_wifi_sta_wpa2_ent_set_identity((uint8_t *) eap.identity.c_str(), eap.identity.length()); + err = esp_eap_client_set_identity((uint8_t *) eap.identity.c_str(), eap.identity.length()); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed! %d", err); + ESP_LOGV(TAG, "esp_eap_client_set_identity failed! %d", err); } int ca_cert_len = strlen(eap.ca_cert); int client_cert_len = strlen(eap.client_cert); int client_key_len = strlen(eap.client_key); if (ca_cert_len) { - err = esp_wifi_sta_wpa2_ent_set_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1); + err = esp_eap_client_set_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed! %d", err); + ESP_LOGV(TAG, "esp_eap_client_set_ca_cert failed! %d", err); } } // workout what type of EAP this is // validation is not required as the config tool has already validated it if (client_cert_len && client_key_len) { // if we have certs, this must be EAP-TLS - err = esp_wifi_sta_wpa2_ent_set_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1, - (uint8_t *) eap.client_key, client_key_len + 1, - (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str())); + err = esp_eap_client_set_certificate_and_key((uint8_t *) eap.client_cert, client_cert_len + 1, + (uint8_t *) eap.client_key, client_key_len + 1, + (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str())); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed! %d", err); + ESP_LOGV(TAG, "esp_eap_client_set_certificate_and_key failed! %d", err); } } else { // in the absence of certs, assume this is username/password based - err = esp_wifi_sta_wpa2_ent_set_username((uint8_t *) eap.username.c_str(), eap.username.length()); + err = esp_eap_client_set_username((uint8_t *) eap.username.c_str(), eap.username.length()); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed! %d", err); + ESP_LOGV(TAG, "esp_eap_client_set_username failed! %d", err); } - err = esp_wifi_sta_wpa2_ent_set_password((uint8_t *) eap.password.c_str(), eap.password.length()); + err = esp_eap_client_set_password((uint8_t *) eap.password.c_str(), eap.password.length()); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err); + ESP_LOGV(TAG, "esp_eap_client_set_password failed! %d", err); } } - err = esp_wifi_sta_wpa2_ent_enable(); + err = esp_wifi_sta_enterprise_enable(); if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); + ESP_LOGV(TAG, "esp_wifi_sta_enterprise_enable failed! %d", err); } } #endif // USE_WIFI_WPA2_EAP @@ -552,7 +552,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); #if USE_NETWORK_IPV6 - this->set_timeout(100, [] { WiFi.enableIpV6(); }); + this->set_timeout(100, [] { WiFi.enableIPv6(); }); #endif /* USE_NETWORK_IPV6 */ break; @@ -662,12 +662,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { -#if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0) - const auto status = WiFiClass::status(); -#else const auto status = WiFi.status(); -#endif - if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f7a937c28d..a5d9f45e53 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -149,7 +149,7 @@ #define USE_WIFI_11KV_SUPPORT #ifdef USE_ARDUINO -#define USE_ARDUINO_VERSION_CODE VERSION_CODE(2, 0, 5) +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 3) #define USE_ETHERNET #endif diff --git a/platformio.ini b/platformio.ini index 96926eadd1..f67226d657 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,7 +34,6 @@ build_flags = [common] lib_deps = esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.7.3 ; neopixelbus improv/Improv@1.2.4 ; improv_serial / esp32_improv bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code @@ -101,6 +100,7 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp + makuna/NeoPixelBus@2.7.3 ; neopixelbus ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) @@ -118,23 +118,26 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = platformio/espressif32@5.4.0 +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip platform_packages = - platformio/framework-arduinoespressif32@~3.20005.0 + pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.1.3/esp32-3.1.3.zip framework = arduino lib_deps = ; order matters with lib-deps; some of the libs in common:arduino.lib_deps ; don't declare built-in libraries as dependencies, so they have to be declared first FS ; web_server_base (Arduino built-in) + Networking ; wifi,web_server_base,ethernet (Arduino built-in) WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} ESP32Async/AsyncTCP@3.4.4 ; async_tcp - WiFiClientSecure ; http_request,nextion (Arduino built-in) + NetworkClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) + ESP32 Async UDP ; captive_portal (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) + makuna/NeoPixelBus@2.8.0 ; neopixelbus esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@1.1.4 ; audio diff --git a/tests/components/addressable_light/common-ard-esp32_rmt_led_strip.yaml b/tests/components/addressable_light/common-ard-esp32_rmt_led_strip.yaml index 9c5e63cdc6..a071f9df91 100644 --- a/tests/components/addressable_light/common-ard-esp32_rmt_led_strip.yaml +++ b/tests/components/addressable_light/common-ard-esp32_rmt_led_strip.yaml @@ -6,7 +6,6 @@ light: rgb_order: GRB num_leds: 256 pin: ${pin} - rmt_channel: 0 display: - platform: addressable_light diff --git a/tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml b/tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml deleted file mode 100644 index bf01b65b6f..0000000000 --- a/tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml +++ /dev/null @@ -1,8 +0,0 @@ -<<: !include common.yaml - -esp32_ble_tracker: - max_connections: 3 - -bluetooth_proxy: - active: true - connection_slots: 2 diff --git a/tests/components/bluetooth_proxy/test.esp32-c3-idf.yaml b/tests/components/bluetooth_proxy/test.esp32-c6-idf.yaml similarity index 100% rename from tests/components/bluetooth_proxy/test.esp32-c3-idf.yaml rename to tests/components/bluetooth_proxy/test.esp32-c6-idf.yaml diff --git a/tests/components/bluetooth_proxy/test.esp32-ard.yaml b/tests/components/bluetooth_proxy/test.esp32-s3-ard.yaml similarity index 100% rename from tests/components/bluetooth_proxy/test.esp32-ard.yaml rename to tests/components/bluetooth_proxy/test.esp32-s3-ard.yaml diff --git a/tests/components/bluetooth_proxy/test.esp32-idf.yaml b/tests/components/bluetooth_proxy/test.esp32-s3-idf.yaml similarity index 100% rename from tests/components/bluetooth_proxy/test.esp32-idf.yaml rename to tests/components/bluetooth_proxy/test.esp32-s3-idf.yaml diff --git a/tests/components/e131/common-ard.yaml b/tests/components/e131/common-ard.yaml index 418453d6ef..8300dbb01b 100644 --- a/tests/components/e131/common-ard.yaml +++ b/tests/components/e131/common-ard.yaml @@ -8,7 +8,6 @@ light: rgb_order: GRB num_leds: 256 pin: ${pin} - rmt_channel: 0 effects: - e131: universe: 1 diff --git a/tests/components/esp32_camera_web_server/common.yaml b/tests/components/esp32_camera_web_server/common.yaml index 5edefdf0a8..fe2a6a2739 100644 --- a/tests/components/esp32_camera_web_server/common.yaml +++ b/tests/components/esp32_camera_web_server/common.yaml @@ -32,3 +32,7 @@ esp32_camera_web_server: mode: stream - port: 8081 mode: snapshot + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/esp32_rmt_led_strip/common-ard.yaml b/tests/components/esp32_rmt_led_strip/common-ard.yaml deleted file mode 100644 index 287690e86e..0000000000 --- a/tests/components/esp32_rmt_led_strip/common-ard.yaml +++ /dev/null @@ -1,18 +0,0 @@ -light: - - platform: esp32_rmt_led_strip - id: led_strip1 - pin: ${pin1} - num_leds: 60 - rmt_channel: 0 - rgb_order: GRB - chipset: ws2812 - - platform: esp32_rmt_led_strip - id: led_strip2 - pin: ${pin2} - num_leds: 60 - rmt_channel: 1 - rgb_order: RGB - bit0_high: 100us - bit0_low: 100us - bit1_high: 100us - bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/common-idf.yaml b/tests/components/esp32_rmt_led_strip/common.yaml similarity index 100% rename from tests/components/esp32_rmt_led_strip/common-idf.yaml rename to tests/components/esp32_rmt_led_strip/common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-ard.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-ard.yaml index d5a9ec9435..0949b676d5 100644 --- a/tests/components/esp32_rmt_led_strip/test.esp32-ard.yaml +++ b/tests/components/esp32_rmt_led_strip/test.esp32-ard.yaml @@ -3,4 +3,4 @@ substitutions: pin2: GPIO14 packages: - common: !include common-ard.yaml + common: !include common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3-ard.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3-ard.yaml index 2a3cdec60d..6cc0667e77 100644 --- a/tests/components/esp32_rmt_led_strip/test.esp32-c3-ard.yaml +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3-ard.yaml @@ -3,4 +3,4 @@ substitutions: pin2: GPIO4 packages: - common: !include common-ard.yaml + common: !include common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml index 8feded852c..6cc0667e77 100644 --- a/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml @@ -3,4 +3,4 @@ substitutions: pin2: GPIO4 packages: - common: !include common-idf.yaml + common: !include common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml index bb26436e5b..0949b676d5 100644 --- a/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml +++ b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml @@ -3,4 +3,4 @@ substitutions: pin2: GPIO14 packages: - common: !include common-idf.yaml + common: !include common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-s3-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-s3-idf.yaml index f64bb9d8a5..ad273903b2 100644 --- a/tests/components/esp32_rmt_led_strip/test.esp32-s3-idf.yaml +++ b/tests/components/esp32_rmt_led_strip/test.esp32-s3-idf.yaml @@ -3,7 +3,7 @@ substitutions: pin2: GPIO4 packages: - common: !include common-idf.yaml + common: !include common.yaml light: - id: !extend led_strip1 diff --git a/tests/components/internal_temperature/test.esp32-s3-ard.yaml b/tests/components/internal_temperature/test.esp32-s3-ard.yaml index bdd704756c..dade44d145 100644 --- a/tests/components/internal_temperature/test.esp32-s3-ard.yaml +++ b/tests/components/internal_temperature/test.esp32-s3-ard.yaml @@ -1,5 +1 @@ <<: !include common.yaml - -esp32: - framework: - version: 2.0.9 diff --git a/tests/components/partition/common-ard.yaml b/tests/components/partition/common-ard.yaml index 654eacf54f..b2ceadd6f7 100644 --- a/tests/components/partition/common-ard.yaml +++ b/tests/components/partition/common-ard.yaml @@ -5,7 +5,6 @@ light: chipset: ws2812 num_leds: 256 rgb_order: GRB - rmt_channel: 1 pin: ${pin} - platform: partition name: Partition Light diff --git a/tests/components/remote_receiver/esp32-common-ard.yaml b/tests/components/remote_receiver/esp32-common-ard.yaml deleted file mode 100644 index e331a35307..0000000000 --- a/tests/components/remote_receiver/esp32-common-ard.yaml +++ /dev/null @@ -1,14 +0,0 @@ -remote_receiver: - - id: rcvr - pin: ${pin} - rmt_channel: ${rmt_channel} - dump: all - tolerance: 25% - <<: !include common-actions.yaml - -binary_sensor: - - platform: remote_receiver - name: Panasonic Remote Input - panasonic: - address: 0x4004 - command: 0x100BCBD diff --git a/tests/components/remote_receiver/esp32-common-idf.yaml b/tests/components/remote_receiver/esp32-common.yaml similarity index 100% rename from tests/components/remote_receiver/esp32-common-idf.yaml rename to tests/components/remote_receiver/esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-ard.yaml b/tests/components/remote_receiver/test.esp32-ard.yaml index 5d29187206..10dd767598 100644 --- a/tests/components/remote_receiver/test.esp32-ard.yaml +++ b/tests/components/remote_receiver/test.esp32-ard.yaml @@ -1,6 +1,9 @@ substitutions: pin: GPIO2 - rmt_channel: "2" + clock_resolution: "2000000" + filter_symbols: "2" + receive_symbols: "4" + rmt_symbols: "64" packages: - common: !include esp32-common-ard.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-c3-ard.yaml b/tests/components/remote_receiver/test.esp32-c3-ard.yaml index 5d29187206..10dd767598 100644 --- a/tests/components/remote_receiver/test.esp32-c3-ard.yaml +++ b/tests/components/remote_receiver/test.esp32-c3-ard.yaml @@ -1,6 +1,9 @@ substitutions: pin: GPIO2 - rmt_channel: "2" + clock_resolution: "2000000" + filter_symbols: "2" + receive_symbols: "4" + rmt_symbols: "64" packages: - common: !include esp32-common-ard.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-c3-idf.yaml b/tests/components/remote_receiver/test.esp32-c3-idf.yaml index f017a2d807..10dd767598 100644 --- a/tests/components/remote_receiver/test.esp32-c3-idf.yaml +++ b/tests/components/remote_receiver/test.esp32-c3-idf.yaml @@ -6,4 +6,4 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-idf.yaml b/tests/components/remote_receiver/test.esp32-idf.yaml index f017a2d807..10dd767598 100644 --- a/tests/components/remote_receiver/test.esp32-idf.yaml +++ b/tests/components/remote_receiver/test.esp32-idf.yaml @@ -6,4 +6,4 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-s3-idf.yaml b/tests/components/remote_receiver/test.esp32-s3-idf.yaml index 74f49866cd..cdae8b1e4e 100644 --- a/tests/components/remote_receiver/test.esp32-s3-idf.yaml +++ b/tests/components/remote_receiver/test.esp32-s3-idf.yaml @@ -6,7 +6,7 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml remote_receiver: - id: !extend rcvr diff --git a/tests/components/remote_transmitter/esp32-common-ard.yaml b/tests/components/remote_transmitter/esp32-common-ard.yaml deleted file mode 100644 index 420cea326d..0000000000 --- a/tests/components/remote_transmitter/esp32-common-ard.yaml +++ /dev/null @@ -1,8 +0,0 @@ -remote_transmitter: - - id: xmitr - pin: ${pin} - rmt_channel: ${rmt_channel} - carrier_duty_percent: 50% - -packages: - buttons: !include common-buttons.yaml diff --git a/tests/components/remote_transmitter/esp32-common-idf.yaml b/tests/components/remote_transmitter/esp32-common.yaml similarity index 100% rename from tests/components/remote_transmitter/esp32-common-idf.yaml rename to tests/components/remote_transmitter/esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-ard.yaml b/tests/components/remote_transmitter/test.esp32-ard.yaml index 5d29187206..0522f4d181 100644 --- a/tests/components/remote_transmitter/test.esp32-ard.yaml +++ b/tests/components/remote_transmitter/test.esp32-ard.yaml @@ -1,6 +1,7 @@ substitutions: pin: GPIO2 - rmt_channel: "2" + clock_resolution: "2000000" + rmt_symbols: "64" packages: - common: !include esp32-common-ard.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-c3-ard.yaml b/tests/components/remote_transmitter/test.esp32-c3-ard.yaml index c755b11563..0522f4d181 100644 --- a/tests/components/remote_transmitter/test.esp32-c3-ard.yaml +++ b/tests/components/remote_transmitter/test.esp32-c3-ard.yaml @@ -1,6 +1,7 @@ substitutions: pin: GPIO2 - rmt_channel: "1" + clock_resolution: "2000000" + rmt_symbols: "64" packages: - common: !include esp32-common-ard.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-c3-idf.yaml b/tests/components/remote_transmitter/test.esp32-c3-idf.yaml index cc1fe69b4d..0522f4d181 100644 --- a/tests/components/remote_transmitter/test.esp32-c3-idf.yaml +++ b/tests/components/remote_transmitter/test.esp32-c3-idf.yaml @@ -4,4 +4,4 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-idf.yaml b/tests/components/remote_transmitter/test.esp32-idf.yaml index cc1fe69b4d..0522f4d181 100644 --- a/tests/components/remote_transmitter/test.esp32-idf.yaml +++ b/tests/components/remote_transmitter/test.esp32-idf.yaml @@ -4,4 +4,4 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-s3-idf.yaml b/tests/components/remote_transmitter/test.esp32-s3-idf.yaml index d23463b531..fe4c46d9e7 100644 --- a/tests/components/remote_transmitter/test.esp32-s3-idf.yaml +++ b/tests/components/remote_transmitter/test.esp32-s3-idf.yaml @@ -4,7 +4,7 @@ substitutions: rmt_symbols: "64" packages: - common: !include esp32-common-idf.yaml + common: !include esp32-common.yaml remote_transmitter: - id: !extend xmitr diff --git a/tests/components/wled/test.esp32-ard.yaml b/tests/components/wled/test.esp32-ard.yaml index a24f28e154..156b31181e 100644 --- a/tests/components/wled/test.esp32-ard.yaml +++ b/tests/components/wled/test.esp32-ard.yaml @@ -12,6 +12,5 @@ light: rgb_order: GRB num_leds: 256 pin: 2 - rmt_channel: 0 effects: - wled: diff --git a/tests/components/wled/test.esp32-c3-ard.yaml b/tests/components/wled/test.esp32-c3-ard.yaml index a24f28e154..156b31181e 100644 --- a/tests/components/wled/test.esp32-c3-ard.yaml +++ b/tests/components/wled/test.esp32-c3-ard.yaml @@ -12,6 +12,5 @@ light: rgb_order: GRB num_leds: 256 pin: 2 - rmt_channel: 0 effects: - wled: From b7b1d17ecbf30762c9768e5e38fdfd81b8642b64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 02:06:39 +0200 Subject: [PATCH 05/21] Remove empty generated protobuf methods (#9098) --- esphome/components/api/api_pb2.cpp | 28 -------------------------- esphome/components/api/api_pb2.h | 28 -------------------------- esphome/components/api/proto.h | 6 ++++-- script/api_protobuf/api_protobuf.py | 31 ++++++++++++++--------------- 4 files changed, 19 insertions(+), 74 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index bde1824d71..517b4d41b4 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -797,28 +797,18 @@ void ConnectResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void DisconnectRequest::encode(ProtoWriteBuffer buffer) const {} -void DisconnectRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } #endif -void DisconnectResponse::encode(ProtoWriteBuffer buffer) const {} -void DisconnectResponse::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } #endif -void PingRequest::encode(ProtoWriteBuffer buffer) const {} -void PingRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } #endif -void PingResponse::encode(ProtoWriteBuffer buffer) const {} -void PingResponse::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } #endif -void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {} -void DeviceInfoRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } #endif @@ -1039,18 +1029,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {} -void ListEntitiesRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } #endif -void ListEntitiesDoneResponse::encode(ProtoWriteBuffer buffer) const {} -void ListEntitiesDoneResponse::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } #endif -void SubscribeStatesRequest::encode(ProtoWriteBuffer buffer) const {} -void SubscribeStatesRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); } #endif @@ -3371,8 +3355,6 @@ void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {} -void SubscribeHomeassistantServicesRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeassistantServicesRequest {}"); @@ -3498,8 +3480,6 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void SubscribeHomeAssistantStatesRequest::encode(ProtoWriteBuffer buffer) const {} -void SubscribeHomeAssistantStatesRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeAssistantStatesRequest {}"); @@ -3603,8 +3583,6 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {} -void GetTimeRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } #endif @@ -7499,8 +7477,6 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {} -void SubscribeBluetoothConnectionsFreeRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { out.append("SubscribeBluetoothConnectionsFreeRequest {}"); @@ -7784,8 +7760,6 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { out.append("}"); } #endif -void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} -void UnsubscribeBluetoothLEAdvertisementsRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); @@ -8451,8 +8425,6 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { out.append("}"); } #endif -void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} -void VoiceAssistantConfigurationRequest::calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { out.append("VoiceAssistantConfigurationRequest {}"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9d270bcdc1..7d92125290 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -357,8 +357,6 @@ class DisconnectRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "disconnect_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -372,8 +370,6 @@ class DisconnectResponse : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "disconnect_response"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -387,8 +383,6 @@ class PingRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "ping_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -402,8 +396,6 @@ class PingResponse : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "ping_response"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -417,8 +409,6 @@ class DeviceInfoRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "device_info_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -468,8 +458,6 @@ class ListEntitiesRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -483,8 +471,6 @@ class ListEntitiesDoneResponse : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_done_response"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -498,8 +484,6 @@ class SubscribeStatesRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "subscribe_states_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1011,8 +995,6 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "subscribe_homeassistant_services_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1061,8 +1043,6 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "subscribe_home_assistant_states_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1115,8 +1095,6 @@ class GetTimeRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "get_time_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2117,8 +2095,6 @@ class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "subscribe_bluetooth_connections_free_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2244,8 +2220,6 @@ class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "unsubscribe_bluetooth_le_advertisements_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2512,8 +2486,6 @@ class VoiceAssistantConfigurationRequest : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "voice_assistant_configuration_request"; } #endif - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index eb0dbc151b..6ece509c8d 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -327,9 +327,11 @@ class ProtoWriteBuffer { class ProtoMessage { public: virtual ~ProtoMessage() = default; - virtual void encode(ProtoWriteBuffer buffer) const = 0; + // Default implementation for messages with no fields + virtual void encode(ProtoWriteBuffer buffer) const {} void decode(const uint8_t *buffer, size_t length); - virtual void calculate_size(uint32_t &total_size) const = 0; + // Default implementation for messages with no fields + virtual void calculate_size(uint32_t &total_size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP std::string dump() const; virtual void dump_to(std::string &out) const = 0; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 24b6bef843..5ac101c673 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -959,36 +959,35 @@ def build_message_type( prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;" protected_content.insert(0, prot) - o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" + # Only generate encode method if there are fields to encode if encode: + o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120: o += f" {encode[0]} " else: o += "\n" o += indent("\n".join(encode)) + "\n" - o += "}\n" - cpp += o - prot = "void encode(ProtoWriteBuffer buffer) const override;" - public_content.append(prot) + o += "}\n" + cpp += o + prot = "void encode(ProtoWriteBuffer buffer) const override;" + public_content.append(prot) + # If no fields to encode, the default implementation in ProtoMessage will be used - # Add calculate_size method - o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{" - - # Add a check for empty/default objects to short-circuit the calculation - # Only add this optimization if we have fields to check + # Add calculate_size method only if there are fields if size_calc: + o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{" # For a single field, just inline it for simplicity if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120: o += f" {size_calc[0]} " else: - # For multiple fields, add a short-circuit check + # For multiple fields o += "\n" - # Performance optimization: add all the size calculations o += indent("\n".join(size_calc)) + "\n" - o += "}\n" - cpp += o - prot = "void calculate_size(uint32_t &total_size) const override;" - public_content.append(prot) + o += "}\n" + cpp += o + prot = "void calculate_size(uint32_t &total_size) const override;" + public_content.append(prot) + # If no fields to calculate size for, the default implementation in ProtoMessage will be used o = f"void {desc.name}::dump_to(std::string &out) const {{" if dump: From a08d021f77a9d076eebb24d28ae67c067bb44612 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 02:10:01 +0200 Subject: [PATCH 06/21] Reduce code duplication in auto-generated API protocol code (#9097) --- esphome/components/api/api_pb2_service.cpp | 442 +++++---------------- esphome/components/api/proto.h | 20 + script/api_protobuf/api_protobuf.py | 49 ++- 3 files changed, 151 insertions(+), 360 deletions(-) diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index dacb23c12b..03017fdfff 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -620,544 +620,300 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) { } } void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - DeviceInfoResponse ret = this->device_info(msg); - if (!this->send_message(ret)) { - this->on_fatal_error(); + if (this->check_connection_setup_()) { + DeviceInfoResponse ret = this->device_info(msg); + if (!this->send_message(ret)) { + this->on_fatal_error(); + } } } void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->list_entities(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->list_entities(msg); } void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_states(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_states(msg); } void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_logs(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_logs(msg); } void APIServerConnection::on_subscribe_homeassistant_services_request( const SubscribeHomeassistantServicesRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_homeassistant_services(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_homeassistant_services(msg); } void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_home_assistant_states(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_home_assistant_states(msg); } void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - GetTimeResponse ret = this->get_time(msg); - if (!this->send_message(ret)) { - this->on_fatal_error(); + if (this->check_connection_setup_()) { + GetTimeResponse ret = this->get_time(msg); + if (!this->send_message(ret)) { + this->on_fatal_error(); + } } } void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->execute_service(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->execute_service(msg); } #ifdef USE_API_NOISE void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg); - if (!this->send_message(ret)) { - this->on_fatal_error(); + if (this->check_authenticated_()) { + NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg); + if (!this->send_message(ret)) { + this->on_fatal_error(); + } } } #endif #ifdef USE_BUTTON void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->button_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->button_command(msg); } #endif #ifdef USE_ESP32_CAMERA void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->camera_image(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->camera_image(msg); } #endif #ifdef USE_CLIMATE void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->climate_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->climate_command(msg); } #endif #ifdef USE_COVER void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->cover_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->cover_command(msg); } #endif #ifdef USE_DATETIME_DATE void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->date_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->date_command(msg); } #endif #ifdef USE_DATETIME_DATETIME void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->datetime_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->datetime_command(msg); } #endif #ifdef USE_FAN void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->fan_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->fan_command(msg); } #endif #ifdef USE_LIGHT void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->light_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->light_command(msg); } #endif #ifdef USE_LOCK void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->lock_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->lock_command(msg); } #endif #ifdef USE_MEDIA_PLAYER void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->media_player_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->media_player_command(msg); } #endif #ifdef USE_NUMBER void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->number_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->number_command(msg); } #endif #ifdef USE_SELECT void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->select_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->select_command(msg); } #endif #ifdef USE_SIREN void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->siren_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->siren_command(msg); } #endif #ifdef USE_SWITCH void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->switch_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->switch_command(msg); } #endif #ifdef USE_TEXT void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->text_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->text_command(msg); } #endif #ifdef USE_DATETIME_TIME void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->time_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->time_command(msg); } #endif #ifdef USE_UPDATE void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->update_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->update_command(msg); } #endif #ifdef USE_VALVE void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->valve_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->valve_command(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( const SubscribeBluetoothLEAdvertisementsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_bluetooth_le_advertisements(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_bluetooth_le_advertisements(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_device_request(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_device_request(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_get_services(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_get_services(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_read(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_read(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_write(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_write(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_read_descriptor(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_read_descriptor(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_write_descriptor(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_write_descriptor(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_gatt_notify(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_gatt_notify(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_connections_free_request( const SubscribeBluetoothConnectionsFreeRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); - if (!this->send_message(ret)) { - this->on_fatal_error(); + if (this->check_authenticated_()) { + BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); + if (!this->send_message(ret)) { + this->on_fatal_error(); + } } } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->unsubscribe_bluetooth_le_advertisements(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->unsubscribe_bluetooth_le_advertisements(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->bluetooth_scanner_set_mode(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->bluetooth_scanner_set_mode(msg); } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->subscribe_voice_assistant(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_voice_assistant(msg); } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); - if (!this->send_message(ret)) { - this->on_fatal_error(); + if (this->check_authenticated_()) { + VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); + if (!this->send_message(ret)) { + this->on_fatal_error(); + } } } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->voice_assistant_set_configuration(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->voice_assistant_set_configuration(msg); } #endif #ifdef USE_ALARM_CONTROL_PANEL void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; + if (this->check_authenticated_()) { + this->alarm_control_panel_command(msg); } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->alarm_control_panel_command(msg); } #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 6ece509c8d..e850236db6 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -379,6 +379,26 @@ class ProtoService { // Send the buffer return this->send_buffer(buffer, message_type); } + + // Authentication helper methods + bool check_connection_setup_() { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return false; + } + return true; + } + + bool check_authenticated_() { + if (!this->check_connection_setup_()) { + return false; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return false; + } + return true; + } }; } // namespace api diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5ac101c673..bd1be66649 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1424,25 +1424,40 @@ def main() -> None: hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" - body = "" - if needs_conn: - body += "if (!this->is_connection_setup()) {\n" - body += " this->on_no_setup_connection();\n" - body += " return;\n" - body += "}\n" - if needs_auth: - body += "if (!this->is_authenticated()) {\n" - body += " this->on_unauthenticated_access();\n" - body += " return;\n" - body += "}\n" - if is_void: - body += f"this->{func}(msg);\n" - else: - body += f"{ret} ret = this->{func}(msg);\n" - body += "if (!this->send_message(ret)) {\n" - body += " this->on_fatal_error();\n" + # Start with authentication/connection check if needed + if needs_auth or needs_conn: + # Determine which check to use + if needs_auth: + check_func = "this->check_authenticated_()" + else: + check_func = "this->check_connection_setup_()" + + body = f"if ({check_func}) {{\n" + + # Add the actual handler code, indented + handler_body = "" + if is_void: + handler_body = f"this->{func}(msg);\n" + else: + handler_body = f"{ret} ret = this->{func}(msg);\n" + handler_body += "if (!this->send_message(ret)) {\n" + handler_body += " this->on_fatal_error();\n" + handler_body += "}\n" + + body += indent(handler_body) + "\n" body += "}\n" + else: + # No auth check needed, just call the handler + body = "" + if is_void: + body += f"this->{func}(msg);\n" + else: + body += f"{ret} ret = this->{func}(msg);\n" + body += "if (!this->send_message(ret)) {\n" + body += " this->on_fatal_error();\n" + body += "}\n" + cpp += indent(body) + "\n" + "}\n" if ifdef is not None: From 95544e489dc59b1bb751b4c2d417d7ab41dfa35e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 02:10:50 +0200 Subject: [PATCH 07/21] Use smaller atomic types for ESP32 BLE Tracker ring buffer indices (#9106) --- .../components/esp32_ble_tracker/esp32_ble_tracker.cpp | 10 +++++----- .../components/esp32_ble_tracker/esp32_ble_tracker.h | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index c5906779f1..4785c29230 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -122,10 +122,10 @@ void ESP32BLETracker::loop() { // Consumer side: This runs in the main loop thread if (this->scanner_state_ == ScannerState::RUNNING) { // Load our own index with relaxed ordering (we're the only writer) - size_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed); + uint8_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed); // Load producer's index with acquire to see their latest writes - size_t write_idx = this->ring_write_index_.load(std::memory_order_acquire); + uint8_t write_idx = this->ring_write_index_.load(std::memory_order_acquire); while (read_idx != write_idx) { // Process one result at a time directly from ring buffer @@ -409,11 +409,11 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) { // IMPORTANT: Only this thread writes to ring_write_index_ // Load our own index with relaxed ordering (we're the only writer) - size_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed); - size_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE; + uint8_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed); + uint8_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE; // Load consumer's index with acquire to see their latest updates - size_t read_idx = this->ring_read_index_.load(std::memory_order_acquire); + uint8_t read_idx = this->ring_read_index_.load(std::memory_order_acquire); // Check if buffer is full if (next_write_idx != read_idx) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 16a100fb47..490ed19645 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -289,9 +289,9 @@ class ESP32BLETracker : public Component, // Consumer: ESPHome main loop (loop() method) // This design ensures zero blocking in the BT callback and prevents scan result loss BLEScanResult *scan_ring_buffer_; - std::atomic ring_write_index_{0}; // Written only by BT callback (producer) - std::atomic ring_read_index_{0}; // Written only by main loop (consumer) - std::atomic scan_results_dropped_{0}; // Tracks buffer overflow events + std::atomic ring_write_index_{0}; // Written only by BT callback (producer) + std::atomic ring_read_index_{0}; // Written only by main loop (consumer) + std::atomic scan_results_dropped_{0}; // Tracks buffer overflow events esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; From 43c677ef37f84cc826191f16f148387979118497 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 02:12:14 +0200 Subject: [PATCH 08/21] Optimize API server performance by using cached loop time (#9104) --- esphome/components/api/api_server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 6852afe937..740e4259b1 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -106,7 +106,7 @@ void APIServer::setup() { } #endif - this->last_connected_ = millis(); + this->last_connected_ = App.get_loop_component_start_time(); #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { @@ -164,7 +164,7 @@ void APIServer::loop() { } if (this->reboot_timeout_ != 0) { - const uint32_t now = millis(); + const uint32_t now = App.get_loop_component_start_time(); if (!this->is_connected()) { if (now - this->last_connected_ > this->reboot_timeout_) { ESP_LOGE(TAG, "No client connected; rebooting"); From eeb0710ad461036521568eb3cc005ad5efa1c1b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 03:08:25 +0200 Subject: [PATCH 09/21] Optimize API component memory usage by reordering class members to reduce padding (#9111) --- esphome/components/api/api_connection.cpp | 45 +++++---- esphome/components/api/api_connection.h | 55 ++++++----- esphome/components/api/api_frame_helper.h | 106 ++++++++++++---------- esphome/components/api/api_server.h | 16 +++- 4 files changed, 125 insertions(+), 97 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3e2b7c0154..ca5689bdf6 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -61,8 +61,8 @@ void APIConnection::start() { APIError err = this->helper_->init(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), - errno); + ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->get_client_combined_info().c_str(), + api_error_to_str(err), errno); return; } this->client_info_ = helper_->getpeername(); @@ -91,7 +91,7 @@ void APIConnection::loop() { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); - ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str()); return; } if (this->next_close_) { @@ -104,7 +104,7 @@ void APIConnection::loop() { APIError err = this->helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err), errno); return; } @@ -118,12 +118,12 @@ void APIConnection::loop() { } else if (err != APIError::OK) { on_fatal_error(); if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { - ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); } else if (err == APIError::CONNECTION_CLOSED) { - ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); } else { - ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), - errno); + ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), + api_error_to_str(err), errno); } return; } else { @@ -157,7 +157,7 @@ void APIConnection::loop() { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) { on_fatal_error(); - ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); } } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) { ESP_LOGVV(TAG, "Sending keepalive PING"); @@ -166,7 +166,7 @@ void APIConnection::loop() { this->next_ping_retry_ = now + ping_retry_interval; this->ping_retries_++; std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);", - this->client_combined_info_.c_str(), this->ping_retries_); + this->get_client_combined_info().c_str(), this->ping_retries_); if (this->ping_retries_ >= max_ping_retries) { on_fatal_error(); ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str()); @@ -233,7 +233,7 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop - ESP_LOGD(TAG, "%s disconnected", this->client_combined_info_.c_str()); + ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); this->next_close_ = true; DisconnectResponse resp; return resp; @@ -1544,8 +1544,7 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char HelloResponse APIConnection::hello(const HelloRequest &msg) { this->client_info_ = msg.client_info; this->client_peername_ = this->helper_->getpeername(); - this->client_combined_info_ = this->client_info_ + " (" + this->client_peername_ + ")"; - this->helper_->set_log_info(this->client_combined_info_); + this->helper_->set_log_info(this->get_client_combined_info()); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), @@ -1567,7 +1566,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { // bool invalid_password = 1; resp.invalid_password = !correct; if (correct) { - ESP_LOGD(TAG, "%s connected", this->client_combined_info_.c_str()); + ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); #ifdef USE_HOMEASSISTANT_TIME @@ -1673,7 +1672,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { APIError err = this->helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err), errno); return false; } @@ -1695,10 +1694,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) if (err != APIError::OK) { on_fatal_error(); if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { - ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); } else { - ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), - errno); + ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(), + api_error_to_str(err), errno); } return false; } @@ -1707,11 +1706,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) } void APIConnection::on_unauthenticated_access() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s requested access without authentication", this->client_combined_info_.c_str()); + ESP_LOGD(TAG, "%s requested access without authentication", this->get_client_combined_info().c_str()); } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s requested access without full connection", this->client_combined_info_.c_str()); + ESP_LOGD(TAG, "%s requested access without full connection", this->get_client_combined_info().c_str()); } void APIConnection::on_fatal_error() { this->helper_->close(); @@ -1860,10 +1859,10 @@ void APIConnection::process_batch_() { if (err != APIError::OK && err != APIError::WOULD_BLOCK) { on_fatal_error(); if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { - ESP_LOGW(TAG, "%s: Connection reset during batch write", this->client_combined_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection reset during batch write", this->get_client_combined_info().c_str()); } else { - ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), - errno); + ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), + api_error_to_str(err), errno); } } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 7cd41561d4..66b7ce38a7 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -275,7 +275,13 @@ class APIConnection : public APIServerConnection { bool try_to_clear_buffer(bool log_out_of_space); bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override; - std::string get_client_combined_info() const { return this->client_combined_info_; } + std::string get_client_combined_info() const { + if (this->client_info_ == this->client_peername_) { + // Before Hello message, both are the same (just IP:port) + return this->client_info_; + } + return this->client_info_ + " (" + this->client_peername_ + ")"; + } // Buffer allocator methods for batch processing ProtoWriteBuffer allocate_single_message_buffer(uint16_t size); @@ -432,37 +438,44 @@ class APIConnection : public APIServerConnection { // Helper function to get estimated message size for buffer pre-allocation static uint16_t get_estimated_message_size(uint16_t message_type); - enum class ConnectionState { + // Pointers first (4 bytes each, naturally aligned) + std::unique_ptr helper_; + APIServer *parent_; + + // 4-byte aligned types + uint32_t last_traffic_; + uint32_t next_ping_retry_{0}; + int state_subs_at_ = -1; + + // Strings (12 bytes each on 32-bit) + std::string client_info_; + std::string client_peername_; + + // 2-byte aligned types + uint16_t client_api_version_major_{0}; + uint16_t client_api_version_minor_{0}; + + // Group all 1-byte types together to minimize padding + enum class ConnectionState : uint8_t { WAITING_FOR_HELLO, CONNECTED, AUTHENTICATED, } connection_state_{ConnectionState::WAITING_FOR_HELLO}; - + uint8_t log_subscription_{ESPHOME_LOG_LEVEL_NONE}; bool remove_{false}; - - std::unique_ptr helper_; - - std::string client_info_; - std::string client_peername_; - std::string client_combined_info_; - uint32_t client_api_version_major_{0}; - uint32_t client_api_version_minor_{0}; -#ifdef USE_ESP32_CAMERA - esp32_camera::CameraImageReader image_reader_; -#endif - bool state_subscription_{false}; - int log_subscription_{ESPHOME_LOG_LEVEL_NONE}; - uint32_t last_traffic_; - uint32_t next_ping_retry_{0}; - uint8_t ping_retries_{0}; bool sent_ping_{false}; bool service_call_subscription_{false}; bool next_close_ = false; - APIServer *parent_; + uint8_t ping_retries_{0}; + // 8 bytes used, no padding needed + + // Larger objects at the end InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; - int state_subs_at_ = -1; +#ifdef USE_ESP32_CAMERA + esp32_camera::CameraImageReader image_reader_; +#endif // Function pointer type for message encoding using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index dc71a7ca17..7e90153091 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -125,38 +125,6 @@ class APIFrameHelper { const uint8_t *current_data() const { return data.data() + offset; } }; - // Queue of data buffers to be sent - std::deque tx_buf_; - - // Common state enum for all frame helpers - // Note: Not all states are used by all implementations - // - INITIALIZE: Used by both Noise and Plaintext - // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol - // - DATA: Used by both Noise and Plaintext - // - CLOSED: Used by both Noise and Plaintext - // - FAILED: Used by both Noise and Plaintext - // - EXPLICIT_REJECT: Only used by Noise protocol - enum class State { - INITIALIZE = 1, - CLIENT_HELLO = 2, // Noise only - SERVER_HELLO = 3, // Noise only - HANDSHAKE = 4, // Noise only - DATA = 5, - CLOSED = 6, - FAILED = 7, - EXPLICIT_REJECT = 8, // Noise only - }; - - // Current state of the frame helper - State state_{State::INITIALIZE}; - - // Helper name for logging - std::string info_; - - // Socket for communication - socket::Socket *socket_{nullptr}; - std::unique_ptr socket_owned_; - // Common implementation for writing raw data to socket APIError write_raw_(const struct iovec *iov, int iovcnt); @@ -169,15 +137,41 @@ class APIFrameHelper { APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector &tx_buf, const std::string &info, StateEnum &state, StateEnum failed_state); + // Pointers first (4 bytes each) + socket::Socket *socket_{nullptr}; + std::unique_ptr socket_owned_; + + // Common state enum for all frame helpers + // Note: Not all states are used by all implementations + // - INITIALIZE: Used by both Noise and Plaintext + // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol + // - DATA: Used by both Noise and Plaintext + // - CLOSED: Used by both Noise and Plaintext + // - FAILED: Used by both Noise and Plaintext + // - EXPLICIT_REJECT: Only used by Noise protocol + enum class State : uint8_t { + INITIALIZE = 1, + CLIENT_HELLO = 2, // Noise only + SERVER_HELLO = 3, // Noise only + HANDSHAKE = 4, // Noise only + DATA = 5, + CLOSED = 6, + FAILED = 7, + EXPLICIT_REJECT = 8, // Noise only + }; + + // Containers (size varies, but typically 12+ bytes on 32-bit) + std::deque tx_buf_; + std::string info_; + std::vector reusable_iovs_; + std::vector rx_buf_; + + // Group smaller types together + uint16_t rx_buf_len_ = 0; + State state_{State::INITIALIZE}; uint8_t frame_header_padding_{0}; uint8_t frame_footer_size_{0}; - - // Reusable IOV array for write_protobuf_packets to avoid repeated allocations - std::vector reusable_iovs_; - - // Receive buffer for reading frame data - std::vector rx_buf_; - uint16_t rx_buf_len_ = 0; + // 5 bytes total, 3 bytes padding // Common initialization for both plaintext and noise protocols APIError init_common_(); @@ -213,19 +207,28 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const std::string &reason); + + // Pointers first (4 bytes each) + NoiseHandshakeState *handshake_{nullptr}; + NoiseCipherState *send_cipher_{nullptr}; + NoiseCipherState *recv_cipher_{nullptr}; + + // Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer) + std::shared_ptr ctx_; + + // Vector (12 bytes on 32-bit) + std::vector prologue_; + + // NoiseProtocolId (size depends on implementation) + NoiseProtocolId nid_; + + // Group small types together // Fixed-size header buffer for noise protocol: // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase uint8_t rx_header_buf_[3]; uint8_t rx_header_buf_len_ = 0; - - std::vector prologue_; - - std::shared_ptr ctx_; - NoiseHandshakeState *handshake_{nullptr}; - NoiseCipherState *send_cipher_{nullptr}; - NoiseCipherState *recv_cipher_{nullptr}; - NoiseProtocolId nid_; + // 4 bytes total, no padding }; #endif // USE_API_NOISE @@ -252,6 +255,12 @@ class APIPlaintextFrameHelper : public APIFrameHelper { protected: APIError try_read_frame_(ParsedFrame *frame); + + // Group 2-byte aligned types + uint16_t rx_header_parsed_type_ = 0; + uint16_t rx_header_parsed_len_ = 0; + + // Group 1-byte types together // Fixed-size header buffer for plaintext protocol: // We now store the indicator byte + the two varints. // To match noise protocol's maximum message size (UINT16_MAX = 65535), we need: @@ -263,8 +272,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type) uint8_t rx_header_buf_pos_ = 0; bool rx_header_parsed_ = false; - uint16_t rx_header_parsed_type_ = 0; - uint16_t rx_header_parsed_len_ = 0; + // 8 bytes total, no padding needed }; #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 971c192e4b..33412d8a68 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -142,19 +142,27 @@ class APIServer : public Component, public Controller { } protected: - bool shutting_down_ = false; + // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; - uint16_t port_{6053}; + Trigger *client_connected_trigger_ = new Trigger(); + Trigger *client_disconnected_trigger_ = new Trigger(); + + // 4-byte aligned types uint32_t reboot_timeout_{300000}; uint32_t batch_delay_{100}; uint32_t last_connected_{0}; + + // Vectors and strings (12 bytes each on 32-bit) std::vector> clients_; std::string password_; std::vector shared_write_buffer_; // Shared proto write buffer for all connections std::vector state_subs_; std::vector user_services_; - Trigger *client_connected_trigger_ = new Trigger(); - Trigger *client_disconnected_trigger_ = new Trigger(); + + // Group smaller types together + uint16_t port_{6053}; + bool shutting_down_ = false; + // 3 bytes used, 1 byte padding #ifdef USE_API_NOISE std::shared_ptr noise_ctx_ = std::make_shared(); From 2e11e66db473cf454b93f6dbd115d53e5e4f3b28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 03:11:13 +0200 Subject: [PATCH 10/21] Optimize bluetooth_proxy memory usage on ESP32 (#9114) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../bluetooth_proxy/bluetooth_connection.h | 11 +++++-- .../bluetooth_proxy/bluetooth_proxy.h | 12 +++++-- .../esp32_ble_client/ble_client_base.h | 33 +++++++++++++------ .../esp32_ble_tracker/esp32_ble_tracker.h | 12 ++++--- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index fd83f8dd00..73c034d93b 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -26,10 +26,17 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { protected: friend class BluetoothProxy; - bool seen_mtu_or_services_{false}; - int16_t send_service_{-2}; + // Memory optimized layout for 32-bit systems + // Group 1: Pointers (4 bytes each, naturally aligned) BluetoothProxy *proxy_; + + // Group 2: 2-byte types + int16_t send_service_{-2}; // Needs to handle negative values and service count + + // Group 3: 1-byte types + bool seen_mtu_or_services_{false}; + // 1 byte used, 1 byte padding }; } // namespace bluetooth_proxy diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 16db0a0a11..f0632350e0 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -134,11 +134,17 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com BluetoothConnection *get_connection_(uint64_t address, bool reserve); - bool active_; - - std::vector connections_{}; + // Memory optimized layout for 32-bit systems + // Group 1: Pointers (4 bytes each, naturally aligned) api::APIConnection *api_connection_{nullptr}; + + // Group 2: Container types (typically 12 bytes on 32-bit) + std::vector connections_{}; + + // Group 3: 1-byte types grouped together + bool active_; bool raw_advertisements_{false}; + // 2 bytes used, 2 bytes padding }; extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 814a9664d9..bf3b589b1b 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -96,21 +96,34 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void set_state(espbt::ClientState st) override; protected: - int gattc_if_; - esp_bd_addr_t remote_bda_; - esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; - uint16_t conn_id_{UNSET_CONN_ID}; + // Memory optimized layout for 32-bit systems + // Group 1: 8-byte types uint64_t address_{0}; - bool auto_connect_{false}; + + // Group 2: Container types (grouped for memory optimization) std::string address_str_{}; - uint8_t connection_index_; - int16_t service_count_{0}; - uint16_t mtu_{23}; - bool paired_{false}; - espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; std::vector services_; + + // Group 3: 4-byte types + int gattc_if_; esp_gatt_status_t status_{ESP_GATT_OK}; + // Group 4: Arrays (6 bytes) + esp_bd_addr_t remote_bda_; + + // Group 5: 2-byte types + uint16_t conn_id_{UNSET_CONN_ID}; + uint16_t mtu_{23}; + + // Group 6: 1-byte types and small enums + esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; + espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; + uint8_t connection_index_; + uint8_t service_count_{0}; // ESP32 has max handles < 255, typical devices have < 50 services + bool auto_connect_{false}; + bool paired_{false}; + // 6 bytes used, 2 bytes padding + void log_event_(const char *name); }; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 490ed19645..414c9f4b48 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -129,7 +129,7 @@ class ESPBTDeviceListener { ESP32BLETracker *parent_{nullptr}; }; -enum class ClientState { +enum class ClientState : uint8_t { // Connection is allocated INIT, // Client is disconnecting @@ -165,7 +165,7 @@ enum class ScannerState { STOPPED, }; -enum class ConnectionType { +enum class ConnectionType : uint8_t { // The default connection type, we hold all the services in ram // for the duration of the connection. V1, @@ -193,15 +193,19 @@ class ESPBTClient : public ESPBTDeviceListener { } } ClientState state() const { return state_; } - int app_id; + + // Memory optimized layout + uint8_t app_id; // App IDs are small integers assigned sequentially protected: + // Group 1: 1-byte types ClientState state_{ClientState::INIT}; // want_disconnect_ is set to true when a disconnect is requested // while the client is connecting. This is used to disconnect the // client as soon as we get the connection id (conn_id_) from the // ESP_GATTC_OPEN_EVT event. bool want_disconnect_{false}; + // 2 bytes used, 2 bytes padding }; class ESP32BLETracker : public Component, @@ -262,7 +266,7 @@ class ESP32BLETracker : public Component, /// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed. void set_scanner_state_(ScannerState state); - int app_id_{0}; + uint8_t app_id_{0}; /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; From 8ba22183b962db2664112c772c3b5fbd88f0a7ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 03:30:41 +0200 Subject: [PATCH 11/21] Add enable_loop_soon_any_context() for thread and ISR-safe loop enabling (#9127) --- esphome/core/application.cpp | 94 ++++++++++++++++--- esphome/core/application.h | 3 + esphome/core/component.cpp | 23 ++++- esphome/core/component.h | 31 +++++- .../loop_test_component/__init__.py | 18 ++++ .../loop_test_isr_component.cpp | 80 ++++++++++++++++ .../loop_test_isr_component.h | 32 +++++++ .../fixtures/loop_disable_enable.yaml | 5 + tests/integration/test_loop_disable_enable.py | 59 +++++++++++- 9 files changed, 325 insertions(+), 20 deletions(-) create mode 100644 tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.cpp create mode 100644 tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.h diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 58df49f0f2..49c1e5fd61 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -97,6 +97,20 @@ void Application::loop() { // Feed WDT with time this->feed_wdt(last_op_end_time); + // Process any pending enable_loop requests from ISRs + // This must be done before marking in_loop_ = true to avoid race conditions + if (this->has_pending_enable_loop_requests_) { + // Clear flag BEFORE processing to avoid race condition + // If ISR sets it during processing, we'll catch it next loop iteration + // This is safe because: + // 1. Each component has its own pending_enable_loop_ flag that we check + // 2. If we can't process a component (wrong state), enable_pending_loops_() + // will set this flag back to true + // 3. Any new ISR requests during processing will set the flag again + this->has_pending_enable_loop_requests_ = false; + this->enable_pending_loops_(); + } + // Mark that we're in the loop for safe reentrant modifications this->in_loop_ = true; @@ -286,24 +300,82 @@ void Application::disable_component_loop_(Component *component) { } } +void Application::activate_looping_component_(uint16_t index) { + // Helper to move component from inactive to active section + if (index != this->looping_components_active_end_) { + std::swap(this->looping_components_[index], this->looping_components_[this->looping_components_active_end_]); + } + this->looping_components_active_end_++; +} + void Application::enable_component_loop_(Component *component) { - // This method must be reentrant - components can re-enable themselves during their own loop() call - // Single pass through all components to find and move if needed - // With typical 10-30 components, O(n) is faster than maintaining a map + // This method is only called when component state is LOOP_DONE, so we know + // the component must be in the inactive section (if it exists in looping_components_) + // Only search the inactive portion for better performance + // With typical 0-5 inactive components, O(k) is much faster than O(n) const uint16_t size = this->looping_components_.size(); - for (uint16_t i = 0; i < size; i++) { + for (uint16_t i = this->looping_components_active_end_; i < size; i++) { if (this->looping_components_[i] == component) { - if (i < this->looping_components_active_end_) { - return; // Already active - } // Found in inactive section - move to active - if (i != this->looping_components_active_end_) { - std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]); - } - this->looping_components_active_end_++; + this->activate_looping_component_(i); return; } } + // Component not found in looping_components_ - this is normal for components + // that don't have loop() or were not included in the partitioned vector +} + +void Application::enable_pending_loops_() { + // Process components that requested enable_loop from ISR context + // Only iterate through inactive looping_components_ (typically 0-5) instead of all components + // + // Race condition handling: + // 1. We check if component is already in LOOP state first - if so, just clear the flag + // This handles reentrancy where enable_loop() was called between ISR and processing + // 2. We only clear pending_enable_loop_ after checking state, preventing lost requests + // 3. If any components aren't in LOOP_DONE state, we set has_pending_enable_loop_requests_ + // back to true to ensure we check again next iteration + // 4. ISRs can safely set flags at any time - worst case is we process them next iteration + // 5. The global flag (has_pending_enable_loop_requests_) is cleared before this method, + // so any ISR that fires during processing will be caught in the next loop + const uint16_t size = this->looping_components_.size(); + bool has_pending = false; + + for (uint16_t i = this->looping_components_active_end_; i < size; i++) { + Component *component = this->looping_components_[i]; + if (!component->pending_enable_loop_) { + continue; // Skip components without pending requests + } + + // Check current state + uint8_t state = component->component_state_ & COMPONENT_STATE_MASK; + + // If already in LOOP state, nothing to do - clear flag and continue + if (state == COMPONENT_STATE_LOOP) { + component->pending_enable_loop_ = false; + continue; + } + + // If not in LOOP_DONE state, can't enable yet - keep flag set + if (state != COMPONENT_STATE_LOOP_DONE) { + has_pending = true; // Keep tracking this component + continue; // Keep the flag set - try again next iteration + } + + // Clear the pending flag and enable the loop + component->pending_enable_loop_ = false; + ESP_LOGD(TAG, "%s loop enabled from ISR", component->get_component_source()); + component->component_state_ &= ~COMPONENT_STATE_MASK; + component->component_state_ |= COMPONENT_STATE_LOOP; + + // Move to active section + this->activate_looping_component_(i); + } + + // If we couldn't process some requests, ensure we check again next iteration + if (has_pending) { + this->has_pending_enable_loop_requests_ = true; + } } #ifdef USE_SOCKET_SELECT_SUPPORT diff --git a/esphome/core/application.h b/esphome/core/application.h index ea298638d2..93d5a78958 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -577,6 +577,8 @@ class Application { // to ensure component state is properly updated along with the loop partition void disable_component_loop_(Component *component); void enable_component_loop_(Component *component); + void enable_pending_loops_(); + void activate_looping_component_(uint16_t index); void feed_wdt_arch_(); @@ -682,6 +684,7 @@ class Application { uint32_t loop_interval_{16}; size_t dump_config_at_{SIZE_MAX}; uint8_t app_state_{0}; + volatile bool has_pending_enable_loop_requests_{false}; Component *current_component_{nullptr}; uint32_t loop_component_start_time_{0}; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 3117f49ac1..625a7b2125 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -148,10 +148,12 @@ void Component::mark_failed() { App.disable_component_loop_(this); } void Component::disable_loop() { - ESP_LOGD(TAG, "%s loop disabled", this->get_component_source()); - this->component_state_ &= ~COMPONENT_STATE_MASK; - this->component_state_ |= COMPONENT_STATE_LOOP_DONE; - App.disable_component_loop_(this); + if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP_DONE) { + ESP_LOGD(TAG, "%s loop disabled", this->get_component_source()); + this->component_state_ &= ~COMPONENT_STATE_MASK; + this->component_state_ |= COMPONENT_STATE_LOOP_DONE; + App.disable_component_loop_(this); + } } void Component::enable_loop() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) { @@ -161,6 +163,19 @@ void Component::enable_loop() { App.enable_component_loop_(this); } } +void IRAM_ATTR HOT Component::enable_loop_soon_any_context() { + // This method is thread and ISR-safe because: + // 1. Only performs simple assignments to volatile variables (atomic on all platforms) + // 2. No read-modify-write operations that could be interrupted + // 3. No memory allocation, object construction, or function calls + // 4. IRAM_ATTR ensures code is in IRAM, not flash (required for ISR execution) + // 5. Components are never destroyed, so no use-after-free concerns + // 6. App is guaranteed to be initialized before any ISR could fire + // 7. Multiple ISR/thread calls are safe - just sets the same flags to true + // 8. Race condition with main loop is handled by clearing flag before processing + this->pending_enable_loop_ = true; + App.has_pending_enable_loop_requests_ = true; +} void Component::reset_to_construction_state() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { ESP_LOGI(TAG, "Component %s is being reset to construction state", this->get_component_source()); diff --git a/esphome/core/component.h b/esphome/core/component.h index a37d64086a..7f2bdd8414 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -171,6 +171,27 @@ class Component { */ void enable_loop(); + /** Thread and ISR-safe version of enable_loop() that can be called from any context. + * + * This method defers the actual enable via enable_pending_loops_ to the main loop, + * making it safe to call from ISR handlers, timer callbacks, other threads, + * or any interrupt context. + * + * @note The actual loop enabling will happen on the next main loop iteration. + * @note Only one pending enable request is tracked per component. + * @note There is no disable_loop_soon_any_context() on purpose - it would race + * against enable calls and synchronization would get too complex + * to provide a safe version that would work for each component. + * + * Use disable_loop() from the main thread only. + * + * If you need to disable the loop from ISR, carefully implement + * it in the component itself, with an ISR safe approach, and call + * disable_loop() in its next ::loop() iteration. Implementations + * will need to carefully consider all possible race conditions. + */ + void enable_loop_soon_any_context(); + bool is_failed() const; bool is_ready() const; @@ -331,16 +352,18 @@ class Component { /// Cancel a defer callback using the specified name, name must not be empty. bool cancel_defer(const std::string &name); // NOLINT + // Ordered for optimal packing on 32-bit systems + float setup_priority_override_{NAN}; + const char *component_source_{nullptr}; + const char *error_message_{nullptr}; + uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s) /// State of this component - each bit has a purpose: /// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED) /// Bit 2: STATUS_LED_WARNING /// Bit 3: STATUS_LED_ERROR /// Bits 4-7: Unused - reserved for future expansion (50% of the bits are free) uint8_t component_state_{0x00}; - float setup_priority_override_{NAN}; - const char *component_source_{nullptr}; - uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s) - const char *error_message_{nullptr}; + volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context }; /** This class simplifies creating components that periodically check a state. diff --git a/tests/integration/fixtures/external_components/loop_test_component/__init__.py b/tests/integration/fixtures/external_components/loop_test_component/__init__.py index c5eda67d1e..b66d4598f4 100644 --- a/tests/integration/fixtures/external_components/loop_test_component/__init__.py +++ b/tests/integration/fixtures/external_components/loop_test_component/__init__.py @@ -7,9 +7,13 @@ CODEOWNERS = ["@esphome/tests"] loop_test_component_ns = cg.esphome_ns.namespace("loop_test_component") LoopTestComponent = loop_test_component_ns.class_("LoopTestComponent", cg.Component) +LoopTestISRComponent = loop_test_component_ns.class_( + "LoopTestISRComponent", cg.Component +) CONF_DISABLE_AFTER = "disable_after" CONF_TEST_REDUNDANT_OPERATIONS = "test_redundant_operations" +CONF_ISR_COMPONENTS = "isr_components" COMPONENT_CONFIG_SCHEMA = cv.Schema( { @@ -20,10 +24,18 @@ COMPONENT_CONFIG_SCHEMA = cv.Schema( } ) +ISR_COMPONENT_CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LoopTestISRComponent), + cv.Required(CONF_NAME): cv.string, + } +) + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(LoopTestComponent), cv.Required(CONF_COMPONENTS): cv.ensure_list(COMPONENT_CONFIG_SCHEMA), + cv.Optional(CONF_ISR_COMPONENTS): cv.ensure_list(ISR_COMPONENT_CONFIG_SCHEMA), } ).extend(cv.COMPONENT_SCHEMA) @@ -76,3 +88,9 @@ async def to_code(config): comp_config[CONF_TEST_REDUNDANT_OPERATIONS] ) ) + + # Create ISR test components + for isr_config in config.get(CONF_ISR_COMPONENTS, []): + var = cg.new_Pvariable(isr_config[CONF_ID]) + await cg.register_component(var, isr_config) + cg.add(var.set_name(isr_config[CONF_NAME])) diff --git a/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.cpp b/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.cpp new file mode 100644 index 0000000000..30afec0422 --- /dev/null +++ b/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.cpp @@ -0,0 +1,80 @@ +#include "loop_test_isr_component.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace loop_test_component { + +static const char *const ISR_TAG = "loop_test_isr_component"; + +void LoopTestISRComponent::setup() { + ESP_LOGI(ISR_TAG, "[%s] ISR component setup called", this->name_.c_str()); + this->last_check_time_ = millis(); +} + +void LoopTestISRComponent::loop() { + this->loop_count_++; + ESP_LOGI(ISR_TAG, "[%s] ISR component loop count: %d", this->name_.c_str(), this->loop_count_); + + // Disable after 5 loops + if (this->loop_count_ == 5) { + ESP_LOGI(ISR_TAG, "[%s] Disabling after 5 loops", this->name_.c_str()); + this->disable_loop(); + this->last_disable_time_ = millis(); + // Simulate ISR after disabling + this->set_timeout("simulate_isr_1", 50, [this]() { + ESP_LOGI(ISR_TAG, "[%s] Simulating ISR enable", this->name_.c_str()); + this->simulate_isr_enable(); + // Test reentrancy - call enable_loop() directly after ISR + // This simulates another thread calling enable_loop while processing ISR enables + this->set_timeout("test_reentrant", 10, [this]() { + ESP_LOGI(ISR_TAG, "[%s] Testing reentrancy - calling enable_loop() directly", this->name_.c_str()); + this->enable_loop(); + }); + }); + } + + // If we get here after being disabled, it means ISR re-enabled us + if (this->loop_count_ > 5 && this->loop_count_ < 10) { + ESP_LOGI(ISR_TAG, "[%s] Running after ISR re-enable! ISR was called %d times", this->name_.c_str(), + this->isr_call_count_); + } + + // Disable again after 10 loops to test multiple ISR enables + if (this->loop_count_ == 10) { + ESP_LOGI(ISR_TAG, "[%s] Disabling again after 10 loops", this->name_.c_str()); + this->disable_loop(); + this->last_disable_time_ = millis(); + + // Test pure ISR enable without any main loop enable + this->set_timeout("simulate_isr_2", 50, [this]() { + ESP_LOGI(ISR_TAG, "[%s] Testing pure ISR enable (no main loop enable)", this->name_.c_str()); + this->simulate_isr_enable(); + // DO NOT call enable_loop() - test that ISR alone works + }); + } + + // Log when we're running after second ISR enable + if (this->loop_count_ > 10) { + ESP_LOGI(ISR_TAG, "[%s] Running after pure ISR re-enable! ISR was called %d times total", this->name_.c_str(), + this->isr_call_count_); + } +} + +void IRAM_ATTR LoopTestISRComponent::simulate_isr_enable() { + // This simulates what would happen in a real ISR + // In a real scenario, this would be called from an actual interrupt handler + + this->isr_call_count_++; + + // Call enable_loop_soon_any_context multiple times to test that it's safe + this->enable_loop_soon_any_context(); + this->enable_loop_soon_any_context(); // Test multiple calls + this->enable_loop_soon_any_context(); // Should be idempotent + + // Note: In a real ISR, we cannot use ESP_LOG* macros as they're not ISR-safe + // For testing, we'll track the call count and log it from the main loop +} + +} // namespace loop_test_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.h b/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.h new file mode 100644 index 0000000000..20e11b5ecd --- /dev/null +++ b/tests/integration/fixtures/external_components/loop_test_component/loop_test_isr_component.h @@ -0,0 +1,32 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace loop_test_component { + +class LoopTestISRComponent : public Component { + public: + void set_name(const std::string &name) { this->name_ = name; } + + void setup() override; + void loop() override; + + // Simulates an ISR calling enable_loop_soon_any_context + void simulate_isr_enable(); + + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + std::string name_; + int loop_count_{0}; + uint32_t last_disable_time_{0}; + uint32_t last_check_time_{0}; + bool isr_enable_pending_{false}; + int isr_call_count_{0}; +}; + +} // namespace loop_test_component +} // namespace esphome diff --git a/tests/integration/fixtures/loop_disable_enable.yaml b/tests/integration/fixtures/loop_disable_enable.yaml index 17010f7c34..f19d7f60ca 100644 --- a/tests/integration/fixtures/loop_disable_enable.yaml +++ b/tests/integration/fixtures/loop_disable_enable.yaml @@ -35,6 +35,11 @@ loop_test_component: test_redundant_operations: true disable_after: 10 + # ISR test component that uses enable_loop_soon_any_context + isr_components: + - id: isr_test + name: "isr_test" + # Interval to re-enable the self_disable_10 component after some time interval: - interval: 0.5s diff --git a/tests/integration/test_loop_disable_enable.py b/tests/integration/test_loop_disable_enable.py index 84301c25d8..d5f868aa93 100644 --- a/tests/integration/test_loop_disable_enable.py +++ b/tests/integration/test_loop_disable_enable.py @@ -41,17 +41,25 @@ async def test_loop_disable_enable( redundant_disable_tested = asyncio.Event() # Event fired when self_disable_10 component is re-enabled and runs again (count > 10) self_disable_10_re_enabled = asyncio.Event() + # Events for ISR component testing + isr_component_disabled = asyncio.Event() + isr_component_re_enabled = asyncio.Event() + isr_component_pure_re_enabled = asyncio.Event() # Track loop counts for components self_disable_10_counts: list[int] = [] normal_component_counts: list[int] = [] + isr_component_counts: list[int] = [] def on_log_line(line: str) -> None: """Process each log line from the process output.""" # Strip ANSI color codes clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) - if "loop_test_component" not in clean_line: + if ( + "loop_test_component" not in clean_line + and "loop_test_isr_component" not in clean_line + ): return log_messages.append(clean_line) @@ -92,6 +100,18 @@ async def test_loop_disable_enable( ): redundant_disable_tested.set() + # ISR component events + elif "[isr_test]" in clean_line: + if "ISR component loop count:" in clean_line: + count = int(clean_line.split("ISR component loop count: ")[1]) + isr_component_counts.append(count) + elif "Disabling after 5 loops" in clean_line: + isr_component_disabled.set() + elif "Running after ISR re-enable!" in clean_line: + isr_component_re_enabled.set() + elif "Running after pure ISR re-enable!" in clean_line: + isr_component_pure_re_enabled.set() + # Write, compile and run the ESPHome device with log callback async with ( run_compiled(yaml_config, line_callback=on_log_line), @@ -148,3 +168,40 @@ async def test_loop_disable_enable( assert later_self_disable_counts, ( "self_disable_10 was re-enabled but did not run additional times" ) + + # Test ISR component functionality + # Wait for ISR component to disable itself after 5 loops + try: + await asyncio.wait_for(isr_component_disabled.wait(), timeout=3.0) + except asyncio.TimeoutError: + pytest.fail("ISR component did not disable itself within 3 seconds") + + # Verify it ran exactly 5 times before disabling + first_run_counts = [c for c in isr_component_counts if c <= 5] + assert len(first_run_counts) == 5, ( + f"Expected 5 loops before disable, got {first_run_counts}" + ) + + # Wait for component to be re-enabled by periodic ISR simulation and run again + try: + await asyncio.wait_for(isr_component_re_enabled.wait(), timeout=2.0) + except asyncio.TimeoutError: + pytest.fail("ISR component was not re-enabled after ISR call") + + # Verify it's running again after ISR enable + count_after_isr = len(isr_component_counts) + assert count_after_isr > 5, ( + f"Component didn't run after ISR enable: got {count_after_isr} counts total" + ) + + # Wait for pure ISR enable (no main loop enable) to work + try: + await asyncio.wait_for(isr_component_pure_re_enabled.wait(), timeout=2.0) + except asyncio.TimeoutError: + pytest.fail("ISR component was not re-enabled by pure ISR call") + + # Verify it ran after pure ISR enable + final_count = len(isr_component_counts) + assert final_count > 10, ( + f"Component didn't run after pure ISR enable: got {final_count} counts total" + ) From 40a5638005fe3bcf00401c1e32ed8fa283e8a966 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 03:33:00 +0200 Subject: [PATCH 12/21] Optimize OTA loop to avoid unnecessary stack allocations (#9129) --- .../components/esphome/ota/ota_esphome.cpp | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 227cb676ff..4cc82b9094 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -26,19 +26,19 @@ void ESPHomeOTAComponent::setup() { ota::register_ota_platform(this); #endif - server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections - if (server_ == nullptr) { + this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections + if (this->server_ == nullptr) { ESP_LOGW(TAG, "Could not create socket"); this->mark_failed(); return; } int enable = 1; - int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); if (err != 0) { ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); // we can still continue } - err = server_->setblocking(false); + err = this->server_->setblocking(false); if (err != 0) { ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); this->mark_failed(); @@ -54,14 +54,14 @@ void ESPHomeOTAComponent::setup() { return; } - err = server_->bind((struct sockaddr *) &server, sizeof(server)); + err = this->server_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); return; } - err = server_->listen(4); + err = this->server_->listen(4); if (err != 0) { ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); this->mark_failed(); @@ -82,7 +82,14 @@ void ESPHomeOTAComponent::dump_config() { #endif } -void ESPHomeOTAComponent::loop() { this->handle_(); } +void ESPHomeOTAComponent::loop() { + // Skip handle_() call if no client connected and no incoming connections + // This optimization reduces idle loop overhead when OTA is not active + // Note: No need to check server_ for null as the component is marked failed in setup() if server_ creation fails + if (this->client_ != nullptr || this->server_->ready()) { + this->handle_(); + } +} static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; @@ -101,23 +108,21 @@ void ESPHomeOTAComponent::handle_() { size_t size_acknowledged = 0; #endif - if (client_ == nullptr) { - // Check if the server socket is ready before accepting - if (this->server_->ready()) { - struct sockaddr_storage source_addr; - socklen_t addr_len = sizeof(source_addr); - client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len); - } + if (this->client_ == nullptr) { + // We already checked server_->ready() in loop(), so we can accept directly + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + this->client_ = this->server_->accept((struct sockaddr *) &source_addr, &addr_len); + if (this->client_ == nullptr) + return; } - if (client_ == nullptr) - return; int enable = 1; - int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); if (err != 0) { ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno); - client_->close(); - client_ = nullptr; + this->client_->close(); + this->client_ = nullptr; return; } From 2e9ac8945d62f6ae05970127a6ae50b3538c2382 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Thu, 19 Jun 2025 03:41:20 +0200 Subject: [PATCH 13/21] [nextion] Fix command spacing double timing and response blocking issues (#9134) --- esphome/components/nextion/nextion.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 3de32bfde9..24c31713bc 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -33,6 +33,7 @@ bool Nextion::send_command_(const std::string &command) { #ifdef USE_NEXTION_COMMAND_SPACING if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) { + ESP_LOGN(TAG, "Command spacing: delaying command '%s'", command.c_str()); return false; } #endif // USE_NEXTION_COMMAND_SPACING @@ -43,10 +44,6 @@ bool Nextion::send_command_(const std::string &command) { const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; this->write_array(to_send, sizeof(to_send)); -#ifdef USE_NEXTION_COMMAND_SPACING - this->command_pacer_.mark_sent(); -#endif // USE_NEXTION_COMMAND_SPACING - return true; } @@ -377,12 +374,6 @@ void Nextion::process_nextion_commands_() { size_t commands_processed = 0; #endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP -#ifdef USE_NEXTION_COMMAND_SPACING - if (!this->command_pacer_.can_send()) { - return; // Will try again in next loop iteration - } -#endif - size_t to_process_length = 0; std::string to_process; @@ -430,6 +421,7 @@ void Nextion::process_nextion_commands_() { } #ifdef USE_NEXTION_COMMAND_SPACING this->command_pacer_.mark_sent(); // Here is where we should mark the command as sent + ESP_LOGN(TAG, "Command spacing: marked command sent at %u ms", millis()); #endif break; case 0x02: // invalid Component ID or name was used From d527398dae111ad484dfda0b44f0a5d4042cb0fa Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:50:47 +1200 Subject: [PATCH 14/21] [i2c] Expose internal i2c bus port number (#9136) --- esphome/components/i2c/__init__.py | 6 ++++-- esphome/components/i2c/i2c_bus.h | 8 +++++++- esphome/components/i2c/i2c_bus_arduino.cpp | 5 +++-- esphome/components/i2c/i2c_bus_arduino.h | 9 ++++++--- esphome/components/i2c/i2c_bus_esp_idf.h | 8 +++++--- esphome/core/defines.h | 3 +++ 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index e47dec650d..d56bb2d07c 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -22,8 +22,9 @@ import esphome.final_validate as fv CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") I2CBus = i2c_ns.class_("I2CBus") -ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", I2CBus, cg.Component) -IDFI2CBus = i2c_ns.class_("IDFI2CBus", I2CBus, cg.Component) +InternalI2CBus = i2c_ns.class_("InternalI2CBus", I2CBus) +ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component) +IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") @@ -71,6 +72,7 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(1.0) async def to_code(config): cg.add_global(i2c_ns.using) + cg.add_define("USE_I2C") var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index fbfc88323e..5fa00b9d15 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include #include #include @@ -108,5 +108,11 @@ class I2CBus { bool scan_{false}; ///< Should we scan ? Can be set in the yaml }; +class InternalI2CBus : public I2CBus { + /// @brief Returns the I2C port number. + /// @return the port number of the internal I2C bus + virtual int get_port() const = 0; +}; + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index e9d8c2415c..a85df0a4cd 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -1,11 +1,11 @@ #ifdef USE_ARDUINO #include "i2c_bus_arduino.h" +#include +#include #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include namespace esphome { namespace i2c { @@ -23,6 +23,7 @@ void ArduinoI2CBus::setup() { } else { wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) } + this->port_ = next_bus_num; next_bus_num++; #elif defined(USE_ESP8266) wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory) diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 6a670a2a05..7e6616cbce 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -2,9 +2,9 @@ #ifdef USE_ARDUINO -#include "i2c_bus.h" -#include "esphome/core/component.h" #include +#include "esphome/core/component.h" +#include "i2c_bus.h" namespace esphome { namespace i2c { @@ -15,7 +15,7 @@ enum RecoveryCode { RECOVERY_COMPLETED, }; -class ArduinoI2CBus : public I2CBus, public Component { +class ArduinoI2CBus : public InternalI2CBus, public Component { public: void setup() override; void dump_config() override; @@ -29,12 +29,15 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } void set_timeout(uint32_t timeout) { timeout_ = timeout; } + int get_port() const override { return this->port_; } + private: void recover_(); void set_pins_and_clock_(); RecoveryCode recovery_result_; protected: + int8_t port_{-1}; TwoWire *wire_; uint8_t sda_pin_; uint8_t scl_pin_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index afb4c2d22b..ee29578944 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -2,9 +2,9 @@ #ifdef USE_ESP_IDF -#include "i2c_bus.h" -#include "esphome/core/component.h" #include +#include "esphome/core/component.h" +#include "i2c_bus.h" namespace esphome { namespace i2c { @@ -15,7 +15,7 @@ enum RecoveryCode { RECOVERY_COMPLETED, }; -class IDFI2CBus : public I2CBus, public Component { +class IDFI2CBus : public InternalI2CBus, public Component { public: void setup() override; void dump_config() override; @@ -31,6 +31,8 @@ class IDFI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } void set_timeout(uint32_t timeout) { timeout_ = timeout; } + int get_port() const override { return static_cast(this->port_); } + private: void recover_(); RecoveryCode recovery_result_; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a5d9f45e53..657827c364 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -136,6 +136,7 @@ #define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA +#define USE_I2C #define USE_IMPROV #define USE_MICROPHONE #define USE_PSRAM @@ -179,6 +180,7 @@ #define USE_CAPTIVE_PORTAL #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS +#define USE_I2C #define USE_SOCKET_IMPL_LWIP_TCP #define USE_SPI @@ -195,6 +197,7 @@ #ifdef USE_RP2040 #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 0) +#define USE_I2C #define USE_LOGGER_USB_CDC #define USE_SOCKET_IMPL_LWIP_TCP #define USE_SPI From 0ce3621ac0f6e646e853e1d18c7c7b8591f5e01e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 04:49:31 +0200 Subject: [PATCH 15/21] Disable Ethernet loop polling when connected and stable (#9102) --- esphome/components/ethernet/ethernet_component.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 0a6ba6470e..180a72ec7e 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -274,6 +274,9 @@ void EthernetComponent::loop() { ESP_LOGW(TAG, "Connection lost; reconnecting"); this->state_ = EthernetComponentState::CONNECTING; this->start_connect_(); + } else { + // When connected and stable, disable the loop to save CPU cycles + this->disable_loop(); } break; } @@ -397,11 +400,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base case ETHERNET_EVENT_START: event_name = "ETH started"; global_eth_component->started_ = true; + global_eth_component->enable_loop_soon_any_context(); break; case ETHERNET_EVENT_STOP: event_name = "ETH stopped"; global_eth_component->started_ = false; global_eth_component->connected_ = false; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes break; case ETHERNET_EVENT_CONNECTED: event_name = "ETH connected"; @@ -409,6 +414,7 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base case ETHERNET_EVENT_DISCONNECTED: event_name = "ETH disconnected"; global_eth_component->connected_ = false; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes break; default: return; @@ -425,8 +431,10 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b global_eth_component->got_ipv4_address_ = true; #if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #else global_eth_component->connected_ = true; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #endif /* USE_NETWORK_IPV6 */ } @@ -439,8 +447,10 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_ #if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) global_eth_component->connected_ = global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #else global_eth_component->connected_ = global_eth_component->got_ipv4_address_; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #endif } #endif /* USE_NETWORK_IPV6 */ @@ -620,6 +630,7 @@ bool EthernetComponent::powerdown() { } this->connected_ = false; this->started_ = false; + // No need to enable_loop() here as this is only called during shutdown/reboot if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { ESP_LOGE(TAG, "Error powering down ethernet PHY"); return false; From a9e1a4cef35d9e6a3de6c6d069d37cd3ba2f1c37 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:53:54 +1200 Subject: [PATCH 16/21] Clean up RAMAllocators in audio related code (#9140) --- esphome/components/audio/audio_transfer_buffer.cpp | 4 ++-- .../components/i2s_audio/speaker/i2s_audio_speaker.cpp | 4 ++-- esphome/components/micro_wake_word/streaming_model.cpp | 4 ++-- esphome/components/voice_assistant/voice_assistant.cpp | 8 ++++---- esphome/core/ring_buffer.cpp | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/audio/audio_transfer_buffer.cpp b/esphome/components/audio/audio_transfer_buffer.cpp index 1566884c3d..790cd62db0 100644 --- a/esphome/components/audio/audio_transfer_buffer.cpp +++ b/esphome/components/audio/audio_transfer_buffer.cpp @@ -86,7 +86,7 @@ bool AudioTransferBuffer::reallocate(size_t new_buffer_size) { bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { this->buffer_size_ = buffer_size; - RAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; this->buffer_ = allocator.allocate(this->buffer_size_); if (this->buffer_ == nullptr) { @@ -101,7 +101,7 @@ bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { void AudioTransferBuffer::deallocate_buffer_() { if (this->buffer_ != nullptr) { - RAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; allocator.deallocate(this->buffer_, this->buffer_size_); this->buffer_ = nullptr; this->data_start_ = nullptr; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 41da8a4642..1042a7ebee 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -484,7 +484,7 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size) { if (this->data_buffer_ == nullptr) { // Allocate data buffer for temporarily storing audio from the ring buffer before writing to the I2S bus - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; this->data_buffer_ = allocator.allocate(data_buffer_size); } @@ -698,7 +698,7 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { this->audio_ring_buffer_.reset(); // Releases ownership of the shared_ptr if (this->data_buffer_ != nullptr) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; allocator.deallocate(this->data_buffer_, buffer_size); this->data_buffer_ = nullptr; } diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 31341bba0d..2b073cce56 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -27,7 +27,7 @@ void VADModel::log_model_config() { } bool StreamingModel::load_model_() { - RAMAllocator arena_allocator(RAMAllocator::ALLOW_FAILURE); + RAMAllocator arena_allocator; if (this->tensor_arena_ == nullptr) { this->tensor_arena_ = arena_allocator.allocate(this->tensor_arena_size_); @@ -96,7 +96,7 @@ bool StreamingModel::load_model_() { void StreamingModel::unload_model() { this->interpreter_.reset(); - RAMAllocator arena_allocator(RAMAllocator::ALLOW_FAILURE); + RAMAllocator arena_allocator; if (this->tensor_arena_ != nullptr) { arena_allocator.deallocate(this->tensor_arena_, this->tensor_arena_size_); diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index a692a7556e..366a020d1c 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -85,7 +85,7 @@ bool VoiceAssistant::start_udp_socket_() { bool VoiceAssistant::allocate_buffers_() { #ifdef USE_SPEAKER if ((this->speaker_ != nullptr) && (this->speaker_buffer_ == nullptr)) { - ExternalRAMAllocator speaker_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator speaker_allocator; this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE); if (this->speaker_buffer_ == nullptr) { ESP_LOGW(TAG, "Could not allocate speaker buffer"); @@ -103,7 +103,7 @@ bool VoiceAssistant::allocate_buffers_() { } if (this->send_buffer_ == nullptr) { - ExternalRAMAllocator send_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator send_allocator; this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE); if (send_buffer_ == nullptr) { ESP_LOGW(TAG, "Could not allocate send buffer"); @@ -136,7 +136,7 @@ void VoiceAssistant::clear_buffers_() { void VoiceAssistant::deallocate_buffers_() { if (this->send_buffer_ != nullptr) { - ExternalRAMAllocator send_deallocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator send_deallocator; send_deallocator.deallocate(this->send_buffer_, SEND_BUFFER_SIZE); this->send_buffer_ = nullptr; } @@ -147,7 +147,7 @@ void VoiceAssistant::deallocate_buffers_() { #ifdef USE_SPEAKER if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - ExternalRAMAllocator speaker_deallocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator speaker_deallocator; speaker_deallocator.deallocate(this->speaker_buffer_, SPEAKER_BUFFER_SIZE); this->speaker_buffer_ = nullptr; } diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp index f779531263..b77a02b2a7 100644 --- a/esphome/core/ring_buffer.cpp +++ b/esphome/core/ring_buffer.cpp @@ -14,7 +14,7 @@ static const char *const TAG = "ring_buffer"; RingBuffer::~RingBuffer() { if (this->handle_ != nullptr) { vRingbufferDelete(this->handle_); - RAMAllocator allocator(RAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; allocator.deallocate(this->storage_, this->size_); } } @@ -24,7 +24,7 @@ std::unique_ptr RingBuffer::create(size_t len) { rb->size_ = len; - RAMAllocator allocator(RAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; rb->storage_ = allocator.allocate(rb->size_); if (rb->storage_ == nullptr) { return nullptr; From 9c90ca297a4b226ac7b27c6b09bae106c1629149 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Jun 2025 05:03:09 +0200 Subject: [PATCH 17/21] Fix missing BLE GAP events causing RSSI sensor and beacon failures (#9138) --- esphome/components/esp32_ble/ble.cpp | 97 ++++++++++++++---- esphome/components/esp32_ble/ble_event.h | 120 ++++++++++++++++++++--- 2 files changed, 188 insertions(+), 29 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 5a66f11d0f..cf63ad34d7 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -324,23 +324,69 @@ void ESP32BLE::loop() { } case BLEEvent::GAP: { esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event; - if (gap_event == ESP_GAP_BLE_SCAN_RESULT_EVT) { - // Use the new scan event handler - no memcpy! - for (auto *scan_handler : this->gap_scan_event_handlers_) { - scan_handler->gap_scan_event_handler(ble_event->scan_result()); - } - } else if (gap_event == ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT || - gap_event == ESP_GAP_BLE_SCAN_START_COMPLETE_EVT || - gap_event == ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT) { - // All three scan complete events have the same structure with just status - // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe - // This is verified at compile-time by static_assert checks in ble_event.h - // The struct already contains our copy of the status (copied in BLEEvent constructor) - ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler( - gap_event, reinterpret_cast(&ble_event->event_.gap.scan_complete)); - } + switch (gap_event) { + case ESP_GAP_BLE_SCAN_RESULT_EVT: + // Use the new scan event handler - no memcpy! + for (auto *scan_handler : this->gap_scan_event_handlers_) { + scan_handler->gap_scan_event_handler(ble_event->scan_result()); + } + break; + + // Scan complete events + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + // All three scan complete events have the same structure with just status + // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe + // This is verified at compile-time by static_assert checks in ble_event.h + // The struct already contains our copy of the status (copied in BLEEvent constructor) + ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); + for (auto *gap_handler : this->gap_event_handlers_) { + gap_handler->gap_event_handler( + gap_event, reinterpret_cast(&ble_event->event_.gap.scan_complete)); + } + break; + + // Advertising complete events + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + // All advertising complete events have the same structure with just status + ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); + for (auto *gap_handler : this->gap_event_handlers_) { + gap_handler->gap_event_handler( + gap_event, reinterpret_cast(&ble_event->event_.gap.adv_complete)); + } + break; + + // RSSI complete event + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); + for (auto *gap_handler : this->gap_event_handlers_) { + gap_handler->gap_event_handler( + gap_event, reinterpret_cast(&ble_event->event_.gap.read_rssi_complete)); + } + break; + + // Security events + case ESP_GAP_BLE_AUTH_CMPL_EVT: + case ESP_GAP_BLE_SEC_REQ_EVT: + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: + case ESP_GAP_BLE_PASSKEY_REQ_EVT: + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); + for (auto *gap_handler : this->gap_event_handlers_) { + gap_handler->gap_event_handler( + gap_event, reinterpret_cast(&ble_event->event_.gap.security)); + } + break; + + default: + // Unknown/unhandled event + ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event); + break; } break; } @@ -399,11 +445,26 @@ template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gat void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { - // Only queue the 4 GAP events we actually handle + // Queue GAP events that components need to handle + // Scanning events - used by esp32_ble_tracker case ESP_GAP_BLE_SCAN_RESULT_EVT: case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + // Advertising events - used by esp32_ble_beacon and esp32_ble server + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + // Connection events - used by ble_client + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + // Security events - used by ble_client and bluetooth_proxy + case ESP_GAP_BLE_AUTH_CMPL_EVT: + case ESP_GAP_BLE_SEC_REQ_EVT: + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: + case ESP_GAP_BLE_PASSKEY_REQ_EVT: + case ESP_GAP_BLE_NC_REQ_EVT: enqueue_ble_event(event, param); return; diff --git a/esphome/components/esp32_ble/ble_event.h b/esphome/components/esp32_ble/ble_event.h index 30118d2afd..dd3ec3da42 100644 --- a/esphome/components/esp32_ble/ble_event.h +++ b/esphome/components/esp32_ble/ble_event.h @@ -24,16 +24,45 @@ static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == si "ESP-IDF scan_stop_cmpl structure has unexpected size"); // Verify the status field is at offset 0 (first member) -static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == - offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl), +static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == 0, "status must be first member of scan_param_cmpl"); -static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == - offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl), +static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == 0, "status must be first member of scan_start_cmpl"); -static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == - offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl), +static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == 0, "status must be first member of scan_stop_cmpl"); +// Compile-time verification for advertising complete events +static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_cmpl_evt_param) == sizeof(esp_bt_status_t), + "ESP-IDF adv_data_cmpl structure has unexpected size"); +static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_rsp_data_cmpl_evt_param) == sizeof(esp_bt_status_t), + "ESP-IDF scan_rsp_data_cmpl structure has unexpected size"); +static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_raw_cmpl_evt_param) == sizeof(esp_bt_status_t), + "ESP-IDF adv_data_raw_cmpl structure has unexpected size"); +static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_start_cmpl_evt_param) == sizeof(esp_bt_status_t), + "ESP-IDF adv_start_cmpl structure has unexpected size"); +static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_stop_cmpl_evt_param) == sizeof(esp_bt_status_t), + "ESP-IDF adv_stop_cmpl structure has unexpected size"); + +// Verify the status field is at offset 0 for advertising events +static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_cmpl.status) == 0, + "status must be first member of adv_data_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, scan_rsp_data_cmpl.status) == 0, + "status must be first member of scan_rsp_data_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_raw_cmpl.status) == 0, + "status must be first member of adv_data_raw_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, adv_start_cmpl.status) == 0, + "status must be first member of adv_start_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, adv_stop_cmpl.status) == 0, + "status must be first member of adv_stop_cmpl"); + +// Compile-time verification for RSSI complete event structure +static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.status) == 0, + "status must be first member of read_rssi_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(esp_bt_status_t), + "rssi must immediately follow status in read_rssi_cmpl"); +static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t), + "remote_addr must follow rssi in read_rssi_cmpl"); + // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). // This class stores each event with minimal memory usage. // GAP events (99% of traffic) don't have the vector overhead. @@ -67,6 +96,17 @@ class BLEEvent { GATTS, }; + // Type definitions for cleaner method signatures + struct StatusOnlyData { + esp_bt_status_t status; + }; + + struct RSSICompleteData { + esp_bt_status_t status; + int8_t rssi; + esp_bd_addr_t remote_addr; + }; + // Constructor for GAP events - no external allocations needed BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { this->type_ = GAP; @@ -147,12 +187,21 @@ class BLEEvent { struct gap_event { esp_gap_ble_cb_event_t gap_event; union { - BLEScanResult scan_result; // 73 bytes + BLEScanResult scan_result; // 73 bytes - Used by: esp32_ble_tracker // This matches ESP-IDF's scan complete event structures // All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout - struct { - esp_bt_status_t status; - } scan_complete; // 1 byte + // Used by: esp32_ble_tracker + StatusOnlyData scan_complete; // 1 byte + // Advertising complete events all have same structure + // Used by: esp32_ble_beacon, esp32_ble server components + // ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, ADV_START, ADV_STOP + StatusOnlyData adv_complete; // 1 byte + // RSSI complete event + // Used by: ble_client (ble_rssi_sensor component) + RSSICompleteData read_rssi_complete; // 8 bytes + // Security events - we store the full security union + // Used by: ble_client (automation), bluetooth_proxy, esp32_ble_client + esp_ble_sec_t security; // Variable size, but fits within scan_result size }; } gap; // 80 bytes total @@ -180,6 +229,9 @@ class BLEEvent { esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; } const BLEScanResult &scan_result() const { return event_.gap.scan_result; } esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; } + esp_bt_status_t adv_complete_status() const { return event_.gap.adv_complete.status; } + const RSSICompleteData &read_rssi_complete() const { return event_.gap.read_rssi_complete; } + const esp_ble_sec_t &security() const { return event_.gap.security; } private: // Initialize GAP event data @@ -215,8 +267,47 @@ class BLEEvent { this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status; break; + // Advertising complete events - all have same structure with just status + // Used by: esp32_ble_beacon, esp32_ble server components + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + this->event_.gap.adv_complete.status = p->adv_data_cmpl.status; + break; + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + this->event_.gap.adv_complete.status = p->scan_rsp_data_cmpl.status; + break; + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: // Used by: esp32_ble_beacon + this->event_.gap.adv_complete.status = p->adv_data_raw_cmpl.status; + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: // Used by: esp32_ble_beacon + this->event_.gap.adv_complete.status = p->adv_start_cmpl.status; + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: // Used by: esp32_ble_beacon + this->event_.gap.adv_complete.status = p->adv_stop_cmpl.status; + break; + + // RSSI complete event + // Used by: ble_client (ble_rssi_sensor) + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + this->event_.gap.read_rssi_complete.status = p->read_rssi_cmpl.status; + this->event_.gap.read_rssi_complete.rssi = p->read_rssi_cmpl.rssi; + memcpy(this->event_.gap.read_rssi_complete.remote_addr, p->read_rssi_cmpl.remote_addr, sizeof(esp_bd_addr_t)); + break; + + // Security events - copy the entire security union + // Used by: ble_client, bluetooth_proxy, esp32_ble_client + case ESP_GAP_BLE_AUTH_CMPL_EVT: // Used by: bluetooth_proxy, esp32_ble_client + case ESP_GAP_BLE_SEC_REQ_EVT: // Used by: esp32_ble_client + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Used by: ble_client automation + case ESP_GAP_BLE_PASSKEY_REQ_EVT: // Used by: ble_client automation + case ESP_GAP_BLE_NC_REQ_EVT: // Used by: ble_client automation + memcpy(&this->event_.gap.security, &p->ble_security, sizeof(esp_ble_sec_t)); + break; + default: - // We only handle 4 GAP event types, others are dropped + // We only store data for GAP events that components currently use + // Unknown events still get queued and logged in ble.cpp:375 as + // "Unhandled GAP event type in loop" - this helps identify new events + // that components might need in the future break; } } @@ -295,6 +386,13 @@ class BLEEvent { } }; +// Verify the gap_event struct hasn't grown beyond expected size +// The gap member in the union should be 80 bytes (including the gap_event enum) +static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes"); + +// Verify esp_ble_sec_t fits within our union +static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult"); + // BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding) } // namespace esp32_ble From d4cb4ef99406faf60c1fbfee8d3d58acbef4aff4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:11:18 +1200 Subject: [PATCH 18/21] Clean up RAMAllocators in http_request code (#9143) --- esphome/components/http_request/http_request.h | 2 +- esphome/components/http_request/update/http_request_update.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index a67b04eadc..95515f731a 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -239,7 +239,7 @@ template class HttpRequestSendAction : public Action { std::string response_body; if (this->capture_response_.value(x...)) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; uint8_t *buf = allocator.allocate(max_length); if (buf != nullptr) { size_t read_index = 0; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index d683495ac6..828fb5bd8b 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -54,7 +54,7 @@ void HttpRequestUpdate::update_task(void *params) { UPDATE_RETURN; } - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); From 30bea20f7a1962bc74b37c805a124cd8a2bbd8fd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:17:08 +1200 Subject: [PATCH 19/21] Clean up RAMAllocators in display related code (#9141) --- esphome/components/display/display_buffer.cpp | 2 +- esphome/components/font/font.h | 4 ++-- esphome/components/inkplate6/inkplate.cpp | 4 ++-- esphome/components/nextion/nextion.cpp | 12 ++++++------ .../components/nextion/nextion_upload_arduino.cpp | 8 ++++---- esphome/components/nextion/nextion_upload_idf.cpp | 14 +++++++------- .../waveshare_epaper/waveshare_epaper.cpp | 10 +++++----- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 3af1b63e01..0ecdccc38a 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -11,7 +11,7 @@ namespace display { static const char *const TAG = "display"; void DisplayBuffer::init_internal_(uint32_t buffer_length) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; this->buffer_ = allocator.allocate(buffer_length); if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 9ee23b3ec5..992c77cb9f 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -67,10 +67,10 @@ class Font inline int get_height() { return this->height_; } inline int get_bpp() { return this->bpp_; } - const std::vector> &get_glyphs() const { return glyphs_; } + const std::vector> &get_glyphs() const { return glyphs_; } protected: - std::vector> glyphs_; + std::vector> glyphs_; int baseline_; int height_; uint8_t bpp_; // bits per pixel diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 247aa35ead..b3d0b87e83 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -57,8 +57,8 @@ void Inkplate6::setup() { * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. */ void Inkplate6::initialize_() { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); - ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; + RAMAllocator allocator32; uint32_t buffer_size = this->get_buffer_length_(); if (buffer_size == 0) return; diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 24c31713bc..e6fee10173 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1,8 +1,8 @@ #include "nextion.h" -#include "esphome/core/util.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" #include +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" namespace esphome { namespace nextion { @@ -1003,7 +1003,7 @@ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { } #endif - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; nextion::NextionQueue *nextion_queue = allocator.allocate(1); if (nextion_queue == nullptr) { ESP_LOGW(TAG, "Queue alloc failed"); @@ -1159,7 +1159,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { } #endif - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; nextion::NextionQueue *nextion_queue = allocator.allocate(1); if (nextion_queue == nullptr) { ESP_LOGW(TAG, "Queue alloc failed"); @@ -1191,7 +1191,7 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; nextion::NextionQueue *nextion_queue = allocator.allocate(1); if (nextion_queue == nullptr) { ESP_LOGW(TAG, "Queue alloc failed"); diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index 6652e70172..aa7350bb57 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -3,12 +3,12 @@ #ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_ARDUINO +#include +#include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" -#include "esphome/core/util.h" #include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include +#include "esphome/core/util.h" #ifdef USE_ESP32 #include @@ -52,7 +52,7 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { } // Allocate the buffer dynamically - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; uint8_t *buffer = allocator.allocate(4096); if (!buffer) { ESP_LOGE(TAG, "Buffer alloc failed"); diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index fc98056bc3..43b80f7761 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -3,14 +3,14 @@ #ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_ESP_IDF -#include "esphome/core/application.h" -#include "esphome/core/defines.h" -#include "esphome/core/util.h" -#include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include #include #include +#include +#include "esphome/components/network/util.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" namespace esphome { namespace nextion { @@ -51,7 +51,7 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r } // Allocate the buffer dynamically - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; uint8_t *buffer = allocator.allocate(4096); if (!buffer) { ESP_LOGE(TAG, "Buffer alloc failed"); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 084747c09e..575234e780 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1,9 +1,9 @@ #include "waveshare_epaper.h" +#include +#include #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include namespace esphome { namespace waveshare_epaper { @@ -185,7 +185,7 @@ void WaveshareEPaper7C::setup() { this->initialize(); } void WaveshareEPaper7C::init_internal_7c_(uint32_t buffer_length) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; uint32_t small_buffer_length = buffer_length / NUM_BUFFERS; for (int i = 0; i < NUM_BUFFERS; i++) { @@ -2054,7 +2054,7 @@ void GDEW029T5::initialize() { this->deep_sleep_between_updates_ = true; // old buffer for partial update - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; this->old_buffer_ = allocator.allocate(this->get_buffer_length_()); if (this->old_buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate old buffer for display!"); @@ -2199,7 +2199,7 @@ void GDEW029T5::dump_config() { void GDEW0154M09::initialize() { this->init_internal_(); - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator; this->lastbuff_ = allocator.allocate(this->get_buffer_length_()); if (this->lastbuff_ != nullptr) { memset(this->lastbuff_, 0xff, sizeof(uint8_t) * this->get_buffer_length_()); From 2c17b2bacc2b4540f51ca883cbaebc28b38f20c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:44:33 +1200 Subject: [PATCH 20/21] [i2c] Make ``get_port()`` public (#9146) --- esphome/components/i2c/i2c_bus.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index 5fa00b9d15..5c1e15d814 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -109,6 +109,7 @@ class I2CBus { }; class InternalI2CBus : public I2CBus { + public: /// @brief Returns the I2C port number. /// @return the port number of the internal I2C bus virtual int get_port() const = 0; From 4d0f8528d2e78b2860a43a0bc4fb77bd7d755b73 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:31:19 +1200 Subject: [PATCH 21/21] [esp32_camera] Allow sharing i2c bus (#9137) Co-authored-by: Keith Burzinski --- esphome/components/esp32_camera/__init__.py | 195 ++++++++++-------- .../components/esp32_camera/esp32_camera.cpp | 17 +- .../components/esp32_camera/esp32_camera.h | 16 +- 3 files changed, 134 insertions(+), 94 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index b4038c1841..05522265ae 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,5 +1,6 @@ from esphome import automation, pins import esphome.codegen as cg +from esphome.components import i2c from esphome.components.esp32 import add_idf_component import esphome.config_validation as cv from esphome.const import ( @@ -7,6 +8,7 @@ from esphome.const import ( CONF_CONTRAST, CONF_DATA_PINS, CONF_FREQUENCY, + CONF_I2C_ID, CONF_ID, CONF_PIN, CONF_RESET_PIN, @@ -149,93 +151,104 @@ CONF_ON_IMAGE = "on_image" camera_range_param = cv.int_range(min=-2, max=2) -CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(ESP32Camera), - # pin assignment - cv.Required(CONF_DATA_PINS): cv.All( - [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) - ), - cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( - { - cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, - cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.Range(min=8e6, max=20e6) - ), - } - ), - cv.Required(CONF_I2C_PINS): cv.Schema( - { - cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, - cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, - } - ), - cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, - # image - cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( - FRAME_SIZES, upper=True - ), - cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63), - cv.Optional(CONF_CONTRAST, default=0): camera_range_param, - cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, - cv.Optional(CONF_SATURATION, default=0): camera_range_param, - cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, - cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, - cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( - ENUM_SPECIAL_EFFECT, upper=True - ), - # exposure - cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( - ENUM_GAIN_CONTROL_MODE, upper=True - ), - cv.Optional(CONF_AEC2, default=False): cv.boolean, - cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, - cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), - # gains - cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( - ENUM_GAIN_CONTROL_MODE, upper=True - ), - cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), - cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( - ENUM_GAIN_CEILING, upper=True - ), - # white balance - cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True), - # test pattern - cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, - # framerates - cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( - cv.framerate, cv.Range(min=0, min_included=False, max=60) - ), - cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( - cv.framerate, cv.Range(min=0, max=1) - ), - cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), - cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ESP32CameraStreamStartTrigger - ), - } - ), - cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ESP32CameraStreamStopTrigger - ), - } - ), - cv.Optional(CONF_ON_IMAGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32CameraImageTrigger), - } - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.ENTITY_BASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ESP32Camera), + # pin assignment + cv.Required(CONF_DATA_PINS): cv.All( + [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) + ), + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( + { + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( + cv.frequency, cv.Range(min=8e6, max=20e6) + ), + } + ), + cv.Optional(CONF_I2C_PINS): cv.Schema( + { + cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, + cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, + } + ), + cv.Optional(CONF_I2C_ID): cv.Any( + cv.use_id(i2c.InternalI2CBus), + msg="I2C bus must be an internal ESP32 I2C bus", + ), + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, + # image + cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( + FRAME_SIZES, upper=True + ), + cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63), + cv.Optional(CONF_CONTRAST, default=0): camera_range_param, + cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, + cv.Optional(CONF_SATURATION, default=0): camera_range_param, + cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, + cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( + ENUM_SPECIAL_EFFECT, upper=True + ), + # exposure + cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AEC2, default=False): cv.boolean, + cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, + cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), + # gains + cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), + cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( + ENUM_GAIN_CEILING, upper=True + ), + # white balance + cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum( + ENUM_WB_MODE, upper=True + ), + # test pattern + cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, + # framerates + cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( + cv.framerate, cv.Range(min=0, min_included=False, max=60) + ), + cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( + cv.framerate, cv.Range(min=0, max=1) + ), + cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), + cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStartTrigger + ), + } + ), + cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStopTrigger + ), + } + ), + cv.Optional(CONF_ON_IMAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraImageTrigger + ), + } + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_I2C_PINS, CONF_I2C_ID), +) SETTERS = { # pin assignment @@ -280,8 +293,12 @@ async def to_code(config): extclk = config[CONF_EXTERNAL_CLOCK] cg.add(var.set_external_clock(extclk[CONF_PIN], extclk[CONF_FREQUENCY])) - i2c_pins = config[CONF_I2C_PINS] - cg.add(var.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL])) + if i2c_id := config.get(CONF_I2C_ID): + i2c_hub = await cg.get_variable(i2c_id) + cg.add(var.set_i2c_id(i2c_hub)) + else: + i2c_pins = config[CONF_I2C_PINS] + cg.add(var.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL])) cg.add(var.set_max_update_interval(1000 / config[CONF_MAX_FRAMERATE])) if config[CONF_IDLE_FRAMERATE] == 0: cg.add(var.set_idle_update_interval(0)) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index da0f277358..243d3d3e47 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -1,9 +1,9 @@ #ifdef USE_ESP32 #include "esp32_camera.h" -#include "esphome/core/log.h" -#include "esphome/core/hal.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" #include @@ -16,6 +16,12 @@ static const char *const TAG = "esp32_camera"; void ESP32Camera::setup() { global_esp32_camera = this; +#ifdef USE_I2C + if (this->i2c_bus_ != nullptr) { + this->config_.sccb_i2c_port = this->i2c_bus_->get_port(); + } +#endif + /* initialize time to now */ this->last_update_ = millis(); @@ -246,6 +252,13 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { this->config_.pin_sccb_sda = sda; this->config_.pin_sccb_scl = scl; } +#ifdef USE_I2C +void ESP32Camera::set_i2c_id(i2c::InternalI2CBus *i2c_bus) { + this->i2c_bus_ = i2c_bus; + this->config_.pin_sccb_sda = -1; + this->config_.pin_sccb_scl = -1; +} +#endif // USE_I2C void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index d5fe48c2a7..75139ba400 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -2,13 +2,17 @@ #ifdef USE_ESP32 +#include +#include +#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" -#include -#include -#include + +#ifdef USE_I2C +#include "esphome/components/i2c/i2c_bus.h" +#endif // USE_I2C namespace esphome { namespace esp32_camera { @@ -118,6 +122,9 @@ class ESP32Camera : public EntityBase, public Component { void set_pixel_clock_pin(uint8_t pin); void set_external_clock(uint8_t pin, uint32_t frequency); void set_i2c_pins(uint8_t sda, uint8_t scl); +#ifdef USE_I2C + void set_i2c_id(i2c::InternalI2CBus *i2c_bus); +#endif // USE_I2C void set_reset_pin(uint8_t pin); void set_power_down_pin(uint8_t pin); /* -- image */ @@ -210,6 +217,9 @@ class ESP32Camera : public EntityBase, public Component { uint32_t last_idle_request_{0}; uint32_t last_update_{0}; +#ifdef USE_I2C + i2c::InternalI2CBus *i2c_bus_{nullptr}; +#endif // USE_I2C }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)