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.


Tuesday, July 14, 2015

If Not Smarter then Smaller!

I've long maintained a list of "things you only buy once, in life." On that list are items like: a pool, a boat, a ferret, a hot tub and an RV. We call them RVs (recreational vehicles); others call them
motorhomes.  While the story on how we acquired one is interesting, it's not the topic of this post. This post is another "remind me how I did that?" post.



This post is about doors.  Making doors.  Making frame and panel doors. Making cope and stick doors.  Because I had to make another door this weekend and I do it so infrequently, I forget the steps involved.



First a little context on how door  making relates to the motorhome. On our vehicle, the original bathroom door was a three panel, tambor style door. Tambor style doors are those doors you'd find on an old roll-top desk.  The tambor doors that work well are made of many (dozens) of small, thin strips of wood.  Our RV door, as mentioned, had three panels and didn't work well at all.

I had made some repairs but finally reached the conclusion that diminishing returns had long since set in and it was simply time to replace it with a real door. A door door. A door door with a handle and a hinge. A door door that swings.

Without further ado, I'm writing down the steps I take to make a door.

Step 0 - The Router Bits


Frame and panel router bit sets make the work much easier. I currently own two bit sets: one from Infinity Tools, their Shaker Bit Set  and the other from MLCS Woodworking - their Katana Matched Rail and Stile Bit Set - Ogee router bit set.



The Katana bits were the closest match to the profile of the existing cabinets in the RV so they got the nod.


These bits are also called "cope and stick" bits by some other woodworkers.  One bit, called the stick bit, makes the stick cuts. The other bit makes the cope cut. This other bit is called - wait for it - the cope bit.

Remembering which one is which isn't easy for me.

Step 1 - The Spreadsheet

The math behind a door is detailed enough that it's worth using a computer to figure things out. There are programs out there that are dedicated to door making, but the the math isn't complicated enough to warrant spending the money. It can be done in a spreadsheet.  I made one and I keep it on on Google Docs.

Rail and Stile Calculations Sheet



Here's the output of the spreadsheet for this project. The finished door will be 22 1/4" wide by 73" tall. It'll be a two-panel door.  The rails and stiles of the existing cabinets are 2 1/8" wide, so we'll use the same width for the door.

The bottom rail will be 4" tall to give the door some visual weight.








For the math to all work out, it's important to know the depth of the panel groove that'll be cut by the bit. For my Katana Ogee bit set, it's a 3/16" deep groove.

It'll change from bit set to bit set.

And, it does affect the size of the stock milled, so be sure and understand the depth of the groove cut by your bit set.

 

 

Step 2 - Stock Prep

Not much to say here that you don't already know. Mill your stock (joint, plane) to 3/4" thickness. Rough cut rails and stiles long - as the router bit can get a bit wonky on the ends. It's easier to trim off the wonkiness AFTER the rails and stiles are routed.  Rip your rails and stiles to the finished width (the 2 1/8" width for top and middle rails and the stiles. 4" for the bottom rail).


Note that I also have plenty of scrap pieces left over. They'll come in handy for test cuts and setup.

 

Step 3 - Stick It

Make the stick cuts first; the cope cuts are last. Mount the stick bit in the router table. This is the bit with the bearing on the top.  If you're lucky enough to have made, or bought, a setup block then use it to set the height of the bit.



Bring the router fence in and make the fence and the bearing flush.


Using scrap, make some test cuts.  When satisfied, make all of the stick cuts on the rails and stiles. Remember, if you care, that the face side goes DOWN on the router table.


Step 4 - Cut Rails to Exact...

Cut them to the finished width (or length as I realize I'm overloading the use of "width"). Before you cope the ends, the rails must now be cut down to their final widths.  In my case that was 18 3/4" and I use my Incra Miter 1000 sled to ensure all rails are cut to the same dimension.

Step 5 - Cope Cut Rail Ends

I use a home-made sled when coping the rail ends. The sled is 1/2" hardboard with a 90* strip of scrap as the fence and scrap as the backer material to prevent blowouts.



Mount the cope bit (the bit with the bearing in the middle). Set the height, remembering the height of the sled.  As before, bring the router fence in and make the fence and the bearing flush.




Make a test cut on some scrap and assemble the rail and stile -- face up. If the rail is proud of (higher than) the stile, lower the bit and retest.


Carefully make the cope cuts.  Keep the stock square and tight to the fence.
Go slow. Use a piece of scrap as a backer.




Step 6 - Check the Fit








Step 7 - Make Your Panels

I had some leftover Quarter Sawn White Oak (QSWO) veneer, so I used that for the panels. While a J-Roller works, a vacuum press will save you some sweat and shoulder pain.


This veneer was also "PSA enabled" - peel and stick. Which means an adhesive had already been applied to the backing.  When using other veneer, I've used Plastic Resin glue with great results.

[ No, I'm not veneering the back sides. Sue me.  ;) ]

Step 8 - Trim Panels

I recommend double checking everything - trim some scrap and test fit before ripping your panels to width. Once the width is OK, cross cut the panels to final length.












And test the fit. In my case, the veneer added enough thickness to the panel to make the fit a bit too tight.

So a dado blade and a thin cut on the backside removed a few thousandths of material to make the fit 'just right'.


You want the panel to slide into the groove with no binding. And you want a tight, rattle free fit.




Step 9 - Test Assemble

Check everything. Don't glue yet - finishing is much easier before the door is assembled.



Step 10 - Finish


When you first get into woodworking, you'll hear others grumble about how they hate finishing or detest sanding.  It's true. Finishing sucks. And, it's as much work as the wood working (woodworking work?) part.

Matching colors is usually difficult. So, I've learned two things: one, the stain colors on the cans won't come close to the color you'll get in the end and  two, keep the lights dim and you won't notice.

Lowes was close by, they had new product on the shelf that advertised faster drying time so I gave it a shot. I went with a quart of Rust-Oleum's Ultimate Wood Stain - Wheat color. Interestingly enough, I don't see that shade, Wheat, on any website.


In any event, the panels, rails and stiles were stained. Then, when dry, got two coats of General Finishes Arm-R-Seal Gloss.  With a 400 grit light polish between coats to knock off the nibs.

Two final coats of General Finishes Arm-R-Seal Satin to knock the shine down and we're ready to assemble..

Step 11 - Assemble

Glue, clamp and square.

Voila.  Finito.





Since I expect the door to get some abuse, 23g pins are put through the tenons

Step 12 - Installation

Hang with piano hinge. Still needs hardware.