Introduction
This is the heating tutorial (DESIGN PATTERN) which is a part of my tutorial list, I keep coming back to them, improving them add functionality to them etc, so if you like it then hit “like” it and you will get notified when changes occurs. I would like to thanks @rlkoshak and @ThomDietrich for the help with this one.
Change Log
Please keep in mind that the tutorial is not quite finished and a few details might be missing. I decided to post it rather sooner than later. Comments are welcome!
!Update 18.11.2019
Better explanation of how it works
!Update 08.11.2019
Winter is coming so I posted the updated code now(Since OH2.3) when we can get triggering item from a group trigger
!Update 22.02.2018
Initial Commit
Recommended Temperatures
In general we can agree, that everyone has their own preference regarding the ideal temperature in a specific room. Vast recommendations and studies online suggest slightly different settings. The majority of them agree on a few ground rules:
-
Do not over-heat. You are burning money and the human body doesn’t need the extra warmth. Only increase to comfortable temperatures.
-
Only heat where needed. The hallway or closet don’t have to be cozy. A warm bedroom is not ideal during sleep. Plan your temperature schedules per room.
-
Do not let your home cool out. Heating up cold furniture and walls costs more energy than keeping the temperature at a certain level for a few hours.
-
Do not go below a certain point. Even when gone for a longer time the temperature needs to stay stay above a certain temperature. Otherwise pipes can freeze or mold can develop in moist corners.
Principle
- So we have several rooms in our house/flat/etc, i.e Group
- We want that each of these rooms will have a given Temperature , i.e setPoint
- So the rooms will also need a device/sensor to measure the temperature, in many cases you will have several devices measuring the temperature, and I therefore like to the average of these measurements, that way if one sensor fails you have redundancy built in to your home. i.e avgNumber
- The rooms will have also need to have one or more ways to heat the room, this devices/actuators needs to be able to receive a wanted temperature (I will show how you can do this in case your device is only able to be on/off as well)
so then we want to alter the setpoint in each room dependent on time of the day:
- we don’t want to heat unnecessary if we are not there, i.e at work
- if its evening and we chill in front of the TV we want it warmer
- we don’t want to sweat at night
- we don’t want want to wake up to a freezing cold room
In addition we want to alter the setpoint in each room dependent on special events such as
- holiday, we lower the temperature to safe level
- guest over, we don’t want to heat the guest room while not in use
- you just feel like turning the heat on for a moment because you are cold, i.e override
OpenHab implementation
So lets move over to how we can implement this in openhab, openhab is not as straight forward as mill or other heating apps
Rooms
You need to create some basic groups first, one for each room in your house, here it is important that you are consequent with the naming because we will use this to grab temperature, setpoints etc.
Group Group_Livingroom
Group Group_Kitchen
Group Group_Hallway
Group Group_Balcony
Group Group_GuestBedroom
Group Group_MasterBedroom
Group Group_Bathroom
Generic groups, used for rules
then we need to generate some generic groups, its just used for the rules but it might also be useful for you otherwise as well
Group Group_Heating
Group Group_SetPoint
Group Group_SetPoint_Command
Group Group_Room_Temperature
Set points
Then we need to create the item that will always hold the current value of the wanted room, so one item for each room:
Number Livingroom_Heating_Setpoint (Group_SetPoint,Group_SetPoint_Command,Group_Livingroom)
Number GuestBedroom_Heating_Setpoint (Group_SetPoint,Group_SetPoint_Command,Group_GuestBedroom)
Number MasterBedroom_Heating_Setpoint (Group_SetPoint,Group_SetPoint_Command,Group_MasterBedroom)
Number Bathroom_Heating_Setpoint (Group_SetPoint,Group_SetPoint_Command,Group_Bathroom)
Temperature from sensors
Then we need to make the groups which takes the average temperature in each room:
Group:Number:AVG Livingroom_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Group:Number:AVG MasterBedroom_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Group:Number:AVG GuestBedroom_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Group:Number:AVG Bathroom_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Group:Number:AVG Hallway_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Group:Number:AVG Balcony_Temperature "Average Temperature [%.1f C]" <temperature> (Group_Room_Temperature)
Ovens and other actuators
Then finally we need to add our ovens, this will be spesfic for each of you, I have underfloor heating with on/off and will simply show that here
Switch Hallway_Heating (Group_Heating) {gpio="pin:27" } //Relay 9
Switch Livingroom_Heating (Group_Heating) {gpio="pin:04" } //Relay 7
Switch Kitchen_Heating (Group_Heating) {gpio="pin:06" }//Relay8
Switch Bathroom_Heating (Group_Heating){gpio="pin:16" }//Relay 6
Switch MasterBedroom_Heating (Group_Heating){gpio="pin:21" }//Relay 11
Switch GuestBedroom_Heating (Group_Heating){gpio="pin:5" }//Relay 10
But to be able to send a setpoint to a simple on/off oven, I will turn it on if the temperature is below 1deg of set point an switch off when it raises to more then the set point, and I do this with a simple lambda:
val Functions.Function3 heatings = [ NumberItem setPoint,NumberItem temperature,GenericItem relay
// Turn on the heater if the temp gets more than 1 degrees below the Heating_XXXX_Setpoint to prevent rapid cycling
if((temperature.state as Number)-1.0< (setPoint.state as Number) ) {
if(relay.state != OFF) relay.sendCommand(ON)
}
else {
if(relay.state != ON) relay.sendCommand(OFF)
}
Pew you made it, now you can control your home if you add a simple rule, that will triggers if you change your setpoint or if one of your sensors gets updated:)
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Update Heating"
when
Member of Group_SetPoint_Command changed or
Member of Group_Room_Temperature changed
then
logInfo("Notification", "Sensor/control item: " + triggeringItem.name.split("_").get(0))
val roomName= triggeringItem.name.split("_").get(0) //as SwitchItem
//Here you need to make your own lamda that can handle your heaters
heatings.apply(ScriptServiceUtil.getItemRegistry.getItem(roomName+"_Heating_Setpoint"),ScriptServiceUtil.getItemRegistry.getItem(roomName+"_Temperature"),ScriptServiceUtil.getItemRegistry.getItem(roomName+"_Heating"))
end
Graphical user interface
If you use basic UI you can typically do something like this:
Frame label="Temperature" {
Setpoint item=Livingroom_Heating_Setpoint label="Temperature Set Point [%.1f °C]" icon="temperature" minValue=1 maxValue=30 step=0.5
Text item=Livingroom_Temperature label="Average Temperature [%.1f °C]" icon="temperature"
Switch item=Livingroom_Heating label="Heating Livingroom status" icon="temperature"
}
And this will look simple but practical:
So you can see that you can overide the setpoint, see the average temperature, and simply boost the heat if you want to. This works superb for us, but we also wanted to do more as mentioned above. So if you managed to follow so far lets move on to alter the setpoint:
Time of the day
So to lets start with changing the temperature based on the time of the day. So for now lets just create a string that holds the time of the day, as well as some other time relevant items:
String vTimeOfDay "Current Time of Day [%s]"
String vWorkMode "Current work Mode [%s]"
DateTime vSunrise_Time "Sunrise [%1$tH:%1$tM]" {channel="astro:sun:home:rise#start" }
DateTime vSunset_Time "Sunset [%1$tH:%1$tM]" {channel="astro:sun:home:set#start" }
DateTime vEvening_Time "Evening [%1$tH:%1$tM]" {channel="astro:sun:minus60:set#start" }
So for testing I recommend to create an selection item in your sitemap so that you can test different times of the day
Selection item=Scene_DayMode label="Day Mode" mappings=[1="PREHEAT", 2="SUNRISE", 3="MORNING", 4="DAY",5="WORK", 6="AFTERNOON", 7="EVENING", 8="SUNSET",9="NIGHT"]
So thats 9 different triggers, however not all time of day needs to be used to change the set point of the temperature in the home, like sunrise will only be used for turning off the outdoor light and so on. This means that
vTimeOfDay="NIGHT", or vTimeOfDay="SUNSET" and so on.
So lets move on to alter the the temperature in the rooms dependent on the time of the day!
Multiple set points
So lets assume you want to have one temperature at night and another one during the day.
Then we first need to create some general time of the day groups, we only create two since we only want to alter the temperature twice during the day.
Group Group_SetPoint_TimeOfDay
Group Group_SetPoint_Night (Group_SetPoint_TimeOfDay)
Group Group_SetPoint_Day (Group_SetPoint_TimeOfDay)
In case you want to alter the temperature more during the day, then create several more group, however the name of the group needs to be exact this syntax “Group_SetPoint_” + the time of day you want to change the temperature.
To achieve this we need to create more set points, so that we can actually decide the temperature in each room, and these needs to be member of the previously created groups
Number Heating_LivingRoom_Setpoint_Day (Group_SetPoint,Group_SetPoint_Day, Group_LivingRoom)
Number Heating_LivingRoom_Setpoint_Night (Group_SetPoint, Group_SetPoint_Night, Group_LivingRoom)
The idea is when the time of the day changes to night the Heating_LivingRoom_Setpoint
will be equal to Heating_LivingRoom_Setpoint_Night
and when when time of day changes to day it will be Heating_LivingRoom_Setpoint
equal to Heating_LivingRoom_Setpoint_Day
So in my flat the administrator sitemap looks like this:
All that’s left to do now is to make a rule that gets triggered every time the time of day changes and then use the associated item to grab the correct setpoint:
rule "Update Heating dependent of time of day"
when
Item vTimeOfDay changed
then
logInfo("Notification", "Time of day changed to : " + vTimeOfDay.state)
//Time of the day....
val tod = vTimeOfDay.state.toString
// Get the time of day group
val todGroup= Group_SetPoint_TimeOfDay.members.findFirst[g | g.name.equalsIgnoreCase("Group_SetPoint_"+tod)] as GroupItem
// loop through each room
Group_SetPoint_Command.members.forEach[ room |
// Get the setpoint name for this TOD
val setpoint = todGroup.members.findFirst[sptod |sptod.name.contains(room.name)] as NumberItem
// if setpoint is null we do not change the heater's setting and let it stay the same as it was before How to rewrite this?????
if(setpoint != null) {
room.sendCommand(setpoint.state as Number)
logInfo(logName, "Setpoint is now " + setpoint.state + " in room " + room.name.toString)
}
]
end
Here is my time of day that I use for reference…
State | Start | End |
---|---|---|
MORNING | 06:00(mon-fri), 08.00(sat-sun | Sunrise or 08.00(mon-friday), 10.00(sat-sun) |
DAY | Sunrise or 08.00(mon-fri),10.00 (sat-sun) and 16.00(mon-fri) | Sunset - 90 minutes or 20.00 |
WORK(mon-friday) | 09.00 or voice command | 16.00 or presence detection |
EVENING | Sunset - 90 minutes or 20.00 | Sunset or 23.00 |
NIGHT | 23:00(mon-fri),24:00 (sat-sun) or voice command | 06:00 |
and the time of day rule:
val logName = "time of day"
val morningWeekdays = 7
val morningWeekends = 8
val nightWeekdays = 23
val nightWeekends = 0
val preHeatMinutes = 90
rule "Calculate time of day state"
when
// There MUST be a trigger for every time of day start time
System started or
Channel 'astro:sun:home:rise#event' triggered START or
Channel 'astro:sun:home:set#event' triggered START or
Channel 'astro:sun:minus90:set#event' triggered START or
Item Alexa_Off received command OFF or
Time cron "0 0 8 * * ? *" or // Morning weekdays
Time cron "0 0 10 * * ? *" or // Morning weekends
Time cron "0 45 6 * * ? *" or // Preheat weekdays
Time cron "0 45 8 * * ? *" or // Preheat weekends
Time cron "0 0 20 * * * *" or //Eveeings
Time cron "0 0 23 * * ? *" or // Night weekdays
Time cron "1 0 0 * * ? *" // Night weekends
then
// Now that we can use more than one cron trigger we don't need the sleep and can trigger the rule to occur one second after midnight
// Is it the weekend?
val isWeekend = if(now.dayOfWeek >= 6) true else false // you will need to verify that Sat is 6 and Sun is 7
// Use the morning and night hour based on isWeekend
val morning_hour = if(isWeekend) morningWeekends else morningWeekdays
val night_hour = if(isWeekend) nightWeekends else nightWeekdays
//val morning_hour= morningWeekdays
//val night_hour = nightWeekdays
// Calculate the start time in epoch for ALL states
val long preHeat_start = now.withTimeAtStartOfDay.plusHours(morning_hour).minusMinutes(preHeatMinutes ).millis // 15 minutes before morning_start, however alexa does not tell us when alarm is set for, so we need to use backup time minus 75 minutes..
val long morning_start = now.withTimeAtStartOfDay.plusHours(morning_hour).millis // morning_hour based on isWeekend
val long day_start = (vSunrise_Time.state as DateTimeType).calendar.timeInMillis // Astro
val long afternoon_start = (vEvening_Time.state as DateTimeType).calendar.timeInMillis // Astro
val long evening_start = (vSunset_Time.state as DateTimeType).calendar.timeInMillis // Astro
val long night_start = now.withTimeAtStartOfDay.plusHours(night_hour).millis // night_hour based on isWeekend
var curr = "UNKNOWN"
switch now {
case now.isAfter(preHeat_start) && now.isBefore(morning_start): curr = "PREHEAT"
case now.isAfter(morning_start) && now.isBefore(day_start): curr = "MORNING"
case now.isAfter(day_start) && now.isBefore(afternoon_start): curr = "DAY"
case now.isAfter(afternoon_start) && now.isBefore(evening_start): curr = "AFTERNOON"
case now.isAfter(evening_start) && now.isBefore(night_start): curr = "EVENING"
case now.isAfter(night_start) && now.isBefore(preHeat_start): curr = "NIGHT"
}
// These could probably be made a part of the switch statement but seems OK here
// However, this will only work if the state will be set to ON at some point that makes sense.
if Alexa_Off.state==OFF && now.isAfter(morning_start) && now.isBefore(afternoon_start) curr = "WORK"
if Alexa_Off.state==OFF && now.isAfter(evening_start) && now.isBefore(night_start) curr = "NIGHT" //Going earlier to bed
Alexa_Off.sendCommand(ON)
if(vTimeOfDay.state.toString != curr) {
logInfo(logName, "Current time of day is now " + curr)
vTimeOfDay.sendCommand(curr)
}
end
Advanced usage:
As this was not enough we might want to adjust the temperature in some special cases like on holiday, no guest over( you do not want the heat on in the guest room, set it to holiday temperature). and so on. This is also covered by the rule as seen above.
Summary
- Create a group for each room
- Create generic groups
- Create your setpoints command
- Create average temperature readings
- Install bindings and create items for your heating actuators
- Add it to user interface
- Change my generic rule to send the setpoint to your oven
- Create a time of day item and populate this through a rule
- Create groups for the time of day you want to change temperature i.e day, night, evening etc…
- Create an item for each the time of day and room you want to change temperature at
- Create an admin sitemap where you can set the different temperature settings for each room and time of day
- Copy my generic rule to change time
Common bugs
- None as far as I know
Further readings
You are always welcome to my github for a more in depth explanation and easier cloning of files:
https://github.com/skatun/LittleChina/wiki or more spesfic:
- https://github.com/skatun/LittleChina/blob/master/Code/openHAB-conf/rules/heating.rules
- https://github.com/skatun/LittleChina/blob/master/Code/openHAB-conf/items/heating.items
- https://github.com/skatun/LittleChina/blob/master/Code/openHAB-conf/sitemaps/littlechina.sitemap
- https://github.com/skatun/LittleChina/blob/master/Code/openHAB-conf/rules/timeOfDay.rules
Related Design Patterns(Link to Rich’s DP for now)
Design Pattern | How Used |
---|---|
Design Pattern: How to Structure a Rule | Overall structure of the first complex rule follows this DP |
Assocaited Items | Gets the Alerted Item based on the Online Item’s name |
Working with Groups in Rules | Get the Items around the time the rule was triggered, loop through those Items |
Time of Day | Time of the day to alter lights |
Magical Lights | How to control the lights in your Taj Mahal |