Introduction: Reverse Engineering Air Conditioner IR Remote Control Protocol

Hi, this is my first instructable, hope you like it.

To get into electronics I chose a home automation project: a system allowing me to control and program both air conditioner units in my flat. In this instructable I show how I got to understand the IR protocol.



Material:

- Panasonic inverter air conditioner remote control
- Raspberry Pi with raspbian and lirc installed
- 38kHz Infrared (IR) Receiver Module (for instance RadioShack Catalog #: 276-640)
- breadboard

The raspberry Pi is useful in my setup to analyse the incoming IR signals, but also to host other components on the global project. Any other computer equipped with an IR receiver could do. An Arduino could also output the needed information using the IRremote library (https://github.com/shirriff/Arduino-IRremote). This library will be used to control the air conditioners in the end.

The method used here could work with other remote protocols (with adaptations).

Please excuse the screen caps that don't show exactly the same output format: this part of the project was done during last summer and I used files generated by multiple versions of the codes I wrote.

Attachments

Step 1: The Remote to Study

The remote shows multiple options to transmit:
- The target temperature to achieve
- The "mode" ("cool","heat","dry", "auto")
- The air flow swing (5 possible positions, and one automatic mode)
- The fan speed
- A powerful or quiet option (which impacts the fan speed (and more?))
- 2 timers: one to turn off the unit after a certain amount of time, one to bring it back on

One thing to know is that usual remotes controls, for TV, HI-FI, ... send a signal for each key pressed (often in loops while the key is pressed). An air conditioning remote often displays information about the parameters selected. But of course parameters can be changed on the remote while the unit is out of reach, which could lead to synchronization problems between the display and the unit in some cases if it worked like the TV remotes. This implies that when such a remote sends a signal, it sends the whole parameters set. What we'll do is then study what signals are sent to try and understand which part of the signal is for which parameter.

Step 2: Gathering Data

Off we go.
Note1: command recording is a repetitive and slightly boring process, but necessary.
Note2: I don't own an oscilloscope so the only way for my to plot the recorded values is to use a plotting program (gnuplot) on raw data. This can useful to have a visual idea of what happens, but it requires some adaptation in raw data and is in my opinion not necessary at all. For that reason I have not included any graph.

I used the instructions from this page: http://alexba.in/blog/2013/01/06/setting-up-lirc-... to plug the receiver on the Pi and prepare lirc.
I then recorded commands with the -raw option and redirected output to a file. The goal is to have a record of each value for ON and OFF of course, but also for each mode (AUTO, COOL, HEAT, DRY), for each swing and fan value, and, say, for min and max temperature (16 to 30°C in my case). The important part here is to make a reference record and to make a recording for each change of option, making only one option change every single time. Once a record is done, press CTRL+C to terminate the command and do it again with the next command/file

$ mode2 -d /dev/lirc0 -m > auto_21C_swing0_fan0

It could be necessary to gain super privileges to run this command ("sudo mode2 ...") and the lirc daemon can block the file, so it may be necessary to kill it first:

$ sudo /etc/init.d/lirc stop
$ sudo mode2 -d /dev/lirc0 -m > filename
$ [...]
$ sudo /etc/init.d/lirc start

When reading the generated files, what we see is all numbers, organized in 6 columns. These numbers indicate durations in microseconds. The columns work in pair, so

740   1495   920   1345  735   1335

means : "the IR LED was ON for 740us, then OFF for 1495us, then ON for 920us, then OFF for 1345us, etc."

Exemple :

3523     1766      414      451      418     1312
      423      448      419      452      418      452
      428      443      417      453      418      457
      418      452      418      452      416      454
      419      452      417      453      417     1316
      418      452      418      466      410      451
      419      453      414      457      416      452
      418      453      417     1316      418     1315
      418     1320      426      443      419      451
      419     1314      419      452      418      452
      418      452      418      452      418      457
      419      450      420      451      419      452
      421      451      417      452      418      452
      418      452      419      457      418      452
      418      449      422      451      418      452
      419      451      420      451      426      444
      418      457      418      452      417      452
      419      452      416      454      420      450
      418      452      419      452      419      456
      418      452      418     1323      411     1313
      421      451      419      451      418      452
      418      452      418      454      419     9997
     3521     1764      417      452      419     1315
      418      449      421      452      418      453
      417      453      418      451      427      448
      419      453      417      453      417      452
      419      452      417      453      417     1317
      417      451      419      456      419      451
      420      458      411      452      418      452
      416      455      418     1315      418     1315
      418     1321      417      452      419      452
      416     1318      418      452      418      451
      419      451      419      452      418      457
      418      452      418      452      420      448
      422      451      419      457      412      452
      417      452      419      457      418     1315
      417      453      419      449      421      452
      418      452      418      452      418      453
      424      450      418      453      419     1314
      418      452      418      452      418     1316
      418     1315      417      453      418      465
      410      452      419      452      416      453
      418      452      418      452      418      452
      418      453      417     1338      418     1316
      415     1318      418     1315      419     1314
      418      452      418     1315      419      449
      421     1328      410      452      418      453
      417      452      418      452      420      450
      419      451      417      455      416      457
      419      451      418      452      419      452
      419      453      417      453      417      453
      418      451      419      456      418      453
      418     1315      418     1315      418      452
      419      459      411      451      419      451
      416      460      418      452      419      451
      418      453      417      453      418      451
      418     1315      419     1313      420      458
      425      445      413      460      418      445
      422      452      418      452      418      452
      418      452      419      455      421      450
      420      463      407      452      417      452
      418      453      418      452      418      452
      418      457      418      450      421      452
      418      452      417      453      419      451
      424      445      419      451      419     1321
      417      453      417      454      416      452
      418      452      419      452      418      452
      418      452      418      468      408      452
      417     1313      422     1314      418      452
      420      451      418      452      418      452
      419      456      418      453      416      454
      418     1315      417      452      418     1315
      418      452      418     1316      418      451
      422

Ok, that seems insane :)
Note that the file will begin with a line with a single value : it is the time elapsed between the recording start and the arrival of the first IR signal. This line should be ignored.

Of course, as these are measures, with a time scale as small as the microsecond, all timings are different, which makes the detection of small differences between 2 commands impossible.
It can be observed that values are always close to 400 or 1300us, except for 3 (closer to 4400, 9900 and 1700). So what we'll do to make figures comparable is "round" the numbers to the closest of these 2 "reference" values (a spreadsheet is handy at first).
What this manipulation shows is that except for the 3 singular values the ON time is always 400us, what changes is only the OFF time.

Let's make the hypothesis that the OFF time is coding 0 and 1, and let's assume that 400us is for 0 and 1300us for 1. With this assumption it is possible to change each pair of columns to a single bit.
Let's also make an observation : the part between the 3 "singular timings" is always the same, in all the recordings. It can be assumed that :
- the part is an introduction, maybe identifying the remote or the Air Conditioner, and it will never change
- the different timings are Locks and separators between the introduction and the actual payload

It is therefore acceptable to take that part of the message as an invariant and not to study it.

For the ease of exploiting the data I wrote a small c program to round the values and transform it to binary digits. For ease of reading the code skips the intro part. Should you wish to use this code the timing values are defined in the beginning of the program, you will probably need to adapt it.

After compilation (gcc -o decode decode.c) you can use it on every data file :

$ ./decode auto_21C_swing0_Fan0 auto_21C_swing0_Fan0.decoded 


Example with mode auto target temperature 25°C:

$ more auto_25.decode 
01000000 00000100 00000111 00100000 00000000 10000000 01001100 00000001 11110101 00000000 00000000 01100000 00000110 00000000 00000000 00000001 00000000 01100000 00101010

Attachments

Step 3: Exploiting the Data

OK, now what we have is a collection of data files containing bits (actually characters representing binary digits, which is technically different).

The use of a file comparison program will help in identifying which part changes for each parameter.

What we can see is not only one part changes but the ending part of the message also. Generally when communication is involved the data correctness is never guarantied so a checksum is usually used to ensure that no data has been corrupted. The checksum will be studied later.

The commands

ON/OFF:
Only one bit changes, the first of the 6th byte (or 40th bit). If 1 the command is ON, if 0 the command is OFF

AUTO/HEAT/COOL/DRY
3 bits change : bits 45 to 48 (see picture)

0000 = AUTO
1100 = COOL
0100 = DRY
0010 = HEAT

swing

bits 64 to 67:

1111 = swing auto 
1000 = swing lowest
0100 = swing low
1100 = swing high
0010 = swing higest

Fan speed

bit 68 to 72:

1100  = lowest
0010 = low 
1010  = medium
0110 = high
1110 = highest 
0101 = auto 


Options (Byte 13):

Powerful: 10000000
Quiet: 00000100 (and the Fan speed is set to minimum)
None: 00000000


Temperature:
Here it becomes a little trickier because it would be nice to understand how the value is coded and not scan all possibilities.
We have sampled 16°C, 30°C, and 21°C. In binary :

16 = 10000
22 = 10110
30 = 11110

The correct zone for the temperature is not found using these values. But often hardware use bits in reverse order (Most Significant Bit or Least Significant Bit on the left of the number, also referred as Little Endian or Big Endian). If reversing the bits the values obtained is:

16 = 00001
22 = 01101
30 = 01111

This gives a match for the 3 values at bits 49 to 53. This is rather surprising as the value is not aligned with a byte (closest would be 6x8=48th bit). This means that when building a message the code will have to shift of one bit the temperature value.

Which consequently validates the first hypothesis concerning timing values used for 0 and 1 :)

