Skip to content

Instantly share code, notes, and snippets.

@markormesher
Created July 28, 2022 12:57
Show Gist options
  • Save markormesher/0607b7442a79e28789eeb026a2fc7070 to your computer and use it in GitHub Desktop.
Save markormesher/0607b7442a79e28789eeb026a2fc7070 to your computer and use it in GitHub Desktop.
OpenHAB Heating Rule
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