diff --git a/smart-home/scripts/add_heizraum_io_panels.py b/smart-home/scripts/add_heizraum_io_panels.py new file mode 100755 index 000000000..6f9c8b404 --- /dev/null +++ b/smart-home/scripts/add_heizraum_io_panels.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Heizraum I/O Zeilen am Heizung-Dashboard (idempotent).""" +import json +import subprocess + +BASE = "http://100.66.78.56:3000" +DS_UID = "ffptl25vzylfke" # InfluxDB-sensors +ROW_IN = "⚡ Heizraum Eingänge (Optokoppler)" +ROW_OUT = "🔌 Heizraum Ausgänge" +PANEL_IDS = list(range(910, 920)) + + +def curl(path, method="GET", body=None): + cmd = ["curl", "-s", "-u", "admin:astral66", "-X", method, f"{BASE}{path}"] + if body is not None: + cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(body)] + r = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(r.stdout) if r.stdout.startswith(("{", "[")) else r.stdout + + +def io_stat(pid, title, signal, x, y, w=4, h=4, on_text="AN", off_text="AUS"): + return { + "id": pid, + "type": "stat", + "title": title, + "description": ( + f"Heizraum-Knoten Eingang `{signal}` (GPIO Optokoppler, active-low). " + f"MQTT: heizraum/io/{signal}" if signal != "kessel" else + "Kessel-Schaltausgang GPIO16 (Relais). 1=abgeschaltet, 0=freigegeben. " + "MQTT: heizraum/state/kessel" + ), + "datasource": {"type": "influxdb", "uid": DS_UID}, + "gridPos": {"h": h, "w": w, "x": x, "y": y}, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "1": {"text": on_text, "color": "green" if signal != "kessel" else "red", "index": 0}, + "0": {"text": off_text, "color": "red" if signal != "kessel" else "green", "index": 1}, + }, + }, + ], + "noValue": off_text, + "color": {"mode": "thresholds"}, + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "red" if signal != "kessel" else "green", "value": None}, + {"color": "green" if signal != "kessel" else "red", "value": 1}, + ], + }, + } + }, + "options": { + "reduceOptions": {"values": False, "calcs": ["lastNotNull"]}, + "colorMode": "background", + "graphMode": "none", + }, + "targets": [{ + "refId": "A", + "rawQuery": True, + "query": ( + f'SELECT last("value") FROM "io_state" ' + f"WHERE \"node\"='heizraum' AND \"signal\"='{signal}'" + ), + }], + } + + +d = curl("/api/dashboards/uid/heizung") +dash = d["dashboard"] + +remove_titles = { + ROW_IN, ROW_OUT, + "Pumpe 1", "Pumpe 2", "Pumpe 3", "Pumpe 4", "Brenner", + "Kessel (Ausgang)", +} +dash["panels"] = [ + p for p in dash["panels"] + if p.get("title") not in remove_titles and p.get("id") not in PANEL_IDS +] + +max_y = 0 +for p in dash["panels"]: + gp = p.get("gridPos", {}) + max_y = max(max_y, gp.get("y", 0) + gp.get("h", 0)) + +y = max_y + +# --- Eingänge --- +dash["panels"].append({ + "id": 910, + "type": "row", + "title": ROW_IN, + "gridPos": {"h": 1, "w": 24, "x": 0, "y": y}, + "collapsed": False, +}) +y += 1 + +inputs = [ + (911, "Pumpe 1", "pumpe1", 0), + (912, "Pumpe 2", "pumpe2", 4), + (913, "Pumpe 3", "pumpe3", 8), + (914, "Pumpe 4", "pumpe4", 12), + (915, "Brenner", "brenner", 16), +] +for pid, title, signal, x in inputs: + dash["panels"].append(io_stat(pid, title, signal, x, y)) +y += 4 + +# --- Ausgänge --- +dash["panels"].append({ + "id": 916, + "type": "row", + "title": ROW_OUT, + "gridPos": {"h": 1, "w": 24, "x": 0, "y": y}, + "collapsed": False, +}) +y += 1 + +dash["panels"].append( + io_stat(917, "Kessel (Ausgang)", "kessel", 0, y, on_text="ABGESCHALTET", off_text="FREIGEGEBEN") +) + +# Verlauf aller IO-Signale +dash["panels"].append({ + "id": 918, + "type": "timeseries", + "title": "Heizraum I/O Verlauf", + "description": "Eingänge (Pumpen/Brenner) und Kessel-Ausgang über Zeit.", + "datasource": {"type": "influxdb", "uid": DS_UID}, + "gridPos": {"h": 8, "w": 24, "x": 0, "y": y + 4}, + "fieldConfig": { + "defaults": { + "custom": {"drawStyle": "line", "lineWidth": 2, "spanNulls": 60000, "showPoints": "never"}, + "min": 0, + "max": 1, + } + }, + "options": { + "legend": {"displayMode": "table", "placement": "bottom", "calcs": ["last"]}, + }, + "targets": [{ + "refId": "A", + "rawQuery": True, + "query": ( + 'SELECT mean("value") FROM "io_state" ' + "WHERE \"node\"='heizraum' AND $timeFilter " + "GROUP BY time($__interval), \"signal\" fill(null)" + ), + }], +}) + +dash["version"] = dash.get("version", 0) + 1 +resp = curl("/api/dashboards/db", "POST", { + "dashboard": dash, + "folderId": 0, + "overwrite": True, + "message": "Heizraum I/O Eingänge und Ausgänge", +}) +print(json.dumps(resp, indent=2)[:600])