About this tutorial

Nest’s smart thermostats are self learning devices: they remember your preferences and settings and use them to build a personalized schedule, so you do not have to program it yourself. On another hand, these thermostats are not very talkative when it comes to their historical data (e.g. recorded temperature and humidity levels). To obtain the latter the end user can either wait for the Nest Home Report, which is more geared toward consumption facts, or go through the APIs (which is not really an option for a non programmer).

In the remainder of this tutorial, we will implement an application that leverages scriptr.io’s capabilities and our Nest connector to build a simple home automation application to provide insights on thermostat’s data. We also hope to demonstrate how easy it is to use scriptr.io for building such applications.

scriptr.io’s features used in this tutorial

  • Scripts scheduling
  • Invocation of third party REST APIs
  • No SQL persistence
  • Orchestration
  • Messaging
  • Nest connector

Get the source code

Application’s requirements

Our application should regularly retrieve data – temperature and humidity – from all the thermostats of our home and from the outside (using a third party weather API in that latter case). The recorded values are persisted into scriptr.io’s global storage and compared to predefined thresholds. When those values are lower or higher than the latter, an email is automatically sent to a predefined address. Stored values are made accessible, through an API that we will implement, to a simple front-end application that displays the historical data using graphs.

Designing our application

Based on the aforementioned description, we will split our application into the following components:

The main controller
We will create a script – nestControlModule – containing most of the orchestration logic of our application, i.e:

  • Call scriptr.io’s Nest connector to retrieve the temperature and humidity from the Nest thermostats.
  • Invoke a third party weather API to obtain the current temperature and humidity levels at the home’s location
  • Store the values in scriptr.io’storage
  • Compare the values with the predefined threshold and ask another script to send an alert if needed
  • List all the stored values for a given period of time

The alert sender
We will create a second scriptr – AlertManager – responsible for sending an alert message by email, to a predefined address.

The scheduler
We will also create a scheduled script that is automatically triggered every 30 minutes to execute the logic contained in the “nestControlModule”.

The API
We will expose the features of “nestControlModule” to front-end clients trough a fourth script, “listMetrics”, which invokes the former and returns the stored metrics for a given period of time. Front-end clients will issue HTTP requests to our API that forwards them to our “nestControlModule” script.

The configuration
Our fifth script is dedicated to the definition of predefined values that are used by the other scripts, mainly the humidity and temperature min and max thresholds and the email address to send alerts to.

Some coding

In the following paragraph, we describe how to implement – most of – the above scripts. We start with the simplest one, the configuration script, which is required by some of the others.

config

On the bottom left corner of the Workspace, click on New script and set the name of the script to “config”.
In the code editor area, let us define variables to hold the values of the min/max threshold and the email to use when sending alerts. To make it simple, we also specify the coordinates of our home in a variable.

var minHumidity = 30; // the min humidity threshold
var maxHumidity = 80; // the min humidity threshold
var minTemperature = 20; // the min temperature threshold
var maxTemperature = 25; // the max temperature threshold
var homeLocation = { // 
  
  lat:"28.3849554", // latitude, replace with appropriate value
  lon:"-81.563825,18.75" // longitude, replace with appropriate value
};   				   				

See the code

AlertSender

In order to send alerts via email when temperature or humidity bypass our thresholds, we create a specific script called “AlertSender”. As you can see it in the below code, sending an email with scriptr.io is very simple: it only requires invoking the sendMail() built-in function, as in the below:

// prepare the constituents of the email to send
  var emailConfig = {
    "to": config.sendAlertTo,
    "fromName": "scriptr.io home automation",
    "subject": title,
    "bodyHtml": "

” + title + “

" + msg + "(Measured by: " +  id + ", value: " + value + ")"
  };
  
  // just invoke the built-in sendMail() function
  return sendMail(emailConfig.to, emailConfig.fromName, emailConfig.subject, emailConfig.bodyHtml);   	

See the code

nestControlModule

As already mentioned, this script implements the application’s main orchestration and therefore needs to communicate with multiple components, such as the Nest devices and the weather API.

Require the necessary components
We start our implementation by requiring all the needed components.

// Require scriptr.io's Nest connector (note that that you first need 
// to check it out from Github into your workspace)
var clientModule = require("modules/nest/nestClient.js");

// Require the AlertManager to send alerts
var alertManager = require("modules/nest-sample/AlertManager.js");

// Require the configuration script for obtaining the min and max thresholds
var config = require("modules/nest-sample/config.js");

// Require the built-in http module that allows you to invoke third party APIs
var http = require("http");

The NestController class
Let us now create a class that provides the necessary implementation:

/**
 * This class is responsible for reading values from the Nest thermostats, storing them and sending
 * notifications when values are above or below predefined thresholds.
 */
