rotary encoder esphome esp volume control IR blaster

How to resolve issues integrating Home Assistant, ESPHome, IR Blaster, Sony and NEC Protocols

I have an old Yamaha RX-V630 receiver and an older RCA 4K TV which are missing remotes.

I wanted to replace them with an ESP8266-based infrared blaster connected to Home Assistant.

Here are some of the problems I encountered and overcame.

Problems

  1. The TV did not respond to remote_trasmitter.transmit_sony power toggle command
  2. The IR range was only a couple of feet
  3. Rotary encoder events queueing up instead of only executing while rotation is active
  4. Using ESP pullup resistors for rotary encoder reading produced faulty square wave

You can see my full ESPHome config YAML at the end of the post.

1. Transmitting IR Codes

Sony IR protocol commands are repeated 3 times. I am repeating them 5 times just to compensate, in case one or two not registering.

The remote_transmitter’s wait_time configuration option needs to be set to 40ms minus the time it takes to transmit the code.

In my case, transmitting the power toggle code 0xA90 takes almost 10ms, so I set my wait time to 40ms – 10ms = 30ms.

Originally, I made a mistake and set the wait time to 25ms. But it still worked. Seems like leaving less wait time is better than more as it didn’t work when i tried 35+ms wait times. I settled on 30ms.

      - remote_transmitter.transmit_sony:
          data: 0xA90
          nbits: 12
          repeat:
            times: 5
            wait_time: 30ms

2. Increasing IR Range: The schematic

The main takeaway for me was that IR LEDs can be pulsed to much more than the standard 50 mA of constant current they are rated for. IR codes are pulsed so you can refer to the pulsed maximum current in your IR LED’s datasheet.

I chose 10 Ohm resistors to limit the current to about 500 mA each IR because of the BS170 MOSFET, which has a maximum pulsed current of 1200 mA.

I could’ve gone with one IR and pulse it up to 1 A using a 5 Ohm resistor to further increase range, but I wanted to maximize coverage by pointing the two LEDs in slightly different directions.

esp8266 ir blaster schematic

3. on_clockwise and on_anticlockwise VS on_value

TL;DR: on_value takes advantage of all the sensor filters, while on_clockwise and on_anticlockwise ignore them.

I started by using built in on_clockwise and on_anticlockwise triggers. However, those triggers have a big flaw that prevents them from being useful in our case.

The issue is that on_clockwise and on_anticlockwise triggers are fired on values that are not filtered. So any of debounce, throttle, etc filters are ignored and these triggers are called for every click.

That means, if someone spins the encoder for many clicks in either direction, events are queued up and there are as many triggered events as there were clicks. In case of a volume adjustment that is not desired.

Imagine holding down the volume up button on the remote for 1 second and it actually queueing up 20 volume up events. That takes 7-10 seconds to execute and brings the volume way higher than you expect. This actually happened to us and nearly blew our speakers, and ear drums 🙂

The expectation is to raise/lower volume while the button is pressed, and stop as soon as it is released.

To achieve that, we need to use a filters throttle option, which is ignored by on_clockwise / on_anticlockwise.

The solution is to use on_value trigger. It employs all the filters but does not keep track of the previous state to determine if we are rotation clockwise or counterclockwise.

Luckily, that is easy to keep track of with a lambda trigger handler. Like this:

    restore_mode: ALWAYS_ZERO
    filters:
      - throttle: 300ms
    on_value:
      - lambda: |-
          static float prev_x = 0;
          if (x > prev_x) {
            id(living_room_remote_volume_up).press();
          }
          else if (x < prev_x) {
            id(living_room_remote_volume_down).press();
          }
          prev_x = x;

I chose 300ms throttle because it takes about 260ms to transmit volume up/down command using NEC protocol. It actually takes less time but I am repeating it 3 times for each click as one command adjusts the volume on the receiver by only 0.5dB.

4. Weak ESP pin pullup resistors

I ended up disabling pullup resistors on the ESP pins and adding two discrete 10K Ohm resistors to pull up each of CLK and DATA (A and B) connections.

ESPHome config YAML for the button, encoder and emitters

sensor:
  - platform: rotary_encoder
    name: "Living Room TV Volume"
    id: "living_room_tv_volume"
    pin_a:
      number: D6
      mode:
        pullup: false
        input: true
    pin_b:
      number: D5
      mode:
        pullup: false
        input: true
    restore_mode: ALWAYS_ZERO
    filters:
      - throttle: 300ms
    on_value:
      - lambda: |-
          static float prev_x = 0;
          if (x > prev_x) {
            id(living_room_remote_volume_up).press();
          }
          else if (x < prev_x) {
            id(living_room_remote_volume_down).press();
          }
          prev_x = x;

remote_transmitter:
  pin: D2
  carrier_duty_percent: 30%

binary_sensor:
  - platform: gpio
    id: "living_room_remote_button"
    pin:
      number: D7
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on: 50ms
      - delayed_off: 50ms
    on_press:
      - button.press: living_room_remote

button:
  - platform: template
    id: "living_room_remote"
    name: "Living Room TV"
    on_press:
      - remote_transmitter.transmit_sony:
          data: 0xA90
          nbits: 12
          repeat:
            times: 5
            wait_time: 30ms
      - remote_transmitter.transmit_nec:
          address: 0x857A
          command: 0xE01F

  - platform: template
    id: "living_room_remote_volume_up"
    name: "Living Room TV Louder"
    on_press:
      remote_transmitter.transmit_nec:
        address: 0x857A
        command: 0xE51A
        repeat: 4

  - platform: template
    id: "living_room_remote_volume_down"
    name: "Living Room TV Quieter"
    on_press:
      remote_transmitter.transmit_nec:
        address: 0x857A
        command: 0xE41B
        repeat: 4

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *