deladsrianolegend
Created February 13, 2018

nestX Thermostat

Want to control the temperature of your living room but your nest thermostat is in the cold hallway? The Alexa controlled Arduino nestX

nestX Thermostat

Things used in this project

Hardware components

TinyDuino Processor
TinyCircuits TinyDuino Processor
×1
TinyCircuits - TEMPERATURE / HUMIDITY TINYSHIELD
×1
TinyCircuits - TINYSCREEN OLED TINYSHIELD
×1
TinyCircuits - LITHIUM ION POLYMER BATTERY
×1
TinyShield Wifi
TinyCircuits TinyShield Wifi
×1

Software apps and online services

Shiftr.io
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Amazon Alexa Smart Home Skill API
AWS Lambda
Amazon Web Services AWS Lambda
Amazon Alexa App for iPhone
Maker service
IFTTT Maker service
Arduino IDE
Arduino IDE

Story

Read more

Schematics

Schematic for the BMP280 temperature sensor

Supplied by TinyCircuits

Code

nestX Arduino Sketch

Arduino
Main sketch that controls the thermostat
// This example uses an Arduino/Genuino Zero together with
// a WiFi101 Shield or a MKR1000 to connect to shiftr.io.
//
// IMPORTANT: This example uses the new WiFi101 library.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Gilberto Conti
// https://github.com/256dpi/arduino-mqtt

#include <WiFi101.h>
// #include "Adafruit_MQTT.h"
// #include "Adafruit_MQTT_Client.h"
#include "BMP280.h"
#include "Wire.h"
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <TinyScreen.h>
#include <MQTTClient.h>

#define P0 1013.25

#define BMP_SCK 13
#define BMP_MISO 12
#define BMP_MOSI 11 
#define BMP_CS 10

Adafruit_BMP280 bmp280; // I2C
TinyScreen display = TinyScreen(TinyScreenDefault);

const char ssid[] = "SKY52045";
const char pass[] = "SXYSTEBE";

#define SHIFTR_TOKEN    "1461c437"
#define SHIFTR_SECRET   "8f2cf8f35b34ee39"


WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
double maxTemp = 35;  // start high until we understand the true value from http://things.delads.com 
double currentTemp = 0;


void setup() {


    
  USBDevice.init();                             //Initialize SerialUSB
  USBDevice.attach();                          //Attach SerialUSB
  SerialUSB.begin(115200);


  Wire.begin();


   display.begin();
   display.setBrightness(10);


  WiFi.setPins(8,2,A3,-1);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    SerialUSB.print(".");
  }
  SerialUSB.println();

  SerialUSB.println("WiFi connected");
  SerialUSB.println("IP address: "); SerialUSB.println(WiFi.localIP());

  if(!bmp280.begin()){
    SerialUSB.println("BME init failed!");
    while(1);
  }
  else SerialUSB.println("BME init success!");

 client.begin("broker.shiftr.io",net);
 client.onMessage(messageReceived);
 
 connect();

}

uint32_t x=22;

void loop() {

  client.loop();

  if(!client.connected()) {
    connect();
  }

  // publish a message roughly every 30 seconds.
  if(millis() - lastMillis > 30000) {
    lastMillis = millis();

    double temp = bmp280.readTemperature();
      
    //9.12 was the average amount bmp was reading above actual tem (thanks Rian)
    currentTemp = temp - 9.12;

    client.publish("/temperature", String(currentTemp));

    if(currentTemp > maxTemp){
      client.publish("/turn_off_nest", "true");
    } 
    
  }

  // Let's display the temperature settings to the screen if the display buttons
  // are pressed. 

  
  if(display.getButtons()){
    writeText(currentTemp, maxTemp);
  }

  
  
  


}

void connect() {
  SerialUSB.print("connecting...");
  while (!client.connect("delads-nestX", "1461c437", "8f2cf8f35b34ee39")) {
    SerialUSB.print(".");
    delay(1000);
  }

  SerialUSB.println("\nconnected!");

  client.subscribe("/max_temp");
}

void messageReceived(String &topic, String &payload){
  SerialUSB.print("Incoming message - topic : " + topic + ", payload : "  + payload);

  if(topic == "/max_temp"){
    maxTemp = payload.toFloat();
    SerialUSB.print("Setting maxTemp to " + payload);

    writeText(currentTemp, maxTemp);
  }
  
}


