Design Pattern: Sensor Aggregation

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for an explanation of what a DP is and how to use them.

Problem Statement

There are often times where one has a number of sensors of different types which aggregate up to a single status value. The most common of these is presence detection, where one has multiple sensors (PIR, Bluetooth, Network, etc.) any one of which can indicate someone is home (or at least their device is home). Another use for this is rolling up the online status of a bunch of services.

Concept

image
This Design Pattern works best when one can represent the state of all relevant sensors as a Switch or a Number. These switches can be combined and aggregated into Groups and the state of the Group then represents the final state (e.g. whether someone is home).

Simple Example

In this example there are two people who are detected by the positions of their phone. Each phone has multiple ways to detect its presence. The specific bindings/sensors used is irrelevant for this approach so long as the state can be represented as a Switch.

Items

Group:Switch:AND(OFF,ON) gPresent "Presence [%s]"

Switch Sensor1 (gPresent )
Switch Sensor2 (gPresent )
Switch Sensor3 (gPresent )

There are no Rules.

Theory of Operation

The Group definition above means that if all members of the Group are OFF, set the Group’s state to OFF, otherwise set the Group’s state to ON. In other words, so long as one Switch is ON, gPresence will be ON. Therefore you can use gPresence in your Rules and Sitemap to determine if anyone is home.

Complicated Example

To continue this presence detection example, let’s add a anti-flapping (i.e. rapid changing of the state when one is on the edge of detection or taking a short trip to the mailbox or the like) and the ability to detect whether a specific person is home. Lets also add the ability to override the presence detection to always think someone is home even if all the sensors are OFF.

Items

Group:Number:SUM gPresent // counts the number of sensors reporting ON

Switch vPresent "Presence [MAP(en.map):%s]" <presence> // Item to represent presence

Switch tPresent { expire="5m,command=OFF" } // flapping timer

Switch aPresenceOverride "Override Presence Detection" <presence> (gPresent) // presence override

Group:Switch:AND(OFF,ON) gPersonOnePresent "Person One is Present [%s]" <present> // represents the presence of Person One

Group:Switch:AND(OFF,ON) gPersonTwoPresent "Person Two is Present [%s]" <present> // represents the presence of Person Two

Switch Override_Presence "Override Presence Detection" (gPresent) // Set to ON to make Presence alway be true

Switch PersonOneSensorOne (gPresent, gPersonOnePresent)
Switch PersonOneSensorTwo (gPresent, gPersonOnePresent)
Switch PersonOneSensorThree (gPresent, gPersonOnePresent)

Switch PersonTwoSensorOne (gPresent, gPersonTwoPresent)
Switch PersonTwoSensorTwo (gPresent, gPersonTwoPresent)
Switch PersonTwoSensorThree (gPresent, gPersonTwoPresent)

Rules

val logName = "presence"

rule "Reset vPresent to OFF on startup"
when
    System started
then
	vPresent.sendCommand(OFF)
	gPresent.members.forEach[ SwitchItem s | s.sendCommand(OFF)]
end

rule "A presence sensor updated"
when
	Item gPresent changed
then
    logDebug(logName, "gPresent changed to " + gPresent.state.toString)
	if(gPresent.state > 0 && (vPresent.state != ON || tPresent.state == ON)) {
		logDebug(logName, "Someone came home")
		if(tPresent.state != OFF) tPresent.postUpdate(OFF) // cancel the timer
		if(vPresent.state != ON)  vPresent.sendCommand(ON)
	}
	
	else if(gPresent.state == 0 && vPresent.state != OFF && tPresent.state != ON) {
		logDebug(logName, "Everyone is away, setting timer")
		tPresent.sendCommand(ON) // set timer to turn off vPresent
	}
end

rule "Present timer expired, no one is home"
when
	Item tPresent received command OFF
then
	if(gPresent.state == 0) {
		logInfo(logName, "Everyone is still away after the timer expired, setting house to away")
	    vPresent.sendCommand(OFF)
	}
	else {
		logWarn(logName, "Presence Timer expired but gPresent is " + gPresent.state.toString)
	}
end

