Setting up a cron based sprinkler

Thanks for posting! It is great to get examples for stuff like this. Over all the code is pretty good but I see some areas for improvement.

You don’t need to import joda’s DateTime. It gets imported automatically for you.

There is no reason to initialize a Map with null values. When the Map doesn’t contain a given key, it returns null anyway. When you set a key with a null value it removes that key and that value from the Map. So

val java.util.Map<String, Timer> sprinklerTimers = newHashMap("1"->null,"2"->null,"3"->null,"4"->null,"5"->null,"6"->null,"7"->null,"8"->null)

is equivalent to

val java.util.Map<String, Timer> sprinklerTimers = newHashMap

in all respects.

Since you imported Functions, you do not need to use the full package name for Functions$Function4. And if you import Map you won’t have to use it’s full package name either.

Functions$Function4 
    <Switch,
      Map<String, Timer>,
...

Since you already defined the types of the arguments in the <SwitchItems, ...> section, there is no reason to repeat those types here. Or because you define the types here, there is no reason to use the <SwichItems...> part. Choose one or the other. Using both is redundant.

Either

val Functions$Function4
                <SwitchItem,
                Map<String, Timer>,
                String,
                Number,
                Boolean> 
    timerLogic = [
                relayItem,
                timers,
                timerKey, 
                timeout |

or

val Functions$Function4
    timerLogic = [
                SwitchItem relayItem,
                Map<String,Timer> timers,
                String timerKey, 
                Number timeout |

Note, this second version assumes OH 2.3 Release. Prior versions will generate a warning in the logs.

You can collapse your “Zone X timer” Rules down to one Rule.

First, put the sprinklerValveX Items into a Group, let’s call it sprinklerValves

rule "Zone timer"
when
    Member of sprinklerValves changed to ON // presumably we don't want to set the timer again when it changes to OFF
then
    val num = triggeringItem.name.substring(triggeringItem.name.length() - 1));
    timerLogic.apply(triggeringItem, sprinklerTimers, num, 30)
end

Of course, now that there is only one Rule, there really is no need for the lambda any more.

rule "Zone timer"
when
    Member of sprinklerValves changed to ON // presumably we don't want to set the timer again when it changes to OFF
then
    logInfo("Logger",triggeringItem.name + " changed state") // note that the semicolons are not required except with the `return;`
    timers.get(triggeringItem.name)?.cancel // why not just use the full Item name as the key?
    timers.put(triggeringItem.name, createTimer(now.plusMinutes(30), [ |
        logInfo("Logger",triggeringItem.name + " turning off")
        triggeringItem.sendCommand(OFF)
        timers.remove(triggeringItem.name)
    ])) // I'm not a fan of putting the lambda outside the parens, it's an argument to the method call so don't hide that fact
end

But what if I want to have variable times for each zone? There are several approaches you can use. One is you can populate a Map with the Times desired. A more flexible approach would be to put the runtime in an Item so you can change it on your sitemap. We have use Design Pattern: Associated Items for this. The key is to name the Items so we can easily construct its name based on the valve’s Item name. Let’s say we append “_Runtime” to the valve name for the Number Item’s name that stores the amount of time for the zone to Run.

NOTE: There isn’t a good way to give the Item an initial value. See Design Pattern: Encoding and Accessing Values in Rules for details on how to boot strap the values stored in the _Runtime Items.

Group:Switch sprinklerValves
Switch sprinklerValve1 (sprinklerValves) { http=">[ON:GET:http://192.168.10.142:8000/gpio?pin=1?status=1] >[OFF:GET:http://192.168.10.142:8000/gpio?pin=1?status=0] <[sprinklerCache:5000:JSONPATH($.Relay1)]" }
Number sprinklerValve1_Runtime "Valve 1's Runtime [%d]"
Switch sprinklerValve2 (sprinklerValves) { http=">[ON:GET:http://192.168.10.142:8000/gpio?pin=2?status=1] >[OFF:GET:http://192.168.10.142:8000/gpio?pin=2?status=0] <[sprinklerCache:5000:JSONPATH($.Relay2)]" }
Number sprinklerValve2_Runtime "Valve 2's Runtime [%d]"
...

You can use Setpoint on the sitemap so you can adjust the Runtime for each valve from your sitemap. You can even set a min ana a max

sitemap mySitemap label="My home automation" {
    Frame label="Sprinkler" {
        Switch item=sprinklerValve1
        Setpoint item=sprinklerValve1_Runtime minValue=1 maxValue=59
...

I’ll use the Item Registry example from Associated Items DP (I’ll post the full set of Rules).

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import org.eclipse.xtext.xbase.lib.Functions
import java.util.Map

val Map<String, Timer> sprinklerTimers = newHashMap

rule "Zone 1" when Time cron "0 0 8 1/4 * ?" then sprinklerValve1.sendCommand(ON) end
rule "Zone 2" when Time cron "0 0 9 1/4 * ?" then sprinklerValve2.sendCommand(ON) end
rule "Zone 3" when Time cron "0 0 8 2/4 * ?" then sprinklerValve3.sendCommand(ON) end
rule "Zone 4" when Time cron "0 0 9 2/4 * ?" then sprinklerValve4.sendCommand(ON) end
rule "Zone 5" when Time cron "0 0 8 3/4 * ?" then sprinklerValve5.sendCommand(ON) end
rule "Zone 6" when Time cron "0 0 9 3/4 * ?" then sprinklerValve6.sendCommand(ON) end
rule "Zone 7" when Time cron "0 0 8 4/4 * ?" then sprinklerValve7.sendCommand(ON) end
rule "Zone 8" when Time cron "0 0 9 4/4 * ?" then sprinklerValve8.sendCommand(ON) end

rule "Zone timer"
when
    Member of sprinklerValves changed to ON
then
    logInfo("Logger",triggeringItem.name + " changed state")
    timers.get(triggeringItem.name)?.cancel

    // Get the runtime for this valve
    val runtime = (ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_Runtime").state as Number).intValue

    timers.put(triggeringItem.name, createTimer(now.plusMinutes(runtime), [ |
        logInfo("Logger",triggeringItem.name + " turning off")
        triggeringItem.sendCommand(OFF)
        timers.remove(triggeringItem.name)
    ]))
end