void hardwareDrawCommands(){
  //Accelerated drawing commands are executed by the display controller
  //clearScreen();//clears entire display- the same as clearWindow(0,0,96,64)
  display.clearScreen();
  //drawRect(x stary, y start, width, height, fill, 8bitcolor);//sets specified OLED controller memory to an 8 bit color, fill is a boolean- TSRectangleFilled or TSRectangleNoFill
  display.drawRect(10,10,76,44,TSRectangleFilled,TS_8b_Red);
  //drawRect(x stary, y start, width, height, fill, red, green, blue);//like above, but uses 6 bit color values. Red and blue ignore the LSB.
  display.drawRect(15,15,66,34,TSRectangleFilled,20,30,60);
  //clearWindow(x start, y start, width, height);//clears specified OLED controller memory
  display.clearWindow(20,20,56,24);
  //drawLine(x1, y1, x2, y2, 8bitcolor);//draw a line from (x1,y1) to (x2,y2) with an 8 bit color
  display.drawLine(0,0,95,63,TS_8b_Green);
  //drawLine(x1, y1, x2, y2, red, green, blue);//like above, but uses 6 bit color values. Red and blue ignore the LSB.
  display.drawLine(0,63,95,0,0,63,0);
  delay(1000);
  //use 16 bit version of drawLine to fade a rectangle from blue to green:
  for(int i=0;i<64;i++){
    display.drawLine(0,i,95,i,0,i,63-i);
  }
  delay(1000);
}







// Available colors
// TS_8b_Black, TS_8b_White, TS_8b_Blue, TS_8b_Yellow, TS_8b_Red



void writeText(double currentTemperature, double maxTemperature){
  display.clearScreen();


  int width=display.getPrintWidth("10"); //example temperature
  display.setCursor(30,25);

  display.drawRect(0,0,96,64,TSRectangleFilled,TS_8b_Red);
  display.fontColor(TS_8b_White, TS_8b_Red);
  display.setFont(liberationSans_22ptFontInfo);

  display.print(maxTemperature,0);

  display.setCursor(60,5);
  display.fontColor(TS_8b_White, TS_8b_Red);
  display.setFont(liberationSans_12ptFontInfo);
  display.print(currentTemperature, 1);

  
  delay(5000);
  display.clearScreen();
}

nestX Alexa Lambda Skills function

