Rule Example Wasp in Box

This is a very simple implementation of the ‘wasp in box’ algorithm for detecting overall presence. In home automation very often, we want the system to do certain things, but only if someone is home. Thus, this concept can almost be considered a foundation for lots of other decision making. This example is a very rudimentary implementation but I run this rule and it works in openHAB on my system and I hope others can learn from this simple example or use it for a starting point and improve it.
The Concept:
How it works is that if the wasp is in the box, sometimes we can hear him in there buzzing around. So long as the lid of the box is not opened, we can be assured the wasp is still in the box, even if he goes quiet for awhile. (he’s sleeping) But if the lid of the box is opened, we don’t know, the wasp could have flown away. But once the lid of the box is closed again, if we hear him in there buzzing around, we know the wasp is in the box again. If we don’t hear any buzzing, he must have flow away.
How does this work in helping us discern occupancy? Well, we need a door sensor on the lid of the box. Then we need a way of listen for buzzing, a motion sensor will work. When the lid of the box is opened, we listen for buzzing. If we hear buzzing (motion) once the lid is back on the box, we know the wasp is in the box. Then, until the next time the lid of the box is opened, whether we hear buzzing or not, we know the wasp is still in the box. If the lid of the box got opened and we haven’t hear any buzzing, we can only assume the wasp flew away.
Caveat:
I live in a 3rd floor flat with one entrance door. If there is more then one entry point in your home, obviously, we’ll need more sensors. I also live alone so more then one individual coming and going would also make things more complex. If you are attempting to track many people or a cat, you are on your own, try not to get hurt. (search cat flap for explanation… and laughs)
First we need some items created in an items files. If I’ve already lost you, go to the bottom of this post and dig into some of the docs and come back later. We need a contact sensor on the door (the lid of the box) and we need a motion sensor or some other way of listening for buzzing. Motion sensors can be flakey. In my case, I have a motion sensor pointed down the stairwell leading to the front door. You can neither exit or enter without tripping this motion sensor. (unless you are spiderman) There are also no windows in the direction the motion sensor is aimed or anything which could trigger a false alarm. I’ve also used persistence (with influx and grafana) to make sure things work as I hoped. But not good enough, I also use my phones presence on my wifi router, using the network binding to double check. Here we go… real physical items

Contact aeotecDoor  "Aeotec Door Sensor"    <door>      (ghome) {channel="zwave:device:ffa81412:node4:sensor_door"}
Switch fibMoAlarm   "Fibaro motion alarm" <alarm>   (ghome) {channel="zwave:device:ffa81412:node2:alarm_motion"}
Switch      foneIsHome      "Daddys phone is home"  <none>  (ghome) {channel="network:pingdevice:5e529855:online"}

next we need some ‘dummy’ items (all the items can go in one file) A dummy item is an item which is not linked to any binding and simply holds information for us. (see further reading section below if you don’t get this)
we need a DateTime item to hold the last time the lid of the box (door) opened
we need a DateTime item to hold the last time we heard a buzzing sound (motion)
we need a Switch to indicate if the wasp is in the box or not
and we need a Switch item to use with the expire binding as a timer

DateTime    lastBoxOpen     "last time door opened"         <none>  (ghome)
DateTime    lastBoxBuzz     "last time motion deteted"      <none>  (ghome)
Switch      WaspInBox       "Wasp in Box switch"            <none>  (ghome)
Switch      boxTimer        "timer used for wasp in box"    { expire="60s,command=OFF" }

We are also using a number to judge ‘confidence’ in our decision making. How ‘confident’ are we that the wasp really is in the box. We’ll get into it more in a minute but for now, understand confidence is judged by a number, zero (0) means we aren’t very sure and 100 means we are absolutely sure.

Number      WaspConf        "Wasp in Box confidence level"  <none>  (ghome)

OK… now for some rules.
first rule triggers when the lid of the box opens

// wasp in box
rule "boxOpened"
when
	Item aeotecDoor changed from CLOSED to OPEN
then
	var timex = new DateTimeType(now.toString)
	lastBoxOpen.sendCommand(timex)
	WaspConf.sendCommand(0)
	boxTimer.sendCommand(ON)
	WaspInBox.sendCommand(OFF)
end

