Introduction: IR Window AC Controller With Timer

Living in a region with older homes, it's common to have to use window air conditioners. While some of the more expensive ones have scheduling calendars built into them, almost all of them - even many of the cheapest - come with infrared (IR) remote controls.

We recently had a heat wave in NY, and several days I'd get home from work only to find out that I had forgotten to turn up the temperature while I was out of the house - spending my hard earned money to keep the house chilly cool (the way I like it) while I was away.

As I reached for the remote control to turn the AC down, I remembered that the Arduino starter kit I'd ordered had come with an IR receiver and some IR LEDs. I decided to create something that would help me make sure I'd never forget to adjust my AC again.

After I finished the project, I thought it would make a decent 'ible, since I used what I had at hand to create a quick, simple solution. From idea to working prototype only took about 3 hours, and the next night I printed and soldered my current functional production model.

Step 1: Parts List

  1. Your favorite microcontroller - I used an Arduino Uno clone to prototype the device, and an Arduino Nano clone for the finished product.
  2. Breadboard, appropriate jumper cables
  3. IR receiver - I used the one marked "YL-55" in the picture, which apparently comes from a kit similar to this one commonly sold as an HX-1838
  4. Realtime Clock module - I used a DS-1302, which is a little older, but still works for a project like this.
  5. IR LED - sold pretty much everywhere, I got mine from eBay, here's an Amazon link
  6. Appropriate resistor - for a single LED implementation at close range, anything from 100 ohm to 1K ohm will probably work fine. I used 100ohm since I first tested it at a distance, and kept it for the finished product. Hard to tell how bright the LED is shining when you can't see the light :-)
  7. Some way to power the Arduino - I had a female power supply pigtail and a spare 12V power supply
  8. Spare parts to create a box/mount for the device, or a 3D printer to fabricate something clever. I have a FlashForge Finder that works great for quick, simple prototypes like this. But for my prototype, I had an old Arduino Uno box that I had purchased from EBay a while back, and I used rubber bands, wooden sticks, and twist ties to make it work.

Step 2: Figuring Out Your IR Codes

IRremote Arduino Library

For every popular component out there, you can find an Arduino library ready to go that interfaces with that component and does all the heavy lifting. For working with infrared codes, I found this library that I downloaded and installed in the Arduino interface. I won't cover how to do that in this tutorial - there are other tutorials out there to help you learn the Arduino IDE much better than I could.

Reading Your Remote

Along with the IR library, there are example files installed. Load the IRrecvDemo file, wire up the IR receiver component so that VCC goes to a "high" pin on the Arduino, GND goes to a "low" or "GND" pin, and the data pin ("DO") connects to Pin 11 (or modify the "int RECV_PIN = 11;" line in the source to reflect the pin you wired it to). There are good instructions for how to do this on the IR library homepage. After compiling and uploading the code, I opened the Serial Monitor, pointed my remote at the receiver and clicked the up and down arrows. I saw the hex values representing those codes appear in the serial monitor for each button pressed, representing the IR code sequence for that button.

In order to retransmit these codes, though, I discovered that I needed more data than what's provided by this sample program. I modified the code so that it also prints out the bits and decode_type elements of the results structure (the code is also attached):

void loop() {

  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    Serial.print("Bits: ");
    Serial.println(results.bits);
    Serial.print("Remote type: ");
    Serial.println(results.decode_type);
    irrecv.resume(); // Receive the next value
  }
  delay(100);
}

After downloading this program to the Arduino, I pressed the up and down button on the temperature remote and recorded all the values received - these will be specific to the AC remote that you might have, but for my remote it looked like this:

0x8166A15E
Bits: 32
Remote type: 3

One note on the decode_type - it will come back as an integer, and you'll need to decode it into a remote type using the enumeration in the library's IRremote.h file:

typedef
	enum {
		UNKNOWN      = -1,
		UNUSED       =  0,
		RC5,
		RC6,
		NEC,
		SONY,
		PANASONIC,
		JVC,
		SAMSUNG,
		WHYNTER,
		AIWA_RC_T501,
		LG,
		SANYO,
		MITSUBISHI,
		DISH,
		SHARP,
		DENON,
		PRONTO,
	}
decode_type_t;

When I ran this and used my LG air conditioner remote, the decode_type came back as "3", which corresponds to the "NEC" type. The importance of this will be clear in a minute.

