Introduction: An Air Conditioner Remote Replacement

Update (01/2014): correction of the schematics and picture update to comply to the current setup (not necessary final but at least more reliable than the previous version).

This instructable follows my previous one (https://www.instructables.com/id/Reverse-engineering-of-an-Air-Conditioning-control)
Now we have a good understanding of what is sent by the remote, we can make our own.

Like the previous instructable, this circuit was made some months ago, so multiple solutions were tried, leading to a rather strange set up.

The setup goes like this :
A server (Pi or other) hosts a website presenting commands
Connected to the server (USB) is an Arduino with a custom Shield with an IR LED.

This instructable covers the Arduino connected by USB, the associated circuit and programming.

BOM:
- An Arduino, with auto reset function disabled (I used a seeeduino board, it has a switch to disable the auto-reset feature, but solutions exist to disable the feature with any arduino) - http://www.seeedstudio.com/depot/Seeeduino-V30-At...
- An IR LED. Be careful about the characteristics, such as max current in burst and light emission angle (the wider the angle the easier it is to aim the air conditioner but the less is the reach)
- A NPN transistor
- A set of resistors

- An RGB LED is used here but not necessary

This project is well under 30$, if you already have an arduino it would be less that 2$ I think

Note: As the project is old (although still ongoing... well more like "on pause" actually) I updated it so the schematics and pictures are more accurate and I also cleaned and simplified the code for it to be understandable here. But doing so I might have broken it to some extent so don't hesitate to tell me if you use it and encounter problems.

As you can see on the picture no box has been hurt during this instructable (I think I will print one someday). ;)

Step 1: Circuit

Nothing complicated here, the circuit is mainly an LED driver with an NPN transistor and a resistor to limit the current flowing through the LED. I added RGB LED to indicate the schedule mode status (programmed, forced, off...) and a temperature probe. The circuit is powered by the USB cable of the arduino.

Tip: to check that the IR LED is working properly you can watch it through a digital camera (or a smartphone camera), The IR frequency can appear on the screen. I'm not sure it's good for the camera though.

Step 2: Programming - Arduino Side