A few things happen here. 1. we set the time the door opened. 2. we set the wasp confidence level to zero because the lid is open and we have no idea if the wasp is out or not. 3. we start the timer for 60 seconds. 4. we set the wasp in box switch ‘OFF’ (the wasp is out!)
next rule

rule "buzzInBox"
when
	Item fibMoAlarm changed from OFF to ON
then
	if (WaspInBox.state == ON) return;
	var timey = new DateTimeType(now.toString)
	lastBoxBuzz.sendCommand(timey)
	boxTimer.sendCommand(OFF)
end

first thing we do here is check if we are wasting time. A good thing to do at the very begining of a rule is decide if we even need to run the rule. If we don’t, why waste a running thread right? So, if the wasp is in the box, abort execution of the rule and exit using the return statement. (don’t forget the semicolon) Next, record the time motion was triggered. Then, just for kicks, short circuit the timer by sending the ‘OFF’ command to the expire switch item. (why wait, we hear a buzzing)
Now a rule when the expire binding switch turns to ‘OFF’. 60 seconds have expired since the lid of the box opened (or the timer got restarted)

rule "boxTimerExpired"
when
	Item boxTimer changed from ON to OFF
then
	var tally = WaspConf.state as Number
	if (tally > 74) return;
	val openTime = new DateTime(lastBoxOpen.state.toString)
	val motionTime = new DateTime(lastBoxBuzz.state.toString)
	if (foneIsHome.state == ON) { tally = (tally + 20) }
	if (foneIsHome.state == OFF) { tally = (tally - 20) }
	if ( openTime.isBefore(motionTime) ) { tally = (tally + 60) }
	if ( openTime.isAfter(motionTime) ) { tally = (tally - 5) }
	if (tally >= 100){WaspConf.sendCommand(100)}
	if (tally <= 0){WaspConf.sendCommand(0)}
	if (tally > 0 && tally < 100){
		WaspConf.sendCommand(tally)
		boxTimer.sendCommand(ON)
	}
end

OK… follow along and we’ll walk thru this one step at a time. First, we create a variable ‘tally’ to keep a running total of our ‘confidence’ level. We set the initial value of this running total of our confidence level to our dummy item WaspConf, which if the door just opened is set to zero. Next we check the last time the box opened and the last time we heard buzzing. We also check if the phone has logged on to the local wifi network. If my phone is on the network plus 20 or else if not minus 20. If motion has occurred since the door opened plus 60 if not minus 5. Write the value to our confidence level and restart the timer
Now a rule to react to confidence level changing

rule "waspConfChange"
when
	Item WaspConf changed
then
	if (WaspConf.state == 0) return;
	if (WaspConf.state >= 75){ WaspInBox.sendCommand(ON)
	} else { WaspInBox.sendCommand(OFF) }
end

simple, this is our threshold level. if confidence is over 75, the wasp is in the box.

One last thing, if the system restarts, things could be left in a funny state. This rule is not fully explained but it is just to set the ‘dummy’ or unbound items to something if the system restarts so the values aren’t null.
Since for instance, if the value of ‘WaspInBox’ is marked as out and motion is triggered, the system will self correct, we don’t have to worry to much about setting the values correctly, just that they are not set to null.

rule "initializeSys"
when
    System started
then
	// initialize wasp in box unbouund item values from null
	WaspInBox.postUpdate(OFF)
	WaspConf.postUpdate(0)
	boxTimer.postUpdate(OFF)
	lastBoxBuzz.sendCommand(new DateTimeType(now.toString))
	lastBoxOpen.sendCommand(new DateTimeType(now.toString))
end

So, if everything goes to plan, when the door opens, the timers starts for 60 seconds. My phone logs on, motion short circuits the timer and… Alexa says welcome home

rule "waspCameHome"
when
	Item WaspInBox changed from OFF to ON
then
	alexaSpeak.sendCommand("Welcome home Andy")
end
/// end wasp in box code

or, the loop runs again. In my case, when I leave, sometime the wifi drops out at the bottom of the stairwell. Then, when I’m walking across the parking lot to the truck, it picks me up again. When I come home, sometimes it take a minute or two for the phone to log on. Obviously the ‘confidence’ levels can be adjusted. This simple example actually seems to work 100% accurately for me however. Once we have this working, we can check at the beginning of other rules like so…

