HDMI-CEC ESPHome Component
An ESPHome component that supports receiving and transmitting HDMI-CEC messages to connected HDMI devices. The ultimate goal of this project is to eventually be merged into the core ESPHome project once it’s up to quality. It’s currently out for review as esphome/esphome#3017 (see also: esphome/esphome-docs#1789).
My use case: I already have an IR blaster built with ESPHome, but my new TCL TV has a Bluetooth remote. I want to control my older sound gear (connected over optical) with the TV remote but this TV only supports controlling sound gear via HDMI (typically for HDMI-ARC devices). This component allows me to intercept HDMI-CEC volume commands and transmit the IR codes to control the soundbar. It also allows my Apple TV to control the soundbar (so I can control volume from the Remote app on my phone ?). Theoretically it should allow you to make any older non-HDMI equipment work seamlessly with newer gear. You can also do things like monitor which source is selected, which HDMI devices are powered on, etc.
Since HDMI-CEC is a 3.3V protocol, no external components are needed. A HDMI breakout connector might be handy though. Just connect:
- GPIO of your choice to HDMI pin 13 (the examples use GPIO4)
- GND from the ESP board to HDMI pin 17 (DDC/CEC Ground)
- 5V from the ESP board to HDMI pin 18 (typtically how the HDMI device knows something is connected)
For now on the ESP8266, until the HDMI driver is rewritten to fully use interrupts, it’s a good idea to bump the CPU speed to 160MHz:
esphome: ... platformio_options: board_build.f_cpu: 160000000L
Add this repo to your external components
external_components: - source: github://johnboiles/esphome-hdmi-cec
hdmi_cec component. The
on_message triggers will only fire if any specified
data match. Something like this (the below is abbreviated — see full example here).
hdmi_cec: # The initial logical address -- corresponds to device type. This may be # reassigned if there are other devices of the same type on the CEC bus. address: 0x05 # Audio system # Promiscuous mode can be enabled to allow receiving messages not intended for us promiscuous_mode: false # Typically the physical address is discovered based on the point-to-point # topology of the HDMI connections using the DDC line. We don't have access # to that so we just hardcode a physical address. physical_address: 0x4000 pin: 4 # GPIO4 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"
You can use the
hmdi_cec.send action from anywhere you can use actions.
button: - platform: template name: TV Turn On on_press: - hdmi_cec.send: source: 0x05 # Optional since we'll default to `hdmi_cec.address` destination: 0x0 data: [ 0x04 ]
- The timing for receiving and parsing CEC messages depends on the timing with which
loopis called and thus this is sensitive to other things the microcontroller are doing that may delay the
loopmethod getting called. I’ve implemented pin change interrupts which makes receive more reliable but more work needs to be done to make this entirely interrupt driven. The ESP32 will probably work better as-is, though I haven’t tried it yet.
- Adapt or rewrite the CEC driver to be fully interrupt-driven.
- Add GitHub action to test the build
- Write tests?
- Fritzing (or similar) connection diagram
- I originally prototyped something like this with a Raspberry Pi. Here’s my writeup on that.
- CEC-O-MATIC is absurdly useful for parsing CEC messages.
- HDMI 1.3a Spec
- Nuvoton HDMI-CEC application note
- NXP HDMI-CEC application note