Rule for changing temp and resume rule after power loss

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

thanks, they were both lower case, but I change them to Numberitem

Number Numberitem "Numberitem [%.1f °C]" <temperature>
rule "sets"

when   
    Item  Irrigation_Manual received command ON
then
    if(Irrigation_Zone_1.state == ON) {
        Numberitem.sendCommand(60)
    }
    if(Irrigation_Zone_2.state == ON) {
        Numberitem.sendCommand(70)
    }
    if(Irrigation_Zone_3.state == ON) {
        Numberitem.sendCommand(80)
    }
    if(Irrigation_Zone_4.state == ON) {
        Numberitem.sendCommand(86) 
 }
 else {
        /* Do Nothing */
    }
end

after turn on Irrigation_Manual the zone 1 automatically turn on and no set Numberitem comes as the picture of my log above shows.
I want use this number use in other rule for controlling heater.
what do you man about:trigger and method have to fit

Are you sure the rule gets triggered at all? Please use logInfo to ensure, the rule (and the conditional branch) are working as expected:

rule "sets"
when   
    Item  Irrigation_Manual received command ON
then
    logInfo("sets","Rule triggered")
    if(Irrigation_Zone_1.state == ON) {
        Numberitem.sendCommand(60)
        logInfo("sets","Zone_1 ON")
    }
    if(Irrigation_Zone_2.state == ON) {
        Numberitem.sendCommand(70)
        logInfo("sets","Zone_2 ON")
    }
    if(Irrigation_Zone_3.state == ON) {
        Numberitem.sendCommand(80)
        logInfo("sets","Zone_3 ON")
    }
    if(Irrigation_Zone_4.state == ON) {
        Numberitem.sendCommand(86) 
         logInfo("sets","Zone_4 ON")
}
 else {
        logInfo("sets","All Zones OFF")
        /* Do Nothing */
    }
end

This messages should appear in openhab.log

1 Like

This is a case where knowing what you are trying to accomplish can change the answer.

The first thing I’ll say is it sounds like you are trying to use OH as some sort of industrial control manager. It wasn’t designed for this and does not have the degree of reliability nor safety mechanism I would trust with an industrial process, especially one that is operating at such high temperatures.

If I assume from your statement that each “zone” will always be an hour then this cascading timers approach may not the best way to solve this problem.

To start:

  • Is my assumption correct and each time period is always 60 minutes?

  • Do the temperatures change based on some formula? For example, do they go up by a certain amount or a certain percentage until the halfway point and then ramp down in the same manner?

  • Do you ever have to cancel the process?

We cannot control when the Curr Items get restored so if your theory is correct, what needs to be done is to set a lockout flag that prevents any of the rules from running until they are all restored. But before we go down that path, answer the above questions because I don’t think this is the best approach any longer.

1 Like

Thanks to @rlkoshak and @Udo_Hartmann yes, we built a wood drier and wants to dry wood with these process.
60 minutes are correct but it follows several formula during the process, it follows 6 different formula during process.
yes cancel is an option for mistake in starting process.


last week we had to start the drier, so use other approach but it is uncompleted, I use arrays to reduce lines of code

Number tm "[%d]" <temperature> (All) { mqtt="<[mosquitto:openhab/tm:state:default]" }
Number goaltemp "[%d]" <temperature>
val int[] theArray = newArrayList(34, 35, 36, 37, 38, 39)

rule "run1"
when
    Item rsoozani_s received command ON or 
    Item tm received update
then
   goaltemp.sendCommand(theArray.get((tm.state as Number).intValue))
    
if(soozani_s.state == ON)  {
	
if((t.state as DecimalType)+1 > (goaltemp.state as DecimalType)) {
if(Heater.state != OFF) Heater.sendCommand(OFF)
}
else {
if(Heater.state != ON) Heater.sendCommand(ON)
}
	
}

so I have a goaltemp in item.
and i have tm that comes from esp8266, it sends numbers every 60 mins(first 60 mins= 1, second 60 mins=2 and so on)
with this code in esp8266
int tm=(millis()/3600000);
char* tmPayload = f2s(tm,0);
Serial.println™;
Serial.println(tmPayload);
client.publish(tmTopic, tmPayload);

so, when tm is 1, theArray.get get the first goal temp and set it. when tm is 2 …
millis start every time i press the process starts
that works…
now I want to do two things.
one delete esp8266 part and do this tm in rules. when a process being started, a millis begins and set tm a number every 60 mins, like what I did in esp8266
second is restore and resuming the process based on that tm number.

OK, I think a simplified version of the cascading timers approach may work but it might be simpler to use the Cancel Activity Design Pattern.

If I understand your problem (I had guessed you were running a wood drying kiln, drying lumber or blanks for turning?) I think something like the following will work. Note, again I’m just typing this in, it likely has errors.

Preconditions:

  • make sure you have persistence set up with restoreOnStartup

Items:

Number GoalTemp "Goal Temp [%d]" <temperature>
Number ProcessStep "Process Step [%d]"
Switch ProcessTimer { expire="60m,command=OFF" }
String ControlProcess "Control drying process"

Rules:

val targetTemps = newArrayList(34, 35, 36, 37, 38, 39, ...) // enter the target temps for each step of the process

