Controlling Your TV From Software

I have kind of a couple goals with my home network.

  1. I want a better media server than my AppleTV – ideally, my Mac Mini server could serve that role.
  2. I want to be able to control my TV through chat-ops and other automation layers – turn the TV on to a particular input with a command, see if it’s on/off with another, turn it off with yet another.
    • Did I leave the TV on when I left for work? Turn it off.
    • Did something catastrophic happen on my RAID array? Interrupt my TV watching and switch to the server’s display to show me what’s going on.
  3. I want to unify how I control things so I can control everything from a remote control, or my phone, or my laptop.

So. Can I achieve these things, and how?

tl;dr: CEC may be a viable option, but it depends greatly on the TV / audio system, and the hardware options around talking over HDMI can be clunky – prepare to put some work into this route.

Controlling your TV through IR sucks. It’s flaky, it’s slow, and it’s one-way so your code can’t tell if the TV is on/off, whether a command worked or what state things are in. Not ideal for any sort of home automation where state discovery is rather important.

There’s got to be a better way, right? There is! Sort of.

Consumer Electronics Control

It’s called CEC, and it’s a side-channel protocol over HDMI. It’s meant to allow various home entertainment devices to discover and control one another.

It lets you do things like have your TV control the volume of your external speakers, or have your PS4 switch the TV’s input to whichever one the PS4 is connected to when you power it up.

There are a few gotchas to it though:

  1. It’s off by default on most TVs because as it turns out it isn’t that well thought out, and aggressive devices can frustrate and annoy the user.
  2. It goes by various brand names, so it’s rarely actually called “CEC”.
  3. TVs vary in what they implement. A lot.
  4. Available commands are depressingly limited, and prepare for a deep dive on how the CEC “model” works because it’s fairly complex.
  5. Oh yeah: The GPU in your PC/Mac/whatever doesn’t support it. At all.

Well fuck. A bit of a dead end, right?

Nope! The GPU on the Raspberry Pi series apparently does support it. Here’s some info on how to enable it and what your TV’s manufacturer calls it. Also, there’s a USB-CEC adapter from Pulse-Eight.

I picked up one of these to use with an Mac Mini running OS X Server, connected to a Vizio E420i-B0.

After you get things plugged in, you need libcec. On OS X, with Homebrew:

brew install libcec
cec-client --list-devices # Should list your Pulse-Eight device, and the device node associated with it.

Controlling things through libcec is rather annoying and required some digging. Unfortunately, as it turns out, the Vizio I have doesn’t accept things like volume commands (it can control the volume for other devices, but not be controlled by other devices) no matter how it’s configured.

The useful commands I’ve discovered to date are:

# Scan the CEC network to discover available devices:
echo 'scan' | cec-client -s

# Get vendor ID:
echo 'tx 10:8C' | cec-client -s
# Reports 0x87 for Vizio, but every time I see vendor ID elsewhere it's a larger number -- and I've seen Vizio reported as 0x6B746D in some contexts.  So... yeah.

# Switch the power to the TV on:
echo 'on 0' | cec-client -s
# Put the TV in standby mode:
echo 'standby 0' | cec-client -s
# Power off the TV (likely equivalent to standby on any modern TV):
echo 'tx 10:44:6c' | cec-client -s

# Switch the inputs:
# Switches to HDMI 1 (my Mac Mini server):
echo 'tx 1F:82:10:00' | cec-client -s
# Switches to JDMI 2 (my AppleTV):
echo 'tx 1F:82:20:00' | cec-client -s
# Switches to HDMI 3 (not hooked up at the moment):
echo 'tx 1F:82:30:00' | cec-client -s
# Etc...

First, note the apparent absence of any way to switch to a non-HDMI input. The reason for that is that the last two bytes represent an address (route?) on the CEC network. The TV itself shows up as 0.0.0.0 (represented as 00:00), whatever’s attached to its HDMI 1 input gets the address 1.0.0.0, and so on. How do you route to something that isn’t on the network? shrug

So. If you have non-HDMI inputs, oh well! (There is functionality related specifically to controlling a tuner but on my Vizio it’s… unsupported!)

Also note that first byte – 1F, or 10. This is important. The first nibble specifies the source of the message. I suspect the USB-CEC will have a different location depending on which HDMI port on the TV it’s plugged into, although I haven’t tested this. If you get this wrong, then figuring out what commands your TV supports will be rather difficult because it will be sending its responses to the wrong destination!

To determine what that first nibble should be for you, you’ll want to look at the debug messages libcec gives you. During initialization you’ll see something like this:

DEBUG:   [          144198] << Recorder 1 (1) -> Broadcast (F): vendor id Pulse Eight (1582)

The number in the first set of parens is basically the value to use for this first nibble.

The second nibble is the destination of the message. 0 always means the TV, and F means broadcast. Note that I’m sending a broadcast message to get the TV to switch inputs. Yeah, I don’t quite get that but addressing the TV directly didn’t work. Perhaps I’m misunderstanding how CEC is meant to work, or perhaps the Vizio isn’t implementing things properly – but either way, this is what it took to get things working – for me.

Courtesy of CEC-o-matic I’ve also found these commands but as noted they largely aren’t supported on my Vizio:

# Get mute status/volume, but unsupported on my Vizio:
echo 'tx 10:7D' | cec-client -s

# Volume-up pressed, but unsupported on my Vizio:
echo 'tx 10:44:41' | cec-client -s

# Volume-down pressed, but unsupported on my Vizio:
echo 'tx 10:44:42' | cec-client -s

# Last pressed button released -- oddly, this seems to be supported on my Vizio:
echo 'tx 10:45' | cec-client -s

An interesting thing to note is that when OS X is plugged into a TV it will happily set the audio out to the TV’s speakers – and disable your ability to control the volume. So Mac + Vizio TV == bust out the Apple Remote for the Mac, bust out the TV remote for volume. UGH!

So much for the Mac Mini as a media server – at least with this TV and no external speakers!

Other Gotchas

Another thing I’ve found is the Pulse-Eight’s hardware is rather flaky. I’ve found it critical to periodically send an inactive command to it if you force it into active mode (which forces the TV to switch to the input it’s attached to) otherwise eventually it gets stuck and can’t come out of active mode until it’s unplugged from the USB and plugged back in. Also, flashing the firmware must be done from Windows, apparently. And given that it’s quite slow to set up a connection and run a command you really want to run it via some daemon process and send commands to the daemon to forward to it.

Comments