#!/usr/bin/env python3
# Laeuft auf dem HETZNER-SERVER. Abonniert die eingehenden Live-Topics,
# prueft sie gegen den Sende-Standard und protokolliert fertige 15-min-Intervalle.
#
# Zugang ueber Umgebungsvariablen (keine Secrets im File):
#   MQTT_HOST, MQTT_PORT (=8883), MQTT_USER, MQTT_PASS, MQTT_TOPIC (=eeg/greenlama/+/live)
# Installation:  pip install paho-mqtt
# Start:  MQTT_HOST=localhost MQTT_USER=leser MQTT_PASS=... python3 abo_check.py
import os, json, ssl, csv
from datetime import datetime, timezone
import paho.mqtt.client as mqtt

HOST=os.environ.get("MQTT_HOST","localhost"); PORT=int(os.environ.get("MQTT_PORT","8883"))
USER=os.environ.get("MQTT_USER"); PW=os.environ.get("MQTT_PASS")
TOPIC=os.environ.get("MQTT_TOPIC","eeg/greenlama/+/live")
LOGFILE=os.environ.get("MQTT_LOG","live_intervalle.csv")
state={}   # topic -> {"t0":..., "last_ts":..., "last_e":...}

def piso(s):
    try: return datetime.fromisoformat(str(s))
    except Exception: return None

def logrow(topic,t0,e):
    mid=topic.split("/")[2] if topic.count("/")>=2 else topic
    new=not os.path.exists(LOGFILE)
    with open(LOGFILE,"a",newline="",encoding="utf-8") as f:
        w=csv.writer(f)
        if new: w.writerow(["mitglied","t0","e_wh_final"])
        w.writerow([mid,t0,e])

def on_connect(c,u,flags,rc,props=None):
    print(f"[{datetime.now():%H:%M:%S}] verbunden (rc={rc}) -> abonniere {TOPIC}")
    c.subscribe(TOPIC,qos=0)

def on_message(c,u,msg):
    now=datetime.now(timezone.utc); raw=msg.payload.decode("utf-8","replace")
    try: d=json.loads(raw)
    except Exception:
        print(f"WARN {msg.topic}: kein JSON  -> {raw[:70]}"); return
    warn=[]
    for k in ("e_wh","t0","ts"):
        if k not in d: warn.append(f"'{k}' fehlt")
    e=d.get("e_wh"); t0=piso(d.get("t0")); ts=piso(d.get("ts"))
    if not isinstance(e,(int,float)): warn.append("e_wh keine Zahl")
    if t0 and (t0.minute%15!=0 or t0.second!=0): warn.append("t0 nicht auf :00/:15/:30/:45")
    el=(ts-t0).total_seconds() if (ts and t0) else None
    if el is not None and not (0<=el<=900): warn.append(f"ts-t0={el:.0f}s ausserhalb 0..900")
    if isinstance(e,(int,float)) and abs(e)>40000: warn.append(f"|e_wh|={e:.0f} unplausibel")
    st=state.get(msg.topic); de=None
    if st:
        dt=(ts-st["last_ts"]).total_seconds() if (ts and st["last_ts"]) else None
        if dt is not None and dt>90: warn.append(f"Takt {dt:.0f}s (Nachricht verpasst?)")
        if t0 and st["t0"] and t0>st["t0"]:               # neues Intervall -> altes wegschreiben
            logrow(msg.topic,st["t0"].isoformat(),st["last_e"])
            if isinstance(e,(int,float)) and abs(e)>2000: warn.append(f"nach Reset e_wh={e:.0f} (nicht ~0?)")
        elif t0 and st["t0"]==t0 and isinstance(e,(int,float)) and isinstance(st["last_e"],(int,float)):
            de=e-st["last_e"]                              # nur Info; 0/klein ist normal (Speicher haelt Netto ~0)
    state[msg.topic]={"t0":t0,"last_ts":ts,"last_e":e}
    mid=msg.topic.split("/")[2] if msg.topic.count("/")>=2 else msg.topic
    kw=(e*3600/el/1000) if (el and el>0 and isinstance(e,(int,float))) else float("nan")
    des=f"{de:+.0f}" if de is not None else "  ."
    flag="OK  " if not warn else "WARN"
    print(f"[{now:%H:%M:%S}] {flag} {mid:<8} e_wh={e:>8} de={des:>6} t0={d.get('t0','?')[11:16]} +{(el or 0):>3.0f}s ~{kw:>6.1f}kW {'| '+'; '.join(warn) if warn else ''}")

try: client=mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="strom-signal-check")
except Exception: client=mqtt.Client(client_id="strom-signal-check")
if USER: client.username_pw_set(USER,PW)
if PORT==8883: client.tls_set(cert_reqs=ssl.CERT_REQUIRED)   # System-CA (Let's Encrypt)
client.on_connect=on_connect; client.on_message=on_message
print(f"verbinde zu {HOST}:{PORT} ...")
client.connect(HOST,PORT,60)
client.loop_forever()