JavaScript
Main functions to connect Alexa Smart Home API with the Device Cloud over OAuth2.0. It's responsible for the discovery and control of the Arduino devices
exports.handler = function (request, context) {
    log("DEBUG:", "Request",  JSON.stringify(request));
    
    if (request.directive.header.name === 'ReportState') {
        log("DEBUG:", "Report State",  JSON.stringify(request));
        getStateReport(request, context);
    }
    
    else if (request.directive.header.namespace === 'Alexa.Discovery' && request.directive.header.name === 'Discover') {
        log("DEBUG:", "Discover request",  JSON.stringify(request));
        handleDiscovery(request, context, "");
    }
    else if (request.directive.header.namespace === 'Alexa.ThermostatController') {
        if (request.directive.header.name === 'SetTargetTemperature') {
            log("DEBUG:", "SetTargetTemperature Request", JSON.stringify(request));
            setTargetTemperature(request, context);
        }
        else if (request.directive.header.name === 'SetThermostatMode') {
            log("DEBUG:", "SetThermostatMode Request", JSON.stringify(request));
            setThermostatMode(request, context);
        }
    }

    function handleDiscovery(request, context) {
        
         // With the help of the access token, I need to make a request to http://things.delads.com
        // to see what endpoints (Thermostat devices) are available. 
        // Create a rails endpoint to return a JSON response to list available Thermostats for this user
        // Return with a list of devices with a different endpointId. When a request will come through
        // from Alexa Cloud, it should contain this endpointId so we know which Thermostat to control.
        // From our rails application, this will probably be the _id of the thermostats we have
        // The oAuth authorisation token will give the permission needed for the user to edit that 
        // particular Thermostat
        
        // Let's pull 'endpoint', friendlyName' and 'displayCategories' from http://things.delads.com also
        // This will be different by Thermostat, but also allows for expansion into different device types
            


        var header = request.directive.header;
        header.name = "Discover.Response";
   
       
        var endpoints = new Array();
        
        var requestToken = request.directive.payload.scope.token;
        const https = require('https');
        
        
       https.get('https://delads-things.herokuapp.com/apis/listthermostats?access_token=' + requestToken, (resp) => {
        
        let data = '';
         
          // A chunk of data has been recieved.
          resp.on('data', (chunk) => {
            data += chunk;
          });
         
          // The whole response has been received. Print out the result.
          resp.on('end', () => {
              
              //Let's pull out the maker ID
              var json = JSON.parse(String(data));
              
              
              json.forEach(function(id) {
                var thermostatName = id.name;
                console.log("DEBUG: HTTP Discover Request : Name = ", thermostatName);
                
                
                
                 var endpointjson =  {
                     
                                        "endpointId": id.id,
                                        "manufacturerName": "Delads",
                                        "friendlyName": id.name,
                                        "description": "Smart Home Device from Delads",
                                        "displayCategories": [
                                            "THERMOSTAT"
                                        ],
                                        "cookie": {
                                        },
                                        "capabilities":[
                                            {
                                                "interface": "Alexa.ThermostatController",
                                                "version": "3",
                                                "type": "AlexaInterface",
                                                "properties": {
                                                    "supported": [
                                                        {
                                                        "name": "targetSetpoint"
                                                        },
                                                        {
                                                        "name": "thermostatMode"
                                                        }
                                                    ],
                                                     "proactivelyReported": false,
                                                     "retrievable": true
                                                }
                                            },
                                            {
                                                "interface": "Alexa.TemperatureSensor",
                                                "version": "3",
                                                "type": "AlexaInterface",
                                                "properties": {
                                                    "supported": [
                                                        {
                                                        "name": "temperature"
                                                        }
                                                    ],
                                                     "proactivelyReported": false,
                                                     "retrievable": true
                                                }
                                            }
                                        ]
                                    }
                                    
                                    
                    endpoints.push(endpointjson);
                
                });
                
                var payloadResult = {"endpoints": endpoints};
                
                var responseHeader = request.directive.header;
                responseHeader.name = "Discover.Response";
                responseHeader.messageId = responseHeader.messageId + "-R";
                
                var response = {
                    
                    event: {
                        header: responseHeader,
                        payload: payloadResult
                    }
                    
                };
            
            console.log("DEBUG: HTTP Discover Request (endpoints) : ", JSON.stringify(response));
            context.succeed(response);
          });
         
        }).on("error", (err) => {
          log("DEBUG:", "HTTP Discover Request","Error: " + err.message);
          context.fail("Ohh shite!");
        });



    
    }

    function log(message, message1, message2) {
        console.log(message + message1 + message2);
    }

    function setTargetTemperature(request, context) {

        var requestMethod = request.directive.header.name;
        // get user token pass in request
        var requestToken = request.directive.endpoint.scope.token;

        if (requestMethod === "SetTargetTemperature") {

            var newTemperature = request.directive.payload.targetSetpoint.value;
            var thermostat_id = request.directive.endpoint.endpointId;
           
            const https = require('https');
            
            var requestString = 'https://delads-things.herokuapp.com/apis/settemperature?' +
                                'access_token=' + requestToken +
                                '&target_temperature=' + newTemperature +
                                '&id=' + thermostat_id;
                                
            log("DEBUG", "Request to Delads ", requestString);
            
           https.get(requestString, (resp) => {
            
            let data = '';
             
              // A chunk of data has been recieved.
              resp.on('data', (chunk) => {
                data += chunk;
              });
             
              // The whole response has been received. Print out the result.
              // The response is a JSON representation of the thermostat active record 
              // Includes the new temperature that the thermostat is set to
              
              resp.on('end', () => {
                  
                  var json = JSON.parse(String(data));
                  
                   var contextResult = {
                        "properties": [{
                            "namespace": "Alexa.ThermostatController",
                            "name": "targetSetpoint",
                            "value": {
                                "value": json.max_temperature,
                                "scale": "CELSIUS"
                            },
                            "timeOfSample": new Date().toISOString(),
                            "uncertaintyInMilliseconds": 500
                        }, {
                          "namespace": "Alexa.ThermostatController",
                          "name": "thermostatMode",
                          "value": "HEAT",
                          "timeOfSample": new Date().toISOString(),
                          "uncertaintyInMilliseconds": 500
                        }]
                    };
                    
                    var responseHeader = request.directive.header;
                    responseHeader.name = "Response";
                    responseHeader.namespace = "Alexa";
                    responseHeader.messageId = responseHeader.messageId + "-R";
                    var response = {
                        context: contextResult,
                        event: {
                            header: responseHeader
                        },
                        payload: {}
            
                    };
                    log("DEBUG", "Response from Delads ", json);
                    log("DEBUG", "Alexa.ThermostatController ", JSON.stringify(response));
                    context.succeed(response);
              
                });
             
            }).on("error", (err) => {
              log("DEBUG:", "HTTP SetTemperature Request","Error: " + err.message);
              context.fail("Ohh shite!");
            });

        }

       
    }
    
    
    
    function setThermostatMode(request, context) {
        // get device ID passed in during discovery
        var requestMethod = request.directive.header.name;
        // get user token pass in request
        var requestToken = request.directive.endpoint.scope.token;
        var newThermostatMode;


        if (requestMethod === "SetThermostatMode") {

            newThermostatMode = request.directive.payload.thermostatMode;
            // scale = request.directive.payload.temperature.scale;

            // This is where we send a HTTPRequest to http://things.delads.com along with the authorisation token
            // Need to have an endpoint available to receive this message for this user
            
            // Let's see what Thermostat _id we need to change
            var thermostat_id = request.directive.endpoint.endpointId;

        }

        var contextResult = {
            "properties": [{
                "namespace": "Alexa.ThermostatController",
                "name": "SetThermostatMode",
                "value": newThermostatMode,
                "timeOfSample": new Date().toISOString(),
                "uncertaintyInMilliseconds": 50
            }]
        };
        var responseHeader = request.directive.header;
        responseHeader.name = "Alexa.Response";
        responseHeader.messageId = responseHeader.messageId + "-R";
        var response = {
            context: contextResult,
            event: {
                header: responseHeader
            },
            payload: {}

        };
        log("DEBUG", "Alexa.ThermostatController ", JSON.stringify(response));
        context.succeed(response);
    }
    
    
    function getStateReport(request, context) {
        
        
        var requestToken = request.directive.endpoint.scope.token;
        const https = require('https');
        
        
       https.get('https://delads-things.herokuapp.com/apis/listthermostats?access_token=' + requestToken, (resp) => {
        
        let data = '';
         
          // A chunk of data has been recieved.
          resp.on('data', (chunk) => {
            data += chunk;
          });
         
          // The whole response has been received. Print out the result.
          resp.on('end', () => {
              
              //Let's pull out the maker ID
              var json = JSON.parse(String(data));
              var response;
              
              
              json.forEach(function(id) {
                var deviceId = id.id;
                
                if(deviceId == request.directive.endpoint.endpointId){
                    
                    var temp = 0; // Let's give it an initial value. passing through null will break
                    
                    if(id.temperature != null)
                        temp = id.temperature;
                    
                    var contextResult = {
                        "properties": [
                            
                             {
                            "namespace": "Alexa.TemperatureSensor",
                            "name": "temperature",
                            "value": {
                                "value" : temp,
                                "scale" : "CELSIUS"
                            },
                            "timeOfSample": new Date().toISOString(),
                            "uncertaintyInMilliseconds": 1000
                        } ,
                            {  
                            "namespace":"Alexa.ThermostatController",
                            "name":"targetSetpoint",
                            "value":{  
                               "value":id.max_temperature,
                               "scale":"CELSIUS"
                            },
                            "timeOfSample":new Date().toISOString(),
                            "uncertaintyInMilliseconds":1000
                         },
                            {
                            "namespace": "Alexa.ThermostatController",
                            "name": "thermostatMode",
                            "value": "HEAT",
                            "timeOfSample": new Date().toISOString(),
                            "uncertaintyInMilliseconds": 1000
                        } 
 
                        ]
                    };
        
                    var endpointResult = {
                        "scope":{  
                        "type":"BearerToken",
                        "token": request.directive.endpoint.scope.token,
                     },
                     "endpointId":request.directive.endpoint.endpointId,
                     "cookie":{  
            
                     }
                    }
        
                    var responseHeader = request.directive.header;
                    responseHeader.name = "StateReport";
                    responseHeader.messageId = responseHeader.messageId + "-R";
                    response = {
                        context: contextResult,
                        endpoint: endpointResult,
                        event: {
                            header: responseHeader
                        },
                        payload: {}
            
                    };
     
                } //end if
              });
              
              log("DEBUG", "Alexa.ThermostatController ", JSON.stringify(response));
              context.succeed(response);
          
            });
         
        }).on("error", (err) => {
          log("DEBUG:", "HTTP Discover Request","Error: " + err.message);
          context.fail("Ohh shite!");
        });

        

    }
    
   
    
    
};

Delads Device Cloud

This is the Device Cloud that digitally represents the Arduino device and communicates with the Alexa Home Skills Kit over OAuth2.0

Credits

delads

delads

1 project • 0 followers
rianolegend

rianolegend

1 project • 0 followers

Comments