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