Detecting Roku remote volume change

I am looking to setup a TV with a Roku, and backhauling the audio to a receive in another location.

Therefore, the user needs to be able to trigger volume up and down. Ideally they would be using the Roku remote.

Any suggestions for detecting volume up / down from the Roku remote. A bit logically, the Roku integration does not fire an event on volume up / down.

A couple thoughts:

  1. IR to IP receiver (boring)
  2. Select a TV that will report volume events. However, this may be difficult if the TV audio is set to external (the TV generally doesn’t maintain a volume state).
  3. Somehow intercept the HDMI CEC volume commands and send over IP. I haven’t found a solution for this.
  4. Use a universal remote with wifi that can talk to Home Assistant. It appears there aren’t great solutions out there for this.

Ideas?

Hello!

I ran across your post here and was wondering if you found a solution to this problem. I’m running into the same situation and am looking for options.

I did!

I set the Roku to control volume via HDMI-CEC. I think connected an ESP to the TV and catch the volume commands. It works well.

Let me know and I can post more details.

Yes, if you don’t mind. I found a clear explanation about enabling the HDMI-CEC on the Roku. I’m not familiar with ESP devices but I have read about the ESP home integration. If you can post your solution to that I would be immensely grateful. I don’t have and ESP devices but if this fixes the volume control that would be AMAZING.

I just installed ceiling speakers and my amplifier is home assistant friendly. The only problem is volume control and I don’t want to have to use my phone to control volume.

My documentation is poor, but I’ll get you started.

Hardware

ESP32-POE-ISO: ESP32-POE-ISO - Open Source Hardware Board
HDMI: https://www.amazon.com/dp/B0CB33FGG2?psc=1&ref=ppx_yo2ov_dt_b_product_details

GPIO of your choice (GPIO05) to HDMI pin 13
GND from the ESP board to HDMI pin 17 (DDC/CEC Ground)
5V from the ESP board to HDMI pin 18 (typically how the HDMI device knows something is connected)

ESP Yaml

substitutions:
  name: "volumepoe"
  friendly_name: volumepoe

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: esphome.web
    version: '1.0'

esp32:
  board: esp32dev
  framework:
    type: arduino


# Enable logging
logger:

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO17_OUT
  phy_addr: 0
  power_pin: GPIO12

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true


## THIS IS THE IMPORTANT STUFF.  USE TEMPLATE ABOVE

external_components:
  - source: github://Palakis/esphome-hdmi-cec

hdmi_cec:
  # Pick a GPIO pin that can do both input AND output
  pin: GPIO05 # Required
  # The address can be anything you want. Use 0xF if you only want to listen to the bus and not act like a standard device
  address: 0x05 # Required
  # Physical address of the device. In this case: 4.0.0.0 (HDMI4 on the TV)
  # DDC support is not yet implemented, so you'll have to set this manually.
  physical_address: 0x4000 # Required
  # The name that will we displayed in the list of devices on your TV/receiver
  osd_name: "my device" # Optional. Defaults to "esphome"
  # By default, promiscuous mode is disabled, so the component only handles directly-address messages (matching
  # the address configured above) and broadcast messages. Enabling promiscuous mode will make the component
  # listen for all messages (both in logs and the on_message triggers)
  promiscuous_mode: true # Optional. Defaults to false
  # By default, monitor mode is disabled, so the component can send messages and acknowledge incoming messages.
  # Enabling monitor mode lets the component act as a passive listener, disabling active manipulation of the CEC bus.
  monitor_mode: true # Optional. Defaults to false
  # List of triggers to handle specific commands. Each trigger has the following optional filter parameters:
  # - "source": match messages coming from the specified address
  # - "destination": match messages meant for the specified address
  # - "opcode": match messages bearing the specified opcode
  # - "data": exact-match on message content
  # Actions called from these triggers is called with "source", "destination" and "data" as parameters
  on_message:
    # - opcode: 0xC3 # Request ARC start
    #   then:
    #     - hdmi_cec.send: # Report ARC started
    #         destination: 0x0
    #         data: [ 0xC1 ]
    # - opcode: 0x71 # Give audio status
    #   source: 0x0 # From the TV
    #   then:
    #     - hdmi_cec.send:
    #         destination: 0x0
    #         data: [ 0x7A, 0x7F ]
    # - opcode: 0x46 # Give OSD name
    #   then:
    #     - hdmi_cec.send:
    #         destination: 0x0
    #         data: [0x47, 0x65, 0x73, 0x70, 0x68, 0x6F, 0x6D, 0x65]
    - data: [0x44, 0x41] # User control pressed: volume up
      then:
        - logger.log: "Volume up"
        - homeassistant.service:
            service: media_player.volume_up
            data:
              entity_id: media_player.family_room
        - homeassistant.service:
            service: media_player.volume_mute
            data:
              entity_id: media_player.family_room
              is_volume_muted: "False"
    - data: [0x44, 0x42] # User control pressed: volume up
      then:
        - logger.log: "Volume Down"
        - homeassistant.service:
            service: media_player.volume_down
            data:
              entity_id: media_player.family_room
    - data: [0x44, 0x43] # User control pressed: volume up
      then:
        - logger.log: "Volume Mute"
        - homeassistant.service:
            service: media_player.volume_mute
            data:
              entity_id: media_player.family_room
              is_volume_muted: "True"
    # - opcode: 0x04 # Roku requesting TV On
    #   then:
    #     - logger.log: "TV On"
    #     - homeassistant.service:
    #         service: light.turn_on
    #         data:
    #           entity_id: light.kitchen_breakfast

    # - opcode: 0x36 # Roku requesting TV Off
    #   then:
    #     - logger.log: "TV Off"
    #     - homeassistant.service:
    #         service: light.turn_off
    #         data:
    #           entity_id: light.kitchen_breakfast

This is for PoE. You can change it if you’d like for wireless.

I also 3D printed a case I can share if you’d like.