function NestController() {
  
  // Create an instance of the NestClient class and set it as a property of the current class
  this.nest = new clientModule.NestClient(true); 

  // Create data buckets in script.io's global storage if needed (see method below)
  this._initialize(); 
}

// This method makes sure to create data buckets in your storage 
NestController.prototype._initialize = function() {
  
  var dateTime = util.getNow(); // you can check the "util" script when getting the complete code of the app
  
  // we persist data in the global storage, into a dedicated "nest" object
  if (!storage.global.nest) { // if there is no "nest" object in your global store, create one
    storage.global.nest = {};
  }
  
  if (!storage.global.nest[dateTime.date]) { // if there is no object for the current date, create one
    storage.global.nest[dateTime.date] = {};
  }
};   				

Store temperature and humidity data
First thing to do next is to write the code to persist humidity and temperature data. In scriptr.io, you can persist data using the storage object that provides two storage scopes: the global scope, to persist data that is accessible to all the scripts in your scriptr.io account, and the local scope, to persist data that is only accessible to the script that created it. In this app, we will use the global scope, but feel free to modify the code to use the local scope if that suits you better.

Storing an object in the storage is pretty simple, just create a new property and assign a value to it, e.g. store.global.myObjet = someValueOrObject: now the global storage contains a persisted object name “myObject”. Reading the persisted value is very simple as well: var someVariable = store.global.myObjet.

In the below, we create two methods to respectively (1) store temperature and humidity values per thermostat; and (2) store the outside temperature and humidity values.

// This method is called to store provided temperature and humidity obtained
// from a given thermostat at a given date/time
NestController.prototype.storeMetricsByThermostat = function(thermostatId, humidity, temperature, dateTime) {
  
  var array = [];
  
  // data is stored per date per thermostat in in the form of a stringified array
  // if data was previously stored, we need to parse the stringified array
  if (storage.global.nest[dateTime.date][thermostatId]) {    
    array = JSON.parse(storage.global.nest[dateTime.date][thermostatId]);
  }
  
  var record = {
    time: dateTime.hour,
    values: {
    	humidity: humidity,
    	temperature: temperature
    }
  };
  
  // add the new data
  array.push(record); 
  storage.global.nest[dateTime.date][thermostatId] = JSON.stringify(array);
};

// This method is called to store provided temperature and humidity obtained
// from the outside (through a remote weather API) at a given date/time
NestController.prototype.storeOutsideMetrics = function(humidity, temperature, dateTime) {
  
  var outsideArray = [];
  if (storage.global.nest[dateTime.date].outside) {
      outsideArray = outsideArray.concat(JSON.parse(storage.global.nest[dateTime.date].outside));
  }
    
  var outsideRecord = { 
    time: dateTime.hour,
    values: {
      humidity: humidity,
      temperature: temperature
    }
  };
 
  outsideArray.push(outsideRecord);
  storage.global.nest[dateTime.date].outside = JSON.stringify(outsideArray);
};

Main orchestration: invoke the Nest thermostats and the remote weather API
Now that we are able to persist data, let us write the code to obtain the latest temperature and humidity values from the Nest thermostats and from the outside. We add a new method – checkHumidityAndTemperature that takes care on invoking the thermostats using our connector, and also invokes the remote weather API using the built-in http object:

NestController.prototype.checkHumidityAndTemperature = function() {
  
  // use the listThermostat method of our Nest connector to obtain the list 
  // of all thermostats and their corresponding data
  var allThermostats = this.nest.listThermostats();
  var identifier = "";
  var dateTime = util.getNow();  
  for (var id in allThermostats) {
    
    identifier = allThermostats[id].name ? allThermostats[id].name : allThermostats[id].id;

    // invoke the "storeMetricsByThermostat" method defined earlier to persist the data
    this.storeMetricsByThermostat(identifier, allThermostats[id].humidity, allThermostats[id].ambient_temperature_c, dateTime);
  
  // prepare a request to the third party weather API   
  var query = {
    url: "http://api.openweathermap.org/data/2.5/weather",
    method: "GET",
    params: {
      lat:config.homeLocation.lat, // retrieve our home's location from the config script
      lon:config.homeLocation.lon
    }
  };
  
  // send a request to the remote weather API by calling the request() method 
  // of the built-in http objects and parse the returned response into a JSON object
  var response = http.request(query);
  var weather = JSON.parse(response.body);
  var outsideMetrics = {
    humidity: weather.main.humidity,
    temperature: ("" + Math.round(weather.main.temp * 10) / 100).substring(0,5)
  };

  // invoke the "storeOutsideMetrics" method defined earlier to persist the data
  this.storeOutsideMetrics(outsideMetrics.humidity, outsideMetrics.temperature, dateTime);

  // add below the code to send alerts on threshold bypassed
  // ...
};

List the persisted values
Let us now implement a method that returns a structure containing all the persisted metrics. This method is called by the API we will expose to front-end clients (more on the API further in this document). What we need is to retrieve the content of the persisted “nest” object of the global storage. To make things a bit more sophisticated, we introduce some filters (from, to and sort) to specify the date interval and order we are interested in:

// this method returns the persisted temperatures and humidity values
// the "params" parameter allows the caller to specify optional "from" and "to" filters
// as well as an optional "sort" (desc, asc)
NestController.prototype.listMetrics = function(params) {
  
  if (!storage.global.nest) {
  	return {};
  }
  
  params = params ? params : {};
  var currentDate = util.getNow().date; 
  var records = []; 
  var max = "";
  var min = "";
  
  // iterate through the properties (dates) of the "nest" object that we created in the global storage
  // the record variable below is a date 
  for (var record in storage.global.nest) {
  
    // check if filters were defined in the function parameters. 
    // if so, compare the date (record) to the filters
    min = params.from ? params.from : record;
    max = params.to ? params.to : currentDate;
    if (record >= min && record <= max) {      
      
      // retrieve the persisted data for that date and clone it
      var data = JSON.stringify(storage.global.nest[record]);
      data = JSON.parse(data);     

      // since data is a stringified array (see above), parse it
      for (var key in data) {
        data[key] = JSON.parse(data[key]);
      }
        
      var candidate = {
        date : record,
      	values : data
      };
      
      records.push(candidate);
    }    
  }
  
  // if asked to, sort the data by ascending or descending order
  records.sort(function (obj1, obj2) {
      return obj1.date - obj2.date2;
    }, "down");
  
  if (params.sort && params.sort.toLowerCase() == "desc") {
    records.reverse();
  }
  
  return records;
};

See the code

The listMetrics API

Now that we are done with our main orchestration class, let us expose its “listMetrics” method to front end clients. We do this by creating a new script that receives HTTP requests, extract parameters from them and convey the latter to NestController.listMetrics(), then returns the received response or an error message if something went bad.

Among the very cool things in scriptr.io is the fact that any script can be turned into a REST API. The only thing you need to do is actually to write code that is executed as soon as the script receives a request.

// first, require our nestControlModule (you can change the below path depending on your needs)
var nestControlModule = require("modules/nest-sample/nestControlModule.js");

// retrieve the request's parameters using the built-in "request" object
// we expect three optional parameters: "from", "to" and "sort"
var from = request.parameters.from;
var to = request.parameters.to;
var sort = request.parameters.sort;

try {
  
  var nestController = new nestControlModule.NestController();
  return nestController.listMetrics({from:from, to:to, sort:sort});
}catch(exception) {
  
  return {
    "status": "failure",
    "errorCode": exception.errorCode ? exception.errorCode : "Internal_Error",
    "errorDetail": exception.errorDetail ? exception.errorDetail : JSON.stringify(exception)
  };
}   				

See the code

Last step, schedule the execution of the orchestration

Our final step consists in creating a last script that will execute at regula intervals to triggers the execution of our orchestration (“NestController.checkHumidityAndTemperature”). The script (we will call it “daemonCheck”) is pretty straightforward:

// require our nestControlModule and create an instance of our NestController
var nestControlModule = require("modules/nest-sample/nestControlModule.js");
var nestController = new nestControlModule.NestController();
try {
  
  nestController.checkHumidityAndTemperature();
  return storage.global.nest;
}catch(exception) {
  
   return {
    "status": "failure",
    "errorCode": exception.errorCode ? exception.errorCode : "Internal_Error",
    "errorDetail": exception.errorDetail ? exception.errorDetail : JSON.stringify(exception)
  };
}   				

See the code

So far, you might be wondering how will this script execute regularly. Well, the answer is simple: we need to schedule it and doing so is – as many things with scriptr.io – super easy: make sure that the “deamonCheck” script is opened and has focus in the editor. On top of the code area, click on the “Schedule” button:

schedule1

Schedule a script

schedule2

Create a cron expression

In the wizard form that is displays, click on “+ Add Trigger”, the on “Select” and choose “every”.

schedule4

Your cron expression

Click on “Advanced” then enter the following cron expression: “*/30 * * * ?”, then click on the check sign. Your new trigger appears on the wizard form.

schedule3

Exection of the script is scheduled

Close the form and you are done: the daemon script is now scheduled to run every 30 minutes! You can verify this by looking at the left side of the Workspace (code tree). You should see a small clock next to “daemonCheck”.

The user interface

In the last part of this tutorial, we briefly describe the minimal user interface we provide to our sample application. We will not go into the details of its implementation but it is quite simple. Kindly refer to the README.md file on Github for more.

After deploying the scripts on your work environment (or on some web server) you should get a screen displaying a list of daily charts (one for temperature and the other for humidity, per day).

nest-history

Daily temperature and humidity charts