Faking an IR remote control using ESPHome

ESPHome offers the Remote Receiver and Remote Transmitter components to interact with IR remote controls and IR remote receivers. In this guide I will show you how to control IR receiver units (like TVs or LED controllers) by sending fake signals from ESPHome.

The demo object here will be this simple LED controller with a 44 key infrared remote.
(There is also a 24 key version that uses the same protocol but different command codes.)
But the information in this guide can be applied to any other IR remote.

We want to control that little white box from an ESP8266 or ESP32.
Using the manual remote in parallel will still be possible.

The ESP I use will be an ESP8266 D1 Mini. For other ESPs you only need to adjust the basic ESP config and the GPIO pin names in the YAML code below.

The tasks at hand are:

  1. Attach an infrared emitter diode to the ESP
  2. Temporarily attach an IR receiving transistor to the ESP (needed for tasks 3. and 5.)
  3. Analyse the protocol of the IR remote handheld
  4. Try to send the same protocol(s) from ESPHome
  5. Get the command data for all keys of the handheld in the chosen protocol

Let’s go:

1. Attach an IR sending diode to the ESP

I chose this IR transmitter board from AliExpress. It runs on 5 Volts Vcc, has a driver stage so the input can be controlled with 3.3 Volt signals and – most importantly – it features two IR diodes in series which results in a stronger signal. In my setup it easily bridges several meters with rock solid performance. The board also has a small red SMD LED that blinks visibly when the unit is sending.

Connect the board to the ESP like this:

ESP – IR transmitter
---------
D7  – DAT
5V  – VCC
GND – GND

Hint: There are also IR emitters available that are already installed in nice little casings with a cable attached. See Brian’s comment.


2. Attach an IR receiving transistor to the ESP
The receiver is only needed for the analysis steps. It can be removed later.
This board from AliExpress is basically the counterpart to the sender chosen above.

Connect the board to the ESP like this:

ESP – IR receiver
---------
D5  – DATA
5V  – VCC
GND – GND

I did not add any level shifter between this board and my ESP. Fortunately the ESP did not complain. If you want to play it safe you might want to connect to the ESP using a voltage divider 1.8 kOhm / 3.3 kOhm like so:

DATA – 1.8k – D5 – 3.3k – GND

3. Analyse the protocol of the IR remote handheld

Now we have to find out what protocol the IR remote sends so we can emulate it later.
The following YAML will program the ESP so that it listens on the IR receiver and dumps any incoming signals to the log.

Make sure you have /config/esphome/secrets.yaml in place to provide the secret parts.

esphome:
  name: "ir-project"
  platform: ESP8266
  board: d1_mini

wifi:
  ssid:     !secret my_wlan_ssid
  password: !secret my_wlan_pwd

captive_portal:

logger:
  level: VERBOSE

api:
  password: !secret my_esp_api_pwd

ota:
  password: !secret my_esp_ota_pwd

remote_receiver:
  pin:
    number: D5
    inverted: true
  dump: all

Compile, upload and keep the log open.
Then take your 44 key handheld and push some buttons.
With each button press your log should show something like this:

[08:53:41][D][remote.jvc:048]: Received JVC: data=0x00FF
[08:53:41][D][remote.lg:053]: Received LG: data=0x00FF02FD, nbits=32
[08:53:41][D][remote.nec:068]: Received NEC: address=0xFF00, command=0xBF40
[08:53:41][D][remote.pioneer:144]: Received Pioneer: rc_code_X=0x0040

This is the protocol data we are looking for.

And there will be many occasional lines like this:

[08:53:41][D][remote.raw:041]: Received Raw: 206
[08:53:41][D][remote.raw:041]: Received Raw: 261, -1864, 267
[08:53:41][D][remote.raw:041]: Received Raw: 182, -704, 208
[08:53:41][D][remote.raw:041]: Received Raw: 472

This is IR static that the receiver constantly picks up. You can safely ignore it.

Note:
If you only receive raw data and none that is interpreted as a known protocol, chances are that your receiver is giving you inverted signals. If so change the inverted: setting in the above YAML. You can recognize a wrong polarity by looking at the raw data stream. The very first number should always be positive, never negative. Here is a correct example:

[11:12:42][D][remote.raw:028]: Received Raw: 8995, -4553, 523, -655, 498, -632, 549, -607, 548, -607, 522, -632, 550, -604, 551, -605, 523, -632, 522, -1753, 577, -1698, 549, -1726, 548, -1727, 522, -1751, 550, -1724, 550, -1724, 550, -1725, 549, -605, 550, -604, 550, -606, 550, -604, 
[11:12:42][D][remote.raw:041]:   551, -604, 549, -606, 550, -1725, 522, -632, 550, -1724, 576, -1698, 550, -1723, 578, -1696, 551, -1723, 550, -1723, 576, -579, 550, -1723, 551

Now let’s have a look at the protocol data (first log above):
We have set our remote_receiver to dump:all in our YAML. So with every transmission ESPHome tries to make sense of it by decoding it along various possible protocol patterns. In our case it could be interpreted as JVC protocol, but also as LG, NEC or Pioneer.

4. Try to send the same protocol(s) from ESPHome

To find out which protocol the controller really understands, we now try to send each of these protocols from ESPHome and see how the LED controller reacts. My example above is from pressing the Power button on the remote. So let’s add some YAML code and see, whether we can fake a Power On/Off from ESPHome.

Add the following lines to the above YAML:

remote_transmitter:
  pin: D7
  carrier_duty_percent: 50%

button:
  - platform: template
    name: "On/Off JVC"
    on_press:
      - remote_transmitter.transmit_jvc:
          data: 0x00FF
  - platform: template
    name: "On/Off LG"
    on_press:
      - remote_transmitter.transmit_lg:
          data: 0x00FF02FD
          nbits: 32
  - platform: template
    name: "On/Off NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xBF40
  - platform: template
    name: "On/Off Pioneer"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0040

As you can see, we created 4 buttons that send data in the 4 different protocols that we received earlier. The payload data that we send with each protocol is exactly what we received in the log above.

Compile this extended YAML and make sure you import the new ESPHome integration in Home Assistant, so the 4 buttons appear in the HA entities list.

If you now press any of the buttons in HA you should see the red LED on the transmitter board blinking. Your LED controller should react to some of these buttons, but not to all of them. In my case I could reliably control the LED strip with both the Pioneer and the NEC protocol. LG worked sometimes but was not at all reliable. JVC never worked.

Because Pioneer seemed to be a tiny bit faster than NEC, I chose the Pioneer protocol.

Note:
It is also possible to dump the raw data received by the IR receiver (set dump: to - raw). You can then simply replay it from ESPHome without any upfront protocol interpretation. While you will find several guides on the web that choose that approach, I do definitely not recommend it. The reason is that the raw data is in fact an analog data stream. You can see that because the raw data of the same key press changes a bit with each transmission. If you replay that it will contain all static and distortions that happened during recording. The result is reduced transmission success rate. That’s surely not what you want. Sending raw data should basically be a last resort if the protocol of your remote is really unknown to ESPHome.

5. Get the command data for all keys in the chosen protocol

Now you can use the handheld and the log once more to find the command codes of all keys that you want to emulate from ESPHome. If you want to concentrate on the protocol you have chosen, you can change the dump: parameter to show just that protocol:

  dump: 
    - pioneer

When done add all command codes that you found as buttons to your YAML.
As an example here are all the definitions for the first 6 rows / 24 keys including all the color buttons:

button:
  - platform: template
    name: "IR remote, brighter"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x005C
  - platform: template
    name: "IR remote, darker"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x005D
  - platform: template
    name: "IR remote, play"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0041
  - platform: template
    name: "IR remote, on/off"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0040
  - platform: template
    name: "IR remote, red"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0058
  - platform: template
    name: "IR remote, green"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0059
  - platform: template
    name: "IR remote, blue"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0045
  - platform: template
    name: "IR remote, white"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0044
  - platform: template
    name: "IR remote, dark orange"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0054
  - platform: template
    name: "IR remote, orange"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0050
  - platform: template
    name: "IR remote, warm yellow"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001C
  - platform: template
    name: "IR remote, yellow"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0018
  - platform: template
    name: "IR remote, emerald"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0055
  - platform: template
    name: "IR remote, turquoise"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0051
  - platform: template
    name: "IR remote, cyan green"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001D
  - platform: template
    name: "IR remote, cyan"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0019
  - platform: template
    name: "IR remote, blue violet"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0049
  - platform: template
    name: "IR remote, violet"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x004D
  - platform: template
    name: "IR remote, red violet"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001E
  - platform: template
    name: "IR remote, magenta"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001A
  - platform: template
    name: "IR remote, white rose"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0048
  - platform: template
    name: "IR remote, rose"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x004C
  - platform: template
    name: "IR remote, white cyan"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001F
  - platform: template
    name: "IR remote, light cyan"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x001B

The IR receiver board and its YAML lines (remote_receiver:...) can be removed now.
Your fake IR remote control is ready to play.

Enjoy! :smiley:

49 Likes

Wouldn’t that write up be a perfect fit for the esphome website? Maybe in the cookbook :man_cook:?

1 Like

Well, I think it would. But to be honest I hardly ever use the cookbook section of the ESPHome website myself. I usually either reed the component docs of ESPHome or I use the HA forum. Maybe the mods could share a thought about the right place (@petro, @tom_l)?

Do what you want to support.

Hmmm. No intention of repeating myself, so either here or there. I guess I will leave it here as the Guides section is open for anybody to edit and contribute.

Anyway, thanks for the valid suggestion @orange-assistant !

Great write-up! For what it’s worth, I just hooked a passive IR emitter (this one, actually) directly to a pin on an ESP32 and it works great, no level-shifting needed. Of course YMMV. Thanks!!

7 Likes

Thanks Brian, I added that information to the guide.

1 Like

Is it possible to make light component out of this in ESPHome?

Thanks for the great explanations.

I’ve managed to setup the receiver and transmitter and it works well with regular TV remote (i.e Sony)

However i have a swimming pool cover that is IR controlled and its remote only send RAW data. I’ve tried to grab one and send it with the transmitter but it doesn’t work. The code seems slightly different each time.

Is there a solution to make that work ?

Thanks

1 Like
2 Likes

The reason for that is explained in the guide: You record the “analog” data including noise.
If ESPHome does not recognize any encoding, I am afraid, you have no other option.

1 Like

Thanks for your clear answer.

I’m trying with another remote for an old device and I get pronto code.

Close button:

[19:56:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0054 0012 06C3
[19:56:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0013 0055 0011 06C3
[19:56:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0012 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0012 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0012 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0012 0054 0012 06C3
[19:56:05][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:06][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:06][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0012 0055 0011 06C3
[19:56:11][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:12][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0012 0055 0011 06C3
[19:56:12][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0011 06C3
[19:56:12][D][remote.pronto:238]: Received Pronto: data=0000 006D 0002 0000 0011 0055 0012 06C3

Ope button:

[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0027 06C3
[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:01][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:02][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:03][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3
[19:57:03][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0026 06C3
[19:57:03][D][remote.pronto:238]: Received Pronto: data=0000 006D 0001 0000 0025 06C3

I’ve been looking in ESPHome documentation and can’t figure out how to deal with Pronto code.

Thanks for your help

Sending Pronto is explained here:

BTW:
If your ESPhome can’t interpret your remote: Did you try to reverse the polarity as explained in the guide?

I have a use for this on a future project and recently found this ESP8285 based IR transmitter receiver module on Ali Express.

I still have to test it but it should do the job without any additional wiring.

ESP IR TxRx-2

1 Like

It’s the same one as I posted a link to just a few posts ago.

Missed that :grimacing:

Yep i tried reversing polarity but didn’t help.

Thanks for link about Pronto, sorry i missed it. I will try that later this week.

What I usually do if I get different codes like that is to create a large sample, say 20-30 or 40 presses.
Then copy it to Excel and find the code that is most frequent and use that as the code. It has worked every time.

2 Likes

Hello, instead of buttons in HA
I’d like to use physical remote (to emulate logitech harmony) that send ir signal to esphome and then it will toggle the predefined buttons.
Is there an example for remote receiver?

Yes:

:point_up_2: just check the docs behind the blue text for examples

2 Likes