Commit Graph

11 Commits

Author SHA1 Message Date
Jannis b8bee14839 Switch to persistent BLE connection model
The connect/disconnect-every-cycle approach caused ~50% failure rate
over 48h — each of the ~2880 daily connection attempts per device had
a significant chance of failure through ESPHome proxies.

New model (same as the user's Android app):
- Connect once, keep the connection alive across poll cycles
- _ensure_connected() reconnects automatically if the link drops
- _on_disconnect() callback detects unexpected disconnections
- On timeout, force-disconnect so next cycle gets a clean reconnect
- Polls now only send commands (no connection overhead) — expected
  completion in <1s instead of 10-25s

Connection lifecycle:
  startup → first poll → _ensure_connected() → persistent
  drop detected → next poll → _ensure_connected() → reconnected
  shutdown → async_teardown() → disconnect()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:46:20 +02:00
Jannis dcc528b96a Add global BLE lock + faster timeouts for multi-device reliability
Root cause: 3 BMS devices fighting for 3 ESPHome proxy connection slots
simultaneously, causing 80% timeout failures and 22s+ poll times.

Fixes:
- Add shared asyncio.Lock so only one BMS polls at a time — eliminates
  proxy slot contention entirely
- Pass ble_device_callback to establish_connection so retry attempts
  get a fresh BLEDevice (handles proxy path changes)
- Reduce command timeout 5s -> 3s, retries 3 -> 2 (BMS responds in
  <200ms when connection is clean)
- Reduce establish_connection max_attempts 3 -> 2 (fail fast, retry
  next cycle instead of blocking 25s)
- Fixed poll timeout to 15s (was poll_interval-5=25s)

Expected: polls complete in 2-5s instead of 22s, ~95%+ success rate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 10:05:54 +02:00
Jannis 1520ed3c0f Refactor BLE layer for 24/7 reliability
- 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>
2026-04-12 09:36:08 +02:00
Jannis b52b25973e Add MOS gate control via select entity
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>
2026-04-11 20:46:10 +02:00
Jannis b6c3e597f7 Prevent unavailable oscillation with failure tolerance
- 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>
2026-04-11 20:29:50 +02:00
Jannis e1e6d69d2c Use BLE device name, add hard poll timeout, tighten request timing
- 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>
2026-04-11 20:21:37 +02:00
Jannis 3fc25c083b Switch to connect→poll→disconnect per cycle (single-connection BMS fix)
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>
2026-04-11 20:09:43 +02:00
Jannis 5d527168e2 Full protocol coverage: binary sensors, energy, hardware version
Sensors added:
- energy_stored (kWh = V × Ah / 1000) for energy dashboard

Binary sensors added (all from existing 0x03 frame, no extra BLE requests):
- Charge MOSFET / Discharge MOSFET (MOS gate status)
- Cell Balancing (any balance bit active)
- 13× protection flags: cell/pack over/under-voltage, charge/discharge
  over/under-temperature, charge/discharge over-current, short circuit,
  frontend IC error, software lock

Other:
- Hardware version string fetched once via CMD 0x05, shown in device card
- DeviceInfo centralised on coordinator (sensor + binary_sensor share it)
- CONF_ADDRESS removed from sensor.py (coordinator holds address)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:52:10 +02:00
Jannis 78c8a23131 Fix setup failure when BMS not immediately reachable
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>
2026-04-11 19:37:54 +02:00
Jannis db45679c12 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>
2026-04-11 19:33:04 +02:00
Jannis 4a769bf50f Initial commit: Xiaoxiang Smart BMS Home Assistant integration
- BLE GATT communication via bleak (UART-over-GATT, service 0xff00)
- Parses 0x03 (general info) and 0x04 (cell voltages) frames
- Protocol verified against official RS485/UART spec V4
- DataUpdateCoordinator with 5s poll interval (configurable 2-60s)
- Auto-reconnect on BLE disconnect
- Config flow: BT auto-discovery + manual MAC entry + options flow
- Sensors: voltage, current, power, SoC, capacity, cycles, temps, cells, cell delta
- HACS-ready (hacs.json, manifest.json)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:07:18 +02:00