We need to communicate over serial interface and send IR commands.
For the IR remote part we will use the IRremote Arduino library (https://github.com/shirriff/Arduino-IRremote). This library can be used to send predefined data over IR, as LIRC does. But We'll use it only for its helper functions (to set the 38KHz frequency for IR and to manage timings while sending messages).

In the previous instructable we found that to communicate commands to the air conditioner we needed to send a lock sequence, a constant introduction, the lock sequence again, then the actual payload. Well the Arduino can very well have the introduction in its code and just wait for the payload. We could just send the options values and make the Arduino modify a template with these values, but to keep it simple we'll just provide the entire payload and let it send it as is. To send the bits it will turn the IR LED ON and OFF according the the timings found previously. 

Here the Arduino acts as a "slave", never initiating a communication itself. As I intend to make multiple uses of this unit I begin the communications by a single character 'I' meaning "send by IR", followed by 19 bytes of payload. The Arduino then sends the message to the air conditioner.
On the Arduino side the code can look a little over-complicated, but it's because it is supposed to serve as a radio relay as well, so the communication between the Pi and the Arduino over Serial interface won't always be just to send IR message.
Here is the arduino source that will be used to send commands to the Air Conditioner.

A bit of explanation:
-> the loop code just checks if a command is ready. In that case it executes the command (for now just "send an IR message").
-> the SerialEvent() is called after a loop(), if there is serial data in the buffer. In this function the command is read, the payload is prepared, then a flag is set to tell the loop() function that a command is ready

serialEvent will receive ASCII characters representing HEX values. To convert it we use the sscanf function with the "%x" (hexadecimal) format option. We store the received characters in a 5 char array and fill it with : "0x"+the 2 chars+'\0' (to finish the string).

          char hexConvert[5] = "0x";
          // ASCII is received
          for (int i = 0 ; i < 19 ; i++){
            hexConvert[2] = Serial.read();
            hexConvert[3] = Serial.read();
            hexConvert[4] = '\0';
            // convert the received chars to a numeric value in payload[i]
            sscanf(hexConvert, "%x", &(payload[i]));
          } 
At this point payload[i] contains the binary value represented by the received chars.

In the send command proper timings are used to send locks, introduction, and payload. To send data, the code has to send each bit separately with the function irsend.mark(duration) and irsend.space(duration). As explained in the previous instructable the duration is of about 400us (should actually be closer to 420 I think) for the mark (ON state), and either 400us or 1300us for the space (OFF state) to send a 0 or a 1, respectively.
To send each bit of each byte the following code is used :

for (int s = 0 ; s < payloadSize ; s++ ){
    for (int i = 7 ; i >= 0 ; i--){ // Most Significant Bit comes first
      irsend.mark(irSpace);
      irsend.space((payload[s] >> i) & 1 == 1 ? irOne : irZero);
    }
  }

If you are not familiar with bitwise operations there are plenty of tutorials available on the net.
What happens here is:
For each byte of the payload (works the same manner with the intro):
   For i going from 7 to 0 
      shift the byte for i ranks to the right
      look at the last bit
      if that bit is one, send a signal of "irOne" us (1300us), else send a signal of "irZero" us (400us)

Basically what we obtain by shifting the byte right of N ranks (adding zeros at the left side) is the Nth bit on the rightmost side. When masking with a bitwise AND (&) 1 we take only the rightmost bit, so globally we obtain the Nth bit value.

Example with 5B in hexa = 0101 1011 in binary. The parenthesis are here to emphasize the shifted bits, for readability...

i = 7 => shift (0)101 1011 for 7 ranks  => 0000 000(0)
               mask with 1 => 0000 0000 & 0000 0001 =  0 => send irZero

i = 6 => shift (01)01 1011 for 6 ranks => 0000 00(01)
               mask with 1 => 0000 0001 & 0000 0001 =  1 => send irOne

i = 5 => shift (010)1 1011 for 5 ranks => 0000 0(010)
               mask with 1 => 0000 0010 & 0000 0001 =  0 => send irZero

i = 4 => shift (0101) 1011 for 4 ranks => 0000 (0101)
               mask with 1 => 0000 0101 & 0000 0001 =  1 => send irOne

i = 3 => shift (0101 1)011 for 3 ranks => 000(0 1011)
               mask with 1 => 0000 1011 & 0000 0001 =  1 => send irOne

i = 2 => shift (0101 10)11 for 2 ranks => 00(01 0110)
               mask with 1 => 0001 0110 & 0000 0001 =  0 => send irZero

i = 1 => shift (0101 101)1 for 1 rank => 0(010 1101)
               mask with 1 => 0010 1101 & 0000 0001 =  1 => send irOne

i = 0 => shift (0101 1011) for 0 rank => (0101 1011)
               mask with 1 => 0101 1011 & 0000 0001 =  1 => send irOne

So we sent bits in the left to right order.

Note that IRremote library uses pin3 to send messages. I suppose it could be possible to use the pin 11 (same timer for PWM) by modifying the target pin in the library code, but I haven't tested it.

Step 3: Programming - PC Side

On the PC side (be it a Pi or any other linux PC), here is a program to send commands to the arduino. It is based on the previously given "encode.c" to build an IR command.

The program will build the command payload according to the command line options (see usage) and send the command to the device connected to the USB port.
The port can be specified with the -u option, otherwise it will try /dev/ttyUSB0, /dev/ttyUSB0, /dev/ttyACM0, /dev/ttyACM1, as the arduinos can be mapped as (at least) these files, depending on the arduino type and disconnections/reconnections.

As indicated in the introduction the Arduino has to have its autoreset function disabled, otherwise it will reset each time a connection is made. This is the way the IDE can program the chip, but it is not desirable in our case. Should you wish to test the code with an autoreset enabled arduino, you should add a sleep(2) command after the connection (after opening the tty file) to let the arduino reset and being able to get the messages.

As expected the code sends a 'I' character to the Arduino, then sends the payload and read the file to wait the Arduino's response. This step is not crucial for a simple command send, but it will be if the goal is to fetch information from the Arduino itself or another device controlled by radio. For now the Arduino just responds "OK" so the program just displays the response as is.

As for the Arduino part, there is a part of the code where the message is converted to ASCII representation of the binary message.

message[0] = 'I';
  // turning binary payload into ASCII characters (HEX representation)
  for (i = 1, j = 0 ; i < 39 ; i+=2, j++){
   message[i] = (payload[j] & 0xF0) >> 4;
   if (message[i] < 10){
     message[i] += '0'; // ASCII
   } else {
     message[i] += 'A' - 10;
   }
   message[i+1] = payload[j] & 0x0F;
   if (message[i+1] < 10){
     message[i+1] += '0'; // ASCII
   } else {
     message[i+1] += 'A' - 10;
   }
   printf("0x%c%c ",message[i], message[i+1]);
  }

Once again bitwise operators are used to select and shift the first or last 4 bits and convert it to a digit or a character (A to F):
message[i] = (payload[j] & 0xF0) >> 4;
Here we take the byte and apply a mask 11110000 so we obtain only the foremost left bits, and we shift these bits to the right
message[i+1] = payload[j] & 0x0F;
Same here but selected bits are already at the right of the byte so no shift is necessary.

if (message[i] < 10){
     message[i] += '0'; // ASCII
   } else {
     message[i] += 'A' - 10;
   }

Here if the digit is < 10 the ASCII value is comprised between 48 and 57 (decimal), and we just have to add the represented value to the character '1' value (which is 48 but '1' is usable without knowing the ASCII table by heart ;) ). The same apply to characters A to F but A(Hex) is 10(Decimal) so if we added the represented value to the ASCII value for 'A' we'd be 10 values to high. So we add the value -10 and end up with the proper character.

