From 156cb92e26de4ef3f38348417b56978ebfce5998 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 16 Mar 2026 16:31:06 +0700 Subject: [PATCH] Grafana-Tool: Temperaturen, Energie, Heizung live aus InfluxDB --- homelab-ai-bot/tools/grafana.py | 195 ++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 7 deletions(-) diff --git a/homelab-ai-bot/tools/grafana.py b/homelab-ai-bot/tools/grafana.py index 699cc9cf..c840af38 100644 --- a/homelab-ai-bot/tools/grafana.py +++ b/homelab-ai-bot/tools/grafana.py @@ -1,21 +1,80 @@ -"""Grafana Monitoring Dashboard Tool.""" +"""Grafana + InfluxDB Smart-Home Tool (CT 143, pve-mu-3).""" import requests +from urllib.parse import quote + +GRAFANA_URL = "http://100.66.78.56:3000" +GRAFANA_USER = "admin" +GRAFANA_PASS = "astral66" +INFLUX_URL = "http://100.66.78.56:8086" +INFLUX_DB = "iobroker" + +SENSOR_MAP = { + "aussen": ("mqtt.0.Holzvergaser_Sensoren_6.Aussenfühler.temperature", "Aussen"), + "wohnstube": ("mqtt.0.Wohnstube_Temperatur_1.Wohnstube.Wohnstube_Temperatur", "Wohnstube"), + "kueche": ("mqtt.0.ESP_Kücheneu_2.Sensor_Küche.Küche", "Kueche"), + "garage": ("mqtt.0.ESP_Easy_3.Garage_neu.Garage", "Garage"), + "puffer_oben": ("mqtt.0.ESP_Easy_Display_4.oben.temperature", "Puffer oben"), + "puffer_unten": ("mqtt.0.ESP_Easy_Display_4.Unten.temperature", "Puffer unten"), + "garten": ("mqtt.0.ESP_Easy_schwimmbad_neu.Lufttemperatur.Lufttemperatur_Garten", "Garten/Luft"), + "schwimmbad": ("mqtt.0.ESP_Easy_schwimmbad_neu.Schwimmbad.Wassertemperatur_", "Schwimmbad"), + "oelkessel_vl": ("mqtt.0.Oelkessel.Oelkessel_VL.Vorlauf", "Oelkessel Vorlauf"), + "brunnen_vl": ("mqtt.0.Brunnenwasser.Oelkessel_VL.Vorlauf", "Brunnenwasser Vorlauf"), + "holzvergaser_1": ("mqtt.0.Holzvergaser_Sensoren_6.temp1.temperature1", "Holzvergaser Sensor 1"), + "holzvergaser_2": ("mqtt.0.Holzvergaser_Sensoren_6.temp2.temperature2", "Holzvergaser Sensor 2"), +} + +ENERGIE_MEASUREMENTS = { + "pv_leistung": ("mqtt.1.openWB.pv.W", "PV Leistung (W)"), + "pv_tagesertrag": ("mqtt.1.openWB.pv.DailyYieldKwh", "PV Tagesertrag (kWh)"), + "netz_bezug": ("mqtt.1.openWB.evu.W", "Netz Bezug/Einspeisung (W)"), + "hausverbrauch": ("mqtt.1.openWB.global.WHouseConsumption", "Hausverbrauch (W)"), + "batterie_soc": ("mqtt.1.openWB.housebattery.%Soc", "Batterie SoC (%)"), + "ueberschuss": ("mqtt.1.openWB.SmartHome.Status.uberschuss", "Ueberschuss (W)"), +} TOOLS = [ { "type": "function", "function": { "name": "get_grafana_status", - "description": "Grafana Monitoring-Status: Health, Dashboards, Datasources, Alerts. Nutze bei 'Grafana', 'Monitoring Dashboard', 'Alerts'.", + "description": "Grafana Monitoring-Status: Health, Dashboards, Datasources, Alerts.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, + { + "type": "function", + "function": { + "name": "get_temperaturen", + "description": "Aktuelle Temperaturen aus dem Haus in Muldenstein: Raeume, Aussen, Puffer, Heizung. Nutze bei Fragen zu Temperatur, Heizung, Puffer, Raumklima, 'wie warm ist es'.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, + { + "type": "function", + "function": { + "name": "get_energie", + "description": "Aktuelle Energiedaten: PV-Leistung, Batterie, Hausverbrauch, Netz. Nutze bei Fragen zu Solar, PV, Strom, Energie, Batterie, Wallbox, Einspeisung.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, + { + "type": "function", + "function": { + "name": "get_heizung", + "description": "Heizungsstatus: Brenner, Puffertemperaturen, Vorlauf, Holzvergaser. Nutze bei Fragen zu Heizung, Brenner, Puffer, Holzvergaser, Oelkessel.", "parameters": {"type": "object", "properties": {}, "required": []}, }, }, ] -GRAFANA_URL = "http://100.66.78.56:3000" -GRAFANA_USER = "admin" -GRAFANA_PASS = "astral66" +SYSTEM_PROMPT_EXTRA = """Fuer Smart-Home-Daten (Temperaturen, Energie, Heizung) im Haus Muldenstein: +- get_temperaturen: alle Raumtemperaturen + Aussen +- get_energie: PV, Batterie, Netz, Hausverbrauch +- get_heizung: Brenner, Puffer, Vorlauf, Holzvergaser +- get_grafana_status: Dashboard-Uebersicht und Alerts +Grafana-URL: https://grafana.orbitalo.net/ +""" def _api(path): @@ -27,9 +86,44 @@ def _api(path): ) if r.ok: return r.json() - return None except Exception: - return None + pass + return None + + +def _influx_query(q): + try: + r = requests.get( + f"{INFLUX_URL}/query", + params={"db": INFLUX_DB, "q": q}, + timeout=10, + ) + if r.ok: + return r.json() + except Exception: + pass + return None + + +def _query_latest(measurements: dict) -> list: + """Query last() for a dict of {key: (measurement, label)}.""" + parts = [] + for key, (meas, label) in measurements.items(): + parts.append(f'last(value) AS "{label}" FROM "{meas}"') + q = "SELECT " + "; SELECT ".join(parts) + " WHERE time > now() - 2h" + result = _influx_query(q) + if not result: + return [] + lines = [] + for stmt in result.get("results", []): + for series in stmt.get("series", []): + name = series["columns"][1] if len(series["columns"]) > 1 else series.get("name", "?") + val = series["values"][0][1] if series.get("values") else None + ts = series["values"][0][0] if series.get("values") else "" + if val is not None: + ts_short = ts[11:16] if len(ts) > 16 else "" + lines.append((name, val, ts_short)) + return lines def handle_get_grafana_status(**kw): @@ -65,6 +159,93 @@ def handle_get_grafana_status(**kw): return "\n".join(lines) +def handle_get_temperaturen(**kw): + data = _query_latest(SENSOR_MAP) + if not data: + return "Keine Temperaturdaten verfuegbar (InfluxDB nicht erreichbar?)" + + raeume = [] + heizung = [] + for name, val, ts in data: + entry = f" {name}: {val:.1f} C ({ts})" + if any(k in name.lower() for k in ["puffer", "vorlauf", "holz", "brunnen", "oel"]): + heizung.append(entry) + else: + raeume.append(entry) + + lines = ["Temperaturen Muldenstein (live):"] + if raeume: + lines.append("\nRaeume & Aussen:") + lines.extend(raeume) + if heizung: + lines.append("\nHeizung & Puffer:") + lines.extend(heizung) + lines.append(f"\nGrafana: https://grafana.orbitalo.net/d/solar/raumtemperaturen") + return "\n".join(lines) + + +def handle_get_energie(**kw): + data = _query_latest(ENERGIE_MEASUREMENTS) + if not data: + return "Keine Energiedaten verfuegbar (InfluxDB nicht erreichbar?)" + + lines = ["Energie Muldenstein (live):\n"] + for name, val, ts in data: + if "%" in name or "kWh" in name: + lines.append(f" {name}: {val:.1f} ({ts})") + else: + lines.append(f" {name}: {val:.0f} ({ts})") + + lines.append(f"\nGrafana: https://grafana.orbitalo.net/d/solar/energie-uebersicht") + return "\n".join(lines) + + +def handle_get_heizung(**kw): + heiz_sensors = { + "puffer_oben": SENSOR_MAP["puffer_oben"], + "puffer_unten": SENSOR_MAP["puffer_unten"], + "oelkessel_vl": SENSOR_MAP["oelkessel_vl"], + "brunnen_vl": SENSOR_MAP["brunnen_vl"], + "holzvergaser_1": SENSOR_MAP["holzvergaser_1"], + "holzvergaser_2": SENSOR_MAP["holzvergaser_2"], + "aussen": SENSOR_MAP["aussen"], + } + data = _query_latest(heiz_sensors) + + brenner_q = 'SELECT last(value) FROM "brennerstatus" WHERE time > now() - 2h' + laufzeit_q = 'SELECT last(value) FROM "brenner_heute" WHERE time > now() - 2h' + starts_q = 'SELECT last(value) FROM "brennerstarts" WHERE time > now() - 24h' + + lines = ["Heizung Muldenstein (live):\n"] + + brenner = _influx_query(brenner_q) + if brenner and brenner.get("results", [{}])[0].get("series"): + val = brenner["results"][0]["series"][0]["values"][0][1] + status = "AN" if val else "AUS" + lines.append(f" Brenner: {status}") + + laufzeit = _influx_query(laufzeit_q) + if laufzeit and laufzeit.get("results", [{}])[0].get("series"): + val = laufzeit["results"][0]["series"][0]["values"][0][1] + lines.append(f" Brenner heute: {val:.0f} min") + + starts = _influx_query(starts_q) + if starts and starts.get("results", [{}])[0].get("series"): + val = starts["results"][0]["series"][0]["values"][0][1] + lines.append(f" Brennerstarts: {val:.0f}") + + if data: + lines.append("") + for name, val, ts in data: + lines.append(f" {name}: {val:.1f} C ({ts})") + + lines.append(f"\nGrafana: https://grafana.orbitalo.net/d/heizung/heizung-and-puffer") + return "\n".join(lines) + + HANDLERS = { "get_grafana_status": handle_get_grafana_status, + "get_temperaturen": handle_get_temperaturen, + "get_energie": handle_get_energie, + "get_heizung": handle_get_heizung, }