Using an Arduino to Reverse Engineer My A/C Remote

I have central air-conditioning in my apartment, and it’s controlled by a remote, employing IR signals to send commands to the A/C control unit.

As any decent geek would, I’d like to be able to control my A/C using other means (e.g., a smartphone).

Towards that goal, I figured I should first reverse engineer the IR commands the remote sends to the A/C, so I could later send these commands using other methods.

The gist: using an Arduino Uno board with a Phototransistor circuit (see below), I was able to obtain the IR waveform using Ken Shirriff’s Arduino IRremote library (slightly modified), and even graph these waveforms with Python’s matplotlib, as shown below.

Keep reading for a full drill down, or jump straight into my home-control-arduino GitHub project for the actual code and documentation.

Building the IR Receiver Circuit

Buy list:

Assemble the components according to the schematic shown above, which will look something like this:

Connect the Arduino to the PC via USB, and compile and upload my IRACreceiver program to the board:

Capturing A/C Commands

Now, with the circuit built and the program loaded, open the Arduino Serial Monitor, aim the A/C remote at the phototransistor, and press buttons on the remote to make it transmit IR signals to the receiver:

and watch as the Arduino prints to the serial monitor the raw IR signals info:

Analyzing the A/C Commands

In order to produce nice graphs of these signals, like the one above, I created a data-set of A/C-command samples to process using my IRAnalysis Python script.

But before that, a couple of words are in place concerning the specifics of this A/C remote and how it functions.

Basically, the communication channel between the remote and the A/C control unit is one-way – from the remote to the control unit. This means, among other things, that the remote has no knowledge of the current state of the A/C (whether it’s on or off, what state it is set to at the moment with respect to operation mode, fan speed, temperature, etc.). Because of this, every press of a button on the remote will send the entire state as it is currently presented on the display of the remote, including information about whether the power button was pressed or not.

The implications of this include:

  • By solely sending IR signals in lieu of the remote, it is not possible to explicitly control the On/Off state of the A/C, but only toggle the power state. If you think about this for a second, you can see this is not an issue when operating the remote locally, because the “human-in-the-loop” (you) knows the power state of the A/C, so the remote doesn’t need to. It becomes a problem once we take out the human from the loop – but that’s for another post.

  • There are no “relative” commands (e.g. “increase temperature by 1 degree”) – only absolute ones, which means we need to map all possible state-combinations that we want to be able to send on behalf of the remote to their corresponding IR signals.

So, in order to build this mapping, I decided that I am only interested in a subset of state-combinations that include A/C mode (cool / heat / …), fan speed (low / high / auto) and the temperature. In addition, I need to include the power-button in the mapping (whether a command with a given mode-fan-temp combination should toggle the power state or leave it as it was).

After all this, lets get back to the IRAnalysis Python script. The script expects to get a data-set of A/C-command samples as a collection of plaintext files (in a single directory). Each file has the state-mapping encoded in the filename (Power-Mode-Fan-Temp), and at least one IR capture as the content of the file (simply copy & paste the captures from the serial monitor into the body of the file).

For example, I have included in the GitHub project three sample files, that produce the graphs shown above using the script with the “graph” command.

Since the Phototransistor is susceptible to ambient noise, together with the quantization noise that arises from the way the Arduino-IRremote library samples the signal, the readings produced may contain outliers. For that reason, it is highly recommended that all sample files contain multiple independent readings (say at least 3), so my script could try and detect anomalous readings.

The script tries to do this by comparing each reading to the average signal of all readings, and flag any single reading that has an element differing by more than 100us from the average reading as anomalous.

Whenever the script reports such anomalies, you should manually remove that outlying reading from the sample file, and maybe replace it with another, better, reading.

Finally, it is a natural path to dig deeper in the waveforms of different A/C-command samples, and characterize (reverse engineer) them up to the level of understanding which part of the signal encodes which part of the state, and then use this characterization to recreate waveforms from a given state.

I started looking into such undertakings (this is actually the reason that I implemented the graphing functionality), but pretty quickly I reached an understanding that it is not going to be easy (for example, I saw that two commands that differ only by 1 degree in the temperature value, differ in multiple regions of their respective waveforms) – so I left that part as an exercise for the devoted reader πŸ™‚ (or a future self?)

Conclusion

With that, this post reaches its conclusion.

Feel free to go on and read my other posts related to my A/C-control project!

31 Comments
  • marciofernandesrn
    May 26, 2014

    thank you dude, your receiving sketch was all i needed to get the intervals and assembly program an atmega328p for my final semester project =)

    • itamaro
      May 26, 2014

      My pleasure!
      Glad to hear you found it helpful πŸ™‚

  • Barak Weiss
    September 24, 2014

    Hi Itamar
    I wrote an Electra A/C IR sender the uses IRremote, it might be helpful.
    https://github.com/barakwei/IRelectra

  • David
    September 22, 2015

    Hi there! Some of the signals i receive are 194 int long and some are 188 int long. Is it supposed to be like that? Am i foing something wrong here?

    Toda. Thank you
    David

    • Itamar Ostricher
      September 22, 2015

      The signals are supposed to be repeatable, AFAIK, unless the same remote control button triggers different IR commands on every push… (Or if you’re trying with different remote buttons)

      • David
        September 28, 2015

        hey Itamar. it seems like my last post haven’t been sent,
        i attached a screenshot of the IR output.
        while pressing the same button (temp up) i get a different array long.
        what seems to be the problem?

        http://postimg.org/image/51ezxbuc5/

        thank you!
        David

        • Itamar Ostricher
          September 28, 2015

          At least on my remote, “temp up” is not the same command every time, because it always sends the explicit temperature as shown on the remote. But it’s also possible that your environment is noisy (IR-wise), making measurements inconsistent.
          What kind of remote and AC do you have? Can you try to record and retransmit the most basic commands (something like power on, with all other settings always the same, in a dark room, with the remote very close to the circuit)?

          • David
            September 28, 2015

            i am using the Electra remote (mine written ELCO),
            i know each time you press the ‘temp up’ button the command is different.
            each time the remote sends a different array with the current settings (which are displayed on the remote)

            but the array length should stay the same. (isn’t it?)

            i will try to capture and retransmit the command, ill report the results

            thank you

  • Andy
    September 26, 2015

    Hi there!
    I got the raw buff code from my ac remote. But im not sure how you generate the arduino code from that to send the ir signal to the ac. I created a text file named β€œLeave-Cool-Auto-25β€³ with my raw buffer data in it and put it in the same folder with the IRAnalysis Python script. Not sure if that is correct? However, the IRAnalysis Python script couldn’t compile as well. My first error compiling the IRAnalysis Python script was empty character constant on “print ””. Then when I took that off i got my second compiling error ‘prog_uint16_t’ does not name a type. I am nont sure where I did wrong. Can you be more specific on the IRAnalysis Python script ?
    Many thanks,
    Andy

    • Itamar Ostricher
      September 26, 2015

      Hi Andy!

      Sorry about the confusion.

      The raw-buff files should all be together in one directory, with no other files, wherever you want. The path to that directory should be passed to the IRAnalysis script via the --dumps-dir flag.

      If you have any more trouble with it, feel free to share your raw-buff file and the command-line you try to execute.

      • Andy
        September 26, 2015

        Hi,

        Sorry I am still confused. So am I supposed to copy the code from iranalysis.py and execute it in the arduino ide?
        heres my raw code i got from iracrev
        0x25D5AC5F (32 bits)
        Raw (100): -7142 3400 -1700 450 -1250 400 -1300 450 -400 450 -450 400 -450 400 -1300 400 -450 450 -400 450 -1300 400 -1300 400 -450 400 -1300 400 -450 400 -500 400 -1300 400 -1300 400 -450 400 -1300 450 -1300 400 -450 400 -450 400 -1300 450 -400 450 -450 400 -1300 400 -450 400 -450 400 -450 450 -400 450 -450 400 -450 400 -450 450 -400 450 -400 450 -450 400 -450 400 -450 400 -450 450 -400 450 -450 400 -450 400 -450 400 -450 400 -450 400 -450 450 -1300 400 -450 400 -450 400

        how do i generate a code from this that i can replace it in the uSendBuff_Toggle_Cool_Auto_25 function in the iracsender code??

        Thank you!
        Andy

        • Itamar Ostricher
          September 26, 2015

          The IRAnalysis.py script is pretty lazy actually. All it intended to do is to write the generated Arduino array(s) to stdout. You are left with copying it from the output of the script to the Arduino code, in the appropriate location.

          • Andy
            September 28, 2015

            Hi sorry I was confused about python before
            now executing it, i got an error
            PS C:\Users\YW\temp\stuff> python IRAnalysis.py
            usage: IRAnalysis.py [-h] [-d DUMPS_DIR] {graph,code} …
            IRAnalysis.py: error: too few arguments

          • Itamar Ostricher
            September 28, 2015

            What’s the exact command line you are trying to run?

          • Andy
            September 28, 2015

            I was running python IRAnalysis.py using powershell (not sure if this was right) and putting Leave-Cool-Auto-25 under directory C:\User\dumps

          • Itamar Ostricher
            September 28, 2015

            powershell is fine. the command line should look something like this python IRAnalysis.py --dumps-dir C:\User\dumps code (note that the flag comes before the name of the command – this is the way the argparse module works with the way the parsers are defined here).

  • Andy
    September 30, 2015

    Sorry to keep bothering you,
    here is what i got for entering that command
    PS C:\Users\YW\temp\stuff> python IRAnalysis.py –dumps-dir C:\Users\YW\dumps code
    Traceback (most recent call last):
    File “IRAnalysis.py”, line 154, in
    args.func(load_from_raw(args.dumps_dir))
    File “IRAnalysis.py”, line 118, in load_from_raw
    sample = AcSample(pwr, mode, fan, int(temp))
    ValueError: invalid literal for int() with base 10: ’25.txt’

    • Itamar Ostricher
      September 30, 2015

      the files in the dumps directory should have no extension at all. this error indicates that the files have .txt extension.

      • Andy
        October 1, 2015

        Thank you!! I got this
        PS C:\Users\YW\temp\stuff> python IRAnalysis.py –dumps-dir C:\Users\YW\dumps code
        PROGMEM prog_uint16_t uSendBuff_Leave_Cool_Auto_25[] = {99, 3400, 1700, 450, 1250, 400, 1300, 450, 400, 450, 450, 400, 450, 400, 1300, 400, 450, 450, 400, 450, 1300, 400, 1300, 400, 450, 400, 1300, 400, 450, 400, 500, 400, 1300, 400, 1300, 400, 450, 400, 1300, 450, 1300, 400, 450, 400, 450, 400, 1300, 450, 400, 450, 450, 400, 1300, 400, 450, 400, 450, 400, 450, 450, 400, 450, 450, 400, 450, 400, 450, 450, 400, 450, 400, 450, 450, 400, 450, 400, 450, 400, 450, 450, 400, 450, 450, 400, 450, 400, 450, 400, 450, 400, 450, 400, 450, 450, 1300, 400, 450, 400, 450, 400};

        but when i tried put this into your irAcsender code, i got an error ‘prog_uint16_t’ does not name a type

        • Itamar Ostricher
          November 1, 2015

          Hi, sorry for the delay in response!

          I haven’t compiled this code for the Arduino for a while, so maybe things have changed in the Arduino environment and the PROGRAM stuff are broken now.

          I found this – https://github.com/arduino/Arduino/issues/2456

          Can you try to follow the suggestions there and modify the generated code, and see if it works? If you get a successful result, I will appreciate the feedback to fix my code generation accordingly πŸ™‚

  • Manav Jaiswal
    November 11, 2015

    Hello Itamar
    I have Halcyon AC with Fujitsu Generic ac remote. I am getting 32 bit raw code each time. Its length is 116 when I OFF the AC. The OFF signal is working fine and I can turn the AC off with arduio. I don’t exactly know the length of other button press. At raw=255 I received signals of length 255 so I thought of increasing rawbuf. When I set rawbuf above 255, I am unable to receive raw code for other buttons except the same OFF signal of length 116. The code I receive at rawbuf <= 255 doesn’t work. Please help.

    • Itamar Ostricher
      November 13, 2015

      Hi Manav,
      Sorry, but I haven’t got a clue why you are not receiving codes with rawbuf > 255.
      What I would do in your situation is to go deeper into the IRremote library, and try to figure out how it’s working. Since 255 is MAXBYTE, I would look for byte-variables that are used when reading signals, to see if it’s limited because of something related to this.

  • Josh
    November 16, 2015

    Hello Itamar,
    I need some help with your Python IRAnalysis part. So far I have been able to get raw code from iracrev. I saved the code under the name Leave-Cool-Auto-25 under the directory C:\User\M.SartaJ Khan which also have your IRAnalysis python script. After that I tried running the command (like the one you adviced to andy)
    PS C:\Users\M.SartaJ khan> python IRAnalysis.py –dumps-dir C:\Users\M.SartaJ khan
    I keep getting the following error:

    usage: IRAnalysis.py [-h] [-d DUMPS_DIR] {graph,code} …
    IRAnalysis.py: error: invalid choice: ‘\x96dumps-dir’ (choose from ‘graph’, ‘code’)

    Can you please guide me in the python part as I am totally new with this software. Thankyou

    • Itamar Ostricher
      November 17, 2015

      Hi!

      Are you trying to generate Arduino code, or graph the the raw IR data?

      In any case, you should put the raw data file under a dedicate directory, so it’s the only file in the directory, e.g. C:\User\M.SartaJ Khan\IR Data.

      Then, for graphing that data, you would run the script like this: C:\Users\M.SartaJ khan> python IRAnalysis.py --dumps-dir "C:\Users\M.SartaJ khan\IR Data" graph

      Note that you should have two regular - symbols before the dumps-dir flag (and not one weird - which translates to \x96 and confuses Python), and the path should be quoted if it contains spaces, and the command should be specified after it (e.g. graph in this case, or code if you’d like code generation).

      Also note that if you have a recent version of the Arduino compiler, the generated code might not work for you, as the case for Andy. If you run into the same issue, and you’re able to find a way to change the generated Arduino code to fix it, please let me know. I’d love to fix the code generation so it works again.

  • Josh
    November 17, 2015

    Hi Itamar,
    Thanks for replying so fast, Actually I’m trying to generate code. I saved my raw IR data in a dedicate directory, so it’s the only file in the directory, i.e. C:\User\M.SartaJ Khan\IR Data. After that I run the command as to your instruction C:UsersM.SartaJ khan> python IRAnalysis.py –dumps-dir “C:UsersM.SartaJ khanIR Data” code

    This is what I’m getting upon running it
    prog_uint16_t * getAcSendBuff() {
    return 0;
    }

    It’s empty. I am unable to get the code to replace it in the uSendBuff_Toggle_Cool_Auto_25 function in the iracsender code??
    I saved the file under the name :Toggle-Cool-Auto-25 with atleast 3 sample Raw IR codes

    Thank you.
    Josh

    • Itamar Ostricher
      November 17, 2015

      Hi Josh,
      Can you paste the output of running the dir command on the IR Data dir, and also any output of the IRAnalysis script?

  • Josh
    November 18, 2015

    Hi Itamar,
    Sorry I hope I’m not disturbing you a lot.
    On running the dir command this is what i got
    PS C:\Users\Project> dir

    Directory: C:\Users\Project

    Mode LastWriteTime Length Name
    —- ————- —— —-
    -a— 31/10/2015 1:08 AM 6435 IRAnalysis.py
    -a— 18/11/2015 12:29 AM 1510 Toggle-Cool-Auto-25.txt

    I am unable to get any output of IRAnalysis script.
    Upon running the command All I get is this
    PS C:\Users\Project> python IRAnalysis.py –dumps-dir “C:UsersProject” code

    prog_uint16_t * getAcSendBuff() {
    return 0;
    }

    It’s empty again. I have created an entirely new directory under the name Project which only have two files IRAnalysis script and my raw IR code file
    Thank you.
    Josh

    • Itamar Ostricher
      November 18, 2015

      Not disturbing at all πŸ™‚

      Try removing the “.txt” extension from the filename and try again.

      • Josh
        November 18, 2015

        After removing the .txt extension
        PS C:\Users\Project> dir

        Directory: C:\Users\Project

        Mode LastWriteTime Length Name
        —- ————- —— —-
        -a— 31/10/2015 1:08 AM 6435 IRAnalysis.py
        -a— 18/11/2015 4:16 PM Toggle-Cool-Auto-25

        PS C:\Users\Project> python IRAnalysis.py –dumps-dir “C:UsersProject” code

        prog_uint16_t * getAcSendBuff() {
        return 0;
        }

        It’s still empty.
        The data I have saved in Toggle-Cool-Auto-25 is as follows:
        E458A050
        0xE458A050 (32 bits)
        Raw (101): -3920 5700 -2550 400 -350 400 -950 350 -950 400 -350 400 -900 400 -350 400 -350 400 -350 400 -350 400 -900 400 -350 400 -350 400 -950 350 -400 350 -400 350 -400 350 -400 350 -950 400 -350 400 -350 350 -400 350 -400 350 -400 350 -400 350 -400 350 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -900 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -900 400 -350 400 -350 400 -350 400 -950
        E458A050
        0xE458A050 (32 bits)
        Raw (101): -35558 5700 -2550 350 -400 350 -950 400 -950 350 -400 350 -950 400 -350 350 -400 350 -400 350 -400 350 -950 400 -350 400 -350 400 -900 400 -350 400 -350 400 -350 400 -350 400 -950 350 -400 350 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 400 -350 350 -400 350 -400 350 -400 350 -400 350 -400 350 -950 400 -350 400 -350 350 -400 350 -400 350 -400 350 -400 350 -400 350 -950 400 -350 400 -350 350 -400 350 -950
        E458A050
        0xE458A050 (32 bits)
        Raw (101): -45616 5700 -2600 350 -400 350 -950 350 -950 400 -350 400 -950 350 -400 350 -400 350 -400 350 -350 400 -950 350 -400 350 -400 350 -950 350 -400 350 -400 350 -400 350 -400 350 -950 400 -350 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -400 350 -350 400 -350 400 -350 350 -1000 350 -400 350 -400 350 -400 350 -400 350 -400 350 -350 400 -350 350 -1000 350 -400 350 -400 350 -400 350 -950

        Thank you

        • Itamar Ostricher
          November 20, 2015

          Hi,

          Now in the output of your dir command I see that the IRAnalysis.py script is also there, together with the IR data file. The script fails if the dumps-dir contains anything other than what it expects (yes, I know, it’s not very robust..).

          I know your IR data is valid, because I copied it into a file named Toggle-Cool-Auto-25 with no other files in a directory called IR_data, and ran python IRAnalysis.py --dumps-dir IR_data code, and got non-empty output:

          PROGMEM prog_uint16_t uSendBuff_Toggle_Cool_Auto_25[] = {100, 5700, 2550, 350, 400, 350, 950, 350, 950, 400, 350, 400, 950, 400, 350, 350, 400, 350, 400, 350, 350, 400, 950, 400, 350, 400, 350, 400, 950, 350, 400, 350, 400, 350, 400, 350, 400, 350, 950, 400, 350, 350, 350, 350, 400, 350, 400, 350, 400, 350, 400, 350, 400, 350, 350, 400, 350, 400, 350, 400, 350, 400, 350, 400, 350, 350, 400, 350, 400, 350, 350, 400, 350, 400, 350, 350, 950, 400, 350, 400, 350, 350, 400, 350, 400, 350, 400, 350, 350, 400, 350, 350, 950, 400, 350, 400, 350, 350, 400, 350, 950};
          
          prog_uint16_t * getAcSendBuff() {
          	if ( (Toggle == pwr) && (Cool == mode) &&(Auto == fan) && (25 == temp) ) { return uSendBuff_Toggle_Cool_Auto_25; }
          	return 0;
          }
          

          So try moving the data file to a sub-directory and running again with the --dumps-dir pointing to that sub-directory.

Leave a Reply