From 89cc689864718a1a29c691d8487be963deeb9cf6 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 28 Sep 2026 22:08:06 +0100 Subject: [PATCH] [esp32_hosted][esp32_ble] Support esp_hosted 3.x alongside 2.x, keep builds on 2.12.13 for now (#19621) --- esphome/components/esp32_ble/__init__.py | 9 +- esphome/components/esp32_ble/ble.cpp | 44 +++- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 6 +- .../esp32_ble_beacon/esp32_ble_beacon.h | 6 +- .../esp32_ble_server/ble_server.cpp | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 6 +- esphome/components/esp32_hosted/__init__.py | 219 ++++++++++++++++-- .../update/esp32_hosted_update.cpp | 16 +- esphome/idf_component.yml | 2 +- 9 files changed, 259 insertions(+), 51 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 77ec17f389..ef95243bc1 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -474,12 +474,17 @@ def final_validation(config: ConfigType) -> None: # Check if hosted bluetooth is being used if "esp32_hosted" in full_config: + from esphome.components.esp32_hosted import uses_esp_hosted_3x + add_idf_sdkconfig_option("CONFIG_BT_CLASSIC_ENABLED", False) add_idf_sdkconfig_option("CONFIG_BT_BLE_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_BLUEDROID_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_CONTROLLER_DISABLED", True) - add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID", True) - add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI", True) + if uses_esp_hosted_3x(): + add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_FEAT_BT", True) + else: + add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID", True) + add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI", True) # Check if BLE Client is needed (via esp32_ble_tracker or esp32_ble_client) has_ble_client = ( diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 81fa328c16..a44886b8c8 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -6,16 +6,21 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED #include #else #include "esphome/components/watchdog/watchdog.h" #include extern "C" { #include +#ifndef CONFIG_ESP_HOSTED_HOST_FEAT_BT #include #include +#endif } +#ifdef CONFIG_ESP_HOSTED_HOST_FEAT_BT +#include +#endif #endif #include #include @@ -35,7 +40,7 @@ namespace esphome::esp32_ble { static const char *const TAG = "esp32_ble"; -#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifdef CONFIG_BT_CONTROLLER_DISABLED // Bringing up the remote BT controller issues synchronous RPCs to the // co-processor with 5 second response timeouts, and the default task watchdog // is also 5 seconds. If the co-processor firmware does not answer (for example @@ -43,8 +48,9 @@ static const char *const TAG = "esp32_ble"; // device before the RPC could return an error, causing a boot loop. Raise the // watchdog for the duration of the bring-up so failures surface as error // returns instead. 60 seconds covers the worst case: transport reconnect -// (up to ~20s), version preflight (1s), controller init/enable (5s each) and -// the bluedroid host bring-up over the hosted HCI transport. +// (up to ~20s), version preflight (1s), the controller init retry window +// (5s of 5s RPCs), controller enable (5s) and the bluedroid host bring-up +// over the hosted HCI transport. static constexpr uint32_t HOSTED_BT_WDT_TIMEOUT_MS = 60000; #endif @@ -202,10 +208,10 @@ void ESP32BLE::advertising_init_() { bool ESP32BLE::ble_setup_() { esp_err_t err; -#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifdef CONFIG_BT_CONTROLLER_DISABLED watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS); #endif -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -239,8 +245,7 @@ bool ESP32BLE::ble_setup_() { } // Fast preflight (1 second RPC timeout): verifies the co-processor answers - // RPCs at all before the 5 second timeout BT controller RPCs below, and - // before hosted_hci_bluedroid_open(), which aborts if the transport is down. + // RPCs at all before the 5 second timeout BT controller RPCs below. esp_hosted_coprocessor_fwver_t fw_ver{}; if (esp_hosted_get_coprocessor_fwversion(&fw_ver) != ESP_OK) { ESP_LOGE(TAG, "Co-processor not responding; BLE disabled. Update its firmware with the esp32_hosted " @@ -249,6 +254,19 @@ bool ESP32BLE::ble_setup_() { } ESP_LOGD(TAG, "Co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32, fw_ver.major1, fw_ver.minor1, fw_ver.patch1); +#ifdef CONFIG_ESP_HOSTED_HOST_FEAT_BT + esp_hosted_bt_host_stack_cfg_t bt_cfg{}; + bt_cfg.stack = ESP_HOSTED_BT_HOST_STACK_BLUEDROID; + bt_cfg.bring_up_controller = true; + bt_cfg.controller_ready_timeout_ms = EH_BT_CTRL_DEFAULT_READY_TIMEOUT_MS; + if (esp_hosted_bt_host_stack_setup(&bt_cfg) != ESP_OK) { + ESP_LOGE(TAG, + "BT controller bring-up failed; co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32 + " may lack BT support. Update it with the esp32_hosted update component; BLE disabled", + fw_ver.major1, fw_ver.minor1, fw_ver.patch1); + return false; + } +#else if (esp_hosted_bt_controller_init() != ESP_OK) { ESP_LOGE(TAG, "BT controller init failed; co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32 @@ -273,6 +291,7 @@ bool ESP32BLE::ble_setup_() { .register_host_callback = hosted_hci_bluedroid_register_host_callback, }; esp_bluedroid_attach_hci_driver(&operations); +#endif #endif err = esp_bluedroid_init(); @@ -393,7 +412,7 @@ bool ESP32BLE::ble_setup_() { } bool ESP32BLE::ble_dismantle_() { -#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifdef CONFIG_BT_CONTROLLER_DISABLED // Same 5 second RPCs as the bring-up path; see HOSTED_BT_WDT_TIMEOUT_MS watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS); #endif @@ -416,7 +435,7 @@ bool ESP32BLE::ble_dismantle_() { ESP_LOGD(TAG, "Already deinitialized"); } -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { // stop bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) { @@ -440,6 +459,11 @@ bool ESP32BLE::ble_dismantle_() { return false; } } +#elif defined(CONFIG_ESP_HOSTED_HOST_FEAT_BT) + if (esp_hosted_bt_host_stack_teardown() != ESP_OK) { + ESP_LOGE(TAG, "esp_hosted_bt_host_stack_teardown failed"); + return false; + } #else if (esp_hosted_bt_controller_disable() != ESP_OK) { ESP_LOGE(TAG, "esp_hosted_bt_controller_disable failed"); diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index ab728f9f6f..47e4b7ebb2 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -3,7 +3,7 @@ #ifdef USE_ESP32 -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED #include #endif #include @@ -35,7 +35,7 @@ void ESP32BLEBeacon::dump_config() { } } *bpos = '\0'; -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED ESP_LOGCONFIG(TAG, " UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" ", TX Power: %ddBm", @@ -81,7 +81,7 @@ void ESP32BLEBeacon::on_advertise_() { ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast(this->measured_power_); esp_err_t err; -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED ESP_LOGD(TAG, "Setting BLE TX power"); err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, this->tx_power_); if (err != ESP_OK) { diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index 986778de57..a7132a5465 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -5,7 +5,7 @@ #ifdef USE_ESP32 -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED #include #endif #include @@ -47,7 +47,7 @@ class ESP32BLEBeacon final : public Component { void set_min_interval(uint16_t val) { this->min_interval_ = val; } void set_max_interval(uint16_t val) { this->max_interval_ = val; } void set_measured_power(int8_t val) { this->measured_power_ = val; } -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; } #endif void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -61,7 +61,7 @@ class ESP32BLEBeacon final : public Component { uint16_t min_interval_{}; uint16_t max_interval_{}; int8_t measured_power_{}; -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED esp_power_level_t tx_power_{}; #endif esp_ble_adv_params_t ble_adv_params_; diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 45679b9b98..e34a57cf37 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -10,7 +10,7 @@ #include #include #include -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED #include #endif #include diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e25b6f59fa..9bb90026e3 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -7,7 +7,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED #include #endif #include @@ -635,7 +635,7 @@ void ESP32BLETracker::log_unexpected_state_(const char *operation, ScannerState #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE void ESP32BLETracker::update_coex_preference_(bool force_ble) { -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#ifndef CONFIG_BT_CONTROLLER_DISABLED if (force_ble && !this->coex_prefer_ble_) { ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection."); this->coex_prefer_ble_ = true; @@ -645,7 +645,7 @@ void ESP32BLETracker::update_coex_preference_(bool force_ble) { this->coex_prefer_ble_ = false; esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default } -#endif // CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID +#endif // CONFIG_BT_CONTROLLER_DISABLED } #endif diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index ad01e130eb..d78941ea4c 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -1,5 +1,6 @@ import os from pathlib import Path +import re from esphome import pins from esphome.components import esp32 @@ -7,15 +8,22 @@ from esphome.components.const import CONF_SLOT, CONF_USE_PSRAM import esphome.config_validation as cv from esphome.const import ( CONF_CLK_PIN, + CONF_COMPONENTS, CONF_CS_PIN, + CONF_FRAMEWORK, CONF_FREQUENCY, CONF_MISO_PIN, CONF_MOSI_PIN, + CONF_NAME, + CONF_REF, CONF_RESET_PIN, CONF_TYPE, CONF_VARIANT, + KEY_ESP32, ) +from esphome.core import CORE from esphome.cpp_generator import add_define +import esphome.final_validate as fv from esphome.types import ConfigType CODEOWNERS = ["@swoboda1337"] @@ -23,6 +31,9 @@ DEPENDENCIES = ["esp32"] # esp32_ble raises the task watchdog around the remote BT controller bring-up AUTO_LOAD = ["watchdog"] +# 3.x is opt-in via a pin under esp32.framework.components (needs ESP-IDF 5.5) +ESP_HOSTED_COMPONENT = "espressif/esp_hosted" + CONF_ACTIVE_HIGH = "active_high" CONF_BUS_WIDTH = "bus_width" CONF_CMD_PIN = "cmd_pin" @@ -145,6 +156,34 @@ CONFIG_SCHEMA = cv.typed_schema( ) +# Version specs that stay within one major (exact, ^, ~, ~=); other ranges are 3.x +_PINNED_MAJOR = re.compile(r"^(?:==|\^|~=|~)?(\d+)(?:\.(?:\d+|\*))*$") + + +def user_esp_hosted_major() -> int | None: + """Major version of a user-pinned esp_hosted, or None without a pin.""" + try: + full_config = fv.full_config.get() + except LookupError: + full_config = CORE.config + esp32_config = full_config.get(KEY_ESP32) or {} + for component in esp32_config.get(CONF_FRAMEWORK, {}).get(CONF_COMPONENTS, []): + if component.get(CONF_NAME) != ESP_HOSTED_COMPONENT: + continue + ref = component.get(CONF_REF, "").strip() + if not ref: + return 2 + if match := _PINNED_MAJOR.match(ref): + return int(match.group(1)) + return 3 + return None + + +def uses_esp_hosted_3x() -> bool: + """Whether the user pinned esp_hosted 3.x.""" + return (major := user_esp_hosted_major()) is not None and major >= 3 + + def _final_validate(config: ConfigType) -> None: # The esp_hosted releases compatible with older ESP-IDF versions crash at # boot with a heap double free in the SDIO RX path (fixed in esp_hosted @@ -155,12 +194,34 @@ def _final_validate(config: ConfigType) -> None: "Remove the framework version from your configuration to use the " "recommended version, or pin a version at or above 5.3." ) + if (major := user_esp_hosted_major()) is None or major < 3: + return + if idf_ver < cv.Version(5, 5, 0): + raise cv.Invalid( + f"esp_hosted 3.x requires ESP-IDF 5.5 or newer, got {idf_ver}. " + f"Remove the {ESP_HOSTED_COMPONENT} pin from esp32.framework." + "components to stay on the 2.x line, or use ESP-IDF 5.5 or newer." + ) + if not config[CONF_ACTIVE_HIGH]: + raise cv.Invalid( + "esp_hosted 3.x always parks the reset line high with a low pulse, so " + "'active_high: false' cannot be expressed on it. Remove the " + f"{ESP_HOSTED_COMPONENT} pin from esp32.framework.components to stay " + "on the 2.x line." + ) + if config[CONF_TYPE] == "sdio" and config[CONF_BUS_WIDTH] == 1: + raise cv.Invalid( + "esp_hosted 3.x cannot be built with a 1-bit SDIO bus " + "(espressif/esp-hosted-mcu#245). Remove the " + f"{ESP_HOSTED_COMPONENT} pin from esp32.framework.components to stay " + "on the 2.x line, or use a 4-bit bus." + ) FINAL_VALIDATE_SCHEMA = _final_validate -def _configure_sdio(config: ConfigType) -> None: +def _configure_sdio_2x(config: ConfigType) -> None: slot = config[CONF_SLOT] esp32.add_idf_sdkconfig_option( f"CONFIG_ESP_HOSTED_SDIO_SLOT_{slot}", @@ -202,7 +263,42 @@ def _configure_sdio(config: ConfigType) -> None: ) -def _configure_spi(config: ConfigType) -> None: +def _configure_sdio_3x(config: ConfigType) -> None: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_TRANSPORT_BUS_SDIO", True) + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_SDIO_SLOT_{config[CONF_SLOT]}", + True, + ) + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_HOST_SDIO_BUS_WIDTH_{config[CONF_BUS_WIDTH]}", + True, + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_CLK", config[CONF_CLK_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_CMD", config[CONF_CMD_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_D0", config[CONF_D0_PIN] + ) + if config[CONF_BUS_WIDTH] == 4: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_D1", config[CONF_D1_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_D2", config[CONF_D2_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_PIN_D3", config[CONF_D3_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SDIO_CLK_KHZ", + int(config[CONF_SDIO_FREQUENCY] // 1000), + ) + + +def _configure_spi_2x(config: ConfigType) -> None: esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE", True) # SPI mode is set via per-variant choice options variant = config[CONF_VARIANT] @@ -250,11 +346,53 @@ def _configure_spi(config: ConfigType) -> None: esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_DR_ACTIVE_LOW", True) -async def to_code(config: ConfigType) -> None: - add_define("USE_ESP32_HOSTED") - transport = config[CONF_TYPE] - transport_prefix = "SDIO" if transport == "sdio" else "SPI" +def _configure_spi_3x(config: ConfigType) -> None: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_TRANSPORT_BUS_SPI", True) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_MODE", config[CONF_SPI_MODE] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_CLK_MHZ", int(config[CONF_FREQUENCY] // 1e6) + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_MOSI_GPIO", config[CONF_MOSI_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_MISO_GPIO", config[CONF_MISO_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_CLK_GPIO", config[CONF_CLK_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_SPI_CS_GPIO", config[CONF_CS_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_HANDSHAKE_GPIO", config[CONF_HANDSHAKE_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_DATA_READY_GPIO", config[CONF_DATA_READY_PIN] + ) + # Handshake and data_ready polarity + if config[CONF_HANDSHAKE_ACTIVE_HIGH]: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HANDSHAKE_ACTIVE_HIGH", True + ) + else: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HANDSHAKE_ACTIVE_LOW", True + ) + if config[CONF_DATA_READY_ACTIVE_HIGH]: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_DATAREADY_ACTIVE_HIGH", True + ) + else: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_DATAREADY_ACTIVE_LOW", True + ) + +def _configure_2x(config: ConfigType) -> None: + transport_prefix = "SDIO" if config[CONF_TYPE] == "sdio" else "SPI" # Reset polarity if config[CONF_ACTIVE_HIGH]: esp32.add_idf_sdkconfig_option( @@ -274,24 +412,11 @@ async def to_code(config: ConfigType) -> None: f"CONFIG_SLAVE_IDF_TARGET_{config[CONF_VARIANT]}", # NOLINT True, ) - - # Transport-specific configuration - if transport == "sdio": - _configure_sdio(config) + if config[CONF_TYPE] == "sdio": + _configure_sdio_2x(config) else: - _configure_spi(config) - - # ESP-NOW-over-hosted shim: only the radio-less ESP32-P4 host needs it (see - # the note by _MAX_CUSTOM_MSG_HANDLERS). Enabled for every P4 host, not - # gated on the `espnow` component being present: the shim is tiny and the - # esp_now_* symbols/CustomRpc calls it defines require these Kconfig options - # to link whenever esp_now_hosted.cpp compiles (which is on any P4 host), so - # coupling the two keeps the build consistent. When `espnow` is absent the - # symbols are simply unused and never register a callback at runtime. + _configure_spi_2x(config) if esp32.get_esp32_variant() == esp32.VARIANT_ESP32P4: - add_define("USE_ESP_NOW_HOSTED") - # esp_now_hosted.cpp includes esp_now.h, which esp_wifi provides - esp32.include_builtin_idf_component("esp_wifi") # esp-hosted's CustomRpc ("peer data transfer") path — off by default. esp32.add_idf_sdkconfig_option( "CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER", True @@ -299,18 +424,64 @@ async def to_code(config: ConfigType) -> None: esp32.add_idf_sdkconfig_option( "CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS", _MAX_CUSTOM_MSG_HANDLERS ) - # Place the transport mempool in PSRAM. Required on memory-tight host # configurations (e.g. P4 with a large LVGL UI) where the internal-RAM # mempool allocation fails at boot with `sdio_mempool_create` assert. if config[CONF_USE_PSRAM]: esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_MEMPOOL_PREFER_SPIRAM", True) + +def _configure_3x(config: ConfigType) -> None: + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_RESET_GPIO", config[CONF_RESET_PIN] + ) + # Co-processor variant + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_CP_TARGET_{config[CONF_VARIANT]}", + True, + ) + if config[CONF_TYPE] == "sdio": + _configure_sdio_3x(config) + else: + _configure_spi_3x(config) + if esp32.get_esp32_variant() == esp32.VARIANT_ESP32P4: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_FEAT_PEER_DATA", True) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_HOST_FEAT_PEER_DATA_MAX_CUSTOM_MSG_HANDLERS", + _MAX_CUSTOM_MSG_HANDLERS, + ) + if config[CONF_USE_PSRAM]: + esp32.add_idf_sdkconfig_option("CONFIG_EH_HOST_PORT_DMA_PREFER_SPIRAM", True) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_DFLT_TASK_FROM_SPIRAM", True) + + +async def to_code(config: ConfigType) -> None: + add_define("USE_ESP32_HOSTED") + use_3x = uses_esp_hosted_3x() + + if use_3x: + _configure_3x(config) + else: + _configure_2x(config) + + # ESP-NOW-over-hosted shim: only the radio-less ESP32-P4 host needs it (see + # the note by _MAX_CUSTOM_MSG_HANDLERS). Enabled for every P4 host, not + # gated on the `espnow` component being present: the shim is tiny and the + # esp_now_* symbols/CustomRpc calls it defines require the peer-data Kconfig + # options (set above) to link whenever esp_now_hosted.cpp compiles (which is + # on any P4 host), so coupling the two keeps the build consistent. When + # `espnow` is absent the symbols are simply unused and never register a + # callback at runtime. + if esp32.get_esp32_variant() == esp32.VARIANT_ESP32P4: + add_define("USE_ESP_NOW_HOSTED") + # esp_now_hosted.cpp includes esp_now.h, which esp_wifi provides + esp32.include_builtin_idf_component("esp_wifi") + # Library versions; this component set requires ESP-IDF 5.3 or newer, # which is enforced at validation time. idf_ver = esp32.idf_version() os.environ["ESP_IDF_VERSION"] = f"{idf_ver.major}.{idf_ver.minor}" - esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.6.3") + esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.6.5") esp32.add_idf_component(name="espressif/wifi_remote_over_eppp", ref="0.3.3") esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.5") esp32.add_idf_component(name="espressif/esp_hosted", ref="2.12.13") diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index d9b375dd20..c35301a685 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -26,6 +26,8 @@ static const char *const TAG = "esp32_hosted.update"; // Older coprocessor firmware versions have a 1500-byte limit per RPC call constexpr size_t CHUNK_SIZE = 1500; +// OTA begin blocks while the coprocessor erases its partition +constexpr uint32_t OTA_WDT_TIMEOUT_MS = 60000; #ifdef USE_ESP32_HOSTED_HTTP_UPDATE // Interval/timeout IDs (uint32_t to avoid string comparison) @@ -336,7 +338,11 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() { ESP_LOGI(TAG, "Firmware size: %zu bytes", total_size); // Begin OTA on coprocessor - esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT + esp_err_t err; + { + watchdog::WatchdogManager wdt(OTA_WDT_TIMEOUT_MS); + err = esp_hosted_slave_ota_begin(); // NOLINT + } if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); container->end(); @@ -425,7 +431,11 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() { ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_); - esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT + esp_err_t err; + { + watchdog::WatchdogManager wdt(OTA_WDT_TIMEOUT_MS); + err = esp_hosted_slave_ota_begin(); // NOLINT + } if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); this->status_set_error(LOG_STR("Failed to begin OTA")); @@ -472,8 +482,6 @@ void Esp32HostedUpdate::perform(bool force) { this->update_info_.has_progress = false; this->publish_state(); - watchdog::WatchdogManager watchdog(60000); - #ifdef USE_ESP32_HOSTED_HTTP_UPDATE if (!this->stream_firmware_to_coprocessor_()) #else diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 65d56495af..1a972896c3 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -26,7 +26,7 @@ dependencies: espressif/mdns: version: 1.12.0 espressif/esp_wifi_remote: - version: 1.6.3 + version: 1.6.5 rules: - if: "target in [esp32h2, esp32p4]" espressif/wifi_remote_over_eppp: