Faking an IR remote control using ESPHome

Many thanks to OP and all the other contributions in here :heart:
Thanks to you guys I was able to create a light abstraction for some IR-only lights, and it works perfectly now :tada:

I have created a simple GitHub repo to document my progess. The code specifically replicates the IR Remote that came with the lights I wanted to control, but it should be relatively straight forward to adapt it to some other remote that you got. Feel free to give it a look :smiling_face:

1 Like

Hi everyone,

I’d like to introduce a new project I’ve been working on: the RGB Strip Remote IR Card for Home Assistant!

This custom card replicates the functionality of the original 44-key RGB strip remote, allowing you to fully control your RGB strip lights directly from Home Assistant. It works seamlessly with an ESPHome-configured IR transmitter, sending the exact same IR signals as the original remote, giving you full access to all the RGB modes, colors, and effects.

Here’s a quick overview of what it does:

  • Complete Control: Includes buttons for power, color presets, DIY modes, and transition effects (fade, jump).
  • Easy Setup: Simple installation by adding a few lines to your Home Assistant Lovelace UI and configuring an ESPHome device.
  • Works with Any RGB Strip: As long as you have an IR receiver and the appropriate ESPHome setup, this card should work with your RGB strip.

Features:

  • Emulates the original remote with the same button layout and functionality.
  • Control your RGB strip’s colors, brightness, and transitions directly from Home Assistant.
  • Integrates perfectly with ESPHome.

If you’re looking for a user-friendly solution to control your RGB strips with IR signals from Home Assistant, check out the project here: rgb_strip_remote_ir_card

Capture

Feel free to give it a try and let me know what you think. I’d love to hear your feedback!

1 Like

Thanks, very useful thread.

For the sake of anyone trying to do what I was doing, there’s a really useful resource for Sony IR codes here:

https://www.righto.com/2010/03/understanding-sony-ir-remote-codes-lirc.html

And a list of reference codes here:

http://www.hifi-remote.com/sony/
http://www.hifi-remote.com/sony/Sony_rcvr.htm for my specific device.

You can sniff codes as detailed in this post, but some Sony commands (e.g. explicit power on and power off) are not exposed on any remote control, so you need to convert them from the list above, which is detailed in the first link:

To work through a 20-bit example, if I have a Sony VCR/DVD Combo with a device code of 26.83, the site tells me that the code for “power” is 21. Convert 21 to a 7 bit binary value, 26 to a 5 bit binary value, and 83 to a 8 bit binary value. Then reverse and concatenate the bits: binary 1010100 + 01011 + 11001010, which is a8bca in hex.

Oh, and don’t forget that Sony devices expect each command to be repeated three times, so use repeat: times: 3 in your esphome config!

Once you get your head around it, it’s very simple but it cost me a couple of hours last night so this is here for the next person who wants to fiddle with Sony stuff.

1 Like

Very useful thread, thanks. But I need some help please!

I’ve got the IR receiver and transmitter working, each attached to a separate ESP8266, and integrated into Home Assistant. I’ve recorded the IR codes from my Logitech Harmony remote control - itself set up for Dynaudio Xeo 3 active loudspeakers - and can see what look like the right codes coming up.
e.g. Power On

[15:58:27][I][remote.jvc:049]: Received JVC: data=0x00FF
[15:58:27][I][remote.lg:054]: Received LG: data=0x00FF00FF, nbits=32
[15:58:27][I][remote.nec:097]: Received NEC: address=0xFF00, command=0xFF00 command_repeats=1
[15:58:27][I][remote.pioneer:149]: Received Pioneer: rc_code_X=0x0000

plus various lines of pronto codes.

Power Off

[16:18:28][I][remote.jvc:049]: Received JVC: data=0x00FF
[16:18:28][I][remote.lg:054]: Received LG: data=0x00FF28D7, nbits=32
[16:18:28][I][remote.nec:097]: Received NEC: address=0xFF00, command=0xEB14 command_repeats=1
[16:18:28][I][remote.pioneer:149]: Received Pioneer: rc_code_X=0x0014

plus various lines of pronto codes.

This and the others seem to align with the Dynaudio guidance here.

I have put the codes into the ESPHome script for the transmitter and can successfully send the signal to the receiver, where it is decoded correctly. (The range is about 30cm, which is a bit feeble, but as I’m going to tape the transmitter to the loudspeaker IR sensor, I don’t think that will matter.)

