To expand on @gpbenton’s solution:
Items updates:
Create a Group to put all your Irrigation_X Switches into. I’ll call it gIrrigation. Create another Group to put your Times into. I’ll call it gIrrigationTime. Also, I like to keep state in Items so add the following one:
String Irrigation_Curr "The current active valve is [%s]"
import org.openhab.model.script.actions.*
import org.openhab.core.library.types.*
val Timer irrigationTimer = null
// If you do not have persistence set up use this rule to turn everything off
rule "System started, make sure everything is off"
when
System started
then
gIrrigation.members.forEach[v | if(v.state != OFF) v.sendCommand(OFF)
end
// If you do have persistence and it saves Strings (i.e. not rrd4j) use this rule to restart the cascade
rule "System started, restart the cascade"
when
System started
then
sendCommand(Irrigation_Curr.state.toString, ON) // kicks off the cascade rule to recreate the timer
// does not take into account how long the valve has already been on
end
// If you have persistence and it does not save Strings use this rule
rule "System started"
when
System started
then
val curr = gIrrigation.members.filter[v|v.state == ON].head // assumes no more than one ON at a time
Irrigation_Curr.sendCommand(curr.name)
curr.sendCommand(ON) // retrigger the cascade rule to recreate the timer
// does not take into account how long it has already been running
end
rule "Start Irrigation"
when
Time cron "0 0 8 * * ?"
then
if(Irrigation_Auto.state == ON || Irrigation_Now.state == ON){
logInfo("Irrigation", "Irrigation started, turning on Irrigation_1")
Irrigation_Curr.sendCommand(Irrigation_1.name)
Irrigation_1.sendCommand(ON)
}
end
rule "Irrigation switch cascade"
when
Item Irrigation_1 received command ON or
Item Irrigation_2 received command ON or
Item Irrigation_3 received command ON or
...
then
// Get the currently running valve
val currValveName = Irrigation_Curr.state.toString
val currValveSwitch = gIrrigation.members.filter[i|i.name == currValveName].head
val currValveNum = Integer::parseInt(currValveName.split("_").get(1))
val currValveMins = gIrrigationTime.members.filter[t|t.name == currValveName+"_Time"].head.state as DecimalType
// Get the next running valve in the sequence
val nextValveNum = currValveNum + 1
val nextValveName = "Irrigation_" + currValveNum
val nextValveSwitch = gIrrigation.members.filter[i|i.name == nextValveName].head // will be null if not found
// Create a timer to turn off curr and turn on next
irrigationTimer = createTimer0(now.plusMinutes(currValveMins), [|
logInfo("Irrigation", "Turing off " + currValveName)
currValveSwitch.sendCommand(OFF)
if(nextValveSwitch != null) {
logInfo("Irrigation", "Turing on " + nextValve.name)
Irrigation_Curr.sendCommand(nextValveName)
nextValveSwitch.sendCommand(ON)
}
else{
logInfo("Irrigation", "Irrigation is complete")
}
irrigationTimer = null
])
end
rule "Cancel Irrigation"
when
Item Irrigation_Stop received command ON
then
// Cancel any timers
if(irrigationTimer != null && !irrigationTimer.hasTerminated){
irrigationTimer.cancel
}
irrigationTimer = null
// Turn off any open valves
gIrrigation.members.forEach[v | if(v.state != OFF) v.sendCommand(OFF) ]
// Update the status
Irrigation_Curr.sendCommand("None")
end
Theory of Operation:
When the Irrigation is turned on Irrigation_1 is turned ON and Irrigation_Curr is updated with the value Irrigation_1.
This triggers the cascade rule. This rule looks at Irrigation_Curr to see which valve is currently ON and based on its name constructs the name of the next valve and grabs them both out of gIrrigation. It also grabs the number of minutes this valve is to remain ON so it sets the timer appropriately. (NOTE: You don’t need Irrigation_Curr, you could get the current valve using gIrrigation.members.filter[v|v.state == ON].head
since only one valve should be on at a time but I thought having something to put on the sitemap would be nice to have).
Armed with a reference to the current and next valve switches and how long the current is supposed to run it creates a timer which, when it goes off turns off the current valve and turns on the next valve. Before turning on the next one it updates Irrigation_Curr. When the ON command is sent, the cascade rule will trigger again, now for the next valve in the sequence.
When the Irrigation_Stop Item receives an ON command, any running Timers are canceled, and open valves are closed, and Irrigation_Curr is reset to indicate nothing is running.
NOTES:
- You need to keep a reference to your timers to be able to cancel them
- You cannot cancel a triggered Timer (i.e. if you have a long running Timer you can’t easily stop it
- I just typed in the above, it almost certainly has errors
- The sendCommand method on the Item works better than the sendCommand action. I recommend using it where ever possible.
- Your naming scheme must be sequential (i.e. Irrigation_1, Irrigation_2, etc). If you skip one more numbers additional logic needs to be added to the cascade rule. If you want to turn one valve off, set its Time to 0 which I think will cause the Timer to immediately execute. If not you need to add some more logic to handle it.
- You will probably want to add some error checking for null throughout while debugging, additional logging is needed as well
- We can’t trigger on the gIrrigations group because we would need to trigger on updates and a group received multiple updates per single command to one of its members. We could add some checks to maybe work around this but I prefer to be really specific when dealing with running water.