[SOLVED] OpenSprinkler Irrigation and Cascading timers rule

Tags: #<Tag:0x00007f61710661f0>

******** See working example at the end of this thread **********

Hi,

I am currently running the latest stable image and am having some trouble successfully getting cascading timers to operate.

I have based my rules on the Cascading timers design pattern (Design Pattern: Cascading Timers).

When acting the item “Irrigation_Auto” or an individual zone (all zones have the setpoint at 2mins) I get the following error in openhab.log

[INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.items.Item.getState() on null

Zone 1 turns on as expected, however Zone 1 never turns off and no other zones are activated.

Turning on each zone manually works fine as well as turning off.

Is anyone able to offer some advice on what may be causing this issue?

Thanks.

I have the following configuration:

Items

Group  			gIrriAll					"All Irrigation Devices"			
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			"Side Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station01" }
Switch  		Irrigation_Zone_2			"Front Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station02" }
Switch  		Irrigation_Zone_3			"Veggie Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station03" }
Switch  		Irrigation_Zone_4			"Back Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station04" }
String  		Irrigation_Curr				"Current active zone is [%s]"							(gIrriAll)
Switch  		Irrigation_Auto				"Irrigation Auto"										(gIrriAll)
Switch 			Irrigation_Manual 			"Irrigation state [%s]"									(gIrriAll)
Number  		Irrigation_Zone_1_Time 		"Side Garden duration [%d mins]"						(gIrrigationTime, gIrriAll)
Number  		Irrigation_Zone_2_Time 		"Front Garden duration [%d mins]"						(gIrrigationTime, gIrriAll)
Number  		Irrigation_Zone_3_Time 		"Veggie Garden duration [%d mins]"						(gIrrigationTime, gIrriAll)
Number  		Irrigation_Zone_4_Time 		"Back Garden duration [%d mins]"						(gIrrigationTime, gIrriAll)

Sitemap

Frame {
    Group item=gIrriAll		label="Irrgiation Test"		icon="water_drops"
	Switch item=gIrrigation
	Text item=gIrrigation_Times
    Setpoint item=Irrigation_Zone_1_Time minValue=1 maxValue=30 step=1
    Setpoint item=Irrigation_Zone_2_Time minValue=1 maxValue=30 step=1
    Setpoint item=Irrigation_Zone_3_Time minValue=1 maxValue=30 step=1
	Setpoint item=Irrigation_Zone_4_Time minValue=1 maxValue=30 step=1
  }

Rule

var Timer irrigationTimer = null

rule "Reset Irrigation at OH Start"
when
    System started
then
    // use this line to just turn everything off if OH happens to restart when irrigation is running
    gIrrigation.members.filter[valve|valve.state != OFF].forEach[valve| valve.sendCommand(OFF)]
end

rule "Start Irrigation"
when
    Item Irrigation_Auto received command ON
then
	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 ON or
    Item Irrigation_Zone_2 received command ON or
    Item Irrigation_Zone_3 received command ON or
    Item Irrigation_Zone_4 received command ON 
    

then
    // get info for the current valve
    val currValve = gIrrigation.members.filter[valve|valve.name == Irrigation_Curr.state.toString].head
    //logInfo("Irrigation", "currValue: " + currValve.name)
    val currValveNum = Integer::parseInt(currValve.name.split("_").get(2))
    //logInfo("Irrigation", "currValveNum: " + currValveNum)
    val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name+"_Time"].head.state as Number
	logInfo("Irrigation", "irrigation active for : " + currValveMins + " mins")

    // 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.intValue), [|
        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_Auto.sendCommand(OFF) // causes the cancel rule to trigger for cleanup
        }
        irrigationTimer = null
    ])
end


rule "Cancel Irrigation"
when
    Item Irrigation_Auto received command OFF    
then
    // Cancel the timer if there is one, the ? will cause the line to be skipped if timer is null
    irrigationTimer?.cancel
    irrigationTimer = 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

Event log included for completeness:

[ome.event.ItemCommandEvent] - Item 'Irrigation_Auto' received command ON
[vent.ItemStateChangedEvent] - Irrigation_Auto changed from OFF to ON
[ome.event.ItemCommandEvent] - Item 'Irrigation_Zone_1' received command ON
[nt.ItemStatePredictedEvent] - Irrigation_Zone_1 predicted to become ON
[vent.ItemStateChangedEvent] - Irrigation_Curr changed from OFF to Irrigation_Zone_1
[vent.ItemStateChangedEvent] - Irrigation_Zone_1 changed from OFF to ON
 [GroupItemStateChangedEvent] - gIrrigation changed from OFF to ON through Irrigation_Zone_1