button:
  - platform: template
    name: "On NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xFF00
  - platform: template
    name: "Off NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xEB14
  - platform: template
    name: "On Pioneer"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0000
  - platform: template
    name: "Off Pioneer"
    on_press:
      - remote_transmitter.transmit_pioneer:
          rc_code_1: 0x0014  

All good so far. The problem is that the loudspeakers fail to respond at all to the signals sent from the IR transmitter, even when they are right together. I’ve been tending to use the NEC codes because the Dynaudio guidance refers to the NEC protocol. However that guidance only gives 2 digit codes, whereas the actual NEC codes are of the form 0x0014 etc. I also see the guidance states that the “Vendor Code” is 00FF. I don’t know what this is so I’ve done nothing with it.

I also noticed, when writing the yaml for the transmitter that the Pioneer syntax required rc_code_1 to make it work but it shows up as rc_code_X on the receiver logs. It wouldn’t compile unless I used the rc_code_1

Does anyone have any advice on what to do now? As it all seems to be set up to mimic the same codes as the Logitech Harmony remote correctly, I am flummoxed about why it won’t operate the actual speakers.

Embarrassingly, I think I have already found the problem. It seems the IR led I’m using is highly directional and has a very narrow beam, as well as being low power. It does make the speakers change settings when positioned close and at a certain angle. I’ll need to find some more powerful LEDs from somewhere.

I spoke too soon. Rigged up with the transmitter as close as possible to the sensor on the loudspeakers I am getting some commands to work, but most not. I can adjust volume up and down and two out of three inputs, but on, off, mute, and one input channel don’t work.

I’ve rescanned the codes from the (working perfectly with the loudspeakers) Logitech Harmony remote and checked that I have put in the same NEC commands into the transmitter yaml that show up.

button:
 - platform: template
   name: "On NEC"
   on_press:
     - remote_transmitter.transmit_nec:
         address: 0xFF00
         command: 0xFF00
 - platform: template
   name: "Off NEC"
   on_press:
     - remote_transmitter.transmit_nec:
         address: 0xFF00
         command: 0xEB14
 - platform: template
   name: "Volume Up NEC"
   on_press:
     - remote_transmitter.transmit_nec:
         address: 0xFF00
         command: 0xF906
 - platform: template
   name: "Volume Down NEC"
   on_press:
     - remote_transmitter.transmit_nec:
         address: 0xFF00
         command: 0xFB04

The first two of these don’t work - the second two do. Is there any subtly I don’t know about the syntax of NEC codes in the yaml? I’ve just plugged in the address and command values that have come off the IR sniffer logs. I’m unsure where the values after the 0x come from or mean as they are all different and not mentioned in the Dynaudio guidance (linked in the original post)

Is it possible that the LED you are using has a different infrared wavelength than your loud speakers?
Most remotes use a wavelength of 940 nm but you might have bought a LED with 850 nm.

Thanks for this, helped me to get my lights from toom hardware store to night light mode and back to normal

1 Like

Thanks and sorry to be slow replying- I have been travelling. The transmitter I currently have is a rx/tx pair I bought off Amazon and I was hoping for the best with respect to compatibility, but the wavelength isn’t specified.

I’ve now got some 940nm ones on order from a reputable shop which I will try out and report back on later

1 Like

Hi.

I’m adding a comment here because I appreciate the hardware suggestions but as a first-time ESPHome flasher, I ran into some confusion setting up the HiLetgo 2pcs ESP8285 ESP-01M IR Infrared Transmitter Receiver Module Digital Infrared Transceiver Sensor WiFi Remote Control Switch and HiLetgo CP2102 USB 2.0 to TTL Module Serial Converter Adapter Module USB to TTL Downloader with Jumper Wires. I’m posting this in case it might help other noobs like me.

The trouble involved two problematic steps:

  • ESPHome Web Installer error: wifi not found after initialization.
  • Not understanding that the ESP8285 ESP-01M needs the jumper removed after initialization.

ESPHome is super cool but this wifi error after initialization makes it seem like I did something wrong. Nothing is actually wrong and this probably shouldn’t show as an error. I will write something on the ESPHome site about that but I’m only saying it here to point out to others that it’s not you!! :slight_smile:

