]> git.basschouten.com Git - openhab-addons.git/commitdiff
[growatt] better and more documentation of possible rule actions (#16947)
authorAndrew Fiddian-Green <software@whitebear.ch>
Fri, 5 Jul 2024 14:50:48 +0000 (15:50 +0100)
committerGitHub <noreply@github.com>
Fri, 5 Jul 2024 14:50:48 +0000 (16:50 +0200)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
bundles/org.openhab.binding.growatt/README.md

index ab3d5e7fed79a41090ba6ad31419423be408984a..9a9e6407a2d4d92a41959673b35d58eb827d20e1 100644 (file)
@@ -180,9 +180,26 @@ The permission for passing 'null' parameters, and the effect of such 'null' para
 | enableAcCharging                   | If 'null' the prior `enableAcCharging` (if any) continues to apply. Shall <u>**not**</u> be 'null' on 'mix' inverter 'Battery First' program. |
 | startTime, stopTime, enableProgram | May be 'null' on 'tlx' inverters whereby the prior `programMode` / `time segment` continues to apply - note all 'null' resp. non-'null'.      |
 
-Example:
+### Example program to charge battery during night-time low tariff time window
+
+The following is an example program to charge the battery during a night-time low tariff period, and depending on the forecast solar energy for the coming day.
 
 ```php
+// solar power constants
+val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First
+val Integer powerLevel = 23 // percent
+val batteryFull = 6500.0 // Wh
+val batteryMin = 500.0 // Wh
+val daylightConsumption = 10000.0 // Wh
+val offPeakConsumption = 2000.0 // Wh
+val maximumSOC = 100.0 // percent
+val minimumSOC = 10.0 // percent
+val maxChargingPower = 4000.0 // W
+val offPeakEndMinute = 420 // 07:00
+val offPeakStartMinute = 20 // 00:20
+
+..
+
 rule "Setup Solar Battery Charging Program"
 when
     Time cron "0 10 0 ? * * *"
@@ -190,39 +207,138 @@ then
     val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") // thing UID
     if (growattActions === null) {
         logWarn("Rules", "growattActions is null")
-    } else {
+        return
+    }
+
+    // variable algorithm parameters
+    var Integer startMinute = offPeakStartMinute
+    var Boolean enableProgram = true
+    var Boolean enableAcCharging = true
 
-        // fixed algorithm parameters
-        val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First
-        val Integer powerLevel = 23 // percent
-        val Boolean enableAcCharging = true
-        val String startTime = "00:20"
-        val String stopTime = "07:00"
-        val Boolean enableProgram = true
-
-        // calculation intermediaries
-        val batteryFull = 6500.0 // Wh
-        val batteryMin = 500.0 // Wh
-        val daylightConsumption = 10000.0 // Wh
-        val maximumSOC = 100.0 // percent
-        val minimumSOC = 20.0 // percent
-
-
-        // calculate stop SOC based on weather forecast
-        val Double solarForecast = (ForecastSolar_PV_Whole_Site_Forecast_Today.state as QuantityType<Energy>).toUnit("Wh").doubleValue()
-        var Double targetSOC = (100.0 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull
-        if (targetSOC > maximumSOC) {
-            targetSOC = maximumSOC
-        } else if (targetSOC < minimumSOC) {
-            targetSOC = minimumSOC
+    // calculate required stop SOC based on weather forecast
+    val Double solarForecast = (Solar_Forecast_Energy_Today_Full.state as QuantityType<Energy>).toUnit("Wh").doubleValue()
+    var Double targetSOC = (100.0 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull
+    if (targetSOC > maximumSOC) {
+        targetSOC = maximumSOC
+    }
+
+    // calculate notional SOC at end of off peak period
+    var Double morningSOC = ((Battery_SOC_Level.state as QuantityType<Dimensionless>).toUnit("one").doubleValue() - (offPeakConsumption / batteryFull)) * 100.0
+    if (morningSOC < minimumSOC) {
+        morningSOC = minimumSOC
+    }
+
+    // calculate charging start time (if any)
+    if (targetSOC > morningSOC) {
+        startMinute = (offPeakEndMinute - (60.0 * (targetSOC - morningSOC) * batteryFull / (powerLevel * maxChargingPower))).intValue()
+        if (startMinute < offPeakStartMinute) {
+            startMinute = offPeakStartMinute
         }
+    } else {
+        enableProgram = false
+        enableAcCharging = false
+        targetSOC = minimumSOC
+    }
 
-        // convert to integer
-        val Integer stopSOC = targetSOC.intValue() // percent
+    // convert times to strings
+    val String startTime = String.format("%02d:%02d", startMinute / 60, startMinute % 60);
+    val String stopTime = String.format("%02d:%02d", offPeakEndMinute / 60, offPeakEndMinute % 60);
 
-        logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + "Wh, programMode:" + programMode + ", powerLevel:" + powerLevel + "%, stopSOC:" + stopSOC + "%, enableCharging:" + enableAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", enableProgram:" + enableProgram +"}")
-        growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram)
+    // convert to integer percent
+    val Integer stopSOC = targetSOC.intValue()
+
+    logInfo("Rules", "Setup Charging Program(morningSOC=" + morningSOC + "%, solarForecast=" + solarForecast + "Wh, programMode=" + programMode + ", powerLevel=" + powerLevel +
+        "%, stopSOC=" + stopSOC + "%, enableCharging=" + enableAcCharging + ", startTime=" + startTime + ", stopTime=" + stopTime + ", enableProgram=" + enableProgram +")")
+    growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram)
+end
+```
+
+### Example program to charge battery prior to an extra high tariff window in the day
+
+The following is an example program to charge the battery in preparation to avoid importing energy during a coming extra high tariff time window.
+
+```php
+// solar power constants
+var pauseProgramLastSetupDate
+
+..
+
+rule "Setup Solar Power Pause Program"
+when
+    Time cron "59 0 8-22 ? * * *" or
+    Item Power_Pause_Program_Start changed
+then
+    val programSetupDate = now.toLocalDate()
+    if (programSetupDate.equals(pauseProgramLastSetupDate)) {
+        logInfo("Rules", "Power Pause program already setup for " + programSetupDate)
+        return
     }
+
+    val pauseStartState = Power_Pause_Program_Start.state
+    if (pauseStartState == NULL || pauseStartState == UNDEF) {
+        logWarn("Rules", "Power_Pause_Program_Start state is null or undefined")
+        return
+    }
+
+    var pauseStartDateTime = (pauseStartState as DateTimeType).getZonedDateTime()
+    if (pauseStartDateTime.getHour() < 8) {
+        logWarn("Rules", "Power Pause program shall not start before 08:00h => " + pauseStartDateTime)
+        return 
+    }
+
+    val programDuration = Duration.between(now, pauseStartDateTime)
+    if (programDuration.isNegative() || programDuration.toDays() > 0) {
+        logInfo("Rules", "Power Pause program date is not today => " + pauseStartDateTime)
+        return
+    }
+    if (programDuration.toHours() < 1) {
+        logWarn("Rules", "Power Pause program lead time is too short => " + pauseStartDateTime)
+        return
+    }
+
+    // setup program to start late and end early in case inverter clock not in synch
+    val delta = 600
+    pauseStartDateTime = pauseStartDateTime.minusSeconds(delta)
+    val chargeStartDateTime = pauseStartDateTime.minusSeconds(programDuration.toSeconds()).plusSeconds(2 * delta)
+    if (chargeStartDateTime.isBefore(now)) {
+        logWarn("Rules", "Power Pause program start time is in the past")
+        return
+    }
+
+    val formatter = DateTimeFormatter.ofPattern("HH:mm");
+    val String stopTime = pauseStartDateTime.format(formatter)
+    val String startTime = chargeStartDateTime.format(formatter)
+
+    val socState = Battery_SOC_Level.state
+    if (socState == NULL || socState == UNDEF) {
+        logWarn("Rules", "Battery_SOC_Level is null or undefined")
+        return
+    }
+    val currentSOC = (socState as Number)
+
+    var targetPowerLevel = ((maximumSOC - currentSOC) * batteryFull) / (maxChargingPower * (programDurationSeconds / 3600.0))
+    if (targetPowerLevel < 23.0) {
+        targetPowerLevel = 23.0
+    } else if (targetPowerLevel > 100.0) {
+        targetPowerLevel = 100.0
+    }
+    val Integer powerLevel = targetPowerLevel.intValue()
+
+    val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First
+    val Boolean enableAcCharging = true
+    val Boolean enableProgram = true
+    val Integer stopSOC = maximumSOC.intValue()
+
+    val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") // thing UID
+    if (growattActions === null) {
+        logWarn("Rules", "growattActions is null")
+        return
+    }
+
+    pauseProgramLastSetupDate = programSetupDate
+    logInfo("Rules", "Setup Solar Power Pause Program(programMode=" + programMode + ", powerLevel=" + powerLevel + "%, stopSOC=" + stopSOC + "%, enableCharging=" +
+        enableAcCharging + ", startTime=" + startTime + ", stopTime=" + stopTime + ", enableProgram=" + enableProgram +")")
+    growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram)
 end
 ```