// not running code... just snipe
if (WaspInBox.state == ON){
	//do stuff
}

Also to consider, when your loop runs down, if there is motion but no phone, maybe an intruder? Obviously, you have to decide based on your set up, your confidence in the system reporting accurately, but this popular algorithmn works a treat even in it’s most simple form.
If you find mistakes please post or if you use this and it works or helps you do even cooler stuff, post it up!

Further reading:

Rich explains unbound items, which I call ‘dummy’ items

another rule example this one was based on


Items documentation

Expire binding documentation

extend syntax documentation
https://eclipse.org/xtend/documentation/203_xtend_expressions.html

18 Likes

Thanks for posting! Great tutorial! I’ve moved it to the Tutorials category.

The code is clear and easy to follow as is the explanation.

The if statements in the boxTimerExpired Rule could be consolidated just a little but it’s not worth the effort. It’s simple and easy enough to understand as it is.

thanks Rich

yes, that was what I was shooting for.

I’ve altered this rule on my system because I’ve learned a few things and I am going to update the first post to reflect the changes but use this post to discuss the changes so as to not clutter the example.

First change is to the system start up rule. Here is a thread I’ve started to discuss this issue. What I am beginning to realize is that the practice of using what I am now calling ‘unbound’ items, in other words, an item which is not linked to a binding and used instead to hold a value requires these values to be set at system start or bad things can happen.

I have used unbound items since before I even attempted writing rules but I recently questioned the practice until I stumbled across this design pattern by Rich promoting the technique. Since eventually a cron rule or some other process was setting these items to a value, I hadn’t noticed this problem sooner. (beginner’s luck)

So on to the first fix. The original system start up rule was almost an after thought in this example. What I originally did was simply make a guess using the time of day and day of week. Not only did this not work very well because of how things work during start up, I now believe this went against the whole principle of how the algorithm is supposed to work. Here is the original system start up rule

rule "initializeSys"
when
    System started
then
	// initialize wasp in box value from null
	// if M-F 9-5 off & otherwise on
	var dayofweeknum = DayOfWeek.state as Number
	if(dayofweeknum < 6 && now.getHourOfDay() >= 09 && now.getHourOfDay() < 17){
		WaspInBox.postUpdate(OFF)
	} else { WaspInBox.postUpdate(ON) }
	// post update only so wasp in box is not null 
end

The first step is to understand that at system start up, rules files are loading and the persistence system is restoring values that are set to restoreOnStartup at the same time. At this time the system is very busy and operating under heavy load. There are no guarantees of the rules files loading or item values being restored in any particular order. This is how OpenHAB works and may change in some future version but for now it is best to work with how the system works.
Rich suggests using MapDB for restoring item state using restoreOnStartup because MapDB is an embedded database running within OpenHAB. This means is should run a little quicker and encounter less problems

The second problem I had with this rule is that if the system restarts, we really don’t know the status of the wasp, the box is effectively open. Rather then guess, I decided to set the wasp as gone. I looked at the rest of the rules and made further changes to ensure the rule would ‘self correct’. In other words, if the system restarts, and the Wasp is mark as ‘out’ but then motion is detected, the rules will automatically switch the wasp to ‘in the box’.

So in the end, the strategy I choose for the system started rule is to simply set the unbound items to values which indicate that the box has opened and wasp presence is unknown.

rule "initializeSys"
when
    System started
then
	// initialize wasp in box unbouund item values from null
	WaspInBox.postUpdate(OFF)
	WaspConf.postUpdate(0)
	boxTimer.postUpdate(OFF)
	lastBoxBuzz.sendCommand(new DateTimeType(now.toString))
	lastBoxOpen.sendCommand(new DateTimeType(now.toString))
end

I’ve chosen to use postUpdate so this rule merely sets the values so they are not NULL. It causes no other effect. I also choose to use ways of setting the values with out requiring items to be initialized. The two switches are set to OFF and the number item set to zero. The two DateTime items I use the now command, setting the lastBoxBuzz first so it will be older (indicating non-presence )

Hi @Andrew_Rowe,
thank you for this tutorial, which seems very interesting to me.

