Opinion on rule set for programmable watering system

I am a fresh beginner of openhab. I already like it. It went quite smooth and I am starting to develop my own set of rules for a watering system.

  • Platform information:
    • openHAB version: docker image openhab/openhab:2.2.0 (docker run on ubuntu 16.04)
    • mqtt: mosquitto 1.4.12 eclipse-mosquitto:1.4.2
    • embedded system: sonoff 4ch pro with ESPURNA_802DF2 1.12.6

My goal is to open a set of valves, each one at a time, for a specified amount of time (user configurable) at a certain time (user configurable) and giving the option to manual open the valves.

Here is how I (partially) translated this (watering time is in seconds for testing):

all.sitemap

sitemap all label="Main Menu"
{
	Frame label="Jardin" {
                Setpoint item=arrstarth label="Starting hour [%d h]" step=1 minValue=0 maxValue=23
                Setpoint item=arrstartm label="Starting minute [%d min]" step=1 minValue=0 maxValue=59
                Switch item=arrpelauto label="Automatic water on"
                Switch item=arrall label="Start watering all zones"

                Switch item=arrbambounord label="Bambou watering"
                Setpoint item=arrbambounordsec label="Bambou watering duration [%d s]" step=1 minValue=1 maxValue=12
                Switch item=arrpel label="Grass watering"
                Setpoint item=arrpelsec label="Grass watering duration [%d s]" step=1 minValue=1 maxValue=12
	}
}

