{"id":1442,"date":"2012-01-08T11:21:05","date_gmt":"2012-01-08T17:21:05","guid":{"rendered":"http:\/\/www.wiredprairie.us\/blog\/?p=1442"},"modified":"2022-06-29T11:00:40","modified_gmt":"2022-06-29T16:00:40","slug":"nest-thermostat-api","status":"publish","type":"post","link":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1442","title":{"rendered":"Nest Thermostat API\/Protocol"},"content":{"rendered":"

While Nest Labs hasn\u2019t released a formal (documented & supported) API, I thought I\u2019d do a bit of digging to see how they\u2019re using the network and what might be achievable.<\/p>\n

A few things are going on, the majority as you\u2019d probably expect.<\/p>\n

The web interface is using a long polling technique apparently to watch for updates to the schedule, temperature, etc.<\/p>\n

\"image\"<\/a><\/p>\n

I haven\u2019t determined what the frequency is though, or the wait time. It\u2019s very inconsistent, even when I wouldn\u2019t expect much new \u201clive\u201d data to be available on the network, it frequently updates and polls again.<\/p>\n

There are a few constants set in the HOME page script:<\/p>\n

C.ABSENT_USER_THRESHOLD     = +('300'<\/span>) || 0;  \/\/ seconds<\/span>\nC.DEAD_DEVICE_THRESHOLD     = +('300'<\/span>) || 0;  \/\/ seconds<\/span>\nC.pollingInterval           = +('2500'<\/span>) || 0;       \/\/ ms<\/span>\nC.WEATHER_POLLING_INTERVAL  = +('120000'<\/span>) || 0; \/\/ ms<\/span><\/pre>\n

 <\/p>\n

If the C.pollingInterval value were for the subscribe endpoint mentioned above, I\u2019d see a LOT more calls than I do \u2013 so I\u2019m still not clear how the polling interval is decided.<\/p>\n

The API calls, for the most part are using JSONP syntax over an HTTPS connection.<\/p>\n

The most frequent request is to \u201csubscribe.\u201d It sends as part of the GET request a large block of encoded JSON (using encodeURIComponent and then JSON.stringify).<\/p>\n

I’m not familiar with the key\/value system that they\u2019re using (it may just be something they\u2019ve constructed in-house \u2013 although given the number of open source JavaScript<\/a> libraries they\u2019re using, I thought someone might recognize it):<\/p>\n

\u201ckey<\/strong>\u201d, \u201c{actualkey<\/strong>}.{value<\/strong>}\u201d<\/p>\n

I don\u2019t understand why they\u2019ve redundantly specified \u201ckey\u201d in a list of keys when it\u2019s evident that the actual key <\/em>is contained within the value as a delimited string. It\u2019s more data to send and more data to parse this way. So, again, maybe it\u2019s based on some DB or model system I\u2019m not familiar with. (Anyone recognize it?)<\/p>\n

I\u2019ve substituted the actual values (as they are serial numbers of my devices) with text representations of what the value represented below:<\/p>\n

{\"keys\"<\/span>:\n    [{\"key\"<\/span>:\"user.#USERID#\"<\/span>,\n        \"version\"<\/span>:209478897,\"timestamp\"<\/span>:1324159145000},\n    {\"key\"<\/span>:\"user_alert_dialog.#USERID#\"<\/span>,\n        \"version\"<\/span>:-1320296685,\"timestamp\"<\/span>:1325967612000},\n    {\"key\"<\/span>:\"structure.#STRUCTURE-GUID#\"<\/span>,\n        \"version\"<\/span>:656192675,\"timestamp\"<\/span>:1325967612000},\n    {\"key\"<\/span>:\"device.#DEVICE 1 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:1485027516,\"timestamp\"<\/span>:1326034984000},\n    {\"key\"<\/span>:\"shared.#DEVICE 1 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:588844038,\"timestamp\"<\/span>:1326034818000},\n    {\"key\"<\/span>:\"schedule.#DEVICE 1 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:1187107985,\"timestamp\"<\/span>:1326005677000},\n    {\"key\"<\/span>:\"track.#DEVICE 1 SERIAL NUMBER#\"<\/span>,\n        \"timestamp\"<\/span>:1326035650601,\"version\"<\/span>:1041047847},\n    {\"key\"<\/span>:\"device.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:149169270,\"timestamp\"<\/span>:1326034820000},\n    {\"key\"<\/span>:\"shared.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:659841570,\"timestamp\"<\/span>:1326034820000},\n    {\"key\"<\/span>:\"schedule.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:-2016290692,\"timestamp\"<\/span>:1326005625000},\n    {\"key\"<\/span>:\"track.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n        \"timestamp\"<\/span>:1326035650862,\"version\"<\/span>:528978433},\n    {\"key\"<\/span>:\"device.#DEVICE 3 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:1637112547,\"timestamp\"<\/span>:1326035399000},\n    {\"key\"<\/span>:\"shared.#DEVICE 3 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:760504326,\"timestamp\"<\/span>:1326035397000},\n    {\"key\"<\/span>:\"schedule.#DEVICE 3 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:-314552357,\"timestamp\"<\/span>:1326003402000},\n    {\"key\"<\/span>:\"track.#DEVICE 3 SERIAL NUMBER#\"<\/span>,\n        \"version\"<\/span>:-645931164,\"timestamp\"<\/span>:1326035531802}]}\"<\/pre>\n

We\u2019ve got three thermostats, so there are always three sets of subscription requests for each call to subscribe.<\/strong><\/p>\n

Using my iPad, I adjusted the set point for our second story (#DEVICE 2#) down one degree Fahrenheit (to 67\u00b0).<\/p>\n

Within approximately a second, the most recent pending subscribe<\/strong> request returned with a far more interesting payload:<\/p>\n

jQuery17108417355176061392_1326035646750(\n    { \"status\"<\/span>: 200,\n        \"headers\"<\/span>: {\n            \"X-nl-skv-key\"<\/span>: \"shared.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n            \"X-nl-skv-version\"<\/span>: 869022424,\n            \"X-nl-skv-timestamp\"<\/span>: 1326038279000,\n            \"X-nl-service-timestamp\"<\/span>: 1326038279825\n        },\n        \"payload\"<\/span>: {\n            \"current_temperature\"<\/span>: 19.98,\n            \"hvac_fan_state\"<\/span>: false<\/span>,\n            \"name\"<\/span>: \"TWO\"<\/span>, \"hvac_heat_x2_state\"<\/span>: false<\/span>,\n            \"hvac_ac_state\"<\/span>: false<\/span>,\n            \"can_cool\"<\/span>: true<\/span>,\n            \"auto_away\"<\/span>: 0,\n            \"compressor_lockout_enabled\"<\/span>: false<\/span>,\n            \"target_temperature_low\"<\/span>: 16.66667,\n            \"target_temperature_high\"<\/span>: 26.66667,\n            \"compressor_lockout_timeout\"<\/span>: 0,\n            \"hvac_heater_state\"<\/span>: false<\/span>,\n            \"hvac_aux_heater_state\"<\/span>: false<\/span>,\n            \"target_temperature\"<\/span>: 19.44444,\n            \"can_heat\"<\/span>: true<\/span>,\n            \"target_temperature_type\"<\/span>: \"heat\"<\/span>,\n            \"target_change_pending\"<\/span>: true<\/span>\n        }\n    });<\/pre>\n

Everything above is needed to update the current state of the UI. As you can see, the current temperature (returned as Celsius apparently) is 19.98 (67.964\u00b0F). The current temperature as displayed on the thermostat and the web UI was 68.<\/p>\n

Seeing these return values makes me think that they may be using Ruby and Rails (as the naming convention tends to follow Rails naming using underscores between words). I know for example, I wouldn\u2019t name variables\/columns that way when building a C#\/JavaScript MVC project.<\/em><\/p>\n

Rather than just a delta payload of what\u2019s changed, they\u2019ve currently opted for a full update of all information related to the thermostat state. <\/span><\/p>\n

Several seconds later, a much larger payload was returned to a subscribe <\/strong>request:<\/span><\/p>\n

\"status\"<\/span>: 200,\n\"headers\"<\/span>: {\n    \"X-nl-skv-key\"<\/span>: \"device.#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n    \"X-nl-skv-version\"<\/span>: -2086438581,\n    \"X-nl-skv-timestamp\"<\/span>: 1326038378000,\n    \"X-nl-service-timestamp\"<\/span>: 1326038379023\n},\n\"payload\"<\/span>: {\n    \"ob_orientation\"<\/span>: \"O\"<\/span>,\n    \"upper_safety_temp\"<\/span>: 1000.0,\n    \"forced_air\"<\/span>: true<\/span>,\n    \"creation_time\"<\/span>: 1324142042019,\n    \"switch_preconditioning_control\"<\/span>: false<\/span>,\n    \"click_sound\"<\/span>: \"on\"<\/span>,\n    \"leaf\"<\/span>: false<\/span>, \"user_brightness\"<\/span>: \"auto\"<\/span>,\n    \"learning_state\"<\/span>: \"steady\"<\/span>,\n    \"heat_pump_comp_threshold\"<\/span>: -1000.0,\n    \"local_ip\"<\/span>: \"10.0.0.205\"<\/span>,\n    \"backplate_serial_number\"<\/span>: \"#SHOULD BE DEVICE 2 SERIAL NUMBER, BUT ISN'T?#\"<\/span>,\n    \"capability_level\"<\/span>: 1.03,\n    \"postal_code\"<\/span>: \"#POSTALCODE#\"<\/span>,\n    \"upper_safety_temp_enabled\"<\/span>: false<\/span>,\n    \"heat_pump_aux_threshold\"<\/span>: 10.0,\n    \"lower_safety_temp_enabled\"<\/span>: true<\/span>,\n    \"serial_number\"<\/span>: \"#DEVICE 2 SERIAL NUMBER#\"<\/span>,\n    \"temperature_lock\"<\/span>: false<\/span>,\n    \"learning_time\"<\/span>: 1002,\n    \"current_version\"<\/span>: \"1.0.4\"<\/span>,\n    \"model_version\"<\/span>: \"Diamond-1.10\"<\/span>,\n    \"backplate_bsl_info\"<\/span>: \"BSL\"<\/span>,\n    \"auto_away_enable\"<\/span>: true<\/span>,\n    \"heat_pump_comp_threshold_enabled\"<\/span>: false<\/span>,\n    \"fan_mode\"<\/span>: \"auto\"<\/span>,\n    \"range_enable\"<\/span>: false<\/span>,\n    \"temperature_scale\"<\/span>: \"F\"<\/span>,\n    \"backplate_mono_info\"<\/span>: \"TFE (BP_DVT) 3.5.2 (ehs@ubuntu) 2011-11-05 12:00:00\"<\/span>,\n    \"backplate_bsl_version\"<\/span>: \"1.1\"<\/span>,\n    \"equipment_type\"<\/span>: \"gas\"<\/span>,\n    \"range_mode\"<\/span>: false<\/span>,\n    \"lower_safety_temp\"<\/span>: 7.0,\n    \"has_fan\"<\/span>: true<\/span>,\n    \"hvac_wires\"<\/span>: \"Heat,Cool,Fan,Common Wire,Rc\"<\/span>,\n    \"learning_mode\"<\/span>: true<\/span>,\n    \"away_temperature_high\"<\/span>: 32.0,\n    \"switch_system_off\"<\/span>: false<\/span>,\n    \"time_to_target\"<\/span>: 1326039444,\n    \"away_temperature_low\"<\/span>: 14.444444444444445,\n    \"current_humidity\"<\/span>: 45,\n    \"mac_address\"<\/span>: \"#MACADDR#\"<\/span>,\n    \"backplate_mono_version\"<\/span>: \"3.5.2\"<\/span>,\n    \"has_aux_heat\"<\/span>: false<\/span>,\n    \"type\"<\/span>: \"TBD\"<\/span>,\n    \"hvac_pins\"<\/span>: \"W1,Y1,C,Rc,G\"<\/span>,\n    \"has_heat_pump\"<\/span>: false<\/span>,\n    \"heat_pump_aux_threshold_enabled\"<\/span>: true<\/span>,\n    \"battery_level\"<\/span>: 3.945,\n    \"target_time_confidence\"<\/span>: 1.0\n}<\/pre>\n

 <\/p>\n

A few things to note:<\/p>\n