Files
shaggy-solar/eg4battery/NOTES.md
2026-04-24 16:34:10 -04:00

161 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EG4 LifePower4 v2 — architecture & protocol notes
## Modes — choose per deployment
`bus.mode` in the config picks one of three daemon modes:
| Mode | Wire protocol | Baud | Role | Status |
|------------------|----------------------|-------|----------------|------------------------------------------------------------|
| `modbus_per_pack`| Modbus RTU, fn 0x03 | 9600 | master (per pack)| **Primary path for LP4V2 Auto-Addressing.** One FTDI per pack's RS485 port, polls once per cycle, fully decodes into named HA entities. |
| `active` | EG4 7E/0D legacy | 9600 | master (shared) | **Legacy.** V1 firmware only. V2 packs don't respond to this protocol — kept for reference. |
| `passive` | Modbus RTU sniff | 19200 | listener | **Diagnostic.** Originally intended to listen on an LVX6048 BMS bus; the LVX6048 doesn't poll EG4 packs that way, so has no production use here. |
## How we got here (summary)
1. **Port matrix test** (2026-04-24). Established that the LP4V2 back panel's four RJ45s (CAN / RS485 / Comm1 / Comm2) carry three distinct electrical buses:
- Comm1 + Comm2 = inter-pack hub bus (19200 Modbus, master-to-slave coordination). Pin 1-2 and pin 7-8 both tap the same bus.
- RS485 = external monitor bus. Inactive until an external master drives it.
- CAN = separate bus, not in scope.
2. **EG4 BMS Tool capture.** User confirmed the stock Windows/macOS BMS Tool connects to the RS485 port at 9600 baud, Modbus slave ID **0x40**. Our first canonical Modbus probe at that address returned a clean 99-byte reply.
3. **`lv_host.app` reverse-engineering.** The BMS Tool is a Qt app. Its Mach-O binary contains:
- SQLite schema for the `total` table → complete list of 39 fields the BMS Tool stores per cycle.
- String-table names for warning / protection bit flags.
- C++ symbols `BmsDatalog::allFunctionModbusAnalysis`, `BmsMonitoring::allFunctionModbusAnalysis`, `usModbusAskRegBase` etc. → confirms Modbus RTU fn 0x03 read-holding-regs.
4. **Register map construction.** Correlated live register values (cell voltages, pack V) against the SQL field list to derive the 47-reg map below. High-confidence fields promoted to named HA entities; unknowns still emitted as `register_NN` for future correlation.
## Register map (Modbus fn 0x03, start 0x0000, count 47)
From live observation + lv_host.app schema:
| Reg | Observed | Field | Scale | Unit | HA entity suffix |
|-------|--------------|---------------------|---------|------|----------------------|
| 00 | 5256 | Total_Voltage | × 0.01 | V | `pack_voltage` |
| 01 | 0 (signed) | Current_I | × 0.01 | A | `pack_current` |
| 02-17 | ~3285 each | Vol_Cell01..16 | × 0.001 | V | `cell_01_voltage`..`cell_16_voltage` + `cell_voltage_min/max/delta_mv/lowest/highest` |
| 18 | 21 | Temp_01 | × 1 | °C | `temperature_01` |
| 19 | 21 | Temp_02 | × 1 | °C | `temperature_02` |
| 20 | 20 | Temp_03 | × 1 | °C | `temperature_03` |
| 21 | 54 | Temp_04 | × 1 | °C | `temperature_04` |
| 22 | 100 | SOC | × 1 | % | `soc` |
| 23 | 100 | SOH | × 1 | % | `soh` |
| 24 | 55 | Temp_PCB | × 1 | °C | `temperature_pcb` |
| 25-29 | 0 | reserved | | | (register_NN only) |
| 30 | 1 | Heater (bit 0) | | | `heater` (on/off) |
| 31 | 5493 | MAX_Curren | × 0.01 | A | `max_current_limit` |
| 32 | 10752 | *?* | | | (register_32) |
| 33 | ? | Warning bitfield | | | `warning_*` (14 bits)|
| 34 | ? | Protection bitfield | | | `protection_*` (14 bits)|
| 35 | 0 | Error_Code | | | `error_code` |
| 36 | 16 | Cell_Num | | | `cell_count` |
| 37 | 1000 | Capacity | × 0.1 | Ah | `capacity_ah` |
| 38 | 0 | Remaining | × 0.01 | Ah | `remaining_ah` |
| 39 | 0 | CycleNum | | | `cycle_count` |
| 40 | 7 | Battery_Mode (enum) | | | `battery_mode` |
| 41 | 0x0fff | BMS_Version (hi) | | | `bms_version_hi` |
| 42 | 0x07ff | BMS_Version (lo) | | | `bms_version_lo` |
| 43-45 | — | *?* | | | (register_NN only) |
| 46 | +1.25 Hz | runtime counter | × 0.1 s?| | `uptime_ds` |
**Confidence levels**: Bold-worthy certain (confirmed by live values + UI labels): pack_voltage, cells 01-16, SOC, SOH, cell_count, capacity_ah. Medium (fits data, unverified): temps, current, bitfields, Battery_Mode. Unknown: regs 32, 35 (probably Error_Code but value always 0 so far), 38-40, 43-45.
## Warning / protection bit maps
From the UI labels in lv_host.app (bit 0 = first listed):
| Bit | Warning (reg 33) | Protection (reg 34) |
|-----|-------------------|---------------------|
| 0 | pack_ov | pack_ov |
| 1 | cell_ov | cell_ov |
| 2 | pack_uv | pack_uv |
| 3 | cell_uv | cell_uv |
| 4 | charge_oc | charge_oc |
| 5 | discharge_oc | discharge_oc |
| 6 | temp_anomaly | temp_anomaly |
| 7 | mos_ot | mos_ot |
| 8 | charge_ot | charge_ot |
| 9 | discharge_ot | discharge_ot |
| 10 | charge_ut | charge_ut |
| 11 | discharge_ut | discharge_ut |
| 12 | low_capacity | float_stopped |
| 13 | other_error | discharge_sc |
Each bit becomes an HA sensor reporting `on` / `off`. Exact bit ordering is guessed from UI display order — adjust if the EG4 tool ever shows a flag we don't match.
## Hardware topology notes
### Critical: RS485 port only works when the pack is standalone
**Empirical finding** (2026-04-24): the LP4V2's external `RS485` port
only answers Modbus queries when the pack is **not** daisy-chained to
other packs via `Comm1`/`Comm2`. Specifically:
- With daisy chains intact (bat1 Comm2 → bat2 Comm1 etc.), one pack
elects as master and polls slaves over the internal hub bus
(19200 Modbus on Comm1/Comm2). Slave packs' RS485 ports go silent —
only the master responds externally.
- Remove the inter-pack Comm jumpers and each pack becomes a
self-contained master: its Comm1/Comm2 LEDs flash (it's trying to
poll slaves that aren't there), and its own RS485 port becomes fully
live for external queries.
**Implication**: `modbus_per_pack` mode requires **each pack standalone**
— one FTDI adapter per pack's RS485 port, no inter-pack Comm jumpers.
This is how we got bat1 responding cleanly. If the batteries later
need to be daisy-chained to an inverter, only the master pack's RS485
port will answer external queries; slave per-pack data would need to
come from decoding the Comm1/Comm2 hub bus instead (a future mode).
### Port roles on the LP4V2 pack (from the port matrix test)
| Port | Role | Pins carrying signal | Protocol / baud |
|-------|--------------------------------|-------------------------|-----------------------|
| CAN | Inverter CAN comms | (CAN-specific pinout) | CANbus (not RS-485) |
| RS485 | External monitor | 1-2 | Modbus RTU @ 9600 |
| Comm1 | Inter-pack hub bus (in/out) | 1-2 and 7-8 both tap it | Modbus RTU @ 19200 |
| Comm2 | Inter-pack hub bus (in/out) | 1-2 and 7-8 both tap it | Modbus RTU @ 19200 |
- The **stock USB-RS485 cable** ships wired to pins 1-2 — usable on either Comm or RS485.
- The **pin 7-8 modified cable** only gains us the Comm/Comm2 hub-bus tap; since pins 1-2 reach the same bus, it's not strictly necessary. Kept in the toolkit for diagnostic purposes.
- Factory inter-pack jumpers (between packs) are 8-conductor CAT5 — they carry both pin pairs.
### Adapters
On this host, three USB-FTDI adapters are plugged into the three packs' RS485 ports:
| Adapter ID | Pack | `/dev/serial/by-id/...` |
|------------------|----------------|--------------------------------------------------------|
| A994XMVK | bat1 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XMVK-if00-port0` |
| A994XGUY | bat2 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XGUY-if00-port0` |
| A994XMBR | bat3 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XMBR-if00-port0` |
Each pack gets polled on its own bus → no shared-bus arbitration, no master/slave coordination needed, pack Modbus address is 0x40 for all of them.
## LVX6048 compatibility (still true)
LVX6048 BMS port protocols: `PYL` (Pylontech), `LIb` (MPP LIO), `WEC` (WECO), `SOL` (Soltaro), `VSC` (Pylontech-CAN), `USE` (voltage-only). **No native EG4 LP4V2 support.** For inverter↔battery comms, set `P05/P14 = USE` and manage charge profile via `lvx-flash`. See DIY Solar Forum threads 67496 & 96019, LVX6048WP manual §9-2.
## Bring-up checklist (when a new pack goes live)
1. Wire: plug USB-FTDI adapter (stock pin-1-2 cable) into the pack's **RS485** port.
2. Confirm the pack's BMS is powered (LEDs steady on Comm1 + Comm2, not dark).
3. Verify the `/dev/serial/by-id/...` symlink exists for the adapter.
4. Add a pack entry to the config:
```yaml
packs:
- name: lifepower4_N
address: 0x40
port: /dev/serial/by-id/usb-FTDI_...-if00-port0
baud: 9600
```
5. `sudo systemctl restart eg4-battery.service`. Watch journal — within ~10 s you should see the first MQTT publish, or `WARNING: no/bad response` if the pack isn't answering.
6. In HA: `EG4 LifePower4 lifepower4_N` device appears with ~65 auto-discovered entities.
## Sources / references
- `lv_host.app` (Qt) — Contents/MacOS/lv_host Mach-O binary + my1.db/my2.db SQLite schemas
- `../battery/eg4_lifepower.py` — V1 7E/0D decoder (Louisvdw/dbus-serialbattery port), historical reference
- `../battery/sweep.py` — protocol + baud scanner used for initial triage
- EG4 "Cables Needed for Updating" PDF
- EG4 Community Forum: "Specs for LifePower4 V2 BAT-COM ports"
- LVX6048WP manual §9-2 (BMS pinout), §Programs P03/P05