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

Examples:
  python3 modbus_w.py 192.168.65.146 42000 21930   # RS485-Kontrolle aktivieren
  python3 modbus_w.py 192.168.65.146 42010 0        # Force Mode: Stop/Idle
  python3 modbus_w.py 192.168.65.146 42010 1        # Force Mode: Laden
  python3 modbus_w.py 192.168.65.146 42010 2        # Force Mode: Entladen
  python3 modbus_w.py 192.168.65.146 42020 800      # Ladeleistung 800 W
  python3 modbus_w.py 192.168.65.146 42021 800      # Entladeleistung 800 W
"""

import sys
import socket
import struct

PORT = 502

def modbus_write(host, register, value, slave_id):
    # FC6: Write Single Register
    pdu     = struct.pack(">BHH", 0x06, register, value)
    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) < 12:
            chunk = sock.recv(256)
            if not chunk:
                break
            raw += chunk
    except socket.timeout:
        pass
    finally:
        sock.close()

    return raw

def try_write(host, register, value, slave_id):
    raw = modbus_write(host, register, value, slave_id)
    if not raw or len(raw) < 8:
        return False, 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 False, f"Exception FC=0x{fc_resp:02X} → {exc_text}"

    # FC6 Erfolg: Gerät echot Register und Wert zurück
    if len(raw) >= 12:
        reg_echo = struct.unpack(">H", raw[8:10])[0]
        val_echo = struct.unpack(">H", raw[10:12])[0]
        return True, f"OK — Register {reg_echo} = {val_echo}"

    return True, "OK"

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

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

    if not (0 <= value <= 65535):
        print(f"ERROR: Wert {value} außerhalb uint16 (0–65535)")
        sys.exit(1)

    ok, msg = try_write(host, register, value, slave_id)
    print(f"Register : {register}  Wert: {value}  Slave: {slave_id}")
    print(msg)
    sys.exit(0 if ok else 1)
