Rules - Heating

Please can some help. I love the software, and I understand items and sitemaps but I’m lost on rules (and not even thinking about persistence and logs yet!).

Long story short we were using eq3 max cube and a few thermostats and wall thermostats, turning the boiler on manually. My dad has spent some time in hospital, and now needs to stay with us, bedridden, so I needed a way for the boiler to turn itself on and off (which Max doesnt yet support).

I have bought a zwave boiler switch and had it wired in, all working - along with controller.

I’ve tried to set up two rules (I dont understand groups, I’m sure thats the way to go but as everything is in a different room, including on sitemaps, cant work it out).

Anyway, my rules do sort of work. I have this for the turning on of the boiler, which compares the target temp against actual, and if lower it turns the boiler on. Please do say if there is something im not doing.

rule “Boiler On - Dads Bedroom”

when
Item DSBWTSmaxSetTemp changed or
Item DSBWTSmaxActual received update
then

if ((DSBWTSmaxActual.state < (DSBWTSmaxSetTemp.state) as DecimalType))
if (MainBoiler.state == ON) sendCommand(MainBoiler, OFF)
end

rule “Boiler On - Bedroom”

when
Item DSKWTSmaxSetTemp changed or
Item DSKWTSmaxActual received update
then

if ((DSKWTSmaxActual.state < (DSKWTSmaxSetTemp.state) as DecimalType))
if (MainBoiler.state == OFF) sendCommand(MainBoiler, ON)
end

rule “Boiler On - Kitchem”

when
Item KITWTSmaxSetTemp changed or
Item KITWTSmaxActual received update
then

if ((KITWTSmaxActual.state < (KITWTSmaxSetTemp.state) as DecimalType))
if (MainBoiler.state == OFF) sendCommand(MainBoiler, ON)
end

rule “Boiler On - Living Room”

when
Item LVRWTSmaxSetTemp changed or
Item LVRWTSmaxActual received update
then

if ((LVRWTSmaxActual.state < (LVRWTSmaxSetTemp.state) as DecimalType))
if (MainBoiler.state == OFF) sendCommand(MainBoiler, ON)
end

My off rule is the issue. I can’t seem to work out an ‘and’ command, as I only want to turn the boiler off it all 4 are at temperature, where as if i use the same rule as above in reverse, it will turn it off as soon as one is.

rule “Boiler Off - Room Check”

when
Item DSBWTSmaxSetTemp changed or
Item DSBWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then

if
((LVRWTSmaxActual.state > (LVRWTSmaxSetTemp.state) as DecimalType))

if (MainBoiler.state == ON) sendCommand(MainBoiler, OFF)
end

I have no idea what to do, please help…

Scott

Many ways of doing this, the most simple one would probably be

if ((LVRWTSmaxActual.state > (LVRWTSmaxSetTemp.state) as DecimalType) && (DSBWTSmaxActual.state > (DSBWTSmaxSetTemp.state) as DecimalType) && (DSKWTSmaxActual.state > (DSBKTSmaxSetTemp.state) as DecimalType) && (KITWTSmaxActual.state > (KITWTSmaxSetTemp.state) as DecimalType))
    if (MainBoiler.state == OFF) sendCommand(MainBoiler, ON)

Ahhh, does that work?! I tried that but it kept giving my errors in the Smart Eclipse software

So is the way I’ve done it a fairly reasonable way to have it coded? If I add -1 to the coding will it turn the boiler on when the actual temp is -1 from the set point rather than straight away?

Will it be easy to add logging, presence detection in later when I get more confident?

I don’t know if it’s the above code but the boiler keeps getting an OFF command even when those statements haven’t been met?

When posting code, it is easier to read if you use code fences,

```
code goes here
```

This will preserve your white spaces and indentation.

The code posted looks fairly reasonable. You have a bunch of extra and unnecessary parens in some of your if statements and I don’t know if you have to cast the states to DecimalType, but if you do for the stuff on the right you also need to do so for the stuff on the left:

