Raspberry Pi as HDMI-CEC to IR Bridge

The Problem:

I recently got an IP-TV subscription and the set-top box delivered by my provider does not support HDMI-CEC. The remote control included in the package has limited “smart” functionality – as in, it can be configured to somewhat control your TV – but that’s far from ideal, or functional if your set-up consists of anything more than a TV. In my case the set-up is not that much more complex. It consists of a TV, an AVR receiver and an OpenELEC media player. With the help of a Pulse Eight USB – CEC Adapter, I’ve been able to happily control them all from my TV remote over HDMI-CEC.

Too many remotes
Too many remotes

The new addition to the group was causing me a bunch of headaches. Not only did I need the set-top box remote to change channels, but I also needed the AVR remote to select the correct input. Using a second HDMI input on the TV could solve that problem but the ARC channel from the TV to the receiver is not pass-through. My Samsung TV seems to pre-process audio to either PCM or DTS NEO 2:5.

The Solution:

Well, the most obvious one would be a Logitech Harmony but where would the fun be in that? Nothing to break open, nothing to tinker with and I would have to keep the set-top box in a visible place to maintain line of sight. An Arduino came to mind (and I may still get back to this idea – more on that further down), but at the time is sounded like a lot of work.

Then I remembered that the Raspberry Pi HDMI port supports CEC and that while I was setting up the OpenELEC player, I had to set the correct address for the AVR to switch to the right input. That’s when the idea started becoming simple. Connect the Pi to a free HDMI port and make it pretend to be the set-top box. Listen to the CEC commands coming from the TV and translate them to IR commands using LIRC.

All ingredients were available. The good people of Pulse Eight have made libCEC available, including sample clients and there are a ton of tutorials out there on how to use LIRC to make an IR Remote out of a Raspberry Pi so I got to work.

The Hardware:

IR transmitter circuit
IR transmitter circuit

The transmitter circuit is straight forward. A couple of IR LEDs, a current limiting resistor and an NPN transistor that can be switched at 3.3V. The 2N2222 seems to be the transistor of choice in most circuits I came across but the BC338 that was in my box of loose components works nicely too. Any resistor below 500 Ohms, should be fine. It could even be skipped if the power supply is strong enough to prevent a voltage drop on the 5V line. Of course the range of the transmitter and the lifetime of the LEDs depend on that resistor so pick one that suits your needs. The capacitor is added as extra protection against that voltage drop and again, it can be omitted.

Samsung IR extender
Samsung IR extender

A piece of scrap strip-board and a quick soldering job and I had a prototype. I didn’t have any IR LEDs around but luckily enough my TV came with an “IR Extender” module which seems to contain two IR LEDs in a nicely polished package with a long wire and a 3.5mm jack.

IR transmitter board
IR transmitter board

If it’s not obvious from the picture, the resistor and capacitor were added after the initial “board design”. On the bench, the circuit worked without them but when I tried powering the Raspberry Pi from the USB port of the set-top box, the current wasn’t enough and the Pi kept rebooting every time I pressed a key on the remote.

The Software:


I started with a standard Raspbian Jessie installation, at least for the purposes of this story. I actually net-boot Raspbian from NFS using u-boot but the result is the same and that’s perhaps a story for another entry. The LIRC installation and configuration is extensively covered online so I will just highlight the key steps. Using a DIY transmitter means that the lirc-rpi driver was needed. This driver creates a virtual device using two of the GPIO pins on the Raspberry Pi. To enable that, I needed to load the lirc-rpi overlay at boot time by adding the following line in config.txt.


This defines the hardware for the kernel and allows the lirc-rpi driver to be loaded and the /dev/lirc0 device to be created. The LIRC installation is standard and there are plenty of places where “remote” definitions can be found for the lirc.conf file. I found one for my set-top box on a Gist from proycon. With the original remote and an IR receiver, such a configuration file can also be created with the irrecord utility which is part of the lirc package.

CEC Client:

At this point I had working hardware and LIRC configured. I was able to issue commands to the set-top box from the command line using the irsend utility and all I needed was to tap into the incoming HDMI-CEC messages and handle those. libCEC and the utilities that come with it are great. The cec-client made it simple to manually play around with CEC messages until things started making sense. With the help of cec-o-matic, I was then able to create a list of commands that I needed to send and consume in order to bring the TV, AVR and set-top box in sync.

