#!/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])