Design Pattern: Motion Sensor Timer

It sounds so simple but how do you:

  • deal with the events that took place 61+ seconds ago? How do those time out? You can’t just keep a running count because the events have to time out over time

  • trigger the alarm immediately after the three events occur rather than having to wait up to a minute after the three events occur before triggering the alarm?

You need a timer for each ON event, not a Timer for when to turn off the siren.

Now that I’m at a computer, here is the code. It is as simple as it can be made.

import java.util.List

var List<Timer> timers = createArrayList
var Timer shutoffTimer = null
var lastRun = now

rule "SonoffMotion changed from OFF to ON 3 times in a minute"
when
    Item Sonoff1 changed from OFF to ON or
    Item Sonoff3 changed from OFF to ON
then
    // ignore the event if we are playing the siren already or the Alarm is OFF or it has been less 
    // then five minutes since the last time the siren was played
    if(shutoffTimer !== null || Alarm.state == OFF || lastRun.isAfter(now.minusMinutes(5))) return;

    // if timers.size < 2 we have not yet had three ON events in a minute
    if(timers.size < 2) {
        val t = createTimer(now.plusMinutes(1), [ | timers.remove(t) ])
        timers.add(t)
    }

    // if timers.size == 2 then this event is the thrid ON event in a minute
    if(timers.size == 2) {
        sendMail("xxx@gmail.com", "Sonoff1", "Sonoff1 motion")
        Sonoff2.sendCommand(ON)
        lastRun = now
        logInfo("RuleInfo", "Siren START")
        shutoffTimer = createTimer(now.plusSeconds(240)) [|
            Sonoff2.sendCommand(OFF)
            logInfo("RuleInfo", "Siren STOP")
            shutoffTimer = null
        ]
    }
end

It is the exact same concept but slightly “cleaner” to use Design Pattern: Expire Binding Based Timers.

Group:Number:SUM MotionEventTimers
Switch MotionEvent1Timer (MotionEventTimers) { expire="1m,command=OFF" }
Switch MotionEvent2Timer (MotionEventTimers) { expire="1m,command=OFF" }

// Add the expire to the Siren
Switch Sonoff2 ... { mqtt=... , expire="4m,command=OFF" }
var lastRun = now

rule "SonoffMotion changed from OFF to ON 3 times in a minute"
when
    Item Sonoff1 changed from OFF to ON or
    Item Sonoff3 changed from OFF to ON
then
    // ignore the event if we are playing the siren already or the Alarm is OFF or it has been less 
    // then five minutes since the last time the siren was played
    if(shutoffTimer !== null || Alarm.state == OFF || lastRun.isAfter(now.minusMinutes(5))) return;

    // if there are less than 2 ON timers, find the one that is OFF and set it to ON
    if(MotionEventTimers.state < 2) {
        MotionEventTimers.members.filter[ t | t.state == OFF ].head.sendCommand(ON)
    }

    // if timers.size == 2 then this event is the thrid ON event in a minute
    if(MotionEventTimers.state == 2) {
        sendMail("xxx@gmail.com", "Sonoff1", "Sonoff1 motion")
        Sonoff2.sendCommand(ON)
        lastRun = now
        logInfo("RuleInfo", "Siren START")
    }

    // The Expire binding will automatically turn off the siren
end

One could try to use persitence for this but because both ON and OFF events gets stored in the database I don’t think it will work. Persistence also doesn’t record NULL so we can’t know if the event was changing from OFF to ON.