Simplifying Rule

I was wondering if @rlkoshak or anyone else is interested in helping me simply a rule.

 rule "Motion_02 Sensor Trip"
when
  Item Motion_02 changed from CLOSED to OPEN
then
  sendCommand(Camera_02, ON)
end

rule "Motion_03 Sensor Trip"
when
  Item Motion_03 changed from CLOSED to OPEN
then
  sendCommand(Camera_03, ON)
end

rule "Motion_26 Sensor Trip"
when
  Item Motion_26 changed from CLOSED to OPEN
then
  sendCommand(Camera_26, ON)
end

rule "Motion_27 Sensor Trip"
when
  Item Motion_27 changed from CLOSED to OPEN
then
  sendCommand(Camera_27, ON) 
end

I have 4 for testing, but I actually need ~20 rules. Not every camera has a motion sensor (1 does not, 18 does not as an example), but they are all named with the same number. So rather then having ~20 rules, is it possible to make one rule that takes the incoming Motion_{number} and sends a Camera_{same number} ON?

best is to search “Working With groups” Design pattern in the forum. That will help to solve your problem.

Use the triggeringItem variable.

Put all your items in the trigger condition and reference just triggeringItem in the rule.

Generate the new item name with triggeringItem.name.

I’d apply Design Pattern: Associated Items (i.e note, you don’t have to put your Camera Items into a Group for this to work since you only send a command to them and don’t need to check their state).

You now have two choices. You can use the Group/Persistence hack documented in Design Pattern: Working with Groups in Rules or use triggeringItem. I’ll present triggerinItem as it sets you up for when triggeringItem will work with Groups, hopefully by the next time a new ESH baseline is merged into OH.

Unfortunately, until the new GroupTrigger gets into the OH baseline you will need a Rule that lists all of your camera as triggers. The new triggeringItem implicit variable will be set to the Item that triggers the Rule. When the GroupTrigger gets added, you can just put the Group as the trigger to the Rule and triggeringItem will be the member of the Group that triggerd the Rule (it will be a glorious day).

Then we just do a little parsing on the triggeringItem name and construct the camera’s Item name.

rule "Camera motion sensor trip"
when
    Item Motion_02 changed from CLOSED to OPEN or
    Item Motion_03 changed from CLOSED to OPEN or
    Item Motion_26 changed from CLOSED to OPEN or
    Item Motion_27 changed from CLOSED to OPEN or
    ...
then
    val cameraNum = triggeringItem.name.split("_").get(1)
    sendCommand("Camera_"+cameraNum, ON)
end

Once the GroupTrigger gets implemented the rule would look something like (notional syntax, I’ve not followed the Issue well enough to know the true syntax):

rule "Camera motion sensor trip"
when
    GroupTrigger MotionSensors changed from CLOSED to OPEN
then
    val cameraNum = triggeringItem.name.split("_").get(1)
    sendCommand("Camera_"+cameraNum, ON)
end

I choose triggeringItem over the persistence hack because:

  • it sets you up to take advantage of the new GroupTrigger when it comes down the lline
  • it frees us from having to deal with the fact that the rule will trigger multiple times for each motion sensor trigger
  • it does not depend on timing and persistence making the rule more robust

Running into a small error:

2018-01-24 16:41:21.921 [INFO ] [clipse.smarthome.model.script.Motion] - Motion Detected in zone 03
2018-01-24 16:41:21.922 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Camera motion sensor trip’: An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(java.lang.String,java.lang.String) on instance: null

That is an odd error.

Log out “Camera_”+cameraNum, triggeringItem.name, and cameraNum.

There isn’t much here that could go wrong. This should cover all the bases. Perhaps the name of the Camera Item isn’t ending up right.

Oh, first try "ON" instead of ON in the sendCommand. Sometimes the Rules DSL has challenges converting objects to Srings.

1 Like

Yes that was it!

1 Like

So, now things are getting much more complex, I need my motion sensors send ON to the camera and then start a 30 second timer, if a new event comes in for that camera during 30 seconds it should extend the timer, if no event comes in and the timer expires it should send OFF.

