暂无描述

lrosales cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前
docs cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前
tools cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前
.gitignore cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前
README.md cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前
must_pv1800_monitor.py cee4eed3a7 Initial commit: MUST PV1800 (2024) Modbus RTU monitor 1 月之前

README.md

must

Real-time monitor for the MUST PV1800 (2024) solar inverter/charger via Modbus RTU.

The PV1800 firmware is non-compliant — it ignores the count field and often dumps a 39-register block in response to any read. This script handles that by reading the whole block in one go and extracting the registers we care about, which is the only way to unambiguously identify which response belongs to which request.

Hardware

  • Inverter: MUST PV1800 (2024 variant, 24V battery / 120V AC).
  • USB-serial adapter: CH340 (vendor 1a86:7523), shows up as /dev/ttyUSB0 on the remote host.
  • Remote host: Proxmox LXC at 192.168.44.94, user root. The CH340 is plugged into this host directly.
  • Baud: 19200 8N1, Modbus RTU, slave ID 4.

Files

File Purpose
must_pv1800_monitor.py The monitor script. Self-contained — embeds the pyserial-based TCP proxy it runs on the remote host, plus the full PV1800 register map.
docs/register-map.md The PV1800 register map, derived from taHC81/MUST-ESPhome/PH1800 PV1800 EP1800 PV3500 EP3500 RS485 Modbus RTU communication Protocol 1.4.15.xlsx.
docs/quirks.md Things I learned the hard way about the PV1800 firmware and the CH340 driver. Read this before debugging "why isn't it working".
tools/test_proxy.py A standalone copy of the TCP<->serial proxy, useful for ad-hoc debugging with nc or socat.

Quick start

# Live updates every 3 s (default)
./must_pv1800_monitor.py

# One snapshot, for scripting
./must_pv1800_monitor.py --once

# JSON output, one line per snapshot
./must_pv1800_monitor.py --json

# If you're on the LXC with the USB cable attached directly
./must_pv1800_monitor.py --serial

# If you already have a socat/ser2net bridge running on a remote host
./must_pv1800_monitor.py --tcp --tcp-host 192.168.44.94 --tcp-port 8502

How it works

┌─────────────────┐    SSH     ┌────────────────────┐    pyserial    ┌──────────────┐
│  this script    │ ─────────► │ remote host LXC    │ ────────────► │ /dev/ttyUSB0 │
│  (your laptop)  │ ◄───────── │ 192.168.44.94      │ ◄──────────── │   (CH340)    │
└─────────────────┘            └────────────────────┘                └──────┬───────┘
                                                                        │ RS-485
                                                                        ▼
                                                                  ┌──────────────┐
                                                                  │  MUST PV1800 │
                                                                  │  slave ID 4  │
                                                                  └──────────────┘
  1. The script opens an SSH session to the remote host.
  2. It uploads a tiny pyserial-based TCP<->serial proxy (/tmp/.must_pv1800_proxy.py) over SFTP and runs it as a detached background process.
  3. The proxy pre-flushes stale bytes from the kernel TTY buffer (critical — see docs/quirks.md), then opens /dev/ttyUSB0 and bridges it to 127.0.0.1:9500 on the remote.
  4. The script tunnels back through SSH (direct-tcpip channel) to that localhost port and runs Modbus RTU over the resulting byte stream.
  5. Each cycle polls three register blocks (CHARGER, INVERTER, SETTINGS), prints a snapshot, sleeps --interval seconds, and loops.

Dependencies

  • Local: pyserial, paramiko. Install with:

    pip install --break-system-packages pyserial paramiko
    
  • Remote: python3-serial (apt package). The script auto-installs it if missing — just needs apt-get available.

Output

Example snapshot (panels disconnected, inverter in ByPass mode feeding a 350W load from grid):

=== 2026-09-10 16:51:55 ===
---------------
  CHARGER
---------------
  Charger workstate = Standby
  MPPT state        = Stop
  Charging state    = Stop
  PV voltage        = 0.0V
  Battery voltage   = 27.7V
  Charger current   = 0.0A
  Charger power     = 0.0W

---------------
  INVERTER
---------------
  Inverter Work state = ByPass
  Battery voltage     = 28.2V
  Inverter voltage    = 115.7V
  Grid voltage        = 117.0V
  Inverter power      = -17W
  Grid power          = -369W
  Load power          = 351W
  System load         = 18.0%
  AC radiator temp    = 33.0°C
  Transformer temp    = 52.0°C
  Battery power       = 13W
  Battery current     = -3A

References

  • taHC81/MUST-ESPhome — ESPHome config for a similar inverter. Their PV18-output-example.log shows the register map in action.
  • PH1800 PV1800 EP1800 PV3500 EP3500 RS485 Modbus RTU communication Protocol 1.4.15.xlsx (in the same repo) — the authoritative register map for the inverter family. Parsed copy in docs/register-map.md.

Caveats / known issues

  • The PV1800 occasionally drops a byte during a long Modbus dump. The script retries on CRC mismatch and logs [poll error: ...] to stderr. The next cycle almost always succeeds.
  • DC radiator temp reads as 0.0°C on units that don't have that sensor installed. Not a bug.
  • Inverter power shows a small negative value in ByPass mode — that's the measurement offset of the inverter just passing grid through.
  • The script keeps a proxy running on the remote host between invocations. If Ctrl-C doesn't clean it up, run on the remote: pkill -9 -f must_pv1800_proxy.