"""Read a few Solis hybrid inverter registers through the Waveshare Modbus TCP gateway.

No third-party packages needed (plain sockets, Modbus function 04).

Usage:  python solis_read_test.py [host] [port] [unit]
        defaults: 192.168.1.201 502 1
"""
import socket
import struct
import sys

HOST = sys.argv[1] if len(sys.argv) > 1 else "192.168.1.201"
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 502
UNIT = int(sys.argv[3]) if len(sys.argv) > 3 else 1


def recv_exact(sock, n):
    buf = b""
    while len(buf) < n:
        chunk = sock.recv(n - len(buf))
        if not chunk:
            raise ConnectionError("gateway closed the connection")
        buf += chunk
    return buf


def read_input_registers(sock, tid, addr, count):
    """Modbus function 04. Returns a list of unsigned 16-bit register values."""
    pdu = struct.pack(">BHH", 4, addr, count)
    sock.sendall(struct.pack(">HHHB", tid, 0, len(pdu) + 1, UNIT) + pdu)
    _tid, _proto, length, _unit = struct.unpack(">HHHB", recv_exact(sock, 7))
    body = recv_exact(sock, length - 1)
    if body[0] & 0x80:
        raise IOError(f"Modbus exception code {body[1]} reading register {addr}")
    return list(struct.unpack(f">{body[1] // 2}H", body[2:]))


def s16(v):
    return v - 0x10000 if v & 0x8000 else v


def u32(hi, lo):
    return (hi << 16) | lo


def s32(hi, lo):
    v = u32(hi, lo)
    return v - (1 << 32) if v & 0x80000000 else v


with socket.create_connection((HOST, PORT), timeout=10) as s:
    s.settimeout(10)
    print(f"Connected to {HOST}:{PORT}, unit {UNIT}")

    y, mo, d, h, mi, se = read_input_registers(s, 1, 33022, 6)
    print(f"Inverter clock   : 20{y % 100:02}-{mo:02}-{d:02} {h:02}:{mi:02}:{se:02}")

    print(f"PV power         : {u32(*read_input_registers(s, 2, 33057, 2))} W")
    print(f"Inverter AC power: {s32(*read_input_registers(s, 3, 33079, 2))} W")
    print(f"Inverter temp    : {s16(read_input_registers(s, 4, 33093, 1)[0]) / 10:.1f} C")

    b = read_input_registers(s, 5, 33133, 18)  # 33133 .. 33150
    print(f"Battery voltage  : {b[0] / 10:.1f} V")
    print(f"Battery current  : {s16(b[1]) / 10:.1f} A ({'discharging' if b[2] else 'charging'})")
    print(f"Battery SOC      : {b[6]} %")
    print(f"House load       : {b[14]} W")
    print(f"Battery power    : {s32(b[16], b[17])} W")
