An Ecobee Automation Hack

I've had an Ecobee thermostat in my house and now in my apartment for a number of years. It's a touchscreen thermostat equipped with 802.11 wireless that can be remotely adjusted and monitored from Ecobee's website as well as iPhone and Android applications. While the expected use case might be monitoring the temperature of one's home while at work, I often lazily use the phone applications while at home when I'm too cold to get out of bed to turn the heat up. With some Ruby code and SNMP, I am now able to automatically detect when I am home and when I leave the apartment, and adjust the temperature automatically.

ecobee thermostat mounted on wall

Communicating with Ecobee's Servers

When I first installed the Ecobee years ago, I watched its network traffic to try to figure out how it worked, hoping to create a proxy that would allow me to automate temperature changes or at least read the data coming from the unit. The thermostat maintains a persistent TCP connection out to Ecobee's server, over which it receives temperature and setting changes made remotely from the website, and pushes out local changes made from the display. Local temperature and humidity conditions are also sent to Ecobee to be logged and alerted on if necessary. The wire protocol used between the thermostat and the Ecobee server was not immediately discernible, so I looked to automate interacting with the Ecobee website. But after seeing their use of DWR and not wanting to work with it, I gave up and moved on to other projects. With a recent need to fetch local humidity information, I thought to look at Ecobee's recently released Android application to see how it communicated with their servers.

Unpacking the Android APK file, it was apparent that the application was made with PhoneGap and that all of the code was clearly visible in well-written and even well-commented JavaScript files. The application made AJAX requests with jQuery to the Ecobee website and received data in JSON format. A quick tour through the code combined with a Ruby library for fetching web pages allowed me to quickly write a utility to fetch basic information:

jcs@air:~> ruby ecobee.rb
checking thermostat 121107XXXXXX
hvac mode is: cool
hold temp is: 76.0F
room temp is: 76.2F
room humidity is: 46%

With that working, I thought about whether I could automate temperature changes in response to my leaving the apartment. Since I always have my phone on me, the easiest solution was to monitor whether my phone was on my local wireless network (a modern version of my blueping utility) and do temperature adjustments accordingly.

Communicating with the Phone

The simplest solution that came to mind was to just ping the phone every so often:

jcs@air:~> ping
PING ( 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
64 bytes from icmp_seq=0 ttl=64 time=2090.010 ms
64 bytes from icmp_seq=1 ttl=64 time=1089.269 ms
64 bytes from icmp_seq=2 ttl=64 time=89.664 ms

But because the phone goes into various states of sleep, it is not always responding to layer 3 traffic. Sometimes it doesn't respond at all, even though it's still maintaining an association with my Airport Extreme access point (as verified by its syslog output).

Communicating with the Access Point

Next, I thought about using SNMP against the Airport, fetching the list of MAC addresses of associated clients, and looking for my phone's entry. I hoped that this would be more accurate than pinging because the phone wouldn't even have to respond. This would also mean less traffic has to be processed by the phone when it is awake, resulting in better battery performance. (Due to an annoying problem with my Nexus S taking a long time to re-associate to the wireless network after waking up, I changed a setting months ago so it keeps its wireless radio on all the time. The battery performance hasn't been affected much, surprisingly.)

After working around a weird data caching problem^1^ where the Airport would still show my phone in its previous state long after it changed, I had a working script.

With my phone's wireless disabled, the program sees me as gone and lets the temperature get up to 78:

jcs@air:~/ecobee> ruby ecobee.rb
2011-08-30 01:01:55 - querying airport access point at
2011-08-30 01:01:56 -  initialized phone (B4:07:F9:XX:XX:XX) connection state to not connected
2011-08-30 01:01:56 - establishing ecobee session
2011-08-30 01:01:56 -  found ecobee thermostat id 121107XXXXXX
2011-08-30 01:01:57 -  hvac mode cool, hold temp 78.0F, room temp 74.9F, humidity 47%
2011-08-30 01:01:57 - sleeping for 30 seconds
2011-08-30 01:02:27 - querying airport access point at
2011-08-30 01:02:28 -  phone still not connected, leaving temperature as-is
2011-08-30 01:02:28 - sleeping for 30 seconds

Once I re-enable the wireless and associate to the access point simulating me re-entering the apartment, it lowers the hold temperature to 74:

2011-08-30 01:06:33 - querying airport access point at
2011-08-30 01:06:34 - updating ecobee thermostat
2011-08-30 01:06:34 -  hvac mode cool, hold temp 78.0F, room temp 74.9F, humidity 47%
2011-08-30 01:06:34 -  phone (B4:07:F9:XX:XX:XX) has just associated, changing cool hold temperature to 74.0F
2011-08-30 01:06:35 - updating ecobee thermostat
2011-08-30 01:06:35 -  hvac mode cool, hold temp 74.0F, room temp 74.9F, humidity 47%
2011-08-30 01:06:35 - sleeping for 30 seconds
2011-08-30 01:07:05 - querying airport access point at
2011-08-30 01:07:06 -  phone still connected, leaving temperature as-is
2011-08-30 01:07:06 - sleeping for 30 seconds

I later removed the hard-coded temperatures and let it adapt to the thermostat's hold temperature. This way if I am home and bump the temperature up 2 degrees to 76, and then leave, when I come back, it will change it back down to 76. Some persistent storage of these values would be useful, as well as support for heat hold temperatures once it gets cold enough to need it. I will eventually release this code on GitHub.

  1. Instead of directly walking the MAC address table at . where cached data seems to be poorly expired, walk . to force it to refresh the data.

Update: I've adapted this code into a plugin for SiriProxy (demo).

Questions or comments?
Please feel free to contact me.