diff --git a/custom_components/xiaoxiang_bms/__init__.py b/custom_components/xiaoxiang_bms/__init__.py index b45d833..be098f1 100644 --- a/custom_components/xiaoxiang_bms/__init__.py +++ b/custom_components/xiaoxiang_bms/__init__.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.data[CONF_ADDRESS] poll_interval = entry.options.get(CONF_POLL_INTERVAL, DEFAULT_POLL_INTERVAL) - coordinator = BmsCoordinator(hass, address, poll_interval) + coordinator = BmsCoordinator(hass, address, poll_interval, name=entry.title) await coordinator.async_setup() await coordinator.async_config_entry_first_refresh() diff --git a/custom_components/xiaoxiang_bms/bluetooth_handler.py b/custom_components/xiaoxiang_bms/bluetooth_handler.py index cb8a6c0..af711c7 100644 --- a/custom_components/xiaoxiang_bms/bluetooth_handler.py +++ b/custom_components/xiaoxiang_bms/bluetooth_handler.py @@ -47,7 +47,7 @@ class BmsBluetoothHandler: self, ble_device: BLEDevice, commands: list[bytes], - timeout: float = 5.0, + timeout: float = 3.0, retries: int = 3, ) -> list[bytes | None]: """Connect, send each command in sequence, disconnect. @@ -63,7 +63,7 @@ class BmsBluetoothHandler: await client.start_notify(RX_CHAR_UUID, self._on_notify) # Give the BMS a moment to register the subscription before # we start sending commands - await asyncio.sleep(0.5) + await asyncio.sleep(0.3) return [ await self._request(client, cmd, timeout, retries) for cmd in commands @@ -147,7 +147,7 @@ class BmsBluetoothHandler: _LOGGER.error("BLE write failed (attempt %d/%d): %s", attempt, retries, exc) if attempt < retries: - await asyncio.sleep(0.5) + await asyncio.sleep(0.3) continue try: @@ -157,7 +157,7 @@ class BmsBluetoothHandler: _LOGGER.warning("BMS timeout (cmd=0x%s, attempt %d/%d)", command.hex(), attempt, retries) if attempt < retries: - await asyncio.sleep(0.5) + await asyncio.sleep(0.3) return None diff --git a/custom_components/xiaoxiang_bms/coordinator.py b/custom_components/xiaoxiang_bms/coordinator.py index e3b0abb..5bcce2c 100644 --- a/custom_components/xiaoxiang_bms/coordinator.py +++ b/custom_components/xiaoxiang_bms/coordinator.py @@ -1,6 +1,7 @@ """DataUpdateCoordinator for the Xiaoxiang Smart BMS.""" from __future__ import annotations +import asyncio import logging from datetime import timedelta @@ -27,6 +28,7 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): hass: HomeAssistant, address: str, poll_interval: int, + name: str = "Xiaoxiang Smart BMS", ) -> None: super().__init__( hass, @@ -35,6 +37,8 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): update_interval=timedelta(seconds=poll_interval), ) self.address = address + self._device_name = name + self._poll_timeout = max(poll_interval - 3, 10) # hard cap, leaves 3s slack self._handler = BmsBluetoothHandler(address) self.hw_version: str | None = None @@ -46,7 +50,7 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): def device_info(self) -> DeviceInfo: return DeviceInfo( identifiers={(DOMAIN, self.address)}, - name="Xiaoxiang Smart BMS", + name=self._device_name, manufacturer="Xiaoxiang", model=self.hw_version or "Smart BMS", ) @@ -79,7 +83,14 @@ class BmsCoordinator(DataUpdateCoordinator[dict]): commands.append(CMD_VERSION) try: - responses = await self._handler.poll(device, commands) + responses = await asyncio.wait_for( + self._handler.poll(device, commands), + timeout=self._poll_timeout, + ) + except asyncio.TimeoutError: + raise UpdateFailed( + f"BMS poll timed out after {self._poll_timeout}s" + ) except Exception as exc: raise UpdateFailed(f"BMS poll failed: {exc}") from exc