How to implement timer with parameter

Hi !

I know that it was many topics about timers, but I have not found answer for my case.

What I want to achieve:

  • sequentially open valve one by one, so Irrigation_1 ON, OFF, then Irrigation 2 ON, etc
  • have influence /parameter how long valve is open
  • have possibility to stop executing of the rule

Items:
Switch Irrigation_Now - to execute rule
Switch Irrigation_Stop - stop irrigation
Switch Irrigation_1 - valve_1
Number Irrigation1_Time - time of opening valve 1
Switch Irrigation_2 - valve 2
Number Irrigation_1_Time - time of opening valve 2

rule "Enabling irrigation"
when
Time cron “0 0 8 * * ?” or
Item Irrigation_Now changed from OFF to ON
then
if ((Irrigation_Auto.state == ON) || (Irrigation_Now.state == ON))
{
sendCommand(Irrigation_1, ON)
Thread::sleep(180000)
sendCommand(Irrigation_1, OFF)
}

This works well, but I have no influence on time, so I changed it to:

{
sendCommand(Irrigation_1, ON)
timer = createTimer(now.plusSeconds((60*Irrigation_1_Time.state as DecimalType).intValue)) [|
sendCommand(Irrigation_1, OFF) ]

I can influence on time, but I lost sequence, after I open Irrigation ON, timer is wating to close, but the rest of the code is executed, so Irrigation 2 will be open, etc.

Last problem is to how to implement possibility to stop execution, when item Irrigation_Stop will be change to ON ?

I tried with while loop but it didn’t stop.

You can probably simply add a simple switch to your sitemap for Irrigation_1 to manually switch it to off (or on if you need to). You could try to create a rule to cancel the timer, but it will run out by itself anyway.

I have switches per each irrigation, but it is inconvenience to wait when each circuit will be open (during execution of the rule) and manually close it.

I think you need this sort of approach, where each item is triggered by the previous one closing.

rule "Start Irrigation"
when
    Time cron "0 0 8 * * ?"
then
    Irrigation_1.sendCommand(ON)
    createTimer(now.plusMinutes((Irrigation1_Time.state as DecimalType).intValue)) [|
        Irrigation_1.sendCommand(OFF)
        Irrigation_2.sendCommand(ON)
    ]

end

rule "Irrigation 2 On"
when
    Item Irrigation_2 received command ON
then
    createTimer(now.plusMinutes((Irrigation2_Time.state as DecimalType).intValue)) [|
        Irrigation_2.sendCommand(OFF)
    ]
end

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.
1 Like

Hi,
maybe the following example can help you:

I have an irrigation system consisting of four (4) lines (Strang1, Strang2, Vordergarten, BlumenHinten).
I have also a cistern with a pump as well a a humidity sensor in the lawn.
Note that switches (weekdays from Montag/Monday to Sonntag/Sunday) and Numbers (Stunde/hours and Minute/minutes) per line are also defined.

My item file Looks like this:

// ### Bewaesserungstimer ##################################################################################################################

String Bewaesserungstimer “[%s]”

Switch weckerMontag “Montag” (gWeckerWochentage)
Switch weckerDienstag “Dienstag” (gWeckerWochentage)
Switch weckerMittwoch “Mittwoch” (gWeckerWochentage)
Switch weckerDonnerstag “Donnerstag” (gWeckerWochentage)
Switch weckerFreitag “Freitag” (gWeckerWochentage)
Switch weckerSamstag “Samstag” (gWeckerWochentage)
Switch weckerSonntag “Sonntag” (gWeckerWochentage)

String weckerZeitMessage “%s”

Number weckerZeitStunde “Stunde [%d]” (gWeckerZeit)
Number weckerZeitMinute “Minute [%d]” (gWeckerZeit)
Number weckerZeitMinuteTimer “Minute [%d]” (gWeckerZeit)
Number weckerZeitStundeTimer “Minute [%d]” (gWeckerZeit)
Number weckerZeitDauerStrang1 “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerTimerStrang1 “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerStrang2 “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerTimerStrang2 “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerTerrasse “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerTimerTerrasse “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerVorne “Dauer in Minuten [%d]” (gWeckerZeit)
Number weckerZeitDauerTimerVorne “Dauer in Minuten [%d]” (gWeckerZeit)

Number ZeitHelp “ZeitHelp [%d]”

Dimmer Strang1 “Strang 1 [%d %%]” (Steckdose, Garten, Bewaesserung) { rwe=“id=25de4c34-66fb-4fc5-a5ba-a006228439fb,param=dimmer” }
Dimmer Strang2 “Strang 2 [%d %%]” (Steckdose, Garten, Bewaesserung) { rwe=“id=3276a4bc-1cb9-4e56-9e77-6d8fd139eff4,param=dimmer” }
Dimmer Vordergarten “Vordergarten [%d %%]” (Steckdose, Garten, Bewaesserung) { rwe=“id=ce6863fa-a67d-4219-8047-505e060aada7,param=dimmer” }
Dimmer BlumenHinten “Blumen Hinten [%d %%]” (Steckdose, Garten, Bewaesserung) { rwe=“id=aa20a890-2647-47c0-9b8e-6408bbbe03c2,param=dimmer” }
Number Bewaesserung_Chart_Period “Bewaesserung_Chart_Period”

Switch Zisternenpumpe “Zisternenpumpe” (Steckdose, Garten) {rwe=“id=f607806f-7360-4c68-bdc5-a854f273f8b4,param=switch”}

Number Bewaesserungszeit “Bewaesserungszeit”
Number Countdown “Countdown [%.0f]”

Number FeuchteRasen1 “Feuchte Rasen1 [%.0f]” (Garten, Bodenfeuchte) { exec="<[/data/OpenHab/NETAVRIO.sh:60000:REGEX((.*?))]" }
Number FeuchteRasen_Chart_Period “Bewaesserung_Chart_Period”

Based on the example presented here “https://github.com/openhab/openhab/wiki/Samples-Rules#irrigation-controller” I have adopted the rule to the follwing:

// ### Zeit Minuten ################################################################################################################

rule “Zeit Minuten”
when
Item weckerZeitMinuteTimer received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitMinute, 0}
case 1 : {postUpdate(weckerZeitMinute, 5}
case 2 : {postUpdate(weckerZeitMinute, 10}
case 3 : {postUpdate(weckerZeitMinute, 15}
case 4 : {postUpdate(weckerZeitMinute, 20}
case 5 : {postUpdate(weckerZeitMinute, 25}
case 6 : {postUpdate(weckerZeitMinute, 30}
case 7 : {postUpdate(weckerZeitMinute, 35}
case 8 : {postUpdate(weckerZeitMinute, 40}
case 9 : {postUpdate(weckerZeitMinute, 45}
case 10 : {postUpdate(weckerZeitMinute, 50}
case 11 : {postUpdate(weckerZeitMinute, 55}
}
end

  // ### Zeit Stunden ################################################################################################################	

rule “Zeit Stunden”
when
Item weckerZeitStundeTimer received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitStunde, 0}
case 1 : {postUpdate(weckerZeitStunde, 1}
case 2 : {postUpdate(weckerZeitStunde, 2}
case 3 : {postUpdate(weckerZeitStunde, 3}
case 4 : {postUpdate(weckerZeitStunde, 4}
case 5 : {postUpdate(weckerZeitStunde, 5}
case 6 : {postUpdate(weckerZeitStunde, 6}
case 7 : {postUpdate(weckerZeitStunde, 7}
case 8 : {postUpdate(weckerZeitStunde, 8}
case 9 : {postUpdate(weckerZeitStunde, 9}
case 10 : {postUpdate(weckerZeitStunde, 10}
case 11 : {postUpdate(weckerZeitStunde, 11}
case 12 : {postUpdate(weckerZeitStunde, 12}
case 13 : {postUpdate(weckerZeitStunde, 13}
case 14 : {postUpdate(weckerZeitStunde, 14}
case 15 : {postUpdate(weckerZeitStunde, 15}
case 16 : {postUpdate(weckerZeitStunde, 16}
case 17 : {postUpdate(weckerZeitStunde, 17}
case 18 : {postUpdate(weckerZeitStunde, 18}
case 19 : {postUpdate(weckerZeitStunde, 19}
case 20 : {postUpdate(weckerZeitStunde, 20}
case 21 : {postUpdate(weckerZeitStunde, 21}
case 22 : {postUpdate(weckerZeitStunde, 22}
case 23 : {postUpdate(weckerZeitStunde, 23}
}
end

// ### Zeit Dauer Strang 1 ################################################################################################################

rule “Zeit Dauer Strang 1”
when
Item weckerZeitDauerTimerStrang1 received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitDauerStrang1, 0}
case 1 : {postUpdate(weckerZeitDauerStrang1, 5}
case 2 : {postUpdate(weckerZeitDauerStrang1, 10}
case 3 : {postUpdate(weckerZeitDauerStrang1, 15}
case 4 : {postUpdate(weckerZeitDauerStrang1, 20}
case 5 : {postUpdate(weckerZeitDauerStrang1, 25}
case 6 : {postUpdate(weckerZeitDauerStrang1, 30}
case 7 : {postUpdate(weckerZeitDauerStrang1, 35}
case 8 : {postUpdate(weckerZeitDauerStrang1, 40}
case 9 : {postUpdate(weckerZeitDauerStrang1, 45}
case 10 : {postUpdate(weckerZeitDauerStrang1, 50}
case 11 : {postUpdate(weckerZeitDauerStrang1, 55}
}
end

  // ### Zeit Dauer Strang 2 ################################################################################################################	

rule “Zeit Dauer Strang 2”
when
Item weckerZeitDauerTimerStrang2 received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitDauerStrang2, 0}
case 1 : {postUpdate(weckerZeitDauerStrang2, 5}
case 2 : {postUpdate(weckerZeitDauerStrang2, 10}
case 3 : {postUpdate(weckerZeitDauerStrang2, 15}
case 4 : {postUpdate(weckerZeitDauerStrang2, 20}
case 5 : {postUpdate(weckerZeitDauerStrang2, 25}
case 6 : {postUpdate(weckerZeitDauerStrang2, 30}
case 7 : {postUpdate(weckerZeitDauerStrang2, 35}
case 8 : {postUpdate(weckerZeitDauerStrang2, 40}
case 9 : {postUpdate(weckerZeitDauerStrang2, 45}
case 10 : {postUpdate(weckerZeitDauerStrang2, 50}
case 11 : {postUpdate(weckerZeitDauerStrang2, 55}
}
end

  // ### Zeit Dauer Terrasse ################################################################################################################	

rule “Zeit Dauer Terrasse”
when
Item weckerZeitDauerTimerTerrasse received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitDauerTerrasse, 0}
case 1 : {postUpdate(weckerZeitDauerTerrasse, 5}
case 2 : {postUpdate(weckerZeitDauerTerrasse, 10}
case 3 : {postUpdate(weckerZeitDauerTerrasse, 15}
case 4 : {postUpdate(weckerZeitDauerTerrasse, 20}
case 5 : {postUpdate(weckerZeitDauerTerrasse, 25}
case 6 : {postUpdate(weckerZeitDauerTerrasse, 30}
case 7 : {postUpdate(weckerZeitDauerTerrasse, 35}
case 8 : {postUpdate(weckerZeitDauerTerrasse, 40}
case 9 : {postUpdate(weckerZeitDauerTerrasse, 45}
case 10 : {postUpdate(weckerZeitDauerTerrasse, 50}
case 11 : {postUpdate(weckerZeitDauerTerrasse, 55}
}
end

  	// ### Zeit Dauer Vorne ################################################################################################################	

rule “Zeit Dauer Vorne”
when
Item weckerZeitDauerTimerVorne received command
then
switch (receivedCommand) {
case 0 : {postUpdate(weckerZeitDauerVorne, 0}
case 1 : {postUpdate(weckerZeitDauerVorne, 5}
case 2 : {postUpdate(weckerZeitDauerVorne, 10}
case 3 : {postUpdate(weckerZeitDauerVorne, 15}
case 4 : {postUpdate(weckerZeitDauerVorne, 20}
case 5 : {postUpdate(weckerZeitDauerVorne, 25}
case 6 : {postUpdate(weckerZeitDauerVorne, 30}
case 7 : {postUpdate(weckerZeitDauerVorne, 35}
case 8 : {postUpdate(weckerZeitDauerVorne, 40}
case 9 : {postUpdate(weckerZeitDauerVorne, 45}
case 10 : {postUpdate(weckerZeitDauerVorne, 50}
case 11 : {postUpdate(weckerZeitDauerVorne, 55}
}
end

// ### BEWAESSERUNGSTIMER ###########################################################################################################################

rule “Initialization”
when
System started
then
postUpdate(weckerZeitStundeTimer, 0)
postUpdate(weckerZeitMinuteTimer, 0)
postUpdate(weckerZeitDauerTimerStrang1, 0)
postUpdate(weckerZeitDauerTimerStrang2, 0)
postUpdate(weckerZeitDauerTimerTerrasse, 0)
postUpdate(weckerZeitDauerTimerVorne, 0)
postUpdate(weckerZeitStunde, 0)
postUpdate(weckerZeitMinute, 0)
postUpdate(weckerZeitDauerStrang1, 0)
postUpdate(weckerZeitDauerStrang2, 0)
postUpdate(weckerZeitDauerTerrasse, 0)
postUpdate(weckerZeitDauerVorne, 0)
postUpdate(weckerMontag, OFF)
postUpdate(weckerDienstag, OFF)
postUpdate(weckerMittwoch, OFF)
postUpdate(weckerDonnerstag, OFF)
postUpdate(weckerFreitag, OFF)
postUpdate(weckerSamstag, OFF)
postUpdate(weckerSonntag, OFF)
postUpdate(weckerZeitMessage, “OFF”)
postUpdate(Bewaesserungstimer, “OFF”)
end

rule “Weckzeit”
when
Item weckerMontag changed or
Item weckerDienstag changed or
Item weckerMittwoch changed or
Item weckerDonnerstag changed or
Item weckerFreitag changed or
Item weckerSamstag changed or
Item weckerSonntag changed or
Item weckerZeitStunde changed or
Item weckerZeitMinute changed
then

postUpdate(Bewaesserungstimer, “ON”)

if ((weckerMontag.state == OFF) && (weckerDienstag.state == OFF) && (weckerMittwoch.state == OFF) && (weckerDonnerstag.state == OFF) && (weckerFreitag.state == OFF) && (weckerSamstag.state == OFF) && (weckerSonntag.state == OFF)) {
postUpdate(weckerZeitStundeTimer, 0)
postUpdate(weckerZeitMinuteTimer, 0)
postUpdate(weckerZeitDauerTimerStrang1, 0)
postUpdate(weckerZeitDauerTimerStrang2, 0)
postUpdate(weckerZeitDauerTimerTerrasse, 0)
postUpdate(weckerZeitDauerTimerVorne, 0)
postUpdate(weckerZeitStunde, 0)
postUpdate(weckerZeitMinute, 0)
postUpdate(weckerZeitDauerStrang1, 0)
postUpdate(weckerZeitDauerStrang2, 0)
postUpdate(weckerZeitDauerTerrasse, 0)
postUpdate(weckerZeitDauerVorne, 0)
postUpdate(weckerZeitMessage, “OFF”)
postUpdate(Bewaesserungstimer, “OFF”)
}

var String msg = ""

// Copy the Alarm-Time from the UI to local variables
var stunde = weckerZeitStunde.state as DecimalType
var minute = weckerZeitMinute.state as DecimalType
// Combine the hour and minutes to one string to be displayed in the 
// user interface
if (stunde < 10) { msg = "0" } 
msg = msg + weckerZeitStunde.state.format("%d") + ":"
if (minute < 10) { msg = msg + "0" }
msg = msg + weckerZeitMinute.state.format("%d")
postUpdate(weckerZeitMessage,msg)
// calculate the alarm time [min]
var int weckzeit1
weckzeit1 = (weckerZeitStunde.state as DecimalType).intValue * 60 + 
            (weckerZeitMinute.state as DecimalType).intValue
weckzeit1 = weckzeit1.intValue

// calculate current time [min]
var int jetzt1
jetzt1 = now.getMinuteOfDay
jetzt1 = jetzt1.intValue
// calculate the difference between the requested alarm time and 
// current time (again in minutes)  
var int delta1
delta1 = (weckzeit1 - jetzt1)
delta1 = delta1.intValue
// add one day (1440 minutes) if alarm time for today already passed
if (jetzt1 > weckzeit1) { delta1 = delta1 + 1440 }
// check if there is already an alarm timer; cancel it if present
if (timer1 != null) {
   timer1.cancel
   timer1 = null
}

var int endtimeStrang1
endtimeStrang1 = (weckerZeitDauerStrang1.state as DecimalType).intValue * 60 * 1000
endtimeStrang1 = endtimeStrang1.intValue
var int endtimeStrang2
endtimeStrang2 = (weckerZeitDauerStrang2.state as DecimalType).intValue * 60 * 1000
endtimeStrang2 = endtimeStrang2.intValue
var int endtimeTerrasse
endtimeTerrasse = (weckerZeitDauerTerrasse.state as DecimalType).intValue * 60 * 1000
endtimeTerrasse = endtimeTerrasse.intValue
var int endtimeVorne
endtimeVorne = (weckerZeitDauerVorne.state as DecimalType).intValue * 60 * 1000
endtimeVorne = endtimeVorne.intValue

// create a new timer using the calculated delta [min]
timer1 = createTimer(now.plusMinutes(delta1)) [|
    // This code will be executed if the timer triggers
    // ************************************************
    // check if alarm clock is armed for this weekday
    var Number day = now.getDayOfWeek
    if (((day == 1) && (weckerMontag.state == ON))     ||
        ((day == 2) && (weckerDienstag.state == ON))   ||
        ((day == 3) && (weckerMittwoch.state == ON))   ||
        ((day == 4) && (weckerDonnerstag.state == ON)) ||
        ((day == 5) && (weckerFreitag.state == ON))    ||
        ((day == 6) && (weckerSamstag.state == ON))    ||
        ((day == 7) && (weckerSonntag.state == ON))) {
            // The things to do if the alarm clock is enabled for this day of week: 
            // ...
  					Strang1.persist
  					Strang2.persist
  					Vordergarten.persist
  					BlumenHinten.persist
  					if (endtimeStrang1>0) {
  						sendCommand(HueOfen, ON)
  						sendCommand(Strang1, "100")
  						// ##############################
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################							
  						Thread::sleep(endtimeStrang1)
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						sendCommand(HueOfen, OFF)
  						sendCommand(Strang1, "0")
  						}
  					if (endtimeStrang2>0) {
  						sendCommand(HueRegalLinks, ON)
  						sendCommand(Strang2, "100")
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						Thread::sleep(endtimeStrang2)
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						sendCommand(HueRegalLinks, OFF)
  						sendCommand(Strang2, "0")
  						}
  					if (endtimeTerrasse>0) {
  						sendCommand(HueRegalRechts, ON)
  						sendCommand(Vordergarten, "100")
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						Thread::sleep(endtimeTerrasse)
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						sendCommand(HueRegalRechts, OFF)
  						sendCommand(Vordergarten, "0")
  						}
  					if (endtimeVorne>0) {
  						sendCommand(HueKlavier, ON)
  						sendCommand(BlumenHinten, "100")
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						Thread::sleep(endtimeVorne)
  						Strang1.persist
  						Strang2.persist
  						Vordergarten.persist
  						BlumenHinten.persist
  						// ##############################
  						sendCommand(HueKlavier, OFF)
  						sendCommand(BlumenHinten, "0")
  						}
  					Strang1.persist
  					Strang2.persist
  					Vordergarten.persist
  					BlumenHinten.persist
  					// ##############################
  				]
       }
       // Re-Arm the timer to trigger again tomorrow (same time) 
       timer1.reschedule(now.plusHours(24))
    // ************************************************
    // Here the code ends that executes once the timer triggers 
    ]

Finally my sitemap Looks like the follwing:

Group item=Garten icon=“Pflanzen”{
Frame item=Markise
{
Slider item=MarkiseRechts icon=“Markise”
Slider item=MarkiseLinks icon=“Markise”
}
Frame item=Licht
{
Switch item=Gartenbeleuchtung icon=“Gartenbeleuchtung”
}
Frame label=Bewaesserung icon=“GartenHinten”
{
Slider item=Strang1 icon=“Irrigation1” switchSupport
Slider item=Strang2 icon=“Irrigation1” switchSupport
Slider item=BlumenHinten icon=“Irrigation1” switchSupport
Slider item=Vordergarten icon=“Irrigation1” switchSupport
}
Frame label=“Bewaesserungszeiten” {
Text label=“Bewässerungstimer [%s]” item=Bewaesserungstimer icon=“Irrigation1” {
Frame label=“Startzeit” {
Selection label=“Stunde” icon=“clock” item=weckerZeitStundeTimer mappings=[0=“00 h”, 1=“01 h”, 2=“02 h”, 3=“03 h”, 4=“04 h”, 5=“05 h”, 6=“06 h”, 7=“07 h”, 8=“08 h”, 9=“09 h”, 10=“10 h”, 11=“11 h”, 12=“12 h”, 13=“13 h”, 14=“14 h”, 15=“15 h”, 16=“16 h”, 17=“17 h”, 18=“18 h”, 19=“19 h”, 20=“20 h”, 21=“21 h”, 22=“22 h”, 23=“23 h”]
Selection label=“Minute” icon=“clock” item=weckerZeitMinuteTimer mappings=[0=“0 min”, 1=“5 min”, 2=“10 min”, 3=“15 min”, 4=“20 min”, 5=“25 min”, 6=“30 min”, 7=“35 min”, 8=“40 min”, 9=“45 min”, 10=“50 min”, 11=“55 min”]
}
Frame label=“Beregnungsdauern” {
Selection label=“Strang 1 (min)” icon=“clock” item=weckerZeitDauerTimerStrang1 mappings=[0=“OFF”, 1=“5 min”, 2=“10 min”, 3=“15 min”, 4=“20 min”, 5=“25 min”, 6=“30 min”, 7=“35 min”, 8=“40 min”, 9=“45 min”, 10=“50 min”, 11=“55 min”]
Selection label=“Strang 2 (min)” icon=“clock” item=weckerZeitDauerTimerStrang2 mappings=[0=“OFF”, 1=“5 min”, 2=“10 min”, 3=“15 min”, 4=“20 min”, 5=“25 min”, 6=“30 min”, 7=“35 min”, 8=“40 min”, 9=“45 min”, 10=“50 min”, 11=“55 min”]
Selection label=“Terrasse (min)” icon=“clock” item=weckerZeitDauerTimerTerrasse mappings=[0=“OFF”, 1=“5 min”, 2=“10 min”, 3=“15 min”, 4=“20 min”, 5=“25 min”, 6=“30 min”, 7=“35 min”, 8=“40 min”, 9=“45 min”, 10=“50 min”, 11=“55 min”]
Selection label=“Vordergarten (min)” icon=“clock” item=weckerZeitDauerTimerVorne mappings=[0=“OFF”, 1=“5 min”, 2=“10 min”, 3=“15 min”, 4=“20 min”, 5=“25 min”, 6=“30 min”, 7=“35 min”, 8=“40 min”, 9=“45 min”, 10=“50 min”, 11=“55 min”]
}
Frame label=“Wochentage” {
Switch item=weckerMontag
Switch item=weckerDienstag
Switch item=weckerMittwoch
Switch item=weckerDonnerstag
Switch item=weckerFreitag
Switch item=weckerSamstag
Switch item=weckerSonntag
}
}
Text label=“Start Bewässerung [%s]” item=weckerZeitMessage icon=“Irrigation1”
Text label=“Bewässerungszeit Strang 1 [%s]” item=weckerZeitDauerStrang1 icon=“Irrigation1”
Text label=“Bewässerungszeit Strang 2 [%s]” item=weckerZeitDauerStrang2 icon=“Irrigation1”
Text label=“Bewässerungszeit Terrasse [%s]” item=weckerZeitDauerTerrasse icon=“Irrigation1”
Text label=“Bewässerungszeit Vorne [%s]” item=weckerZeitDauerVorne icon=“Irrigation1”
}
Frame label=Zisterne icon=“Zisterne1”
{
Switch item=Zisternenpumpe icon=“Zisterne1”
}
Frame label=Rasenzustand icon=“Luftfeuchte”
{
Text item=FeuchteRasen1 icon=“Luftfeuchte”
Switch item=FeuchteRasen_Chart_Period icon=“clock-on” label=“Chart Period” mappings=[0=“D”, 1=“W”, 2=“M”]
Chart item=Bodenfeuchte label="%" period=D refresh=100000 service=“mysql” visibility=[FeuchteRasen_Chart_Period==0]
Chart item=Bodenfeuchte label="%" period=W refresh=100000 service=“mysql” visibility=[FeuchteRasen_Chart_Period==1, FeuchteRasen_Chart_Period==“Uninitialized”]
Chart item=Bodenfeuchte label="%" period=M refresh=100000 service=“mysql” visibility=[FeuchteRasen_Chart_Period==2]
}
Frame label=“Bewaesserungszeiten”{
Switch item=Bewaesserung_Chart_Period icon=“clock-on” label=“Chart Period” mappings=[0=“h”, 1=“4h”, 2=“D”, 3=“W”, 4=“M”]
Chart item=Bewaesserung label=“h” period=h refresh=100000 service=“mysql” visibility=[Bewaesserung_Chart_Period==0, Bewaesserung_Chart_Period==“Uninitialized”]
Chart item=Bewaesserung label=“h” period=4h refresh=100000 service=“mysql” visibility=[Bewaesserung_Chart_Period==1]
Chart item=Bewaesserung label=“h” period=D refresh=100000 service=“mysql” visibility=[Bewaesserung_Chart_Period==2]
Chart item=Bewaesserung label=“h” period=W refresh=100000 service=“mysql” visibility=[Bewaesserung_Chart_Period==3]
Chart item=Bewaesserung label=“h” period=M refresh=100000 service=“mysql” visibility=[Bewaesserung_Chart_Period==4]
}
}

Finally it looks on level 1 GUI like that:

If you press on "Bewässerungstimer " to set the times you see the following:

And behind the Stunde and the Minute as well as behind the time of the lines you see the following:



Cheers

1 Like

Rikoshak, thank you so much for proposed code.

Logically it makes sence, but I have problem with implementation.

var curr = gIrrigation.members.filter[v|v.state == ON].head

How to understand it ? Especially this syntax: filter[v|v.state == ON].head
I got an errot in this rule:

Error during the execution of startup rule ‘System started Irrigation’: cannot invoke method public abstract java.lang.String org.openhab.core.items.Item.getName() on null

The rest after small changes it looks like working ok :slight_smile:

Bjorn, thanks for your code, it looks interesting. I have started implementing above solution + my modification, but your post will be also helpfull (Even I almost forgot German :grin:).

BTW. Which sensor are you using ? Are ok with it ?

I had an idea to based on weather station data, which I can obtain from wunderground.com. Some of the station collect info about total amount of rain per day. I have more then 10 station with few km distance, so it should be accurate. Now I am waiting for rain to check accuracy :slight_smile:
I also bought rain sensor and ground humidity for Anduino, but had no time to check it.

That line says, in English, “Give me the first switch that is on in the gIrrigation group.” No l broken down by words:

*gIrrigation - the group with your irrigation switches

  • . members - all the members as a list
  • .filter - apply a rule to give me a list of only those members that match a criteria
  • [v|v.state==ON] - this is the criteria to filter. It’s a little complicated to explain the full syntax here. Suffice to say it means those members whose state is ON
    *.head - the first item in the list

If none of your switches are on you could get a null exception here.

Hi Marcin,
I use a Vegetronix sensor together with an NETIOAVR board.
See here:


It works as it should.
Access to the NETIOAVR board via short shell script and telnet access.
Cheers
Björn

Yes, so if all of them are off, so this check will cause an error:

Error during the execution of startup rule ‘System started Irrigation’:
cannot invoke method public abstract java.lang.String
org.openhab.core.items.Item.getName() on null

how to avoid it ?

Also I have no idea about strange delay, according your code, this part is responsible for switching off current relay and switching on next one:

// Create a timer to turn off curr and turn on next
irrigationTimer = createTimer(now.plusSeconds((60 * currValveMins as DecimalType).intValue)) [|
logInfo(“Irrigation”, "Turing off " + currValveName)
currValveSwitch.sendCommand(OFF)
if((nextValveSwitch != null) && (irrigationTimer != null)){
logInfo(“Irrigation”, "Turing on " + nextValveName)
Irrigation_Curr.sendCommand(nextValveName)
nextValveSwitch.sendCommand(ON)
}

So after off immediately next one should be on, but I got:

2016-05-24 23:12:54.433 [INFO ] [penhab.model.script.Irrigation] - Irrigation started, turning on Irrigation_1
2016-05-24 23:13:40.638 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_1
2016-05-24 23:13:51.452 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_2
2016-05-24 23:14:56.232 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_2
2016-05-24 23:15:17.404 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_3
2016-05-24 23:16:15.773 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_3
2016-05-24 23:16:38.789 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_4
2016-05-24 23:17:36.284 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_4
2016-05-24 23:17:46.274 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_5
2016-05-24 23:18:49.669 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_5
2016-05-24 23:19:16.199 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_6
2016-05-24 23:20:40.171 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_6
2016-05-24 23:21:38.967 [INFO ] [penhab.model.script.Irrigation] - Turing on Irrigation_7
2016-05-24 23:24:04.792 [INFO ] [penhab.model.script.Irrigation] - Turing off Irrigation_7
2016-05-24 23:25:51.426 [INFO ] [penhab.model.script.Irrigation] - Irrigation is complete

All Irrigation_Time set to 0.5min, but if you check off - on it is almost one minute.

Trying to get the “Irrigaton Switch cascade” rule to work. I’m getting the following errors message in my logs:

[ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Irrigation switch cascade': The name '<XFeatureCallImplCustom>.state' cannot be resolved to an item or type.

Here is my rule:

var Timer irrigationTimer = null

rule "Irrigation switch cascade" 
when
    Item Zone_1 received command ON or
    Item Zone_2 received command ON or
    Item Zone_3 received command ON or
    Item Zone_4 received command ON or
    Item Zone_5 received command ON or
    Item Zone_6 received command ON   
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 = GIrrigationTimer.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 = "Zone_" + nextValveNum
    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 = createTimer(now.plusMinutes((currValveMins.state as DecimalType).intValue)) [|
        logInfo("Irrigation", "Turning off " + currValveName)
        currValveSwitch.sendCommand(OFF)

        if(nextValveSwitch != null) {
            logInfo("Irrigation", "Turning on " + nextValveName)
            Irrigation_Curr.sendCommand(nextValveName)
            nextValveSwitch.sendCommand(ON)
        }
        else{ 
            logInfo("Irrigation", "Irrigation is complete")
            Irrigation_Curr.sendCommand("None")
            Sprinkler_Now.sendCommand(OFF)
        }
        irrigationTimer = null
    ]
end

I’ve tried changing the createTimer line to just: IrrigationTimer = createTimer(now.plusMinutes(currValveMins) but it still doesn’t work. My zones and timers are in this format:

Switch Zone_1  "Front Yard-Main"     (GIrrigation)  {openSprinkler="0"}  
Switch Zone_2  "Front Yard-Bushes"   (GIrrigation)  {openSprinkler="1"}
Switch Zone_3  "Front Yard-Sidewalk" (GIrrigation)  {openSprinkler="2"}
Switch Zone_4  "Back Yard-Perimeter" (GIrrigation)  {openSprinkler="3"}
Switch Zone_5  "Back Yard-Interior"  (GIrrigation)  {openSprinkler="4"}
Switch Zone_6  "Front Yard-Walk Way" (GIrrigation)  {openSprinkler="5"}
Number Zone_1_Time "Zone #1 Minutes [%d mins]"  (GIrrigationTimer)
Number Zone_2_Time "Zone #2 Minutes [%d mins]"  (GIrrigationTimer)
Number Zone_3_Time "Zone #3 Minutes [%d mins]"  (GIrrigationTimer)
Number Zone_4_Time "Zone #4 Minutes [%d mins]"  (GIrrigationTimer)
Number Zone_5_Time "Zone #5 Minutes [%d mins]"  (GIrrigationTimer)
Number Zone_6_Time "Zone #6 Minutes [%d mins]"  (GIrrigationTimer)

Any help would be appreciated.

There is a function createTimerWithArgument which allows you pass an argument to the closure.
Maybe this will help you?

Also for such complex rules you should (imho) consider the jsr223-binding to create rules (e.g. jython or something else).

The error is complaining about one of your calls to .state is referencing an Item that doesn’t exist. I think your problem is

(currValveMins.state as DecimalType)

You have already extracted and cast the state from GIrrigationTimer to populate currValveMins. In otherwords, currValveMins is already a DecimalType, not an Item.

1 Like

Hi Rich,
I changed the line to:

irrigationTimer = createTimer(now.plusMinutes(currValveMins))

and received this error message.

[o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Irrigation switch cascade': Could not invoke method: org.joda.time.DateTime.plusMinutes(int) on instance: 2016-08-31T13:15:41.555-04:00

I am importing org.joda.time.* at the top of the rules file. Anything else I’m missing or doing wrong?

Hmmmm. You might need to explicitly get at the int using .intValue

currValveMins.intValue

OK, this is a copy of my log file after making that change:

2016-08-31 13:42:06.585 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_1
2016-08-31 13:42:06.966 [INFO ] [penhab.model.script.Irrigation] - Turning on Zone_2
2016-08-31 13:43:07.317 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_2
2016-08-31 13:43:07.371 [INFO ] [penhab.model.script.Irrigation] - Turning on Zone_3
2016-08-31 13:43:42.427 [WARN ] [g.openhab.io.net.http.HttpUtil] - Method failed: HTTP/1.1 500 Internal Server Error
2016-08-31 13:44:07.440 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_3
2016-08-31 13:44:07.491 [INFO ] [penhab.model.script.Irrigation] - Turning on Zone_4
2016-08-31 13:45:07.544 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_4
2016-08-31 13:45:07.597 [INFO ] [penhab.model.script.Irrigation] - Turning on Zone_5
2016-08-31 13:46:07.679 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_5
2016-08-31 13:46:07.841 [INFO ] [penhab.model.script.Irrigation] - Turning on Zone_6
2016-08-31 13:47:07.948 [INFO ] [penhab.model.script.Irrigation] - Turning off Zone_6
2016-08-31 13:47:08.149 [INFO ] [penhab.model.script.Irrigation] - Irrigation is complete

I can see the zones changing on my sitemap watching the “irrigaton_curr” item I have setup. The problem I’m having now is when I look outside only the first zone is actually going on. The other zones are not actually turning on. This is a opensprinkler type system. Any ideas why OpenHAB thinks the zones are running, when actually they are not?

I figured it out. I put a 2 second thread:sleep command between the zone closing and the next zone starting up. It works great now. Maybe the commands from OpenHAB were coming to fast for OpenSprinkler to handle. Thanks for the help.

Sounds very reasonable. You might do some experimentation to see how short of a time you can have between commands and post that to the binding’s wiki page. While knowing this isn’t a big deal for day to day operation it could come up in testing like you experienced.

I am daring to revive this thread for various reasons:

  1. I am trying to implement this sprinkler / irrigation approach
  2. Silent changes were made during the discussion
  3. I am also having trouble making this work
  4. I though there is value in it for those who want to work with the thread’s solution.

I have been abstinent form OH v1 for quite a while, and seem to have forgotten a few things.
I basically implemented the following based on this thread:

sitemap irrigation label="Irrigation" {
  Frame {
    Group item=gIrriAll		label="Irrgiation Test"		icon="water_drops"
    Setpoint item=Irrigation1_1_Time|minValue=1|maxValue=30|step=1|
    Setpoint item=Irrigation1_2_Time|minValue=1|maxValue=30|step=1|
    Setpoint item=Irrigation1_3_Time|minValue=1|maxValue=30|step=1|
  }
}
Group   gIrriAll
Group   gIrrigation													(gIrriAll)
Group   gIrrigationTime												(gIrriAll)
Switch  Irrigation_1				"Zone 1 valve"					(gIrrigation, gIrriAll)
Switch  Irrigation_2				"Zone 2 valve"					(gIrrigation, gIrriAll)
Switch  Irrigation_3				"Zone 3 valve"					(gIrrigation, gIrriAll)
String  Irrigation_Curr				"Current active zone is [%s]"	(gIrriAll)
Switch  Irrigation_Auto				"Irrigation Auto"				(gIrriAll)
Switch  Irrigation_Now				"Irrigation Now"				(gIrriAll)
Number  Irrigation_1_Time 			"Zone 1 duration [%d mins]"		(gIrrigationTime, gIrriAll)
Number  Irrigation_2_Time 			"Zone 2 duration [%d mins]"		(gIrrigationTime, gIrriAll)
Number  Irrigation_3_Time 			"Zone 3 duration [%d mins]"		(gIrrigationTime, gIrriAll)
Switch  Irrigation_Allowed			"Test switch to trigger cascade"	(gIrriAll)
import org.openhab.core.library.types.*
import org.openhab.core.library.items.*
import org.openhab.core.types.*
import org.openhab.core.items.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.eclipse.xtext.xbase.lib.*
import org.joda.time.*
import org.openhab.library.tel.types.CallType
import java.util.Set

// ------------------------------------------------------------------------

val Timer  irrigationTimer   = null

// https://community.openhab.org/t/how-to-implement-timer-with-parameter/10600/7

// If you have persistence and it does not save Strings use this rule
rule "System started"
  when
    System started
  then
    logInfo("Irrigation.0", "System Start: Irrigation .")
    // assumes no more than one is ON at a time
    val curr = gIrrigation.members.filter[v|v.state == ON].head
    logInfo("Irrigation.0", "System Start: Irrigation ..")
    
    if(curr == null) {
      Irrigation_Curr.sendCommand("None")
      logInfo("Irrigation.0", "System Start: Irrigation ...")
    } else {
      Irrigation_Curr.sendCommand(curr.name)
      logInfo("Irrigation.0", "System Start: Irrigation ....")

      // retrigger the cascade rule to recreate the timer
      curr.sendCommand(ON)
      logInfo("Irrigation.0", "System Start: Irrigation .....")
    }
    
    // any newly created timer duration variable won't be set until intitialised
    if (Irrigation_1_Time.state == Uninitialized) {
      logInfo("Irrigation.0", "System Start: Irrigation initialise state: Irrigation_1_Time")
      Irrigation_1_Time.postUpdate(5)
    }
    if (Irrigation_2_Time.state == Uninitialized) {
      Irrigation_2_Time.postUpdate(5)
      logInfo("Irrigation.0", "System Start: Irrigation initialise state: Irrigation_2_Time")
    }
    if (Irrigation_3_Time.state == Uninitialized) {
      Irrigation_3_Time.postUpdate(5)
      logInfo("Irrigation.0", "System Start: Irrigation initialise state: Irrigation_3_Time")
    }
    logInfo("Irrigation1.0", "System Start: Irrigation initialise state: done.")
    
    // does not take into account how long it has already been running
end


rule "Start Irrigation"
  when
    Item Irrigation_Allowed changed to ON
  then
    if(Irrigation_Auto.state == ON || Irrigation_Now.state == ON){
      // start with valve 1
      logInfo("Irrigation.0", "Irrigation started...")
      Irrigation_Curr.sendCommand(Irrigation_1.name)
      Irrigation_1.sendCommand(ON)
      logInfo("Irrigation.1", "... turning on {}", Irrigation_Curr.state)
    }
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
  then
    // Get the currently running valve
    val currValveName   = Irrigation_Curr.state.toString
    //logInfo("Irrigation.2", "... currValveName..: {}", currValveName)
    
    val currValveSwitch = gIrrigation.members.filter[i|i.name == currValveName].head
    //logInfo("Irrigation.3", "... currValveSwitch: {}", currValveSwitch.state)
    
    val currValveNum    = Integer::parseInt(currValveName.split("_").get(1))
    //logInfo("Irrigation.4", "... currValveNum...: {}", currValveNum)
    
    val currValveMins   = gIrrigationTime.members.filter[t|t.name == currValveName + "_Time"].head.state as DecimalType
    //logInfo("Irrigation.5", "... currValveMins..: {}", currValveMins)
    
    // Get the next running valve in the sequence
    val nextValveNum    = currValveNum + 1
    val nextValveName   = "Irrigation_" + nextValveNum
    
    // will be null if not found
    val nextValveSwitch = gIrrigation.members.filter[i|i.name == nextValveName].head

    // Create a timer to turn off curr and turn on next
    irrigationTimer = createTimer(now.plusMinutes(currValveMins.intValue), [|
    logInfo("Irrigation", "Turing off " + currValveName)
    currValveSwitch.sendCommand(OFF)

    // without the sleep commands OFF one ON the next follow too quickly
    Thread::sleep(2000)

    if(nextValveSwitch != null) {
      logInfo("Irrigation", "Turing on " + nextValve.name)
      Irrigation_Curr.sendCommand(nextValveName)
      nextValveSwitch.sendCommand(ON)
    } else {
      logInfo("Irrigation", "Irrigation is complete")
      Irrigation_Curr.sendCommand("None")
    }
    irrigationTimer = null
    ])
end

rule "Cancel Irrigation"
  when
    Item Irrigation_Allowed received command OFF
  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")
    logInfo("Irrigation", "Irrigation stopped / ended / finished.")
end

The silent change that was made (Rich’s initial line)

    // Get the next running valve in the sequence
    val nextValveName   = "Irrigation_" + currValveNum

should read (as reflected in my listing)

    // Get the next running valve in the sequence
    val nextValveName   = "Irrigation_" + nextValveNum

Persistence in rrd4j.persist:

  gIrriAll			: strategy = everyChange, restoreOnStartup

Errors I encounter…

In start-up

[ERROR] [m.r.internal.engine.RuleEngine] - Error during the execution of startup rule 'System started': cannot invoke method public abstract java.lang.String org.openhab.core.items.Item.getName() on null

which is related to this line:

    Irrigation_Curr.sendCommand(curr.name)

The next error is:

[ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Irrigation switch cascade': Cannot cast org.openhab.core.types.UnDefType to org.openhab.core.library.types.DecimalType

2

related to this line:

    val currValveMins   = gIrrigationTime.members.filter[t|t.name == currValveName + "_Time"].head.state as DecimalType

Any hints appreciated.

[2018-03-10 Updated] incorporated all changes to make this a working solution