Variables in Rules what is wrong with this?

I am trying to maintain a variable which can be used in several rules to show whether is it dark. The first rule poles every minute and should populate the var isdark as true or false. This seems to work as the logs are accurate.

The second rule though just gets an error on compile - saying that it can’t resolve the variable isdark. How do I pass the info from one rule to another?

import org.joda.time.*
import org.openhab.core.library.types.*
import org.openhab.core.library.types.PercentType
import org.openhab.core.library.items.SwitchItem
import org.openhab.model.script.actions.*
import org.openhab.model.script.actions.Timer
import org.openhab.core.persistence.*
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.ArrayList
import java.util.Map
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

import java.util.Date

var Number office_group_counter = 0

rule “test whether it is dark”
when
Time cron “0 * * * * ?”
then
var DateTime daystart = new DateTime((dawnStart.state as DateTimeType).calendar.timeInMillis)
var DateTime dayend = new DateTime((duskEnd.state as DateTimeType).calendar.timeInMillis)
var boolean isdark = now.isBefore(daystart) || now.isAfter (dayend)
logInfo(“Alarm”, "variable daystart = " + daystart)
logInfo(“Alarm”, "variable dayend = " + dayend)
logInfo(“Alarm”, "timenow = " + now)
logInfo(“Alarm”, "is it dark = " + isdark)
end

rule “turn on lights if dark and movement detected”
when
Item ZONE12_GENERAL_STATUS received update OPEN
then
if (office_ceiling.state == OFF && isdark){
sendCommand(office_ceiling, ON)
office_group_counter = office_group_counter + 1
logInfo(“Alarm”, "movement in office and is it dark = " + isdark)
}
end

Maybe it’s because isDark is a local (rule) and not a global variable?

Regards
Dieter

1 Like

@DieterL is correct. You need to define the isdark variable globally, like you did with office_group_counter.

However, I’ve found it to be more flexible and less error prone to set flags like these in an Item rather than as a rule variable. You can then reference the state from other rules files and take advantage of persistence retoreOnStartup, and use it as a rule trigger should you decide to. It also lets you separate the logic that determines these sorts of flags from the logic that cares about it.

1 Like

Thanks that is what I tried first using an example that’s on the openhab wiki.

I get the following error if I try putting the Variable at the top:

04 17:05:43.982 [WARN ] [m.r.i.engine.RuleContextHelper] - Variable ‘isdark’ on rule file ‘alarm.rules’ cannot be initialized with value ’ || ': An error occured during the script execution: The name ‘daystart’ cannot be resolved to an item or type.

Also in the log instead of getting isdark = false or true it becomes null??

import org.joda.time.*
import org.openhab.core.library.types.*
import org.openhab.core.library.types.PercentType
import org.openhab.core.library.items.SwitchItem
import org.openhab.model.script.actions.*
import org.openhab.model.script.actions.Timer
import org.openhab.core.persistence.*
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.ArrayList
import java.util.Map
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import java.util.Date

var Number office_group_counter = 0
var Number landing_counter = 0
var DateTime daystart = new DateTime((dawnStart.state as DateTimeType).calendar.timeInMillis)
var DateTime dayend = new DateTime((duskEnd.state as DateTimeType).calendar.timeInMillis)
var boolean isdark = now.isBefore(daystart) || now.isAfter (dayend)

rule “test whether it is dark”
when
Time cron “0 * * * * ?”
then
logInfo(“Alarm”, "variable daystart = " + daystart)
logInfo(“Alarm”, "variable dayend = " + dayend)
logInfo(“Alarm”, "timenow = " + now)
logInfo(“Alarm”, "is it dark = " + isdark)
end

rule “turn on lights if dark and movement detected”
when
Item ZONE12_GENERAL_STATUS received update OPEN
then
if (office_ceiling.state == OFF && isdark){
sendCommand(office_ceiling, ON)
office_group_counter = office_group_counter + 1
logInfo(“Alarm”, "movement in office and is it dark = " + isdark)
}
end

I’ve tested some more and it seems the problem is the line

val boolean isdark = now.isBefore(daystart) || now.isAfter (dayend)

If I include this in each rule then it runs. This seems a bit inefficient as I have about 25 detectors which need coding. Can someone explain a better way to do this.

Hi Rich just saw this must have arrived whilst I was typing…

So I need to find a way to put this into a switch - it there somewhere that tells me how to do this? It would also need to initialise somehow - does that happen at startup or do you need to poll?

Thanks

At the top of the rule define the variable and initialize it to something:

var boolean isdark = false

In your rules is where you really set it. In your rules you do not need to redefine it so do not use “var” or “val”:

isdark = now.isBefore(daystart) || now.isAfter(dayend)

Two important things:
You must use “var” instead of “val”. A “val” is final meaning once you set it to somthing you can never change it.
You can define a variable globally and set it locally.

Assuming you want “isdark” to actually represent night (i.e. the time between sunset and sunrise with or without some buffer), you might be happier with a solution similar to the example I posted here. Otherwise you will be forced to keep every rule that needs to know isdark to reside in the same file, plus you lose all the other benefits I site above.

Whenever you see polling like this in a rule (i.e. your cron to run every minute) you should stop and consider whether there is a better way. I’ve yet to find a case where there wasn’t.

In this particular case the Items and Rules would look like the following:

Items:

Switch Sunset_Event   { astro="planet=sun, type=set, property=start" }
DateTime Sunset_Time  { astro="planet=sun, type=set, property=start" }
Switch Sunrise_Event  { astro="planet=sun, type=rise, property=start" }
DateTime Sunrise_Time { astro="planet=sun, type=rise, property=start" }
Switch Is_Dark

Rules:

// Rules to set and unset Is_Dark
rule "Start Dark"
when
    Item Sunrise_Event received update
then
    Is_Dark.sendCommand(OFF)
end

rule "End Dark"
when
    Item Sunset_Event received update
then
    Is_Dark.sendCommand(ON)
end

rule "turn on lights if dark and movement detected"
when
    Item ZONE12_GENERAL_STATUS received update OPEN
then
    if(office_ceiling.state == OFF && Is_Dark.state == ON) {
        sendCommand(office_ceiling, ON)
        office_group_counter= office_group_counter + 1
        logInfo("Alarm", "movement in office and is it dark = " + Is_Dark.state)
    }
end

NOTE: I just typed this in, there might be a typo. Also, this assumes that you have persistence set up and have restoreOnStartup strategy enabled and applied to your items, in particular Is_Dark.

NOTE 2: If you want to make sure Is_Dark is always accurate, there is one case where openHAB went down before the Sunrise_Event or Sunset_Event and came up after. In that case you can create a System Started triggered rule and check the Sunrise_Time and Sunset_Time to see if you are inside that time period and manually set Is_Dark as appropriate.

1 Like

Wow Rich thanks very much for this comprehensive answer - this makes a lot of sense. I did try using a Var for the isDark but from what you are saying they must be set locally. The switch element looks like a good solution.

I also looked at the example you posted where by using groups you can streamline rules quite a bit.

In my case I have lights which I want to turn off after a certain period following no motion being detected. My current strategy is to have a counter for each light, which turns on the light, and increments each time there is movement. There is another cron based rule for no movement, which resets the counter to zero, then reduces it by one every minute if there is still no movement. That way I can tell each light to turn at any point based on a chosen negative number.

Is it possible to have one rule which would run timers for many lights or is this asking too much?

Not exactly. But apparently the rules engine doesn’t like you to initialize a global variable using || or &&. Also, the globals get initialized at startup so whatever time it was when isdark was initialized would have been its state forever. So what you need to do (so you do it correctly in the future) is use a “var” if you know you need to change it instead of “val”, give it an initial value, and then update that value in your rules. When you update it, you don’t use “var”.

Oops! A poll! There must be a better way. :wink:

The standard approach for dealing with motion controlled lighting is to use timers. And I’m a strong proponent of using Groups. So, if all the lights you want to turn on are members of gMC_Lights and your sensor is Motion a simple rule like this will turn off the lights five minutes after the last motion was detected.

var Timer timer = null

rule "Motion controlled lights"
when
    Item Motion received update
then
    if(timer == null) {
        gMC_Lights.members.forEach[s | s.sendCommand(ON)]
        timer = createTimer(now.plusMinutes(5), [|
            gMC_Lights.members.forEach[s | s.sendCommand(OFF)]
            timer = null
        ])
    } else {
        timer.reschedule(now.plusMinutes(5)
    }
end

What the rule does is if there is no timer turn on all the lights and set a timer for five minutes from now with code to turn off the lights. If the timer exists, reschedule it to go off five minutes from now.

You will likely want to add some logic to handle cases where the lights are already on, or you want to override the timer so they just stay on even without motion but this should get you started.

This rule also works slightly differently from how your approach works, if I’m reading it correctly. In your approach, if you are moving around a whole lot, the amount of time after the last motion detection before the lights turn off will be longer than if you only moved a little bit. If that is indeed how it works and that is how you want it to work, stick with what you have, only you can operate on the Group instead of each light individually.

Thanks - so I see how the groups work for the lights now but in my case I also have many motion detectors all around the house each controller one or more lights independantly. Can I also group the motion detectors?

That is a bit trickier because you care about each motion sensor individually, not as a Group. In other words, if one sensor goes off you have a specific set of lights to turn on but if another sensor goes off you have a different set of lights. You don’t want to turn on all lights if any motion sensor goes off.

The big problem is there is not a direct way to get the specific item that triggered an update to the group in your rule. There is a hackey way to get at it which may or may not be suitable in this situation but it requires you have persistence set up, but given another problem it might not be worth pursuing.

We have another problem because there needs to be a way to map the motion sensor to the Group of lights it is associated with. You can’t get a reference to a Group or an Item if all you have is its name (unless you have them in another Group and filter the items by name.

So, the tl;dr is that it is possible but it is a bit hackey and understanding the code that does it requires a lot of expertise on how openHAB works and how the rules engine works. It also may not be any shorter unless you have a lot of motion sensors.

So, if you have only three or four motion sensor/lights groupings I would just use a separate rule for each motion sensor. However, if you just want to know how to do it, or you have a lot of motion sensor pairs we can go through how it would work. But I’ll need you to post your Items so I can get a handle on of how to organize the Groups.

I’ve been playing around today with the options and I think you’re right, It is just as easy to create individual rules. This gives a bit more flexibility for each zone in terms of when and how long the lights come on.

The main thing is I got the isdark function working which works nicely using a Sytem Started rule to make sure it is correct on startup and an event rule to hopefully keep it in sync.

Thanks again for the help.