I am trying to get if (“Motion_”+cameraNum+“Timer" === “null” ) to work, I know that Motion_03_Timer === null, but I can’t get the "Motion”+cameraNum+"_Timer" to evaluate to null.

Rule I am playing with:

var Integer timeOut = 30
var Timer Motion_02_Timer = null
var Timer Motion_03_Timer = null
var Timer Motion_25_Timer = null
var Timer Motion_26_Timer = null
var Timer Motion_27_Timer = null

rule "Camera motion sensor trip"
when
    Item Motion_02 changed from CLOSED to OPEN or
    Item Motion_03 changed from CLOSED to OPEN or
    Item Motion_25 changed from CLOSED to OPEN or
    Item Motion_26 changed from CLOSED to OPEN or
    Item Motion_27 changed from CLOSED to OPEN
then
    val cameraNum = triggeringItem.name.split("_").get(1)
    logInfo("Motion", "Motion detected in zone " + cameraNum)
    logInfo("Motion", Motion_03_Timer)
    logInfo("Motion", "Motion_" + cameraNum + "_Timer")
    logInfo("Motion", ("Motion_" + cameraNum + "_Timer"))
    if ("Motion_"+cameraNum+"_Timer"  === "null" ) {                      
      // create timer
      logInfo("Motion", "creating Motion_"+cameraNum+"_Timer: " + timeOut + " sec")
      Motion_03_Timer = createTimer(now.plusSeconds(timeOut))
        [ 
          logInfo("Motion", "Motion_03_Timer expired, switching OFF ")
          sendCommand("Camera_"+cameraNum, "ON")
          Motion_03_Timer=null
        ]               
      sendCommand("Camera_"+cameraNum, "OFF")
    } 
    else {
      logInfo("Motion", "rescheduling Motion_03_Timer:" + timeOut + " sec" )
      Motion_03_Timer.reschedule(now.plusSeconds(timeOut))
    }
end

I’ll recommend using Design Pattern: Expire Binding Based Timers and apply the Associated Items DP to access the Timers. It is amazing how much they simplify the code.

Note, I’m assuming you have a fixed timeOut of 30 seconds and that you do not want to dynamically calculate the timeOut at runtime.

Create a Group to hold the Motion Timer Switches and for each Motion Item create a Timer Switch.

Group:Switch MotionTimers
Switch Motion_02_Timer (MotionTimers) { expire="30s,command=OFF" }
...

In the Rule we will use Associated Items to get a reference to the Timer Item. We can cancel the Timer by postUpdate(OFF) on the Timer. We can start the Timer by sendCommand(ON). The “body” of the Timers will be put in a new Rule and we will use the same approach for the new Rule as we use for “Camera motion sensor trip.”

rule "Camera motion sensor trip"
when
    Item Motion_02 changed from CLOSED to OPEN or
    Item Motion_03 changed from CLOSED to OPEN or
    Item Motion_25 changed from CLOSED to OPEN or
    Item Motion_26 changed from CLOSED to OPEN or
    Item Motion_27 changed from CLOSED to OPEN
then
    val cameraNum = triggeringItem.name.split("_").get(1)
    logInfo("Motion", "Motion detected in zone " + cameraNum)

    val timer = MotionTimers.members.findFirst[t | t.name = "Motion_"+cameraNum+"_Timer"] as SwitchItem
    if(timer.state == OFF){
        logInfo("Motion", "Starting " + timer.name)
        timer.sendCommand(ON) // always use the sendCommand method ...
        sendCommand("Camera_"+cameraNum, "ON") // unless all you have is the name of the Item
    }
    else {
        logInfo("Motion", "Rescheduling " + timer.name)
        timer.sendCommand(ON)
    }
end

rule "Camera motion timer expired"
when
    Item Motion_02_Timer received command OFF or
    Item Motion_03_Timer received command OFF or
    Item Motion_25_Timer received command OFF or
    Item Motion_26_Timer received command OFF or
    Item Motion_27_Timer received command OFF
then
    val timerNum = triggeringItem.name.split("_").get(1)
    logInfo("Motion", "Motion timer " + timerNum + " expired.")
    seneCommand("Camera_"+timerNum, "OFF")
end

Now there are ways to make it work with Timers like the path you initially went down, but I think the above is far less complex. And again, once the GroupTrigger stuff gets merged in you can reduce all those triggers on the expired timer down to one.

If you have reasons to want to use Timers, I can present a solution that uses them instead.

One additional rule that you might want to add is a System started cleanup rule to either restart any running Timers or just turn off cameras that were running.

Assuming you have restoreOnStartup on your Timers you can reschedule the timers that were ON when OH went down:

rule "Reset camera timers"
when
    System started
then
    // schedule this to run a bit after system started so restoreOnStartup is done and the rest of the system has 
    // settled
    createTimer(now.plusSeconds(5), [ |
        MotionTimers.members.filter[timer | timer.state == ON].forEach[timer | timer.sendCommand(timer.state) ]
    ]
end

Or you can turn off the cameras that were ON when OH went down (add the Camera switches to a Group) and use restoreOnStartup on the Camera Items:

rule "Stop running cameras"
when
    System started
then
    createTimer(now.plusSeconds(5), [ |
        Cameras.members.filter[camera | camera.state == ON].forEach[camera | camera.sendCommand(OFF)]
        MotionTimers.members.filter[timer | timer.state == ON].forEach[timer | timer.postUpdate(OFF)]
    ]
end

Or you can just do a blanket reset of Cameras and Timers regardless of what state they were in before:

rule "Stop running cameras"
when
    System started
then
    createTimer(now.plusSeconds(5), [ |
        Cameras.members.forEach[camera | camera.sendCommand(OFF)]
        MotionTimers.members.forEach[timer | timer.postUpdate(OFF)]
    ]
end

All three approaches have potentials for side effects I can’t predict because I don’t know the specifics of your cameras. For example, what will a camera do if it receives multiple OFF commands? The five second delay may be too much. There is a potential for sensors to trigger in that five seconds and the cleanup rule will mess that up. You will need to experiment with the amount of time to be as small as possible but long enough that your rule doesn’t throw errors complaining about Items not existing or running before restoreOnStartup has finished.

And you may decide it isn’t worth the effort to clean up the cameras at all. In the worst case scenario you end up with cameras that remains ON after an OH reboot.

1 Like