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 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 19:33:04 +02:00
parent ea1e74c384
commit db45679c12
2 changed files with 27 additions and 6 deletions
@@ -6,6 +6,7 @@ import logging
import struct import struct
from bleak import BleakClient, BleakError from bleak import BleakClient, BleakError
from bleak.backends.device import BLEDevice
from .const import ( from .const import (
FRAME_END, FRAME_END,
@@ -42,13 +43,17 @@ class BmsBluetoothHandler:
def is_connected(self) -> bool: def is_connected(self) -> bool:
return self._client is not None and self._client.is_connected return self._client is not None and self._client.is_connected
async def connect(self) -> None: async def connect(self, ble_device: BLEDevice) -> None:
"""Open BLE connection and start notifications.""" """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: if self.is_connected:
return 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._client = BleakClient(
self._address, ble_device,
disconnected_callback=self._on_disconnect, disconnected_callback=self._on_disconnect,
) )
await self._client.connect() await self._client.connect()
+18 -2
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import logging import logging
from datetime import timedelta from datetime import timedelta
from homeassistant.components.bluetooth import async_ble_device_from_address
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -35,9 +36,22 @@ class BmsCoordinator(DataUpdateCoordinator[dict]):
# Lifecycle # 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: async def async_setup(self) -> None:
"""Connect to the BMS. Called once during config entry setup.""" """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: async def async_teardown(self) -> None:
"""Disconnect cleanly. Called on entry unload.""" """Disconnect cleanly. Called on entry unload."""
@@ -52,7 +66,9 @@ class BmsCoordinator(DataUpdateCoordinator[dict]):
if not self._handler.is_connected: if not self._handler.is_connected:
_LOGGER.debug("BMS not connected, attempting reconnect…") _LOGGER.debug("BMS not connected, attempting reconnect…")
try: try:
await self._handler.connect() await self._handler.connect(self._get_ble_device())
except UpdateFailed:
raise
except Exception as exc: except Exception as exc:
raise UpdateFailed(f"BMS reconnect failed: {exc}") from exc raise UpdateFailed(f"BMS reconnect failed: {exc}") from exc