Arduino Thermostat Project

Now! With Android app

Nest aint got nothin' on me!

When my "smart" thermostat died last year, I went to the store to replace it. The thermostat market has changed since I last bought one. There used to be only four tiers of thermostats:

  • "Dumb" ones, full manual control of the temperature. seven days of the week. Priced below $20 (All prices US $).
  • "One day" programmable ones: assumes every day has the same schedule. Priced below $30.
  • "Five/Two day" programmable ones: assumes that the weekend and weekdays are on two different schedules, but the weekdays are all the same and Saturday and Sunday are the same. Priced below $50.
  • "Seven day" programmable ones: Allows seven different programs for the seven days of the week. Priced between $50 and $100.

Now, there's a fifth tier: "internet" and "phone controlled" thermostats, which run an eye-watering $250. I thought "that's stupid, I can make a better one than that, and it'll be waaaaay cooler than any of these."

Several months of hacking and coding later, I have succeeded. I finished the project just in time for the heating season to end last year, but winter 2014 heating season is under the control of my own thermostats.

I decided on the following specifications/features:

  • The thermostat would be on my network and would be controllable via the web.
  • It would get the time via NTP, and so handle Daylight Savings Time, which may be stupid, but that's what we've got, so I deal with it. But at least I won't have to dick around with the thermostat's time twice a year.
  • The thermostat would control 3 of the four zones of my home heating system. The fourth zone would remain under the control of an ordinary "2 day/5 day" thermostat, thus acting as a backup in case the Arduino packs it in. That way I won't come home to frozen pipes if the main thermostat breaks and I'm away for a weekend during a cold spell.
  • The thermostat would have "one day" programmability, as I'm retired so I don't give a crap about "work days" vs "weekends" anymore.
  • I would try to have ONE Arduino control all three zones, and the two remote display units.
  • The remote display units would be at least as capable as any ordinary "one day" thermostat, in that one would be able to turn the heat on and off, adjust the temperature, and fiddle with the programming for that zone from the zone display unit.
  • The main display unit would be able to control the programs for all three zones. This way you don't have to go running around the house to change all three programs; you can do it from the main unit or from the web interface.
  • Finally, I invented my "killer" feature: "Vacation Mode", which allows one to tell the system how many weeks,days,hours one will be out of the house, and the system will turn the temperatures down to 60° in all three zones, and keep them there until the vacation timer counts down to 0, at which time the temperature returns to 70°