With this particular device, after the initialization step (which does NOT need the device to be connect to the HA instance) you then need to remove the double jumper (see photos).


In the end, I successfully set up a Toto Washlet remote transmitter. For completeness, here is my YAML for the Toto:

esphome:
  name: "toto-washlet-ir-521cbe"
  friendly_name: Toto Washlet IR ESPHome 521cbe
  min_version: 2024.11.0
  name_add_mac_suffix: false

esp8266:
  board: esp01_1m

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

remote_receiver:
  pin:
    number: GPIO14
    inverted: true
#  dump: all
  dump: toto

remote_transmitter:
  pin: GPIO4
  carrier_duty_percent: 50%

button:

  - platform: template
    name: "Stop"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0x0  # Stop
          rc_code_1: 0x0
          rc_code_2: 0x0

  - platform: template
    name: "Rear"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0xA8  # Rear
          rc_code_1: 0x6
          rc_code_2: 0x2

  - platform: template
    name: "User 1"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0x95  # User
          rc_code_1: 0x6
          rc_code_2: 0x1 # User 2?

  - platform: template
    name: "User 2"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0x95  # User
          rc_code_1: 0x6
          rc_code_2: 0x2 # User 2?

  - platform: template
    name: "Oscillate"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0x60  # Oscillate
          rc_code_1: 0x0
          rc_code_2: 0x0

  - platform: template
    name: "Puslate"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0xE0  # Pulsate
          rc_code_1: 0x0
          rc_code_2: 0x0

  - platform: template
    name: "Ron Special"
    on_press:
      - remote_transmitter.transmit_toto:
          command: 0xA8  # Rear
          rc_code_1: 0x6
          rc_code_2: 0x2
      - remote_transmitter.transmit_toto:
          command: 0x95  # User
          rc_code_1: 0x6
          rc_code_2: 0x2 # User 2?
      - remote_transmitter.transmit_toto:
          command: 0x60  # Oscillate
          rc_code_1: 0x0
          rc_code_2: 0x0
1 Like

Or maybe it IS you. This is from the product page you linked:

It would be awesome if the remote controller could be used in parallel. I mean: keep the receiver connected to the ESP so when pressing the physical keys the state of the light and the brightness level keep “synchronised”.

  • I turn on the light from HA: HA light state ON
  • I turn off the light from the remote controller: HA light state off.

Could this be possible?

Sure…
Just read the remote transmission and use this to set the entity in HA.

1 Like

Big shoutout to the OP for the instructions and recommendations! I fiddled with ESP32 and infrared receiving/sending before, but never could get it to work. With the parts mentioned by @Jpsy and a ESP32 devkit board I was finally able to make my own infrared remote for my JVC receiver. I can now finally toss my Broadlink RM Pro out the window. Or set in on fire. Or something else that utterly destroys it. Huge thanks!

1 Like

This is an excellent set of instructions and experience - many thanks to the OP for starting it off. Because of that I’m hoping someone might be able to help me with a problem I’ve got trying to replace the IR remote for a pair of active loudspeakers. I’ve tried all sorts of things out but still, only 4 of the 11 codes work! (I posted about this back in February but needed to break off from it, but now reapplying myself)

Background: The speakers are Dynaudio Xeo 3 and Dynaudio even publish the hex codes for remote control here.
Nevertheless, I set up an IR sniffer as laid out here and it reported NEC codes such as:
Power On: Address=0xFF00 Command=0xFF00
Power Off: Address=0xFF00 Command=0xEB14
Volume + : Address=0xFF00 Command=0xF906
Volume - : Address=0xFF00 Command=0xFB04

Using a little IR transmitter board, I programmed Home Assistant with the set of 11 NEC commands for the remote. 4 of the 11 codes worked. Thinking that it might be to do with marginal power from the IR diode, I reconfigured with a discrete LED and transistor to up the power. The same 4 of the 11 codes worked. For example, Volume + and Volume - work but Power On and Power Off do not. Tried it with different wavelength IR diodes but no difference unfortunately.

I’ve checked the yaml, tried repeats, Pioneer codes, higher duty cycles but without success. Here is the yaml


esphome:
  name: ${name}
  name_add_mac_suffix: false
  friendly_name: ${friendly_name}
  min_version: 2024.11.0

substitutions:
  name: "ir-blaster"
  friendly_name: IR Blaster

