Rule for changing temp and resume rule after power loss

Hi, I want to write a rule that be something like this:
set temp to 30c for 30 min
after that set temp to 60c for 30 min
after…
end
I think to create a setpoint rule and write another rule with timer sth like this:
setpoint.state == 30
createTimer(now.plusMinutes(30)) [|
setpoint.state == 60
createTimer(now.plusMinutes(30))

the setpoing obey setpoint rule…but
if I go with this kind of code, what can I do for resuming rule after power loss?
any suggestion for better implementation of this need?
thanks

I would suggest to persist the setpoint Item, when starting the system, you could start a rule triggered by system started, then take a look at the persisted state. I don’t know, if there is an easy way to see, when there was a power failure, though, But I’m pretty sure you could calculate some reaction.

In general, the rule would be something like this:
.items

Group gPersisted //setup Persistence for every change for group members

Number setpoint "Temp [%.1f °C]" (gPersisted) {...} 
Number oldValue "Old Setpoint [%.1f °C]" (gPersisted)
Switch myAutoHeat "AutoHeat is [%s]" (gPersisted)
rule "auto heat"
when
    Item myAutoHeat changed to ON
then
    oldValue.postUpdate((setpoint.state as DecimalType).floatValue)
    setpoint.sendCommand(30)
    createTimer(now.plusMinutes(30),[| //start first timer to change temperature to 60°C
        setpoint.sendCommand(60)
    ])
    createTimer(now.plusMinutes(60),[| //start second timer to change temperature to old value
        setpoint.sendCommand((oldValue.state as DecimalType).floatValue)
        AutoHeat.sendCommand(OFF) // switch off AutoHeat
    ])
end

rule "get AutoHeat"
when
    System started
then
    if(myAutoHeat.state == ON) {
        // recalculate remaining heat time and set timers accordingly
    }
end
2 Likes

You could use this as a starting point:

The only part that would be an exercise left to the student is figuring out how much time is left in the current activity when OH comes back up. The code above will restart the activity but do it for the full amount of time, not just the amount of time that might be left. (e.g. if OH went down 20 minutes into your 30 minutes at 30c it would restart the timer on that activity for a total of 50 minutes).

1 Like

tnx a lot, so each timer is a separate one?
I mean, consider first 30 mins finish and second 30 mins is on and 20mins of it passed…power loss happens
when it restart, it continue second timer? or it starts from first timer?
if it restart from second timer, we can live with that, but if it starts from the beginning of first timer, we have problem, if second option happens, any hint for student part to calculate time? Im new :grinning:
tnx agian

When openHAB is restarted (e.g. after power loss), every timer is lost. Timers are non-persistent, so you would have to calculate the remaining time for every timer and restart them.

2 Likes

Yes. You start an activity and set a timer to stop the activity and start the next activity. This causes a new timer to be created for that activity and so on until the end of the last activity is done.

Neighter. What happens is it restarts the timer that was running when OH went down. In your example, the second 30 mins will be restarted. Therefore, without modifications, the second activity will run for

20 mins + amount of time OH was offline + 30 mins

I explicitly wrote the design pattern to start from the second timer. If you want it to take into account how much time has passed when restarting the second Timer you will need to do more work.

So if you wanted to make the second timer only run for the amount of time that is left rather than the full 30 minutes it will get complicated but not impossible.

First of all, create StartTime Items for each activity and when the corresponding activity starts, update that StartTime Item to now.

Then, in your System started Rule, you need to look at the StartTime for the last activity that started, compare that with now to see how much time has passed, and subtract that amount of time from the runtime for the activity.

If the remaining runtime is negative then skip past the current activity and run the next one. If this is the last activity you are done. If it is positive, start the timer to stop the activity and kick off the next activity.

You will need your StartTime Items to be persisted just like the current activity Item is.

NOTE: Pay attention to the comments in the System started rule in the Design pattern that talks about what line to use based on whether you have persistence or just want everything to stop on an OH restart.

1 Like

Hi agian. tnx a lot and tnx for your time. I decided to go the easy way for now.
I create Item file like this:

Group All
Group gAttic (All)


Switch Relay1 "Relay" <switch> {mqtt=">[mosquitto:test/sonoff:command:ON:1],>[mosquitto:test/sonoff:command:OFF:0]"}

Number temperature "Temperature [%.1f °C]" <temperature> (All) { mqtt="<[mosquitto:openhab/temperature:state:default]" }
Number humidity "Humidity [%.1f %%]" <humidity> (All) { mqtt="<[mosquitto:openhab/humidity:state:default]" }
Number Setpoint "Setpoint [%.1f °C]" <degreesf>

String Irrigation_Curr "The current active zone is [%s]"
Switch Irrigation_Auto "Automatic irrigation is [%s]"
Switch Irrigation_Manual "Irrigation state [%s]"

Group:Switch:OR(ON,OFF) gIrrigation "Irrigation is currently [%s]"
Group:Number:SUM gIrrigation_Times "Total Irrigation Time is [%d mins]"

Switch Irrigation_Zone_1      (gIrrigation)
Number Irrigation_Zone_1_Time (gIrrigation_Times)

Switch Irrigation_Zone_2      (gIrrigation)
Number Irrigation_Zone_2_Time (gIrrigation_Times)

Switch Irrigation_Zone_3      (gIrrigation)
Number Irrigation_Zone_3_Time (gIrrigation_Times)

Switch Irrigation_Zone_4      (gIrrigation)
Number Irrigation_Zone_4_Time (gIrrigation_Times)

Switch Irrigation_Zone_5      (gIrrigation)
Number Irrigation_Zone_5_Time (gIrrigation_Times)

Switch Irrigation_Zone_6      (gIrrigation)
Number Irrigation_Zone_6_Time (gIrrigation_Times)

Switch Irrigation_Zone_7      (gIrrigation)
Number Irrigation_Zone_7_Time (gIrrigation_Times)

Switch Irrigation_Zone_8      (gIrrigation)
Number Irrigation_Zone_8_Time (gIrrigation_Times)

Switch Irrigation_Zone_9      (gIrrigation)
Number Irrigation_Zone_9_Time (gIrrigation_Times)

where and how can I determine time for each zone?! should I make item for them? confusing for time part

my rule that is your code of course and sth adds up is like this:

var Timer irrigationTimer = null

rule "Reset Irrigation at OH Start"
when
    System started
then
     // use this line if you have persistence and want to reset the timer cascade when OH comes back online
        sendCommand(Irrigation_Curr.state.toString, ON) // kicks off the cascade again on the last zone that was running
end

rule "Start Irrigation at 08:00"
when
    Item Irrigation_Manual received command ON
then
    if(Irrigation_Manual.state == ON){
        Irrigation_Manual.postUpdate(ON) // set it on if not already
        logInfo("Irrigation", "Irrigation started, turning on Zone 1")
        Irrigation_Curr.postUpdate(Irrigation_Zone_1.name)
        Irrigation_Zone_1.sendCommand(ON)
    }
end

rule "Irrigation Cascade"
when
    Item Irrigation_Zone_1 received command or
    Item Irrigation_Zone_2 received command or
    Item Irrigation_Zone_3 received command or
    Item Irrigation_Zone_4 received command or
    Item Irrigation_Zone_5 received command or
    Item Irrigation_Zone_6 received command or
    Item Irrigation_Zone_7 received command or
    Item Irrigation_Zone_8 received command or 
    Item Irrigation_Zone_9 received command
then
    // get info for the current valve
    val currValve = gIrrigation.members.filter[valve|valve.name == Irrigation_Curr.state.toString].head
    val currValveNum = Integer::parseInt(currValve.name.split("_").get(2))
    val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name"_Time"].head.state as Number

    // get info for the next valve in the sequence
    val nextValveNum = currValveNum + 1
    val nextValveName = Irrigation_Zone_+nextValveNum
    val nextValve = gIrrigation.members.filter[.valve|valve.name == nextValveName].head // null if there is no member by that name
    
    // Create a timer to turn off curr valve and start the next valve
    irrigationTimer = createTimer(now.plusMinutes(currValveMins), [|
        logInfo("Irrigation", "Turning off " + currValve.name)
        currValve.sendCommand(OFF)

        if(nextValve != null) {
            logInfo("Irrigation", "Turning on " + nextValve.name)
            Irrigation_Curr.postUpdate(nextValve.name)
            nextValve.sendCommand(ON) // causes the Irrigation Cascade rule to trigger
        }
        else {
            logInfo("Irrigation", "Irrigation is complete")
            Irrigation_Manual.sendCommand(OFF) // causes the cancel rule to trigger for cleanup
        }
        irrigationTimer = null
    ])
end

rule "Cancel Irrigation"
when
    Item Irrigation_Manual received command OFF    
then
    // Cancel the timer if there is one, the ? will cause the line to be skipped if timer is null
    timer?.cancel
    timer = null

    // Turn off any open valves
    gIrrigation.members.filter[valve|valve.state != OFF].forEach[valve| valve.sendCommand(OFF)]

    // Update curr status
    Irrigation_Curr.postUpdate("off")
end




rule "send setpoints"

when   
    Item  Irrigation_Manual changed to ON
then
	if(Irrigation_Zone_1.state == ON) {
	    Setpoint.sendCommand("60")
	}
	if(Irrigation_Zone_2.state == ON) {
	    Setpoint.sendCommand("65")
	}
	if(Irrigation_Zone_3.state == ON) {
	    Setpoint.sendCommand("70")
    }
	if(Irrigation_Zone_4.state == ON) {
	    Setpoint.sendCommand("75")
	}
	if(Irrigation_Zone_5.state == ON) {
	    Setpoint.sendCommand("80")
	}
	if(Irrigation_Zone_6.state == ON) {
	    Setpoint.sendCommand("85")
    } 
	if(Irrigation_Zone_7.state == ON) {
	    Setpoint.sendCommand("90")
	}
	if(Irrigation_Zone_8.state == ON) {
	    Setpoint.sendCommand("95")
	}
	if(Irrigation_Zone_9.state == ON) {
	    Setpoint.sendCommand("100")
    } 
	else {
	    /* Do Nothing */
    }
