From 27a97f36af38e998f9d8073293a906623e8c7ad9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Aug 2026 09:14:17 -0500 Subject: [PATCH] Key backend and engine selection on their own defines Each backend arm now has a codegen-emitted USE_BLE_GATT_BACKEND_ define, making the alias ladder order-independent and letting the proxy capability constants describe the backend instead of the platform (a second esp32 backend carries its own facts). The legacy raw-gattc engine is selected by USE_BLE_CLIENT_LEGACY_ENGINE instead of platform identity, so esp32's future move to the neutral engine is one line per language. The address-scoped maintenance calls key on the stack define they wrap. --- esphome/components/ble_client/__init__.py | 19 +++++++++++++----- esphome/components/ble_client/automation.h | 4 +++- .../components/ble_client/automation_gatt.h | 4 ++-- esphome/components/ble_client/ble_client.cpp | 2 +- esphome/components/ble_client/ble_client.h | 2 +- .../components/ble_client/ble_client_gatt.cpp | 4 ++-- .../components/ble_client/ble_client_gatt.h | 4 ++-- .../components/ble_client/ble_client_node.h | 8 ++++---- .../bluetooth_connection/__init__.py | 15 +++++++++++--- .../bluetooth_connection.cpp | 4 ++-- .../bluetooth_connection.h | 20 ++++++++++--------- .../bluetooth_connection_gatt_backend.h | 6 ++++-- esphome/core/defines.h | 3 +++ 13 files changed, 61 insertions(+), 34 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 98f2ceccda..06434a4384 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -44,13 +44,20 @@ from esphome.types import ConfigType # platforms' registries (the bluetooth_proxy pattern). +def _legacy_engine() -> bool: + """True when the build uses the legacy raw-gattc engine - one line to + flip when esp32 moves to the neutral engine (with + USE_BLE_CLIENT_LEGACY_ENGINE in _to_code_esp32).""" + return CORE.is_esp32 + + def AUTO_LOAD() -> list[str]: """The engine's closure per platform: the legacy esp32 engine builds on esp32_ble_client plus bluetooth_connection (the shared service-table materializer; its sources compile empty in builds without a neutral node), the neutral engine on the bluetooth_connection backend. The platform-less arm is the union for manifest-resolving tooling.""" - if CORE.is_esp32 or CORE.target_platform is None: + if _legacy_engine() or CORE.target_platform is None: return ["bluetooth_connection", "esp32_ble_client"] return ["bluetooth_connection"] @@ -242,7 +249,7 @@ def _validate_platform(config: ConfigType) -> ConfigType: # The language-schema dumper runs without a platform; expose the # esp32 (legacy-engine) shape. return _esp32_config_schema() - if CORE.is_esp32: + if _legacy_engine(): return _esp32_config_schema()(config) if CORE.target_platform in bluetooth_connection.GATT_CLIENT_PLATFORMS: return _gatt_config_schema(CORE.target_platform)(config) @@ -269,7 +276,7 @@ class BLEClientFeatures(StrEnum): def _engine_features() -> set[BLEClientFeatures]: """Features the validated platform's engine provides.""" - if CORE.is_esp32: + if _legacy_engine(): return { BLEClientFeatures.GATT_NODE, BLEClientFeatures.RAW_GATTC, @@ -342,10 +349,11 @@ def _request_gatt_node_build() -> None: compiled in", plus the esp32 bridge/materializer defines.""" _request_node_slot() cg.add_define("USE_BLE_CLIENT_GATT_NODES") - if CORE.is_esp32: + if _legacy_engine(): # Deliberately not ble_device_base.request_gatt_client(): that would # claim a phantom backend slot on combined proxy builds. cg.add_define("USE_BLE_GATT_CLIENT") + cg.add_define("USE_BLE_GATT_BACKEND_BLUEDROID") cg.add_define("USE_BLE_GATT_SERVICE_TABLE") @@ -525,6 +533,7 @@ async def _to_code_esp32(config: ConfigType) -> cg.MockObj: # Register the loggers this component needs esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP) cg.add_define("USE_ESP32_BLE_UUID") + cg.add_define("USE_BLE_CLIENT_LEGACY_ENGINE") var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -552,7 +561,7 @@ async def _to_code_gatt(config: ConfigType) -> cg.MockObj: async def to_code(config: ConfigType) -> None: - if CORE.is_esp32: + if _legacy_engine(): var = await _to_code_esp32(config) else: var = await _to_code_gatt(config) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 74ce9f82b2..1ea99706eb 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -1,6 +1,8 @@ #pragma once -#ifdef USE_ESP32 +#include "esphome/core/defines.h" + +#ifdef USE_BLE_CLIENT_LEGACY_ENGINE #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" diff --git a/esphome/components/ble_client/automation_gatt.h b/esphome/components/ble_client/automation_gatt.h index be5a861684..e333e008dc 100644 --- a/esphome/components/ble_client/automation_gatt.h +++ b/esphome/components/ble_client/automation_gatt.h @@ -9,7 +9,7 @@ #include "esphome/core/defines.h" -#if defined(USE_BLE_GATT_CLIENT) && !defined(USE_ESP32) +#if defined(USE_BLE_GATT_CLIENT) && !defined(USE_BLE_CLIENT_LEGACY_ENGINE) #include @@ -115,4 +115,4 @@ template class BLEClientDisconnectAction final : public Action @@ -40,9 +40,9 @@ class BLEClientNode { virtual void on_write_result(uint16_t handle, int error) {} virtual void on_pairing_result(int status) {} #endif -#ifdef USE_ESP32 - // Legacy raw surface (esp32 engine only); components overriding these are - // esp32-only until migrated to the neutral surface above. +#ifdef USE_BLE_CLIENT_LEGACY_ENGINE + // Legacy raw surface; components overriding these need the legacy engine + // until migrated to the neutral surface above. virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {} virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index a26f17e7ee..a525906889 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -124,6 +124,8 @@ class _PlatformBackend: backend_class: cg.MockObjClass schema_fragment: Callable[[], cv.Schema] register: Callable[[cg.MockObj, ConfigType], Awaitable[None]] + # Selects the backend's alias-ladder arm (order-independent arms). + define: str # The single registry of platforms with a GATT client backend; a platform @@ -131,9 +133,14 @@ class _PlatformBackend: # platform's arm. _PLATFORM_BACKENDS: dict[str, _PlatformBackend] = { PLATFORM_ESP32: _PlatformBackend( - BluedroidGattClient, _esp32_schema_fragment, _esp32_register + BluedroidGattClient, + _esp32_schema_fragment, + _esp32_register, + "USE_BLE_GATT_BACKEND_BLUEDROID", + ), + PLATFORM_RP2: _PlatformBackend( + RP2GattClient, _rp2_schema_fragment, _rp2_register, "USE_BLE_GATT_BACKEND_RP2" ), - PLATFORM_RP2: _PlatformBackend(RP2GattClient, _rp2_schema_fragment, _rp2_register), } # Gates dedicated-backend consumers (cv.only_on). @@ -244,7 +251,9 @@ async def new_gatt_backend( """ from esphome.components import ble_device_base + entry = _backend_entry() ble_device_base.request_gatt_client() + cg.add_define(entry.define) if service_table: cg.add_define("USE_BLE_GATT_SERVICE_TABLE") backend = cg.new_Pvariable(config[CONF_BACKEND_ID]) @@ -253,7 +262,7 @@ async def new_gatt_backend( # schema carries keys that register_component would misapply to the # backend (e.g. a polling interval) must not put them in this config. await cg.register_component(backend, config) - await _backend_entry().register(backend, config) + await entry.register(backend, config) return backend diff --git a/esphome/components/bluetooth_connection/bluetooth_connection.cpp b/esphome/components/bluetooth_connection/bluetooth_connection.cpp index a001729083..c90459459e 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection.cpp @@ -46,7 +46,7 @@ BatchClose close_service_batch(api::BluetoothGATTGetServicesResponse &resp, size #endif // USE_BLUETOOTH_PROXY_CONNECTIONS -#if defined(USE_ESP32) && defined(USE_BLE_GATT_CLIENT) +#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) namespace esphome::bluetooth_connection { // Address-scoped Bluedroid maintenance. Gated with the connection surface: @@ -65,4 +65,4 @@ conn_err_t clear_gatt_cache(uint64_t address) { } } // namespace esphome::bluetooth_connection -#endif // USE_ESP32 && USE_BLE_GATT_CLIENT +#endif // USE_ESP32_BLE && USE_BLE_GATT_CLIENT diff --git a/esphome/components/bluetooth_connection/bluetooth_connection.h b/esphome/components/bluetooth_connection/bluetooth_connection.h index b21d997b4f..566bfb68ef 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection.h @@ -48,15 +48,16 @@ static constexpr conn_err_t CONN_OK = 0; // GATT contract so backend and wrapper cannot drift. static constexpr conn_err_t GATT_NOT_CONNECTED = ble_device_base::GATT_ERR_NOT_CONNECTED; -// What the platform's connection backend supports beyond GATT operations; -// the proxy derives its feature flags and legacy version from these. -#if defined(USE_ESP32) +// What the build's connection backend supports beyond GATT operations; the +// proxy derives its feature flags and legacy version from these. Keyed on +// the backend define, never the platform, so a second backend on one +// platform carries its own facts. +#if defined(USE_BLE_GATT_BACKEND_BLUEDROID) static constexpr bool SUPPORTS_PAIRING = true; static constexpr bool SUPPORTS_CACHE_CLEARING = true; -#elif defined(USE_RP2040_BLE) && defined(USE_BLE_GATT_CLIENT) +#elif defined(USE_BLE_GATT_BACKEND_RP2) // The rp2 BTstack backend pairs (just works + bonding); it has no service -// cache to clear. Keyed on the backend, not the generic client define, so a -// future backend without pairing keeps the stub arm below. +// cache to clear. static constexpr bool SUPPORTS_PAIRING = true; static constexpr bool SUPPORTS_CACHE_CLEARING = false; #else @@ -64,13 +65,14 @@ static constexpr bool SUPPORTS_PAIRING = false; static constexpr bool SUPPORTS_CACHE_CLEARING = false; #endif -// Address-scoped (not connection-scoped) maintenance requests. -#if (defined(USE_ESP32) || defined(USE_RP2040_BLE)) && defined(USE_BLE_GATT_CLIENT) +// Address-scoped (not connection-scoped) maintenance requests; keyed on the +// stack (the calls need no backend instance). +#if (defined(USE_ESP32_BLE) || defined(USE_RP2040_BLE)) && defined(USE_BLE_GATT_CLIENT) conn_err_t unpair_device(uint64_t address); #else inline conn_err_t unpair_device(uint64_t) { return GATT_NOT_CONNECTED; } #endif -#if defined(USE_ESP32) && defined(USE_BLE_GATT_CLIENT) +#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) conn_err_t clear_gatt_cache(uint64_t address); #else inline conn_err_t clear_gatt_cache(uint64_t) { return GATT_NOT_CONNECTED; } diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h index 3c982d81ae..9946c83aad 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h @@ -12,10 +12,12 @@ #include "esphome/components/ble_device_base/ble_gatt_client.h" -#if defined(USE_RP2040_BLE) +// Arms are keyed on codegen-emitted per-backend defines (_PLATFORM_BACKENDS +// in __init__.py), so they are order-independent. +#if defined(USE_BLE_GATT_BACKEND_RP2) #include "bluetooth_connection_rp2.h" #define ESPHOME_BLE_GATT_CONNECTION_TYPE bluetooth_connection::RP2GattClient -#elif defined(USE_ESP32_BLE) +#elif defined(USE_BLE_GATT_BACKEND_BLUEDROID) #include "bluetooth_connection_bluedroid.h" #define ESPHOME_BLE_GATT_CONNECTION_TYPE bluetooth_connection::BluedroidGattClient #elif defined(USE_BLE_GATT_CLIENT_STUB_BACKEND) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f33f30257d..8272a9e06e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -318,8 +318,10 @@ #define USE_ESP32_BLE_SERVER_ON_DISCONNECT #define USE_ESP32_BLE_TRACKER #define USE_BLE_GATT_CLIENT +#define USE_BLE_GATT_BACKEND_BLUEDROID #define USE_BLE_GATT_SERVICE_TABLE #define USE_BLE_CLIENT_GATT_NODES +#define USE_BLE_CLIENT_LEGACY_ENGINE #define ESPHOME_BLE_CLIENT_MAX_NODES 1 #define ESPHOME_BLE_GATT_CLIENT_COUNT 1 #define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1 @@ -488,6 +490,7 @@ #define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1 #define USE_BLE_SCAN_RESPONSE_MERGER #define USE_BLE_GATT_CLIENT +#define USE_BLE_GATT_BACKEND_RP2 #define USE_BLE_CLIENT_GATT_NODES #define ESPHOME_BLE_GATT_CLIENT_COUNT 3 #define ESPHOME_BLE_CLIENT_MAX_NODES 1