esp8266:
  board: d1_mini

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

captive_portal:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

remote_transmitter:
  pin: D7
  carrier_duty_percent: 50%

button:
  - platform: template
    name: "On NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xFF00
  - platform: template
    name: "On NEC v2"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0x00FF
          command: 0xFF00   
  - platform: template
    name: "Off NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xEB14
  - platform: template
    name: "Volume + NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xF906
  - platform: template
    name: "Volume - NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xFB04
  - platform: template
    name: "Mute NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xE916
  - platform: template
    name: "Input 1. Line NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xF30C
  - platform: template
    name: "Input 2. Optical NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xF20D
  - platform: template
    name: "Input 3. USB NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xF10E
  - platform: template
    name: "Channel A NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xEF10
  - platform: template
    name: "Channel B NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xEE11
  - platform: template
    name: "Channel C NEC"
    on_press:
      - remote_transmitter.transmit_nec:
          address: 0xFF00
          command: 0xED12

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

From the Dynaudio hex codes link posted at the beginning, they refer to the NEC protocol so am I right in thinking that the NEC code is likely the best one to use?

They give a “Vendor Code” of 00FF which doesn’t really show up in the codes from the sniffer. Does this mean anything and how do I represent in in the ESPhome yaml?

I’ve used the codes as sniffed, for consistency to avoid compilation errors. They map to the 2 digit codes given by Dynaudio but those in ESPhome have the address element and 4 preceding digits in the command. Does any of that matter?

Grateful for any insights, as I’m stuck but would really like to get the full set of codes working. Thanks.

Fantastic tutorial. Just rescued an old HT-CT150 sound bar from 15 years ago - the remote is not long for this world and now I can automate the power on and off with the TV! what a world we live in. thanks for the post

1 Like

As they are not being sold anymore, I was looking for a plan to get independent from my Logitech Harmony remote control in the future. So I stumbled across this topic and in combination with this virtual remote card, I think I now finally have a good plan for my exit strategy.
Just ordered the according parts from AliExpress which I will attach to my beloved LILYGO T-Internet-PoE ESP32 device.
Another awesome project for me and I will let you guys know how it went as soon as I got it working!

1 Like

So, as promised, I’ll just report back how my project went.
Since I did my research first and I knew I want to attach multiple IR transmitters, I mentioned that there is no way to create and use multiple IR receiver instances with the ESP8266 microcontroller because of its lack on dedicated RMT hardware peripheral. Based on that, things got clear I will have to use an ESP32 device instead for my project, which I have already installed anyway on my HAOS instance, in my case it is the much beloved LILYGO T-Internet-POE dev board.
Now that the project’s general conditions have been established, I first started off by ordering the IR transmitter and IR receiver parts recommended by OP on AliExpress, which took around two weeks to arrive.
As soon as they got delivered I first attached the IR receiver to the ESP32 with the code part which I just copy-pasted from OP’s initial tutorial and worked perfectly.
Since my Logitech Harmony Elite remote got all my needed different remote IR commands from all different vendors already bundled, I did not have to dig out all the dusty physical original remotes from all of my HiFi devices, instead I just physically placed the ESP32 dev board with the IR receiver in front of the Logitech Harmony’s IR blaster and was ready to go.
When it came into learning the Flirc IR commands, which I use to control a LibreELEC media center running a Raspberry Pi 5, I mentioned that ESPHome was not recognizing the IR commands as a pre-defined category. So I had to learn them as raw commands. Since OP suggested in his tutorial to not use raw commands if not explicitly necessary, I was worried about on how reliable it would work - but it really works surprinsigly great in the end!
As OP did not leave an example on how to learn raw commands, I’ll paste a working example out of my personal ESPHome config, where you can also see on how to configure multiple IR transmitter instances (Remember: This does not work on ESP8266, but only on ESP32!) in case you need a code example:


# Infrared receiver:
remote_receiver:
  pin:
    number: GPIO39
    inverted: true
  dump: all
# Infrared transmitter:
remote_transmitter:
  - id: IRT001
    pin: GPIO16
    carrier_duty_percent: 50%
  - id: IRT002
    pin: GPIO32
    carrier_duty_percent: 50%
  - id: IRT003
    pin: GPIO33
    carrier_duty_percent: 50%