Timer A:

The timers are coded in multiple positions: first avalue is used to indicate that the A timer is on, then a second one is used to indicate the timer value.
Timer active state can be found at the same byte as ON_OFF (6th byte, second bit, so starting from the beginning of the payload 41st bit).

So the Timer A is active if the byte is the 6th byte looks like :

x010xxx, the first bit being the ON/OFF bit.

For the timer duration, using the same encoding that with temperature and coding minutes and not hours (as the remote displays), the value can be found on the second half of the 12th byte.


Timer B:

Timer B is active if the 2nd bit of the ON/OFF byte is 1. As timer A is always ON when using the timer B, the byte looks like :

x110xxx, the first bit being the ON/OFF bit.

Ok, here I must admit that I failed to find the key to this value, maybe it is a delta between timer A and B, maybe another unit... Truth is: who cares about timers when the goal is to control it (automatically) from a computer ? :) Even timer A is not used in the end in my system, I was just happy to have found it.

Checksum:
Ah. Here is a value that cannot be disregarded. It is crucial to understand it.
After a few tests it appears that the checksum is for the payload part only (not including the introduction), and is the result of the simple sum of all bytes, keeping only one byte of the result (ignoring overflows). The trick is, of course, that this checksum is made by a Big Endian system, so every byte has to be reversed before being summed, then the result reversed again.

Step 4: Verifying the Analysis

The goal of the study is to be able to generate messages that the air conditioner will understand and accept. So the program joined will take arguments and generate a binary message. Now all that's left to do (until we have a working IR emitter) is to compare the message generated by the program and the recorded messages we had in inputs.

The values and position of each data in the payload is defined here.

Once compiled (gcc -o encode encode.c) this code should allow us to verify that the generated frame is equal to the one sampled

The encode program separates the bytes for easier reading.

$ more auto_25.decode
01000000 00000100 00000111 00100000 00000000 10000000 01001100 00000001 11110101 00000000 00000000 01100000 00000110 00000000 00000000 00000001 00000000 01100000 00101010
$ ./encode -m AUTO 25
01000000 00000100 00000111 00100000 00000000 10000000 01001100 00000001 11110101 00000000 00000000 01100000 00000110 00000000 00000000 00000001 00000000 01100000 00101010

That's all for this instructable, not much pictures or fun in it, I must admit, but there's more to come. Hope it might help someone.

Don't hesitate to ask for precision and comment.

Attachments