That error almost always means a type error which almost always means you have an Item that is unexpectedly NULL.

But in this case it could also mean that the filter is returning no matches (i.e. currValve is null).

Uncomment those log statements at the top of the Rule to see which line it’s failing on. Add some more logging as well to determine what the values of all the cur and next variables get set to.

I’m going to guess that it’s failing on the line for currValveMins because that’s the only place we call .state and if that Item is NULL or UNDEF the as Number would generate an error like that.

And one final note, this code is old. I really need to rewrite that DP. There are tons of improvements that can be made here given the improvements since OH 2.1 which is when I think I wrote it. The over all logic is sound. But adding tests for NULL and UNDEF, using the Member of Rule trigger, use findFirst instead of filter[ ].head, and stuff like that.

Thanks Rich,

I have enabled the extra logging and I am now getting the following:

[INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValue: Irrigation_Zone_1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveNum: 1
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.items.Item.getState() on null

I think this suggests that your guess is correct and the error is in the following line:

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

I tried changing it to:

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

This changes the error to:

[ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': 'head' is not a member of 'org.eclipse.smarthome.core.items.Item'; line 34, column 25, length 76

Correcting for this with the following:

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

Returns us to the original error.

It also proves that while I can follow the logic I don’t really understand the how and why. An issue I am working to correct.

Any other suggestions?

If you use find first you don’t need the call to head. filter returns a List and head gets the first item in the list.

findFirst returns the first item that matches the critera.

Go back to the original but get rid of the “as Number” and log the value of currValveMins. It either will be null or its state will be NULL or UNDEF.

Thanks

I have changed the line to:

val currValveMins = gIrrigation_Times.members.filter[t|t.name == currValve.name+"_Time"].head.state
logInfo("Irrigation", "irrigation active for : " + currValveMins + " mins")

There was already a log line for "currValveMins " following.
This appears to throw the same error before reaching the log line, as such it doesnt appear to be defining anything.

[INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValue: Irrigation_Zone_1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveNum: 1
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.items.Item.getState() on null

If i am understanding this right it appears that "currValveMins " is never actually being defined as anything and fails out as it is NULL.

I did try to preempt this by actually defining currValveMins as null at the top of the rule and log add an extra log line in before the line in question.

var currValveMins = null 
[INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'Irrigation.rules'
[INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValue: Irrigation_Zone_1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveNum: 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveMins original state: null mins
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.items.Item.getState() on null

There are two possibilities:

  1. The Item currValve.name+"_Time" does not exist in which case .head will be null
  2. The Item currValve.name+"_Time" has a state that is not a Number, i.e. NULL or UNDEF

The fact that it is throwing the error before the log statement points to 1. currValve.name doesn’t exist.

So this means Irrigation_Zone_1_Time doesn’t exist or it doesn’t exist in gIrrigation_Times.

And when I look at the definition for Irrigation_Zone_1_Time I see it is a member of gIrrigation_Time, not gIrrigation_Times. There’s your problem. Fix the Group on the Time Items and this problem at least will be fixed.

Or wait until later today when I’ll have a chance to rewrite the cascading timers DP.

Wait, I already have rewritten that DP. When did you copy the code for this? It is definitely the old version. The latest version from last June fixes all the stuff I mentioned above. Definitely look at the latest version as a guide.

Thanks Rich,

You are correct, I thought I was being clever and had used the example from the post below yours in the DP which suggested that it had fixed the issue.

I have now moved to your new example as suggested. This now gives a different error:

[INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 1
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': java.lang.String cannot be cast to java.lang.Boolean

The code(with extra logging) is:

    // get info for the current valve
    val currValve = gIrrigation.members.findFirst[ valve | valve.name == Irrigation_Curr.state.toString]
    logInfo("Irrigation", "currValve is " + currValve.name)
	val currValveNum = Integer::parseInt(currValve.name.split("_").get(2))
    logInfo("Irrigation", "currValveNum is " + currValveNum)
	val currValveMins = gIrrigation_Times.members.findFirst[ t | t.name == currValve.name"_Time"].state as Number
	logInfo("Irrigation", "currValveMins is " + currValveMins)

The first zone is turned on, but never turns off.
I have also checked that each item is in the correct group - gIrrigation_Times (you were correct that this was previously wrong.

I am not sure but appears that the issue is back in the same line.

.
.
.

There also appears to be an issue in the DP in the “Cancel Irrigation” section.

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

When this is activated while there is a timer it gives the error:

 Rule 'Cancel Irrigation': The name 'timer' cannot be resolved to an item or type; line 67, column 5, length 5

The var Timer is defined at the top of the rule changing timer to Timer gives the following:

Rule 'Cancel Irrigation': 'cancel' is not a member of 'java.lang.Class<org.eclipse.smarthome.model.script.actions.Timer>'; line 67, column 5, length 13

At the top it’s defined as irrigationTimer. Change to that.

irrigationTimer?.cancel
irrigationTimer = null

That is an error in the DP which I’ll fix right now.

This is a really odd one that doesn’t make a whole lot of sense not matter how I look at it. I do know that strange errors can occur when you don’t have a space between the code in the lambda and the closing ]. Try adding one there.

OH! :tired_face: It’ missing a +.

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

Add a space before the closing ] as well. Sometimes that can cause problems too.

Yup, you were right, both of those progressed us a lot.

I also had to modify this line adding the "

val nextValveName = "Irrigation_Zone_"+nextValveNum

I feel we are getting close now…

Although we have hit another bump.

[INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 1
[INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
[INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 2
[INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_2
[INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_2
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': An error occurred during the script execution: Could not invoke method: org.joda.time.DateTime.plusMinutes(int) on instance: 2019-02-02T18:37:31.475+11:00

Looks like its occurring here:

    // 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)

After a bit of searching I also tried the following with the same result:

    // 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)

I’ve never seen that error before.

Try calling intValue on currValveMins on that line.

currValveMins.intValue

Thanks, that corrected that issue and after clearing the cache and restarting OpenHAB to rule mostly runs correctly.

It now appears that the cancel rule is not being called correctly. In my case I have 4 valves, when valve it tries to call valve 5 and then appears to error out on null.

2019-02-03 13:11:24.718 [INFO ] [se.smarthome.model.script.Irrigation] - Irrigation started, turning on Zone 1
2019-02-03 13:11:24.721 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_1
2019-02-03 13:11:24.721 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 1
2019-02-03 13:11:24.722 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:11:24.723 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 2
2019-02-03 13:11:24.723 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_2
2019-02-03 13:11:24.724 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_2
2019-02-03 13:12:24.726 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_1
2019-02-03 13:12:24.728 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_2
2019-02-03 13:12:24.731 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_1
2019-02-03 13:12:24.731 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 1
2019-02-03 13:12:24.732 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:12:24.733 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 2
2019-02-03 13:12:24.733 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_2
2019-02-03 13:12:24.734 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_2
2019-02-03 13:12:24.736 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_2
2019-02-03 13:12:24.736 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 2
2019-02-03 13:12:24.737 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:12:24.738 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 3
2019-02-03 13:12:24.738 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_3
2019-02-03 13:12:24.739 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_3
2019-02-03 13:13:24.736 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_1
2019-02-03 13:13:24.738 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_2
2019-02-03 13:13:24.742 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_2
2019-02-03 13:13:24.742 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_2
2019-02-03 13:13:24.743 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_3
2019-02-03 13:13:24.743 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 2
2019-02-03 13:13:24.746 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_2
2019-02-03 13:13:24.747 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 2
2019-02-03 13:13:24.747 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_2
2019-02-03 13:13:24.747 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 2
2019-02-03 13:13:24.748 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:13:24.748 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 3
2019-02-03 13:13:24.748 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_3
2019-02-03 13:13:24.749 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_3
2019-02-03 13:13:24.749 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_2
2019-02-03 13:13:24.748 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:13:24.750 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 3
2019-02-03 13:13:24.751 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_3
2019-02-03 13:13:24.751 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_3
2019-02-03 13:13:24.750 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 2
2019-02-03 13:13:24.752 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:13:24.756 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 3
2019-02-03 13:13:24.756 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:13:24.756 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_3
2019-02-03 13:13:24.757 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_3
2019-02-03 13:13:24.762 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 3
2019-02-03 13:13:24.763 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_3
2019-02-03 13:13:24.764 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_3
2019-02-03 13:14:24.751 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_2
2019-02-03 13:14:24.751 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_2
2019-02-03 13:14:24.754 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_3
2019-02-03 13:14:24.756 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_3
2019-02-03 13:14:24.758 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.758 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.759 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.759 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_2
2019-02-03 13:14:24.760 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.761 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.761 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_3
2019-02-03 13:14:24.762 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.762 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.762 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.763 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.763 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.764 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.763 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.765 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.765 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.765 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.764 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_2
2019-02-03 13:14:24.766 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.764 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.766 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.766 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_3
2019-02-03 13:14:24.766 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.767 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.767 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.767 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.767 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.768 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.768 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.768 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.768 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.769 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.769 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.768 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.770 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.779 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.779 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_3
2019-02-03 13:14:24.780 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.781 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.781 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.782 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.782 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 3
2019-02-03 13:14:24.783 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:14:24.783 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 4
2019-02-03 13:14:24.784 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:14:24.784 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_4
2019-02-03 13:14:24.785 [INFO ] [se.smarthome.model.script.Irrigation] - nextValve is Irrigation_Zone_4
2019-02-03 13:15:24.764 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_3
2019-02-03 13:15:24.766 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_4
2019-02-03 13:15:24.772 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.772 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_3
2019-02-03 13:15:24.773 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.773 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_4
2019-02-03 13:15:24.774 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_3
2019-02-03 13:15:24.775 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_4
2019-02-03 13:15:24.775 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.776 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.777 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.778 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.778 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.779 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.776 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.780 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.780 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.781 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.784 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.785 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.785 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_3
2019-02-03 13:15:24.785 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_4
2019-02-03 13:15:24.787 [INFO ] [se.smarthome.model.script.Irrigation] - Turning off Irrigation_Zone_3
2019-02-03 13:15:24.787 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.789 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.804 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.804 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.805 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - Turning on Irrigation_Zone_4
2019-02-03 13:15:24.805 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.808 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.808 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.808 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.808 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.809 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.809 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.809 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.810 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.810 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.811 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.812 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.812 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.812 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.813 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.813 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.813 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.814 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.814 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.814 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.814 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.814 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.815 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.815 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.815 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.815 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.816 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.816 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.817 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.817 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.817 [INFO ] [se.smarthome.model.script.Irrigation] - currValve is Irrigation_Zone_4
2019-02-03 13:15:24.818 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2019-02-03 13:15:24.818 [INFO ] [se.smarthome.model.script.Irrigation] - currValveNum is 4
2019-02-03 13:15:24.819 [INFO ] [se.smarthome.model.script.Irrigation] - currValveMins is 1.0
2019-02-03 13:15:24.819 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveNum is 5
2019-02-03 13:15:24.820 [INFO ] [se.smarthome.model.script.Irrigation] - nextValveName is Irrigation_Zone_5
2019-02-03 13:15:24.823 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Irrigation Cascade': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null


This is the rule as it stands right now:

rule "Irrigation Cascade"
when
    Member of gIrrigation received command
then
    // get info for the current valve
    val currValve = gIrrigation.members.findFirst[ valve | valve.name == Irrigation_Curr.state.toString]
    logInfo("Irrigation", "currValve is " + currValve.name)
	val currValveNum = Integer::parseInt(currValve.name.split("_").get(2))
    logInfo("Irrigation", "currValveNum is " + currValveNum)
	val currValveMins = gIrrigation_Times.members.findFirst[ t | t.name == currValve.name+"_Time" ].state as Number
	logInfo("Irrigation", "currValveMins is " + currValveMins)
	
    // get info for the next valve in the sequence
    val nextValveNum = currValveNum + 1
    logInfo("Irrigation", "nextValveNum is " + nextValveNum)
	val nextValveName = "Irrigation_Zone_"+nextValveNum
	logInfo("Irrigation", "nextValveName is " + nextValveName)
    val nextValve = gIrrigation.members.findFirst[ valve | valve.name == nextValveName] // null if there is no member by that name
    logInfo("Irrigation", "nextValve is " + nextValve.name)
	
    // Create a timer to turn off curr valve and start the next valve
    irrigationTimer = createTimer(now.plusMinutes(currValveMins.intValue), [ |
        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
    irrigationTimer?.cancel
    irrigationTimer = 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

Your log statement after

val nextValve = gIrrigation.members.findFirst[ valve | valve.name == nextValveName] // null if there is no member by that name

is causing the rule to error out before it can create the time to end the cascade.

When you get to the last valve, nextValve will come back null. This is expected and required. But the log statement is calling nextValve. name which causes an error when nextValve is null.

Thanks Rich, everything is working now.
I really appreciate your help with this, I have learnt a massive amount.

For future reference a fully working setup is as follows:

Items

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

//OpenSprinkler
Switch  		Irrigation_Zone_1			"Side Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station01" }
Switch  		Irrigation_Zone_2			"Front Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station02" }
Switch  		Irrigation_Zone_3			"Veggie Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station03" }
Switch  		Irrigation_Zone_4			"Back Garden"											(gIrrigation, gIrriAll)								{ channel="opensprinkler:http:192_168_0_150:station04" }
String  		Irrigation_Curr				"Current active zone is [%s]"							(gIrriAll)
Switch  		Irrigation_Auto				"Irrigation Auto"										(gIrriAll)
Switch 			Irrigation_Manual 			"Irrigation state [%s]"									(gIrriAll)
Number  		Irrigation_Zone_1_Time 		"Side Garden duration [%d mins]"						(gIrrigation_Times, gIrriAll)
Number  		Irrigation_Zone_2_Time 		"Front Garden duration [%d mins]"						(gIrrigation_Times, gIrriAll)
Number  		Irrigation_Zone_3_Time 		"Veggie Garden duration [%d mins]"						(gIrrigation_Times, gIrriAll)
Number  		Irrigation_Zone_4_Time 		"Back Garden duration [%d mins]"						(gIrrigation_Times, gIrriAll)

Sitemap

	Frame {
    Group item=gIrriAll		label="Irrgiation Test"		icon="water_drops"
	Group item=gIrrigation		label="gIrrigation Test"		icon="water_drops"
	Switch item=gIrrigation
	Text item=gIrrigation_Times
    Setpoint item=Irrigation_Zone_1_Time minValue=1 maxValue=30 step=1
    Setpoint item=Irrigation_Zone_2_Time minValue=1 maxValue=30 step=1
    Setpoint item=Irrigation_Zone_3_Time minValue=1 maxValue=30 step=1
	Setpoint item=Irrigation_Zone_4_Time minValue=1 maxValue=30 step=1
  }

Rule

var Timer irrigationTimer = null


rule "Reset Irrigation at OH Start"
when
    System started
then
    // use this line to just turn everything off if OH happens to restart when irrigation is running
    gIrrigation.members.filter[valve|valve.state != OFF].forEach[valve| valve.sendCommand(OFF)]

    // 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
    Time cron "0 0 8 * * ?" or
    Item Irrigation_Manual received command ON
then
    if(Irrigation_Auto.state == ON || receivedCommand == 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
    Member of gIrrigation received command
then
    // get info for the current valve
    val currValve = gIrrigation.members.findFirst[ valve | valve.name == Irrigation_Curr.state.toString]
    //logInfo("Irrigation", "currValve is " + currValve.name)
	val currValveNum = Integer::parseInt(currValve.name.split("_").get(2))
    //logInfo("Irrigation", "currValveNum is " + currValveNum)
	val currValveMins = gIrrigation_Times.members.findFirst[ t | t.name == currValve.name+"_Time" ].state as Number
	//logInfo("Irrigation", "currValveMins is " + currValveMins)
	
    // get info for the next valve in the sequence
    val nextValveNum = currValveNum + 1
    //logInfo("Irrigation", "nextValveNum is " + nextValveNum)
	val nextValveName = "Irrigation_Zone_"+nextValveNum
	//logInfo("Irrigation", "nextValveName is " + nextValveName)
    val nextValve = gIrrigation.members.findFirst[ valve | valve.name == nextValveName] // null if there is no member by that name
    //logInfo("Irrigation", "nextValve is " + nextValve.name)
	
    // Create a timer to turn off curr valve and start the next valve
    irrigationTimer = createTimer(now.plusMinutes(currValveMins.intValue), [ |
        //logInfo("Irrigation", "Turning off " + currValve.name)
        currValve.sendCommand(OFF)
		Thread::sleep(5000)

        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
    irrigationTimer?.cancel
    irrigationTimer = 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

Due to some quirks with the OpenSprinkler needed to add a “Thread::sleep(5000)” between sprinkler zones. If you don’t some weird stuff can happen. I have seen, zones being scheduled or flickering of the zones.

A sleep of that amount of time is a pretty bad idea. It’s less so in a Timer. The problem is by default Timers only have two threads to run in. If you have other Rules with Timers, cron triggers or Astro triggers and you have two long running Timers, those may be delayed until the other Timers complete. Better to use a Timer, but in this case I’m not sure of the full impact.

If you have problems I recommend a minor change:

    // Create a timer to turn off curr valve and start the next valve
    irrigationTimer = createTimer(now.plusMinutes(currValveMins.intValue), [ |
        //logInfo("Irrigation", "Turning off " + currValve.name)
        currValve.sendCommand(OFF)
	
        irrigationTimer = createTimer(now.plusSeconds(5), [ |
            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
        ])
    ])

Another more thorough approach would be Design Pattern: Gate Keeper which is probably overkill for this case.

I’m not suggesting you change anything, just providing something to try should the long sleep become a problem. Make sure to test that the cancels work properly if you do make the change.