all.items

    Number arrpelsec 
    Number arrb
    Number bambounordsec
    Number arrstarth
    Number arrstartm
    Switch arrpelauto
    Switch arrall

    Switch arrpel 
        { mqtt=">[agde:ESPURNA_802DF2/relay/0/set:command:*:default],
                <[agde:ESPURNA_802DF2/relay/0:state:MAP(onoff.map)]" }
    Switch arrbambounord 
        { mqtt=">[agde:ESPURNA_802DF2/relay/1/set:command:*:default],
                <[agde:ESPURNA_802DF2/relay/1:state:MAP(onoff.map)]" }

arr.rules

    var Timer myTimer = null
    //********************************************************************
    rule "arrall water all"
    when 
        Item arrall changed
    then
       if (arrall.state==OFF) {
          arrbambounord.sendCommand(OFF)
          arrpel.sendCommand(OFF)
       }
       else if (arrall.state==ON) {
          arrbambounord.sendCommand(ON)
       }
    end
    //********************************************************************
    rule "arrbambounord timed"
    when
        Item arrbambounord changed
    then
        if (arrbambounord.state==OFF) {
            if (myTimer!==null) {
                myTimer.cancel
                myTimer = null
        }
    }
        else if (arrbambounord.state==ON) {
            if (myTimer!==null) {
                myTimer.cancel
                myTimer = null
            }
            myTimer = createTimer(now.plusSeconds((arrbambounordsec.state as DecimalType).intValue)) [|
                arrbambounord.sendCommand(OFF)
              if (arrall.state==ON) {
                arrpel.sendCommand(ON)}

            ]
        }
    end   
    //********************************************************************
    rule "arrpel timed"
    when
        Item arrpel changed
    then
        if (arrpel.state==OFF) {
            if (myTimer!==null) {
                myTimer.cancel
                myTimer = null
        }
    }
        else if (arrpel.state==ON) {
            if (myTimer!==null) {
                myTimer.cancel
                myTimer = null
            }
            myTimer = createTimer(now.plusSeconds((arrpelsec.state as DecimalType).intValue)) [| //use now.plusSeconds() for testing
                arrpel.sendCommand(OFF)
              if (arrall.state==ON) {
                arrall.sendCommand(OFF)}
            ]
        }

What is missing yet: scheduler for beginning of the watering session, persistance and the other 5 valves.

This work pretty well so far. But before progress I would like to:

  • to be sure I did not miss certain paradigm or better design pattern.
  • to know if there is a way to refactor the code for the timing rules (arrbambounord & arrpel) as the logic is the same and a lot of duplicate code will shows if I just copy past for every valve.
  • what is the best way to send arrall.sendCommand(ON) based on hours & minutes in arrstarth & arrstarts ?

Thanks a lot for your help.

See:

You didn’t publish those rules

yes, as I have not a single clue of how to create a cron rule based on the Number arrstarth and arrstartm items and condition arrpelauto.
thank you for your pointer. I will rewrite the rules based on this new information.

You can’t set up a cron rule based on variable
Have a cron rule to check every minute if the hour and the minutes match your item state and trigger your valves

Perhaps also use the expire binding to give you an automatic shut-off if something goes wrong and the valves remain open longer than you ever expect.

e.g.

Switch arrpel 
    { mqtt=">[agde:ESPURNA_802DF2/relay/0/set:command:*:default],
            <[agde:ESPURNA_802DF2/relay/0:state:MAP(onoff.map)]", expire="1h, command=OFF" }

to give you auto-switch off after an hour.

some of the proposed things in the desing pattern can be done easier when using openHAB > 2.3.0 Build #1212
Using implicit variables and rule trigger MemberOf.

see the examples below, maybe the LED stripe example is of most interrest.

Just to name some things you could improve: one rule for all plants and group triggers, lock your timer to avoid seperate executed rules to cancle each other or better use multiple timer so they can execute in parallel…

Indeed the triggeringItem is a life saver. How safe is it to use the last snapshot in production environment ?

Thanks for the improvement ideas. It was not clear in my first post but it is by design those task are mutually exclusive. The exclusion is managed by ESPURNA

triggeringItem is available in 2.2
Member of is in 2.3

When migrating from 2.2.0 to 2.3.0-SNAPSHOT build #1272 I faced this behavior:

Rule 'arrbambounord timed': Could not cast 2 s to org.eclipse.smarthome.core.library.types.DecimalType; line 41, column 48, length 37

on this line:

myTimer = createTimer(now.plusSeconds((arrbambounordsec.state as DecimalType).intValue)) [|

I updated my rule with:

when
...
then
  logInfo("triggeringItem",triggeringItem) 

But triggeringItem is null

What is the trigger of your rule?

when
    Item arrpel changed // here triggeringItem 
    Item arrpel received update // here receivedCommand 
then
    logInfo("triggeringItem", triggeringItem.toString ) 

Actually the .toString was needed. I do not understood the error displayed without the .toString:
Rule 'arrpel timed': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.LogAction.logInfo(java.lang.String,java.lang.String,java.lang.Object[]) on instance: null
Was because of the .toString missing.
It seems I have trouble understanding the stacktrace.

when
...
then
  logInfo("triggeringItem",triggeringItem.name + " - triggered to - "+ triggeringItem.state.toString)

With triggerItem I still have an issue. I do not know how to get the duration associated.
arrbambounord has duration stored in arrbambounordsec
arrpel has duration stored in arrpel.

I do not know how to solve this case. It is like a property of arrpel or arrbambounord but I cannot use this in rules right ? Or I though about maps but I do not know how to set value to key in rules or items. Maybe concatenate the triggeringItem.name with + “sec” but how to get the value ?

put arrpel and arrbambounordsec in a group called arrgroup

var itemName = triggeringItem.name
var state = 0
if (itemName == "arrbambounord") {
    state = arrgroup.members.filter[ i | i.name == itemName + "sec" ].get(0).state as Number
} else if (itemName == "arrpel") {
    state = arrgroup.members.filter[ i | i.name == itemName ].get(0).state as Number
logInfo("VALUE", state.toString)
}
1 Like

Let`s put it simple, the compiler tries to gues what you want to do. but it is not always able to do so.
Writing

logInfo("triggeringItem", "Make compiler knows it is a string: " + triggeringItem) 

schould work, as know there is some information what you want to do. Now the compiler will call the toString funktion for you, automatically, mostly.
Just putting a Object into a function call gives not enough information on what you want.

Yes, from what I remember the toString() was automatic in java. And for a moment I though I was still writing some. When you brought the toString solution I understood what you just wrote.

However, I am puzzled by the stacktrace which seem at first quite confusing. But now I know.

there seems to be a bug. As your label is [%d s] the value for the setpoint seems to be number s which is not only a number and this string can not be converted to a DecimalType. For now you can just remove the s and have only the number. i will make a bug report and ask if this is intended to work like this.

1 Like

Thanks, did you note that this behavior changed from 2.2.0 to 2.3.0-SNAPSHOT ?