(#18) More Esper Tidbits
As I continue to use and explore the capabilities of Esper, I'm going to put down some of the answers to questions that I ran into.
The documentation in Esper is certainly comprehensive but, to me, it's not as approachable for a novice, as I think it could be.
So I'm coding, playing and using the Netbeans debugger to see how things work.
Note, take all of the code snippets with a grain of salt. I'm not claiming them to be correct nor proper nor efficient. They're just examples of my discoveries.
Recall that I'm using Esper to be the CEP engine for a Home Automation project. My sensors around the house emit MQTT packets periodically. There are door sensors, motion sensors, weather sensors, a Nest Thermostat, a Caller ID box and so on.
Esper's job is to take the MQTT packet data from the sensors and try to draw conclusions about what's going on in the house. Did someone just leave? Did someone just come home? Did my spouse just crank up the thermostat to some hellish level and single-handily impact, adversely, the Natural Gas stores in Colorado?
While the Esper documentation is great about providing examples on the EPL, I've also been struggling with what the handler code should do? The handler, "listener" in Esper parlance, is the code that's invoked when the EPL finds a match.
Esper Listener Examples
Q: For the EPL "select count(*)..." what does the listener code look like?
A: The attribute to get is named "count(*) and is of type long
For example:
String subQuery1 = "SELECT COUNT(*) FROM WS2308WeatherStatusEvent wse WHERE wse.oTemp < cast(50.0, double)";
The Listener:
@Override
public void update(EventBean[] newData, EventBean[] oldData)
{
Long number = (Long) newData[ 0 ].get( "count(*)" );
An easier way would probably have been to modify the select clause:
String subQuery1 = "SELECT COUNT(*) as numEvents FROM WS2308WeatherStatusEvent wse WHERE wse.oTemp < cast(50.0, double)";
The cast is there because I didn't just default all floating point types in Java to double. I used floats too. Without the cast, Esper complained, at compile time, about the type mismatch.
Q: The Solution Patterns document, under the section "How do I detect N events in X seconds" has the example: "select count(*), window(*) from MyEvent(somefield = 10).win:time(3 min) having count(*) >= 5 output first every 3 minutes".
If I change it to reflect my needs:
anEPLQuery = "SELECT COUNT(*), WINDOW(*) FROM NestStatusEvent( hvacStatus = 'HEATING' ).win:length(5) HAVING COUNT(*) >= 5 OUTPUT FIRST EVERY 5 MINUTES";
then what does the Listener code need to do?
A: Here's what's working for me. To serve as a tutorial, the snippet takes no shortcuts. Some of the statements could be combined.
class FurnaceRunningListener implements UpdateListener
{
@Override
public void update(EventBean[] newData, EventBean[] oldData)
{
logger.info( "FurnaceRunningListener - update called" );
try {
//
// When the EPL is simple "SELECT * FROM EventObject" - you
// use the getUnderlying() method. For example,
// NestStatusEvent eventOne =
// (NestStatusEvent) newData[ 0 ].getUnderlying();
//
// For this, More complex EPL
// "SELECT COUNT(*), WINDOW(*) FROM
// NestStatusEvent( hvacStatus = 'HEATING' ).win:time( 5 min )
// HAVING COUNT(*) >= 5 OUTPUT FIRST EVERY 5 MINUTES";
// Now EventBean[] is coming in with a hashmap
EventType et = newData[ 0 ].getEventType();
String propertyName1 = et.getPropertyNames()[ 0 ]; // "count(*)"
String propertyName2 = et.getPropertyNames()[ 1 ]; // "window(*)
// propertyName1 is "count(*)" - get the value
long countValue = ((Long) (newData[0].get( propertyName1 ))).longValue();
// propertyName2 is "window(*)" - the events are coming in as an array
NestStatusEvent[] triggeringEvents = (NestStatusEvent[]) newData[0].get( propertyName2 );
// How many events? Our EPL asked for (at least) 5
int numEvents = triggeringEvents.length;
// so the first event object is at [0], the last at [4]
NestStatusEvent firstEvent = triggeringEvents[ 0 ];
NestStatusEvent lastEvent = triggeringEvents[ numEvents - 1 ];
//
And go on from there.