- Server node: sensor.keller_server_power is dead (UPS retired) -> Wave PM Mini server outlet (kg_vorratskeller_wave_pm_mini_server_steckdose) - Garden node: sensor.eg_garten_teichpumpe_power_2 is dead -> sensor.shelly_teichpumpe_power_0 (live, peaks ~77 W) - Home node/series: floor sums only cover submetered circuits (702 W shown vs ~1.1 kW real); Home is now the meter balance PV + Grid + Battery, with a new grey 'Rest' node for the non-submetered remainder - Flow lines: Heat Pump is metered inside Keller (was drawn from EG); Garden is on floor Aussenbereich, in no floor sum (was drawn from OG) - Drop redundant batteryIn target (derived from the signed battery sensor); update stale comments from the retired *_absolute sensors - History: max by (entity) guards against duplicate-scrape residue that doubled the summed Home series; sign hints in Battery/Grid legends; grid query pinned to entity id instead of friendly_name Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
13 KiB
JSON
87 lines
13 KiB
JSON
{
|
||
"annotations": {"list": []},
|
||
"description": "Energy flow visualization showing power sources and consumption by floor",
|
||
"editable": true,
|
||
"fiscalYearStartMonth": 0,
|
||
"graphTooltip": 1,
|
||
"id": null,
|
||
"links": [],
|
||
"liveNow": true,
|
||
"panels": [
|
||
{
|
||
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||
"description": "Live-Energiefluss. Vorzeichen: Batterie + = Entladen / − = Laden (open3e), Netz + = Bezug / − = Einspeisung. Home = PV + Netz + Batterie (Bilanz am Zähler). Die Etagen-Summen (powercalc) decken nur submeterte Kreise ab — 'Rest' ist die Differenz zur Bilanz. Server & Wärmepumpe sind in der Keller-Summe enthalten; die Teichpumpe (Floor Außenbereich) ist in keiner Etage.",
|
||
"gridPos": {"h": 18, "w": 24, "x": 0, "y": 0},
|
||
"id": 1,
|
||
"options": {
|
||
"getOption": "const data = context.panel.data;\n// null when the series is missing (dead sensor / no data) -> rendered as 'n/a', not a silent 0\nconst getVal = (refId) => {\n const s = data.series.find(s => s.refId === refId);\n const v = s?.fields[1]?.values[0];\n return (v === undefined || v === null) ? null : v;\n};\nconst num = (v) => v ?? 0;\n\n// Get all values\nconst solar = num(getVal('solar'));\nconst grid = num(getVal('grid'));\nconst battery = num(getVal('batteryOut'));\nconst batterySoc = num(getVal('soc'));\nconst keller = num(getVal('keller'));\nconst eg = num(getVal('eg'));\nconst og = num(getVal('og'));\nconst dg = num(getVal('dg'));\nconst server = getVal('server');\nconst heatpump = getVal('heatpump');\nconst garden = getVal('garden');\n\n// Derived values - vitocharge_vx3_vx3_battery_power is SIGNED: + = discharge, - = charge (open3e);\n// smart meter grid power is SIGNED: + = import, - = export\nconst solarPower = Math.max(0, solar);\nconst gridImport = Math.max(0, grid);\nconst gridExport = Math.max(0, -grid);\nconst batteryDischarge = Math.max(0, battery);\nconst batteryCharge = Math.max(0, -battery);\n// Total household consumption from the meter balance; the floor sums only cover submetered circuits\nconst homeTotal = Math.max(0, solar + grid + battery);\nconst floorsPower = keller + eg + og + dg;\nconst rest = Math.max(0, homeTotal - floorsPower - num(garden));\n\nconst fmt = (w) => Math.abs(w) >= 1000 ? (w/1000).toFixed(1) + ' kW' : Math.round(w) + ' W';\n\n// Chart dimensions\nconst w = context.panel.chart.getWidth();\nconst h = context.panel.chart.getHeight();\n\n// Battery display: show discharge or charge with direction indicator\nconst batteryDisplay = batteryDischarge > 0 ? fmt(batteryDischarge) + ' ↑' : (batteryCharge > 0 ? fmt(batteryCharge) + ' ↓' : '0 W');\nconst gridDisplay = gridImport > 0 ? fmt(gridImport) + ' →' : (gridExport > 0 ? fmt(gridExport) + ' ←' : '0 W');\n\n// Node definitions with percentage positions\nconst nodes = [\n // Sources (left side)\n { name: 'Solar', value: solarPower, x: w * 0.1, y: h * 0.15, color: '#F59E0B', icon: '☀️' },\n { name: 'Grid', value: gridImport > 0 ? gridImport : -gridExport, x: w * 0.1, y: h * 0.5, color: '#3B82F6', icon: '⚡', displayText: gridDisplay },\n { name: 'Battery', value: batteryDischarge > 0 ? batteryDischarge : -batteryCharge, x: w * 0.1, y: h * 0.85, color: '#10B981', icon: '🔋', extra: Math.round(batterySoc) + '%', displayText: batteryDisplay },\n \n // Home (center)\n { name: 'Home', value: homeTotal, x: w * 0.4, y: h * 0.5, color: '#F97316', icon: '🏠', size: 70 },\n \n // Floors (right side - top)\n { name: 'Keller', value: keller, x: w * 0.7, y: h * 0.1, color: '#1F60C4', icon: 'KG' },\n { name: 'EG', value: eg, x: w * 0.7, y: h * 0.3, color: '#37872D', icon: 'EG' },\n { name: 'OG', value: og, x: w * 0.7, y: h * 0.5, color: '#E0B400', icon: 'OG' },\n { name: 'DG', value: dg, x: w * 0.7, y: h * 0.7, color: '#C4162A', icon: 'DG' },\n { name: 'Rest', value: rest, x: w * 0.7, y: h * 0.9, color: '#9CA3AF', icon: '…' },\n \n // Large consumers (right side - bottom)\n { name: 'Server', value: num(server), x: w * 0.9, y: h * 0.2, color: '#8F3BB8', icon: '🖥️', displayText: server === null ? 'n/a' : undefined },\n { name: 'Heat Pump', value: num(heatpump), x: w * 0.9, y: h * 0.45, color: '#5794F2', icon: '🌡️', displayText: heatpump === null ? 'n/a' : undefined },\n { name: 'Garden', value: num(garden), x: w * 0.9, y: h * 0.7, color: '#96D98D', icon: '🌳', displayText: garden === null ? 'n/a' : undefined }\n];\n\n// Build graphic elements\nconst elements = [];\n\n// Helper to draw flow line\nconst drawLine = (x1, y1, x2, y2, value, color) => {\n if (value <= 0) return;\n const width = Math.max(2, Math.min(value / 300, 15));\n elements.push({\n type: 'line',\n shape: { x1, y1, x2, y2 },\n style: { stroke: color, lineWidth: width, opacity: 0.6 }\n });\n};\n\n// Draw flow lines from sources to home\nif (solarPower > 0) drawLine(w*0.1+30, h*0.15, w*0.4-35, h*0.5, solarPower, '#F59E0B');\nif (gridImport > 0) drawLine(w*0.1+30, h*0.5, w*0.4-35, h*0.5, gridImport, '#3B82F6');\nif (gridExport > 0) drawLine(w*0.4-35, h*0.5, w*0.1+30, h*0.5, gridExport, '#22C55E');\nif (batteryDischarge > 0) drawLine(w*0.1+30, h*0.85, w*0.4-35, h*0.5, batteryDischarge, '#10B981');\nif (batteryCharge > 0) drawLine(w*0.4-35, h*0.5, w*0.1+30, h*0.85, batteryCharge, '#60A5FA');\n\n// Draw flow lines from home to floors\nif (keller > 0) drawLine(w*0.4+35, h*0.5, w*0.7-25, h*0.1, keller, '#1F60C4');\nif (eg > 0) drawLine(w*0.4+35, h*0.5, w*0.7-25, h*0.3, eg, '#37872D');\nif (og > 0) drawLine(w*0.4+35, h*0.5, w*0.7-25, h*0.5, og, '#E0B400');\nif (dg > 0) drawLine(w*0.4+35, h*0.5, w*0.7-25, h*0.7, dg, '#C4162A');\nif (rest > 0) drawLine(w*0.4+35, h*0.5, w*0.7-25, h*0.9, rest, '#9CA3AF');\n\n// Sub-consumers: Server & Heat Pump are metered inside Keller; Garden (floor Außenbereich) is in no floor sum -> hangs off Home\nif (num(server) > 0) drawLine(w*0.7+25, h*0.1, w*0.9-25, h*0.2, num(server), '#8F3BB8');\nif (num(heatpump) > 0) drawLine(w*0.7+25, h*0.1, w*0.9-25, h*0.45, num(heatpump), '#5794F2');\nif (num(garden) > 0) drawLine(w*0.4+35, h*0.5, w*0.9-25, h*0.7, num(garden), '#96D98D');\n\n// Draw nodes\nnodes.forEach(node => {\n const size = node.size || 50;\n // Circle\n elements.push({\n type: 'circle',\n shape: { cx: node.x, cy: node.y, r: size/2 },\n style: { fill: '#1a1a2e', stroke: node.color, lineWidth: 4 }\n });\n // Icon\n elements.push({\n type: 'text',\n style: { text: node.icon, x: node.x, y: node.y - 8, fontSize: 18, textAlign: 'center', textVerticalAlign: 'middle' }\n });\n // Value\n let valueText = node.displayText || fmt(Math.abs(node.value));\n if (node.extra) valueText += '\\n' + node.extra;\n elements.push({\n type: 'text',\n style: { text: valueText, x: node.x, y: node.y + 14, fontSize: 11, fill: '#fff', textAlign: 'center', textVerticalAlign: 'middle' }\n });\n // Label\n elements.push({\n type: 'text',\n style: { text: node.name, x: node.x, y: node.y + size/2 + 12, fontSize: 11, fill: '#9CA3AF', textAlign: 'center' }\n });\n});\n\nreturn { backgroundColor: 'transparent', graphic: { elements } };"
|
||
},
|
||
"targets": [
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.vitocharge_vx3_vx3_pv_power\"}", "instant": true, "refId": "solar"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.smart_meter_sum_active_instantaneous_power\"}", "instant": true, "refId": "grid"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.vitocharge_vx3_vx3_battery_power\"}", "instant": true, "refId": "batteryOut"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_battery_percent{entity=\"sensor.vitocharge_vx3_vx3_battery_soc\"}", "instant": true, "refId": "soc"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.keller_power\"}", "instant": true, "refId": "keller"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.erdgeschoss_power\"}", "instant": true, "refId": "eg"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.obergeschoss_power\"}", "instant": true, "refId": "og"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.dachgeschoss_power\"}", "instant": true, "refId": "dg"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.kg_vorratskeller_wave_pm_mini_server_steckdose_electric_consumption_w\"}", "instant": true, "refId": "server"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.stromverbrauch_heizungsanlage\"}", "instant": true, "refId": "heatpump"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "homeassistant_sensor_power_w{entity=\"sensor.shelly_teichpumpe_power_0\"}", "instant": true, "refId": "garden"}
|
||
],
|
||
"title": "Power Flow",
|
||
"type": "volkovlabs-echarts-panel"
|
||
},
|
||
{
|
||
"collapsed": false,
|
||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 18},
|
||
"id": 100,
|
||
"panels": [],
|
||
"title": "Power History",
|
||
"type": "row"
|
||
},
|
||
{
|
||
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||
"description": "Home = PV + Netz + Batterie (bilanzierte Gesamtlast, alle Werte vorzeichenbehaftet). max by (entity) dedupliziert Alt-Serien aus dem Doppel-Scrape (behoben 2026-07-16).",
|
||
"fieldConfig": {
|
||
"defaults": {
|
||
"color": {"mode": "palette-classic"},
|
||
"custom": {"axisCenteredZero": true, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "opacity", "hideFrom": {"legend": false, "tooltip": false, "viz": false}, "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "scaleDistribution": {"type": "linear"}, "showPoints": "never", "spanNulls": false, "stacking": {"group": "A", "mode": "none"}, "thresholdsStyle": {"mode": "off"}},
|
||
"mappings": [],
|
||
"thresholds": {"mode": "absolute", "steps": [{"color": "green", "value": null}]},
|
||
"unit": "watt"
|
||
},
|
||
"overrides": [
|
||
{"matcher": {"id": "byName", "options": "Solar"}, "properties": [{"id": "color", "value": {"fixedColor": "#F59E0B", "mode": "fixed"}}]},
|
||
{"matcher": {"id": "byName", "options": "Battery (+ Entladen / − Laden)"}, "properties": [{"id": "color", "value": {"fixedColor": "#10B981", "mode": "fixed"}}]},
|
||
{"matcher": {"id": "byName", "options": "Grid (+ Bezug / − Einspeisung)"}, "properties": [{"id": "color", "value": {"fixedColor": "#3B82F6", "mode": "fixed"}}]},
|
||
{"matcher": {"id": "byName", "options": "Home"}, "properties": [{"id": "color", "value": {"fixedColor": "#F97316", "mode": "fixed"}}]}
|
||
]
|
||
},
|
||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 19},
|
||
"id": 40,
|
||
"options": {"legend": {"calcs": ["mean", "max", "lastNotNull"], "displayMode": "table", "placement": "right", "showLegend": true}, "tooltip": {"mode": "multi", "sort": "desc"}},
|
||
"targets": [
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "max by (entity) (homeassistant_sensor_power_w{entity=\"sensor.vitocharge_vx3_vx3_pv_power\"})", "legendFormat": "Solar", "refId": "A"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "max by (entity) (homeassistant_sensor_power_w{entity=\"sensor.vitocharge_vx3_vx3_battery_power\"})", "legendFormat": "Battery (+ Entladen / − Laden)", "refId": "B"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "max by (entity) (homeassistant_sensor_power_w{entity=\"sensor.smart_meter_sum_active_instantaneous_power\"})", "legendFormat": "Grid (+ Bezug / − Einspeisung)", "refId": "C"},
|
||
{"datasource": {"type": "prometheus", "uid": "prometheus"}, "expr": "sum(max by (entity) (homeassistant_sensor_power_w{entity=~\"sensor.vitocharge_vx3_vx3_pv_power|sensor.vitocharge_vx3_vx3_battery_power|sensor.smart_meter_sum_active_instantaneous_power\"}))", "legendFormat": "Home", "refId": "D"}
|
||
],
|
||
"title": "Power Sources & Consumption",
|
||
"type": "timeseries"
|
||
}
|
||
],
|
||
"refresh": "10s",
|
||
"schemaVersion": 38,
|
||
"style": "dark",
|
||
"tags": ["home", "energy", "power", "solar", "flow"],
|
||
"templating": {"list": []},
|
||
"time": {"from": "now-6h", "to": "now"},
|
||
"timepicker": {},
|
||
"timezone": "",
|
||
"title": "Power Flow",
|
||
"uid": "power-flow",
|
||
"version": 10,
|
||
"weekStart": ""
|
||
}
|