4 Commits
Author SHA1 Message Date
Andreas Wrede e5e776405a li3: decode status flags, fix info-sensor expire_after, restart bluetooth after first failed scan
- status_text: decode the 24-bit "status" field into human-readable flag
  names (e.g. "Cell Temp Low, Low Voltage") instead of raw hex, using the
  bit table pulled from com.lithionics.bms's array/advanced resource (dumped
  with aapt -- the app's own StatusCodeTable class references stale/wrong
  resource IDs and can't be trusted for this). Applied to "status" only;
  last_fault_code is a lifetime latch that accumulates many bits over time
  and isn't meaningfully summarized the same way.

- Fixed the info sensors (total_consumed, firmware_version, serial_number,
  etc.) showing "Unavailable" in HA a minute after connecting: they were
  getting the same expire_after as the periodic telemetry sensors, but the
  $info line the come from is only sent once per BLE connection, so HA's
  expire timer always fired. These are retained-topic diagnostics meant to
  hold their last value indefinitely; expire_after now only applies to the
  periodic SENSORS group.

- STUCK_DISCOVERY_THRESHOLD 3 -> 1: restart bluetooth.service after the
  first failed scan instead of waiting for three, cutting reconnect time
  roughly in third. Still rate-limited via BLUETOOTH_RESTART_COOLDOWN_S.

Also removed the per-line "RAW ..." debug print (was flooding the journal).

All three changes verified live on host wan via journalctl/mosquitto_sub.
2026-08-18 10:18:56 -04:00
Andreas Wrede 382f99b508 li3: publish CAN-trace fields with telemetry, info fields as separate retained topic
Decoded the "&" and "$" line formats from log1 by matching them against the
decompiled com.lithionics.bms app: TraceFormat/InfoFormat gate on the raw
line's first byte ('&'=trace, '$'=info) before BmsSeries.create() ever sees
the row, which is why the earlier decode (MainBmsCsParameters) never touched
them.

"&" trace lines stream continuously (enabled by our own $traceon) and carry
CAN-charger-bus fields: remaining_capacity, remaining_time, can_charger_
voltage/current, can_charger_status, can_status. These merge into the
existing periodic state message alongside the Cs telemetry.

"$" info is sent once per connection (response to $info) and never repeats:
total_consumed, last_fault_code, highest/lowest_recorded_temp, firmware_
version, aging_factor_temp/soc, serial_number. Published to its own retained
topic (van/<device_id>/info) instead of the periodic one, with its own
discovery config (entity_category: diagnostic) — so HA keeps the last known
value across BMS disconnects rather than expiring it.

Deployed and verified live on host wan: info message publishes once on
connect, state messages carry the merged fields, and 14 new HA entities
registered with correct precision.
2026-08-18 09:56:32 -04:00
Andreas Wrede 7eab5ed422 fix: adjust precision of temperatures and current 2026-08-18 07:22:45 -04:00
Andreas Wrede 7a1c47d34c li3: add Lithionics Li3 house battery BMS -> Home Assistant MQTT
Publishes the RV's 12V LiFePO4 house battery to HA via MQTT discovery
(pack voltage, 4 cell voltages, current, SOC, BMS/battery temp, status).
Connects over the battery's BLE HM-10 UART module (service ffe0/char
ffe1, no pairing) using bleak; protocol reverse-engineered from the
com.lithionics.bms Android app's own BLE/parsing code.

Deliberately named li3/ and van-li3-battery, not battery/van-battery —
that name is reserved for the host's own AC/UPS power-supply monitor
(different hardware, unrelated concern).

Self-heals a bluetoothd discovery-state wedge (Discovering stuck "yes",
connects failing with le-connection-abort-by-local) that shows up after
repeated failed connects to this device on the Pi's onboard adapter: the
daemon retries scan/connect internally (MQTT session and HA entities
stay up across retries) and restarts bluetooth.service itself after 3
consecutive scan failures, rate-limited to once per 5 min.
2026-08-17 17:26:14 -04:00