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!