diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 98f2ceccda..cd33f211f0 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,11 +349,12 @@ 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_SERVICE_TABLE") + cg.add_define("USE_BLE_GATT_BACKEND_BLUEDROID") + cg.add_define("USE_BLUEDROID_GATT_SERVICE_TABLE") async def register_gatt_node(var, config): @@ -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/ble_device_base/ble_gatt_client.h b/esphome/components/ble_device_base/ble_gatt_client.h index 7fc9aab10d..aecffa582d 100644 --- a/esphome/components/ble_device_base/ble_gatt_client.h +++ b/esphome/components/ble_device_base/ble_gatt_client.h @@ -11,13 +11,16 @@ // interface. All listener calls are delivered on the ESPHome main loop; // borrowed data pointers are valid only for the duration of the call. // -// Error domain (plain int, forwarded to the API without translation): +// Error domain (plain int, forwarded to the API without translation, so the +// values are wire-frozen - API clients interpret them): // 0 success -// 1..0x11 ATT error codes (Bluetooth spec; BTstack and Bluedroid agree) +// 1..0x11 ATT error codes (Bluetooth spec) - reserved; a backend whose +// native error codes land in this window must remap them out // GATT_ERR_NOT_CONNECTED (-1) no connection to the peer (on esp32 a raw // ESP_FAIL from the stack shares this value; both read as a // failed, unusable connection on the client side) // GATT_ERR_NO_MEMORY (-2) backend storage exhausted +// -1..-15 reserved for future contract sentinels // anything else: platform stack error/status code, surfaced opaquely. // Connection events carry HCI status/disconnect reason codes (same code // space on every controller). @@ -99,9 +102,18 @@ class GattClientListener { // The BLEGattConnection op surface, asserted where the alias binds // (bluetooth_connection_gatt_backend.h). Operations return 0 when accepted (completion arrives // through the listener) or a synchronous error (busy, not connected, stack -// rejection); one operation may be outstanding at a time. Semantics beyond -// the signatures: +// rejection); one operation may be outstanding at a time. An accepted +// operation's completion is delivered from the event loop, NEVER +// synchronously from inside the op call - a synchronous terminal +// on_connection_state from within gatt_disconnect() would re-enter the +// consumer mid-teardown. Semantics beyond the signatures: // - connect: addr_type is a BLE_ADDR_TYPE_* constant (ble_device.h). +// Returning 0 means the request is accepted, not that the radio acted: the +// backend owns integration with its platform's scan/connect arbitration +// (Bluedroid parks the request for the tracker's promote loop, which owns +// scan-stop/coex/one-connect-at-a-time; the rp2 backend opens immediately +// and relies on sighting-gated consumers). Consumers must not assume +// connect timing. // - gatt_disconnect: also cancels a connect in progress (named to coexist // with a platform stack's own void disconnect() on one backend class). // Nonzero means nothing to tear down and no completion will follow; an diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index a26f17e7ee..69a250f809 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,16 +251,18 @@ 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") + cg.add_define("USE_BLUEDROID_GATT_SERVICE_TABLE") backend = cg.new_Pvariable(config[CONF_BACKEND_ID]) # The backend is the slot's real Component: component keys from the # connection entry (setup_priority, ...) apply to it. Consumers whose own # 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_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index c7ff99b242..7cbc02e456 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -301,7 +301,7 @@ int BluedroidGattClient::update_connection_params(uint16_t min_interval, uint16_ void BluedroidGattClient::release_services() { this->service_total_ = 0; -#ifdef USE_BLE_GATT_SERVICE_TABLE +#ifdef USE_BLUEDROID_GATT_SERVICE_TABLE this->table_.free(); #endif // Always set: terminates any in-flight stream on every cache config. @@ -316,7 +316,7 @@ void BluedroidGattClient::release_services() { #endif } -#ifdef USE_BLE_GATT_SERVICE_TABLE +#ifdef USE_BLUEDROID_GATT_SERVICE_TABLE ble_device_base::GattServiceTable BluedroidGattClient::get_service_table() { // Lifetime: every teardown path (CLOSE_EVT, the safety timeout, stack-down, // passive DISCONNECT) routes through release_services(), so a materialized @@ -332,7 +332,7 @@ ble_device_base::GattServiceTable BluedroidGattClient::get_service_table() { } return this->table_.view(); } -#endif // USE_BLE_GATT_SERVICE_TABLE +#endif // USE_BLUEDROID_GATT_SERVICE_TABLE // ---- internals ---- @@ -380,7 +380,7 @@ void BluedroidGattClient::log_gattc_warning_(const char *operation, int code) { // ---- service streaming ---- int BluedroidGattClient::handle_search_cmpl_(esp_gatt_status_t status) { -#ifdef USE_BLE_GATT_SERVICE_TABLE +#ifdef USE_BLUEDROID_GATT_SERVICE_TABLE // Re-discovery moves the counts the table view derives offsets from; free // the stale table. this->table_.free(); diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h index 5503b6026c..122ae8059b 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h @@ -76,9 +76,9 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public int pair(); int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); // On-demand table for direct consumers; the proxy streams instead, so the - // materializer compiles only under USE_BLE_GATT_SERVICE_TABLE (emitted by + // materializer compiles only under USE_BLUEDROID_GATT_SERVICE_TABLE (emitted by // direct-consumer codegen, never by the proxy). -#ifdef USE_BLE_GATT_SERVICE_TABLE +#ifdef USE_BLUEDROID_GATT_SERVICE_TABLE ble_device_base::GattServiceTable get_service_table(); #else // A direct consumer reaching this stub misconfigured its codegen @@ -113,7 +113,7 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public // Group 1: pointers / composed objects ble_device_base::GattClientListener *listener_{nullptr}; -#ifdef USE_BLE_GATT_SERVICE_TABLE +#ifdef USE_BLUEDROID_GATT_SERVICE_TABLE BluedroidServiceTable table_; #endif // Group 2: 4-byte types 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/components/bluetooth_connection/gatt_service_table_bluedroid.cpp b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp index 6bbb5cb2d7..71a487c590 100644 --- a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp @@ -1,6 +1,6 @@ #include "gatt_service_table_bluedroid.h" -#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) && defined(USE_BLE_GATT_SERVICE_TABLE) +#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) && defined(USE_BLUEDROID_GATT_SERVICE_TABLE) #include "esphome/core/log.h" @@ -195,4 +195,4 @@ void BluedroidServiceTable::log_walk_warning_(const char *operation, int code) { } // namespace esphome::bluetooth_connection -#endif // USE_ESP32_BLE && USE_BLE_GATT_CLIENT && USE_BLE_GATT_SERVICE_TABLE +#endif // USE_ESP32_BLE && USE_BLE_GATT_CLIENT && USE_BLUEDROID_GATT_SERVICE_TABLE diff --git a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h index 96f4fb3815..a125c010ff 100644 --- a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h +++ b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h @@ -6,7 +6,7 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) && defined(USE_BLE_GATT_SERVICE_TABLE) +#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT) && defined(USE_BLUEDROID_GATT_SERVICE_TABLE) #include "esphome/components/ble_device_base/ble_gatt_client.h" #include "esphome/core/helpers.h" @@ -73,4 +73,4 @@ class BluedroidServiceTable { } // namespace esphome::bluetooth_connection -#endif // USE_ESP32_BLE && USE_BLE_GATT_CLIENT && USE_BLE_GATT_SERVICE_TABLE +#endif // USE_ESP32_BLE && USE_BLE_GATT_CLIENT && USE_BLUEDROID_GATT_SERVICE_TABLE diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f33f30257d..096b052681 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_SERVICE_TABLE +#define USE_BLE_GATT_BACKEND_BLUEDROID +#define USE_BLUEDROID_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