diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 224b30aa3f..93bb643596 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -117,22 +117,9 @@ CONF_BLE_ID = "ble_id" CONF_IO_CAPABILITY = "io_capability" CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time" CONF_DISABLE_BT_LOGS = "disable_bt_logs" -CONF_PREFERRED_PHY = "preferred_phy" NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] -# ESP32 variants that support BLE -BLE_VARIANTS = { - const.VARIANT_ESP32, - const.VARIANT_ESP32C3, - const.VARIANT_ESP32S3, - const.VARIANT_ESP32C6, - const.VARIANT_ESP32H2, -} - -# ESP32 variants that support 2M PHY -BLE_2M_PHY_VARIANTS = BLE_VARIANTS - {const.VARIANT_ESP32} - esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) @@ -153,13 +140,6 @@ IO_CAPABILITY = { "display_yes_no": IoCapability.IO_CAP_IO, } -BLEPhy = esp32_ble_ns.enum("BLEPhy") -BLE_PHY_OPTIONS = { - "1m": BLEPhy.BLE_PHY_1M, - "2m": BLEPhy.BLE_PHY_2M, - "auto": BLEPhy.BLE_PHY_AUTO, -} - esp_power_level_t = cg.global_ns.enum("esp_power_level_t") TX_POWER_LEVELS = { @@ -173,18 +153,6 @@ TX_POWER_LEVELS = { 9: esp_power_level_t.ESP_PWR_LVL_P9, } - -def validate_phy(value: str) -> str: - """Validate PHY selection based on ESP32 variant.""" - variant = get_esp32_variant() - if value == "2m" and variant not in BLE_2M_PHY_VARIANTS: - raise cv.Invalid( - f"2M PHY is not supported on {variant}. " - f"Only supported on: {', '.join(sorted(BLE_2M_PHY_VARIANTS))}" - ) - return value - - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), @@ -199,10 +167,6 @@ CONFIG_SCHEMA = cv.Schema( cv.SplitDefault(CONF_DISABLE_BT_LOGS, esp32_idf=True): cv.All( cv.only_with_esp_idf, cv.boolean ), - cv.Optional(CONF_PREFERRED_PHY, default="1m"): cv.All( - cv.enum(BLE_PHY_OPTIONS, lower=True), - validate_phy, - ), } ).extend(cv.COMPONENT_SCHEMA) @@ -273,7 +237,6 @@ async def to_code(config): cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) - cg.add(var.set_preferred_phy(config[CONF_PREFERRED_PHY])) if (name := config.get(CONF_NAME)) is not None: cg.add(var.set_name(name)) await cg.register_component(var, config) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index f953ccd1f4..33258552c7 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -23,35 +23,6 @@ namespace esphome::esp32_ble { static const char *const TAG = "esp32_ble"; -static const char *phy_mode_to_string(BLEPhy phy) { - switch (phy) { - case BLE_PHY_1M: - return "1M"; - case BLE_PHY_2M: - return "2M"; - case BLE_PHY_AUTO: - return "AUTO"; - default: - return "UNKNOWN"; - } -} - -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32H2) -static uint8_t phy_mode_to_mask(BLEPhy phy) { - switch (phy) { - case BLE_PHY_1M: - return ESP_BLE_GAP_PHY_1M_PREF_MASK; - case BLE_PHY_2M: - return ESP_BLE_GAP_PHY_2M_PREF_MASK; - case BLE_PHY_AUTO: - return ESP_BLE_GAP_PHY_1M_PREF_MASK | ESP_BLE_GAP_PHY_2M_PREF_MASK; - default: - return ESP_BLE_GAP_PHY_1M_PREF_MASK; // Default to 1M - } -} -#endif - void ESP32BLE::setup() { global_ble = this; if (!ble_pre_setup_()) { @@ -237,23 +208,6 @@ bool ESP32BLE::ble_setup_() { return false; } - // Configure PHY settings -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32H2) - // Only newer ESP32 variants support PHY configuration - if (this->preferred_phy_ != BLE_PHY_AUTO) { - uint8_t phy_mask = phy_mode_to_mask(this->preferred_phy_); - - err = esp_ble_gap_set_preferred_default_phy(phy_mask, phy_mask); - if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_ble_gap_set_preferred_default_phy failed: %d", err); - // Not a fatal error, continue - } else { - ESP_LOGD(TAG, "Set preferred PHY to %s", phy_mode_to_string(this->preferred_phy_)); - } - } -#endif - // BLE takes some time to be fully set up, 200ms should be more than enough delay(200); // NOLINT @@ -563,10 +517,8 @@ void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "BLE:\n" " MAC address: %s\n" - " IO Capability: %s\n" - " Preferred PHY: %s", - format_mac_address_pretty(mac_address).c_str(), io_capability_s, - phy_mode_to_string(this->preferred_phy_)); + " IO Capability: %s", + format_mac_address_pretty(mac_address).c_str(), io_capability_s); } else { ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); } diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index abe0d57aa0..543b2f26a3 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -55,12 +55,6 @@ enum IoCapability { IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, }; -enum BLEPhy : uint8_t { - BLE_PHY_1M = 0x01, - BLE_PHY_2M = 0x02, - BLE_PHY_AUTO = 0x03, -}; - enum BLEComponentState : uint8_t { /** Nothing has been initialized yet. */ BLE_COMPONENT_STATE_OFF = 0, @@ -104,7 +98,6 @@ class BLEStatusEventHandler { class ESP32BLE : public Component { public: void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } - void set_preferred_phy(BLEPhy phy) { this->preferred_phy_ = phy; } void set_advertising_cycle_time(uint32_t advertising_cycle_time) { this->advertising_cycle_time_ = advertising_cycle_time; @@ -177,7 +170,6 @@ class ESP32BLE : public Component { // 1-byte aligned members (grouped together to minimize padding) BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum) bool enable_on_boot_{}; // 1 byte - BLEPhy preferred_phy_{BLE_PHY_1M}; // 1 byte (uint8_t enum) }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index bf425b3730..0d48c1f163 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -5,6 +5,8 @@ #ifdef USE_ESP32 +#include + namespace esphome { namespace esp32_ble_client { @@ -129,6 +131,25 @@ void BLEClientBase::connect() { ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), this->remote_addr_type_); this->paired_ = false; + + // For connections without cache, set fast connection parameters before connecting + // This ensures service discovery completes within the 10-second timeout that + // some devices like HomeKit BLE sensors enforce + if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + auto ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, + 0x06, // min_int: 7.5ms + 0x06, // max_int: 7.5ms + 0, // latency: 0 + 1000); // timeout: 10s + if (ret != ESP_OK) { + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_, + this->address_str_.c_str(), ret); + } else { + ESP_LOGD(TAG, "[%d] [%s] Set preferred connection params for fast discovery (no cache)", this->connection_index_, + this->address_str_.c_str()); + } + } + auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); if (ret) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), @@ -278,12 +299,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->address_str_.c_str(), ret); } this->set_state(espbt::ClientState::CONNECTED); + ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str()); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { - ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str()); // only set our state, subclients might have more stuff to do yet. this->state_ = espbt::ClientState::ESTABLISHED; break; } + ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str()); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } @@ -296,8 +319,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_DISCONNECT_EVT: { if (!this->check_addr(param->disconnect.remote_bda)) return false; - ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, - this->address_str_.c_str(), param->disconnect.reason); + // Check if we were disconnected while waiting for service discovery + if (param->disconnect.reason == 0x13 && // 0x13 = ESP_GATT_CONN_TERMINATE_PEER + this->state_ == espbt::ClientState::CONNECTED) { + ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_, + this->address_str_.c_str()); + } else { + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, + this->address_str_.c_str(), param->disconnect.reason); + } this->release_services(); this->set_state(espbt::ClientState::IDLE); break; @@ -353,7 +383,23 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_.c_str(), svc->start_handle, svc->end_handle); } - ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str()); + + // For non-cached connections, restore default connection parameters after service discovery + // Now that we've discovered all services, we can use more balanced parameters + // that save power and reduce interference + if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t)); + conn_params.min_int = 0x0A; // 12.5ms - ESP-IDF default minimum (BTM_BLE_CONN_INT_MIN_DEF) + conn_params.max_int = 0x0C; // 15ms - ESP-IDF default maximum (BTM_BLE_CONN_INT_MAX_DEF) + conn_params.latency = 0; + conn_params.timeout = 600; // 6s - ESP-IDF default timeout (BTM_BLE_CONN_TIMEOUT_DEF) + ESP_LOGD(TAG, "[%d] [%s] Restoring default connection parameters after service discovery", + this->connection_index_, this->address_str_.c_str()); + esp_ble_gap_update_conn_params(&conn_params); + } + this->state_ = espbt::ClientState::ESTABLISHED; break; } diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 4ed70a04c2..4fc837be67 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -57,7 +57,8 @@ from esphome.final_validate import full_config from . import mipi_dsi_ns, models -DEPENDENCIES = ["esp32"] +# Currently only ESP32-P4 is supported, so esp_ldo and psram are required +DEPENDENCIES = ["esp32", "esp_ldo", "psram"] DOMAIN = "mipi_dsi" LOGGER = logging.getLogger(DOMAIN) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e85acbf5a7..2731dc1f3f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -533,9 +533,17 @@ void WiFiComponent::check_scanning_finished() { return false; if (a.get_matches() && b.get_matches()) { - // if both match, check priority + // For APs with the same SSID, always prefer stronger signal + // This helps with mesh networks and multiple APs + if (a.get_ssid() == b.get_ssid()) { + return a.get_rssi() > b.get_rssi(); + } + + // For different SSIDs, check priority first if (a.get_priority() != b.get_priority()) return a.get_priority() > b.get_priority(); + // If priorities are equal, prefer stronger signal + return a.get_rssi() > b.get_rssi(); } return a.get_rssi() > b.get_rssi(); diff --git a/tests/components/esp32_ble/common.yaml b/tests/components/esp32_ble/common.yaml index 86d5dcee60..76b35fc8f8 100644 --- a/tests/components/esp32_ble/common.yaml +++ b/tests/components/esp32_ble/common.yaml @@ -1,3 +1,2 @@ esp32_ble: io_capability: keyboard_only - # Default configuration - should use 1m PHY diff --git a/tests/components/esp32_ble/test.esp32-s3-ard.yaml b/tests/components/esp32_ble/test.esp32-s3-ard.yaml deleted file mode 100644 index 3c05b93858..0000000000 --- a/tests/components/esp32_ble/test.esp32-s3-ard.yaml +++ /dev/null @@ -1,2 +0,0 @@ -esp32_ble: - preferred_phy: 2m diff --git a/tests/components/esp32_ble/test.esp32-s3-idf.yaml b/tests/components/esp32_ble/test.esp32-s3-idf.yaml deleted file mode 100644 index 8f1d32779d..0000000000 --- a/tests/components/esp32_ble/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -esp32_ble: - preferred_phy: auto diff --git a/tests/components/mipi_dsi/test.esp32-p4-idf.yaml b/tests/components/mipi_dsi/test.esp32-p4-idf.yaml index 8a6f3c87ba..9c4eb07d9b 100644 --- a/tests/components/mipi_dsi/test.esp32-p4-idf.yaml +++ b/tests/components/mipi_dsi/test.esp32-p4-idf.yaml @@ -12,6 +12,8 @@ display: #- platform: mipi_dsi #id: backlight_id +psram: + i2c: sda: GPIO7 scl: GPIO8