Sending the Codes

Now load the example file called IRsendDemo and take a look at it. One thing to note is that there is no variable setting the pin that's being used for the IR LED, by default it's pin 3. I didn't figure out how to modify it, wasn't necessary to for this project as I simply used pin 3. I wired the ground side of the LED with a 100 ohm resistor to limit the current and protect the LED, while still ensuring a nice bright light.

NOTE - the IR LED used to transmit is not the same as the IR Module used to receive the codes. 940nm IR LEDs are sold pretty much everywhere - Amazon, eBay, your favorite electronics store, Radio Shack (if you can still find one). They act pretty much just like regular visible light LEDs, except you can't see the light they produce.

Next - examine the line of code that sends the example IR signal:

irsend.sendSony(0xa90, 12);

The irsend object has a series of methods that correspond to the decode_type that we captured in the receive step above. Depending on what decode_type you got from your remote, you'll need to modify the test code (and my final code in the upcoming steps) to reflect the appropriate IR device type. Alternatively, there is a way to "send raw" in the library, but I didn't take the time to figure that out since it worked for me to change this line to sendNEC instead (the below also substitutes the code for "temp up" on my remote, and the number of bits that I received from the irrecv function call in the previous section):

irsend.sendNEC(0x8166A15E, 32);

If your decode_type was 7, for example, then your code corresponds to SAMSUNG, and you'd need to use the sendSAMSUNG() method to transmit your code.

My final IRsend test code is also attached.

Result

At this point I had successfully reverse engineered my remote control and knew the remote type and discrete codes that I needed to create the AC controller.

Step 3: Using the Realtime Clock

DS1302 Real Time Clock Arduino Library

I found this library that worked well with the DS1302, there are others but I'm a big fan of "if it ain't broke, don't fix it." It comes with a set of good example code that I'll use to demonstrate how to setup the clock in the following sections.

Setting the Date/Time

Before I could use the RTC module, I needed to set the current date/time and verify that it was funcitoning.

NOTE: make sure that your clock module has a battery installed in order to save the date/time when it's not powered. If it doesn't, install a CR2032 coin battery.

I loaded the SetSerial demo app and reviewed the settings (typically at the top of any Arduino program in the "globals" space):

// Set pins:  CE, IO,CLK
DS1302RTC RTC(27, 29, 31);

// Optional connection for RTC module
#define DS1302_GND_PIN 33
#define DS1302_VCC_PIN 35

I modified this to use pins 8-12 as follows so that I could simply plug the RTC module directly into the Arduino Uno header and avoid using jumpers for my prototype. After all, laziness is the father of innovation! Note that the pinouts of your module may vary so review your setup carefully before finalizing your code:

// Set pins:  CE, IO,CLK
DS1302RTC RTC(12,11,10);

// Optional connection for RTC module
#define DS1302_GND_PIN 9
#define DS1302_VCC_PIN 8

With that done, I uploaded the code, attached the DS1302, and opened the serial monitor. The demo app displays a diagnostic message and then starts showing the (incorrect) time, so I knew that I had configured things right in the app. Next I entered the current date/time (in 24-hour format) in the serial monitor in the following format and pressed enter:

yyyy,mm,dd,hh,mm,ss

The demo app reported success in setting the time, and started reporting the new date/time that was now accurate.

Reading the Date/Time

Next I loaded the demo code DS1302_Serial from the DS1302_RTC library. Again, I modified the configuration to match the wiring of my RTC module, same as I did in the prior section. I downloaded the program, loaded the serial monitor, and confirmed that it initializes the RTC module and reads back the current time, which was now accurate to the time I set in the prior section.

Note the code in the setup section required to configure the RTC for reading:

// Activate RTC module
  digitalWrite(DS1302_GND_PIN, LOW);
  pinMode(DS1302_GND_PIN, OUTPUT);
  digitalWrite(DS1302_VCC_PIN, HIGH);
  pinMode(DS1302_VCC_PIN, OUTPUT);
  
  Serial.println("RTC module activated");
  Serial.println();
  delay(500);
  
  if (RTC.haltRTC()) {
    Serial.println("The DS1302 is stopped.  Please run the SetTime");
    Serial.println("example to initialize the time and begin running.");
    Serial.println();
  }
  if (!RTC.writeEN()) {
    Serial.println("The DS1302 is write protected. This normal.");
    Serial.println();
  }