Theory of Operation

  • Override: By making the Override Switch a member of gPresent, one can simply turn off presence detection by setting the Override Switch to ON. This will force gPresentto always be ON regardless of what the rest of the sensors say.
  • By adding sensors for specific people to a separate Group, one can detect when certain people (or at least their device) is present.
  • The vPresent Switch represents the anti-flapped presence state. It will only turn OFF five minutes after all the other sensors turn OFF. Use this Switch to determine whether everyone is really gone. However, if you want a warning or to do something immediately when everyone leaves (e.g. reminder that a door is open) use the state of gPresent instead.
  • Adding additional sensors or changing sensors used is a simple matter of adding them to the gPresent Group.
  • These Rules use Design Pattern: Expire Binding Based Timers

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Expire Binding Based Timers Antiflapping Timer
Design Pattern: Motion Sensor Timer This is a specific implementation of Motion Sensor Timer
Design Pattern: Proxy Item vPresence is a Proxy Item
Design Pattern: Separation of Behaviors This DP is a specific implementation of Separation of Behaviors
18 Likes

Thanks for putting this in writing @rlkoshak , very nicely done. Your rule refers to gPresent, but the example doesn’t define this anywhere. From what I can see, it should be gPresence? Yes?

Thanks, I was transcribing from my rules where I call the group gPresent but decided to be a bit more consistent in the posting. Yes, it should be gPresence. Correcting the original…

Great example, can you also post your sitemap for the design pattern? Also what would be neat would be to have “scene item” so that we could list out all the presence sensors detected, I made a sketch here:

I don’t put these on my user’s sitemap. My admin sitemap just has

Switch item=gPresence

There is nothing in Basic UI, Classic UI, or the android apps that allow this from the display perspective. As four your “scene item”, I fail to see how that is any different from a Group.

I am trying implement this logic by adding a single Contact to a presence group, but I can’t get the group to turn off.

presence.items:

    Group:Switch:AND(OFF,ON) gPresence // represents the immediate presence
    Group:Switch:AND(OFF,ON) gPersonDavePresence "Dave is Present [%s]" // represents the presence of Person One
    Contact Presence_Mobile_Dave_Online  "Daves G5 [MAP(unifi.map):%s]" (gPresence, gPersonDavePresence)     { channel="unifi:client:4d85ed4c:online" }

unifi.map translates the values to ON/OFF:

OPEN=OFF
CLOSED=ON

My sitemap shows both the group and contact status:

Text item=gPersonDavePresence 
Text item=Presence_Mobile_Dave_Online label="Daves Mobile"

But my sitemap shows
dpresencse

Thanks in advance!
Dave

The map only affects how the state gets presented in the label.

Since a Contact Item will never have an OFF state the test to see if all members of gPresence is OFF will always fail, resulting in the Group’s state always being ON.

You either need to change your Group to be a Contact and your function to use CLOSED, OPEN, or you need to create a proxy switch Item and Rule to sync the Switch Item to your Contact Item.

This is what I was misunderstanding. For me, having the mapping definition on the Item seemed to imply it would affect the underlying state (as opposed to just the label).

I changed the Group to Contact for now. If I add any non-contact items I will then add a rule.

Thanks!
Dave

PS @rlkoshak Thank you for all of your designpattern posts, they have been a huge help! Also, thanks for extracting them out of the bug designpattern thread.

1 Like

In the original post, I think presenceTimer should be a var instead of val

Smarthome Designer was giving warnings of “Assignment to final field” with val.
Warning are gone with var

You are correct. I’ve fixed the OP. Thanks for the posting.

Since upgrading to OH 2.1.0, I’ve started seeing the following in my main log file:

2018-03-13 23:27:02.540 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'presence.rules', using it anyway:
The operator '!=' should be replaced by '!==' when null is one of the arguments.
The operator '==' should be replaced by '===' when null is one of the arguments.

I see it’s just an INFO, but was curious to get the groups thoughts

The tl;dr is if null is on either side of the comparison then you need to use an extra = sign (i.e. !== or ===).

The longer story is the != and == operators test equivalency and call the .equals methods on the operands. So, for example, if you have two String objects with the same set of characters, == will return true.

But null is a primitive so it doesn’t have a .equals method to call and therefore it doesn’t really have the ability to test for equivalency. So the warning is saying you should use the identity operators, the ones with the extra equal sign. If you have to String objects with the same set of characters, === will return false because they are two different objects. The identity operator tests to see if the two operands point to the same object in memory.

This is one of the many updates I need to make across all the DPs to reflect changes since 2.2 and there will be more when 2.3 is released.

1 Like