Contents

How to Automate Your Home Theater with Home Assistant and HDMI-CEC

You can use HDMI-CEC commands through Home Assistant ’s HDMI-CEC integration - or a CEC-capable device like a Raspberry Pi running cec-client - to control TV power, input switching, and volume from automations and dashboards. Instead of juggling three or four remotes, you wire up a “Movie Mode” automation that dims the lights, powers on the TV, switches to the correct HDMI input, and sets volume to a comfortable level. One tap. Done.

This guide covers how CEC works at the wire level, how to set it up with a Raspberry Pi or USB adapter, how to integrate it into Home Assistant, and how to build practical scene automations that turn your phone or a wall tablet into the only remote you need.

How HDMI-CEC Works and What It Can Control

HDMI-CEC (Consumer Electronics Control) is a one-wire protocol running over HDMI pin 13. It lets connected devices send commands to each other without any network configuration or pairing - if two devices are connected by an HDMI cable, they can talk over CEC.

Every device on the CEC bus has a logical address. The TV is always address 0. A recording device gets address 1. Playback devices (Blu-ray players, streaming sticks) get address 4. An audio system (AVR or soundbar) gets address 5. CEC commands travel between these addresses over the shared bus, and any device can send a message to any other device.

What CEC can reliably do:

  • Turn any device on (on 0) or off (standby 0)
  • Tell the TV to switch to a specific HDMI port (e.g., tx 1F:82:20:00 for HDMI2)
  • Send volume up, volume down, and mute commands to the TV or an AVR
  • Query which device currently has the TV’s attention (active source)
  • Play, pause, stop, fast-forward, rewind on compatible devices
  • Send directional pad and select commands (Up, Down, Left, Right, OK)

What CEC typically cannot do:

  • Launch specific apps on a smart TV (no Netflix button via CEC)
  • Change TV picture settings like brightness, contrast, or picture mode
  • Work reliably on every device - many budget TVs and soundbars have incomplete CEC implementations

The branding confusion does not help. Samsung calls it “Anynet+”. LG calls it “SimpLink”. Sony calls it “BRAVIA Sync”. Roku calls it “1-Touch Play”. These are all the same underlying CEC protocol with varying levels of implementation quality. A TV that advertises “SimpLink” or “Anynet+” supports CEC, but that does not guarantee it handles every CEC opcode correctly.

One critical limitation: CEC is a shared bus. Only one controller should send commands at a time. If your Roku stick, your TV’s built-in CEC handler, and Home Assistant all try to send CEC commands simultaneously, you get conflicts - dropped commands, unexpected input switches, or devices turning on when you wanted them off. The fix is disabling CEC on devices you do not want acting as controllers (most TVs have a setting for this), and letting Home Assistant be the single point of control.

CEC Opcode Quick Reference

The most commonly used CEC commands for home automation:

Actioncec-client CommandRaw CEC FrameNotes
TV Onon 0tx 10:04Image View On to address 0
TV Offstandby 0tx 10:36Standby to address 0
Query Powerpow 0tx 10:8FReturns power status
Switch to HDMI1-tx 1F:82:10:00Active Source, physical 1.0.0.0
Switch to HDMI2-tx 1F:82:20:00Active Source, physical 2.0.0.0
Switch to HDMI3-tx 1F:82:30:00Active Source, physical 3.0.0.0
Switch to HDMI4-tx 1F:82:40:00Active Source, physical 4.0.0.0
Volume Upvoluptx 15:44:41User Control Pressed
Volume Downvoldowntx 15:44:42User Control Pressed
Mute Togglemutetx 15:44:43User Control Pressed
D-Pad Up-tx 1F:44:01User Control Pressed
D-Pad Down-tx 1F:44:02User Control Pressed
D-Pad Left-tx 1F:44:03User Control Pressed
D-Pad Right-tx 1F:44:04User Control Pressed
Select/OK-tx 1F:44:00User Control Pressed

In the raw frames, the first nibble is the source address and the second nibble is the destination. 1F means “from Playback Device 1 (address 1) to Broadcast (address F)”. The opcode 82 is Active Source, 44 is User Control Pressed, 36 is Standby, and 04 is Image View On.

Setting Up CEC Control with a Raspberry Pi and cec-client

Home Assistant needs a CEC adapter to send commands over HDMI. If you are running Home Assistant OS on a Raspberry Pi 4 or 5, the built-in HDMI port is already a CEC adapter. Connect an HDMI cable from the Pi to your TV or AVR and you are ready to go. No extra hardware required.

If Home Assistant runs on a virtual machine, an Intel NUC, or any other x86 server, you need a Pulse-Eight USB-CEC adapter . It connects via USB and presents as /dev/ttyACM0. The adapter is supported by the libcec library, which is what both cec-client and the Home Assistant HDMI-CEC integration use under the hood.

Pulse-Eight USB-CEC adapter with HDMI passthrough connector
The Pulse-Eight USB-CEC adapter plugs into any HDMI port and provides CEC control via USB
Image: Pulse-Eight

Another option for non-Pi setups is the SMLIGHT SLWF-08 , an ESP8266-based HDMI-CEC controller that works over WiFi with ESPHome firmware and also supports addressable LED strip control for ambient backlighting.

SMLIGHT SLWF-08 HDMI-CEC controller board
The SLWF-08 connects to any HDMI port and communicates with Home Assistant over WiFi via ESPHome
Image: SMLIGHT

Installing cec-client

On Home Assistant OS, cec-client is pre-installed. On a Debian or Ubuntu system, install it with:

sudo apt install cec-utils

This gives you the cec-client command-line tool, which is the fastest way to test CEC before configuring anything in Home Assistant.

Testing CEC Connectivity

Scan for all devices on the HDMI-CEC bus:

echo 'scan' | cec-client -s -d 1

The -s flag runs a single command (instead of starting interactive mode), and -d 1 sets the debug level low to suppress noise. You should see output listing your TV at address 0, any AVR at address 5, and connected playback devices at their respective addresses.

Test power control:

# Turn TV on
echo 'on 0' | cec-client -s -d 1

# Turn TV off
echo 'standby 0' | cec-client -s -d 1

# Query TV power status
echo 'pow 0' | cec-client -s -d 1

Test input switching:

# Switch TV to HDMI2
echo 'tx 1F:82:20:00' | cec-client -s -d 1

If your Raspberry Pi connects to the TV through an AVR, CEC commands still traverse the HDMI chain - but some AVRs strip CEC signals. Test before committing to a particular cable layout. If commands do not reach the TV through the AVR, try connecting the Pi directly to the TV on a separate HDMI port.

Configuring the Home Assistant HDMI-CEC Integration

With CEC hardware confirmed working from the command line, you can integrate it into Home Assistant. There are two approaches, and picking the right one depends on your TV’s CEC behavior.

Approach 1: The Built-in HDMI-CEC Integration

Add the HDMI-CEC integration to your configuration.yaml:

hdmi_cec:
  devices:
    0: "Living Room TV"
    5: "Soundbar"
    4: "Roku"

This creates media_player entities for each discovered CEC device. You get power on/off, volume up/down/mute, and source selection as standard media player controls. These entities work with dashboards, automations, and voice assistants out of the box.

Before configuring the integration, use the CEC Scanner add-on (available in the Home Assistant add-on store) to detect connected devices and their addresses. Do not leave the CEC Scanner set to start on boot - it will interfere with the integration.

If you see a “failed to open vchiq instance” error in the logs, add the Home Assistant user to the video group:

sudo usermod -a -G video homeassistant

Approach 2: Shell Commands for Direct CEC Control

The shell_command approach gives you direct control over the exact CEC opcodes being sent. This is more reliable than the integration for TVs with quirky CEC implementations, because the integration’s abstraction layer sometimes sends extra commands that confuse certain TVs.

shell_command:
  tv_on: 'echo "on 0" | cec-client -s -d 1'
  tv_off: 'echo "standby 0" | cec-client -s -d 1'
  tv_hdmi1: 'echo "tx 1F:82:10:00" | cec-client -s -d 1'
  tv_hdmi2: 'echo "tx 1F:82:20:00" | cec-client -s -d 1'
  tv_hdmi3: 'echo "tx 1F:82:30:00" | cec-client -s -d 1'
  tv_hdmi4: 'echo "tx 1F:82:40:00" | cec-client -s -d 1'
  tv_volume_up: 'echo "volup" | cec-client -s -d 1'
  tv_volume_down: 'echo "voldown" | cec-client -s -d 1'
  tv_mute: 'echo "mute" | cec-client -s -d 1'

You can then expose these as button entities using template buttons for clean dashboard integration:

button:
  - platform: template
    buttons:
      tv_power_on:
        friendly_name: "TV On"
        press:
          - service: shell_command.tv_on
      tv_power_off:
        friendly_name: "TV Off"
        press:
          - service: shell_command.tv_off
      tv_input_roku:
        friendly_name: "Roku (HDMI2)"
        press:
          - service: shell_command.tv_hdmi2

Which Approach to Choose

Use the built-in integration if your TV responds well to standard CEC and you want media player entities with full state tracking. Use shell commands if your TV has spotty CEC support, if you need precise control over timing, or if the integration sends unwanted extra commands. Many people end up using a hybrid - the integration for state tracking and shell commands for reliable command execution.

Building Automated Scenes - Movie Mode, Gaming Mode, Music Mode

Individual CEC commands are useful on their own, but they get much more interesting when combined into scenes that configure your entire system with one trigger.

Movie Mode

Triggered by a dashboard button, an NFC tag on the coffee table, or a voice command:

automation:
  - alias: "Movie Mode"
    trigger:
      - platform: state
        entity_id: input_select.theater_mode
        to: "Movie"
    action:
      - service: shell_command.tv_on
      - delay: "00:00:03"
      - service: shell_command.tv_hdmi2
      - delay: "00:00:01"
      - service: media_player.volume_set
        target:
          entity_id: media_player.soundbar
        data:
          volume_level: 0.25
      - service: light.turn_off
        target:
          entity_id: light.living_room
      - service: light.turn_on
        target:
          entity_id: light.tv_bias_light
        data:
          brightness_pct: 20
          color_temp: 370

The 3-second delay after tv_on is important. TVs need boot time before they accept input-switching commands. Sending tv_hdmi2 immediately after tv_on will get ignored by most TVs. Adjust the delay based on how fast your specific TV boots - some need 5 seconds, others are fine with 2.

Gaming Mode

Triggered automatically when a gaming console powers on, detected via a smart plug monitoring power draw (a PS5 at idle draws about 50W versus 1-2W in standby):

automation:
  - alias: "Gaming Mode"
    trigger:
      - platform: numeric_state
        entity_id: sensor.ps5_plug_power
        above: 40
    action:
      - service: shell_command.tv_on
      - delay: "00:00:03"
      - service: shell_command.tv_hdmi3
      - delay: "00:00:01"
      - service: light.turn_on
        target:
          entity_id: light.tv_bias_light
        data:
          brightness_pct: 100
          rgb_color: [0, 0, 255]
      - service: input_select.select_option
        target:
          entity_id: input_select.theater_mode
        data:
          option: "Gaming"

Note that CEC cannot switch TV picture mode to “Game” mode. If that matters for input lag, you can pair CEC with a Broadlink RM4 Pro IR blaster to send the IR command for picture mode changes, or set your TV to auto-detect game mode per HDMI input (most modern TVs support this via ALLM - Auto Low Latency Mode).

Music Mode

Triggered from a dashboard or voice assistant - turns on audio without powering the TV:

automation:
  - alias: "Music Mode"
    trigger:
      - platform: state
        entity_id: input_select.theater_mode
        to: "Music"
    action:
      - service: media_player.turn_on
        target:
          entity_id: media_player.soundbar
      - delay: "00:00:02"
      - service: media_player.volume_set
        target:
          entity_id: media_player.soundbar
        data:
          volume_level: 0.15
      - service: media_player.play_media
        target:
          entity_id: media_player.spotify
        data:
          media_content_id: "spotify:playlist:37i9dQZF1DX0XUsuxWHRQd"
          media_content_type: "playlist"

All Off

Every theater setup needs a kill switch:

automation:
  - alias: "All Off"
    trigger:
      - platform: state
        entity_id: input_select.theater_mode
        to: "Off"
    action:
      - service: shell_command.tv_off
      - service: media_player.turn_off
        target:
          entity_id: media_player.soundbar
      - service: media_player.media_stop
        target:
          entity_id: media_player.spotify
      - service: light.turn_on
        target:
          entity_id: light.living_room
        data:
          brightness_pct: 60
          color_temp: 370

Tracking Mode with input_select

Use an input_select helper to track the current theater mode and prevent conflicts:

input_select:
  theater_mode:
    name: "Theater Mode"
    options:
      - "Off"
      - "Movie"
      - "Gaming"
      - "Music"
    initial: "Off"

All automations trigger off this helper, and each automation sets it when activated by other means (like the Gaming Mode auto-detection). This prevents Movie Mode from firing while Gaming Mode is active.

Dashboard Design for a Remote Control Replacement

Once the automations work, the next step is a Lovelace dashboard that replaces the stack of remotes on your coffee table. A well-built dashboard does everything a remote does, plus things no remote can - like showing device state at a glance and triggering multi-device scenes.

Create a dedicated “Remote” dashboard view with a dark background. The layout should feel familiar to anyone who has held a remote control:

Put power buttons for each device across the top - TV, soundbar, streaming box. Use media_player.toggle service calls with conditional coloring: green when the device is on, red when off. This gives instant visual feedback that no physical remote can match.