rule "Control the process"
when
    Item ControlProcess received command
then
    switch receivedCommand.toString {

        case "Start": ProcessStep.sendCommand(0)

        case "Pause": {
            Heater.sendCommand(OFF) // turn off the heater
            ProcessTimer.postUpdate(OFF) // cancel the timer
        }

        // Note: Resume will run the current step the full 60 minutes regardless of how much time in the step was run before the pause
        case "Resume":  ProcessStep.sendCommand(ProcessStep.state) // resume the process by sending the current state again

        case "Stop": ProcessStep.sendCommand(-1) // stop the process by sending the end state
    }
end

rule "A step has completed"
when
    Item ProcessTimer received command OFF
then
    val currStep = ProcessStep.state as Number
    val nextStep = currStep + 1

    // if nextStep is the size of the array of target temps we have finished the last step
    if(nextStep == targetTemps.size) ProcessStep.sendCommand(-1)
    else ProcessStep.sendCommand(nextStep)
end

rule "Transition to new step"
when
    Item ProcessStep received command
then
    val currStep = receivedCommand as Number

    // Turn off the heater if we were cancelled, reach the last step, or the curr step is greater than the total number of steps
    if(currStep == -1 || currStep >= numSteps) {
        Heater.sendCommand(OFF)
        ProcessTimer.postUpdate(OFF)
    }

    // Transition to the next step, set the GoalTemp from the array, turn on the heater if it isn't already, and start the timer
    else {
        GoalTemp.sendCommand(targetTemps.get(currStep))
        if(Heater.state != ON) Heater.sendCommand(ON)
        ProcessTimer.sendCommand(ON)
    }
end

rule "Resume the process if it was running when OH went down"
when
    System started
then
    Thread:sleep(1000) // give everything time to be restored from persistence

    // If the Timer was on the process was running, restart the current step
    if(ProcessTimer.state == ON)  ProcessStep.sendCommand(ProcessStep.state)

    // If the process wasn't running, make sure the Heater is OFF
    else Heater.sendCommand(OFF)
end

Sitemap:

    Switch item=ControlProcess mappings=[Start=Start,Pause=Pause,Resume=Resume,Stop=Stop]

This will put a line on your sitemap with four buttons labeled Start, Pause, Resume, and Stop. You can get clever using the visibility Flag to swap out the Pause and Resume buttons based on whether the process is running or not.

While this approach resuses some of the same concepts as the cascading timers, I think it has become simplified enough now to avoid the problems you experienced last time.

You will probably want to add a bunch of log statements to watch it go through the steps while you test it.

I hope it works for you!

1 Like

tnx :slightly_smiling_face:
yes it is a wood drying kiln.
just change

ProcessStep.sendCommand(ProcessStep.state)

to

ProcessStep.postUpdate(ProcessStep.state)

it has error and now fixed.
I have only one error left

GoalTemp.sendCommand(targetTemps.get(nextStep))

the nextStep part has error:
The method or field nextStep is undefined

edit:
change this part of code and add val nextStep and change get array part and it seems problem solves! im going to test it :slightly_smiling_face:

rule "Transition to new step"
when
    Item ProcessStep received command
then
    val currStep = receivedCommand as Number
    val nextStep = (currStep + 1) 

    // Turn off the heater if we were cancelled, reach the last step, or the temperatures step is greater than the total number of steps
    if(currStep == -1 || currStep >= 7) {
        Heater.sendCommand(OFF)
        ProcessTimer.postUpdate(OFF)
    }

    // Transition to the next step, set the GoalTemp from the array, turn on the heater if it isn't already, and start the timer
    else {
        GoalTemp.sendCommand(targetTemps.get((nextStep).intValue))
       // if(Heater.state != ON) Heater.sendCommand(ON)
        ProcessTimer.sendCommand(ON)
        if((t.state as DecimalType)+2 > (GoalTemp.state as DecimalType)) {
        if(Heater.state != OFF) Heater.sendCommand(OFF)
    }
       else {
       if(Heater.state != ON) Heater.sendCommand(ON)
    }
}
end

I test it, for testing change 60m to 60s …after a min processtimer does not turn off…
is it because of what I did with nextStep cause the problem? now I have two val nextStep!
tnx

You have to use sendCommand on ProcessStep or else the rule “Transition to new step” won’t trigger. What was the error?

1 Like

tnx

ProcessStep.sendCommand(ProcessStep.state)

error:
Type mismatch: cannot convert from State to String

GoalTemp.sendCommand(targetTemps.get(nextStep))

error:
The method or field nextStep is undefined

@rlkoshak
change the ProcessStep.state to

ProcessStep.sendCommand(ProcessStep.state.toString)

solve this error, is this Ok?
nextStep warning is still on the table

That is what I was going to suggest to fix the String error.

I need to get to a computer to look at the other error. I wonder if it doesn’t like that nextStep isn’t explicitly an int.

Try using nextStep.intValue

Sometimes the rules engine complains about things not existing when the real problem is it can’t cover it to the right type.

1 Like

Doh! Change the line to:

GoalTemp.sendCommand(targetTemps.get(currStep)

That was a silly mistake on my part.

1 Like