The first four lines configure the GND pin and VCC pin on the arduino to HIGH and LOW so that we can use them as a low-amp power source for the clock. The bottom half calls some RTC methods to make sure that the RTC is functioning normally, I didn't investigate these in detail, I just assumed that I'd be better off including them in my final app.

In the main loop(), note the syntax for using the read function:

tmElements_t tm;

// snip

if (! RTC.read(tm)) {

With a quick google search, I refreshed my memory that the tmElements_t structure is a standard C struct defined in the Time.h header file as follows:

typedef struct  { 
  uint8_t Second; 
  uint8_t Minute; 
  uint8_t Hour; 
  uint8_t Wday;   // day of week, sunday is day 1
  uint8_t Day;
  uint8_t Month; 
  uint8_t Year;   // offset from 1970; 
} 	tmElements_t, TimeElements, *tmElementsPtr_t;

I will also use this data in the final application, since I wanted to set a simple timer that would change the temperature at certain times on days that I go to work (weekdays in my case).

Result

My DS1302 RTC module is configured, time is set, battery backup installed, and I've got the basic code needed to read the time in the final application.

Step 4: Assembling the Code

Requirements, Copy & Paste, Troubleshoot, Repeat

Now we have all the basic building blocks:

  • IR device type and codes for the up and down temperature controls
  • IR control library
  • IR LED
  • RTC module configured with the current date/time
  • RTC control library

Time to paste it all together!

Requirements:

  1. Read the current time
  2. Make sure it's a weekday
  3. If it's 6:30 AM, turn the temperature up by "pressing" the up button 5 times
  4. If it's 6:30 PM, turn the temperature down by "pressing" the down button 5 times
  5. That's it!

In order to satisfy these requirements, I first copied & pasted in all the appropriate #includes and global variable declarations from the IR and RTC library example code:

#include <IRremote.h>
#include <Time.h>
#include <DS1302RTC.h>

// Set pins:  CE, IO,CLK
DS1302RTC RTC(12, 11, 10);

// Optional connection for RTC module
#define DS1302_GND_PIN 9
#define DS1302_VCC_PIN 8

IRsend irsend;

Then I added a global variable structure to hold the "temp up" time and the "temp down" time, along with the IR codes as global constants, and a couple flags to make sure that I only activated the "up" or "down" sequence once per cycle.

unsigned long upcode = 0x8166A15E;
unsigned long downcode = 0x816651AE;


int upHr = 6;
int upMn = 30;
int dnHr = 18;
int dnMn = 30;

bool morningFlag = false;
bool eveningFlag = false;

Next I updated the setup() function by copying and pasting in the code to set up the serial port, as well as the code required to initialize the RTC from the prior step. There was no setup specific code for the IR library:

void setup() {
  // Setup Serial connection
  Serial.begin(9600);
  
  Serial.println("AC Controller with Timer");
  Serial.println("> Configure the DS1302 for Reading");
  
  // Activate RTC module
  digitalWrite(DS1302_GND_PIN, LOW);
  pinMode(DS1302_GND_PIN, OUTPUT);
  digitalWrite(DS1302_VCC_PIN, HIGH);
  pinMode(DS1302_VCC_PIN, OUTPUT);
  
  Serial.println("RTC module activated");
  Serial.println();
  delay(500);
  
  if (RTC.haltRTC()) {
    Serial.println("The DS1302 is stopped.  Please run the SetTime");
    Serial.println("example to initialize the time and begin running.");
    Serial.println();
    while(1);
  }
  if (!RTC.writeEN()) {
    Serial.println("The DS1302 is write protected. This normal.");
    Serial.println();
  }
  
  delay(5000);
}

Finally, I wrote a simple state machine in the main loop() function that would poll the time every 30 seconds or so (accurate enough for me) and compare against the day of the week and the up and down temp times:

void loop() {

  tmElements_t tm;
  
// read the time - I reversed the logic from the sample code and made the code "terminate" if there's an error with the clock - not elegant but functional.  In theory it works or it doesn't.
  if (RTC.read(tm)) {
    Serial.println("DS1302 read error!  Please check the circuitry.");
    Serial.println();
    while(1);
  }

  int hr = tm.Hour;
  int mn = tm.Minute;
  int wday = tm.Wday;

  Serial.print("Checking time: ");
  Serial.print(hr);
  Serial.print(":");
  Serial.print(mn);
  Serial.print("[");
  Serial.print(wday);
  Serial.println("]");
  
  if ((wday > 1) && (wday < 7)) {
    // it's a weekday
    if ((hr == upHr) && (mn == upMn) && !morningFlag) {
      morningFlag = true;
      eveningFlag = false;
      for (int i=0; i<5; i++) {
        Serial.println("Turning up the temp");
        irsend.sendNEC(upcode, 32);
        delay(500);
        //upMn += 2;
      }
    }
    if ((hr == dnHr) && (mn == dnMn) && !eveningFlag) {
      morningFlag = false;
      eveningFlag = true;
      for (int i=0; i<5; i++) {
        Serial.println("Turning down the temp");
        irsend.sendNEC(downcode, 32);
        delay(500);
        //dnMn += 2;
      }
    }
  }
  delay(30000); // wake up and check every 30 seconds
}

Result

Now I had the code that I wanted to use, but I still needed a device that would somehow attach to my AC unit and run the code.

Step 5: Prototype

Making It Functional

Now that the program and wiring are done, I needed to create a functional prototype. This prototype had to sit near the AC unit and hold the LED out in front of the control panel. I shined a bright light onto the surface of the panel to see through the plastic faceplate and spot where the LED receiver was located. Then I proceeded to "hack" the physical prototype using rubber bands and wooden craft strips to hold the breadboard to the Arduino Uno, and to extend the LED in front of the panel. See the attached photos. Functional prototypes don't need to be pretty, they don't need to be compact, and in the best case they make use of materials you already have close at hand. This prototype worked so well that I could have used it as-is for a long time with no issues, although I probably would've found a more permanent way to affix the LED wires to the wooden stick.

Step 6: Improving the Prototype

Making It Better

3D printers like the one I got this year (the FlashForge Finder) are becoming increasingly cheap, yet are still incredibly capable. The Finder is under $500, and there are a number of others in that class. If you are a tinkerer, a maker, then (in my opinion) it's imperative that you get one and invest the time to figure out how to use it well, and how to use basic 3D modeling software.

I created the attached model using 123D Design by Autodesk. What you use doesn't matter - but I will tell you that this software makes it very easy to create, combine, intersect, and subtract simple geometric objects. I would estimate that I've put in maybe 25 or 30 hours playing with it over the past 3 months, and i created both models attached here in under 30 minutes. I'm not going to create any works of art any time soon, but the ability to craft simple structures in plastic in near real-time is truly amazing.

NOTE: The models attached are unlikely to work as-is for anyone unless they have the same AC unit that I do. I'm providing them as examples of what you too can accomplish, and so you can examine how I solved this particular problem with my environmental constraints.

The first model attached (unfortunately I only have the STL format final file, and not the 123D Design file, because I'm horrible at saving things when I feel like I'm just hacking something together quickly) is a base designed to latch onto the surface of the air conditioner and provide me with a flat surface on which to mount the Arduino Nano and the RTC module. I used some small screws that I had reclaimed from some of my kids baby toys to mount the components to the plastic base.

TIP - always strip toys before you throw them out - there are a wealth of screws, springs, gears, and switches to be found.

When the first model was surprisingly effective at hanging onto the AC faceplate, I decided to print a simple periscope that I could glue onto the first platform, to hold the LED light and provide a channel for the wiring. This model was a bit less than perfect, as there was no easy way to insert the LED, so I had to drill a slightly larger hole in the back of the periscope to push the LED through, and use needle nose pliers to pull the anode and cathode into the periscope's channel.

After soldering all of the wires, including soldering the resistor inline with the cathode of the LED, I hot glued all of the connections for insulation, and filled the body of the periscope with hot glue to insulate there as well.

I soldered a female power supply pigtail to the Arduino power and ground through-holes to make it easy and simple to attach the wall wart power supply to the device.

Take a look at the pics for the finished product. To take it to the next level, I'm working on a sleeker 2-piece platform and cover that will hide all of the wires and components - but for now, this is 100% functional, looks a lot better than the prototype, and works perfectly!

Results

The finished product has been working great for over a week now. My girlfriend is looking forward to version 3 with the sleek case to cover the wires, and I'm looking forward to a lower electric bill in the future!