end

rule "Heater"
when
Item temperature received update or
Item Setpoint received update
then
// Turn on the Heater 
if((temperature.state as DecimalType)+1 > (Setpoint.state as DecimalType)) {
if(Relay1.state != OFF) Relay1.sendCommand(OFF)
}
else {
if(Relay1.state != ON) Relay1.sendCommand(ON)
}
end

as you can see at the end of the code I added two rule, one for sending setpoint item a number when each zone activate and one for controling temp based on setpoint temp
am I doing it right?
I want to start it manually, so I ignore automatic part, but i have some error in designer that searching does not solve them.
first one:

 sendCommand(Irrigation_Curr.state.toString, ON) // kicks off the cascade again on the last zone that was running

the error is:
Type mismatch: cannot convert from OnOffType to String
second is:

val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name"_Time"].head.state as Number

Type mismatch: cannot convert from (Item)=>String to Function1<? super Item, Boolean>
in currvalve.name section

third one:

val nextValveName = Irrigation_Zone_+nextValveNum

The method or field Irrigation_Zone_ is undefined
same for this line:

val nextValve = gIrrigation.members.filter[.valve|valve.name == nextValveName].head

The method or field valve is undefined for the type Iterable

the last two are these:

timer?.cancel
    timer = null

first:
The method or field timer is undefined
second:
The method timer(Object) is undefined
sorry for long post, im not familiar with java :blush:

That is what the items that end in “Time” are for. Put the amount of time for that zone in minutes into these items

Put the on in quotes:

"ON"

Put the Irrigation_Zone_ in quotes.

Remove the “.” From in front of the word valve.

Change timer to irrigationTimer

Rules are not written in Java. They are a special language invented just for openHAB

1 Like

thanks.
most of the error resolved. for currValveMins error, I put intValue in the end of it

irrigationTimer = createTimer(now.plusMinutes(currValveMins.intValue)

error disappeared. but this error is still on:

val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name"_Time"].head.state as Number

