DP - Heating Boilerplate

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

  1. Create a group for each room
  2. Create generic groups
  3. Create your setpoints command
  4. Create average temperature readings
  5. Install bindings and create items for your heating actuators
  6. Add it to user interface
  7. Change my generic rule to send the setpoint to your oven
  8. Create a time of day item and populate this through a rule
  9. Create groups for the time of day you want to change temperature i.e day, night, evening etc…
  10. Create an item for each the time of day and room you want to change temperature at
  11. Create an admin sitemap where you can set the different temperature settings for each room and time of day
  12. Copy my generic rule to change time

Common bugs

  1. 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:

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
9 Likes

To help users implement this from scratch, when you reference other postings like the Time of Day DP and Thom’s Heating post include a link.

I usually include the link inline when I first mention it and I’ve started to create a little table at the end with the references as well.

One thing that I find useful is when I post rules code is to provide a textual explanation of what the Rule does. Look at the “theory of operation” sections of any of my DPs for an example. Lots of users on this forum can’t read and understand code and the text helps them understand how it works (most of the time).

Great posting! I look forward to seeing more.

Hi @rlkoshak I turned this into a wiki(Admin did), and I was wondering if I should try to rewrite this now that new methods are availble to items(membersOf group etc…)? What do you think? Feel free to edit original post now that its a wiki

I would consider a rewrite utilizing triggeringItem. I am waiting until 2.3 is released before I go through my DPs and rewrite them to use the Member of trigger. But I’m not entirely certain either helps much as your existing rules need to do the same thing regardless of what Item triggered the rule itself.

1 Like

Hi @skatun. Great article, I’m considering to use it as the template for my own smart home implementation. Looking forward for your further contribution.

I have two questions:

  1. I wonder if you can point me to any good article in the web about controlling the temperature inside the house by turning ON/OFF the boiler. Something more clever than:

if temperature_inside < 22 then turn_on_heating()

  1. I noticed, in one of rules you are using the Amazon Alexa:

Item Alexa_Off received command OFF

Can you refer me to any documentation how to do this?

This is how I do it

val Functions.Function3 heatings = [ NumberItem setPoint,NumberItem temperature,GenericItem relay |
// Turn on the heater if the temp gets more than 2 degrees below the Heating_LivingRoom_Setpoint to prevent rapid cycling

if((temperature.state as Number)-1.0< (setPoint.state as Number) ) {
	//Items connected to NC, i.e sendcommand off is ON!!
	if(relay.state != OFF) relay.sendCommand(OFF)
}
else {
	//Items connected to NC, i.e sendcommand off is ON!!
	if(relay.state != ON) relay.sendCommand(ON)
}

		
]

and one of my trigger to drive the generic function

// Rule to drive the Heater
rule "Heater in living room"
	when
		Item Netatmo_Indoor_Temperature received update or
		Item Heating_LivingRoom_Setpoint received update
	then
		heatings.apply(Heating_LivingRoom_Setpoint,Netatmo_Indoor_Temperature,Heating_LivingRoom)
end

Hi Kim,

thank you for your answer. The algorithm looks not so complicated. Does it work fine for you?

I have got at my home the thermo-regularization system manufactured by Ariston. The reason why I’m asking is that this system looks for me more complicated. Except for the gas boiler, it includes the control panel with a temperature sensor on board, and another one temperature sensor located outside. In the user manual, it is written that outside sensor is needed to avoid overheating when the weather outside is hot. There are also plenty of system parameters for the heating scenario.

The system works pretty well but its problem is that it doesn’t have any public API and I can’t control it over OH. Now I’m considering to replace this system with my own algorithm, but I’m afraid I can not make it as good as Ariston did.

If there is anyone who has some experience in development of an efficient thermo-regulation system, please share some ideas with us!

Great heating DP!

But, I do not understand completely how to use the different groups (day, night and so on) to aggregate the setpoint values to the generic rule that switches the ovens on and off. Anyone, please share some insight that would help me implement the different group setpoint values…

UPDATE 20.11.2019: Thanks for the updated description! DP - Heating Boilerplate