Now, you might wonder why we bother converting from Hex to ASCII and from ASCII to Hex on the other side. You'd be right to ;)
Indeed, it is useless in that case, and the first version of the code worked directly with binary values. Actually it worked by sending only the 5 bytes that are actually useful and the Arduino made the update of the payload template. So now we send 40 bytes instead of 5. For what reason?
Well, although this is out of this particular instructable I indicated that I had a second micro controller in my room for the second Air Conditioner unit. This chip is a Seeediuno Wifi Bee. I found it difficult to implement the correct behavior in that chip using sockets so I ended up using the embedded minimalist web server, so I send the full IR command in the URL in Hex characters.
Now you have the reason: I wanted to use the same code to control both units, sending data to USB or over WiFi but manipulating the same message, in the same format.

Step 4: Conclusion

In this instructable I obtained a proper replacement to my air conditioner remote with an Arduino connected to a PC. Now I can control the unit by issuing commands to my computer, which is pretty cool :)

$ sendCmd -m HEAT -q -s 5 23
will set the air conditioner to HEAT mode, with the "quiet" option activated (visible by an LED on the unit), with the air flow stuck in high position (swing 5) and a temperature of 23°

$ sendCmd -o OFF
sends an OFF command that turns off the unit.


Air conditioning control from Mat_fr on Vimeo.


In the video I use another version of the code which displays plenty of messages, just ignore that ;)

OK, That's fun. But what is really cool is that from this starting point I will be able to:
- send commands from a remote location (connecting via ssh for instance)
- set a command to be sent at a specific time using the "at" command or a crontab

What comes next (in my project, not necessarily in instructables...) is:
- a remote controller to relay orders to the second unit
- a web page to send commands from a browser instead of a command line (as seen in the picture, sorry it's in french ;) )
- a database to store commands to be sent at specific times, potentially with conditions such as current season, current inside or outside temperature, ...
- a web page to manage programs

Hope this instructable helps.