Created
July 28, 2022 12:57
-
-
Save markormesher/0607b7442a79e28789eeb026a2fc7070 to your computer and use it in GitHub Desktop.
OpenHAB Heating Rule
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.openhab.core.model.script.ScriptServiceUtil | |
val heatingZones = newArrayList( | |
"livingRoom", | |
"bathroom", | |
"masterBedroom", | |
"spareBedroom", | |
"office", | |
// etc... | |
) | |
rule "Heating: turn heating zone virtual items on/off" | |
when | |
// every 5 minutes, offset by 10s to make sure the target temperatures have been set above | |
Time cron "10 0/5 * ? * * *" | |
or Member of gc_targetTemperatures received update | |
then | |
// this rule does two passes over heating zones: | |
// - the first pass decides what kind of call for heat to issue (see below) | |
// - the second pass applies changes to the virtual items for whether heating is enabled in that zone | |
// there are two kinds of calls for heat: firm and soft | |
// zones with a firm call for heat will always receive heat (i.e. radiator turns on, boiler turns on) | |
// zones with a soft call for heat will only receive heat iff there is at least one firm call at the same time | |
// firm/soft calls are determined based on how actual and target temperatures combine with the hysteresis value to create 4 cases: | |
// case 1: actual temp is more than <hysteresis> below the target | |
// - firm call for heat | |
// case 2: actual temp is within <hysteresis> below the target | |
// - if the zone is on (i.e. we're climbing through this case) then make a firm call to keep it on | |
// - if the zone if off (i.e. we're falling through this case) then make a soft call | |
// case 3: actual temp is within <hysteresis> above the target | |
// - if the zone is on (i.e. we're climbing through this case) then make a firm call to keep it on | |
// - if the zone if off (i.e. we're falling through this case) then don't call for heat | |
// case 4: actual temp is more than <hysteresis> above the target | |
// - don't call for heat | |
val Number tempHysteresis = 0.5 | |
val itemRegistry = ScriptServiceUtil.getItemRegistry | |
val softHeatCalls = newArrayList() | |
val firmHeatCalls = newArrayList() | |
heatingZones.forEach [ String zone | | |
val Number zoneActualTemp = itemRegistry.getItem(zone + "_temperatureHumiditySensor_temperature").state | |
val Number zoneTargetTemp = itemRegistry.getItem(zone + "_targetTemperature").state | |
val boolean zoneEnabled = itemRegistry.getItem(zone + "_heatingEnabled").state == ON | |
var String callType = "none" // just for logging | |
if (zoneActualTemp < (zoneTargetTemp - tempHysteresis)) { | |
// case 1 | |
callType = "firm" | |
} else if (zoneActualTemp < zoneTargetTemp) { | |
// case 2 | |
if (zoneEnabled) { | |
callType = "firm" | |
} else { | |
callType = "soft" | |
} | |
} else if (zoneActualTemp < (zoneTargetTemp + tempHysteresis)) { | |
// case 3 | |
if (zoneEnabled) { | |
callType = "firm" | |
} | |
} else { | |
// case 4: do nothing | |
} | |
if (callType == "firm") { | |
firmHeatCalls.add(zone) | |
} else if (callType == "soft") { | |
softHeatCalls.add(zone) | |
} | |
logInfo( | |
"heating", | |
"Evaluating {}: target = {}; actual = {}; enabled = {}; new heat call = {}.", | |
zone, | |
zoneTargetTemp, | |
zoneActualTemp, | |
(if (zoneEnabled) "on" else "off"), | |
callType | |
) | |
] | |
logInfo("heating", "Finished evaluating zones. Firm calls = {}; soft calls = {}", firmHeatCalls.toString(), softHeatCalls.toString()) | |
// apply on/off states to zones | |
heatingZones.forEach [ String zone | | |
val boolean turnZoneOn = firmHeatCalls.size() > 0 && (firmHeatCalls.contains(zone) || softHeatCalls.contains(zone)) | |
sendCommand(zone + "_heatingEnabled", if (turnZoneOn) "ON" else "OFF") | |
] | |
// apply on/off state to boiler | |
garage_boiler_callForHeat.sendCommand(if (firmHeatCalls.size() > 0) ON else OFF) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment