PIR Sensor trigger if event occured more than x times in timespan

I want to use a PIR for intrusion detection. Unfortunately the sensor is very sensitive and triggers false positives. Therefore, I want to proxy my PIR item and add a rule:

  1. If motion detected 3 times in 1 Minute then it’s actually an event.
  2. When motion is detected I want to do something and undo it after a while.

My current work:

Contact PIR_GPIO "PIR GPIO" { gpio="pin:27 activelow:yes force:yes" }
Switch PIR "PIR [MAP(pir.map):%s]" {expire="60s,command=OFF"} // Send OFF command after 5 minutes
    rule "Handle PIR_GPIO Events"
    when
        Item PIR_GPIO changed from OPEN to CLOSED
    then
        logInfo("PIR_GPIO Event", "changed from OPEN to CLOSED")
        PIR.sendCommand(ON)
    end

    rule "Start PIR Actions"
    when
        Item PIR changed from OFF to ON
    then
        logInfo("Motion", "update " + PIR.state)
        // if resident is not present
        if (androidOPTO6.state == CLOSED) {
            logInfo("Motion", "Unlocking trapdoor")
            //Camera_Send.send(ON);
        } else {
            logInfo("Motion", "detected motion, but resident is present")
        }
    end

    rule "Stop PIR Actions"
    when
        Item PIR received command OFF
    then
        logInfo("Motion", "Stopping PIR actions. TODO")
        //Camera_Send.send(OFF);
    end

The second requirement is implemented with the expire binding and should work. But how to count the number of events during a timespan? Do I really have to manage a timer on my own or is there a counterpart to the expire binding to do such things? I just removed a timer for the requirement 2 in favor of the expire binding - that’s why I’m curious.

Somehow or other, yes. Selecting expire binding as your choice of tool is one way to manage that.

What I’ve done with external PIRs that get tripped by rabbits etc. is implement an actual counter, a Number Item. PIR activation triggers a rule to increment the counter. The PIR has its own short duration 5 or 10 secs, which reduces multi triggers on its own account.
Counter is set up with an expire of minutes that resets it to zero when activity ceases.

Then it’s a case of choosing your threshold e.g. when counter changes 3 to 4, then ring bells. You can implement several thresholds, e.g counter 3->4, turn on lights. Counter 7->8, call cops.

This technique can be used with several PIR covering a larger area, but only one “activity counter” for that zone.

1 Like

I found some inspiration in comments of the tutorial.

    Contact PIR_GPIO "PIR GPIO" { gpio="pin:27 activelow:yes force:yes" }
    Switch PIR "PIR [MAP(pir.map):%s]" {expire="10s,command=OFF"} // Send OFF command after time x
    Group:Number:SUM MotionEventTimers "Number of active timers [%d]"
    Switch MotionEvent1Timer (MotionEventTimers) { expire="1m,command=OFF" }
    Switch MotionEvent2Timer (MotionEventTimers) { expire="1m,command=OFF" }
    var lastRun = now
    val numTimers = MotionEventTimers.members.size

    rule "Handle PIR_GPIO Events"
    when
        Item PIR_GPIO changed from OPEN to CLOSED
    then
        
        logInfo("PIR_GPIO Event", "changed from OPEN to CLOSED")

        // ignore the event if it has been less 
        // then n seconds since the last time or PIR state is on already
        if(PIR.state == OFF && lastRun.isAfter(now.minusSeconds(20))) {
            logInfo("PIR_GPIO Event", "Ignoring event")
            return;
        }

        logInfo("PIR_GPIO Event", "MotionEventTimers == " + MotionEventTimers)

        logInfo("PIR_GPIO Event", "MotionEventTimers.state == " + MotionEventTimers.state)
        // if there are less than numTimers ON timers, find the one that is OFF and set it to ON
        if(MotionEventTimers.state < numTimers) {
            logInfo("PIR_GPIO Event", "MotionEventTimers.members: " + MotionEventTimers.members)
            MotionEventTimers.members.filter[ t | t.state != ON ].head.sendCommand(ON)
            logInfo("PIR_GPIO Event", "Setting " + MotionEventTimers.state + "/" + numTimers + " Timer ON")
        }

        logInfo("PIR_GPIO Event", "MotionEventTimers.state == " + MotionEventTimers.state)

        // if timers.size == numTimers then this event is the numTimers + 1 ON event in a minute
        if(MotionEventTimers.state == numTimers) {
            lastRun = now
            PIR.sendCommand(ON)
            logInfo("PIR_GPIO Event", numTimers + " timers ON")
        }
       
    end

