Making our Dorm's Thermostat Smart

0 30140 Medium

Use Particle Argons as the brains for a smart thermostat, without having to replace your thermostat!

projectImage

Things used in this project

 

Hardware components

HARDWARE LIST
1 DFRobot Gravity: DS18B20 Temperature Sensor (Arduino Compatible)
1 DHT11 Temperature & Humidity Sensor (3 pins)
1 SG90 Micro-servo motor
2 Particle Argon
2 Breadboard (generic)
10 Jumper wires (generic)

Software apps and online services

 

Particle Build Web IDE

 

ThingSpeak API

Hand tools and fabrication machines

 

Tape, Painters Tape

 

Tape, Duct

Story

 

Introduction

 

In a world where nearly everything can be smart (even toasters, why toasters?), our thermostat sure isn't. Imagine having to get out of bed, walk all the way down the hallway, and hold down the thermostat button for nearly three seconds just to change the temperature by one degree. What is this, the 2010's? We should be able to pull out our phones and adjust the thermostat remotely from wherever we are. That's what our project is about. We used two Particle Argons, a few temperature sensors, and a servo to remotely collect data and control the thermostat of our dorm from anywhere with internet connection.

 

 

 

The Setup

 

The first Argon is connected to a DS18B20 temperature sensor, and a 9g Servo motor. The servo is attached to the thermostat, and when given a command, it rotates the servo horn far enough to press the increase and decrease buttons on the thermostat panel. Additionally, the first Argon publishes the temperature data to the Particle cloud, which is then sent to both the second argon device and ThingSpeak. This allows us to check on the temperature remotely and adjust the thermostat as well. The reason for all the tape is to securely hold the servo against the thermostat panel. One constraint we faced in this project was that the attachment method had to be removable and non-destructive. This meant we could not screw a mount to the wall, or glue a mount to the thermostat. As a result, we used many strips of tape to securely attach the servo to the thermostat.

 

The second argon device is attached to a wall on the other side of our dorm. Our roommates often complain that the further end of the dorm is warmer, so we were able to see if this was true. The second argon device is connected to a DHT Type 11 temperature and humidity sensor. This data is sent to the Particle cloud and then to ThingSpeak. Additionally, this Argon communicates with the Particle cloud and keeps track of the current time. This allows Argon 2 to instruct Argon 1 to change the temperature at certain times. Currently, there are only two of these routines in the code: One for lowering the temperature at night, and one for raising the temperature in the morning.

 

 

 

How it Works

 

While both Argons communicate with the cloud every 0.5 seconds, they don't publish data this fast. The frequency of data publication is based upon a certain time interval. When that interval is reached, Argon 1 publishes its temperature to Thingspeak. It also publishes its temperature through a separate variable that Argon 2 is subscribed to. Once Argon 2 receives Argon 1's temperature, Argon 2 takes a temperature reading of its own. Argon 2 then publishes its temperature and humidity data, as well as the difference between Argon 1's temperature reading and Argon 2's temperature reading.

 

Argon 2 keeps track of the current time. Within the code are two set times and corresponding temperatures. When the current time matches one of the set times, the corresponding temperature is published. Argon 1 subscribes to this variable and will change the temperature accordingly. For example, at 8:30 am (0830), Argon 2 publishes "70". Argon 1 subscribes and receives "70", and then changes the thermostat to 70 degrees.

 

Argon 1 also has several functions that allow the user to make changes without editing the code.

 

The first function, Set Servo Angle, allows the user to change the angle of the servo to a value between 0 and 180. This function is for testing and is not intended to be used for changing the temperature. If successful, the response will be the angle given, and the servo will move. If there is an error, the response will be -1 and the servo will not move. The second function, Change Temp, is the primary method for changing the temperature. When a temperature value is called, Argon 1 evaluates it to see if it is warmer or cooler than the current set temperature. Then, the absolute value of the difference in temperature is found. The servo is changed to a specific angle (depending on whether the temperature is raised or lowered). The servo is held at this angle for a period of time that is computed by multiplying the temperature difference by a time constant (the time it takes for the thermostat to change by 1 degree when the button is pushed and held). If successful, the response will be the temperature given, and the servo will move. If an error occurs, the response will be -1, and the servo will not move. The third function, Calibrate Temp, is responsible for telling Argon 1 what the thermostat is set to. This is useful if the thermostat is changed manually. When a temperature value is called, the Argon updates the variable in its code that keeps track of the thermostat temperature. If this is successful, the response will be the temperature given. If an error occurs, the response will be -1. The fourth and fifth functions, Change Time Interval mins and Change Time Interval hours, are responsible for adjusting how frequently Argon 1 publishes its data. If these functions are used successfully, the response will be the value given. If an error occurs, the response will be -1.

 

 

Flowchart

 

This flowchart shows the flow of information between the Particle Cloud API and the two Argon devices.

 

Flowchart showing the communication between Argon devices and Particle Cloud API

 

Flowchart showing the communication between Argon devices and Particle Cloud API

 

As shown above, Argon 1 does most of the heavy lifting. It shares 6 variables with the Particle cloud that can be viewed (through the Particle app or Particle.io) at any time. It also listens to the cloud for 5 different functions. The second device, Argon 2, only shares 3 variables with the cloud. Additionally, Argon 1 and Argon 2 have bi-directional communications. Argon 1 shares one variable with Argon 2. Argon 2 shares two variables with Argon 1.

 

 

 

Pictures

 

These are pictures of the project and some of its components.

 

First Agron device attached to the wall; connected to a DS18B20 temperature sensor.

 

First Agron device attached to the wall; connected to a DS18B20 temperature sensor.

 

Servo motor attached to thermostat

 

Servo motor attached to thermostat

 

Side-view of servo attached to thermostat

 

Side-view of servo attached to thermostat

 

Servo motor pushing thermostat button and changing the temperature

 

Servo motor pushing thermostat button and changing the temperature

 

Second Argon attached to wall; connected to a DHT11 temperature and humidity sensor.

 

Second Argon attached to wall; connected to a DHT11 temperature and humidity sensor.

 

 

ThingSpeak Graphs

 

All of the data that is recorded from the temperature and humidity sensors are recorded through live ThingSpeak channels. These channels are listed here:

 

Temperature 1 Graph (Temperature sensor near thermostat)

Temperature 1 Current Temperature

Humidity Graph

Current Humidity

Temperature 2 Graph (Temperature sensor away from thermostat)

Difference in Temperature (Difference between the two temperatures. When positive, it is warmer by the thermostat. When negative, it is cooler by the thermostat.)

 

 

 

Project Video

Schematics

 

Argon 1 Circuit Diagram

 

This argon is attached to a temperature sensor and servo motor, and is used to change the temperature of the dorm thermostat

 

 

Argon 2 Circuit Diagram

 

This argon is attached to a dual temperature and humidity sensor and is located across the dorm from the thermostat.

 


 

Code

 

Code for First Argon

C/C++

This code is for the first Argon device. This Argon is responsible for reading temperature from the attached temperature sensor, publishing data to ThingSpeak, and adjusting the attached servo to change the thermostat temperature.

CODE
//=====================
//  Code for 1st Argon Device
//  Written By Andrew Thompson and Kealoha Hankins
//  For UNCC MEGR3171 IoT Project
//=====================



//The two libraries below are needed for the dallas temperature sensor
#include <OneWire.h>
#include <spark-dallas-temperature.h>

//=====================
//SERVO:    (library already built into IDE)
//Servo pin: D8
Servo servo1;
int pos = 90;   //initial position of the servo
const int defaultPos = 90;    //default position of the servo arm.
    //our servo is a 180 degree servo, so 90 degrees is right in the middle.
int setPos(String angle);   //setPos is the variable for the function to set the angle
int angleIncreaseTemp = 35;    //this is the anlge the servo needs to be to press the increase button on the thermostat
int angleDecreaseTemp = 150;     //this is the angle the servo needs to be to press the decrease button on the thermostat
double timeConstant = 2.85*1000; //this is the number of seconds * 1000 that button must be held down in order to change the temp by 1 degree on the thermostat


//=====================
//TEMPERATURE SENSOR:
//Pin 1: GND
//Pin 2: POWER
//Pin 3: D6
#define ONE_WIRE_BUS D6
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
int dsRawTemp = 0;  //this is the temp directly from the temp sensor
int dsTemp = 0; //this is the temp that will be reported

//=====================
//These variables are for getting the time from the internet and time-related actions in the code
int hours = -1; //sets the hour to be -1. If the time value is ever neagtive, this indicates a problem
int mins = -1;  //sets the minute to be -1. If the time value is ever negative, this indicates a problem
String sHour;
String sMin;
String currentTime; //create a string for getting the current time.

//=====================
//These variables control how often the program publishes data
int cycle = 3600000;    //cycle time in milliseconds that the program loops before. Changing cycle will change how often actions are performed.
int interval = 0;   //(check) this is the time in milliseconds for setting when actions are performed.
double cycleMin = cycle/60000;  //this is the cycle time in minutes
double cycleHr = cycleMin/60;   //this is the cycle time in hours
int timeSince = 0;  //this variable will be used to keep track of the time that has passed since the last check

double curTemp = 70;    //this double is for the current temperature the thermostat is set at. This is a double because our thermostat is capable of adjusting temp to the tenths place

//=====================
//these variables are for changing the cycle time. Associated with Particle functions below
int setCycleHr(String newCycle);
int setCycleMin(String newCycle);
int setTemp(String newTemp);

int calibrateTemp(String caliTemp);


int tempDiffCN = 0; //difference between the Current temp and New temp
int morningTemp = 0;
int nightTemp = 0;

//====================================================================================

void setup() 
{
servo1.attach(D8);  //tells the argon the servo is connected to pin D8
sensors.begin();    //tells the temp sensor to be ready

Time.zone(-5);  //the line sets the time zone to EST, which is 5 hours behind GMT

Particle.variable("Temperature",dsTemp);    //particle variable for the temperature
Particle.variable("Angle", pos);    //particle variable for the servo angle
Particle.variable("Time_Interval_millis", cycle);
Particle.variable("Time_Interval_mins",cycleMin);
Particle.variable("Time_Interval_hour",cycleHr);
Particle.variable("Thermostat_Temperature", curTemp);
Particle.variable("Current_Time", currentTime);

Particle.function("Set_Servo_Angle", setPos); //particle function for adjusting the servo angle over the web
Particle.function("Change_Time_Interval (hours)", setCycleHr);
Particle.function("Change_Time_Interval (minutes)", setCycleMin);
Particle.function("Change_Temperature", setTemp);
Particle.function("Calibrate_Temperature", calibrateTemp);
Particle.function("Change_Temperature", setTemp);

Particle.subscribe("Day_Temp", morningRoutine, MY_DEVICES);
Particle.subscribe("Night_Temp", nightRoutine, MY_DEVICES);
}

//====================================================================================

void loop() 
{
sensors.requestTemperatures();  //requests that the temp sensor to take the temperature
dsRawTemp = sensors.getTempFByIndex(0); //sets dsrawTemp equal to the temperature (in F)

hours = Time.hour();
sHour = String(hours);
if (hours >= 1 && hours <= 9)
{
    sHour = "0"+String(hours);
}

mins = Time.minute();
sMin = String(mins);
if (mins <=1 <= 9 )
{
    sMin = "0"+String(mins);
}
currentTime = sHour + sMin;

//the following if statmenet is used to suppress random spikes in data.
//The environement where the sensor is located will never realistically be above 100F or below 20F
if (dsRawTemp < 100 && dsRawTemp > 20)  //if dsRawTemp is less than 100 AND dsRawTemp is greater than 20...
{
    dsTemp = dsRawTemp; //sets dstemp to the value of dsRawTemp
}

if (millis() - interval > cycle)    //if the number of miliseconds since the program began minus the interval period is GEATER than the cycle period
{
    Particle.publish("Temperature_internal", String(dsTemp));
    interval = millis();    //sets interval to the number of milliseconds that have passed since the program began
}
timeSince = millis();   //sets timeSince to the number of milliseconds that have passed since the program began

cycleMin = cycle/60000; //converts the cycle time to minutes
cycleHr = cycleMin/60;  //converts the cycle time to hours

delay (500);    //waits .5 seconds
}


//====================================================================================

int setPos(String angle)    //function code for changing the servo's angle
{
    if(angle.toInt() >= 0 && angle.toInt() <= 180)
    {
        pos = angle.toInt();
        servo1.write(pos);
        return pos; //returns the angle given and end the function
    }
    return -1;  //returns -1. This implies there was a problem (such as an angle above 180 or a string that wasn't a number)
}

//====================================================================================

int setCycleHr(String newCycle)   //function for changing the cycle time (in hours) from the web
{
    if (newCycle.toInt() > 0)   //checks to see if the passed string is an INT greater than 0
    {
        cycle = newCycle.toInt()*60000*60; //since the expected time is in hours, newCycle is multiplied by 60000, and then 60, to convert back to milliseconds.
        return newCycle.toInt();    //returns the time given and ends the function.
    }
    return -1;  //if an error occured, -1 will be returned
}

//====================================================================================

int setCycleMin(String newCycle)    //function for changing the cycle time (in minutes) from the web
{
    if (newCycle.toInt() > 0)
    {
        cycle = newCycle.toInt()*60000;
        return newCycle.toInt();
    }
    return -1;  //if an error occured, -1 will be returned
}

//====================================================================================

int setTemp(String newTemp) //this function is in charge of changing the temperature in the program AND physically. Later code will be added to adjust the servo to push the buttons.
{
    if (newTemp.toFloat() >= 60 && newTemp.toFloat() <= 80)
    {
        if(newTemp.toFloat() > curTemp)
        {
            tempDiffCN = curTemp-newTemp.toFloat();
            tempDiffCN = abs(tempDiffCN);
            //finds the absolute value of the difference between the new temperature and the old temperature
            
            servo1.write(angleIncreaseTemp);
            delay(timeConstant*tempDiffCN);
            servo1.write(defaultPos);
            //sets the servo to hold down the INCREASE button long enough to raise the temperature by tempDiffCN degrees
            
            curTemp = newTemp.toFloat();    //updates the current temperature within the program
            return newTemp.toFloat();       //returns the value it was given
        }
        else if (newTemp.toFloat() < curTemp)
        {
            tempDiffCN = curTemp-newTemp.toFloat();
            tempDiffCN = abs(tempDiffCN);
            //finds the absolute value of the difference between the new temperature and the old temperature
            
            servo1.write(angleDecreaseTemp);
            delay(timeConstant*tempDiffCN);
            servo1.write(defaultPos);
            //sets the servo to hold down the DECREASE button long enough to raise the temperature by tempDiffCN degrees
            
            curTemp = newTemp.toFloat();    //updates the current temperature within the program
            return newTemp.toFloat();   //returns the value it was given
        }
        
    }
    return -1;  //if an error occured, -1 will be returned
}

//====================================================================================

int calibrateTemp(String caliTemp)  //this function is responsible for changing ONLY the temp in the program. This is useful in case the thermostat  temperature and curTemp differ.
{
    if(caliTemp.toFloat() >= 60 && caliTemp.toFloat() <= 80)
    {
        curTemp = caliTemp.toFloat();
        return caliTemp.toFloat();  //if the program runs correctly, the number entered will be returned
    }
    return -1;  //if an error occured, -1 will be returned
}

//====================================================================================

void morningRoutine(const char *topic, const char *data)    //this function is for chaing the temperature based on the daily morning routine. Does not return any value
{
    morningTemp = String(data).toInt(); //sets morning temp to the value from the Day_Temp event
    
    if(morningTemp > curTemp)   //if the morning temp is warmer than the current temp, the temp needs to be INCREASED
    {
        tempDiffCN = curTemp-morningTemp;
        tempDiffCN = abs(tempDiffCN);
            //finds the absolute value of the difference between the new temperature and the old temperature
        
        servo1.write(angleIncreaseTemp);
        delay(timeConstant*tempDiffCN);
        servo1.write(defaultPos);
            //sets the servo to hold down the INCREASE button long enough to raise the temperature by tempDiffCN degrees
        
        curTemp = morningTemp;   //updates the curret temperature within the program
    }
    
    if(morningTemp < curTemp)   //if the morning temp is cooler than the current temp, the temp needs to be DECREASED
    {
        tempDiffCN = curTemp-morningTemp;
        tempDiffCN = abs(tempDiffCN);
        //finds the absolute value of the difference between the new temperature and the old temperature
        
        servo1.write(angleDecreaseTemp);
        delay(timeConstant*tempDiffCN);
        servo1.write(defaultPos);
            //sets the servo to hold down the DECREASE button long enough to lower the temp by tempDiffCN degrees
        
        curTemp = morningTemp;   //updates the current temperature within the program
    }
}

//====================================================================================

void nightRoutine(const char *topic, const char *data)    //this function is for chaing the temperature based on the daily night routine. Does not return any value
{
    nightTemp = String(data).toInt(); //sets night temp to the value from the Night_temp event
    
    if(nightTemp > curTemp)   //if the night temp is warmer than the current temp, the temp needs to be INCREASED
    {
        tempDiffCN = curTemp-nightTemp;
        tempDiffCN = abs(tempDiffCN);
            //finds the absolute value of the difference between the new temperature and the old temperature
        
        servo1.write(angleIncreaseTemp);
        delay(timeConstant*tempDiffCN);
        servo1.write(defaultPos);
            //sets the servo to hold down the INCREASE button long enough to raise the temperature by tempDiffCN degrees
        
        curTemp = nightTemp;   //updates the curret temperature within the program
    }
    
    if(nightTemp < curTemp)   //if the night temp is cooler than the current temp, the temp needs to be DECREASED
    {
        tempDiffCN = curTemp-nightTemp;
        tempDiffCN = abs(tempDiffCN);
        //finds the absolute value of the difference between the new temperature and the old temperature
        
        servo1.write(angleDecreaseTemp);
        delay(timeConstant*tempDiffCN);
        servo1.write(defaultPos);
            //sets the servo to hold down the DECREASE button long enough to lower the temp by tempDiffCN degrees
        
        curTemp = nightTemp;   //updates the current temperature within the program
    }
}

Code for Second Argon

C/C++

This is the code for the second argon device. This argon is responsible for reading temperature and humidity data. It is also responsible for telling the other argon to adjust the thermostat at certain times throughout the day.

CODE
//=====================
//  Code for 2nd Argon Device
//  Written By Andrew Thompson and Kealoha Hankins
//  For UNCC MEGR3171 IoT Project
//=====================


#include <Adafruit_DHT.h>   //library for the DHT sensor
#define DHTPIN D6   //declares pin D6 as the DHT sensor pin
#define DHTTYPE DHT11   //delcares the DHT sensors as a type 11 sensor

DHT dhtSensor(DHTPIN,DHTTYPE);  //declares a DHT sensor, the pin its connected to, and what type of sensor it is

int rawTemp = 0;    //temp read from sensor
int temp = 0;       //temp that has been checked for abnormal readings
int rawHum = 0;     //humidity read from sensor
int hum = 0;        //humidity that has been checked for abnormal readings
int tempDiff = 0;   //the difference in the two temps from each temp sensor
int otherTemp = 0;  //the temp from the other temp sensor
//String data1;
//String data2;

//=====================
//These variables are for getting the time from the internet and time-related actions in the code
int hours = -1; //sets the hour to be -1
int mins = -1;  //sets the minute to be -1
String sHour;
String sMin;
String currentTime; //create a string for getting the current time.

