#!/usr/bin/env python3
"""
Marstek Venus A - Modbus Register Reader (raw socket)
Usage: python3 marstek_read.py <IP> <REGISTER> [slave_id]

Examples:
  python3 marstek_read.py 192.168.65.146 32104   # SOC
  python3 marstek_read.py 192.168.65.146 30001   # Battery Power
  python3 marstek_read.py 192.168.65.146 42010   # Force Mode
"""

import sys
import socket
import struct

PORT = 502

def modbus_request(host, register, slave_id, fc):
    pdu     = struct.pack(">BHH", fc, register, 1)
    length  = 1 + len(pdu)
    request = struct.pack(">HHHB", 1, 0, length, slave_id) + pdu

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(5)
    sock.connect((host, PORT))
    sock.sendall(request)

    raw = b""
    try:
        while len(raw) < 9:
            chunk = sock.recv(256)
            if not chunk:
                break
            raw += chunk
    except socket.timeout:
        pass
    finally:
        sock.close()

    return raw

def try_read(host, register, slave_id, fc):
    raw = modbus_request(host, register, slave_id, fc)
    if not raw or len(raw) < 9:
        return None, f"Keine/unvollständige Antwort ({len(raw)} Bytes)"

    fc_resp = raw[7]

    # Marstek vA Firmware-Bug: Exception MBAP length=4 statt 3 → trotzdem auswerten
    if fc_resp & 0x80:
        exc = raw[8] if len(raw) > 8 else "?"
        exc_text = {1: "Illegal Function", 2: "Illegal Data Address", 3: "Illegal Data Value"}.get(exc, f"Code {exc}")
        return None, f"Exception FC=0x{fc_resp:02X} → {exc_text}"

    raw_val = struct.unpack(">H", raw[9:11])[0]
    return raw_val, None

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(__doc__)
        sys.exit(1)

    host      = sys.argv[1]
    register  = int(sys.argv[2])
    slave_id  = int(sys.argv[3]) if len(sys.argv) > 3 else 1

    # Reihenfolge: erst FC3 (Holding), dann FC4 (Input)
    for fc, name in [(3, "Holding FC3"), (4, "Input FC4")]:
        val, err = try_read(host, register, slave_id, fc)
        if val is not None:
            signed = val - 0x10000 if val >= 0x8000 else val
            print(f"Register : {register}  [{name}]  Slave: {slave_id}")
            print(f"Roh      : {val}  (0x{val:04X})")
            print(f"Signed   : {signed}")
            sys.exit(0)
        else:
            print(f"FC{fc}: {err}")

    print("ERROR: Register mit keinem FC lesbar.")
    sys.exit(1)