if(DSBWTSmaxActual.state  < DSBWTSmaxSetTemp.state)

or

if(DSBWTSmaxActual.state as Number < DSBWTSmaxSetTemp.state as Number)

In your OFF rule, if I understand correctly you want to only turn off the boiler if all rooms are greater than their target temps.

As you guess, using a Group is a better way to go, but @mstormi’s solution is how I would do it without Groups.

Its a reasonable first try for someone who doesn’t know how to use Groups or lambdas yet.

Everything being in a different room doesn’t matter. A Group is like a tag you can put on an Item. In your rule you can then gain access to all the Items with that same tag. Each Item can belong to multiple Groups. So if you put all of your SmaxActual Items into the same Group (lets call it gSmaxActual) and put all the SmaxSetTemp Items into another Group (lets call it gSmaxSetTemp) and use a little bit of Group magic you might be able to collapse the above into one Rule but this is actually a little complicated because if any one room is too cold you turn on the boiler but you only turn it off is all the rooms are below the target temp, and each room has its own target temp.

I’m not recommending the code below for use. In fact I’m just typing it in and it may have lots of errors. Just thinking about how I would go about solving this problem.

rule "Boiler control"
when
    Item gSmaxActual received update // triggers when any member of gSmaxActual update
then
    
    // See if we need to turn it off
    if(MainBoiler.state == ON) {
        val turnOff = gSmaxActual.members.reduce[ off, actual |
            off && actual.state > gSmaxSetTemp.members.filter[ st | st.name = actual.name.replace("Actual", "SetTemp").head.state
        ]
        if(turnOff) MainBoiler.sendCommand(OFF)
    }

    // See if we need to turn it on
    else {
        gSmaxActual.members.forEach[actual |
            val setTemp = gSmaxSetTemp.members.filter[ setTemp | setTemp.name == actual.name.replace("Actual", "SetTemp")].head
            if(actual.state < setTemp.state) MainBoiler.sendCommand(ON)
        ]
    }
end

The rule above triggers whenever the measured temperature in a room gets updated.

If the boiler is already on we look to see if it needs to be turned off. The reduce is a way to collapse all the members of a Group into a single value. In this case that value is a boolean. If I did the logic correctly, it should return true only if all the Actual temperatures are higher than their corresponding SetTemps.

If the boiler is OFF we look to see if any room is lower than the target temp and if so turn the boiler ON.

Logging is easy.

logInfo("Name", "Log statement")

Persistence is also easy and independently configured and managed.

So, ignoring the Group based solution above which is frankly not an apporach I would recommend for someone at your skill level (come back later :wink: ), I think you can work through your OFF rule if you split it up a bit for clarity:

rule "Boiler Off -Room Check"
when
    Item DSBWTSmaxSetTemp changed or
    Item DSBWTSmaxActual received update or
    Item DSKWTSmaxSetTemp changed or
    Item DSKWTSmaxActual received update or
    Item KITWTSmaxSetTemp changed or
    Item KITWTSmaxActual received update or
    Item LVRWTSmaxSetTemp changed or
    Item LVRWTSmaxActual received update or
    Time cron "0 0/5 * 1/1 * ? *"
then
    if(MainBoiler.state == ON){
        var turnOff = true
        if(DSBWTSmaxActual.state < DSBWTSmaxSetTemp.state) turnOff = false
        if(DSKWTSmaxActual.state < DSKWTSmaxSetTemp.state) turnOff = false
        if(KITWTSmaxActual.state < KITWTSmaxSetTemp.state) turnOff = false
        if(LVRWTSmaxActual.state < LVRWTSmaxSetTemp.state) turnOff = false

        if(turnOff) MainBoiler.sendCommand(OFF) 
    }
end

Realize you will want to trigger the rule for a change in any room. I’m not sure you need the Time trigger but left it in.

First we check to see if the boiler is on. If not we don’t care.

Then we assume we need to turn the boiler off.

Next we check each room to see if their actual state is less than the target state. If any room is below the target temp we do not turn off the boiler.

1 Like

Thank you so so much, you have no idea how helpful that it!

So I can basically use the Turn Off you have placed above can’t I? It introduces a variable “turnOff” and defaults it’s to true (if the boiler is on), but then change it to false if any of the temperatures are below.

Why do I not need if(turnOff==true) as the end before the command? Does openHAB assume you want true unless told otherwise?

So if I wanted the boiler on rules to only work between say 4pm and 11pm, do I add this to the when, or is it an if statemenr under the then section?

That is correct.

Pretty much all programming languages have a data type called a boolean. A boolean (named after some 18th century mathematician I think) can be true or false.

There are certain operations that return a boolean, ==, <, &&, etc.

When you have an if statement what you really have is:

if(boolean)

So the == true is implied.

There is another handy operator, !

if(boolean == false)

is equivalent to

if(!boolean)

So, in the above, using

if(turnOff == true) 

would be redundant.

I recommend looking at the Time of Day design pattern (search the forum) for how I manage time periods.

Remember that rules are event driven, not state driven. There is no “and” in the when clause so there is no way to make it run only been two time periods in the when clause. So you must use an if statement in the rule similar to how I have an if to test if MainBoiler is ON in the Off rule.

1 Like

I’ve looked at the Pattern and it’s really helpful, thank you. I think I’ll use that for most things, but where the heating is time specific could I add something like this to my code?

...
when

Item DSBWTSmaxSetTemp changed or
Item DSBWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then

if ((DSBWTSmaxActual.state < (DSBWTSmaxSetTemp.state) as DecimalType))
if((new LocalTime().getLocalMillis()) >= (new LocalTime(22, 0, 0, 0).getLocalMillis()))  &&  if((new LocalTime().getLocalMillis()) < (new LocalTime(8, 0, 0, 0).getLocalMillis())) 
if (MainBoiler.state == OFF) sendCommand(MainBoiler, ON)


end
...

The only other bit I can’t seem to sort is the logs. I have created the logs fine, it’s the sending the commands. I added a line to send a log in to the Boiler Off code you kindly sent, but when I did it send the message to the log every 10 seconds or so… I cheched and thankfully the command wasnt being sent that often, just the log.

I’ve playing around with persistence at the moment too, this is one hell of a learning curve!!

Thank you again!

Scrap that, I made it look more like you’re off code, so it’s now:

    rule "Boiler On - Dads Bedroom"

when

Item DSBWTSmaxSetTemp changed or
Item DSBWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then

if(MainBoiler.state == ON){
        var DSBturnOn = true
        if(DSBWTSmaxActual.state > DSBWTSmaxSetTemp.state) DSBturnOn = false
	   if (BoilerOveride.state ==ON) DSBturnOn = false
        if((new LocalTime().getLocalMillis()) >= (new LocalTime(23, 0, 0, 0).getLocalMillis()))  DSBturnOn = false
        if((new LocalTime().getLocalMillis()) < (new LocalTime(7, 0, 0, 0).getLocalMillis()))   DSBturnOn = false

        if(DSBturnOn) MainBoiler.sendCommand(ON) 

}

end



rule "Boiler On - Kitchen"

when
Item KITWTSmaxSetTemp changed or
Item KITWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then

if(MainBoiler.state == ON){
        var KITturnOn = true
        if(KITWTSmaxActual.state > KITWTSmaxSetTemp.state) KITturnOn = false
	   if (BoilerOveride.state ==ON) KITturnOn = false
        if((new LocalTime().getLocalMillis()) >= (new LocalTime(23, 0, 0, 0).getLocalMillis()))  KITturnOn = false
        if((new LocalTime().getLocalMillis()) < (new LocalTime(7, 0, 0, 0).getLocalMillis()))   KITturnOn = false

        if(KITturnOn) MainBoiler.sendCommand(ON) 

}

end


rule "Boiler On - Living Room"

when
Item LVRWTSmaxSetTemp changed or
Item LVRWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then

if(MainBoiler.state == ON){
        var LVRturnOn = true
        if(LVRWTSmaxActual.state > LVRWTSmaxSetTemp.state) LVRturnOn = false
	   if (BoilerOveride.state ==ON) LVRturnOn = false
        if((new LocalTime().getLocalMillis()) >= (new LocalTime(23, 0, 0, 0).getLocalMillis()))  LVRturnOn = false
        if((new LocalTime().getLocalMillis()) < (new LocalTime(7, 0, 0, 0).getLocalMillis()))   LVRturnOn = false

        if(LVRturnOn) MainBoiler.sendCommand(ON) 

}

end

I think that should work, if I’ve understood your code, and the timing code I found is correct.

Persistance and Logs still don’t seem to function as should, but thats my weekend project.

I don’t understand what you think logging does. All it does is add a line to openhab.log when that line in the rule executes.

If you are seeing a lot statement every ten seconds it means your rule is executing every ten seconds. I would guess that your actual temps are updated that frequently.

It isn’t sending a new command every ten seconds because the code checks to see if, for example, the boiler is on it will not send another on command to it.

In your On rules you want to see if MainBoiler != ON. As written it can only ever send an ON command if the boiler is already on.

These are the logs: I have it logging to a seperate file “Boiler.log”

2017-02-11 19:15:32.783 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.783 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.783 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.784 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.784 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.788 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.792 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.816 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:32.823 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:33.422 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:33.515 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:33.629 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:33.715 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:33.723 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:34.815 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:34.918 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:35.015 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:35.114 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:15:35.122 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:17:01.447 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:17:31.541 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:18:31.734 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:19:31.964 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:20:00.045 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:20:02.053 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:22:32.574 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:25:00.043 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:25:03.074 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:26:03.281 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:28:33.788 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:30:00.037 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:30:04.097 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule
2017-02-11 19:31:04.298 [INFO ] [clipse.smarthome.model.script.Boiler] - The Boiler has been turned off via the Boiler Off rule

Well, like I said, the problem isn’t the logging, the “problem” is your rule is executing all of the time. This is to be expected. Every time and actual temp reading is updated, and any time any temp set is updated the rule will execute.

Use an if clause to only log if the boiler is actually being turned off. Also, only log or turn off the boiler if it happens to be on.

Thank you, I’ve tried something different with the log, so hopefully that works (added to the end of the code you gave me):

if((turnOff) logInfo( “Boiler”, "The Boiler has been turned off through the Boiler Off rule) ))

Final question, sorry, is there any easy was to say .state + 1? I want to only turn the boiler off when it’s 1, or maybe 0.5 above the set point rather than straight away. I’ve going to do the same with turning if off. So using the code you gave me (modified with the loginfo, could I do:

rule "Boiler Off -Room Check"
when
Item DSBWTSmaxSetTemp changed or
Item DSBWTSmaxActual received update or
Item DSKWTSmaxSetTemp changed or
Item DSKWTSmaxActual received update or
Item KITWTSmaxSetTemp changed or
Item KITWTSmaxActual received update or
Item LVRWTSmaxSetTemp changed or
Item LVRWTSmaxActual received update or
Time cron "0 0/5 * 1/1 * ? *"
then
if(MainBoiler.state == ON){
var turnOff = true
if(DSBWTSmaxActual.state < DSBWTSmaxSetTemp.state + 1) turnOff = false
if(KITWTSmaxActual.state < KITWTSmaxSetTemp.state + 1) turnOff = false
if(LVRWTSmaxActual.state < LVRWTSmaxSetTemp.state + 1) turnOff = false
if (BoilerOveride.state ==ON) turnOff = false

    if(turnOff) MainBoiler.sendCommand(OFF) 
if((turnOff) logInfo( "Boiler", "The Boiler has been turned off through the Boiler Off rule) ))

}
end

I think it will work as written. If not you may need to add ‘as Number’ after the ‘.state’.