I read up on Arduino thermostat projects, and found this one to my liking. (The web site is so I will call the author "DSG" for "desert home guy" since he doesn't give his name.) So this is the basis for my code, and you'll find similarities between my code to his code, though I re-wrote most of it to fit my own situation.

Like DSG, I'm using this Serial LCD controller to make all my LCDs accessable via a serial port. I'm using the Arduino Mega 2560 which has four hardware serial ports, so I have one for programming the Arduino and three to talk to the three LCDs.

Instead of the LM335 Temp sensors DSG is using, I went with DHT22 Temperature/Humidity sensors. The humidity is a nice thing to be able to display on the LCDs in each room. I get these from random Ebay vendors.

I use a DS1302 Real Time Clock as a backup clock, in case the network goes down, this will keep the thermostat system time.

I use little 5v relays to interface with the 24VAC control inputs to the heating system.

The remote display units are "almost" dumb: they contain the LCD with serial "backpack", a DHT22 Temperature/Humidity sensor, a relay, and a voltage regulator to drop the "raw" 9v to 5v for the other parts. This needs 8 wires (relax, 2 more are called out below), so I use RJ45 connectors and ordinary Cat5 cable to interconnect the remote display units with the main display unit.

I say "almost" because the serial LCD controller has a PIC micro on it, and it has three unused GPIO pins that the vendor has made available as three digital outputs controllable via serial commands. I use this to scan a 2 x 3 "keypad", where the three columns are driven by the GPIO pins on the PIC, and the 2 row lines are sent back to the main unit on the Cat5 cable and from there into 2 of the Arduino Mega 2560's digital inputs.

Unlike DSG, I don't try to use the 24VAC from the heating system to power my thermostat, I just have a 9v DC wall wart mounted in the basement and feed its output up the wall with the rest of the wires and into the back of the main unit. I felt that if I tried to convert the 24VAC to 5V, I would be drawing too much from the 24VAC, causing it to always think I was calling for heat.

Notes and Caveats in no particular order:

  • You will need to filter your temperature readings to prevent the odd spurious/noise reading from causing the system to send a "ON-OFF spike" to the heating system, which cannot be good for it. I use infinite impulse response filters (IIR) because they don't require much computation for the amount of filtering you get.
    I classify inputs as "close to current reading" and "outlier". For the "close" values the filter is:
    new-value = old-value + (reading / 4.0) - (old-value / 4.0)
    and since I'm using floating point arithmetic, this works reasonably well.
    For the "outlier" values I have a much slower IIR filter.
    new-value = old-value + (reading / 16.0) - (old-value / 16.0)
    In this way, if the temperature really does suddenly jump 20 degrees, the system will eventually catch up to this change. The real value is that if a sensor becomes unplugged (or fails) the temperature will drop to some stupid number, like -200 degrees, and then when I fix the problem, the temperature will recover back to the actual reading. I want the display to show a failure if there is one, but I don't want it reacting quickly to the odd spurious reading caused by noise. I'm driving the serial line to the DHT22's over 25 to 50 feet of wire, so noise is likely.
  • The relays draw enough that when they are on, there isn't quite enough voltage making it to the remote units and the LCD units lose contrast. This doesn't seem to hurt anything, and so I haven't bothered to try to raise the supply voltage by one more volt.
  • Yes, using RJ45's for non-ethernet will disturb some people. I clearly label the jacks on the back of the units "NOT ETHERNET", so it doesn't bother me.
  • I use strong magnets (taken from old disk drives) to attach the units to the wall. I glue the magnets to the back of the boxes, and a small piece of steel to the wall, and between that and the chunky wire going into the hole in the wall, the units are stable and yet easy to remove for servicing/hacking.
  • You can do quite a lot of multitasking in an Arduino project by cleverly using timers to schedule "events". What this means is that instead of doing something like:
    // read Foo once a second:
    digitalWrite(FOOENABLE, LOW);
    Foovalue = digitalRead(FOOPIN);
    digitalWrite(FOOENABLE, HIGH);

    You have a milliseconds counter running in a timer interrupt service routine, which looks like this:

    inside TimerISR(), which runs every millisecond:

    . . .
    and then inside loop():
    if(fooTimer >= 1000) {
        fooTimer = 0;
        digitalWrite(FOOENABLE, LOW);
    if(fooTimer = 500) {
        Foovalue = digitalRead(FOOPIN);
        digitalWrite(FOOENABLE, HIGH);
  • My code profiler was invaluable in figuring out where the time was being spent, and in fact I wrote the profiler so I could debug the thermostat "real-time". It's hard to juggle all this: the scan rate of the three keyboards, the scan rate of the temperature sensors and the low pass filtering of same, the web ui responsiveness, and the rest of the housekeeping, like actually monitoring the temperature and controlling the heating system.
  • I have the IP address hard coded in the code, but I also have a "hidden" button that, if pressed during boot, causes the code to use DHCP to get an address from the DHCP server. So if I change my network class C for some reason, I can get the thermostat working again simply by rebooting it with the "use DHCP" button held down. Successful aquisition of an IP address is reported on the LCD and held there until the button is released. Finally, an LED on the side of the box illuminates if the thermostat successfully finds an NTP server, so that I know networking is working and the box is connected.
  • Those illuminated displays are really bright at night. I had to turn the duty cycle almost to zero in order to be able to sleep at night, otherwise the bedroom LCD backlight lit up the whole room. I do have a timer for each display, so that the LCD backlight gets brighter when one touches the keyboard, and dims again after several minutes of inactivity.
  • Of course, the settings are stored in the internal EEPROM of the Arduino.
  • It's worthwhile learning about PROGMEM. In this code, the HTML template for the web page is stored in PROGMEM so that it doesn't use up valuable RAM. Anytime you have constant data of any appreciable size, it should be stored in PROGMEM. For an Arduino UNO, "appreciable" might be as little as 100 bytes, as the UNO has so little RAM.

Link to the code. Last updated 20210117.
Link to a copy of the DHT library.
Link to a copy of the DS1302 library.

Note: this code compiles with the Arduino version 1.5.4 IDE.

Note 2: the code has been updated to my latest version, which has the network parameters (ip address, gateway, etc) stored in EEPROM.

Note 3: the code has been updated to my latest version, which includes the MQTT feature and fixes a bug with the web page.

Pictures of the boxes. No pictures of the insides, it looks like a dog's breakfast in there.

The main thermostat box in the living room. You can see that I spared no expense on the front panel labeling.

The bedroom control unit (with camera flash).

The bedroom control unit (without camera flash).

The kitchen control unit.

The rear of the kitchen control unit showing the mounting magnets and the RJ45 connector and the control pair going to the heating unit.

Screenshot of the web interface.

A schematic would be a lot of work, showing all the interconnects and cabling, and is specific to my situation, so instead, you get this wiring list.

The User Manual.

New feature added much later: The thermostat now "does" MQTT. It broadcasts temperature and humidity readings and how much "vacation" time is left. One can also set the zone temp set points and vacation times via MQTT. You will have to edit your MQTT user and password into thermonew.ino. If you don't have MQTT, you will need to "comment out" the MQTT code. This is left as an exercise for the reader.

The Android application to view/change the current temperature of the three zones. The app scrapes the web page for the current setpoint and current temp, so if you change the format of the web page in the Arduino code, you will have to rebuild the Android app. Source for that here. It's built with MIT App Inventor 2.

Track Back

William Dudley is a retired electrical engineer and computer programmer who spent many years programming embedded systems, on everything from 8048's to 68HC11's. He spent many more years writing code for MSDOS and then Unix systems, and is glad to be retired. He appreciates the Arduino for it's accessible development environment and wealth of libraries supporting every kind of hardware imaginable.

powered by Apache