From db45679c12dbb9d5d879e9fcfc952b8aef9ba898 Mon Sep 17 00:00:00 2001 From: Jannis Christiani Date: Sat, 11 Apr 2026 19:33:04 +0200 Subject: [PATCH] Support ESPHome BLE proxies via HA Bluetooth subsystem Use async_ble_device_from_address() to resolve the BMS through whichever adapter (local or ESPHome proxy) can reach it, instead of connecting by raw MAC address directly. BleakClient now receives a BLEDevice object. Co-Authored-By: Claude Sonnet 4.6 --- .../xiaoxiang_bms/bluetooth_handler.py | 13 ++++++++---- .../xiaoxiang_bms/coordinator.py | 20 +++++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/custom_components/xiaoxiang_bms/bluetooth_handler.py b/custom_components/xiaoxiang_bms/bluetooth_handler.py index 2bcba99..c2aa36b 100644 --- a/custom_components/xiaoxiang_bms/bluetooth_handler.py +++ b/custom_components/xiaoxiang_bms/bluetooth_handler.py @@ -6,6 +6,7 @@ import logging import struct from bleak import BleakClient, BleakError +from bleak.backends.device import BLEDevice from .const import ( FRAME_END, @@ -42,13 +43,17 @@ class BmsBluetoothHandler: def is_connected(self) -> bool: return self._client is not None and self._client.is_connected - async def connect(self) -> None: - """Open BLE connection and start notifications.""" + async def connect(self, ble_device: BLEDevice) -> None: + """Open BLE connection and start notifications. + + Accepts a BLEDevice resolved by HA's Bluetooth subsystem so that + ESPHome BLE proxies are used transparently alongside local adapters. + """ if self.is_connected: return - _LOGGER.debug("Connecting to BMS at %s", self._address) + _LOGGER.debug("Connecting to BMS at %s (via %s)", self._address, ble_device.name) self._client = BleakClient( - self._address, + ble_device, disconnected_callback=self._on_disconnect, ) await self._client.connect() diff --git a/custom_components/xiaoxiang_bms/coordinator.py b/custom_components/xiaoxiang_bms/coordinator.py index 7179f29..972b366 100644 --- a/custom_components/xiaoxiang_bms/coordinator.py +++ b/custom_components/xiaoxiang_bms/coordinator.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging from datetime import timedelta +from homeassistant.components.bluetooth import async_ble_device_from_address from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -35,9 +36,22 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): # Lifecycle # ------------------------------------------------------------------ + def _get_ble_device(self): + """Resolve the best available BLE device via HA's Bluetooth subsystem. + + This automatically uses ESPHome BLE proxies if they can reach the BMS + and the local adapter cannot (or vice versa). + """ + device = async_ble_device_from_address(self.hass, self.address, connectable=True) + if device is None: + raise UpdateFailed( + f"BMS ({self.address}) not reachable by any Bluetooth adapter or proxy" + ) + return device + async def async_setup(self) -> None: """Connect to the BMS. Called once during config entry setup.""" - await self._handler.connect() + await self._handler.connect(self._get_ble_device()) async def async_teardown(self) -> None: """Disconnect cleanly. Called on entry unload.""" @@ -52,7 +66,9 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): if not self._handler.is_connected: _LOGGER.debug("BMS not connected, attempting reconnect…") try: - await self._handler.connect() + await self._handler.connect(self._get_ble_device()) + except UpdateFailed: + raise except Exception as exc: raise UpdateFailed(f"BMS reconnect failed: {exc}") from exc