Simulating Air Conditioner remote control with IR transmitter module
0 4104 Hard

P.S: This tutorial is aimed at advanced and intermediate microcontroller users. People with limited experience with sensors or microcontrollers are encouraged to attempt the project since the project is quite elaborative. However, they will need to research unfamiliar topics on their own.

What you will make 

This tutorial teaches you how to simulate air conditioner/tv remote or basically any remote controller that uses IR (Infrared) technology, using a commonly used infrared signal transmitter module and a microcontroller. 

What you will learn 

How to use ESP32 

How to use IR receiver sensor to decode and store the signal code/raw data of the remote for later use. 

How to transmit the stored signal code/ raw data using IR transmitter to control the AC. 

Things you will need to complete this project: 

Air conditioner that is controlled by IR remote. (I will be using an AC for this tutorial. You can use any IR remote controlled device for learning purpose.) 

FireBeetle ESP32 IoT Microcontroller 

Gravity: Digital IR Receiver Module 

Gravity: Digital IR Transmitter Module 

Jumper Cables (If you don’t want to use jumper cables, you can choose FireBeetle Covers-Gravity I/O Expansion Shield)

Getting the HexCodes of the AC remote control using IR receiver 

The air conditioner is normally controlled by an IR remote control that sends or transmits infrared signals in the form of hexCode or raw data when a certain button in pressed. An important thing to remember that each button has its unique signal code. Then the receiver in the AC interprets the signal codes and converts them into instructions to perform certain tasks such an turning on/off, increasing/reducing temperature, changing modes etc. In our project we will try to mimic the IR remote control and transmit the signals to the receiver already installed in the AC. To do that, firstly, we need to decode the signals from the AC remote. 

Connection Diagram 

Connect your IR receiver to ESP32 pins as shown below.


Head over to Arduino IDE, open a new file and save it before moving on. We adopt the code from IRremoteESP8266 library’s example code for receiving data with IR receiver. We start the code by importing some necessary libraries. If you don’t have the libraries install and download them from Arduino library manager.

#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

Then, we define Pin number, Baudrate and buffersize of the codes that are going to be captured by the IR receiver. Status messages will be sent to the PC at the baud rate of 115200. (We recommend 115200 or faster baudrate.) 

As this program is a special purpose capture/decoder meaning AC hexCodes and raw data are usually longer than other devices since it controls many things at the same time such as fan speed, blade direction, mode, times etc. So, let us use a larger than normal buffer so we can handle Air Conditioner remote codes. 

// An IR Receiver is connected to GPIO pin D4
// e.g. D4 on an ESP32 board.
const uint16_t RecvPin = D4;
const uint32_t BaudRate = 115200;
const uint16_t CaptureBufferSize = 1024;

Timeout is the number of milli-Seconds of no-more-data before we consider a message ends. This parameter is an interesting trade-off. The longer the timeout, the more complex a message it can capture. e.g. Some device protocols will send multiple message packets in quick succession, like Air Conditioner remotes. Air Conditioner protocols often have a considerable gap (20-40+ms) between packets. The downside of a large timeout value is a lot of less complex protocols (we will talk more about it) send multiple messages when the remote's button is held down.

The gap between them is often also around 20+ms. This can result in the raw data be 2-3+ times larger than needed as it has captured 2-3+ messages in a single capture. Setting a low timeout value can resolve this. So, choosing the best Timeout value for your use particular case is quite nuanced. Good luck and happy hunting.

NOTE: Don't exceed MaxTimeoutMs. Typically, 130ms.

// Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator
// A value this large may swallow repeats of some protocols
const uint8_t Timeout = 50;
#else   // DECODE_AC
// Suits most messages, while not swallowing many repeats.
const uint8_t Timeout = 15;
#endif  // DECODE_AC

const uint8_t Timeout = 90;
Suits messages with big gaps like XMP-1 & some aircon units, but can accidentally swallow repeated messages in the rawData[] output.

const uint8_t Timeout = MaxTimeoutMs;
This will set it to our currently allowed maximum. Values this high are problematic because it is roughly the typical boundary where most messages repeat. e.g. It will stop decoding a message and start sending it to serial at precisely the time when the next message is likely to be transmitted and may miss it.

Next, we Set the smallest sized "UNKNOWN" message packets we only care about. This value helps reduce the false-positive detection rate of IR background noise as real messages. The chances of background IR noise getting detected as a message increases with the length of the Timeout value. The downside of setting this message too large is you can miss some valid short messages for protocols that this library doesn't yet decode. Set higher if you get lots of random short UNKNOWN messages when nothing should be sending a message. Set lower if you are sure your setup is working, but it doesn't see messages from your device. (e.g. Other IR remotes work.)

NOTE: Set this value very high to effectively turn off UNKNOWN detection.

const uint16_t MinUnknownSize = 12;

The legacy time info is no longer supported, change to true if you miss or need the old raw timing display.

#define LEGACY_TIMING_INFO false

We define irrecv with features it will capture and results to store the captured data somewhere.

// Use turn on the save buffer feature for more complete capture coverage.
IRrecv irrecv(RecvPin, CaptureBufferSize, Timeout, true);
decode_results results;  // Somewhere to store the results

Next, we have our setup function.

// This section of code runs only once at start-up.
void setup() {
#if defined(ESP8266)
  Serial.begin(BaudRate, SERIAL_8N1, SERIAL_TX_ONLY);
#else  // ESP8266
  Serial.begin(BaudRate, SERIAL_8N1);
#endif  // ESP8266
  while (!Serial)  // Wait for the serial connection to be established.
  Serial.printf("\n" D_STR_IRRECVDUMP_STARTUP "\n", RecvPin);
// Ignore messages with less than minimum on or off pulses.
#endif  // DECODE_HASH
  irrecv.enableIRIn();  // Start the receiver

As the comment says, this function will only run once at the beginning of the program. Once the serial connection is established the receiver is enabled.

Finally, we have our loop function that repeats each time we press a button.

void loop() {
    // Check if the IR code has been received.
    if (irrecv.decode(&results)) 
      // Display a crude timestamp.
      uint32_t now = millis();
      Serial.printf(D_STR_TIMESTAMP " : %06u.%03u\n", now / 1000, now % 1000);
      // Check if we got an IR message that was to big for our capture buffer.
      if (results.overflow)
        Serial.printf(D_WARN_BUFFERFULL "\n", CaptureBufferSize);
      // Display the library version the message was captured with.
      Serial.println(D_STR_LIBRARY "   : v" _IRREMOTEESP8266_VERSION_ "\n");
      // Display the basic output of what we found.
      // Display any extra A/C info if we have it.
      String description = IRAcUtils::resultAcToString(&results);
	if (description.length()) Serial.println(D_STR_MESGDESC ": " + description);
      yield();  // Feed the WDT as the text output can take a while to print.
      // Output legacy RAW timing info of the result.
      yield();  // Feed the WDT (again)
      // Output the results as source code
      Serial.println();    // Blank line between entries
      yield();             // Feed the WDT (again)

The function checks whether a code has been received then shows a timestamp, then it checks the buffersize and the library version it captured. In the next few lines, the results are displayed and any extra info about the AC such as whether its protocol is known or unknown or the raw timing of the results are printed. Now, Verify and upload your program to your ESP32 board. And start the serial monitor to see the received data. If everything goes well, you should see something like the figure below each time you press a button pointing at the IR receiver.


In the above figure we can see that, the protocol is unknown, and the code generated is quite long (50 bits) and the raw data is of array size 99, also quite large. In this case, the IRremoteESP8266.h library does not have any decoded protocol written for this particular brand of AC. Usually, the library includes protocols for well-known/bigger brands such as LG, GREE etc. Hence, the IR receiver prints raw data for my AC remote control. If you happen to get raw data just like this, save the whole array of the raw data for each button you pressed in a text file and note the button functionality for later use.

If you happen to use any well-known brands you might as well get the hexCodes with a protocol name such as the figure below. In this case, just save the hexCode (unint64_t data) for each button you pressed along with the buttons’ functions.


Sending the HexCodes using IR transmitter to control the AC 

Now that we have the hexcodes, we can send them using an IR transmitter to control the AC.  

Connection Diagram 

Connect the IR transmitter to ESP32 pins as shown below.


Let’s have a look at the code. We adopt the code from IRremoteESP8266 library’s example code for sending data with IR Transmitter First, let’s import the necessary libraries, 

#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>

Then we set the pinNumber and use this in IRsend function

const uint16_t IrPin = D4; // ESP32 GPIO pin to use. Recommended: (D4).
IRsend irsend(IrPin);  // Set the GPIO to be used to sending the message.

Now, if the data you received was raw data meaning your AC remote doesn’t have a protocol, do as following.

uint16_t powerOn[] = {9058, 4438,  512, 1728,  512, 1726,  512, 610,  510, 608, 510, 610,510, 608,  510, 608,  510, 1728,  510, 610,  510, 1728,  510, 1728,  510, 610,  510, 608,510, 610,  510, 610,  510, 608,  510, 610,  510, 608,  510, 1728,  512, 608,  510, 610,  510, 608,  510, 610,  510, 610,  510, 610,  510, 1730,  510, 610,  510, 610,  510, 608,  510, 610, 608,  510, 608,  510, 610,  510, 608,  510, 608,  510, 608,  510, 608,  510, 608,  510, 608,510, 610,  510, 608,  510, 608,  510, 610,  510, 608,  510, 608,  510, 608,  510, 608,  510, 610,  510};

uint16_t ModeCool[] = {9050, 4356, 510, 610,510, 608,  510, 608,  510, 1728,  510, 610,  510, 1728,  510, 1728,  510, 610,  510, 608,510, 610,  510, 610,  510, 608,  510, 610,  510, 608,  510, 1728,  512, 608,  510, 610,  510, 608,  510, 610,  510, 610,  510, 610,  510, 1730,  510, 610,  510, 610,  510, 608,  510, 610, , 608,  510, 608,  510, 610,  510, 608,  510, 608,  510, 608,  510, 608,  510, 608,  510, 608,510, 610,  510, 608,  510, 608,  510, 610,  510, 608,  510, 608,  510, 608,  510, 608,  510, 610,  510, 608,  510, 610,  510, 610, 610,  510, 608};

We define a variable of an array for the raw data associated with each button that we wish to transmit over through the IR transmitter. 

If you happened to get hexCodes with protocol for your AC remote, do as following,

uint8_t samsungState[kSamsungAcStateLength] = {
    0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
    0x01, 0xE2, 0xFE, 0x71, 0x40, 0x11, 0xF0};

Here is an example using Samsung remote control. The hexCodes are saved in an array. You can use the whole array or only some indexes to transmit over. 

We then define our setup function where the braudrate is initialized to communicate with the receiver in AC.

void setup() {
#if ESP8266
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
#else  // ESP8266
  Serial.begin(115200, SERIAL_8N1);
#endif  // ESP8266

Similarly as with Receiving, if you are using the RX pin to drive the IR LED/transmitter, make sure to add the following line in the setup() function. Serial.begin(115200,SERIAL_8N1,SERIAL_TX_ONLY); That should limit in-bound serial communications from interfering on the ESP8266.

void loop() {
  Serial.println("a powerOn capture from IRrecv");
  irsend.sendRaw(powerOn, sizeof(powerOn) / sizeof(powerOn[0]), 38);  // Send a raw data capture at 38kHz.
  irsend.sendRaw(ModeCool, sizeof(ModeCool) / sizeof(ModeCool[0]), 38);
  Serial.println("a Samsung A/C state from IRrecv");

Next, in the loop function, irsend.sendRaw sends the raw data/hexCode to the receiver. If everything goes well, after pointing the IR transmitter towards the AC, it should power on and perform whatever signals you are sending. 

Note: Remember, you must send the signal to power on the AC first, only then you send can other signals for the AC to perform such as controlling the temperate, weather modes etc. 

This tutorial only shows the overall steps to control the AC with an IR transmitter. You can use any microcontroller to achieve this. The API offers a lot more options. Explore them on your own. Happy Exploring! 

You can find the code for this project in the zipfile attached at the end of the post. 

icon 1KB Download(204)
icon 2KB Download(141)
All Rights