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.




No comments :

Post a Comment