- Replace raw BleakClient with establish_connection from
bleak-retry-connector (retries, GATT service cache, proxy-aware)
- Replace fragile asyncio.Event with asyncio.Queue for response frames,
drain stale data on each connection to prevent cross-cycle leakage
- Register BLE advertisement callback to keep BLEDevice reference fresh
across ESPHome proxy path changes
- Remove asyncio.sleep(2) device lookup hack
- Increase poll timeout floor from 10s to 20s
- Increase failure tolerance from 3 to 5 consecutive misses
- Bump default poll interval to 30s, min to 15s (halves connection churn)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a Select entity with four options (Normal / Charge Disabled /
Discharge Disabled / Both Disabled) that sends the 0xE1 write command
to the BMS and immediately refreshes sensor state. Current option is
derived from the mos_charge_enabled / mos_discharge_enabled bits
already parsed on every poll.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track consecutive failures; return cached data for up to 3 misses in a row
before marking sensors unavailable. Single transient BLE failures no longer
cause the UI to flip unavailable.
- Retry device lookup: if async_ble_device_from_address returns None (device
not yet back in scanner cache after last disconnect), wait 2s and try once
more before counting it as a failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Device name: coordinator now takes name=entry.title so the HA device card
shows the actual BLE advertised name instead of "Xiaoxiang Smart BMS"
- Hard poll timeout: each poll is capped at (poll_interval - 3)s via
asyncio.wait_for so a stalled poll can't bleed into the next cycle
- Request timeout: 5s → 3s (BMS should reply in <1s under normal conditions)
- Retry delay: 0.5s → 0.3s between retries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The BMS only allows one simultaneous BLE connection. Keeping a persistent
connection blocked the mobile app from connecting at all.
Changes:
- BmsBluetoothHandler: replace connect()/disconnect()/request() public API
with a single poll() method that owns the full connect→read→disconnect
lifecycle. Connection is held only for the duration of one data fetch.
- Coordinator: gutted connection-state management — _async_update_data now
just calls handler.poll() and processes results. No reconnect loop needed.
- Poll interval defaults: 15s default, 10s min, 300s max. The BLE connect
overhead (~2s) makes sub-10s intervals impractical.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Don't connect eagerly in async_setup — let the first coordinator poll
handle it. If the BMS is out of range at startup, UpdateFailed becomes
ConfigEntryNotReady and HA retries automatically instead of crashing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>