Nest history > 10 days

Nest
Nest App

I like data. I’ve always been a bit of a data junky.  In my personal life, I’ve found looking at the data is usually interesting but not terribly useful.  But at work, I identify issues all of the time just by keeping different data up on my dashboards.    This write up however, is for my home, and my new Nest thermostat.  I was so excited to get this thing, I was curious about a dozen different aspects of these smart thermostats.  However, I was terribly disappointed when I got it installed and working and realized that the Nest only stores the last 10 days worth of data.  My assumption is they keep all the data (doesn’t everybody?) but for speed purposes they only let the customer access the last 10 days.  Whatever, its frustrating, but its not like their going to change it for little old me.

Imagine my surprise when I discovered they have an API that the customer can access. I had been bitching about the fact that Nest doesn’t give the customer access to this data, like on the website or something.. somewhere at least.  About 3 weeks later my buddy Buddah sends me this link that I guess showed up on his FaceBalls.

… I may have gotten a lil chub from this.

The guide will get you through the specific steps to making this whole thing happen.

  • I had issues getting google sheets to run the app.  I’d set it to run every 15 minutes, and sometimes it would happen sometimes it would not.  My guess is that either the jobs are queued or that sometimes the first couple take a bit longer.    I had a lot more luck when I’d set it to 15mins and then wait 30 for it to work.
  • The Nest API only allows 1 query every 5 minutes, its real easy to go over this when your coding.  If you go over, they’ll send a denied response which this script isn’t built to handle, so you just end up with no data.
  • Same with the openweathermap.org API.
  • Michael P. got the openweathermap.org to respond to a zip code.  I couldn’t get it to for some reason. I ended up going with city ID instead (its in their documentation).
  • The data from openweathermap.org isn’t super consistent.  My guess is its the opensource-ness of the project.  Inconsistent weather and humidity readings.
// credit to beezly for a lot of this, i just adapted it a bit and added more documentation :)
// --> beezly's work: https://gist.github.com/beezly/9b2de3749d687fdbff3f

// credit to michael pesce's for his modifications to beezly's work.
// --> michael's work: https://gist.github.com/michael-pesce/a4ba55d4fc4f4df7d3a6

// to make this work:
// 1) create a new google sheet (name it whatever you'd like, e.g., "nest data")
//
// 2) on the menu bar click tools -> script editor... to open script editor (new window)
//
// 3) in script editor delete all the default scripts/files, and create a new one (i called it "nestscript.gs")
//
// 4) cut and paste this entire file into nestscript.gs, then save the script
//
// 5) modify the script
//     the following lines must be modified for this to work:
//       line 44 must be updated with the google web application script information
//       line 71 must be updated with your nest credentials
//       line 95 must be updated with your nest serial number
//       line 138 must be updated with your openweathermap.org api key 
//         (this requires an free account with openweathermap.org)
//       line 150 must be updated with your google sheets unique key
//
// 5) on the menu bar click publish -> deploy as web app
//    select "execute the app as me"
//    select who has access to the app: "anyone, even anonymous"
//
// 6) copy/take note of the link to your new web app
//
// 7) on the menu bar click current project's triggers
//      click add new trigger
//      for run select the function rundatacollection, events: time-driven, and select the rest per your preference (i did 15 mins)
// 
// every 15m you should get updated data. 
// note: i have noticed that if the thermostat is not running the heat or the ac it doesn't seem to update the temperature reading...


function rundatacollection() {
  //this function is called by a trigger (create one in: resources -> current project's triggers)
  logger.log("starting data collection")
  // fetch calls your webapp, which via google magic calls the doget() function which is defined below
  //make sure you replace the placeholder (scriptidgoeshere) with your web app id (or the entire link you copied in step 6 )
  var response = urlfetchapp.fetch("http://script.google.com/macros/s/scriptidgoeshere/exec");
}

function performlogin(email, password) {
  var payload = {
    // do not change these:
    "username" : email,
    "password" : password
  };
  
  var options = {
    "method"  : "post",
    "payload" : payload
  };
 
  var response = json.parse(urlfetchapp.fetch('https://home.nest.com/user/login', options).getcontenttext());
  if ('error' in response) {
    throw "invalid login credentials";
  }
  
  return response
}
 
function doget() {
  logger.log("running web function...")
  
  //change these two variables: 
  var login_auth = performlogin('nestusername','nestpassword');
             
  var headers = {
    "authorization" : 'basic '+login_auth['access_token'],
    "x-nl-user-id"  : login_auth['userid'],
    "x-nl-protocol-version" : '1',
    'accept-language': 'en-us',
    'connection'    : 'keep-alive',
    'accept'        : '*/*',
  };
  
  var options = {
    'headers' : headers
  };
  
  var request=urlfetchapp.fetch(login_auth['urls']['transport_url']+'/v2/mobile/user.'+login_auth['userid'], options);
  var result=json.parse(request.getcontenttext());
 
  var structure_id = result['user'][login_auth['userid']]['structures'][0].split('.')[1]
  
  //michael p. added upstairs and downstairs since he had two thermostats. you can get the id for each thermostat
  //by going to the nest dashboard, clicking on the thermostat, clicking the gear icon on the top right
  //then copying the "serial no."" field. it will look something like: 02xx01xx471xxx3s
  var device_id = result['structure'][structure_id]['devices'][0].split('.')[1]
  var hallway =  'nestserialnumber' //upstairs
  // i don't have 2 nest's, so i commented the code required for the second device out.  i didn't want to loose it
  // but it is unneeded for my current configuration.
  
  // var downstairs = 'your_downstairs_device_id' // downstairs
  
  // i modified these some for my purposes.  you shouldn't have to unless your changing the output.
  
  //upstairs
  var u_current_temp = result["shared"][hallway]["current_temperature"] * 1.8000 + 32.00;
  var u_target_temp_low  = result["shared"][hallway]["target_temperature_low"]* 1.8000 + 32.00;
  var u_target_temp_high  = result["shared"][hallway]["target_temperature_high"]* 1.8000 + 32.00;
  var u_target_range = u_target_temp_low.tofixed(1) + "-" + u_target_temp_high.tofixed(1);
  var u_humidity     = result["device"][hallway]["current_humidity"];
  var u_auto_away    = result["shared"][hallway]["auto_away"];
  var u_heater_state = result["shared"][hallway]["hvac_heater_state"];
  var u_ac_state = result["shared"][hallway]["hvac_ac_state"];
  var new_auto_state
  var new_heater_state
  var new_ac_state
  
  if (u_auto_away == "1") {var new_auto_state = "100"}
  else {var new_auto_state = "0"}
  
  if (u_heater_state == "1") {var new_heater_state = "100"}
  else {var new_heater_state = "0"}
  
  if (u_ac_state == "1") {var new_ac_state = "100"}
  else {var new_ac_state = "0"}

  //downstairs
  // var d_current_temp = result["shared"][downstairs]["current_temperature"] * 1.8000 + 32.00;
  // var d_target_temp_low  = result["shared"][downstairs]["target_temperature_low"]* 1.8000 + 32.00;
  // var d_target_temp_high  = result["shared"][downstairs]["target_temperature_high"]* 1.8000 + 32.00;  
  // var d_target_range = d_target_temp_low.tofixed(1) + "-" + d_target_temp_high.tofixed(1);
  // var d_humidity     = result["device"][downstairs]["current_humidity"];
  // var d_auto_away    = result["shared"][downstairs]["auto_away"];
  // var d_heater_state = result["shared"][downstairs]["hvac_heater_state"];
  // var d_ac_state = result["shared"][downstairs]["hvac_ac_state"];

  //get outside data
  // michael p. used the zip code version of the openweathermap api url.  i ended up having to find and use 
  // the city id from the information on their website.
  var wxrequest=urlfetchapp.fetch('http://api.openweathermap.org/data/2.5/weather?id=cityid&appid=apikey');
  var wxresult=json.parse(wxrequest.getcontenttext());
  var outside_temp = (wxresult["main"]["temp"] - 273) * 1.8000 + 32.00;
  var outside_humidity = (wxresult["main"]["humidity"]);

  var time = new date();
  
  //var ss = spreadsheetapp.getactivespreadsheet();
  //if you haven't already, create a google sheet where this data will be logged, and grab the sheet id
  // the sheet id can be grabbed from the sheets url
  // see this pattern for where the sheet id is in the url:
  // https://docs.google.com/spreadsheets/d/this_is_where_the_sheet_id_is/edit#gid=123456789
  var ss = spreadsheetapp.openbyid("1rflvnxmgu0dj2345gds3rznasdfasdfaxjrrqwqqkzd2xc");
  //i have multiple tabs, this puts the data in the first tab. change the [0] to [1] for the second tab, [2] for the third, etc
  var sheet = ss.getsheets()[0];
 
  // write data to the google sheet
  // for readability, make sure the sheet has the following column headers:
  // date/time |	downstairs temp |	downstairs target range |	downstairs humidity |	upstairs temp |	upstairs target range |	upstairs humidity |	outside temp |	outside humidity |	downstairs autoaway |	upstairs autoaway |	downstairs heater state |	downstairs ac state |	upstairs heater state |	upstairs ac state
  sheet.appendrow([time, u_current_temp, u_target_range, u_humidity, outside_temp,outside_humidity, new_auto_state, new_heater_state, new_ac_state]);
  return 0 
}

2 Comments

  1. Avatar
    • keeg

Add a Comment

Your email address will not be published. Required fields are marked *