# Infrared buttons:
button:
  # Roku:
  - platform: template
    name: "Roku Play"
    on_press:
      - remote_transmitter.transmit_nec:
          transmitter_id: IRT001
          address: 0xC2EA
          command: 0xFC03
          command_repeats: 1
  # Flirc:
  - platform: template
    name: "Flic Left"
    on_press:
      - remote_transmitter.transmit_raw:
          transmitter_id: IRT002
          carrier_frequency: 38kHz
          code: [3196, -3143, 563, -1063, 554, -1063, 554, -1063, 554, -527, 554, -1063, 554, -1063, 554, -1063, 554, -2809, 563, -527, 554, -527, 554, -527, 554, -527, 554, -527, 554, -527, 554, -1063, 1089, -1063, 1089, -1063, 554, -6384]

So after I got Home Assistant to learn all the required IR commands and tested them successfully on all of the end devices (e.g. AVR, TV, LibreELEC media center, etc.), I was finally ready to set up my own custom virtual remote control via the universal-remote-card on my Home Assistant dashboard. The universal-remote-card is highly customizable in any way, so I was able to replace the Logitech Harmony Elite device completely in mostly no time, with every virtual remote button mapped to a specific IR command HA entity button, triggered by my ESP32 dev board. To give you a nice example, the virtual remote looks like this:

As you may or may not mention on the picture, I wanted to create a virtual layout which looks as similar as possible to the original Logitech Harmony Elite device’s physical layout, but by getting rid of the buttons I don’t need instead, so that it does not get as overloaded by buttons as physical remotes most always do.

So, that was basically it! Success in any way I guess and I’m really happy with the outcome of this project. Everything went pretty well and it was a lot of fun to me and mostly straight forward, thanks to @Jpsy and its initial work. This is the amazing power of community driven projects guys!
Just wanted to add my two cents to this, so maybe this helps someone.

In addition, here are some impressions on how I managed to 3D print case mounting options for the IR transmitters and the ESP32 itself which I wired with Wago clamps which work very reliable and which in many cases I like more than soldering just because of the additional flexibility they provide:

Feel free to ask questions if you have any.

@Jpsy One cosmetic little thing I mentioned in your initial tutorial is where you describe on how to wire the IR transmitter and the IR receiver to VCC, you wrote 5V as the ESP voltage output. But ESP devices are providing a maximum voltage of 3.3V. Maybe you want to adjust this as it may confuse people. Thanks for considering.

3 Likes

I built an integration to manage your IR devices, if anyone finds that helpful...

2 Likes

Still a great valid tutorial!

A side note: I had some issues with Pronto that took a bit of futzing about. Here's what I received from the receiver when I pressed the Power button on the remote:

[08:10:39.073][I][remote.pronto:232]: Received Pronto: data=

[08:10:39.074][I][remote.pronto:240]: 0000 006D 0022 0000 0159 00AD 0016 0041 0016 0041 0016 0016 0015 0017 0015 0017 0015 0017 0015 0017 0015 0042 0015 0042 0015 0017 0015 0041 0016 0017 0015 0042 0015 0017 0015 0042 0015 0017 0015 0017 0015 0017 0015 0017 0015 0017 
[08:10:39.583][I][remote.pronto:240]: 0015 0042 0015 0017 0015 0017 0015 0042 0015 0041 0016 0042 0015 0042 0015 0042 0015 0017 0015 0042 0015 0042 0015 0017 0015 0181 

After a bunch of trial and error, it took me a while to figure out how to broadcast pronto, especially when the receiver split them into to lines (for whatever reason). My ESPHome On/Off template button looks like this:

button:
  - platform: template
    name: "Projector On/Off"
    on_press:
      - remote_transmitter.transmit_pronto:
          data: "0000 006D 0022 0000 0159 00AE 0015 0042 0015 0042 0015 0017 0015 0017 0015 0017 0015 0017 0015 0017 0015 0042 0015 0042 0015 0017 0015 0042 0015 0017 0015 0042 0015 0017 0015 0042 0015 0017 0015 0017 0015 0017 0015 0017 0015 0017 0015 0042 0015 0017 0015 0017 0015 0042 0015 0042 0015 0042 0015 0042 0015 0042 0015 0017 0015 0042 0015 0042 0015 0017 0015 0181"

I had to concatenate the data in the two messages and put them inside quotes. After I figured that out it works tickety-boo. One down, many to go.