In the center, arrange five button cards in a cross to form a directional pad. Map them to CEC navigation commands - Up (tx 1F:44:01), Down (tx 1F:44:02), Left (tx 1F:44:03), Right (tx 1F:44:04), and Select (tx 1F:44:00). This lets you navigate smart TV menus without reaching for the TV remote.

For volume, either use a slider card mapped to a template number entity that sends volup/voldown based on value changes, or simple +/- button cards. The buttons tend to be more reliable since CEC volume is step-based, not absolute.

Below that, add a row of colored buttons for each HDMI input - HDMI1 (Cable), HDMI2 (Roku), HDMI3 (PS5), HDMI4 (PC). Use icons matching each device so you can tap the right input without reading labels.

At the bottom, put large scene buttons for Movie Mode, Gaming Mode, Music Mode, and All Off. These set input_select.theater_mode, which triggers the automations you built earlier.

Home Assistant Lovelace dashboard configured as a home theater remote control
A community example of a Lovelace dashboard with input selection buttons and power state indicators
Image: Home Assistant Community

For a dedicated wall-mounted control surface, a Fire HD 8 tablet running Fully Kiosk Browser works well. Mount it near the couch, keep it always on with the screen dimmed, and set it to wake on motion. The browser_mod integration (available through HACS) lets you pop up the remote dashboard as an overlay from any view.

Troubleshooting Common CEC Problems

CEC is reliable once working, but the initial setup can involve some debugging. These are the problems you are most likely to run into.

TV ignores commands after power on: Many TVs need 3-5 seconds after receiving on 0 before they process other commands. Add delay actions in your automations.

Commands work from cec-client but not from Home Assistant: Check that the Home Assistant user has permissions to access the CEC device (/dev/ttyACM0 or /dev/vchiq). Add the user to the video group. On Home Assistant OS, this is handled automatically.

Devices turn on unexpectedly: Another device on the CEC bus (often a streaming stick or game console) is sending “Active Source” messages when it wakes. Disable CEC on that device, or configure your TV to ignore CEC wake commands from specific ports.

Input switching does not work: The physical address in the CEC frame must match the actual HDMI port. If HDMI2 is physically wired to port 3 on your AVR, you may need tx 1F:82:30:00 instead of tx 1F:82:20:00. Use echo 'scan' | cec-client -s -d 1 to discover the actual physical addresses.

Samsung TVs (pre-2023 models): Known for aggressive CEC behavior - they often turn on when any connected device sends a CEC message. Disable “Anynet+” in the TV settings and control the TV exclusively through CEC commands sent by Home Assistant, so the TV is a passive receiver rather than an active CEC participant.

AVR strips CEC signals: Some AVRs do not pass CEC commands through. Connect the Raspberry Pi or CEC adapter directly to the TV on a separate HDMI port. You can still control the AVR separately via CEC on that port if the AVR has its own CEC address visible on the bus.

Multiple CEC controllers: If the Roku, PlayStation, and Home Assistant are all trying to control the TV over CEC, commands collide. Pick one controller (Home Assistant) and disable CEC output on the others. Most streaming devices have this setting buried in their system preferences.

Integrating with Voice Assistants

The theater mode input_select makes voice control easy to add. If you use the Home Assistant Cloud (Nabu Casa) service or a local voice assistant setup, the input_select.theater_mode entity gets automatically exposed to Google Home or Alexa.

Saying “Hey Google, set Theater Mode to Movie” or “Alexa, set Theater Mode to Movie” triggers the same automation chain as tapping the dashboard button. For more natural phrasing, create script entities with voice-friendly names:

script:
  movie_time:
    alias: "Movie Time"
    sequence:
      - service: input_select.select_option
        target:
          entity_id: input_select.theater_mode
        data:
          option: "Movie"

Expose this script to your voice assistant, and “Hey Google, activate Movie Time” powers up your entire theater.

Beyond CEC - Wake-on-LAN and IR Fallback

CEC handles most home theater control needs, but there are gaps. Two other technologies cover what CEC cannot.

Wake-on-LAN handles devices that need a network wake before CEC can reach them - an HTPC or NVIDIA Shield that goes into deep sleep, for example. Add a WoL step at the beginning of your automation, followed by a longer delay (10-15 seconds) for the device to fully boot, before sending CEC input switch commands.

IR blasters like the Broadlink RM4 Pro handle the commands CEC cannot send - picture mode changes, TV app launches, or controlling devices with no CEC support at all. The Broadlink integration in Home Assistant lets you learn IR codes from existing remotes and replay them from automations.

With CEC for power/input/volume, WoL for deep-sleeping devices, and IR for the rest, Home Assistant becomes the single control point for your entire setup. The pile of remotes on the coffee table can go in a drawer.