- top-level README.md (new): system overview, subsystem map, skills pointer, notable findings. - eg4battery README/NOTES: 3 -> 6 packs (pack 6 oddball 0x01/115200); SoC drift + calibration section; closed-loop comms evaluated and rejected (loses per-pack telemetry, no native protocol, doesn't fix drift); how to force a grid charge via output-priority SUB. - LVX6048 README: closed-loop pending item -> resolved decision; new "SoC calibration & known firmware quirks" section (POP single-digit/POP01, MCHGC charge-lock, re_discharge=re-discharge can't exceed float, PIRI lag, powermon adhoc wedge); skills pointer. - lvx-control README: POP encoding fix, POP crc-but-applies quirk, verify-by- behavior, grid-charge-via-SUB usage. - troubleshoot-inverter skill: corrected the stale "dead string per inverter" claim — both strings healthy; low PV is tilt/heat/shade/curtailment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lvx-control
Tiny daemon that bridges Home-Assistant-friendly MQTT topics to powermon's adhoc-command queue, so HA buttons / selects / numbers can drive the LVX6048 pair without anyone touching the LCD or the shell.
What it does
HA dashboard (mqtt button)
│
│ payload "solar_battery_utility"
▼
solar/control/lvx6048/output_priority (subscribed by lvx-control)
│
│ validates against the allow-list, encodes to PI18
│ e.g. "POP01" (output source = solar -> battery -> utility)
▼
powermon/lvx6048_1/addcommand ┐ mirrored to BOTH inverters in
powermon/lvx6048_2/addcommand ┘ the same publish so the parallel
cluster never desyncs (fault 86)
│
▼
powermon services execute the command on each unit; result lands in
powermon/lvx6048_{1,2}/result for HA to confirm
Supported actions
Friendly topic suffix → PI18 setter:
| Topic suffix | Payload values | PI18 |
|---|---|---|
output_priority |
solar_utility_battery | solar_battery_utility |
POP |
charger_priority |
solar_first | solar_and_utility | solar_only |
PCP |
solar_power_priority |
battery_load_utility_ac | load_battery_utility |
PSP |
max_charging_current |
10,20,30,40,50,60,70,80 (combined solar+AC, A) |
MCHGC |
max_utility_charging_current |
2,10,20,30,40,50,60,70,80 (grid-side, A) |
MUCHGC |
Risky setters (battery thresholds, type, output mode, factory reset) are
intentionally not exposed here — those should go through
lvx-flash/flash.py apply with an explicit profile and confirmation.
Known limitations & quirks
max_charging_current (MCHGC) and max_utility_charging_current (MUCHGC)
return Failed whenever the inverter is actively charging (charger_status
= charging, even while device_mode reads Battery) — the firmware locks
these charge-current setters during charge. Set them only in a pre-charge idle
window, or via the LCD (Programs 02 / 11), or lvx-flash/flash.py apply.
Other quirks (confirmed 2026-06-25):
POPis single-digit.output_priority=solar_battery_utilitymust encode asPOP1, NOTPOP01— the inverter silently rejects the malformedPOP01(nothing on theresulttopic, no effect). Fixed inPOP_MAPhere and inflash.py. If reinstalling, make sure the live/usr/local/bin/lvx-controlhas the fix.- A
POPset returns "crc check fails" on theresulttopic but still applies;PCPreturns a clean "Succeeded". So verify a POP change by behavior (device_mode/line_power_direction), not the result string. - PIRI readback lags ~5 min — don't trust the
*_output_source_prioritysensor to confirm a just-issued change; watch the behavior instead. - If commands stop landing on the
resulttopic entirely, powermon's adhoc queue has wedged →sudo systemctl restart powermon.service powermon2.service.
Forcing a full grid charge (calibration)
To grid-charge the bank to full (e.g. SoC calibration on a cloudy day), set
output_priority → solar_utility_battery (SUB) so the inverter runs loads off
grid and charges the battery to full, plus charger_priority → solar_and_utility.
Revert to solar_battery_utility + solar_first when done. Automated and
safety-monitored by ../../.claude/skills/lib/grid-cal-monitor.
Track the result topic to see the actual outcome of each command.
Quick test
# subscribe to results in another terminal
mosquitto_sub -h <broker> -u mqtt -P <pass> -v \
-t 'powermon/lvx6048_1/result' \
-t 'powermon/lvx6048_2/result'
# fire a control command
mosquitto_pub -h <broker> -u mqtt -P <pass> \
-t 'solar/control/lvx6048/charger_priority' \
-m 'solar_first'
You should see the encoded PCP0,0 show up at the inverters' result topics
within ~1 second, and the existing PIRI sensor in HA will reflect the new
state on the next 5-minute cycle.
Files
lvx-control/lvx-control <-- single-file Python (PEP-723 deps)
lvx-control/README.md <-- this file
etc/systemd/system/lvx-control.service
install.sh copies the script to /usr/local/bin/lvx-control and enables
the systemd unit. Broker credentials are read from
~/.config/powermon/powermon.yaml (no separate secret file).