I recently got a TCL 635 Roku TV. One annoyance with it is that the RF remote’s volume buttons are useless unless you are using the TV speakers OR using a receiver/soundbar with HDMI ARC. My soundbar is connected over optical, and I’m not eager to replace it, so I started investigating how to intercept HDMI CEC volume commands to send to my ESPHome IR blaster. One glitch I ran into is that my TV seems to not send CEC volume commands unless you pretend to be an HDMI ARC device. I was able to figure out how to advertise as an ARC audio device, after which my TV would send the proper CEC volume commands. I’m adding my notes here in case it’s helpful to others!
- Raspberry Pi + SD card + power supply (I use a Pi Zero W)
- HDMI cable with CEC pin (some cheaper HDMI cables don’t support CEC)
First flash the Pi with Raspbian Lite and set up wifi and ssh. Then ssh to the pi. The following commands should be run via ssh on the pi.
Get some dependencies
sudo apt-get install libcec-dev build-essential python3-dev python3-pip git
Now clone the python-cec library
git clone https://github.com/trainman419/python-cec.git
Now you need to make a change to python-cec to pretend to be an audio device. (I hope to make a permanent change to
python-cec so this won’t be necessary in the future.)
cd python-cec nano cec.cpp
And make the following change from
- CEC_config->deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); + CEC_config->deviceTypes.Add(CEC_DEVICE_TYPE_AUDIO_SYSTEM);
Now build and install
sudo python3 setup.py install
Also grab the Home Assistant API Python package
pip3 install HomeAssistant-API
Now create a new script
And paste my script. You’ll need to change your HA URL, add an access token, and almost certainly change the service call to match the device / method for changing volume in your system. In my case I have an ESPHome IR blaster, that issues commands as part of a universal media_player, in order to control my Vizio soundbar. Modify the contents of the
volume_mute methods based on how your HA config changes volume. You could also just put an IR LED on the Pi itself if you didn’t already have an IR blaster.
import time import cec from homeassistant_api import Client client = Client( # Replace with your Home Assistant domain 'http://10.0.0.42:8123/api/', # Create a long-lived access token from your profile page in HA 'YOURLONGLIVEDACCESSTOKENHERE' ) # Replace as necessary with your own HA entity and services for volume entity_id='media_player.soundbar' def volume_up(): client.get_domains().media_player.services['volume_up'].trigger(entity_id=entity_id) def volume_down(): client.get_domains().media_player.services['volume_down'].trigger(entity_id=entity_id) def volume_mute(): client.get_domains().media_player.services['volume_mute'].trigger(entity_id=entity_id, is_volume_muted=True) def callback(event, *argv): if event == cec.EVENT_COMMAND: command = argv print("command", command) if command['opcode'] == cec.CEC_OPCODE_REQUEST_ARC_START: print("Reporting ARC started") cec.transmit(cec.CECDEVICE_TV, cec.CEC_OPCODE_REPORT_ARC_STARTED, '', cec.CECDEVICE_AUDIOSYSTEM) elif event == cec.EVENT_KEYPRESS: code, duration = argv print("keypress", code, duration) if code == 65 and duration == 0: volume_up() print("volume up") elif code == 66 and duration == 0: volume_down() print("volume down") elif code == 67 and duration == 0: volume_mute() print("mute") else: print("event", event, argv) cec.add_callback(callback, cec.EVENT_ALL & ~cec.EVENT_LOG) cec.init() # Sleep forever (CEC stuff will run in the background) while True: time.sleep(100)
Now try running it to see if it works. If it doesn’t try unplugging/replugging your HDMI, turning things off/on, and other HDMI cables (not all cables support CEC)
(use CTRL-C to exit)
You can also create a systemd unit file so this starts automatically when the pi starts
sudo nano /etc/systemd/system/cecwatcher.service
And paste in the following
# systemd unit file for cecwatcher.py [Unit] Description=CEC Watcher [Service] ExecStart=/usr/bin/python3 /home/pi/cecwatcher.py # Disable Python's buffering of STDOUT and STDERR, so that output from the # service shows up immediately in systemd's logs Environment=PYTHONUNBUFFERED=1 User=pi [Install] # Tell systemd to automatically start this service when the system boots # (assuming the service is enabled) WantedBy=default.target
Now start it up and try it out (you might need to turn
sudo systemctl start cecwatcher sudo systemctl status cecwatcher
And set it up to start on boot
sudo systemctl enable cecwatcher
Now you should have a thing that can receive and intercept CEC volume commands and relay them to HA to control another device! Let me know if you try this and if it works!