PV Wechselrichter Drosselung wenn Akku voll
Verfasst: 30. Jul 2026 18:12
Hallo zusammen,
Ich habe hier schon einige Zeit nichts mehr beigetragen.
Hier möchte ich nun mein aktuelles Projekt vorstellen:
"PV-Wechselrichter drosseln", wenn der Akku voll ist."
Ja, da haben auch "Klaus" & "Ingo" etwas mitgeholfen
Die Rule läuft hier seit einiger Zeit im Produktivsystem und tut, was sie soll.
Beste Grüße Peter.
Ich habe hier schon einige Zeit nichts mehr beigetragen.
Hier möchte ich nun mein aktuelles Projekt vorstellen:
"PV-Wechselrichter drosseln", wenn der Akku voll ist."
Ja, da haben auch "Klaus" & "Ingo" etwas mitgeholfen
Die Rule läuft hier seit einiger Zeit im Produktivsystem und tut, was sie soll.
Beste Grüße Peter.
Code: Alles auswählen
// =====================================================
// PV-Regelung Version 3.6
//
// Funktionen:
// - SOC Hysterese
// - gleitender Mittelwert Netzleistung
// - Lastspitzenfilter
// - variable Schrittweite
// - variable Totzone
// - Dämpfung
// - Zeitsperre
// - WR Online-Erkennung
// - Schutz gegen doppelte WR-Befehle
// - Manueller Freigabe-Schalter (PV_Regelung_Freigabe)
// - Freigabe erst wenn die Verschattung erstmalig gelaufen ist.
//
// Aktivierung:
// Akku >= 97 %
//
// Deaktivierung:
// Akku <= 90 %
//
// Netzleistung:
// > 0 W = Netzbezug
// < 0 W = Einspeisung
// -----------------------------------------------------
// Globale Variablen
// -----------------------------------------------------
var Number gLastSentLimit = null
var DateTime gLastCommandTime = null
var boolean gPvControlActive = false
// merkt sich, ob der WR zuletzt offline war,
// um beim Wiederkommen die Historie zu leeren
var boolean gInverterWasOffline = false
var java.util.List<Integer> gGridPowerHistory =
newArrayList()
rule "PV Begrenzung bei vollem Akku"
when
Item us3000c_soc changed or
Item em0_total_act_power changed or
Item OpenDTU1_inv1_reachable changed or
Item PV_Regelung_Freigabe changed or
Item Rolloautomatik_start_last changed
then
// -------------------------------------------------
// Prüfung auf gültige Werte
// -------------------------------------------------
if(
us3000c_soc.state == NULL ||
us3000c_soc.state == UNDEF ||
em0_total_act_power.state == NULL ||
em0_total_act_power.state == UNDEF ||
OpenDTU1_inv1_limit_absolute.state == NULL ||
OpenDTU1_inv1_limit_absolute.state == UNDEF ||
OpenDTU1_inv1_reachable.state == NULL ||
OpenDTU1_inv1_reachable.state == UNDEF ||
PV_Regelung_Freigabe.state == NULL ||
PV_Regelung_Freigabe.state == UNDEF ||
Rolloautomatik_start_last.state == NULL ||
Rolloautomatik_start_last.state == UNDEF
){
return;
}
val soc =
(us3000c_soc.state as Number).intValue
val rawGridPower =
(em0_total_act_power.state as Number).intValue
val itemLimit =
(OpenDTU1_inv1_limit_absolute.state as Number).intValue
val inverterReachable =
(OpenDTU1_inv1_reachable.state as Number).intValue
val timestamp =
now.toString("HH:mm")
// -------------------------------------------------
// Verschattung heute bereits aktiv gewesen?
// -------------------------------------------------
val verschattungHeute =
Rolloautomatik_start_last.state // Verschattung West
.toString()
.substring(0,10) ==
now.toString().substring(0,10)
// -------------------------------------------------
// WR offline
// -------------------------------------------------
if(inverterReachable == 0){
gInverterWasOffline = true
if(gPvControlActive){
gPvControlActive = false
logInfo(
"PV",
"Wechselrichter offline - PV-Regelung pausiert"
)
sendBroadcastNotification(
timestamp +
" | WR offline | PV-Regelung pausiert"
)
}
return;
}
// -------------------------------------------------
// WR war offline und ist jetzt wieder da
// -> Historie zurücksetzen, damit keine veralteten
// Messwerte den gleitenden Mittelwert verfälschen
// -------------------------------------------------
if(gInverterWasOffline){
gInverterWasOffline = false
gGridPowerHistory.clear()
logInfo(
"PV",
"Wechselrichter wieder erreichbar - Messwertverlauf zurückgesetzt"
)
sendBroadcastNotification(
timestamp +
" | WR wieder erreichbar"
)
}
// -------------------------------------------------
// Manuelle Deaktivierung
//
// Ab hier ist inverterReachable garantiert == 1,
// sendCommand(1500) erreicht also einen echten WR
// -------------------------------------------------
if(PV_Regelung_Freigabe.state != ON){
if(gPvControlActive){
gPvControlActive = false
if(itemLimit != 1500){
OpenDTU1_inv1_limit_absolute.sendCommand(1500)
gLastSentLimit = 1500
gLastCommandTime = now
}
gGridPowerHistory.clear()
logInfo(
"PV",
"PV-Regelung manuell deaktiviert"
)
sendBroadcastNotification(
timestamp +
" | PV-Regelung manuell deaktiviert"
)
}
return;
}
// -------------------------------------------------
// Messwert in Filter übernehmen
// -------------------------------------------------
gGridPowerHistory.add(rawGridPower)
if(gGridPowerHistory.size > 6){
gGridPowerHistory.remove(0)
}
// -------------------------------------------------
// Mittelwert berechnen
// -------------------------------------------------
var gridPowerSum = 0
gGridPowerHistory.forEach[value |
gridPowerSum = gridPowerSum + value
]
val gridPower =
(gridPowerSum / gGridPowerHistory.size)
// -------------------------------------------------
// Aktuelles WR Limit bestimmen (geglättet, für die
// eigentliche Regelung)
// -------------------------------------------------
var currentLimit = itemLimit
if(
gLastSentLimit !== null &&
Math::abs(
itemLimit -
gLastSentLimit.intValue
) < 30
){
currentLimit =
gLastSentLimit.intValue
}
val minPower = 200
val maxPower = 1500
// -------------------------------------------------
// PV-Regelung erst zulassen wenn die
// Verschattung heute bereits gelaufen ist
// -------------------------------------------------
if(!verschattungHeute){
if(gPvControlActive){
gPvControlActive = false
gGridPowerHistory.clear()
if(itemLimit != maxPower){
OpenDTU1_inv1_limit_absolute.sendCommand(maxPower)
gLastSentLimit = maxPower
gLastCommandTime = now
}
logInfo(
"PV",
"PV-Regelung gesperrt - Verschattung heute noch nicht aktiv"
)
}
return;
}
// -------------------------------------------------
// Akku <= 90 %
// Regelung deaktivieren
// -------------------------------------------------
if(soc <= 90){
if(gPvControlActive){
gPvControlActive = false
if(itemLimit != maxPower){
OpenDTU1_inv1_limit_absolute.sendCommand(maxPower)
gLastSentLimit = maxPower
gLastCommandTime = now
}
logInfo(
"PV",
"Akku {}% -> PV-Regelung AUS",
soc
)
sendBroadcastNotification(
timestamp +
" | Akku " +
soc +
"% | PV-Regelung deaktiviert | WR " +
maxPower +
" W"
)
}
return;
}
// -------------------------------------------------
// Akku >= 97 %
// Regelung aktivieren
//
// Historie wird bei jeder Neu-Aktivierung geleert,
// damit alte Werte aus der Pause nicht in den ersten
// Mittelwert einfließen
// -------------------------------------------------
if(soc >= 97 && !gPvControlActive){
gPvControlActive = true
gGridPowerHistory.clear()
gGridPowerHistory.add(rawGridPower)
logInfo(
"PV",
"Akku {}% -> PV-Regelung EIN",
soc
)
sendBroadcastNotification(
timestamp +
" | Akku " +
soc +
"% | PV-Regelung aktiviert"
)
}
// -------------------------------------------------
// Nur weiter wenn PV-Regelung aktiv
// -------------------------------------------------
if(!gPvControlActive){
return;
}
// -------------------------------------------------
// Zeitsperre
// -------------------------------------------------
if(
gLastCommandTime !== null &&
now.isBefore(
gLastCommandTime.plusMillis(10000)
)
){
return;
}
// -------------------------------------------------
// Variable Totzone
// -------------------------------------------------
var deadBand = 50
if(currentLimit < 500){
deadBand = 20
} else if(currentLimit < 1000){
deadBand = 35
}
if(
gridPower >= -deadBand &&
gridPower <= deadBand
){
return;
}
// -------------------------------------------------
// Variable Schrittweite
// -------------------------------------------------
var maxStep = 150
if(currentLimit < 500){
maxStep = 50
} else if(currentLimit < 1000){
maxStep = 100
}
// -------------------------------------------------
// Dämpfung
// -------------------------------------------------
var delta = 0
if(Math::abs(gridPower) < 150){
delta = gridPower
} else {
delta =
(gridPower * 0.6).intValue
}
// -------------------------------------------------
// Schrittweite begrenzen
// -------------------------------------------------
if(delta > maxStep){
delta = maxStep
}
if(delta < -maxStep){
delta = -maxStep
}
var newLimit =
currentLimit + delta
// -------------------------------------------------
// Begrenzung
// -------------------------------------------------
if(newLimit < minPower){
newLimit = minPower
}
if(newLimit > maxPower){
newLimit = maxPower
}
// -------------------------------------------------
// Auf 10 W runden (kaufmännisch)
// -------------------------------------------------
newLimit =
(((newLimit + 5) / 10) * 10)
// -------------------------------------------------
// Kleine Änderungen ignorieren
// -------------------------------------------------
if(
Math::abs(
newLimit - currentLimit
) < 20
){
return;
}
// -------------------------------------------------
// Doppeltes Senden verhindern
// -------------------------------------------------
if(
gLastSentLimit !== null &&
Math::abs(
newLimit -
gLastSentLimit.intValue
) < 20
){
return;
}
// -------------------------------------------------
// Neues Limit senden
// -------------------------------------------------
OpenDTU1_inv1_limit_absolute.sendCommand(newLimit)
gLastSentLimit = newLimit
gLastCommandTime = now
// -------------------------------------------------
// Laufende Regelschritte werden nur geloggt, nicht
// per Push gemeldet, um Notification-Spam bei
// häufigen Anpassungen zu vermeiden. Status-Wechsel
// (EIN/AUS/Offline/Online/manuell) werden weiterhin
// per Push gemeldet.
// -------------------------------------------------
if(gridPower < 0){
logInfo(
"PV",
"Einspeisung {} W (gefiltert) | WR {} -> {} W",
gridPower,
currentLimit,
newLimit
)
} else {
logInfo(
"PV",
"Netzbezug {} W (gefiltert) | WR {} -> {} W",
gridPower,
currentLimit,
newLimit
)
}
end