[
{
"id": "config_globals",
"type": "function",
"name": "CONFIG – Zentrale Einstellungen",
"func": "// ======================================================\n// ZENTRALE KONFIGURATION\n// ======================================================\nconst CONFIG = {\n\n // --------------------------\n // MQTT / Broker\n // --------------------------\n MQTT: {\n BROKER: \"127.0.0.1\",\n PORT: 1883\n },\n\n // --------------------------\n // Victron Topics\n // --------------------------\n TOPICS: {\n SOC: \"N/+/system/0/Dc/Battery/Soc\",\n AC_LOAD: \"N/+/system/0/Ac/Consumption/Total/Power\",\n GRID_POWER: \"N/+/system/0/Ac/Grid/Total/Power\",\n GRID_CONNECTED: \"N/+/system/0/Ac/ActiveIn/Connected\"\n },\n\n // --------------------------\n // Shelly Topics\n // --------------------------\n SHELLY: {\n PV_R1: \"shellies/shelly-2pm-gen3/relay/0/command\",\n PV_R2: \"shellies/shelly-2pm-gen3/relay/1/command\",\n CONSUMER: \"shellies/shelly-1-gen3/relay/0/command\"\n },\n\n // --------------------------\n // Batterie\n // --------------------------\n BATTERY: {\n SOC_MIN: 20,\n SOC_STOP_PV: 95\n },\n\n // --------------------------\n // Netz\n // --------------------------\n GRID: {\n IMPORT_LIMIT_W: 50,\n RECONNECT_SOC: 15\n },\n\n // --------------------------\n // Lastschwellen Haus\n // --------------------------\n LOAD: {\n LOW_W: 1000,\n MEDIUM_W: 3000,\n HIGH_W: 5000\n },\n\n // --------------------------\n // Verbraucherfreigaben\n // --------------------------\n CONSUMER: {\n GX_SOC: 90,\n SHELLY_SOC: 87\n },\n\n // --------------------------\n // Hysterese / Mindestzeit\n // --------------------------\n HYST: {\n SOC: 2,\n POWER: 200,\n MIN_SWITCH_SEC: 60\n }\n\n};\n\nflow.set(\"CONFIG\", CONFIG);\nreturn null;",
"x": 300,
"y": 80,
"wires": []
},
{
"id": "mqtt_soc",
"type": "mqtt in",
"name": "SOC",
"topic": "N/+/system/0/Dc/Battery/Soc",
"broker": "broker",
"x": 140,
"y": 160,
"wires": [["store_soc"]]
},
{
"id": "store_soc",
"type": "function",
"name": "Store SOC",
"func": "flow.set('soc', Number(msg.payload));\nreturn null;",
"x": 320,
"y": 160,
"wires": []
},
{
"id": "mqtt_load",
"type": "mqtt in",
"name": "AC Load",
"topic": "N/+/system/0/Ac/Consumption/Total/Power",
"broker": "broker",
"x": 140,
"y": 220,
"wires": [["store_load"]]
},
{
"id": "store_load",
"type": "function",
"name": "Store Load",
"func": "flow.set('load', Number(msg.payload));\nreturn null;",
"x": 320,
"y": 220,
"wires": []
},
{
"id": "mqtt_grid",
"type": "mqtt in",
"name": "Grid Power",
"topic": "N/+/system/0/Ac/Grid/Total/Power",
"broker": "broker",
"x": 140,
"y": 280,
"wires": [["store_grid"]]
},
{
"id": "store_grid",
"type": "function",
"name": "Store Grid",
"func": "flow.set('grid', Number(msg.payload));\nreturn null;",
"x": 320,
"y": 280,
"wires": []
},
{
"id": "mqtt_grid_state",
"type": "mqtt in",
"name": "Grid Connected",
"topic": "N/+/system/0/Ac/ActiveIn/Connected",
"broker": "broker",
"x": 160,
"y": 340,
"wires": [["store_grid_state"]]
},
{
"id": "store_grid_state",
"type": "function",
"name": "Store Grid State",
"func": "flow.set('grid_connected', msg.payload === 1);\nreturn null;",
"x": 360,
"y": 340,
"wires": []
},
{
"id": "tick_pv",
"type": "inject",
"name": "PV Logic Tick (10s)",
"repeat": "10",
"once": true,
"x": 160,
"y": 420,
"wires": [["pv_logic"]]
},
{
"id": "pv_logic",
"type": "function",
"name": "PV Leistungslogik mit Hysterese",
"func": "// Lese CONFIG und Messwerte\nconst C = flow.get('CONFIG');\nconst soc = flow.get('soc') || 0;\nconst load = flow.get('load') || 0;\nconst gridConnected = flow.get('grid_connected');\n\n// Letzte Zustände merken\nlet lastPV1 = flow.get('lastPV1') || 'off';\nlet lastPV2 = flow.get('lastPV2') || 'off';\nlet lastSwitchTime = flow.get('lastSwitchTime') || 0;\nconst now = Date.now();\n\n// --------------------------\n// OFFLINE: volle Freigabe\n// --------------------------\nif (!gridConnected) {\n flow.set('lastPV1','on');\n flow.set('lastPV2','on');\n flow.set('lastSwitchTime', now);\n return [{payload:'on'}, {payload:'on'}];\n}\n\n// --------------------------\n// Akku laden wenn nicht voll\n// --------------------------\nif (soc < C.BATTERY.SOC_STOP_PV) {\n flow.set('lastPV1','on');\n flow.set('lastPV2','on');\n flow.set('lastSwitchTime', now);\n return [{payload:'on'}, {payload:'on'}];\n}\n\n// --------------------------\n// Lastgestützte PV-Stufen bei fast vollem Akku\n// --------------------------\nlet pv1 = 'off';\nlet pv2 = 'off';\nif (load >= C.LOAD.HIGH_W) {\n pv1 = 'on'; pv2 = 'off';\n} else if (load >= C.LOAD.MEDIUM_W) {\n pv1 = 'off'; pv2 = 'on';\n} else {\n pv1 = 'off'; pv2 = 'off';\n}\n\n// --------------------------\n// Hysterese SOC & Last\n// --------------------------\nif (lastPV1 === 'on' && pv1 === 'off' && soc > C.BATTERY.SOC_STOP_PV - C.HYST.SOC) { pv1 = 'on'; }\nif (lastPV2 === 'on' && pv2 === 'off' && load > C.LOAD.MEDIUM_W - C.HYST.POWER) { pv2 = 'on'; }\n\n// --------------------------\n// Mindest-Schaltzeit\n// --------------------------\nif ((now - lastSwitchTime)/1000 < C.HYST.MIN_SWITCH_SEC) {\n pv1 = lastPV1;\n pv2 = lastPV2;\n} else {\n flow.set('lastPV1', pv1);\n flow.set('lastPV2', pv2);\n flow.set('lastSwitchTime', now);\n}\n\nreturn [{payload: pv1}, {payload: pv2}];",
"x": 360,
"y": 420,
"wires": [["shelly_pv_r1"], ["shelly_pv_r2"]]
},
{
"id": "shelly_pv_r1",
"type": "mqtt out",
"name": "Shelly 2PM – Relais 1",
"topic": "shellies/shelly-2pm-gen3/relay/0/command",
"broker": "broker",
"x": 640,
"y": 400,
"wires": []
},
{
"id": "shelly_pv_r2",
"type": "mqtt out",
"name": "Shelly 2PM – Relais 2",
"topic": "shellies/shelly-2pm-gen3/relay/1/command",
"broker": "broker",
"x": 640,
"y": 440,
"wires": []
},
{
"id": "tick_consumer",
"type": "inject",
"name": "Consumer Logic Tick (15s)",
"repeat": "15",
"once": true,
"x": 160,
"y": 520,
"wires": [["consumer_logic"]]
},
{
"id": "consumer_logic",
"type": "function",
"name": "Verbraucherfreigabe Logik",
"func": "const C = flow.get('CONFIG');\nconst soc = flow.get('soc') || 0;\nconst grid = flow.get('grid') || 0;\n\nconst c1 = (soc >= C.CONSUMER.GX_SOC && grid <= C.GRID.IMPORT_LIMIT_W) ? 1 : 0;\nconst c2 = (soc >= C.CONSUMER.SHELLY_SOC && grid <= C.GRID.IMPORT_LIMIT_W) ? 1 : 0;\n\nreturn [{ payload: c1 }, { payload: c2 }];",
"x": 380,
"y": 520,
"wires": [["gx_relay_2"], ["shelly_consumer"]]
},
{
"id": "gx_relay_2",
"type": "victron-output-relay",
"name": "GX Relais 2 – Verbraucher",
"relay": "2",
"x": 640,
"y": 500,
"wires": []
},
{
"id": "shelly_consumer",
"type": "mqtt out",
"name": "Shelly 1 – Verbraucher",
"topic": "shellies/shelly-1-gen3/relay/0/command",
"broker": "broker",
"x": 640,
"y": 540,
"wires": []
},
{
"id": "broker",
"type": "mqtt-broker",
"name": "Cerbo MQTT",
"broker": "127.0.0.1",
"port": "1883"
}
]