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
- The TV did not respond to
remote_trasmitter.transmit_sony
power toggle command - The IR range was only a couple of feet
- Rotary encoder events queueing up instead of only executing while rotation is active
- 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.
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
Leave a Reply