//=====================
//these are for the times that the morning and night routines occur. The time is a 4 digit number. The first two digits are the hour (24hr format), and the second two numbers are the min
String morningRoutine = "0730";  //0700 refers to 7am
int dayTemp = 70;   //this is the default temp for the thermostat during the day
//int nightRoutine = 2230;    //2230 refers to 10:30pm
String nightRoutine = "1030";
int nightTemp = 68;     //this is the default temp for the thermostat during the night

//=====================
//these booleans are used to control loops below and prevent the program from spamming publish events
bool doMorningRoutine = true;
bool doNightRoutine = true;


//====================================================================================

void setup() {
    //Creates numerous particle variables
Particle.variable("Temperature_2", temp);
Particle.variable("Humidity", hum);
Particle.variable("Difference", tempDiff);
Particle.variable("CurTime", currentTime);
Particle.variable("Hr", hours);
Particle.variable("Mins", mins);
//Particle.variable("Data", data1);
//Particle.variable("Data2",data2);

    //Subscribes to a particle.publish a different Argon
Particle.subscribe("Temperature_internal", altTemp, MY_DEVICES);
}


//====================================================================================

void loop() {
        //these two lines get data from the sensor
    int rawTemp = int(dhtSensor.getTempFarenheit());
    int rawHum = int(dhtSensor.getHumidity());
    
    //gets the current time
    hours = Time.hour();
    sHour = String(hours);
    if (hours >= 1 && hours <= 9)
    {
        sHour = "0"+String(hours);
    }
    
    mins = Time.minute();
    sMin = String(mins);
    if (mins <=1 <= 9 )
    {
        sMin = "0"+String(mins);
    }
    
    currentTime = String(hours) + String(mins);

    Time.zone(-5);  //the line sets the time zone to EST, which is 5 hours behind GMT

        //these if statements check the data incase there is an abnormal reading (such as 20 for the temp)
    if (rawTemp < 100 && rawTemp > 20)
    {
        temp = rawTemp;
    }
    if (rawHum < 100 && rawHum > 0)
    {
        hum = rawHum;
    }
    
        //These if statments check to see if the current time matches any of the routine times.
        //Since the loop could run multiple times during the current minute, the booleans are used to make the loops run once
        //when the clock first changes to the routine time.
    if(currentTime == morningRoutine && doMorningRoutine == true)
    {
        Particle.publish("Day_Temp", String(dayTemp));
        doMorningRoutine = false;
    }
    if(currentTime == nightRoutine && doNightRoutine == true)
    {
        Particle.publish("Night_Temp", String(nightTemp));
        doNightRoutine = false;
    }
    
        //These if statements are designed to turn the routine booleans back to once the set routine time has passed
    if(currentTime == (String(morningRoutine.toInt() +1)) && doMorningRoutine == false)
    {
        doMorningRoutine = true;
    }
    if(currentTime == (String(nightRoutine.toInt() +1)) && doNightRoutine == false)
    {
        doNightRoutine = false;
    }
    
    delay(500); //waits .5 seconds
}


//====================================================================================

    //code for Particle.subscribe. Void type because it doesn't return anything
void altTemp(const char *topic, const char *data)   //*data is the value, *topic is the variable name
{
    //data1 = String(data);
    //data2 = String(topic);
    otherTemp = String(data).toInt();   //in order for the data to be an int, it first has to be cast as a String, and then as an int
    tempDiff = otherTemp-temp;  //if tempDiff is POSITIVE, then it is cooler where this device is located. If tempDiff is NEGATIVE, it is warmer where this device is located
    Particle.publish("Difference", String(tempDiff));
    Particle.publish("Temperature",String(otherTemp));
    Particle.publish("Temperature2", String(temp));
    Particle.publish("Humidity",String(hum));
    //Particle.publish("Temperature", String(otherTemp),String(temp),String(tempDiff));
}

The article was first published in hackster, November 28, 2022

cr: https://www.hackster.io/megr3171-group-34/making-our-dorm-s-thermostat-smart-751a24

author: Team MEGR3171 Group #34: Kealoha Hankins, Andrew Thompson

License
All Rights
Reserved
licensBg
0