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.