Log:

[script.PIR_GPIO Event] - changed from OPEN to CLOSED
[script.PIR_GPIO Event] - MotionEventTimers == MotionEventTimers (Type=GroupItem, BaseType=NumberItem, Members=2, State=0, Label=Number of active timers, Category=null)
[script.PIR_GPIO Event] - MotionEventTimers.state == 0
[script.PIR_GPIO Event] - MotionEventTimers.members: [MotionEvent2Timer (Type=SwitchItem, State=OFF, Label=null, Category=null, Groups=[MotionEventTimers]), MotionEvent1Timer (Type=SwitchItem, State=OFF, Label=null, Category=null, Groups=[MotionEventTimers])]
[script.PIR_GPIO Event] - Setting 1/2 Timer ON
[script.PIR_GPIO Event] - MotionEventTimers.state == 1
[script.PIR_GPIO Event] - changed from OPEN to CLOSED
[script.PIR_GPIO Event] - MotionEventTimers == MotionEventTimers (Type=GroupItem, BaseType=NumberItem, Members=2, State=1, Label=Number of active timers, Category=null)
[script.PIR_GPIO Event] - MotionEventTimers.state == 1
[script.PIR_GPIO Event] - MotionEventTimers.members: [MotionEvent2Timer (Type=SwitchItem, State=ON, Label=null, Category=null, Groups=[MotionEventTimers]), MotionEvent1Timer (Type=SwitchItem, State=OFF, Label=null, Category=null, Groups=[MotionEventTimers])]
[script.PIR_GPIO Event] - Setting 2/2 Timer ON
[script.PIR_GPIO Event] - MotionEventTimers.state == 2
[script.PIR_GPIO Event] - 2 timers ON
[script.Motion] - update ON
[script.Motion] - detected motion, but resident is present
[script.Motion] - Stopping PIR actions. TODO

Note: I have used t.state != ON instead of t.state == OFF because the state was UNDEFINED and therefore not matched in the filter.
I have to tweak the timing because it doesn’t meets my requirement yet and test it in production.

just to add to this topic something similar what I was solving on my PIR’s

if you are using Tasmota firmware with PIR sensors and getting a lot of false alarms you can solve it with rules inside tasmota so into the MQTT are going only “real” movements.

PIR HC-SR501, delay around 5-6s, jumper: repeat trigger

    Rule1 
        on switch1#state=1  do RuleTimer1 20 endon
        on switch1#state=0  do RuleTimer1 0  endon
        on Rules#Timer=1    do backlog publish home/entrance/sensor/MOTION ON; RuleTimer2 90 endon
        on Rules#Timer=2    do publish home/entrance/sensor/MOTION OFF endon

What this rule basically doing is:

  1. when sensor is triggered it fire up 20s timer1
  2. when there is PIR off from the sensor within 20s, timer1 gets removed. (under 20s it is considered as false trigger)
  3. if PIR is sensing longer trigger, eg. it’s not false and actually there is a movement, after 20s of the first timer it will publish MQTT msg and fires another timer2 which lasts for 90s
  4. after timer2 expires MQTT will receive OFF msg

if there is another PIR trigger within 90s everything gets reinitialized and 90s counter counts from 0. So on and so on.

No more flooded mqtt server with false msgs.
sensitivity of the PIR sensors together with Wemos is ridiculously high especially when they are in close distance from each others, as wifi interfere with PIR a lot.
Anyway, this solves it inside given device so ON/OFF can be used in OH directly as proper detected movement.

Cheers

tiny update.
I’ve replaced HC-SR501 with AM312 (those tiny ones) and they have 0 false detections at all!
just simply briliant. HC-SR501 were just too sensitive to light and wifi

these small AM312 are perfectly capable of sensing regular house live and they can be placed anywhere thanks to their tiny dimensions:)