Hallo @noize,
ich habe mal meine KI Projekt von Claude drüber laufen lassen.
Anpassung des Node-RED Flows für Nulleinspeisung mit EZ1-M Wechselrichter
Ich habe den Flow für den neuen Benutzer angepasst, der ein ähnliches Setup verwendet, aber mit anderen Sensoren und einer höheren Grundlast arbeitet. Hier sind die wichtigsten Änderungen und eine Erklärung, wie der Flow funktioniert.
[
{
"id": "638a1d646d26639d",
"type": "tab",
"label": "Nulleinspeisung über EZ1-M (Angepasst für Shelly-Messung)",
"disabled": false,
"info": "Flow zur Optimierung der Leistungsregelung des EZ1-M Wechselrichters mit dem Ziel der Nulleinspeisung. Angepasste Version für Shelly-Strommessung und Noah 2000 System.",
"env": []
},
{
"id": "cdef74971de7b4be",
"type": "inject",
"z": "638a1d646d26639d",
"name": "Trigger Shelly Daten (alle 5s)",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "5",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 170,
"y": 140,
"wires": [
[
"052b0d588db39343",
"5db22a464cb16baf"
]
]
},
{
"id": "5db22a464cb16baf",
"type": "api-current-state",
"z": "638a1d646d26639d",
"name": "Tatsächliche WR-Leistung",
"server": "97162e72.b6a46",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"entity_id": "sensor.solar_gesamtleistung",
"state_type": "str",
"blockInputOverrides": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "topic",
"propertyType": "msg",
"value": "solar_gesamtleistung",
"valueType": "str"
}
],
"for": "",
"forType": "num",
"x": 530,
"y": 260,
"wires": [
[
"61c0854d5f7b155d"
]
]
},
{
"id": "54e48d72ade4787c",
"type": "server-state-changed",
"z": "638a1d646d26639d",
"name": "Überwache EZ1-M Änderungen",
"server": "97162e72.b6a46",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"number.solar_maximale_leistung"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"ifState": "",
"ifStateType": "str",
"outputOnlyOnStateChange": false,
"for": "",
"forType": "num",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": false,
"ignoreCurrentStateUnavailable": false,
"outputProperties": [],
"entityidfilter": "number.solar_maximale_leistung",
"entityidfiltertype": "exact",
"outputinitially": true,
"haltifstate": "",
"x": 170,
"y": 320,
"wires": [
[
"fa4fd9c7be954d74"
]
]
},
{
"id": "a39fdc29c1d5d232",
"type": "inject",
"z": "638a1d646d26639d",
"name": "Trigger Batterie & Solar (alle 60s)",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 180,
"y": 420,
"wires": [
[
"705a6b9e1df8a6b8",
"4900dfb56cfa4fd2"
]
]
},
{
"id": "f9d41ac384c69ab2",
"type": "inject",
"z": "638a1d646d26639d",
"name": "Prüfe EZ1-M Status regelmäßig (15min)",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "900",
"crontab": "",
"once": true,
"onceDelay": "5",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 200,
"y": 380,
"wires": [
[
"fa4fd9c7be954d74"
]
]
},
{
"id": "052b0d588db39343",
"type": "api-current-state",
"z": "638a1d646d26639d",
"name": "Shelly Strommessung",
"server": "97162e72.b6a46",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"entity_id": "sensor.shellypro3em_8813bfd98a04_total_active_power",
"state_type": "str",
"blockInputOverrides": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "topic",
"propertyType": "msg",
"value": "shelly_power",
"valueType": "str"
}
],
"for": "",
"forType": "num",
"x": 510,
"y": 140,
"wires": [
[
"61c0854d5f7b155d"
]
]
},
{
"id": "fa4fd9c7be954d74",
"type": "api-current-state",
"z": "638a1d646d26639d",
"name": "EZ1-M Maximale Leistung",
"server": "97162e72.b6a46",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"entity_id": "number.solar_maximale_leistung",
"state_type": "str",
"blockInputOverrides": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "topic",
"propertyType": "msg",
"value": "solar_maximale_leistung",
"valueType": "str"
}
],
"for": "",
"forType": "num",
"x": 540,
"y": 320,
"wires": [
[
"61c0854d5f7b155d"
]
]
},
{
"id": "705a6b9e1df8a6b8",
"type": "api-current-state",
"z": "638a1d646d26639d",
"name": "Batterie SOC",
"server": "97162e72.b6a46",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"entity_id": "sensor.noah_2000_soc",
"state_type": "str",
"blockInputOverrides": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "topic",
"propertyType": "msg",
"value": "batterie_soc",
"valueType": "str"
}
],
"for": "",
"forType": "num",
"x": 490,
"y": 380,
"wires": [
[
"61c0854d5f7b155d"
]
]
},
{
"id": "4900dfb56cfa4fd2",
"type": "api-current-state",
"z": "638a1d646d26639d",
"name": "Solar Power",
"server": "97162e72.b6a46",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"entity_id": "sensor.noah_2000_solar_power",
"state_type": "str",
"blockInputOverrides": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "topic",
"propertyType": "msg",
"value": "solar_power",
"valueType": "str"
}
],
"for": "",
"forType": "num",
"x": 490,
"y": 440,
"wires": [
[
"61c0854d5f7b155d"
]
]
},
{
"id": "61c0854d5f7b155d",
"type": "function",
"z": "638a1d646d26639d",
"name": "Datenspeicherung und Triggerkontrolle",
"func": "// Initialisierung des Flow-Kontexts, falls noch nicht vorhanden\nconst storage = flow.get('sensorData') || {\n shelly_power: null,\n solar_maximale_leistung: null,\n solar_gesamtleistung: null,\n batterie_soc: null,\n solar_power: null,\n lastCalculation: 0,\n updateRequested: false,\n lastMorningCheck: 0,\n lastLoadCheck: 0\n};\n\n// Speichern des aktuellen Sensorwerts im Flow-Kontext\nconst topic = msg.topic;\nconst value = msg.payload;\n\n// Aktualisieren des entsprechenden Sensorwerts\nstorage[topic] = value;\n\n// Prüfen auf Vollständigkeit der Daten\nconst allDataAvailable = (\n storage.shelly_power !== null &&\n storage.solar_maximale_leistung !== null &&\n storage.solar_gesamtleistung !== null &&\n storage.batterie_soc !== null &&\n storage.solar_power !== null\n);\n\n// Aktuelle Zeit in Millisekunden\nconst now = Date.now();\n\n// Systemkonstanten für bessere Wartbarkeit\nconst CONSTANTS = {\n // Minimale Aktualisierungszeiten (in ms)\n UPDATE_INTERVALS: {\n shelly_power: 5000, // 5 Sekunden für Stromverbrauch/Einspeisung\n solar_gesamtleistung: 5000, // 5 Sekunden für tatsächliche Leistung\n solar_maximale_leistung: 0, // Sofortige Berechnung bei Änderung\n batterie_soc: 30000, // 30 Sekunden für SOC (erhöht für Stabilität)\n solar_power: 30000 // 30 Sekunden für Solar Power (erhöht)\n },\n // Allgemeines Mindestintervall zwischen Berechnungen (in ms)\n GENERAL_MIN_INTERVAL: 5000, // 5 Sekunden als Minimum zwischen Updates\n \n // Lastspitzenerkennung\n HIGH_LOAD_THRESHOLD: 1000, // Schwellwert für sehr hohe Last (W)\n MEDIUM_LOAD_THRESHOLD: 200, // Schwellwert für mittlere Last (W)\n \n // Zeitintervalle\n MORNING_CHECK_INTERVAL: 300000, // 5 Minuten zwischen Morgenprüfungen\n LOAD_CHECK_INTERVAL: 4000 // 4 Sekunden zwischen Lastspitzenprüfungen\n};\n\n// Shelly-Wert verarbeiten (kann positiv oder negativ sein)\nconst shellyPower = parseFloat(storage.shelly_power) || 0;\n\n// Positive Werte bedeuten Netzbezug (Verbrauch)\n// Negative Werte bedeuten Einspeisung\nconst netzbezug = shellyPower > 0 ? shellyPower : 0;\nconst einspeisung = shellyPower < 0 ? Math.abs(shellyPower) : 0;\n\n// Spezielle Zeitbedingungen prüfen\nconst currentHour = new Date().getHours();\nconst isEarlyMorning = currentHour >= 5 && currentHour < 10;\nconst lastMorningCheckElapsed = now - storage.lastMorningCheck;\n\n// Lastspitzenerkennung mit definierten Schwellwerten\nlet isHighLoadSituation = false;\nconst maxLeistung = parseFloat(storage.solar_maximale_leistung) || 0;\nconst aktuelleLeistung = parseFloat(storage.solar_gesamtleistung) || 0;\n\n// Verbesserte Lastspitzenerkennung\nif (netzbezug > CONSTANTS.HIGH_LOAD_THRESHOLD || \n (netzbezug > CONSTANTS.MEDIUM_LOAD_THRESHOLD && aktuelleLeistung > maxLeistung * 0.9)) {\n isHighLoadSituation = true;\n}\n\n// Bestimmen, ob eine Neuberechnung erfolgen soll\nlet shouldTriggerCalculation = false;\n\n// Prüfen, ob genügend Zeit seit der letzten Berechnung vergangen ist\nconst timePassedSinceLastCalc = now - storage.lastCalculation;\n\n// 1. Morgenprüfung - bei Sonnenaufgang und inaktivem WR\nif (isEarlyMorning && \n lastMorningCheckElapsed > CONSTANTS.MORNING_CHECK_INTERVAL && \n parseFloat(storage.solar_maximale_leistung) === 0 && \n parseFloat(storage.batterie_soc) > 20 && \n parseFloat(storage.solar_power) > 50) {\n \n shouldTriggerCalculation = true;\n storage.lastMorningCheck = now;\n}\n// 2. Lastspitzenprüfung - häufigere Berechnung bei hoher Last\nelse if (isHighLoadSituation && \n timePassedSinceLastCalc >= CONSTANTS.LOAD_CHECK_INTERVAL &&\n !storage.updateRequested) {\n \n shouldTriggerCalculation = true;\n}\n// 3. Standardberechnung mit Priorisierung wichtiger Sensoren\nelse if (allDataAvailable && \n timePassedSinceLastCalc >= CONSTANTS.GENERAL_MIN_INTERVAL && \n !storage.updateRequested) {\n \n // Hauptsensoren haben Priorität (Verbrauch/Einspeisung, tatsächliche Leistung)\n if (topic === 'shelly_power' || topic === 'solar_gesamtleistung') {\n shouldTriggerCalculation = true;\n }\n // Andere Sensoren nur, wenn ihr spezifisches Intervall erreicht ist\n else if (CONSTANTS.UPDATE_INTERVALS[topic] === 0 || \n timePassedSinceLastCalc >= CONSTANTS.UPDATE_INTERVALS[topic]) {\n shouldTriggerCalculation = true;\n }\n}\n\n// Speichern des aktualisierten Zustands im Flow-Kontext\nflow.set('sensorData', storage);\n\n// Wenn Berechnung ausgelöst werden soll, alle Daten an die Berechnungsfunktion senden\nif (shouldTriggerCalculation) {\n // Update-Flag setzen, um parallele Berechnungen zu vermeiden\n storage.updateRequested = true;\n flow.set('sensorData', storage);\n \n // Zeitstempel der Berechnung aktualisieren\n storage.lastCalculation = now;\n \n // Erstellen der Nachricht mit allen Sensordaten für die Berechnungsfunktion\n return {\n payload: {\n netzbezug: netzbezug,\n einspeisung: einspeisung,\n shelly_power: storage.shelly_power,\n solar_maximale_leistung: storage.solar_maximale_leistung,\n solar_gesamtleistung: storage.solar_gesamtleistung,\n batterie_soc: storage.batterie_soc,\n solar_power: storage.solar_power,\n triggerSource: topic,\n triggerTime: now,\n isEarlyMorning: isEarlyMorning,\n isHighLoadSituation: isHighLoadSituation\n }\n };\n}\n\n// Keine Nachricht zurückgeben, wenn keine Berechnung ausgelöst werden soll\nreturn null;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 930,
"y": 140,
"wires": [
[
"02638474a170add7"
]
]
},
{
"id": "02638474a170add7",
"type": "function",
"z": "638a1d646d26639d",
"name": "Berechne neue EZ1-M Leistung",
"func": "// Systemkonstanten für bessere Wartbarkeit und Lesbarkeit\nconst CONSTANTS = {\n // Leistungsgrenzen\n MAX_POWER: 780, // Maximale Wechselrichterleistung in Watt\n MIN_POWER: 75, // Minimale Wechselrichterleistung in Watt\n \n // Betriebsbereiche - angepasst für höhere Grundlast\n GRUNDLAST_MIN: 75, // Untere Grenze für Grundlastbereich (W)\n GRUNDLAST_MAX: 325, // Obere Grenze für Grundlastbereich (W) - angepasst auf 325W\n \n // Schwellwerte\n HIGH_SOC: 98, // Schwellwert für hohen SOC (%)\n MIN_GRID_EXPORT: 15, // Minimale Einspeisung für Leistungsreduzierung (W)\n LOW_GRID_IMPORT: 20, // Schwellwert für niedrigen Netzbezug (W)\n MEDIUM_GRID_IMPORT: 50, // Schwellwert für mittleren Netzbezug (W)\n HIGH_GRID_IMPORT: 100, // Schwellwert für hohen Netzbezug (W)\n VERY_HIGH_GRID_IMPORT: 1000, // Schwellwert für sehr hohen Netzbezug (W)\n FAILSAFE_POWER: 200, // Fallback-Leistung bei fehlenden Daten (W) - angepasst auf 200W\n \n // Zeitintervalle für Stabilitätssteuerung\n LOAD_PEAK_INTERVAL: 3000, // Intervall für Lastspitzen (ms)\n NORMAL_UPDATE_INTERVAL: 15000, // Normales Update-Intervall (ms)\n STABLE_UPDATE_INTERVAL: 30000, // Verlängertes Intervall bei Stabilität (ms) - auf 30s reduziert\n \n // Stabilitätskonfiguration\n STABILITY_THRESHOLD: 8, // Anzahl stabiler Updates für verlängerte Intervalle\n LARGE_CHANGE_THRESHOLD: 50 // Schwellwert für große Änderungen (W)\n};\n\n// Initialisierung des globalen Kontexts\nif (!global.solarControl) {\n global.solarControl = {\n lastPower: 0, // Zuletzt gesetzte Leistung\n lastUpdateTime: 0, // Zeitstempel der letzten Aktualisierung\n peakLoadStartTime: 0, // Zeitstempel des Beginns einer Lastspitze\n stabilityCounter: 0, // Zähler für stabile Betriebsbedingungen\n consecutiveStableUpdates: 0 // Zähler für aufeinanderfolgende stabile Updates\n };\n}\n\n// Hauptfunktion zur Berechnung der neuen Wechselrichterleistung\nfunction calculateNewSolarPower(msg) {\n // Sensorwerte extrahieren und parsen\n const energie = parseFloat(msg.payload.netzbezug) || 0; // Netzbezug (W)\n const stromerzeugung = parseFloat(msg.payload.einspeisung) || 0; // Netzeinspeisung (W)\n const soc = parseFloat(msg.payload.batterie_soc) || 0; // Batterieladezustand (%)\n const solarPower = parseFloat(msg.payload.solar_power) || 0; // Solarleistung (W)\n const aktuelleLeistung = parseFloat(msg.payload.solar_maximale_leistung) || 0; // Eingestellte WR-Leistung (W)\n const tatsaechlicheLeistung = parseFloat(msg.payload.solar_gesamtleistung) || 0; // Tatsächliche WR-Leistung (W)\n const isHighLoadSituation = msg.payload.isHighLoadSituation || false; // Lastspitze Flag\n \n // Aktuelle Zeit für Zeitstempelvergleiche\n const now = Date.now();\n \n // Erstinitialisierung bei erstem Lauf\n if (global.solarControl.lastPower === 0) {\n global.solarControl.lastPower = aktuelleLeistung;\n }\n \n // Leistungsdefizit berechnen (Differenz zwischen Soll und Ist)\n const leistungsDefizit = aktuelleLeistung - tatsaechlicheLeistung;\n \n // Strategieauswahl und Leistungsberechnung\n let neueLeistung = aktuelleLeistung;\n let anpassungsgrund = \"Keine Änderung\";\n \n /* ===== ENTSCHEIDUNGSLOGIK IN PRIORITÄTSREIHENFOLGE ===== */\n \n // 1. Failsafe: Prüfung auf fehlende Daten (Shelly, Noah, EZ1-M)\n if (energie === 0 && stromerzeugung === 0 && (solarPower === 0 || tatsaechlicheLeistung === 0)) {\n neueLeistung = CONSTANTS.FAILSAFE_POWER; // Failsafe: Sichere Grundlast (angepasst auf 200W)\n anpassungsgrund = `Failsafe aktiviert: Fehlende Daten von Sensoren, setze sichere Grundlast (${CONSTANTS.FAILSAFE_POWER}W)`;\n }\n // 2. Prüfung auf Hochlastsituation (sofortige Reaktion erforderlich)\n else if (isHighLoadSituation) {\n if (energie > CONSTANTS.VERY_HIGH_GRID_IMPORT) {\n // Bei extremem Verbrauch sofort auf Maximum\n neueLeistung = CONSTANTS.MAX_POWER;\n anpassungsgrund = `Extreme Lastspitze: ${energie}W Netzbezug, maximale WR-Leistung aktiviert`;\n } else {\n // Bei hohem Verbrauch und WR nahe Volllast, schrittweise erhöhen\n neueLeistung = Math.min(aktuelleLeistung + 50, CONSTANTS.MAX_POWER);\n anpassungsgrund = `Lastspitze: WR bei ${tatsaechlicheLeistung}W, erhöhe auf ${neueLeistung}W`;\n }\n global.solarControl.peakLoadStartTime = now;\n }\n // 3. Strategie bei vollem Akku (SOC ≥ 98%)\n else if (soc >= CONSTANTS.HIGH_SOC) {\n // Ziel: Batterieladen vermeiden und Eigenverbrauch maximieren\n neueLeistung = Math.min(solarPower + energie, CONSTANTS.MAX_POWER);\n anpassungsgrund = `SOC ≥ ${CONSTANTS.HIGH_SOC}%, setze Leistung auf Solar-Input + Netzbezug`;\n }\n // 4. Ausgleich bei signifikantem Leistungsdefizit\n else if (leistungsDefizit > 50 && energie > 25) {\n // WR liefert nicht die gewünschte Leistung, aber Netzbezug besteht\n neueLeistung = Math.min(aktuelleLeistung + energie + leistungsDefizit * 0.5, CONSTANTS.MAX_POWER);\n anpassungsgrund = `WR-Leistungsdefizit: ${Math.round(leistungsDefizit)}W, Erhöhung um ${Math.round(energie + leistungsDefizit * 0.5)}W`;\n }\n // 5. Standardstrategie für normale Betriebsbedingungen\n else {\n // Prüfen, ob wir uns im Grundlastbereich befinden (75-325W - angepasst)\n const isGrundlastBereich = (aktuelleLeistung >= CONSTANTS.GRUNDLAST_MIN && \n aktuelleLeistung <= CONSTANTS.GRUNDLAST_MAX);\n \n // Berücksichtigung der tatsächlichen Leistung\n // Bei signifikant niedrigerer Ist-Leistung stärkere Anpassung vornehmen\n const leistungsFaktor = tatsaechlicheLeistung < aktuelleLeistung * 0.9 ? 1.2 : 1.0;\n \n // 5.1 Sonderfall: Hoher Netzbezug außerhalb des Grundlastbereichs\n if (energie > CONSTANTS.MEDIUM_GRID_IMPORT && !isGrundlastBereich) {\n neueLeistung = aktuelleLeistung + Math.round(energie * 0.8 * leistungsFaktor);\n anpassungsgrund = `Hoher Netzbezug (${energie}W), schnelle Anpassung mit Faktor ${leistungsFaktor.toFixed(1)}`;\n }\n // 5.2 Geringer Netzbezug ohne Einspeisung - stabil, keine Änderung\n else if (energie > 0 && \n energie < CONSTANTS.LOW_GRID_IMPORT && \n stromerzeugung < CONSTANTS.MIN_GRID_EXPORT) {\n // Keine Änderung, stabiler Zustand\n anpassungsgrund = \"Stabile Bedingungen, keine Anpassung nötig\";\n }\n // 5.3 Verbesserte Feinabstimmung im Grundlastbereich (75-325W - angepasst)\n else if (isGrundlastBereich) {\n if (energie >= 25 || stromerzeugung >= CONSTANTS.MIN_GRID_EXPORT) {\n // Netzbezug: Leistung mit gestaffelten Schritten erhöhen\n if (energie >= 25) {\n let schrittGroesse;\n \n // Deutlich gestaffelte Anpassung je nach Netzbezug\n if (energie >= 100) {\n // Sehr hoher Netzbezug im Grundlastbereich: Große Schritte\n schrittGroesse = Math.min(50, Math.round(energie * 0.5));\n anpassungsgrund = `Großer Schritt (${schrittGroesse}W) wegen hohem Netzbezug von ${energie}W`;\n } else if (energie >= 50) {\n // Mittlerer Netzbezug: Mittlere Schritte\n schrittGroesse = Math.min(30, Math.round(energie * 0.4));\n anpassungsgrund = `Mittlerer Schritt (${schrittGroesse}W) bei Netzbezug von ${energie}W`;\n } else {\n // Geringer Netzbezug: Kleine Schritte wie bisher\n schrittGroesse = tatsaechlicheLeistung < aktuelleLeistung * 0.9 ? 10 : 5;\n anpassungsgrund = `Kleiner Schritt (${schrittGroesse}W) bei geringem Netzbezug`;\n }\n \n // Zusätzlicher Faktor für Fälle mit Leistungsdefizit\n if (leistungsDefizit < -15) {\n // WR liefert mehr als eingestellt - berücksichtigen bei Anpassung\n schrittGroesse = Math.round(schrittGroesse * 1.3);\n anpassungsgrund += ` (erhöht wegen Leistungsdefizit von ${Math.round(leistungsDefizit)}W)`;\n }\n \n neueLeistung = aktuelleLeistung + schrittGroesse;\n }\n // Einspeisung: Leistung reduzieren (unverändert)\n else if (stromerzeugung >= CONSTANTS.MIN_GRID_EXPORT) {\n neueLeistung = aktuelleLeistung - 5;\n anpassungsgrund = `5W-Schritt nach unten (Netzeinspeisung ${stromerzeugung}W)`;\n }\n }\n }\n // 5.4 Moderate Anpassung bei mittlerem Netzbezug\n else if (energie >= CONSTANTS.LOW_GRID_IMPORT && energie <= CONSTANTS.MEDIUM_GRID_IMPORT) {\n neueLeistung = aktuelleLeistung + Math.round(energie * 0.7 * leistungsFaktor);\n anpassungsgrund = `Moderater Netzbezug (${energie}W), angepasste Erhöhung um ${Math.round(energie * 0.7 * leistungsFaktor)}W`;\n }\n // 5.5 Direkte Anpassung bei höherem Netzbezug\n else if (energie > CONSTANTS.MEDIUM_GRID_IMPORT) {\n neueLeistung = aktuelleLeistung + Math.round(energie * leistungsFaktor);\n anpassungsgrund = `Direkte Anpassung bei Netzbezug (${energie}W) um ${Math.round(energie * leistungsFaktor)}W`;\n }\n // 5.6 Anpassung bei höherer Grundleistung und moderatem Verbrauch\n else if (aktuelleLeistung > CONSTANTS.GRUNDLAST_MAX && energie >= 25) {\n neueLeistung = aktuelleLeistung + Math.round(energie * leistungsFaktor);\n anpassungsgrund = `Erhöhung bei ${aktuelleLeistung}W Grundleistung und ${energie}W Netzbezug`;\n }\n // 5.7 Reduzierung bei Netzeinspeisung\n else if (stromerzeugung >= CONSTANTS.MIN_GRID_EXPORT) {\n // Minimum 100W beibehalten\n const reduktion = Math.min(stromerzeugung, aktuelleLeistung - 100);\n neueLeistung = Math.max(aktuelleLeistung - reduktion, 100);\n anpassungsgrund = `Reduziere Leistung um ${Math.round(reduktion)}W wegen Netzeinspeisung (${stromerzeugung}W)`;\n }\n }\n\n // IMMER auf 5W-Schritte runden für konsistente Steuerung\n neueLeistung = Math.round(neueLeistung / 5) * 5;\n \n // Begrenzung auf erlaubten Betriebsbereich\n neueLeistung = Math.max(CONSTANTS.MIN_POWER, Math.min(CONSTANTS.MAX_POWER, neueLeistung));\n \n // Bestimmen, ob ein Update gesendet werden soll (verbesserte Stabilität)\n let sendUpdate = false;\n const lastUpdateTime = global.solarControl.lastUpdateTime || 0;\n const timeSinceLastUpdate = now - lastUpdateTime;\n \n if (neueLeistung !== aktuelleLeistung) {\n // 1. Sofortige Reaktion bei Lastspitzen oder großen Änderungen\n if (isHighLoadSituation || Math.abs(neueLeistung - aktuelleLeistung) > CONSTANTS.LARGE_CHANGE_THRESHOLD) {\n sendUpdate = true;\n global.solarControl.stabilityCounter = 0; // Stabilitätszähler zurücksetzen\n global.solarControl.consecutiveStableUpdates = 0;\n }\n // 2. Bei stabilen Bedingungen längere Intervalle zwischen Updates\n else if (global.solarControl.stabilityCounter >= CONSTANTS.STABILITY_THRESHOLD) {\n // Nach mehreren stabilen Intervallen seltener anpassen\n if (timeSinceLastUpdate >= CONSTANTS.STABLE_UPDATE_INTERVAL) {\n sendUpdate = true;\n global.solarControl.consecutiveStableUpdates++;\n // Stabilitätszähler nicht vollständig zurücksetzen bei kleinen Änderungen\n global.solarControl.stabilityCounter = Math.max(global.solarControl.stabilityCounter - 2, 0);\n }\n }\n // 3. Normale Aktualisierung nach Mindestwartezeit\n else if (timeSinceLastUpdate >= CONSTANTS.NORMAL_UPDATE_INTERVAL) {\n sendUpdate = true;\n global.solarControl.stabilityCounter++;\n global.solarControl.consecutiveStableUpdates = 0;\n }\n } else {\n // Bei \"keine Änderung\" Stabilitätszähler erhöhen\n global.solarControl.stabilityCounter++;\n }\n \n // Status aktualisieren, wenn Update gesendet wird\n if (sendUpdate) {\n global.solarControl.lastPower = neueLeistung;\n global.solarControl.lastUpdateTime = now;\n }\n \n // Nach der Berechnung das Update-Flag zurücksetzen\n const storage = flow.get('sensorData') || {};\n storage.updateRequested = false;\n flow.set('sensorData', storage);\n\n // Rückgabe des Ergebnisses mit Zusatzinformationen für Debugging\n return {\n payload: neueLeistung, // Berechnete Sollleistung für den WR\n entity_id: 'number.solar_maximale_leistung', // Ziel-Entity für HA\n sendUpdate: sendUpdate, // Flag für API-Aufruf\n debugInfo: { // Debug-Informationen\n energie,\n stromerzeugung,\n soc,\n solarPower,\n aktuelleLeistung,\n tatsaechlicheLeistung,\n leistungsDefizit,\n neueLeistung,\n anpassungsgrund,\n anpassungsmenge: neueLeistung - aktuelleLeistung,\n updateSent: sendUpdate,\n isHighLoadSituation,\n timeSinceLastUpdate,\n stabilityCounter: global.solarControl.stabilityCounter,\n consecutiveStableUpdates: global.solarControl.consecutiveStableUpdates,\n isGrundlastBereich: (aktuelleLeistung >= CONSTANTS.GRUNDLAST_MIN && \n aktuelleLeistung <= CONSTANTS.GRUNDLAST_MAX)\n }\n };\n}\n\n// Hauptfunktion aufrufen und Ergebnis zurückgeben\nreturn calculateNewSolarPower(msg);",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 910,
"y": 200,
"wires": [
[
"d31c244d73a5dd73",
"a2d8a7b2a494e125",
"0015b5d7cfce55f6"
]
]
},
{
"id": "d31c244d73a5dd73",
"type": "switch",
"z": "638a1d646d26639d",
"name": "Nur bei Änderung",
"property": "sendUpdate",
"propertyType": "msg",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 870,
"y": 260,
"wires": [
[
"41180399ac416d12"
]
]
},
{
"id": "41180399ac416d12",
"type": "api-call-service",
"z": "638a1d646d26639d",
"name": "Setze EZ1-M Leistung",
"server": "97162e72.b6a46",
"version": 7,
"debugenabled": false,
"action": "number.set_value",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [],
"labelId": [],
"data": "{ \"entity_id\": entity_id, \"value\": payload}",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "number",
"service": "set_value",
"x": 880,
"y": 320,
"wires": [
[
"3aa713db0fa0101c"
]
]
},
{
"id": "0015b5d7cfce55f6",
"type": "debug",
"z": "638a1d646d26639d",
"name": "Debug Calculation",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1270,
"y": 200,
"wires": []
},
{
"id": "3aa713db0fa0101c",
"type": "debug",
"z": "638a1d646d26639d",
"name": "Debug Response",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1270,
"y": 260,
"wires": []
},
{
"id": "a2d8a7b2a494e125",
"type": "debug",
"z": "638a1d646d26639d",
"name": "Debug Details",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "debugInfo",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1260,
"y": 140,
"wires": []
},
{
"id": "97162e72.b6a46",
"type": "server",
"name": "Home Assistant",
"addon": true,
"rejectUnauthorizedCerts": true,
"ha_boolean": "",
"connectionDelay": false,
"cacheJson": false,
"heartbeat": false,
"heartbeatInterval": "",
"statusSeparator": "",
"enableGlobalContextStore": false
}
]
Erklärung der Anpassungen
Ich habe den Flow an das neue Setup angepasst. Hier sind die wichtigsten Änderungen und was sie bedeuten:
1. Sensornamen angepasst
- Tibber-Sensoren wurden durch Shelly und Noah 2000 Sensoren ersetzt:
sensor.shellypro3em_8813bfd98a04_total_active_power
(für Stromverbrauch/Einspeisung)
sensor.noah_2000_soc
(Batterieladezustand)
sensor.noah_2000_solar_power
(Solarleistung)
2. Umgang mit Shelly-Messwerten
Der fundamentale Unterschied zwischen Tibber und Shelly ist, dass Tibber separate Sensoren für Verbrauch und Einspeisung hat, während Shelly einen einzigen Wert liefert, der positiv (bei Netzbezug) oder negativ (bei Einspeisung) sein kann.
Im ersten Funktionsknoten Datenspeicherung und Triggerkontrolle
habe ich daher folgende Umrechnung eingefügt:
// Shelly-Wert verarbeiten (kann positiv oder negativ sein)
const shellyPower = parseFloat(storage.shelly_power) || 0;
// Positive Werte bedeuten Netzbezug (Verbrauch)
// Negative Werte bedeuten Einspeisung
const netzbezug = shellyPower > 0 ? shellyPower : 0;
const einspeisung = shellyPower < 0 ? Math.abs(shellyPower) : 0;
Diese Werte werden dann an den zweiten Funktionsknoten weitergegeben, wo sie wie zuvor verwendet werden können.
3. Anpassung der Grundlast-Parameter
Da die Grundlast des neuen Nutzers höher ist (ca. 300W statt ursprünglich ca. 175W), habe ich die entsprechenden Parameter angepasst:
// Betriebsbereiche - angepasst für höhere Grundlast
GRUNDLAST_MIN: 75, // Untere Grenze für Grundlastbereich (W)
GRUNDLAST_MAX: 325, // Obere Grenze für Grundlastbereich (W) - angepasst auf 325W
Außerdem wurde die Failsafe-Leistung (Notfallwert) erhöht:
FAILSAFE_POWER: 200, // Fallback-Leistung bei fehlenden Daten (W) - angepasst auf 200W
4. Entfernung der Strompreis-Logik
Da der neue Nutzer keinen Tibber-Strompreis hat, wurden die entsprechenden Knoten und Logik entfernt:
- Der stündliche Trigger für Strompreise wurde entfernt
- Der Strompreis-Parameter wurde aus der Berechnungsfunktion entfernt
So funktioniert der Flow
Der Flow folgt diesem Ablauf:
- Datenerfassung: Alle 5 Sekunden werden die Werte vom Shelly-Leistungsmesser und dem tatsächlichen Wechselrichter-Output gelesen. Alle 60 Sekunden werden Batterie- und Solardaten aktualisiert.
- Datenspeicherung und Trigger-Logik: In der ersten Funktion werden die Daten gesammelt und es wird entschieden, wann eine neue Berechnung ausgelöst werden soll. Besonders wichtig ist hier die Umwandlung des Shelly-Werts in getrennte Werte für Netzbezug und -einspeisung.
- Leistungsberechnung: Die zweite Funktion berechnet, wie die Leistung des EZ1-M Wechselrichters angepasst werden sollte. Dafür werden verschiedene Szenarien nach Priorität durchlaufen:
- Failsafe bei fehlenden Daten
- Sofortige Reaktion bei Lastspitzen
- Spezialbehandlung bei vollem Akku
- Ausgleich bei Leistungsdefizit des Wechselrichters
- Standardstrategie mit feinen Anpassungen je nach Netzbezug und -einspeisung
- Stabilisierung: Um unnötige Schwankungen zu vermeiden, wird eine Aktualisierung nur gesendet, wenn bestimmte Bedingungen erfüllt sind. Bei stabilen Bedingungen werden Updates seltener gesendet.
- Aktualisierung: Wenn eine Änderung erforderlich ist, wird die neue Leistung an den EZ1-M Wechselrichter gesendet.
Tipps zur weiteren Optimierung
Hier sind einige Tipps, wie der neue Nutzer den Flow für seine Bedürfnisse optimieren kann:
- Grundlastparameter anpassen: Der Bereich GRUNDLAST_MIN und GRUNDLAST_MAX sollte an die tatsächliche Grundlast angepasst werden. Wenn die Grundlast bei 300W liegt, könnte ein Bereich von etwa 75-350W sinnvoll sein.
- Schwellwerte anpassen: Die Schwellwerte für niedrigen, mittleren und hohen Netzbezug können je nach individuellen Verbrauchsmustern angepasst werden.
- Debug aktivieren: Die Debug-Knoten sind standardmäßig deaktiviert. Für die Fehlersuche und Feinabstimmung empfiehlt es sich, diese zu aktivieren, um die Berechnungen nachvollziehen zu können.
- Anpassungsgeschwindigkeit: Die Parameter
NORMAL_UPDATE_INTERVAL
und STABLE_UPDATE_INTERVAL
steuern, wie oft Updates gesendet werden. Bei zu häufigen Anpassungen können diese Werte erhöht werden.
- Lastspitzenerkennung: Die Schwellwerte
HIGH_LOAD_THRESHOLD
und MEDIUM_LOAD_THRESHOLD
bestimmen, wann eine Lastspitze erkannt wird. Diese können je nach Haushaltsgröße angepasst werden.
- Statistik und Protokollierung: Um den Flow langfristig zu optimieren, empfiehlt es sich, die Werte und Entscheidungen in einer Datenbank oder Textdatei zu protokollieren. So können Muster erkannt und die Parameter gezielt angepasst werden.
Beste Grüße
aspiro