Friday, October 30, 2015

(#21) Wow what a View

What an Esper View!


The results are in!

I suspected that my EPL, from this post, while functional, wouldn't be ideal. I thought the views that I just plunked in there to get things working would be a problem in the real world. So, I whipped up some sample MQTT events to emulate what would likely happen in the house.

Here's what usually happens, in our event driven home:
  • Door Status events come about every 10 seconds (+/- a second)
  • Weather Status events come about every 30 seconds (+15 seconds, - 5 seconds)
  • Nest Status events come every two minutes


 
So my test data reproduced that pattern with these conditions
  • door starts off as closed
  • door open event seen with 10 seconds of open time
  • door stays open for a total of 120 seconds


Running that EPL, from this post, on the test data produced these results:

CEP/CONCLUSION | 2015-10-29 18:56:08 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 46.2 |
CEP/CONCLUSION | 2015-10-29 18:56:18 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 18:56:18 -0600 | DOOR LEFT OPEN | Front Door | 71 | OFF | FAN OFF | HOME | 46.2 |
CEP/CONCLUSION | 2015-10-29 18:56:29 -0600 | DOOR LEFT OPEN | Front Door | 81 | OFF | FAN OFF | HOME | 46.2 |
CEP/CONCLUSION | 2015-10-29 18:56:39 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 46.2 |
CEP/CONCLUSION | 2015-10-29 18:56:49 -0600 | DOOR LEFT OPEN | Front Door | 71 | OFF | FAN OFF | HOME | 46.2 |

The door status events are sent in chronological order – 61, then 71, then 81, then 91. The numbers are the number of seconds the door has been open.

Notice that not only am I getting multiple, duplicate events, the 71 second event trails in at the end. Clearly using a time based view of 3 minutes isn't ideal.

There are probably a few ways to tackle this – let's crack open the Esper Reference documentation and try a few.

Different View Durations

With the door sensors coming in every 10 seconds, weather events every 30 seconds and Nest events every two minutes, what happens if we alter the EPL to account for the differing times?

anEPLQuery = "SELECT * FROM "
+ " HHBStatusEvent.win:time(10 seconds) as doe, "
+ " NestStatusEvent.win:time(2 min) as nse, "
+ " WS2308WeatherStatusEvent.win:time(30 seconds) as wse "
+ "WHERE "
+ " ( (doe.deviceType = 3 " // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' " // Door is open
+ " AND doe.statusDuration > 60 " // Door has been open for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) " // Sensor is online
+ " AND (nse.awayStatus = 'HOME') " // Nest thinks we're home
+ " AND ( ((wse.oTemp - nse.temperature ) < -10) OR" // the difference more that 10 degrees
+ " ((wse.oTemp - nse.temperature ) > 10) ) )"


And here's the new output:

CEP/CONCLUSION | 2015-10-29 19:34:58 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 19:35:08 -0600 | DOOR LEFT OPEN | Front Door | 71 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 19:35:19 -0600 | DOOR LEFT OPEN | Front Door | 81 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 19:35:29 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |

So this is better – duplicates are eliminated. 

 
But can we gild the gold? Paint the Lily? Throw perfume on the violet?

Can we make it better?

Suppose I only want to be bugged once every minute, not once every ten seconds? 


From the Esper manual:

With an output clause, the engine dispatches to listeners when the output condition occurs. Here, the output condition is a 1-second time interval. The engine thus outputs every 1 second, starting from the first event, even if there are no new events or no expiring events to output.

Let's add it and see what happens:

anEPLQuery = "SELECT * FROM "
+ " HHBStatusEvent.win:time(10 seconds) as doe, "
+ " NestStatusEvent.win:time(2 min) as nse, "
+ " WS2308WeatherStatusEvent.win:time(30 seconds) as wse "
+ "WHERE "
+ " ( (doe.deviceType = 3 " // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' " // Door is open
+ " AND doe.statusDuration > 60 " // Door has been open for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) " // Sensor is online
+ " AND (nse.awayStatus = 'HOME') " // Nest thinks we're home
+ " AND ( ((wse.oTemp - nse.temperature ) < -10) OR"
+ " ((wse.oTemp - nse.temperature ) > 10) ) ) "
+ " OUTPUT FIRST EVERY 1 MINUTE"


And the results:

CEP/CONCLUSION | 2015-10-29 20:18:32 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 20:19:33 -0600 | DOOR LEFT OPEN | Front Door | 121 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-29 20:20:34 -0600 | DOOR LEFT OPEN | Front Door | 181 | OFF | FAN OFF | HOME | 36.2 | 
 
Well that works much better! That's the behavior we're after.






But, why stop now?

Let's go back to the Esper reference manual and dig more into some of the supported view types. 


One of the view types is “length”. 


From the manual:

13.3.1. Length window (win:length)

This view is a moving (sliding) length window extending the specified number of elements into the past. The view takes a single expression as a parameter providing a numeric size value that defines the window size:

If you think about it – regardless of the timing of the event arrival, all we are really interested in are the last matching Door Open event, the last matching Weather Status event and the last matching Nest Status event. Let's change the EPL to this, and try it:

anEPLQuery = "SELECT * FROM "
+ " HHBStatusEvent.win:length( 1 ) as doe, "
+ " NestStatusEvent.win:length( 1 ) as nse, "
+ " WS2308WeatherStatusEvent.win:length( 1 ) as wse "
+ "WHERE "
+ " ( (doe.deviceType = 3 " // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' " // Door is open
+ " AND doe.statusDuration > 60 " // Door has been open for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) " // Sensor is online
+ " AND (nse.awayStatus = 'HOME') " // Nest thinks we're home
+ " AND ( ((wse.oTemp - nse.temperature ) < -10) OR" // the difference between inside and outside is more that 10 degrees
+ " ((wse.oTemp - nse.temperature ) > 10) ) ) "
// + " OUTPUT FIRST EVERY 1 MINUTE"
;

And the results:

CEP/CONCLUSION | 6:31 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 6:41 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 6:41 -0600 | DOOR LEFT OPEN | Front Door | 71 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 6:51 -0600 | DOOR LEFT OPEN | Front Door | 81 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:01 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:11 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:11 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:11 -0600 | DOOR LEFT OPEN | Front Door | 101 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:22 -0600 | DOOR LEFT OPEN | Front Door | 111 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:32 -0600 | DOOR LEFT OPEN | Front Door | 121 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:42 -0600 | DOOR LEFT OPEN | Front Door | 121 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:42 -0600 | DOOR LEFT OPEN | Front Door | 131 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 7:52 -0600 | DOOR LEFT OPEN | Front Door | 141 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 8:02 -0600 | DOOR LEFT OPEN | Front Door | 151 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 8:12 -0600 | DOOR LEFT OPEN | Front Door | 151 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 8:12 -0600 | DOOR LEFT OPEN | Front Door | 161 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 8:22 -0600 | DOOR LEFT OPEN | Front Door | 171 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 8:33 -0600 | DOOR LEFT OPEN | Front Door | 181 | OFF | FAN OFF | HOME | 36.2 |

Hmm – not what we wanted – duplicates aplenty. Not sure why.



What it we added a SELECT DISTINCT to that EPL? 

Instead of “SELECT * FROM”, we change it to “SELECT DISTINCT * FROM”.


In order for an event to be distinct (not a duplicate) every attribute on that event must match another event.



Here's the new EPL:
 
anEPLQuery = "SELECT DISTINCT * FROM "
+ " HHBStatusEvent.win:length( 1 ) as doe, "
+ " NestStatusEvent.win:length( 1 ) as nse, "
+ " WS2308WeatherStatusEvent.win:length( 1 ) as wse "
+ "WHERE "
+ " ( (doe.deviceType = 3 " // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' " // Door is open
+ " AND doe.statusDuration > 60 " // Door has been open for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) " // Sensor is online
+ " AND (nse.awayStatus = 'HOME') " // Nest thinks we're home
+ " AND ( ((wse.oTemp - nse.temperature ) < -10) OR" // the difference between inside and outside is more that 10 degrees
+ " ((wse.oTemp - nse.temperature ) > 10) ) ) "
// + " OUTPUT FIRST EVERY 1 MINUTE"
;

And the new results!

CEP/CONCLUSION | 2015-10-30 20:48:55 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:06 -0600 | DOOR LEFT OPEN | Front Door | 61 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:06 -0600 | DOOR LEFT OPEN | Front Door | 71 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:16 -0600 | DOOR LEFT OPEN | Front Door | 81 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:26 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:36 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:36 -0600 | DOOR LEFT OPEN | Front Door | 91 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:36 -0600 | DOOR LEFT OPEN | Front Door | 101 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:46 -0600 | DOOR LEFT OPEN | Front Door | 111 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:49:56 -0600 | DOOR LEFT OPEN | Front Door | 121 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:07 -0600 | DOOR LEFT OPEN | Front Door | 121 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:07 -0600 | DOOR LEFT OPEN | Front Door | 131 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:17 -0600 | DOOR LEFT OPEN | Front Door | 141 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:27 -0600 | DOOR LEFT OPEN | Front Door | 151 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:37 -0600 | DOOR LEFT OPEN | Front Door | 151 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:37 -0600 | DOOR LEFT OPEN | Front Door | 161 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:47 -0600 | DOOR LEFT OPEN | Front Door | 171 | OFF | FAN OFF | HOME | 36.2 |
CEP/CONCLUSION | 2015-10-30 20:50:57 -0600 | DOOR LEFT OPEN | Front Door | 181 | OFF | FAN OFF | HOME | 36.2 |




No change. 
I'm not sure why – I'll have to think about that for awhile.




There are scores of other view types/functions in the manual. 

Over time, I'll play with them. But for now, I'll go back to the EPL that combined different view durations and limited the output. 


That's producing the best output, for now.




Wednesday, October 28, 2015

(#20) Mon Dieu, Part Deux!


A good sequel always recaps how we got here. 

So, we've got a house. We've got sensors in the house. We're trying to be a bit smarter about warning us when the door's been open for a long time. Sometimes we want it left open, sometimes we don't. And, finally, I'm confident that I was not born in a barn.

In this post, I said I could think of two ways to detect if a door's been open too long and I want to know about it.  Here was the first way:

If the door has been open for longer than one minute and the furnace/AC is running then send an alert.  

And here's the other way I might want to figure it out.


IF #2


If the door has been open for longer than one minute and the difference between the inside and outside temperatures is large, then send an alert.

So, in this one, I don't have to wait for the furnace or AC to kick on - I'm going to try to respond before that happens.  If the door is open and it's freezing outside, then I want to know. If the door is open and it's Dante's Seventh Level outside, then I want to know.

While this may appear as overly convoluted, it's also helping me learn more about Esper.




The EPL

In my house the outside temperature comes from the Weather Station, and the first floor inside temperature comes from the Nest Thermostat.  So I've got to join three event streams.

String anEPLQuery = "SELECT * FROM "
+ " HHBStatusEvent.win:time(3 min) as doe, "

+ " NestStatusEvent.win:time(3 min) as nse, "
+ " WS2308WeatherStatusEvent.win:time(3 min) as wse "
+ "WHERE "
+ " ( (doe.deviceType = 3 "              // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' "      // Door is open
+ " AND doe.statusDuration > 60 "        // for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) "      // Sensor is online
+ " AND (nse.awayStatus = 'HOME') "      // Nest thinks we're home


// and the difference between inside and outside is more that 10 degrees 

+ " AND (((wse.oTemp - nse.temperature ) < -10) OR"     
+ "      ((wse.oTemp - nse.temperature ) > 10) ) )";



 

The changes from our first solution are in red.

We're joining a third event stream, status events from our weather station.  We're checking to make sure that Nest thinks we're Home (don't ask why) and we're comparing the inside and outside temperatures.

Anything more that an 10 degree (F) difference triggers the result. (I didn't see an Absolute Value intrinsic function in EPL. I did see a post about invoking Java's Math.Abs function but also a note that it was having an issue. So a simple pair of subtractions seemed easiest.


And to cut to the chase - this works too.

So let's recap what else we need to do to get things moving.  Jump back to this post on Esper if you need a quick refresher on what it takes to get Esper up and processing events.


The Listener

In Esper parlance, a Listener is the class that gets invoked when a match is found; when the EPL finds a set of events that match what you're looking for.

In our EPL, we've asked Esper to send us the Events (as POJOs) that triggered the match (that's what SELECT * does).

So step one on a Listener is to have it implement the UpdateListener interface and override the update method. Like this:

public class LeftDoorOpenListener implements UpdateListener
{
    @Override
    public void update(EventBean[] newData, EventBean[] oldData)
    {

...





      
The update method will get called when a stream of matching events is detected. So, as a first step, let's extract the events that triggered the update:

NestStatusEvent nse = (NestStatusEvent) newData[ 0 ].get( "nse" );
 

HHBStatusEvent hse = (HHBStatusEvent) newData[ 0 ].get( "doe" );

WS2308WeatherStatusEvent  

    wse = (WS2308WeatherStatusEvent) newData[ 0 ].get( "wse" );

We pull the events out using the names we gave them in the EPL statement above.

Next - well, next is up to you.  You've got the three events that triggered the EPL so you can do whatever you'd like to do.  You can do additional processing or checking or throttling in the Listener.




In my case, since I'm an event driven house, I create a new event and plunk it back onto the MQTT bus:

StringBuffer    sb = new StringBuffer();
sb.append( "CEP/CONCLUSION" );              sb.append( " | " );
sb.append( getCurrentDateTime() );          sb.append( " | " );
sb.append( "DOOR LEFT OPEN" );              sb.append( " | " );
sb.append( hse.getDeviceName() );           sb.append( " | " );
sb.append( hse.getStatusDuration() );       sb.append( " | " );
sb.append( nse.getHvacStatus() );           sb.append( " | " );
sb.append( nse.getFanStatus() );            sb.append( " | " );
sb.append( nse.getAwayStatus() );           sb.append( " | " );
sb.append( wse.getoTemp() );                sb.append( " | " );



Which will result in a new MQTT event like this:

CEP/CONCLUSION | 2015-10-28 18:52:10 -0600 | DOOR LEFT OPEN | Front Door | 81 | HEATING | FAN ON | HOME | 49.0 | 

Breaking it down:
MQTT Topic:          CEP/CONCLUSION
Date/Time:           2015-10-28 18:52:10 -0600
Message:             DOOR LEFT OPEN
Device Name:         Front Door
Num Seconds:         81
Furnace/AC:          HEATING
Fan is:              FAN ON 
Home or Away:        HOME
Outside temperature: 49.0


Next up - let's create some more test data and try out a few scenarios.  I think the EPL will need tweaking of the views.

(#19) "What? Were you born in a barn?"


No, Dad. Well, come to think of it, I'm not entirely sure, but I believe I was born in a hospital. But then you'd know better than I.

The point is there are some times when it's fine to leave the door open.

Let's say, hypothetically speaking of course, that you leave last night's pizza in the toaster oven too long and the smoke cloud clashes with the off-white of the walls.  

Or you accidentally microwaved the key fob to the Honda Pilot, again hypothetically speaking, of course and the house smells of burnt plastic.


That's my gripe with simplistic home monitoring. They're more than willing to alert you to a door being left open whether you want it open or not.  So, how could you make a system a bit smarter? Under what conditions would it be OK for the door to be left open?  Or conversely, or what conditions should the door be closed?


Here's an easy one - how about not leaving the door open in the winter?  Nothing aggravates a parent more than using your home's furnace to heat the entire neighborhood.  


And, respectively, keeping the door closed in the summer. Nothing aggravates a parent -- and a climate scientist -- more than using your air conditioner to combat global warming.



Let's go to the whiteboard!

If you've been following along, first you have my sympathies. You really need to spend less time on the Internet.  But, if you've been following along, you know that we have some simple sensors in the house:
  • sensors that tell us if a door is open or closed - and how long it's been that way
  • sensors that tell us the weather outside
  • sensors that tell us the temperature inside
  • sensors that tell us if the furnace is heating or the air conditioner is cooling
  • an MQTT hub that servers up sensor data as events
And we've been toying with a Complex Event Processing engine, called Esper, to see if we could correlate data from these sensors and make conclusions.

All we need to do is figure out how we want to correlate the sensor data to draw a conclusion. In this case, how can we tell if the door is open when it shouldn't be?

Two approaches pop up quickly.


IFTATATTEIF... #1

"If this and this and this then that else if..."  OK- here's one possibility: If the door has been open for longer than one minute and the furnace is running then send an alert.   That would work for the winter.  In the summer it would change slightly: If the door has been open for longer than one minute and the AC is running then send an alert.

This seems straightforward to express in Esper's EPL syntax. Here's our first attempt:

//
// If we get a HHB status event for a Door (type 3) 

// and it's open, and the Furnace is running
//

String anEPLQuery = "SELECT * FROM "
 + "  HHBStatusEvent as doe, NestStatusEvent as nse "
 + "WHERE "
 + " ( (doe.deviceType = 3 "             // Open Close sensors are of type 3
 + " AND doe.deviceStatus = 'OPEN' "     // Door is open
 + " AND doe.statusDuration > 60) "      // for more than 60 seconds
 + " AND (nse.hvacStatus = 'HEATING' )"  // And the Furnace is on



The Door Open Event ('doe') will come in as an HHBStatusEvent object, and the Furnace running event will be a NestStatusEvent ('nse').

It should be readable, you should be able to get the gist.  There are two small problems. First, it won't run-- it's invalid EPL and Esper will barf and second, it doesn't handle the air conditioner running in the summer.

Let's fix the EPL first.

For the 37th time, I'll profess my SQL skills are abysmal and that makes EPL a challenge too. But what we're doing in the EPL above is an "inner join". We're joining two event streams. Esper needs more information before it can successfully join the two event streams.

Here's part of the error message you'll get from Esper (v5.3) when the Esper engine tries to digest that EPL:

Error starting statement: Joins require that at least one view is specified for each stream, no view was specified for doe



What I think Esper is saying is "Look, I need some bounds around the join.In this case, how many events do I hang on to to find the match?  How many results do you want?" 

 
If you think about it - suppose the matching events (door left open and furnace running) happened at 9:00am. Should Esper still call it a match at 5:00pm? 


No, of course not.





There are many ways to specify a view, but the one I'll try is a time limit. If the event match happens at 9:00am, then I'd like the matching events forgotten about after a few minutes.  While this might not be the best way, it's one way and it popped into my head first.  


Let's try this:

String anEPLQuery = "SELECT * FROM "
 + "  HHBStatusEvent.win:time(3 min) as doe, "    // 3 minutes of events

 + "  NestStatusEvent.win:time(3 min) as nse "    // only. Dump anything older
 + "WHERE "
 + " ( (doe.deviceType = 3 "             // Open Close sensors are of type 3
 + " AND doe.deviceStatus = 'OPEN' "     // Door is open
 + " AND doe.statusDuration > 60 ) "     // for more than 60 seconds
 + " AND (nse.hvacStatus = 'HEATING'  )" // And the Furnace is on


What you'll find out is that this compiles, run and works!  It may not be ideal, it may over alarm, over alert - but it does work.  



Now let's fix the Summer issue:

String  anEPLQuery = "SELECT * FROM "
+ "  HHBStatusEvent.win:time(3 min) as doe, "

+ "  NestStatusEvent.win:time(3 min) as nse "
+ "WHERE "
+ " ( (doe.deviceType = 3 "                 // Open Close sensors are of type 3
+ " AND doe.deviceStatus = 'OPEN' "         // Door is open
+ " AND doe.statusDuration > 60 "           // for more than 60 seconds
+ " AND doe.deviceOnline = TRUE ) "         // Sensor is online
+ " AND (nse.hvacStatus in ('HEATING','COOLING') ) )" // Furnace/AC is on


With the changes in red. I added a check to make sure the sensor is actually online. I've noticed that if an HHB sensor is offline, I cannot trust the status to be correct.


And when I run this EPL on some test data, it works! Again - it might not be optimal and I haven't verified the views are the best, but it is working. It finds those events when the door has been left open at the HVAC is running.



I'll skip the other Esper details - the Listener code because I think this post has gone on far enough and made the point I was shooting for:

A house full of sophisticated sensors that act in isolation isn't enough. In fact, it'll annoy the crap out of you!

It's only useful when you're able to draw meaningful conclusions from the wide variety of sensors in the house.


 
Let's save approach #2 for the next post.