Background
In this post I provide details for building and integrating IR transmitters to control AV components. A raspberry pi is used at each point where I want to transmit IR signals. The post tells you how to enable and wire IR transmission and receiving on a RPI. It also includes examples of how I send IR scancodes to both a Samsung TV and a pioneer AV receiver.
In a previous post I document setting up a whole house audio system. In reality audio is only half of a typical home entertainment system. Video is also important. If you have all new high end components you’ll probable be able to use network base command and control for all of your components. However, if you’re like most people, you have a mixture of old and new components. Some components will have network controls, others will have IR controls, while still others may have no smart controls at all. In another post I documented steps I took to smarten up a very old dumb stereo amp.
My smart network tv is connected via wifi, how do I turn it on
From what I’ve seen Wake On Lan seems to be one of the primary methods utilized by HA integrations to turn on a smart TV connected to the network. This works fine if the TV is connected via Ethernet, however most people connect their TV to their local network via WiFi. Wake On Lan doesn’t work over WiFi. I have a Samsung TV that I can fully control via an HA integration, with one sad exception. I can’t turn it on using the HA integration because the integration uses Wake On Lan. In the post I mentioned above that documents building a full house audio system I document placing a squeezelite media player that runs on a Raspberry PI 3 (RPI3) next to any device that I want to have provide audio output. One way to address the turn on problem is to turn that squeezelite RPI3 into a Ethernet to WiFi bridge and then connect the tv via Ethernet to the RPI3 Ethernet port. This mainly works unless you want to turn the tv back on shortly after turning it off. When you turn the TV off it’s not really off, its in some kind of snooze state. As such you can not wake it via Wake On Lan. A better way that always works is to transmit the remote control IR power on signal. So by adding IR transmission capabilities to my squeezelite RPI3 I can use it to provide network controllable IR transmission capabilities.
Enabling IR receive and transmission capabilities on a RPI
While you only need the IR transmission capability to control your IR enabled components, you need some way to figure out what signal to transmit related to the appropriate button on the remote control you’ll be emulating. As such you really need both transmit and receive capabilities on at least one device.
The Raspberry PI operating system makes it pretty easy to enable IR transmit and Receive capabilities. In the /boot/config.txt file you should fine these two lines commented out via # at the start of the line. You remove the # character to enable the lines so they look like this:
dtoverlay=gpio-ir,gpio_pin=18
dtoverlay=gpio-ir-tx,gpio_pin=17
You need to reboot the RPI for these to take affect. You then need to install two packages that will be used to first discover signals from your remote control and secondly transmit these signals:
apt install ir-keytable
apt install v4l-utils
You can get IR transmitters and receivers here. If you look on the internet for instructions on connecting these to the GPIO pins they always include at least one resisters for both the transmitter and receiver. They also include a transistor for the transmitter. I’m not an electrical engineer so I could be missing something, but I don’t use the resister because the RPI3 gpio pins have built in resisters. I’ve run a receiver for years with no resister and have had 0 issues. The IR receiver does a great job of capturing the signal from my remote controls. The transistor included in the transmitter circuit is to strengthen the transmitted signal strength. The on line documentation tells you that connecting the transmitter directly to the GPIO pins will not provide enough power. They are correct if you want to transmit the signal more than a few feet. However in my setup I just put the transmitter within a few inches of the receiving device and all is good. Here’s a picture of one device with both the receiver and transmitter connected.
While the receiver is just sticking out of the top of the RPI case, the transmitter is connected via longer wires and is sitting at the bottom left of the picture in front of the FIOS cable tuner to control it.
Wiring in the transmitter and receiver
Here is the RPI pinout picture:
I include this because up above we configured the Raspberry PI OS to use GPIO 17 and GPIO 18 for transmit and receive respectively, which aree actually pins 11 and 12.
On the transmitter LED the longer of the two leads is the positive side. This longer lead needs to be connected to gpio 17 (pin 11 ) and then the shorter lead needs to be connected to ground (pin 9).
For the receiver you have three leads. If you look at the receiver as oriented in the picture above, the left lead gets connected to gpio 18 (pin 12 ), the center to ground (pin 6) and the right lead to 5 volt (pin 2).
Capture remote scan codes
There are two methods I’ve used to capture and transmit IR codes. In the easiest method I used ir-keytable to discover scancodes as I push button on the remote I want to emulate. With the IR receiver connected and the software installed you should be able to run the following command to catch an IR signal:
sudo ir-keytable -v -t -p rc-5,rc-5-sz,jvc,sony,nec,sanyo,mce_kbd,rc-6,sharp,xmp -s rc1
If this errors out then change the rc1 at the end of the line to rc0. If the command works it should print out some lines ending with
Protocols changed to rc-5 rc-5-sz jvc sony nec sanyo mce_kbd rc-6 sharp xmp
Testing events. Please, press CTRL-C to abort.
Pushing buttons on the remote you want to emulate should provide output similar to this
1796817.910060: lirc protocol(nec): scancode = 0xa51c
1796817.910087: event type EV_MSC(0x04): scancode = 0xa51c
1796817.910087: event type EV_SYN(0x00).
1796822.140068: lirc protocol(nec): scancode = 0xa55c
1796822.140099: event type EV_MSC(0x04): scancode = 0xa55c
1796822.140099: event type EV_SYN(0x00).
1796822.230135: lirc protocol(nec): scancode = 0xa5c0
1796822.230162: event type EV_MSC(0x04): scancode = 0xa5c0
1796822.230162: event type EV_SYN(0x00).
You want to document the scancode and it’s associated button as you’ll need them in the transmission script. You also need the protocol reported. The above were codes from an AV surround sound amplifier. The first was the power button. I can then use the following command to transmit this remote key:
sudo ir-ctl -S nec:0xa51c
I mentioned above I needed a way to turn on the Samsung TV. When attempting to record the Samsung remote, the power button was the only button that provide a scan code. Good thing it was the only key I needed to transmit. This is the output provided:
1797459.520081: lirc protocol(necx): scancode = 0x707e6
1797459.520111: event type EV_MSC(0x04): scancode = 0x707e6
1797459.520111: event type EV_SYN(0x00).
1797459.630069: lirc protocol(necx): scancode = 0x707e6
1797459.630094: event type EV_MSC(0x04): scancode = 0x707e6
1797459.630094: event type EV_SYN(0x00).
So I should have been able to play this code with the following:
sudo ir-ctl -S necx:0x707e6
Sadly this didn’t work. Don’t worry yet as all is not lost, there is a second method for making a raw capture of the IR signal. You capture the raw signal information with this command:
ir-ctl -d /dev/lirc1 --receive=samsung_power.key
There is a possibly that your receiver device could be /dev/lirc0, so if get an error message stating lirc1 has no receiving capability then try lirc0. Assuming the above doesn’t error out you push the key you want to record then you use c to stop the ir-ctl command. Looking at the created samsung_power.key file and you’ll see something like the following:
pulse 4509
space 4452
pulse 607
space 1648
pulse 607
space 1649
pulse 607
space 1650
...
...
...
space 519
pulse 607
space 520
pulse 606
timeout 75841
You need to delete the timeout line from the end of this file. Then you can replay this IR signal with the following command:
sudo ir-ctl -d /dev/lirc0 --send=samsung_power.key
This method worked for me with the Samsung TV.
In order to enable your user account to transmit the IR signal without using sudo you can add your user account to the video group. If you’re just using the default pi user then it should already be in the video group.
We now create a script file ir-wake-samsung in your users home directory with the following in it:
#!/bin/bash
ir-ctl -d /dev/lirc0 --send=samsung_power1.key
This is the most basic script that is capable of sending one remote key. You set the permission of this file to 755.
Update HA to sent the samsung remote control power key
Obviously I’m documenting using a Samsung TV, you should be able to do something similar with any TV as long as the OS understand the remote control protocol and produced scancodes.
The first thing you need to do on the HA side is create a shell command that can remotely execute the above script on the remote RPI3. If you don’t already have the HA “Terminal & SSH” add-on installed on your HA box install it from the Add-on Store in the HA GUI. From the Terminal & SSH" add on you can start a terminal window from the “OPEN WEB UI” link. This gives you an shell on your HA box. Get into the base configuration directory via:
cd /root/config
I place all of my HA shell commands in the directory shell_cmds, which you can create via:
mkdir shell_cmds
cd shell_cmds
You can then use either the vi or nano editor to create the shell command “wake_samsung_tv” that will execute the script on the remote pi and transmit the remote control scancode. The file should contain the following:
#!/bin/bash
ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no YOURUSER@RPI_IP_ADDR ./ir-wake-samsung
You update YOURUSER and RPI_IP_ADDR with your user account on the RPI and the IP address for the RPI. The permission of the file should also be 755.
If you haven’t previously made an ssh key pair on your HA box via the Terminal window you do that with the following commands
cd /root/config
mkdir .ssh
cd .ssh
ssh-keygen -t rsa
It will ask you where to save the key, enter
/root/config/.ssh/id_rsa
It’ll ask you for a passphrase, just hit the enter/return key
It’ll ask you for the passphare again, just hit the enter/return key
See that they key pair has been generated:
ls -l
Push the id_rsa.pub key to your RPI that will be sending remote control commands:
scp id_rsa.pub YOURUSER@RPI_IP_ADDR:
You update YOURUSER and RPI_IP_ADDR with your user account on the RPI and the IP address for the RPI.
Back on the remote RPI you need to add this key to YOURUSER’s authorized_key file
cd ~/
cat *.pub >> .ssh/authorized_keys
chmod 600 .ssh/authoized_keys
Jump back to the HA web terminal and make sure the key pair is working
ssh -i ./id_rsa -o StricthostKeyChecking=no YOURUSER@RPI_IP_ADDR
Assuming this logs you on to the remote RPI without asking for a the user password you’re set up.
Next in the HA terminal window you need to edit the configuration.yaml file
nano /root/config/configuration.yaml
Add the following lines at the end of the file
shell_command:
wake_samsung_tv: '/config/shell_cmds/wake_samsung_tv'
If your configuration.yaml file already contains a shell_command line you DO NOT add it again, you simply add the second line under the existing shell_command line, maintaining the same indentation as your other shell_command lines. To enable this you need to restart HA core, which you can do in the HA GUI from “Developer Tools → YAML → RESTART”
From HACS I utilize the “SamsungTV Smart” intergration and the TV Remote Card (with touchpad and haptic feedback) to control the Samsung TV. I add a manual card to the HA GUI with the following contents
type: custom:stack-in-card
title: TV Controls
mode: vertical
cards:
- type: custom:tv-card
entity: media_player.backbed_samsung
tv: true
enable_button_feedback: false
media_control_row:
- samsung_power
- mythtv
- music
- select_app
- tv
channel_row:
- return
- info
navigation_row: buttons
volume_row: buttons
custom_keys:
samsung_power:
icon: mdi:power
service: shell_command.wake_samsung_tv
tv:
icon: mdi:television-classic
service: media_player.play_media
service_data:
media_content_id: KEY_SOURCE+KEY_LEFT+KEY_LEFT+KEY_LEFT+KEY_ENTER
media_content_type: send_key
entity_id: media_player.backbed_samsung
mythtv:
icon: mdi:television-play
service: media_player.play_media
service_data:
media_content_id: KEY_SOURCE+KEY_LEFT+KEY_LEFT+KEY_LEFT+KEY_RIGHT+KEY_ENTER
media_content_type: send_key
entity_id: media_player.backbed_samsung
music:
icon: mdi:music
service: media_player.play_media
service_data:
media_content_id: KEY_SOURCE+KEY_LEFT+KEY_LEFT+KEY_LEFT+KEY_RIGHT+KEY_RIGHT+KEY_ENTER
media_content_type: send_key
entity_id: media_player.backbed_samsung
select_app:
icon: mdi:apps
service: media_player.play_media
service_data:
media_content_id: KEY_HOME
media_content_type: send_key
entity_id: media_player.backbed_samsung
You can see under samsung_power it calls shell_command.wake_samsung_tv to send the remote control power key from the remote RPI. If you have a samsung tv you would update all of the references above to media_player.backbed_samsung to be your tv’s entity ID. What I like about the custom TV card is it works well with little to no configuration for a samsung tv. It also allows the function associated for any and all keys to be overridden, so each button can be used to send a remote control button for a different TV if you desire.
The interface provided as configured above looks like this:
Clicking on the power button should turn the Samsung TV on and off by causing the remote control power button scancode to be transmitted.
The above is fine if you only have to send one or two keys, but if you have to send a lot of keys you might want to try a different way to interface between HA and the remote RPI3. Above I showed a few keys that were reported associated with an AV surround sound receiver. On the RPI3 that sits by this receiver I use the following script named proj-amp-networkControl.py to control the receiver and a projector. The script transmits IR commands to control the AV receiver and RS323 commands to control the Epson projector. The proj-amp-networkControl.py contents are here:
#!/usr/bin/python3
# License: Public Domain
from __future__ import division
import time
import sys
import socket
import os
# Uncomment to enable debug output.
#import logging
#logging.basicConfig(level=logging.DEBUG)
state="OFF"
#def feed_drop_servo():
# -- Main ----
HOST = '' # Symbolic name meaning all available interfaces
PORT = 33333 # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen(1)
try:
while True:
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024).decode("ascii")
if not data: break
#print('Data: ',data)
if data == "POWER":
print("Command read is POWER")
# Turns piorneer off (cycle power)
os.system("sudo ir-ctl -S nec:0xa51c")
# Turn projector off
os.system("/home/brian/epson-proj-serial-ctl.py OFF")
state="OFF"
elif data == "AMPPOWER":
print("Command read is AMPPOWER")
# Cycle POWER on AMP and leave state alone
os.system("sudo ir-ctl -S nec:0xa51c")
elif data == "UP":
print("Command read is UP")
# Turn volume up
os.system("sudo ir-ctl -S nec:0xa50a")
time.sleep(.1)
os.system("sudo ir-ctl -S nec:0xa50a")
elif data == "DOWN":
print("Command read is DOWN")
# Turn volume down
os.system("sudo ir-ctl -S nec:0xa50b")
time.sleep(.1)
os.system("sudo ir-ctl -S nec:0xa50b")
elif data == "MUTE":
print("Command read is MUTE")
os.system("sudo ir-ctl -S nec:0xa512")
elif data == "FIRE":
print("Command read is FIRE")
# If state is off then turn on amp for audio
if state == "OFF":
os.system("sudo ir-ctl -S nec:0xa51c")
# Now turn projector on for HDMI1
os.system("/home/brian/epson-proj-serial-ctl.py ON HDMI1")
# set AMP to input connected to FIRESTICK (DVD)
os.system("sudo ir-ctl -S nec:0xa585")
# Mark state as watching Firestick
state="FIRE"
elif data == "MYTH":
print("Command read is MYTH")
# If state is off then turn on amp for audio
if state == "OFF":
os.system("sudo ir-ctl -S nec:0xa51c")
# turn projector on and set to PC input
os.system("/home/brian/epson-proj-serial-ctl.py ON PC")
# set pioneer to PC connected input so sound plays (DVR/BVR)
os.system("sudo ir-ctl -S nec:0xa589")
# Mark state as watching Mythtv
state="MYTH"
elif data == "LMS":
print("Command read is LMS")
# If state is off then turn on amp for audio
if state == "OFF":
os.system("sudo ir-ctl -S nec:0xa51c")
# since we did turn on projector will have to wait for audio to come up
time.sleep(20)
# turn off projector
os.system("/home/brian/epson-proj-serial-ctl.py OFF")
# Set piorneer to input for music (CD)
os.system("sudo ir-ctl -S nec:0xa54c")
# Mark state as listening to Logitect Media Server (squeezebox music)
state="LMS"
conn.close()
sys.stdout.flush()
finally:
print("All done")
While you probable will not be controlling a projector via RS232, I’m including the script epson-proj-serial-ctl.py because it’s referenced above in the previous script. The script epson-proj-serial-ctl.py contains the following:
#!/usr/bin/env python
import sys
import time
import serial
ser = serial.Serial(
#port='/dev/ttyAMA0',
#port='/dev/ttyS0',
port='/dev/ttyUSB0',
baudrate = 9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
write_timeout=1,
xonxoff=False,
rtscts=False,
dsrdtr=False,
timeout=1
)
arg_cnt = len(sys.argv)
if arg_cnt < 2:
print("Usage:",sys.argv[0],"[ON|OFF]","[PC|HDMI1|HDMI2|COMPONENT|SVIDEO|VIDEO]")
exit(0)
cmd = sys.argv[1]
if ((cmd != "ON" and cmd != "OFF") or (cmd == "ON" and arg_cnt < 3)):
print("Usage:",sys.argv[0],"[ON|OFF]","[PC|HDMI1|HDMI2|COMPONENT|SVIDEO|VIDEO]")
exit(0)
#Clear anything that might be in input buffer
readlen=40
while readlen == 40:
x=ser.read(40)
readlen = len(x)
if cmd == "OFF":
full_cmd = ("PWR OFF\r").encode("ascii")
ser.write(full_cmd)
else: # Turning on projector if need be and setting input
full_cmd = ("PWR?\r").encode("ascii")
ser.write(full_cmd)
x=ser.read(10)
if len(x)>1:
resp=(((x.decode("ascii")).split('\r'))[0]).split('=')[1]
print("Response: ", resp )
# 00 means projector is off so turn it on
if resp == "00":
print("Turning projector on")
full_cmd = ("PWR ON\r").encode("ascii")
ser.write(full_cmd)
time.sleep(30)
# Clear out read buffer again
readlen=40
while readlen == 40:
x=ser.read(40)
readlen = len(x)
# Need to wait until projector reports power on when asked to set input
full_cmd = ("PWR?\r").encode("ascii")
done = False
while not done:
# Send command to check power
ser.write(full_cmd)
# read response
x=ser.read(30)
if len(x)>1:
try:
resp=(((x.decode("ascii")).split('\r'))[0]).split('=')[1]
print("Response: ", resp )
# 01 means projector is on
if resp == "02":
done = True
except Exception:
pass
else:
print("No response to PWR? command, will exit")
exit(0)
source = sys.argv[2]
print("Need to set source to",source)
if source == "PC":
full_cmd = ("SOURCE 21\r").encode("ascii")
elif source == "HDMI1":
full_cmd = ("SOURCE 30\r").encode("ascii")
elif source == "HDMI2":
full_cmd = ("SOURCE A0\r").encode("ascii")
elif source == "COMPONENT":
full_cmd = ("SOURCE 14\r").encode("ascii")
elif source == "SVIDEO":
full_cmd = ("SOURCE 42\r").encode("ascii")
elif source == "VIDEO":
full_cmd = ("SOURCE 41\r").encode("ascii")
else:
print("Source",source,"not known")
exit(0)
ser.write(full_cmd)
These two scripts provide a service that runs on port 33333 and is started by the following line in /etc/rc.local
su -c '/home/brian/proj-amp-networkControl.py 2>&1 > /dev/null &' brian
On the HA side there is a simple shell command pioneer_ctl that handles sending commands to the remote pi IR transmitter service
#!/bin/bash
echo -n $1 | nc -w0 192.168.0.115 33333
The IP address would be the IP of the RPI the service is running on. The pioneer_ctl shell comand is made avilable to HA in the configuration.yaml file with the following line under the “shell_command:” directive
pioneer_ctl: '/config/shell_cmds/pioneer_ctl {{ cmd }}'
Here’s then part of a custom card in the HA GUI that results in the transmission of IR signals
type: vertical-stack
cards:
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: MYTH
icon: mdi:television-play
name: Mythtv on
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: FIRE
name: Firestick On
icon: mdi:fire-circle
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: LMS
icon: mdi:music
name: Music On
- show_name: true
show_icon: true
type: custom:button-card
entity: binary_sensor.basement_amp_status
size: 25%
state:
- value: 'on'
color: green
name: AMP 2 Off
icon: mdi:power-plug-off
- value: 'off'
color: default
name: AMP 2 On
icon: mdi:power-plug-outline
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: AMPPOWER
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: POWER
icon: mdi:power-plug-off
name: All Off
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: MUTE
icon: mdi:volume-mute
name: Mute
- show_name: true
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: DOWN
icon: mdi:volume-minus
name: Volume Down
- show_name: true
name: Volume Up
show_icon: true
type: custom:button-card
size: 25%
tap_action:
action: call-service
service: shell_command.pioneer_ctl
service_data:
cmd: UP
icon: mdi:volume-plus
In the above you see that shell_command.pioneer_ctl is called in various places with on parameter being provided to identify the scancode to be sent on the remote RPI3. The custom buttons created from the code above look like this
The Amp 2 on button is a pretty interesting button as it gets the status or the amp using the current draw reported by a TP-Link wall plug. More details on how that is set up are available over in this post.
In my implementation I also use universal media player entries to integrate the above controls with squeezelite media player controls that support my full house audio/video system. For completeness I’ve include them below. The lines below are included in my configuration.yaml file under the “media_player:” line.
media_player:
- platform: universal
name: basement_mp_cmb
children:
- media_player.basement_snap
- binary_sensor.basement_amp_status
commands:
turn_on:
service: shell_command.pioneer_ctl
data:
cmd: LMS
turn_off:
service: shell_command.pioneer_ctl
data:
cmd: AMPPOWER
volume_up:
service: shell_command.pioneer_ctl
data:
cmd: UP
volume_down:
service: shell_command.pioneer_ctl
data:
cmd: DOWN
media_play_pause:
service: media_player.media_play_pause
target:
entity_id: media_player.basement_snap
attributes:
state: binary_sensor.basement_amp_status
media_content_id: media_player.basement_snap|media_content_id
media_title: media_player.basement_snap|media_title
entity_picture: media_player.basement_snap|entity_picture
- platform: universal
name: basement_mp_cmb
children:
- media_player.basement_snap
- binary_sensor.basement_amp_status
commands:
turn_on:
service: shell_command.pioneer_ctl
data:
cmd: LMS
turn_off:
service: shell_command.pioneer_ctl
data:
cmd: AMPPOWER
volume_up:
service: shell_command.pioneer_ctl
data:
cmd: UP
volume_down:
service: shell_command.pioneer_ctl
data:
cmd: DOWN
media_play_pause:
service: media_player.media_play_pause
target:
entity_id: media_player.basement_snap
attributes:
state: binary_sensor.basement_amp_status
media_content_id: media_player.basement_snap|media_content_id
media_title: media_player.basement_snap|media_title
entity_picture: media_player.basement_snap|entity_picture