Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HDMI-CEC Component #3017

Draft
wants to merge 11 commits into
base: dev
Choose a base branch
from
Draft

HDMI-CEC Component #3017

wants to merge 11 commits into from

Conversation

johnboiles
Copy link

@johnboiles johnboiles commented Jan 8, 2022

What does this implement/fix?

This PR adds a component to send and receive HDMI-CEC messages.

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 from the TV and transmit the IR codes to control the soundbar.

This should be handy for others with use cases like mine to modernize older equipment. You can also find monitor which source is selected, detect which HDMI devices are powered on, turn your TV on/off, send remote control commands. CEC-O-Matic is a good reference for possible HDMI-CEC commands.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Other

Docs PR: esphome/esphome-docs#1789

Test Environment

  • ESP32* (*It compiles, but I haven't tested with actual hardware)
  • ESP32 IDF
  • ESP8266

Example entry for config.yaml:

# Creates a fake Audio Return Channel (ARC) device. This convinces some TVs (e.g. TCL)
# to send volume commands over HDMI-CEC.

esphome:
  name: cec
  platform: ESP8266
  board: d1_mini
  # Maybe necessary depending on what else you're running on the ESP chip.
  # The execution loop for hdmi_cec is somewhat timing sensitive.
  # platformio_options:
  #   board_build.f_cpu: 160000000L

logger:

external_components:
  - source: ./components

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: 0x70 # System audio mode request
      then:
        - hdmi_cec.send:
            destination: 0x0
            data: [ 0x72, 0x01 ]
    - opcode: 0x71 # Give audio status
      then:
        - hdmi_cec.send:
            destination: 0x0
            data: [ 0x7A, 0x7F ]
    - opcode: 0x7D # Give audio system mode status
      then:
        - hdmi_cec.send:
            destination: 0x0
            data: [ 0x7E, 0x01 ]
    - opcode: 0x46 # Give OSD name
      then:
        - hdmi_cec.send:
            destination: 0x0
            data: [0x47, 0x65, 0x73, 0x70, 0x68, 0x6F, 0x6D, 0x65] # esphome
    - opcode: 0x8C # Give device Vendor ID
      then:
        - hdmi_cec.send:
            destination: 0x0
            data: [0x87, 0x00, 0x13, 0x37]
    - data: [0x44, 0x41] # User control pressed: volume up
      then:
        - logger.log: "Volume up"
    - data: [0x44, 0x42] # User control pressed: volume down
      then:
        - logger.log: "Volume down"
    - data: [0x44, 0x43] # User control pressed: volume mute
      then:
        - logger.log: "Volume mute"

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

See also, the README.md in my external component repo.

@probot-esphome
Copy link

probot-esphome bot commented Jan 8, 2022

Hey there @johnboiles,
Thanks for submitting this pull request! Can you add yourself as a codeowner for this integration? This way we can notify you if a bug report for this integration is reported.
In __init__.py of the integration, please add:

CODEOWNERS = ["@johnboiles"]

And run script/build_codeowners.py

(message by NeedsCodeownersLabel)

@johnboiles johnboiles force-pushed the hdmi-cec branch 2 times, most recently from 56eaf6b to b742f3b Compare January 14, 2022 04:54
@johnboiles
Copy link
Author

Looks like the failure is some warnings treated as errors on ESP32 in the external s-moch/CEC library :/ I'll probably need to fork that library.

@johnboiles johnboiles marked this pull request as ready for review January 14, 2022 07:16
@jesserockz jesserockz removed the stale label Oct 12, 2022
@johnboiles
Copy link
Author

Thanks @jesserockz! It's been a really busy season for me, sorry I haven't been able to get back to this. If someone else wants to get across the finish line that's fine with me.

@hamishfagg
Copy link

@quentinmit what's left before this is PR-ready? I'll take a look when I have a minute. Thanks for your work so far

@solarmill
Copy link

I would love to experiment with this soon. Can this pull the volume level status from a known audio device and pass that back to an apple tv so the remote knows what relative level it is controlling?

@hamishfagg
Copy link

@quentinmit I've tried to copy your changes into my existing esphome container and test but I've run into a few issues:

  • I think you've included some esp32-only libs which means it won't compile for esp8266
  • I get the following error (plus a few more about undefined timers) when compiling for esp32: src/esphome/components/hdmi_cec/hdmi_cec.cpp:127:70: error: 'timer_group_set_alarm_value_in_isr' was not declared in this scope

Any ideas? Thanks

@Minims
Copy link

Minims commented Dec 22, 2022

I have test this on my TV with an ESP8266, the example works great. Thanks.
Now I need to find the address of my other HDMI devices to control them.

@hamishfagg
Copy link

I have test this on my TV with an ESP8266, the example works great. Thanks. Now I need to find the address of my other HDMI devices to control them.

@Minims are using the version from this PR or quentinmit's repo?

@Minims
Copy link

Minims commented Dec 23, 2022

@hamishfagg

@Minims are using the version from this PR or quentinmit's repo?

I use the repo from @johnboiles branch hdmi-cec

❯ pip3 install 'esphome @ git+https://github.com/johnboiles/esphome@7c7d79e70cf247fb2c82a691da02099dfd2e4ddd'
...
❯ esphome run cec-client.yaml

@Minims
Copy link

Minims commented Dec 23, 2022

@johnboiles my TV always recognize my esp as a Audio Receiver even if I set the address to 0xB > playback device 3.
any idea why ? is it hardcoded ? it reactivate my tv speaker because it says my audio receiver is not working correctly :/

@segfly
Copy link

segfly commented Feb 5, 2023

@johnboiles This PR is not working for me on an ESP32-C3FN4 using GPIO3 (pin 8) on an Seeed XIAO. While I can see that transmitting CEC commands seem to work on my oscope, once this is connected to an HDMI source/sink, receiving any CEC commands appears to hang the MC and then the watchdog timer resets.

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x8 (TG1WDT_SYS_RST),boot:0xe (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380186
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x438
load:0x403ce000,len:0x918
load:0x403d0000,len:0x24e4
entry 0x403ce000

and

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x3 (RTC_SW_SYS_RST),boot:0xe (SPI_FAST_FLASH_BOOT)
Saved PC:0x40381e80
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x438
load:0x403ce000,len:0x918
load:0x403d0000,len:0x24e4
entry 0x403ce000

My config yaml is such:

esp32:
  board: seeed_xiao_esp32c3
  variant: esp32c3

external_components:
  - source: github://pr#3017
    components: [ hdmi_cec ]

hdmi_cec:
  address: 0x05
  promiscuous_mode: true
  physical_address: 0x4000
  pin: 3 # GPIO3
  on_message:
    - data: [0x44, 0x42] # User control pressed: volume down
      then:
        - logger.log: "Volume down"

Using esphome version 2022.12.8

@nagyrobi
Copy link
Member

nagyrobi commented Apr 7, 2023

I have similar issues, posted in the author's repo the issue: johnboiles/esphome-hdmi-cec#4

It would be so good if this would be finished! @johnboiles

@nagyrobi
Copy link
Member

nagyrobi commented Apr 7, 2023

I did a bunch of work to finish moving this entirely to interrupts (including timer interrupts) instead of loop(). It's not quite ready for a PR but if you want to play with it I pushed what I have so far to https://github.com/quentinmit/esphome/tree/hdmi-cec I found that without a timer interrupt the timing was just far enough out that it couldn't reliably talk to other devices.

@quentinmit No success for me with your version on ESP32 and a Sony TV:

[21:25:46][D][hdmi_cec:231]: Ran 1121 times in 10000ms (every 8.920607ms)
[21:25:46][D][hdmi_cec:232]: Current state: 0 interrupts pin 0 timer 12024 desired line state 1 low count 0
[21:25:46][D][hdmi_cec:237]: Timer divider 80 alarm 10000
[21:25:56][D][hdmi_cec:231]: Ran 624 times in 10000ms (every 16.025640ms)
[21:25:56][D][hdmi_cec:232]: Current state: 0 interrupts pin 0 timer 13026 desired line state 1 low count 0
[21:25:56][D][hdmi_cec:237]: Timer divider 80 alarm 10000
[21:26:06][D][hdmi_cec:231]: Ran 624 times in 10000ms (every 16.025640ms)
[21:26:06][D][hdmi_cec:232]: Current state: 1 interrupts pin 0 timer 14029 desired line state 1 low count 0
[21:26:06][D][hdmi_cec:237]: Timer divider 80 alarm 10000
[21:26:16][D][hdmi_cec:231]: Ran 624 times in 10000ms (every 16.025640ms)

no communication with the TV.

@nagyrobi
Copy link
Member

nagyrobi commented Apr 8, 2023

Trying to implement a switch to turn TV on or off:

switch:
  - platform: template
    name: "TV Power"
    id: tv_power
    turn_on_action:
      - hdmi_cec.send:
          destination: 0x0
          data: [0x44, 0x6d]
    turn_off_action:
      - hdmi_cec.send:
          destination: 0x0
          data: [0x44, 0x6c]

This works.

However I'd like to periodically update the state of the switch with:

      - hdmi_cec.send:
          destination: 0x0
          data: [0x8F]

To what the TV responds, which is parsed by:

hdmi_cec:
    ...
    on_message:
        - data: [0x90, 0x00] # Power status: On
          then:
            - logger.log: "Power state On"
            - switch.template.publish:
                id: tv_power
                state: ON
        - data: [0x90, 0x01] # Power status: Standby
          then:
            - logger.log: "Power state Standby"
            - switch.template.publish:
                id: tv_power
                state: OFF

However, template switch only allows for a lambda to be ran periodically.
What's the equivalent of:

      - hdmi_cec.send:
          destination: 0x0
          data: [0x8F]

in lambda?

Of course, I know that this could be simply put in an interval.

@ssieb
Copy link
Member

ssieb commented Apr 9, 2023

What's the equivalent of:

      - hdmi_cec.send:
          destination: 0x0
          data: [0x8F]

in lambda?

void send_data(uint8_t source, uint8_t destination, const std::vector<uint8_t> &data);

@DragRedSim
Copy link

@johnboiles my TV always recognize my esp as a Audio Receiver even if I set the address to 0xB > playback device 3. any idea why ? is it hardcoded ? it reactivate my tv speaker because it says my audio receiver is not working correctly :/

It is indeed hardcoded; see https://github.com/johnboiles/esphome-hdmi-cec/blob/292632456f6e427b0e957299c11b93b3e07f14f5/components/hdmi_cec/hdmi_cec.cpp#L97-101 - this might need to be changed.

@github-actions
Copy link
Contributor

There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. Thank you for your contributions.

@github-actions github-actions bot added the stale label Aug 19, 2023
@nagyrobi nagyrobi added not-stale Won't go stale and removed stale labels Aug 19, 2023
@segfly
Copy link

segfly commented Aug 19, 2023

I've been holding my breath for months hoping this PR would be finished and merged. I'd be happy to pay a bounty to get this finished (including proper fixes for issues reported in this thread) and merged.

@s-hadinger
Copy link

Just to let you guys know, I did the clean porting to ESP8266/ESP32 for Tasmota.

Now there is only one class with no virtual members, Receive is driven by interrupts for precise timing, and Transmit is using spin-loops. It is extremely reliable now.

arendst/Tasmota#19434
https://tasmota.github.io/docs/HDMI_CEC/

@sduponch
Copy link

sduponch commented Dec 23, 2023

I hope this will get merged soon, this should allow to set the channel of TV box (canalsat) for broadcasting football matchs in my bar, directly from a Calendar (the staff always forgot or loose the remote).

@nielsnl68
Copy link
Contributor

then the PR owner need to fix the conflicts etc.

@Minims
Copy link

Minims commented Dec 24, 2023

@nielsnl68 @sduponch I think code need to be a bit cleaner too.
But it's already working on last ESPHOME if you copy/paste the component folder. I'm using it on ESP8266.

@solarmill
Copy link

@johnboiles I'm not a coder and wish I could help, but can I buy you a beer/coffee to get this across the finish line? :)

@Minims
Copy link

Minims commented Feb 14, 2024

This seems to be a good alternative
https://github.com/Palakis/esphome-native-hdmi-cec

@solarmill
Copy link

@Minims wow!! Thank you so much for sharing that, this looks like a really good solution to an issue I've been trying to work around for years but have constantly been roadblocked by incomplete solutions. All I want to do is fake the presence of an AV receiver on the same CEC network as my Apple TV and have it route commands to a HiFiBerry to control the digital audio volume levels. I have found out the hard way that it's more complicated than expected to change digital volume and is best handled by a DSP. The web interface for HiFiBerry is quite good, and the home assistant integration works well, but I really want to be able to use my Apple TV remote to control and mute volume, without needing to use a separate device. It's such a simple dumb thing that has been very frustrating to implement.

@jesserockz jesserockz marked this pull request as draft June 27, 2024 01:54
@jesserockz jesserockz removed the not-stale Won't go stale label Jun 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.