Add request retries and nominal capacity override number entity
Retries (bluetooth_handler): - request() now retries up to 3× with 0.5s pause between attempts - Tries Write With Response first, falls back to Write Without Response automatically if the characteristic rejects it — handles both BMS variants Number entity (number.py): - "Nominal Capacity (Override)" lets user correct a stale BMS capacity value (e.g. after a cell upgrade) without PC software - Value is restored across HA restarts via RestoreEntity - Immediately patches coordinator.data so the sensor reflects it without waiting for the next poll Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -123,22 +123,52 @@ class BmsBluetoothHandler:
|
||||
# Request / response
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def request(self, command: bytes, timeout: float = 5.0) -> bytes | None:
|
||||
"""Send a command frame and wait for the corresponding response frame."""
|
||||
async def request(
|
||||
self,
|
||||
command: bytes,
|
||||
timeout: float = 5.0,
|
||||
retries: int = 3,
|
||||
) -> bytes | None:
|
||||
"""Send a command frame and wait for the corresponding response frame.
|
||||
|
||||
Retries up to `retries` times with a short pause between attempts to
|
||||
handle occasional BLE packet loss or a slow BMS response.
|
||||
Tries Write With Response first; if that raises a GATT error the BMS
|
||||
likely only supports Write Without Response, so we fall back silently.
|
||||
"""
|
||||
async with self._lock:
|
||||
self._response_event.clear()
|
||||
self._response_data = None
|
||||
try:
|
||||
await self._client.write_gatt_char(TX_CHAR_UUID, command, response=True)
|
||||
except BleakError as exc:
|
||||
_LOGGER.error("BLE write failed: %s", exc)
|
||||
return None
|
||||
try:
|
||||
await asyncio.wait_for(self._response_event.wait(), timeout)
|
||||
return self._response_data
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning("BMS response timeout (cmd=%s)", command.hex())
|
||||
return None
|
||||
for attempt in range(1, retries + 1):
|
||||
self._response_event.clear()
|
||||
self._response_data = None
|
||||
try:
|
||||
await self._client.write_gatt_char(
|
||||
TX_CHAR_UUID, command, response=True
|
||||
)
|
||||
except BleakError:
|
||||
# Characteristic may not support Write With Response — try without
|
||||
try:
|
||||
await self._client.write_gatt_char(
|
||||
TX_CHAR_UUID, command, response=False
|
||||
)
|
||||
except BleakError as exc:
|
||||
_LOGGER.error("BLE write failed (attempt %d/%d): %s",
|
||||
attempt, retries, exc)
|
||||
if attempt < retries:
|
||||
await asyncio.sleep(0.5)
|
||||
continue
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(self._response_event.wait(), timeout)
|
||||
return self._response_data
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"BMS response timeout (cmd=0x%s, attempt %d/%d)",
|
||||
command.hex(), attempt, retries,
|
||||
)
|
||||
if attempt < retries:
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
return None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Frame parsers
|
||||
|
||||
Reference in New Issue
Block a user