loading

I was looking at all those swoopy-zoomy internet connected and controllable thermostats. "Self," I told myself, "we should make one of those." He didn't seem all that excited about that - I mean, what do we know about hardware, firmware, electronics or HVAC? Well, now's as good as any to figure it out.

So without further ado, here's my Instructable on my endeavors building a functioning 3-zone thermostat. (internet connectedness coming later)

Step 1: Procure Components

This build requires lots of different things I didn't have, and as such I had to move forward by using the internet! I researched (a LOT of research) all the different components people are using and have success within the realm of what I was trying to do. That meant I needed a bunch (or at least some) of the following:

  • Arduino Uno - I know there's a way to build your own, but I'm trying to keep things simple - at least initially. Besides, I need to be able to program the ATMega chip, right?
  • Temperature Sensors - DS18B20 Temperature Sensors was what I decided on. There was another one that also could handle humidity, but again we're erring on the side of simpler.
  • 16x2 Character LCD display - Hitachi compatible.
  • LCD Keypad Arduino shield
  • miscellaneous wire leads, connectors, and resistors.

Once all my parts arrived, I started looking at hooking things together on a small, local scale.
 

Step 2: Start Programming the Arduino

I am not new to programming, so moving into the programming space of the Arduino was not some imposing task. I went through a handful of tutorials (turning on LED's and such) to get the syntax and some of the conventions down, and I was off.

The first thing I did was document what I wanted to do IN THE CODE. After that, I ran a thumbnail sketch of the program I wanted to develop - that is, I set up the inputs and outputs, sketched out some functions which I could use to open and close the dampers, turn the furnace on or off, those kinds of things. Great, I had my initial program sketched out. Of course it wasn't ready, wouldn't compile, anything like that.

During this process, I was getting pretty frustrated with the poor development environment of the Arduino IDE. A quick search later, I had found an add on for SublimeText called 'Stino'. Stino did everything I wanted it to and was a much more mature dev environment. I switched over immediately.

Step 3: Pull the Program Together

The first step in making the program actually work was to get the temp sensors working. They are at the heart of the thermostat, and without them, the furnace doesn't know when to turn on or off.

Time to introduce a couple of libraries.
The temperature sensor is pretty cool in that it uses a single wire to send data back to the Arduino. What's more, each sensor has an address, a unique number which identifies the sensor. This is really exciting because it means you can have multiple sensors sending their inputs into the same pin on the Arduino (a big deal when you start to look at what it takes to plug in the 16x2 LCD screen. Pin space is at a premium!).

In order to do this, we'll call on a couple of libraries that already exist - no need to reinvent the wheel. The two libraries are OneWire and DallasTemperature. Both are freely available and will come in useful as we move forward. Being unfamiliar with either one of these libraries, I again referred to the interwebs and found a pretty interesting tutorial on just that thing. All I had to do was modify it to fit my needs.

In going through the hacktronics tutorial, it was mentioned that the DS18B20 Temp sensors are addressable - that is, that they are represented by a specific address. This is what allows for multiple sensors to ride into the Arduino on the same pin. So as we go forward, we have to find those temperature sensor addresses. Hacktronics offers another tutorial which outlines how to find those addresses, as well.

OneWire library
DallasTemperature library

Code so far: (a lot of stuff is commented out because there are still a lot of suppositions and unknowns, but I THINK this is how the code will eventually play out. We'll see)

/*
This is the application that I will be using to control the
furnace. The concept is this: I have 3 zones I will be monitoring.
Each zone will have at least one zone damper which will be
controlled by the arduino application.
Each zone will have a single thermometer which will report
back to the controller. Depending on the thermometer reading and
the thermostat setting, the dampers will open or shut and the 
central furnace will be turned on. Once an acceptable temperature
has been reached in all zones, the furnace will turn off. 

In addition to general heating cycles, the system will be
programmable. At this time, however, the programming cycle will be handled here, not via the thermostat.

IMPORTANT TEST CASES: (to be added as I think of them/come across them
    
    * Need to ensure that the furnace is always off if all 3 dampers are closed.


*/

#include   //This is a library needed for the LCD display
#include         //This is a library needed for the thermometer readings
#include  //This is a temperature library
#include 

// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

const int backlight = 13; //controls backlight

#define THERM_BUS 6 // all temperature readings come in via this single pin.


OneWire oneWire(THERM_BUS);
DallasTemperature sensors(&oneWire);

DeviceAddress zone1Therm = {0x28, 0x48, 0x39, 0x11, 0x04, 0x00, 0x00, 0x94};


// START PROGRAMMING
// Times are 6 AM, 8 AM, 3PM, and 9PM
int* PROGRAMMED_VALUES[7];
int SUNDAY_VALUES[4]      = {67, 67, 67, 60};
int MONDAY_VALUES[4]      = {67, 67, 67, 60};
int TUESDAY_VALUES[4]     = {67, 67, 67, 60};
int WEDNESDAY_VALUES[4]   = {67, 67, 67, 60};
int THURSDAY_VALUES[4]    = {67, 67, 67, 60};
int FRIDAY_VALUES[4]      = {67, 67, 67, 60};
int SATURDAY_VALUES[4]    = {67, 67, 67, 60};
//END PROGRAMMING


float zone1Temp = 0;
float zone2Temp = 0;
float zone3Temp = 0;
boolean isOverrideHeld    = false;
boolean isOverridden      = false;
boolean furnaceState      = false; //with furnaceState, false means it's currently off, true means its currently on.

int overrideValue         = 0;

const int furnacePin      = 1;

void setup() {
  Serial.begin(9600); // This connects the arduino to the RPi
  sensors.begin();
  sensors.setResolution(zone1Therm, 10);
  //SETUP OUTPUTS
  pinMode(furnacePin, OUTPUT);
  //SETTING UP THE WEEKLY PROGRAM
  PROGRAMMED_VALUES[0] = SUNDAY_VALUES;
  PROGRAMMED_VALUES[1] = MONDAY_VALUES;
  PROGRAMMED_VALUES[2] = TUESDAY_VALUES;
  PROGRAMMED_VALUES[3] = WEDNESDAY_VALUES;
  PROGRAMMED_VALUES[4] = THURSDAY_VALUES;
  PROGRAMMED_VALUES[5] = FRIDAY_VALUES;
  PROGRAMMED_VALUES[6] = SATURDAY_VALUES;
}


// this runs the continual loop 
void loop() {
  delay(1000);
  log("000", "Getting Temperature");
  sensors.requestTemperatures();
  zone1Temp = getTemperature(zone1Therm);
  
  log("001", (String)(int)zone1Temp);
  // if(c1 || c2 || c3) {
  //   if(!furnaceState) {
  //     startFurnace();
  //   }
  //   TODO - DETERMINE THE CORRECT PINS FOR THE DAMPERS. WILL THE DAMPERS BE DIGITAL OR ANALOG?
  //   if(c1) {
  //     openDamper(1);
  //   }
  //   if(c2) {
  //     openDamper(2);
  //   }
  //   if(c3) {
  //     openDamper(3);
  //   }
  // }
//  else {
//    if(furnaceState){
//      stopFurnace();
//    }
//  }
//  if(!c1) {
//    closeDamper(damper1Pin);
//  }
//  if(!c2) {
//    closeDamper(damper2Pin);
//  }
//  if(!c3) {
//    closeDamper(damper3Pin);
//  }
}


float getTemperature(DeviceAddress deviceAddress) {
  float tempC = sensors.getTempC(deviceAddress);
  //Serial.println(tempC);
  if(tempC == -127){
    Serial.println("ERROR getting temperature.");
  }
  return DallasTemperature::toFahrenheit(tempC);
}
//convenience function. Handles all the boilerplate for writing to the LCDScreen
void toScreen(String line1Value, String line2Value) {
  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(line1Value);
  lcd.setCursor(0,1);
  lcd.print(line2Value);
}

int getTime() {
  return -1;
}
void openDamper(int damper) {
//  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
//    digitalWrite(damper, HIGH);
//  } else {
//    log("Problem opening damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
//  }
}
void closeDamper(int damper) {
//  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
//    digitalWrite(damper, LOW);
//  } else {
//    log("Problem closing damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
//  }  
}

//function for logging messages to the console.
//Eventually plan to write to logfile on RPi
void log(String code, String message) {
  Serial.println(code + ": " + message);
}
void recordData(String type, String object, String value) {
  // TODO - output to RPi database
}
void startFurnace(){
  //Send appropriate signal to the furnace to start up
  if(furnaceState != 0) {
    digitalWrite(furnacePin, HIGH);  
  }
}
void stopFurnace(){
  //Send appropriate signal to the furnace to stop
  if(furnaceState == 1){
    digitalWrite(furnacePin, LOW);
  }
}

Step 4: First Steps in Getting Things Working

So now I have my breadboard, a temperature sensor, an Arduino, and have the program set up so I can test out and see if my temperature sensor is reading correctly. Now to wire everything up and see what happens!

Hooray! We have data! Everything's comin' up Millhouse!

Okay, so first major technical hurdle overcome - we are now receiving data from the temperature sensor. Next, up, lets add the second and third sensors in line with the first, make some slight changes to the code to handle the two new sensors, and see what happens.

...
DeviceAddress zone1Therm = {0x28, 0x48, 0x39, 0x11, 0x04, 0x00, 0x00, 0x94};
DeviceAddress zone2Therm = {0x28, 0xF2, 0x1F, 0x11, 0x04, 0x00, 0x00, 0x38};
DeviceAddress zone3Therm = {0x28, 0xBA, 0xF3, 0x10, 0x04, 0x00, 0x00, 0x98};
...
void loop() {
...
  zone1Temp = getTemperature(zone1Therm);
  zone2Temp = getTemperature(zone2Therm);
  zone3Temp = getTemperature(zone3Therm);
  
  log("001", (String)(int)zone1Temp);
  log("001", (String)(int)zone2Temp);
  log("001", (String)(int)zone3Temp);
...}


Bingo!

Step 5: Make Those Readings DO Something.

All right. I now have an Arduino. It's hooked up via jumper wires to three temperature sensors, all sitting cozily on my desk. I've programmed in a time and day sensitive schedule. Let's make it work.

Except...

The Arduino doesn't really have a clock in it - at least not a clock that remembers anything This would be fine EXCEPT we need to have this information in order to really make this work correctly. Back to the internet I traipse, looking for something which will give my Arduino a mind for time. Seconds later (thanks, Internet!), I hit pay-dirt. The DS1307 RT Clock was exactly what I was looking for. Click to order and it's on its way.

Step 6: Install the Clock

Installing the clock is the next step. After waiting anxiously for its arrival, it finally came! A quick trip out to the shop for a little soldering (adding on the single row of pins needed to plug this baby into the breadboard), and I was ready to rock!

Now, I had never really done much on-board soldering before. This was 'uncharted' territory. Sure, I got the basic premise - get the pin/component you're trying to solder hot and the solder just flows onto, around, and through everything, making a nice solid connection. But I didn't have much practical application of stated principle. Time to change that.

I had a spare 16x2 LCD screen lying around, so I soldered a set of pins onto that, first. At first it was slow going... my soldering iron was not hot enough. After cranking that baby to about 9 (on the 11 scale), things started getting groovy. I soldered all the pins together in short order and was back in the house, and wiring the new clock into the circuit.

(For a detailed tutorial on how the clock is wired up, check this adafruit tutorial out.)

Step 7: Add in the LCD

So with our clock and our temperature sensors working, let's get some output moving to the LCD controller.

The first part of this is determining what I want it to say. Space is somewhat limited since I only have a total of 32 characters across 2 lines, so the first question I had to answer was how do I display everything I need to display within those 32 characters.

Here are my final LCD readouts:

Zone1: ()

Mode:

Zone2: ()

Furnace:

Zone3: ()

Open: <1,2,3/NONE>

where T1 is temperature for zone 1, TT is target temperature, MODE is the current mode it's running (AWAKE, AWAY, HOME, SLEEP, OVERRIDE, or HOLD.

Open refers to which of the zones are currently open and receiving heat (if the furnace is running).

This was actually pretty straightforward. I have some figuring to do as far as a custom symbol is concerned, but it's pretty far down on my priority list.

<tt><tt><tt>
/*
This is the application that I will be using to control the
 furnace. The concept is this: I have 3 zones I will be monitoring.
 Each zone will have at least one zone damper which will be
 controlled by the arduino application.
 Each zone will have a single thermometer which will report
 back to the controller. Depending on the thermometer reading and
 the thermostat setting, the dampers will open or shut and the 
 central furnace will be turned on. Once an acceptable temperature
 has been reached in all zones, the furnace will turn off. 
 
 In addition to general heating cycles, the system will be
 programmable. At this time, however, the programming cycle will be handled here, not via the thermostat.
 
 IMPORTANT TEST CASES: (to be added as I think of them/come across them
 
 * Need to ensure that the furnace is always off if all 3 dampers are closed.
 * dampers should be open unless they specifically need to be closed.
 
 */

#include <LiquidCrystal.h>  //This is a library needed for the LCD display
#include <OneWire.h>        //This is a library needed for the thermometer readings
#include <DallasTemperature.h> //This is a temperature library
#include <Wire.h>
#include "RTClib.h"


// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

byte deg[8] = {
  B01000,
  B10100,
  B01000,
  B00111,
  B00100,
  B00110,
  B00100,
  B00100
};
const int backlight = 13; //controls backlight

#define THERM_BUS 6 // all temperature readings come in via this single pin.


OneWire oneWire(THERM_BUS);
DallasTemperature sensors(&oneWire);

DeviceAddress zone1Therm = {
  0x28, 0x48, 0x39, 0x11, 0x04, 0x00, 0x00, 0x94};
DeviceAddress zone2Therm = {
  0x28, 0xF2, 0x1F, 0x11, 0x04, 0x00, 0x00, 0x38};
DeviceAddress zone3Therm = {
  0x28, 0xBA, 0xF3, 0x10, 0x04, 0x00, 0x00, 0x98};

RTC_DS1307 RTC;

// START PROGRAMMING
// Times are 6 AM, 8 AM, 3PM, and 9PM
int* PROGRAMMED_VALUES[7];
int SUNDAY_VALUES[4]      = {
  67, 67, 67, 60};
int MONDAY_VALUES[4]      = {
  67, 67, 67, 60};
int TUESDAY_VALUES[4]     = {
  67, 67, 67, 60};
int WEDNESDAY_VALUES[4]   = {
  67, 67, 67, 60};
int THURSDAY_VALUES[4]    = {
  67, 67, 67, 60};
int FRIDAY_VALUES[4]      = {
  67, 67, 90, 75};
int SATURDAY_VALUES[4]    = {
  67, 67, 67, 60};
//END PROGRAMMING


float zone1Temp = 0;
float zone2Temp = 0;
float zone3Temp = 0;
boolean z1Check = 0;
boolean z2Check = 0;
boolean z3Check = 0;
boolean isOverrideHeld    = false;
boolean isOverridden      = false;
boolean furnaceState      = false; //with furnaceState, false means it's currently off, true means its currently on.

int overrideValue         = 0;

const int furnacePin      = 1;
int zoneCounter           = 0;

void setup() {
  Serial.begin(57600); // This connects the arduino to the computer
  //TEMPERATURE SENSOR SETUP
  sensors.begin();
  sensors.setResolution(zone1Therm, 10);
  lcd.createChar(0, deg);
  // RTC SETUP.
  Wire.begin();
  RTC.begin();
  if(!RTC.isrunning()){
    log("ERROR", "RTC is NOT running!");
    //RTC.adjust(DateTime(__DATE__, __TIME__)); //THIS ONLY NEEDS TO BE UNCOMMENTED IF YOU ARE SETTING UP YOUR RTC FOR THE FIRST TIME! ONCE IT IS SET, THIS MUST BE DISABLED!
  }

  //SETUP OUTPUTS
  pinMode(furnacePin, OUTPUT);
  pinMode(backlight, OUTPUT);
  digitalWrite(backlight, HIGH);

  //SETTING UP THE WEEKLY PROGRAM
  PROGRAMMED_VALUES[0] = SUNDAY_VALUES;
  PROGRAMMED_VALUES[1] = MONDAY_VALUES;
  PROGRAMMED_VALUES[2] = TUESDAY_VALUES;
  PROGRAMMED_VALUES[3] = WEDNESDAY_VALUES;
  PROGRAMMED_VALUES[4] = THURSDAY_VALUES;
  PROGRAMMED_VALUES[5] = FRIDAY_VALUES;
  PROGRAMMED_VALUES[6] = SATURDAY_VALUES;
}


// this runs the continual loop 
void loop() {
  delay(3000); //DELAY CURRENTLY SET TO 10 SECONDS. WILL ADJUST AS NEEDED
  
  //DATE/TIME LOGIC
  log("TIME", "Loop initiated at " + getDateAndTime());
  //TEMPERATURE LOGIC
  sensors.requestTemperatures();
  zone1Temp = getTemperature(zone1Therm);
  zone2Temp = getTemperature(zone2Therm);
  zone3Temp = getTemperature(zone3Therm);
  logTemperatureData();
  getScreenOutput();
  z1Check = checkZoneTemp(zone1Temp);
  z2Check = checkZoneTemp(zone2Temp);
  z3Check = checkZoneTemp(zone3Temp);
  
  if(z1Check || z2Check || z3Check){
     startFurnace();
  } else {
    stopFurnace();
  }
  if(z1Check && furnaceState) {
    closeDamper(1);
  } else {
    openDamper(1);
  }
  if(z2Check && furnaceState) {
    closeDamper(2); 
  } else {
    openDamper(2);
  }
  if(z3Check && furnaceState) {
    closeDamper(3); 
  } else {
    openDamper(3);
  }
}
boolean checkZoneTemp(int temperature) {
  int temp = getTargetTemperature();
  if(temperature > temp){
    return false;
  }
  return true;
}
void logTemperatureData() {
  log("Zone1Temp", (String)(int)zone1Temp);
  log("Zone2Temp", (String)(int)zone2Temp);
  log("Zone3Temp", (String)(int)zone3Temp); 
}
void getScreenOutput() {
    zoneCounter++;
  if(zoneCounter == 1){
    String val = "Zone1: " + (String)(int)zone1Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Mode: " + getModeString());
  }
  else if (zoneCounter == 2){
    String val = "Zone2: " + (String)(int)zone2Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Furnace: " + getFurnaceState());
  }
  else {
    zoneCounter = 0;
    String val = "Zone3: " + (String)(int)zone3Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Open: " + getActiveZonesForScreen());
  }
}
int getTargetTemperature(){
  int mode = getMode();
  DateTime now = RTC.now();
  int date = now.dayOfWeek();
  int* values = { 
    0         };
  log("info", (String)date);
  values = PROGRAMMED_VALUES[date];
  log("info", "getting target temp: " + (String)values[mode]);
  return values[mode];
}
String getActiveZonesForScreen(){
  String retVal = "";
  if(z1Check && !furnaceState){
    retVal += "1,";
  }
  if(z2Check && !furnaceState) {
    retVal += "2,";
  }
  if(z3Check && !furnaceState){
    retVal += "3";
  }
  if(retVal.length() == 0) {
    retVal += "None";
  }
  return retVal;
}

String getFurnaceState(){
  if(furnaceState){
    return "On";
  } 
  else {
    return "Off";
  }
}
String getModeString() {
  int mode = getMode();
  switch (mode) {
  case 0:
    return "Wake";
  case 1:
    return "Away";
  case 2:
    return "Home";
  case 3:
    return "Sleep";
  default:
    return "N/A";
  }
}
int getMode() {
  DateTime now = RTC.now();
  int hour = now.hour();
  if(hour < 6 || hour >= 21) {
    return 3;
  }
  if(hour >= 6 && hour < 8){
    return 0;
  }
  if(hour >=8 && hour < 15){
    return 1;
  }
  if(hour >= 15 && hour < 21){
    return 2;
  }
}
float getTemperature(DeviceAddress deviceAddress) {
  float tempC = sensors.getTempC(deviceAddress);
  //Serial.println(tempC);
  if(tempC == -127){
    Serial.println("ERROR getting temperature.");
  }
  return DallasTemperature::toFahrenheit(tempC);
}
//convenience function. Handles all the boilerplate for writing to the LCDScreen
void toScreen(String line1Value, String line2Value) {
  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(line1Value);
  lcd.setCursor(0,1);
  lcd.print(line2Value);
}

String getDateAndTime() {
  DateTime now = RTC.now();
  return String(now.year()) + "/" + String(now.month()) + "/" + String(now.day()) + " " + String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
}
void openDamper(int damper) {
  //  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
  //    digitalWrite(damper, HIGH);
  //  } else {
  //    log("Problem opening damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
  //  }
}
void closeDamper(int damper) {
  //  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
  //    digitalWrite(damper, LOW);
  //  } else {
  //    log("Problem closing damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
  //  }  
}

//function for logging messages to the console.
//Eventually plan to write to logfile on RPi
void log(String code, String message) {
  Serial.println(code + ": " + message);
}
void recordData(String type, String object, String value) {
  // TODO - output to RPi database
}
void startFurnace(){
  //Send appropriate signal to the furnace to start up
  if(!furnaceState) {
    furnaceState = !furnaceState;
    log("INFO", "STARTING FURNACE");
    digitalWrite(furnacePin, HIGH); 
  }
}
void stopFurnace(){
  //Send appropriate signal to the furnace to stop
  if(furnaceState){
    furnaceState = !furnaceState;
    log("INFO", "STOPPING FURNACE");    
    digitalWrite(furnacePin, LOW);
  }
}

</tt></tt></tt>

Step 8: Adding in Analog Buttons

All right. Since I'm pretty lean on my available pins on the arduino (I have 7,8, and 9 digital pins available, currently, and I still need to connect the furnace and the dampers), I'm going to look for a way to use the available analog inputs (I have A0 through A3 available, since A4 and A5 are connected to the RTC) to accept button input. Pretty quckly I find something which is very promising - a tutorial on how to accept input from multiple buttons via a single analog input. Pay dirt. This is perfect, and all it requires is some additional resistors. No problem whatever. Let's get to wiring.

(See this tutorial for a from the horses mouth tutorial on how it works)

The wiring for this is super simple. Run one wire into the analog pin of your choice (my program reads from pin A0), and connect that to one side of the button array. The other side of the button array is connected to ground. From there, the buttons are simply chained together via a resistor network. An additional wire is added from each button to the analog input pin, so every button has a direct path to the analog pin (see schematic).

Taking the pertinent pieces of the code from the tutorial and modifying it for our own needs, we get the following:

/*
This is the application that I will be using to control the
 furnace. The concept is this: I have 3 zones I will be monitoring.
 Each zone will have at least one zone damper which will be
 controlled by the arduino application.
 Each zone will have a single thermometer which will report
 back to the controller. Depending on the thermometer reading and
 the thermostat setting, the dampers will open or shut and the 
 central furnace will be turned on. Once an acceptable temperature
 has been reached in all zones, the furnace will turn off. 
 
 In addition to general heating cycles, the system will be
 programmable. At this time, however, the programming cycle will be handled here, not via the thermostat.
 
 IMPORTANT TEST CASES: (to be added as I think of them/come across them
 
 * Need to ensure that the furnace is always off if all 3 dampers are closed.
 * dampers should be open unless they specifically need to be closed.
 * if system is set to "hold", all zones are set to open and the programming does not run.
 * if system is overridden but not held, the override stops once the next programmed interval comes up
 
 */

#include   //This is a library needed for the LCD display
#include         //This is a library needed for the thermometer readings
#include  //This is a temperature library
#include 
#include "RTClib.h"


// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

byte deg[8] = {
  B01000,
  B10100,
  B01000,
  B00111,
  B00100,
  B00110,
  B00100,
  B00100
};
const int backlight = 13; //controls backlight

#define THERM_BUS 6 // all temperature readings come in via this single pin.


OneWire oneWire(THERM_BUS);
DallasTemperature sensors(&oneWire);

DeviceAddress zone1Therm = {
  0x28, 0x48, 0x39, 0x11, 0x04, 0x00, 0x00, 0x94};
DeviceAddress zone2Therm = {
  0x28, 0xF2, 0x1F, 0x11, 0x04, 0x00, 0x00, 0x38};
DeviceAddress zone3Therm = {
  0x28, 0xBA, 0xF3, 0x10, 0x04, 0x00, 0x00, 0x98};

RTC_DS1307 RTC;

// START PROGRAMMING
// Times are 6 AM, 8 AM, 3PM, and 9PM
int* PROGRAMMED_VALUES[7];
int SUNDAY_VALUES[4]      = {
  67, 67, 67, 60};
int MONDAY_VALUES[4]      = {
  67, 67, 67, 60};
int TUESDAY_VALUES[4]     = {
  67, 67, 67, 60};
int WEDNESDAY_VALUES[4]   = {
  67, 67, 67, 60};
int THURSDAY_VALUES[4]    = {
  67, 67, 67, 60};
int FRIDAY_VALUES[4]      = {
  67, 67, 90, 75};
int SATURDAY_VALUES[4]    = {
  67, 67, 67, 60};
//END PROGRAMMING


float zone1Temp = 0;
float zone2Temp = 0;
float zone3Temp = 0;
boolean z1Check = 0;
boolean z2Check = 0;
boolean z3Check = 0;
boolean isOverrideHeld    = false;
boolean isOverridden      = false;
boolean furnaceState      = false; //with furnaceState, false means it's currently off, true means its currently on.
String thermState         = "run";
int overrideValue         = 0;

const int furnacePin      = 1;
int zoneCounter           = 0;
int a = 0;
void setup() {
  Serial.begin(57600); // This connects the arduino to the computer
  //TEMPERATURE SENSOR SETUP
  sensors.begin();
  sensors.setResolution(zone1Therm, 10);
  lcd.createChar(0, deg);
  // RTC SETUP.
  Wire.begin();
  RTC.begin();
  if(!RTC.isrunning()){
    log("ERROR", "RTC is NOT running!");
    //RTC.adjust(DateTime(__DATE__, __TIME__)); //THIS ONLY NEEDS TO BE UNCOMMENTED IF YOU ARE SETTING UP YOUR RTC FOR THE FIRST TIME! ONCE IT IS SET, THIS MUST BE DISABLED!
  }

  //SETUP OUTPUTS
  pinMode(furnacePin, OUTPUT);
  pinMode(backlight, OUTPUT);
  digitalWrite(backlight, HIGH);
  
  //SETUP INPUTS
  pinMode(A0, INPUT_PULLUP); 
  //SETTING UP THE WEEKLY PROGRAM
  PROGRAMMED_VALUES[0] = SUNDAY_VALUES;
  PROGRAMMED_VALUES[1] = MONDAY_VALUES;
  PROGRAMMED_VALUES[2] = TUESDAY_VALUES;
  PROGRAMMED_VALUES[3] = WEDNESDAY_VALUES;
  PROGRAMMED_VALUES[4] = THURSDAY_VALUES;
  PROGRAMMED_VALUES[5] = FRIDAY_VALUES;
  PROGRAMMED_VALUES[6] = SATURDAY_VALUES;
}


// this runs the continual loop 
void loop() {
  delay(3000); //DELAY CURRENTLY SET TO 10 SECONDS. WILL ADJUST AS NEEDED
  
  //DATE/TIME LOGIC
  log("TIME", "Loop initiated at " + getDateAndTime());
  //TEMPERATURE LOGIC
  sensors.requestTemperatures();
  zone1Temp = getTemperature(zone1Therm);
  zone2Temp = getTemperature(zone2Therm);
  zone3Temp = getTemperature(zone3Therm);
  logTemperatureData();
  getScreenOutput();
  z1Check = checkZoneTemp(zone1Temp);
  z2Check = checkZoneTemp(zone2Temp);
  z3Check = checkZoneTemp(zone3Temp);
  
  if(z1Check || z2Check || z3Check){
     startFurnace();
  } else {
    stopFurnace();
  }
  if(z1Check && furnaceState) {
    closeDamper(1);
  } else {
    openDamper(1);
  }
  if(z2Check && furnaceState) {
    closeDamper(2); 
  } else {
    openDamper(2);
  }
  if(z3Check && furnaceState) {
    closeDamper(3); 
  } else {
    openDamper(3);
  }
  a = analogRead(0);
  //THESE NUMBERS CHANGE DEPENDING ON YOUR RESISTORS.
  if(a < 20) {
    //This means temperature up.
    isOverridden = true;
  }
  if(a < 30 && a > 20) {
    //This means temperature down.
    isOverridden = true;
  }
  if(a < 45 && a > 30){
    //This is a toggle for hold/run
    if(thermState == "hold") {
      thermState = "run";
      isOverridden = false;
    } else {
      thermState = "hold";
      isOverridden = true;
    }
    isOverridden = true;
  }
  if(a < 60 && a > 45){
    //DO I NEED A FOURTH BUTTON?
  }  
}
boolean checkZoneTemp(int temperature) {
  int temp = getTargetTemperature();
  if(temperature > temp){
    return false;
  }
  return true;
}
void logTemperatureData() {
  log("Zone1Temp", (String)(int)zone1Temp);
  log("Zone2Temp", (String)(int)zone2Temp);
  log("Zone3Temp", (String)(int)zone3Temp); 
}
void getScreenOutput() {
    zoneCounter++;
  if(zoneCounter == 1){
    String val = "Zone1: " + (String)(int)zone1Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Mode: " + getModeString());
  }
  else if (zoneCounter == 2){
    String val = "Zone2: " + (String)(int)zone2Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Furnace: " + getFurnaceState());
  }
  else {
    zoneCounter = 0;
    String val = "Zone3: " + (String)(int)zone3Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Open: " + getActiveZonesForScreen());
  }
}
int getTargetTemperature(){
  int mode = getMode();
  DateTime now = RTC.now();
  int date = now.dayOfWeek();
  int* values = { 
    0         };
  log("info", (String)date);
  values = PROGRAMMED_VALUES[date];
  log("info", "getting target temp: " + (String)values[mode]);
  return values[mode];
}
String getActiveZonesForScreen(){
  String retVal = "";
  if(z1Check && !furnaceState){
    retVal += "1,";
  }
  if(z2Check && !furnaceState) {
    retVal += "2,";
  }
  if(z3Check && !furnaceState){
    retVal += "3";
  }
  if(retVal.length() == 0) {
    retVal += "None";
  }
  return retVal;
}

String getFurnaceState(){
  if(furnaceState){
    return "On";
  } 
  else {
    return "Off";
  }
}
String getModeString() {
  int mode = getMode();
  switch (mode) {
  case 0:
    return "Wake";
  case 1:
    return "Away";
  case 2:
    return "Home";
  case 3:
    return "Sleep";
  default:
    return "N/A";
  }
}
int getMode() {
  DateTime now = RTC.now();
  int hour = now.hour();
  if(hour < 6 || hour >= 21) {
    return 3;
  }
  if(hour >= 6 && hour < 8){
    return 0;
  }
  if(hour >=8 && hour < 15){
    return 1;
  }
  if(hour >= 15 && hour < 21){
    return 2;
  }
}
float getTemperature(DeviceAddress deviceAddress) {
  float tempC = sensors.getTempC(deviceAddress);
  //Serial.println(tempC);
  if(tempC == -127){
    Serial.println("ERROR getting temperature.");
  }
  return DallasTemperature::toFahrenheit(tempC);
}
//convenience function. Handles all the boilerplate for writing to the LCDScreen
void toScreen(String line1Value, String line2Value) {
  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(line1Value);
  lcd.setCursor(0,1);
  lcd.print(line2Value);
}

String getDateAndTime() {
  DateTime now = RTC.now();
  return String(now.year()) + "/" + String(now.month()) + "/" + String(now.day()) + " " + String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
}
void openDamper(int damper) {
  //  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
  //    digitalWrite(damper, HIGH);
  //  } else {
  //    log("Problem opening damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
  //  }
}
void closeDamper(int damper) {
  //  if(damper == damper1Pin || damper == damper2Pin || damper == damper3Pin) {
  //    digitalWrite(damper, LOW);
  //  } else {
  //    log("Problem closing damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
  //  }  
}

//function for logging messages to the console.
//Eventually plan to write to logfile on RPi
void log(String code, String message) {
  Serial.println(code + ": " + message);
}
void recordData(String type, String object, String value) {
  // TODO - output to RPi database
}
void startFurnace(){
  //Send appropriate signal to the furnace to start up
  if(!furnaceState) {
    furnaceState = !furnaceState;
    log("INFO", "STARTING FURNACE");
    digitalWrite(furnacePin, HIGH); 
  }
}
void stopFurnace(){
  //Send appropriate signal to the furnace to stop
  if(furnaceState){
    furnaceState = !furnaceState;
    log("INFO", "STOPPING FURNACE");    
    digitalWrite(furnacePin, LOW);
  }
}

Step 9: Make the Buttons DO Something.

Each button has to be assigned a function or purpose. In our case, the first and second buttons are for increasing or decreasing the buttons respectively, and the third button is for toggling between run or hold mode. I've included a fourth button, but I don't know if its needed. For now I'll leave it in, but we'll see.

The Final Code:

For the purposes of this Instructable, this is the final code

/*
/*
This is the application that I will be using to control the
 furnace. The concept is this: I have 3 zones I will be monitoring.
 Each zone will have at least one zone damper which will be
 controlled by the arduino application.
 Each zone will have a single thermometer which will report
 back to the controller. Depending on the thermometer reading and
 the thermostat setting, the dampers will open or shut and the 
 central furnace will be turned on. Once an acceptable temperature
 has been reached in all zones, the furnace will turn off. 
 
 In addition to general heating cycles, the system will be
 programmable. At this time, however, the programming cycle will be handled here, not via the thermostat.
 
 IMPORTANT TEST CASES: (to be added as I think of them/come across them
 
 * Need to ensure that the furnace is always off if all 3 dampers are closed.
 * dampers should be open unless they specifically need to be closed.
 * if system is set to "hold", all zones are set to open and the programming does not run.
 * if system is overridden but not held, the override stops once the next programmed interval comes up
 * can only override to explicit upper and lower bounds.
 */

#include <LiquidCrystal.h>  //This is a library needed for the LCD display
#include <OneWire.h>        //This is a library needed for the thermometer readings
#include <DallasTemperature.h> //This is a temperature library
#include <Wire.h>
#include "RTClib.h"


// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

byte deg[8] = {
  B01000,
  B10100,
  B01000,
  B00111,
  B00100,
  B00110,
  B00100,
  B00100
};
// const int backlight = 13; //controls backlight

#define THERM_BUS 6 // all temperature readings come in via this single pin.


OneWire oneWire(THERM_BUS);
DallasTemperature sensors(&oneWire);

DeviceAddress zone1Therm = {
  0x28, 0x48, 0x39, 0x11, 0x04, 0x00, 0x00, 0x94};
DeviceAddress zone2Therm = {
  0x28, 0xF2, 0x1F, 0x11, 0x04, 0x00, 0x00, 0x38};
DeviceAddress zone3Therm = {
  0x28, 0xBA, 0xF3, 0x10, 0x04, 0x00, 0x00, 0x98};

RTC_DS1307 RTC;

// START PROGRAMMING
// Times are 6 AM, 8 AM, 3PM, and 9PM
int* PROGRAMMED_VALUES[7];
int SUNDAY_VALUES[4]      = {
  67, 67, 67, 60};
int MONDAY_VALUES[4]      = {
  67, 67, 67, 60};
int TUESDAY_VALUES[4]     = {
  67, 67, 67, 60};
int WEDNESDAY_VALUES[4]   = {
  67, 67, 67, 60};
int THURSDAY_VALUES[4]    = {
  67, 67, 67, 60};
int FRIDAY_VALUES[4]      = {
  67, 67, 90, 75};
int SATURDAY_VALUES[4]    = {
  67, 67, 67, 60};
//END PROGRAMMING


float zone1Temp           = 0;
float zone2Temp           = 0;
float zone3Temp           = 0;
boolean z1Check           = 0;
boolean z2Check           = 0;
boolean z3Check           = 0;
boolean isOverridden      = false;
boolean furnaceState      = false; //with furnaceState, false means it's currently off, true means its currently on.
String thermState         = "run";
int overrideValue         = 0;
int currentMode           = 0;

const int furnacePin      = 7;
int zoneCounter           = 0;
int a                     = 0;
int loopCounter           = 0;
int z1Pin                 = 8;
int z2Pin                 = 9;
int z3Pin                 = 13; //scavenging backlight pin for z3 
void setup() {
  Serial.begin(57600); // This connects the arduino to the computer
  //TEMPERATURE SENSOR SETUP
  sensors.begin();
  sensors.setResolution(zone1Therm, 10);
  lcd.createChar(0, deg);
  // RTC SETUP.
  Wire.begin();
  RTC.begin();
  if(!RTC.isrunning()){
    log("ERROR", "RTC is NOT running!");
    //RTC.adjust(DateTime(__DATE__, __TIME__)); //THIS ONLY NEEDS TO BE UNCOMMENTED IF YOU ARE SETTING UP YOUR RTC FOR THE FIRST TIME! ONCE IT IS SET, THIS MUST BE DISABLED!
  }

  //SETUP OUTPUTS
  pinMode(furnacePin, OUTPUT);
  pinMode(z1Pin, OUTPUT);
  pinMode(z2Pin, OUTPUT);
  pinMode(z3Pin, OUTPUT);
  // eventually fit backlight control back into arduino. For now we'll just set it to constant.
  // digitalWrite(backlight, HIGH);
  
  //SETUP INPUTS
  pinMode(A0, INPUT_PULLUP); 
  //SETTING UP THE WEEKLY PROGRAM
  PROGRAMMED_VALUES[0] = SUNDAY_VALUES;
  PROGRAMMED_VALUES[1] = MONDAY_VALUES;
  PROGRAMMED_VALUES[2] = TUESDAY_VALUES;
  PROGRAMMED_VALUES[3] = WEDNESDAY_VALUES;
  PROGRAMMED_VALUES[4] = THURSDAY_VALUES;
  PROGRAMMED_VALUES[5] = FRIDAY_VALUES;
  PROGRAMMED_VALUES[6] = SATURDAY_VALUES;
}


// this runs the continual loop 
void loop() {
  delay(100); //DELAY CURRENTLY SET TO .1 SECONDS. WILL ADJUST AS NEEDED
  //DATE/TIME LOGIC
//  if(loopCounter % 2 == 0){
//    log("TIME", "Loop initiated at " + getDateAndTime());
//  }
  //TEMPERATURE LOGIC
//  logTemperatureData();
  if(loopCounter % 30 == 0) {
    log("info", (String)loopCounter);
    //changes output every 3 seconds
    outputToScreen();
  }

  // The following allows us to only check furnace state every 60 seconds while reading the button changes in effective realtime
  if(loopCounter % 60 == 0) { 
    log("info", (String)loopCounter);
    sensors.requestTemperatures();
    zone1Temp = getTemperature(zone1Therm);
    zone2Temp = getTemperature(zone2Therm);
    zone3Temp = getTemperature(zone3Therm);
    z1Check = checkZoneTemp(zone1Temp);
    z2Check = checkZoneTemp(zone2Temp);
    z3Check = checkZoneTemp(zone3Temp);
    loopCounter = 0;
    if(z1Check || z2Check || z3Check){
       startFurnace();
    } else {
      stopFurnace();
    }
    if(z1Check && furnaceState) {
      closeDamper(z1Pin);
    } else {
      openDamper(z1Pin);
    }
    if(z2Check && furnaceState) {
      closeDamper(z2Pin); 
    } else {
      openDamper(z2Pin);
    }
    if(z3Check && furnaceState) {
      closeDamper(z3Pin); 
    } else {
      openDamper(z3Pin);
    }
  }
  a = analogRead(0);
  //THESE NUMBERS CHANGE DEPENDING ON YOUR RESISTORS.
  int targetTemp = getProgrammedTargetTemperature();
  if(a < 20) {
    //This means temperature up.
    isOverridden = true;
    if(overrideValue != targetTemp && overrideValue != 0){
      overrideValue++;
    } else {
      overrideValue = targetTemp + 1;
    }
    if(overrideValue > 80){
      overrideValue = 80; 
    }
    log("info", "temperature up to " + (String)overrideValue);
    loopCounter = -1; //THIS RESETS THE LOOP COUNTER, MEANING EVERYTHING WILL BE RUN AGAIN
  }
  if(a < 30 && a > 20) {
    //This means temperature down.
    isOverridden = true;
    if(overrideValue != targetTemp && overrideValue > 0){
      overrideValue--;
    } else {
      overrideValue = targetTemp - 1;
    }
    if(overrideValue < 50) {
      overrideValue = 50;
    } 
    log("info", "temperature down to " + (String)overrideValue);
    loopCounter = -1; //THIS RESETS THE LOOP COUNTER, MEANING EVERYTHING WILL BE RUN AGAIN  
  }
  if(a < 45 && a > 30){
    //This is a toggle for hold/run
    if(thermState == "hold") {
      thermState = "run";
      isOverridden = false;
      log("info", "setting thermState to run");
    } else {
      thermState = "hold";
      isOverridden = true;
      log("info", "setting thermState to hold");      
    }
    isOverridden = true;
    loopCounter = -1; //THIS RESETS THE LOOP COUNTER, MEANING EVERYTHING WILL BE RUN AGAIN 
  }
  if(a < 60 && a > 45){
    //DO I NEED A FOURTH BUTTON?
  }
  loopCounter++;
}
boolean checkZoneTemp(int temperature) {
  int temp = getTargetTemperature();
  if(temperature > temp){
    return false;
  }
  return true;
}
void logTemperatureData() {
  log("Zone1Temp", (String)(int)zone1Temp);
  log("Zone2Temp", (String)(int)zone2Temp);
  log("Zone3Temp", (String)(int)zone3Temp); 
}
void outputToScreen() {
    zoneCounter++;
  if(zoneCounter == 1){
    String val = "Zone1: " + (String)(int)zone1Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Mode: " + getModeString());
  }
  else if (zoneCounter == 2){
    String val = "Zone2: " + (String)(int)zone2Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Furnace: " + getFurnaceState());
  }
  else {
    zoneCounter = 0;
    String val = "Zone3: " + (String)(int)zone3Temp;
    val += " (" + (String)getTargetTemperature();
    val += (String)")";
    toScreen(val, "Open: " + getActiveZonesForScreen());
  }
}
void checkMode() {
  int mode = getMode();
  if(mode != currentMode) {
    currentMode = mode;
    if(isOverridden) {
      if(thermState == "run") {
        //This logic simply overturns the override value and resumes the program if the thermstate is not held. Otherwise, the overrideValue is used.
        isOverridden = false;
      }
    }
  }
}
int getTargetTemperature(){
  checkMode();
  if(isOverridden) {
    return overrideValue;
  } else {
    return getProgrammedTargetTemperature();
  }
}
int getProgrammedTargetTemperature() {
  int mode = getMode();
  DateTime now = RTC.now();
  int date = now.dayOfWeek();
  int* values = { 0 };
  values = PROGRAMMED_VALUES[date];
//  log("info", "getting programmed target temp: " + (String)values[mode]);
  return values[mode];
}
String getActiveZonesForScreen(){
  String retVal = "";
  if(z1Check && !furnaceState){
    retVal += "1,";
  }
  if(z2Check && !furnaceState) {
    retVal += "2,";
  }
  if(z3Check && !furnaceState){
    retVal += "3";
  }
  if(retVal.length() == 0) {
    retVal += "None";
  }
  return retVal;
}

String getFurnaceState(){
  if(furnaceState){
    return "On";
  } 
  else {
    return "Off";
  }
}
String getModeString() {
  if(isOverridden) {
    if(thermState == "run") {
      return "Override";
    } else {
      return "HOLD";
    }
  }
  int mode = getMode();
  switch (mode) {
  case 0:
    return "Wake";
  case 1:
    return "Away";
  case 2:
    return "Home";
  case 3:
    return "Sleep";
  case -1:
    return "Error";
  default:
    return "N/A";
  }
}
int getMode() {
  DateTime now = RTC.now();
  int hour = now.hour();
  if(hour < 6 || hour >= 21) {
    return 3;
  }
  if(hour >= 6 && hour < 8){
    return 0;
  }
  if(hour >=8 && hour < 15){
    return 1;
  }
  if(hour >= 15 && hour < 21){
    return 2;
  }
  return -1;
}
float getTemperature(DeviceAddress deviceAddress) {
  float tempC = sensors.getTempC(deviceAddress);
  //Serial.println(tempC);
  if(tempC == -127){
    Serial.println("ERROR getting temperature.");
  }
  return DallasTemperature::toFahrenheit(tempC);
}
//convenience function. Handles all the boilerplate for writing to the LCDScreen
void toScreen(String line1Value, String line2Value) {
  lcd.begin(16,2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(line1Value);
  lcd.setCursor(0,1);
  lcd.print(line2Value);
}

String getDateAndTime() {
  DateTime now = RTC.now();
  return String(now.year()) + "/" + String(now.month()) + "/" + String(now.day()) + " " + String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
}
void openDamper(int damper) {
   if(damper == z1Pin || damper == z2Pin || damper == z3Pin) {
     digitalWrite(damper, HIGH);
     // log("info", "Opening damper" + damper);
   } else {
     log("error", "Problem opening damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
   }
}
void closeDamper(int damper) {
   if(damper == z1Pin || damper == z2Pin || damper == z3Pin) {
     digitalWrite(damper, LOW);
     // log("info", "Closing damper" + damper);
   } else {
     log("error", "Problem closing damper. Damper not found. Was expecting 1, 2, or 3. Got " + damper);
   }  
}

//function for logging messages to the console.
//Eventually plan to write to logfile on RPi
void log(String code, String message) {
  Serial.println(code + ": " + message);
}
void recordData(String type, String object, String value) {
  // TODO - output to RPi database
}
void startFurnace(){
  //Send appropriate signal to the furnace to start up
  if(!furnaceState) {
    furnaceState = !furnaceState;
    log("INFO", "STARTING FURNACE");
    digitalWrite(furnacePin, HIGH); 
  }
}
void stopFurnace(){
  //Send appropriate signal to the furnace to stop
  if(furnaceState){
    furnaceState = !furnaceState;
    log("INFO", "STOPPING FURNACE");    
    digitalWrite(furnacePin, LOW);
  }
}

Step 10: Next Steps

This has mostly been a theoretical build. I fully intend on installing it as my actual thermostat, but I need to do some further soldering and other assorted setups in order to have a really nice wall-adorning thermostat (my kids would have a ball pulling wires out of the electronics on the wall, but it's not something I want to continually be putting back together).

My next steps will be to fashion a housing for the main thermostat as well as the two remote temperature sensors. These housings will sit on the wall in their respective zones. I've decided I will be using hardwood maple for the housings, so they should look fairly sharp once attached to the wall. I have some ideas for the design of them, but nothing set in stone, yet.

Additionally, I will want to reclaim my Arduino board and replace this circuit with a hackduino or something similar. No use having a fast prototyping board in the final product of a project.

Beyond that, some simple soldering and affixing the components to permanent breadboards will be required, as again these breadboards are not designed for permanent use.

I have to either procure or build out some zone dampers. Once these have been procured and installed, the zone aspect of this thermostat will be fully realized.

And finally, I will have to install a Raspberry Pi or equivalent tiny web server into my main thermostat body. From the webserver, I'll be able to monitor my thermostat data in real time as well as eventually turning it into a 'smart' thermostat.

I hope you enjoyed this Instructable. I haven't really found exactly this thing, and while a Nest is cool and swoopy zoomy, this can already do things the Nest cannot and will probably never be able to do.

Thanks!

<p>Be careful about Air Temperature Rise in your furnace (supply side vs return side temp delta = Air Temperature Rise). ATR should be measured across the furnace and a safety shutoff built in when the ATR of the furnace is exceeded. If you have a furnace / air conditioner combo unit, ensure that there is enough airflow across the evap coil or it may freeze (when in cooling mode).</p>
You *need* to check out what a PID algorithm is and how to adapt it to your thermostat. It's what would make it smart.
<p>Thanks for the info! I'll check it out. Haven't had a lot of time to dig into the arduino lately, but That's definitely interesting.</p>
<p>Build_It_Bob</p><p>Yes those look awesome. I'm working on folding them into the larger instructable. Still working on the other parts of this Instructable. Stay tuned...</p>
<p>If anyone is interested , I have made the Fritzing layouts for this project . The PCB layout will need tweaking depending on what is decided for the damper and furnacePin outputs .</p><p>I have built this on a breadboard including the DS18B20 temperature sensors.Keep in mind that I use the Arduino Pro Mini in anything I build that is a &quot;permanent &quot; installation , so the PCB includes this as opposed to using the Uno .</p><p>Please check this over hbomb9000 and let me know if there are any issues or if you would like to add to it. </p><p>ps: The latest version of Fritzing is amazing !</p><p>Build_it_Bob</p>
<p>Since it is temperature and LCD. You might not need RTC unless you want better timing or making clock. Just my opinion. Most ATmega 328P will be timing okay without RTC but it will be fastest or slowest. </p>
<p>The reason I've added the RTC is so that the programmed temperatures can be set at the appropriate time. I know the ATmega has some timing in it, but since I need to know day and hour, I wanted something a bit more certain and a bit less susceptible to trivial things like the power dropping.</p><p>Thanks for advice, though. If I was just doing temperature and displaying the temperature and turning the furnace on/off depending on a single temperature, I would definitely have left the RTC off.</p>
Yeah it is good choose if you need preiscon timing. ATmega do have limited timing. You can put time and dating on ATmega; however, with millis it will reset to zero if it reach 1200 hours. Due to limited memory RAM. Delay never have limited timing but it will freeze everything which is not recommend.
<p>I went down this same road to do just a gas powered furnace. The arduino did great at acquiring the temp/hum sensor and triggered the furnace accordingly. I set a hysteresis of 2 degrees. I can control it via the web, but it is very hit and miss. I am attempting to deploy it via Raspberri Pi now.</p>
<p>I am planning on adding a RPi to the system, to handle both logging and data collection but possibly remote control of the system as well. I think that both devices have their place (the Arduino and the RPi) in a setup such as this, and you have to pick the best device for the part of the system you're working on.</p>
<p>You should return a fixed &quot;error&quot; value from getTemperature (or test for the Fahrenheit equivalent of -127 which is -196.6). In its current form, your code will run the furnace until you manually intervene if one of your sensors is disconnected or dies.</p>
<p>Good catch. I'll add it right away, and make changes to the Instructable itself. Thanks!</p>
<p>Where is your provision for dwell? My commercially made home thermostat has a dwell of 1 and 1/2 degree F. This keeps the central air unit from cycling on and off too frequently.</p>
<p>You're absolutely correct, I will have to add in a dwell or 'fudge factor' before this thing is operational. It's been in the back of my head, but lower priority. Thanks for bringing it up!</p>
<p>What an amazing journey ...great job walking through this project . I definately will be reading through your code ( many times ) to understand better what you have managed to do in combining these aspects together . I will be following you to see what else I may be able to learn from your excellent style of writing and problem solving . Thank you again for sharing with the community !</p><p>Build_it_Bob</p>
<p>Thanks. It's been a lot of fun and has definitely been a sanity saver to work on. I've only recently gotten into electronics and have to say I'm very pleased with the simplicity of the Arduino environment as far as development is concerned. Having things prefabricated and modular is super helpful and allowed me to not get bogged down in things that would have made the learning curve much steeper.</p><p>I've got a few other projects in the hopper. Stay tuned for more...!</p>
<p>I love the way you code. I have a lot to learn from your code. My current project would need a cleaning with what I am learning here.</p><p>Please modify when you reach your final step. Would be interesting to see how it fits plus the connection with the rPi.</p>
<p>Thanks for the comments on the code. It's my first foray into C and/or C++, but programming is programming, right?</p><p>That said, one of the most important aspects in programming is commenting before you start putting actual code in. If you know what you WANT to do in plain language, it makes it much easier to ensure your code is doing what you mean for it to do.</p><p>I will definitely be posting more as I push towards final completion of this project. My next step is converting my prototyped circuit into something that can be installed into a box that hangs on the wall. Then, I build out the box, install the dampers and check valve, install the hardware into the box, then FINALLY install the RPi. I haven't decided if I'll build out a webserver in order to attach it to a phone or web app a la a &quot;Nest&quot;. It might be nice, but the security trouble might not make it worth it.</p><p>But stay tuned for updates!</p>
<p>i built something like this but added a few things.</p><p>i made 12C attiny84 boards with dual temp and dual servo control that linked to a arLCD in my hall. it looked at the temp of each room and open / closed vents so each room can be at it's own temp worked with both heating and cooling using PID</p><p>vents are 3D printed and 2 sided tape holds them on(3M VHB Rocks) the vents can point the air +-40deg up-down, left-right. or 95% seal. i will post my files if you like</p>
<p>I was told some time ago by a furnace technician that even something as simple as placing filters at the the room vents could be detrimental to the lifespan of the furnace. When I get my dampers installed, I'll definitely have a weighted 'air overflow' check valve to make sure the pressure the furnace has to push against isn't out of tolerance.</p><p>That said, I'm very interested in seeing the 3D printed vents. I've only done a very few things with 3D printing and am eager to get deeper into the tech as a prototyping tool. So please, POST AWAY! :)</p>
There are I2C &quot;backpacks &quot; available for those displays available very cheaply. If you used one of those, you'd only need 2 pins for the display. ;)
<p>Thanks. I'll check them out before I install everything.</p>
http://pages.ebay.com/link/?nav=item.view&amp;id=350930309635 plenty of other vendors.
<p>That is, before I do the final install into my house.</p>

About This Instructable

54,395views

311favorites

License:

More by hbomb9000:Tactical Paracord Zipper Pull Arduino Powered 3-zone thermostat Leather knife sheath 
Add instructable to: