johnboiles / esphome-hdmi-cec

ESPHome component to send and receive HDMI-CEC messages.
62 stars 10 forks source link

Using lambdas with the 'data' option #7

Open smolyneux opened 1 year ago

smolyneux commented 1 year ago

Hi @johnboiles, thanks for sharing this project! I've been looking for something similar for a while to replace a similar setup I've had running on an rpi, and for the most part this seems to work really well.

I'm trying to get the current volume & mute statuses for an audio receiver via CEC, and map those to a number & switch components that can in turn be used to control the receiver via HA.

Right now, I've got something like this:

number:
  - platform: template
    name: "Hifi Volume"
    icon: mdi:volume-high
    min_value: 0
    max_value: 50
    initial_value: 0
    step: 1
    mode: slider
    id: hifi_volume
    optimistic: False
    set_action:
      - logger.log: "Living Room Hifi Volume - set_action fired"
      - hdmi_cec.send:
          destination: 0x05
          data: [0x44, 0x43]

  - platform: template
    name: "Hifi Mute"
    icon: mdi:volume-off
    id: hifi_mute
    optimistic: False
    turn_on_action:
      - logger.log: "Living Room Hifi Mute - turn_on_action fired"
      - hdmi_cec.send:
          destination: 0x05
          data: [0x44, 0x43]
    turn_off_action:
      - logger.log: "Living Room Hifi Mute - turn_off_action fired"
      - hdmi_cec.send:
          destination: 0x05
          data: [0x44, 0x43]

hdmi_cec:
  address: 0x02 # Audio system
  promiscuous_mode: true
  physical_address: 0x4000
  pin: 4
  on_message:

    - data: [0x7A, 0X00]
      then:
        - logger.log: "Got device status - volume 0 - mute off"
        - switch.turn_off: hifi_mute
        - number.set:
            id: hifi_volume
            value: 0

    - data: [0x7A, 0X01]
      then:
        - logger.log: "Got device status - volume 1 - mute off"
        - switch.turn_off: hifi_mute
        - number.set:
            id: hifi_volume
            value: 1

    - data: [0x7A, 0X02]
      then:
        - logger.log: "Got device status - volume 2 - mute off"
        - switch.turn_off: hifi_mute
        - number.set:
            id: hifi_volume
            value: 2

**...and so on**

time:
  - platform: sntp
    # ...
    on_time:
      # Every 60 seconds
      - seconds: 60
        then:
          - hdmi_cec.send:
              destination: 0x5
              data: [0x71] # ask audio system for current volume level & mute status

This definitely works, but involves a lot of repetition inside on_message due to the amount of messages that need to be handled (100+ handlers, as the mute status is included with the volume byte by adding 128 to the volume if mute is on e.g. volume level 1 mute off is 0X01, volume level 1 mute on is 0X81). Having so many handlers also seems to impact the performance/stability of the ESP8266 I'm running the code on.

Looking into the code a little, it seems that 'data' is a templatable value, so it should be possible to use it with a lambda expression & simplify things a lot. However, any lambda I try to use with data e.g.

  on_message:
    - data: !lambda |- 
        return [0x7A, 0X00];

Seems to result in the following:

INFO Reading configuration /config/living-room-cec-blaster.yaml...
INFO Detected timezone 'Etc/UTC'
INFO Generating C++ source...
Traceback (most recent call last):
  File "/usr/local/bin/esphome", line 33, in <module>
    sys.exit(load_entry_point('esphome', 'console_scripts', 'esphome')())
  File "/esphome/esphome/__main__.py", line 963, in main
    return run_esphome(sys.argv)
  File "/esphome/esphome/__main__.py", line 950, in run_esphome
    rc = POST_CONFIG_ACTIONS[args.command](args, config)
  File "/esphome/esphome/__main__.py", line 395, in command_run
    exit_code = write_cpp(config)
  File "/esphome/esphome/__main__.py", line 176, in write_cpp
    generate_cpp_contents(config)
  File "/esphome/esphome/__main__.py", line 188, in generate_cpp_contents
    CORE.flush_tasks()
  File "/esphome/esphome/core/__init__.py", line 619, in flush_tasks
    self.event_loop.flush_tasks()
  File "/esphome/esphome/coroutine.py", line 246, in flush_tasks
    next(task.iterator)
  File "/esphome/esphome/__main__.py", line 168, in wrapped
    await coro(conf)
  File "/config/.esphome/external_components/856b48e0/components/hdmi_cec/__init__.py", line 133, in to_code
    cg.add(trigger.set_data(data))
  File "/esphome/esphome/cpp_generator.py", line 754, in __call__
    call = CallExpression(self.base, *args)
  File "/esphome/esphome/cpp_generator.py", line 130, in __init__
    self.args = ExpressionList(*args)
  File "/esphome/esphome/cpp_generator.py", line 97, in __init__
    self.args = [safe_exp(arg) for arg in args]
  File "/esphome/esphome/cpp_generator.py", line 97, in <listcomp>
    self.args = [safe_exp(arg) for arg in args]
  File "/esphome/esphome/cpp_generator.py", line 378, in safe_exp
    raise ValueError("Object is not an expression", obj)
ValueError: ('Object is not an expression', Lambda<return [0x7A, 0X00];>)

I'm pretty new with both esphome and C++, so I am likely missing something obvious. Nevertheless, if you have any ideas or an example of how I should be doing this it would be greatly appreciated!

Thanks again!

czerwony03 commented 7 months ago

Hey, did You figure it out? 😬