Sample clients are also provided in different languages. Since I would be coding on the Raspberry Pi over SSH sessions, I opted for python. Now, python is by far not my strongest skill and I kind of got lost trying to get the python libraries working by using the libCEC packages. Something about Swig is mentioned but I didn’t know how exactly to get an API definition using that. I took the next easiest route, I cloned the libCEC repository, followed the instructions and built the library from source. The python client worked straight away.

There are a couple of things that needed doing to the client to get the functionality I wanted out of it. First of all, I wanted it to handle key pressed events so I went ahead and mapped the key pressed events in KeyPressCallback to irsend commands using a whole list of these:

if key == 0:
os.system("irsend SEND_ONCE VIP1853 OK")
elif key == 1:
os.system("irsend SEND_ONCE VIP1853 UP")

For  some reason that I didn’t really look into, that callback function handles both key-pressed and key-released events with only the duration to distinguish between them. On top of that, some keys only raise a single event that doesn’t start at duration 0. This may be something done by my TV, libCEC or the protocol itself. In any case, with these conditions at the top of the callback, I was able to filter the events I wanted to handle:

if duration == 0 or ((key == 150 or key == 145) and duration == 500) or (key == 69 and duration >= 1000):

The  next thing to do was to make the Raspberry Pi announce that it’s connected to the HDMI port that is used by the set-top box. This tricks the TV and the AVR to switch to the “correct” input when I select the Raspberry Pi from the TV’s CEC menu. For that, I needed to override the auto-detected physical address of the client at start-up in the SetConfiguration function:

self.cecconfig.iPhysicalAddress = 0x2200

0x2200 corresponds to the DVD input of my AVR (, where the set-top box is connected and that was it! The client did 99% of what I wanted so I went ahead and created a daemon start-up script for it so that it starts and runs in the background at boot time. The start-up script needs to be copied to /etc/init.d/ and scheduled by running:

sudo update-rc.d cecClient.sh defaults

The Result:

CEC device selection menu
CEC device selection menu

The remaining 1% consisted of three problems. First, the python client wouldn’t stay running because the main loop was expecting input from the console for its standard functionality. Since I’m running the client as a service, that wasn’t needed any more and commenting out that raw_input line solved the issue.

CEC Menu on the TV
CEC Menu on the TV

The second was that by default, libCEC tries to power up my AVR when I switch to the Raspberry Pi. For some reason, it’s unable to get the correct power status from the AVR and issues a POWER ON command which more often than not actually puts my AVR on stand-by. My TV also seems to have trouble handling the power status of the AVR correctly so I’m suspecting Pioneer’s CEC implementation may be the issue. I was able to clear the automatically-filled device list of wakeDevices but I guess I’m doing it at the wrong place because it still happens once after the client starts. Since I’m not rebooting the Pi, this is good enough for me.

Devices and cables hidden away
Devices and cables hidden away

The last problem was power control of the set-top box. Since communication is only possible in one direction, I can’t check if the set-top box is powered on or gone to stand-by mode. I can’t even check the status of the HDMI port for that because when on stand-by, the set-top box goes into screensaver mode and the HDMI port remains active. To work around this, I mapped the IR POWER button to the unused CEC Tools menu option and now power can be controlled by a couple of button presses.

TV furniture nice and tidy
TV furniture nice and tidy

With all that in place, I’m now again able to hide away all other remote controls and control everything from a single one. Without line-of-sight needed, the set-top box could also join the rest of the cable mess in the drawer under the TV and stay out of sight.

I have uploaded the LIRC configuration files as well as the modified python client and daemon script on Github for anyone interested in trying this out.

A warning for the adventurous: the GPIO port on the Raspberry Pi is as sensitive to over-voltage as everyone claims. Even a charged capacitor is enough to damage the BCM2708 SOC. I found out the hard way by plugging in the board on the Pi the wrong way around.

Further Ideas:

Inspired by the Pulse Eight USB – CEC Adapter, I am considering an Adruino based version of this, using the same pass-through trick so that an extra HDMI port isn’t occupied. With a custom Arduino implementation, such a device could be used to emulate and control multiple non-CEC capable devices, be instantly on and consume around 50mA instead of the Pi’s 500(ish). Libraries are already available for both CEC and IR, there are examples of the circuit needed to interface with CEC and a prototype interface can be done using half of a standard HDMI cable. The discouraging part is having to do programming and debugging in front of the TV. It may be an idea for a Kickstarter project or for Pulse Eight to introduce as a new product.



Leave a Reply