I test lots of implementation to set time!!!
replace with “_Time”, replace with “Time”, add “30”, add “30 min” …
sorry for basic questions:disappointed:
how this line should be?

another problem is when I tail the log to see what happens when start Irrigation_Manual switch
its got on and switch on the zone 1
perfectly…
but nothing happens about setpoint, I put this code for setpoints changing

when   
    Item  Irrigation_Manual changed to ON
then
	if(Irrigation_Zone_1.state == ON) {
	    setpoint.sendCommand("60")
	}
	if(Irrigation_Zone_2.state == ON) {
	    setpoint.sendCommand("65")
	}
	if(Irrigation_Zone_3.state == ON) {
	    setpoint.sendCommand("70")
    }
	if(Irrigation_Zone_4.state == ON) {
	    setpoint.sendCommand("75")
	}
	if(Irrigation_Zone_5.state == ON) {
	    setpoint.sendCommand("80")
	}
	if(Irrigation_Zone_6.state == ON) {
	    setpoint.sendCommand("85")
    } 
	if(Irrigation_Zone_7.state == ON) {
	    setpoint.sendCommand("90")
	}
	if(Irrigation_Zone_8.state == ON) {
	    setpoint.sendCommand("95")
	}
	if(Irrigation_Zone_9.state == ON) {
	    setpoint.sendCommand("100")
    } 
	else {
	    /* Do Nothing */
    }
end

can you help me with this?

val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name+"_Time"].head.state as Number

At this point, I’m going to recommend that you step back and start to experiment with the rules, read the documentation on Rules, and gain some more experience with Rules. All of these errors should have been obvious and easy to detect and fix with just a little bit of experience.

Case matters. You defined the Item as “Setpoint”, not “setpoint”.

1 Like

tnx I will read them but have to do this quick because of that, im just copying code and asking question :disappointed:
with that line, no error in rule
but dont know how to define specific time in this line

Number Irrigation_Zone_1_Time (gIrrigation_Times)

change setpoint to lower case for all but logs are like this:

You have to put it on your sitemap and use a slider or setpoint to set it.

Or you can create a System started rule and postUpdate the times for each of the Time Items.

1 Like

Hello agian, Im still struggling with the code
I tested with a few zone and setpoint in System started and every thing works.
after I added more zones and setpoint, seems to have problem resuming after power loss.
it restarts the last zone, but not continue.
do you know what cause this
Im a little bit thinking about mapdb loading zones and setpoints and the last curr, maybe the order of the loading causes problem

Post your code.

If it is a timing problem, add a Thread:sleep(500) to the system stayed rule to give restoreOnStartup time to finish.

1 Like

tnx, it is long! It must have better way to implement setpoints. here is the code:/ code cant post because of character limit. this is rule file:
http://www.mediafire.com/file/xq31q9d0aelqout/home.rules

OK, you have A LOT of zones. Here are some ideas to consolidate this code a bit.

Some questions, comments, and suggestions:

  • Why are you initializing the Time Items in the System started rule? At the most this should only have to be done once, the very first time the Item is added to OH to give it an initial value. Then use restoreOnStartup and these Items will forever have a value. So if you are using restoreOnStartup you can eliminate all the sendCommand lines that populate the Time and setpoint Items.

  • Take advantage of Groups. I see only two Times being used to initialze the Time Items, 1 and 60. If you can’t eliminate the sendCommands from the System started rule, at least use Groups to cut out the massive lines of code. Create a gOneMinute group and a gSixtyMinute group and add the Time Items to the appropriate group. Then replace all the sendCommand calls for the Time Items with:

gOneMinute.members.forEach[time|time.sendCommand(1)]
gSixtyMinute.members.forEach[time|time.sendCommand(60)]

That will save hundreds of lines of code by itself. Though I want to reemphasize that once these Items have a value you can remove these lines entirely from the rule.

  • At some point you will probably want to change the names and comments in this code to more closely match what you are doing. I’m pretty certain you are not running irrigation on your garden. The name “setpoint” is not very informative. Instead use a name that tells you what specific device the setpoint represents (e.g. “MasterBedroom_East_Heat” instead of “setpoint4_35”).

  • I’m not sure if it is allowed to have more than one rule with the same name. Make sure that names of your rules (stuff in " " after the word “rule” are unique and meaningful (e.g. the rule “Start Irrigation at 08:00” is not controlling irrigation and not starting at 08:00 so not only is this rule name repeated but it no longer makes sense.

  • I do not have a good solution right now and it is more important to solve your current problem, but with this many zones it would be worth the time in the long run to come up with a different approach, perhaps using JSR223.

To address the specific problem you raise, I wonder if the number of Items is causing the initial startup of OH to slow down a bit. Move the Thread::sleep(2000) near the bottom of the System started rule to be the very first line of that rule. Try increasing the sleep to 10000.

If that doesn’t work watch events.log and openhab.log when OH starts and see if you can figure out the order everything is initializing. Add log statements to the System started rule to see what state things are in before sending the commands to restart the zones.

1 Like

Another question… what’s the goal of this massive amount of code? Maybe you think to complex… Is this sort of manual sequential logic system?

1 Like

thanks, sorry for the delay. let me explain the whole process.
we have a room, our process is 128 hour. In each hour we want to set the temperature at a certain number, control done by heater.
our temp number are different, from 60 to 245 degrees of C.
so we will have 128 zone for each 60 mins(I put 1 min just for testing)
So, I have done the two first point that you mentioned.
first put them in group

gSixtyMinute.members.forEach[time|time.sendCommand(60)]

with this and run for the first time, delete it, so with restoreOnStartup, openhab2 loads them.
it save around 500 line of code.

I also changes the rules name to diffrent one for each of them.
next, I Put sleep at the end of first system started rule

sendCommand(Irrigation_Curr3.state.toString, "ON") // kicks off the cascade again on the last zone that was running
        sendCommand(Irrigation_Curr4.state.toString, "ON") // kicks off the cascade again on the last zone that was running
        Thread::sleep(5000)

but same problem happens, after restart, it detect last curr and turn On it, but not continue
the only theory I have is it loads the curr before and in the middle of restoreOnStartup items


if it loads after them, I think it will works!!! I dont know for sure.


the big amount of our code because of the temp setpoints. if I can somehow change the code, it will be huge reveal!
I tried this:
use only one setpoint Item and this code

rule "setpoint"

when   
    Item  Irrigation_Manual received command ON
then
    if(Irrigation_Zone_1.state == ON) {
        setpoint.postUpdate("60")
    }
    if(Irrigation_Zone_2.state == ON) {
        setpoint.postUpdate("60")
    }
    if(Irrigation_Zone_3.state == ON) {
        setpoint.postUpdate(75)
    }
    if(Irrigation_Zone_4.state == ON) {
        setpoint.postUpdate(80) 
 }
 else {
        /* Do Nothing */
    }
end

but it does not work. it doesnt send setpoint that number. I used sendCommand, same things happens. I use “” and without “”, dont know which one is correct, but none of them works

one of the other problems that happens is when Irrigation_Manual changed to On, zone 1 doesnt starts! number of tries solve the problem.

the delay in answering is because we are seeking other option, maybe mixed use of openhab and arduino but use only openhab is our ultimate goal.
thanks

thanks. I explain in the above post the process.
one of the big problem that cause this huge code is the setpoint part that
setpoint.postUpdate(“60”)
does not work or sendCommand

All names are case sensitive, so setpoint and Setpoint are different items. Your items definition is

Number Setpoint "Setpoint [%.1f °C]" <degreesf>

so you have to use

Setpoint.sendCommand(60)

to set the item state of Setpoint to 60.

In fact, as your item is an unbound one, it doesn’t really matter if using sendCommand() or postUpdate(), the only thing is, if there is a rule which should trigger when item Setpoint gets a command or an update, trigger and method have to fit :slight_smile:

Another thing… Setpoint is a key word in sitemaps. Although openHAB is not confused by this, I would strongly recommend to rename your item to clarify the difference (e.g. nSetpoint for Numberitem)

2 Likes