I have some questions and hope you can answer them.

  1. Are I’m correct that the network binding/mobile phones cannot be used to identify “buzzInBox”?
    The mobile phone could connect to the network even if I do not enter my home. E.g. sitting in the garden or walk on the street.
    My assumption is that only sensors that can be triggered within the house can be used.

  2. Please can you explain the confidence in more detail.
    It’s great that you have added a confidence calculation already, but unfortunately I cannot get how it works.
    Lets assume you left the home at 8:00 AM. The wasp is out of the box.
    Now there is a false alarm triggered by the motion sensor at 9:00 AM
    Calculation:

  • foneIsHome == OFF --> -20
  • openTime.isBefore(motionTime) --> +40
    One minute later the rule will be executed once again.
  • foneIsHome == OFF --> 20
  • openTime.isBefore(motionTime) --> +60
    Another minute later the rule will be executed once again.
  • foneIsHome == OFF --> 40
  • openTime.isBefore(motionTime) --> +100
    –> wasp in the box is now set to ON
    One (false) event set the wasp in the box to ON, which follows the algorithm, but the calculation just delays the behavior.
  1. How works your motion sensor? Does it make sense to trigger the rule “buzzInBox” with “received update”.
    My motion sensor triggers just the ON event, but not the OFF event. I’m using my motion sensor in combination with the expire binding (10 min).
    Lets assume the motion sensor recognized motion and set the switch item to ON for at least 10 mins. Now the door will be opened, but I’m coming back within 10 minutes.
    The motion sensor wouldn’t change from OFF to ON, so the wasp in the box will stay OFF.

I have some further questions, but honestly I’m not sure if I should post them here or if would be better to create a new topic for those questions.
4. I would like to combine @rlkoshak’s Generic Presence Detection Tutorial with your tutorial. The most important advantage is to have an anti-flapping timer and also the possibility to use the network binding for presence detection.

What do you both think about the following approach?

Switch Presence "Someone is Present" <present> // master presence switch, represents to the rest of OH where someone is home
Group:Switch:AND(OFF,ON) Presence_Sensors <present> 
Switch Presence_Timer { expire="5m,command=OFF" } // anti-flapping timer
Switch MobilePhonePerson1 (Presence_Sensors)
Switch MobilePhonePerson2 (Presence_Sensors)
Switch WaspInBox (gPresence_Sensors) 
Switch DoorSensor
Group gWaspSensors // all presence sensors in the building belong to this group
Switch MotionSensor1 (gWaspSensors) { expire="10m,command=OFF" }
Switch ... (gWaspSensors)
rule "Reset Presence and sensors to OFF on startup"
when
    System started
then
    Presence.sendCommand(OFF)
    Presence_Sensors.sendCommand(OFF)
end
rule "A presence sensor updated"
when
        Item Presence_Sensors changed
then

    if(Presence_Timer.state == ON && Presence_Sensors.state == Presence.state) {
        logInfo(logName, "Timer is running but group and proxy are the same, cancelling timer")
        Presence_Timer.postUpdate(OFF)
    }
    else if(Presence_Sensors.state == Presence.state) {
        logInfo(logName, "No timer and both group and proxy are the same, nothing to do")
        return;
    }

    if(Presence_Sensors.state == OFF) {
        logInfo(logName, "Everyone is away, setting anti-flapping timer")
        Presence_Timer.sendCommand(ON)
    }
    else if(Presence_Sensors.state == ON) {
        logInfo(logName, "Someone came home, setting presence to ON")
        Presence.sendCommand(ON)
    }

end
rule "Presence timer expired, no one is home"
when
    Item Presence_Timer received command OFF
then
    logInfo(logName, "Everyone is still away, setting presence to OFF")
    Presence.sendCommand(OFF)
end
rule "buzzInBox"
when
    Member of gWaspSensors received update //Use received update not changed, because of expire binding on Motion Sensors
then
    Wasp_in_Box.sendCommand(ON)
end
rule "boxOpened"
when
    Item DoorSensor changed to ON // OPEN
then
    Wasp_in_Box.sendCommand(OFF)
end
  1. With the approach above, the presence switch could be set to ON before I open the door. (Based on the network binding). I’m looking for an option to set the presence to ON, earliest when the door will be opened. Could you give some hints how to implement this requirement?

Thanks in advance :slight_smile: