#!/usr/bin/env python3
"""
sigen-poller.py  --  Sigenergy cloud -> Shelly Virtual Components
Polls /openapi/systems/{systemId}/energyFlow every 60 s.
Pushes PV / SOC / Battery / Grid / Load to VCs 200-204 on a Shelly device.
Can run as a Windows Scheduled Task (trigger: At logon, restart on failure).

Configuration — set these environment variables before running:
  SIGEN_AUTH_KEY   base64(AppKey:AppSecret) from developer.sigencloud.com
  SIGEN_SYSTEM_ID  your power station system ID (mySigen app -> Settings -> ...)
  SHELLY_IP        local IP address of your Shelly device  e.g. 192.168.1.100

  Windows PowerShell:
    $env:SIGEN_AUTH_KEY  = "base64string..."
    $env:SIGEN_SYSTEM_ID = "YOURSYSTEMID"
    $env:SHELLY_IP       = "192.168.1.100"

  Linux / macOS:
    export SIGEN_AUTH_KEY="base64string..."
    export SIGEN_SYSTEM_ID="YOURSYSTEMID"
    export SHELLY_IP="192.168.1.100"

How to get SIGEN_AUTH_KEY:
  1. Log in to developer.sigencloud.com
  2. Control Center -> Settings -> copy AppKey, click Generate for AppSecret
  3. base64_key = base64( AppKey + ":" + AppSecret )
  In Python: import base64; base64.b64encode(b"appkey:appsecret").decode()
"""
import urllib.request, json, time, sys, os, base64

AUTH_KEY  = os.environ.get("SIGEN_AUTH_KEY",  "")
SYSTEM_ID = os.environ.get("SIGEN_SYSTEM_ID", "")
SHELLY_IP = os.environ.get("SHELLY_IP",       "")

if not AUTH_KEY or not SYSTEM_ID or not SHELLY_IP:
    print("ERROR: set environment variables SIGEN_AUTH_KEY, SIGEN_SYSTEM_ID, SHELLY_IP")
    sys.exit(1)

CFG = {
    "authKey":  AUTH_KEY,
    "systemId": SYSTEM_ID,
    "baseUrl":  "https://api-eu.sigencloud.com",
    "shelly":   f"http://{SHELLY_IP}",
    "interval": 60,
}

token    = None
tokenExp = 0

def get_token():
    global token, tokenExp
    if token and time.time() < tokenExp - 300:
        return token
    req = urllib.request.Request(
        CFG["baseUrl"] + "/openapi/auth/login/key",
        data=json.dumps({"key": CFG["authKey"]}).encode(),
        headers={"Content-Type": "application/json"}, method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:
        resp = json.loads(r.read())
    td = json.loads(resp["data"])
    token    = td["accessToken"]
    tokenExp = time.time() + td["expiresIn"]
    print(f"[auth] token OK, expires in {td['expiresIn']}s", flush=True)
    return token

def fetch_and_push():
    tok = get_token()
    req = urllib.request.Request(
        CFG["baseUrl"] + f"/openapi/systems/{CFG['systemId']}/energyFlow",
        headers={"Authorization": "Bearer " + tok, "sigen-region": "eu"})
    with urllib.request.urlopen(req, timeout=15) as r:
        d = json.loads(r.read())
    v = json.loads(d["data"]) if isinstance(d.get("data"), str) else d.get("data", {})
    pvW   = round((v.get("pvPower",      0) or 0) * 1000)
    socPc =        v.get("batterySoc",   0) or 0
    batW  = round((v.get("batteryPower", 0) or 0) * 1000)
    griW  = round((v.get("gridPower",    0) or 0) * 1000)
    loadW = round((v.get("loadPower",    0) or 0) * 1000)
    print(f"[data] PV={pvW}W SOC={socPc}% Bat={batW}W Grid={griW}W Load={loadW}W", flush=True)
    for vc_id, val in [(200,pvW),(201,socPc),(202,batW),(203,griW),(204,loadW)]:
        urllib.request.urlopen(urllib.request.Request(
            CFG["shelly"] + "/rpc/Number.Set",
            data=json.dumps({"id": vc_id, "value": val}).encode(),
            headers={"Content-Type": "application/json"}, method="POST"), timeout=10)

print("sigen-poller starting", flush=True)
while True:
    try:
        fetch_and_push()
    except Exception as e:
        print(f"[error] {e}", flush=True)
    time.sleep(CFG["interval"])
