Files
shaggy-solar/LVX6048/README.md
2026-04-27 06:50:04 -04:00

205 lines
10 KiB
Markdown
Raw Permalink 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.
# LVX6048 → Home Assistant
Reproducible install package for monitoring 2× MPP Solar LVX6048 inverters over
USB-HID (PI18) via [`powermon`](https://github.com/jblance/powermon), publishing
to a Home Assistant MQTT broker with HA auto-discovery.
## What's in the box
```
LVX6048/
├── README.md ← start here
├── Install.md ← detailed walkthrough (what install.sh does)
├── Monitoring.md ← background / design notes
├── install.sh ← one-shot installer (idempotent, safe to re-run)
├── etc/ mirror of target system paths
│ ├── udev/rules.d/99-lvx6048.rules
│ └── systemd/system/
│ ├── lvx-resolve-links.service
│ ├── lvx-control.service
│ ├── powermon.service
│ ├── powermon2.service
│ ├── powermon.service.d/10-resolver.conf
│ └── powermon2.service.d/10-resolver.conf
├── config/powermon/ lands at ~/.config/powermon/ (mode 600)
│ ├── powermon.yaml ← unit #1 — edit MQTT creds before deploying
│ └── powermon2.yaml ← unit #2 — edit MQTT creds before deploying
├── bin/
│ └── lvx-resolve-links ← installed as /usr/local/sbin/lvx-resolve-links
│ (install.sh rewrites the shebang to the uv-installed python)
├── powermon-patches/ ← drop-in files for the uv tool install
│ ├── README.md ← what each patch does + upgrade path
│ ├── pi18.py, usbport.py, mqttbroker.py,
│ └── port_config_model.py, ports_init.py
├── lvx-flash/ ← settings-profile CLI
│ ├── flash.py ← dump / diff / apply / compare / sync-check
│ ├── README.md
│ └── profiles/
│ ├── current.yaml ← legacy snapshot
│ └── eg4-lp4-v2.yaml ← canonical EG4 LP4 v2 LiFePO4 bank profile
├── lvx-control/ ← HA → powermon adhoc command bridge
│ ├── lvx-control ← single-file Python daemon
│ └── README.md ← supported actions / topic layout
├── homeassistant/ ← HA-side control entities + dashboard
│ ├── mqtt_controls.yaml ← selects + numbers for the controls
│ ├── lovelace_controls.yaml ← dashboard view
│ └── README.md
└── smoketest/ ← adhoc test configs (one-off powermon -C usage)
├── console.yaml
└── smoketest.yaml
```
## Quick start (reproducing on a fresh machine)
```bash
# 1. Clone / scp this folder into place (e.g. /home/<user>/solar/LVX6048)
cd ~/solar/LVX6048
# 2. Install uv if not already: https://docs.astral.sh/uv/
# 3. Run the installer
./install.sh
# 4. Edit the two things install.sh warns about:
# a. ~/.config/powermon/powermon{,2}.yaml — MQTT broker IP / user / password
# b. /usr/local/sbin/lvx-resolve-links — SERIAL_UNIT_1 / SERIAL_UNIT_2 (see below)
# lvx-flash/flash.py — same two constants
# 5. Capture each inverter's PI18 serial (with services stopped):
sudo systemctl stop lvx-resolve-links.service powermon.service powermon2.service
for d in /dev/hidraw0 /dev/hidraw1; do
TMP=$(mktemp --suffix=.yaml)
printf 'loop: once\ndevice:\n name: probe\n port: {type: usb, path: %s, protocol: PI18}\ncommands:\n - {command: ID, trigger: {loops: 1}}\n' "$d" > "$TMP"
echo "=== $d ==="
~/.local/bin/powermon -C "$TMP" 2>&1 | grep serial_number
rm -f "$TMP"; sleep 2
done
# Edit SERIAL_UNIT_{1,2} in /usr/local/sbin/lvx-resolve-links and lvx-flash/flash.py,
# then:
sudo systemctl start lvx-resolve-links.service powermon.service powermon2.service
```
## How the pieces fit together
```
┌─────────────────────────┐ ┌─────────────────────────┐
│ LVX6048 #1 (USB-HID) │ ──▶ │ /dev/hidraw{0|1} │
│ LVX6048 #2 (USB-HID) │ ──▶ │ (vid:pid 0665:5161) │
└─────────────────────────┘ └──────────┬──────────────┘
│ (99-lvx6048.rules → group=dialout)
┌──────────────────────────────────┐
│ lvx-resolve-links.service │
│ (oneshot, runs before powermon) │
│ │
│ probes each hidraw w/ PI18 ID │
│ creates /dev/lvx6048-{1,2} │
│ symlinks keyed to serial │
└──────────┬───────────────────────┘
│ After= / Requires=
┌──────────────────────────────────┐
│ powermon.service (unit #1) │
│ powermon2.service (unit #2) │
│ │
│ poll GS / MOD / PIRI / ET │
│ publish to HA auto-discovery │
│ topics under homeassistant/* │
└──────────┬───────────────────────┘
│ MQTT (port 1883)
┌──────────────────────────────────┐
│ Home Assistant Mosquitto broker │
│ ~23 auto-discovered sensors/unit │
└──────────────────────────────────┘
```
Separate from the monitoring pipeline, **`lvx-flash/`** is a manual settings
tool: dump the inverter's current config into a YAML profile, diff against a
target profile, apply changes safely (stops powermon, writes via PI18 setters,
verifies via PIRI readback). Also supports `compare` (diff live settings
between two inverters) and `sync-check` (verify parallel-stack health).
## Cable moves and inverter power-cycles
Identification is PI18-serial-based, so moving USB cables between hub ports
never requires config edits. The udev rule fires `lvx-resolve-links` and
restarts both powermon services automatically whenever a matching hidraw
device appears (`ACTION=="add"`), so a cable shuffle or inverter power-cycle
recovers on its own — no manual `systemctl restart` needed in the common case.
If recovery looks stuck (e.g. a unit was off long enough that its hidraw
disappeared but the new one didn't trigger the rule for some reason):
```bash
sudo systemctl restart lvx-resolve-links.service powermon.service powermon2.service
```
## Replacing an inverter
When a unit is swapped, capture its new PI18 serial (see step 5 of Quick start)
and update the two `SERIAL_UNIT_*` constants in:
- `/usr/local/sbin/lvx-resolve-links`
- `~/solar/LVX6048/lvx-flash/flash.py`
then restart the three services.
## Status — done / pending
**Done (2026-04-26):**
- **Parallel commissioning.** Both inverters operationally paralleled and
load-sharing on a 3× EG4 LP4 v2 bank. See `2026-04-26-parallel.md`.
- **Main-CPU firmware parity.** Both units now at main 06306. Slave-CPU
firmware still differs (06126 / 06021) but is benign: cluster handshake
completes, current sharing is symmetric, no faults. The vendor's
`LVX6048 FW63.06.zip` includes a `dsp.hex` slave bin if version parity
is ever desired for cosmetic reasons.
- **Hot-plug recovery.** udev rule auto-restarts the resolver + powermon
services when an inverter re-enumerates. Resolver also has a retry loop
to handle the brief race when both units come up nearly together.
- **`parallel_instance_number` decoder.** Replaced upstream's misleading
2-value flag with a proper instance-index decoder (0 = master, 1+ =
slaves) in both GS and PGS. `flash.py sync-check` updated to match.
- **MOD code 06.** Added "Charge" mapping (was crashing sync-check).
Educated guess on the label — revisit if it shows up in unrelated states.
- **Battery profile.** `lvx-flash/profiles/eg4-lp4-v2.yaml` captures the
conservative open-loop LFP policy in use (USER mode, 56.4 V bulk,
54.0 V float, 48.0 V cutoff, 60 A MCHGC).
- **HA remote control.** `lvx-control` daemon bridges HA-friendly MQTT
topics (`solar/control/lvx6048/<action>`) to powermon's adhoc-command
queue, mirroring every change to both inverters in lock-step. Day-to-day
controls (POP / PCP / PSP / MCHGC / MUCHGC) exposed; risky setters
(battery thresholds, type, output mode, factory reset) deliberately not.
Companion HA configs live in `homeassistant/`.
**Pending / nice-to-have:**
- **PGS field layout.** The LVX6048-specific 30-field PGS response is only
partially named in `powermon-patches/pi18.py` — fields 4, 1322, 2328,
and 30 are still exposed as raw strings. Now that the cluster is actually
paralleled, more of these fields produce meaningful values; worth a
second pass to identify them by comparing PGS0 vs PGS1 captures from
master and slave.
- **Control / set commands via HA.** PI18 setters (POP, PCP, MCHGC, MUCHGC,
PF) are implemented in `lvx-flash/flash.py` for offline use, but aren't
exposed as HA button/select entities. Deferred until monitoring has been
stable for at least a week.
- **Closed-loop BMS comms.** Currently open-loop — inverters estimate SoC
from voltage, batteries don't push real-time SoC / charge limits to the
inverter. Closed-loop would give better SoC accuracy and dynamic CC
tapering near full. Path is the dedicated CAN port on the master pack →
inverter BMS port (separate cable from the inter-pack daisy-chain).
Deferred.