#!/usr/bin/env python3

import csv
import datetime
import socket
import time
from pathlib import Path
import sys

# ----------------- CONFIGURATION -----------------

# PVM1010 network settings
PVM_IP = "192.168.1.178"    # PVM1010 IP on your LAN
PVM_PORT = 14917            # UDP port

# Logging interval (seconds) – 900 = 15 minutes
POLL_INTERVAL_SECONDS = 900

# Log directory (NAS path)
LOG_DIR = Path(r"\\192.168.1.122\nas\PVPowered")
LOG_PREFIX = "pvm1010"

# Scale factors for PVP5200 (from PV Powered documentation)
SCALE_P_AC = 0.726318359     # AC power
SCALE_V_AC = 0.018493652     # AC voltage
SCALE_I_AC = 0.001495361     # AC current
SCALE_V_DC = 0.015441895     # DC voltage
SCALE_TEMP = 0.01            # degrees C per raw unit


# ----------------- CSV LOGGING -----------------

def ensure_log_dir():
    if not LOG_DIR.exists():
        LOG_DIR.mkdir(parents=True, exist_ok=True)


def get_today_logfile():
    """Return Path for today's CSV file, rotated by date."""
    today = datetime.date.today().isoformat()
    return LOG_DIR / f"{LOG_PREFIX}_{today}.csv"


def ensure_csv_header(path: Path):
    """Create CSV with header row if it does not exist."""
    if not path.exists():
        with path.open("w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(
                [
                    "timestamp",
                    "state_hex",
                    "fault_hex",
                    "ac_power_w",
                    "ac_voltage_v",
                    "ac_current_a",
                    "dc_voltage_v",
                    "temperature_c",
                    "total_kwh",
                ]
            )


def append_csv_row(path: Path, decoded: dict):
    """Append one row of decoded data to CSV."""
    ensure_csv_header(path)
    now = datetime.datetime.now().isoformat(timespec="seconds")

    state_hex = f"0x{decoded['state']:04X}"
    fault_hex = f"0x{decoded['fault']:04X}"

    row = [
        now,
        state_hex,
        fault_hex,
        f"{decoded['ac_power_w']:.2f}",
        f"{decoded['ac_voltage_v']:.2f}",
        f"{decoded['ac_current_a']:.3f}",
        f"{decoded['dc_voltage_v']:.2f}",
        f"{decoded['temperature_c']:.2f}",
        decoded["total_kwh"],
    ]

    with path.open("a", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(row)


# ----------------- PVM1010 UDP HANDLING -----------------

def request_pvm_packet(ip: str, port: int, timeout: float = 5.0):
    """
    Send a UDP request and receive the PVM1010 response.

    Returns: list of 18 uint16 words, or None on timeout/error.
    """
    # 48‑byte zeroed buffer; device uses first 2 words (echo/reserved)
    buffer = bytearray(48)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(timeout)

    try:
        sock.sendto(buffer, (ip, port))
        data, _ = sock.recvfrom(64)  # at least 36 bytes expected
    except (socket.timeout, OSError):
        sock.close()
        return None

    sock.close()

    if len(data) < 36:
        return None

    # 18 little‑endian 16‑bit words
    words = []
    for i in range(18):
        offset = i * 2
        w = data[offset] | (data[offset + 1] << 8)
        words.append(w)

    return words


def decode_packet(words):
    """Convert raw words to engineering units for PVP5200."""
    state = words[2]
    fault = words[3]
    raw_pac = words[4]
    raw_vdc = words[5]
    raw_iac = words[6]
    raw_temp = words[7]
    raw_vac = words[8]

    total_kwh = (words[16] << 16) | words[17]

    pac_w = raw_pac * SCALE_P_AC
    vdc_v = raw_vdc * SCALE_V_DC
    iac_a = raw_iac * SCALE_I_AC
    temp_c = raw_temp * SCALE_TEMP
    vac_v = raw_vac * SCALE_V_AC

    return {
        "state": state,
        "fault": fault,
        "ac_power_w": pac_w,
        "ac_voltage_v": vac_v,
        "ac_current_a": iac_a,
        "dc_voltage_v": vdc_v,
        "temperature_c": temp_c,
        "total_kwh": total_kwh,
    }


# ----------------- MAIN LOOP -----------------

def main():
    if sys.version_info[0] < 3:
        print("This script requires Python 3.x")
        sys.exit(1)

    ensure_log_dir()

    print("PVM1010 logger starting")
    print(f"  PVM1010 IP: {PVM_IP}:{PVM_PORT}")
    print(f"  Interval: {POLL_INTERVAL_SECONDS} seconds")
    print(f"  Log dir: {LOG_DIR}")
    print("  Press Ctrl+C in a console to stop.\n")

    try:
        while True:
            words = request_pvm_packet(PVM_IP, PVM_PORT)
            if words is None:
                ts = datetime.datetime.now().isoformat(timespec="seconds")
                print(f"{ts} - No data (timeout/error)")
            else:
                decoded = decode_packet(words)
                logfile = get_today_logfile()
                append_csv_row(logfile, decoded)

                # Optional console echo
                ts = datetime.datetime.now().isoformat(timespec="seconds")
                print(
                    f"{ts}  State=0x{decoded['state']:04X}  "
                    f"P={decoded['ac_power_w']:.0f}W  "
                    f"Vac={decoded['ac_voltage_v']:.1f}V  "
                    f"Vdc={decoded['dc_voltage_v']:.1f}V  "
                    f"T={decoded['temperature_c']:.1f}C  "
                    f"kWh={decoded['total_kwh']}"
                )

            time.sleep(POLL_INTERVAL_SECONDS)

    except KeyboardInterrupt:
        print("\nLogger stopped.")


if __name__ == "__main__":
    main()