Template Light as speaker volume control

Hi guys,

I’m trying to use a workaround for a wifi speaker’s volume control in HomeKit using a template light.

I have the actual input change for change volume working using the brightness, my issue is that the ‘light’ power status and ‘brightness’ status doesn’t sync up with the media player attributes.

If I tell Siri to ‘set Sony speaker to 20%’, then the volume will be changed. But I want home app to know the speaker is already on and its current level. I had an unfortunate error at 3am experimenting with code, I turned the light on in the home app, it sprung to 100% volume. Very uncool in an apartment :stuck_out_tongue:

Seems these error is preventing:

Update for light.sony_speaker fails
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 221, in async_update_ha_state
    await self.async_device_update()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 347, in async_device_update
    await self.async_update()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/light/template.py", line 257, in async_update
    if 0 <= int(brightness) <= 255:
UnboundLocalError: local variable 'brightness' referenced before assignment

&

UndefinedError: 'None' has no attribute 'attributes'

Here is my code, tweaked from the template light component docs which also doesn’t seem to work out of the box with the entity ids changed.

  - platform: template
    lights:

      sony_speaker:
        friendly_name: "Sony Speaker"
        value_template: "{{ is_state('switch.sony_speaker', 'on') }}"
        level_template: "{{ states.media_player.sony_speaker.attributes.volume_level|float * 255)|int }}"

        turn_on:
          service: media_player.turn_on
          data:
            entity_id: media_player.sony_speaker
           
        turn_off:
          service: media_player.turn_off
          data:
            entity_id: media_player.sony_speaker
            
        set_level:
          service: media_player.volume_set
          data_template:
            entity_id: media_player.sony_speaker
            volume_level: "{{ (brightness / 100 * 100)|int / 100 }}"
        level_template: >-
          {% if is_state('media_player.sony_speaker', 'on') %}
            {{ (states.media_player.sony_speaker.attributes.volume_level|float * 100)|int }}
          {% else %}
            0
          {% endif %}

If I remove the volume part, then the power syncs up as expected

Should I be creating a sensor for the volume as set by the speaker to reference initially?

Any help would be appreciated

Thank you

Linton

1 Like

The error is occurring when it’s trying to render the level_template. That’s probably because you’re missing a left parenthesis. I think it should be:

level_template: "{{ (states.media_player.sony_speaker.attributes.volume_level|float * 255)|int }}"

Although not clear, it may also be that states.media_player.sony_speaker may not exist. In that case you may need to account for that possibility as well. Maybe something like:

level_template: "{{ (state_attr('media_player.sony_speaker', 'volume_level')|float * 255)|int }}"

Having said all that, it also looks like there is a bug in the template light implementation. If you look at this code, I would say the if statement below the try statement should be in the try’s else clause. I.e., it should probably be:

        if self._level_template is not None:
            try:
                brightness = self._level_template.async_render()
            except TemplateError as ex:
                _LOGGER.error(ex)
                self._state = None
            else:
                if 0 <= int(brightness) <= 255:
                    self._brightness = int(brightness)
                else:
                    _LOGGER.error(
                        'Received invalid brightness : %s. Expected: 0-255',
                        brightness)
                    self._brightness = None

Thanks @pnbruckner, that first one did the trick thank you.

Seems to be working like a charm so far :slight_smile:

Hi,
Like Linthart said: template light component doesn’t seem to work out of the box with the entity ids changed. So i tried to use his and change it to my entity but somehow i get this error:

duplicated mapping key at line 74, column -169:
level_template: >-
^

Witch is this line in de code below: level_template: >-

This is my version of the code.

light:
  - platform: template
    lights:
      woonkamer:
        friendly_name: "Woonkamer"
        value_template: "{{ is_state('switch.woonkamer', 'on') }}"
        level_template: "{{ (states.media_player.woonkamer.attributes.volume_level|float * 255)|int }}"
        turn_on:
          service: media_player.turn_on
          data:
            entity_id: media_player.woonkamer
        turn_off:
          service: media_player.turn_off
          data:
            entity_id: media_player.woonkamer
        set_level:
          service: media_player.volume_set
          data_template:
            entity_id: media_player.woonkamer
            volume_level: "{{ (brightness / 100 * 100)|int / 100 }}"
        level_template: >-
           {% if is_state('media_player.woonkamer', 'on') %}
            {{ (states.media_player.woonkamer.attributes.volume_level|float * 100)|int }}
          {% else %}
            0
          {% endif %}

I hope some can help me with this error…
tnx

I don’t remember the details, but at the very least, the duplicate key error is because you duplicated a key. I.e., you have level_template specified twice. Remove one of them.

oohh ok… but in the original code of Linthart it is mentioned twise.
Im new to coding in HASSIO.

Should i remove upper line with the mentioning of level_template?
Or the bottom part? :relaxed:

Thnx for the your quick reply

I don’t know. Depends on which you want. If it were me, I’d remove both and use this:

level_template: "{{ (state_attr('media_player.woonkamer', 'volume_level')|float * 255)|int }}"

ok i will try.

I commented the first, restarted HASS.
Now when trying to adjust the volume i get the error below.

On the template light component page https://www.home-assistant.io/components/light.template/

They are saying something about this i think:
### THEATER VOLUME CONTROL

This example shows a light that is actually a home theater’s volume. This component gives you the flexibility to provide whatever you’d like to send as the payload to the consumer including any scale conversions you may need to make; the Media Player component needs a floating point percentage value from 0.0 to 1.0 .

But i really don’t know how to change this if this is what the error refers to?

Hoop you can help me with this.
tnx

value must be at most 1 for dictionary value @ data['volume_level']
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 121, in handle_call_service
    connection.context(msg))
  File "/usr/src/homeassistant/homeassistant/core.py", line 1150, in async_call
    self._execute_service(handler, service_call))
  File "/usr/src/homeassistant/homeassistant/core.py", line 1172, in _execute_service
    await handler.func(service_call)
  File "/usr/src/homeassistant/homeassistant/components/light/__init__.py", line 297, in async_handle_light_on_service
    await light.async_turn_on(**pars)
  File "/usr/src/homeassistant/homeassistant/components/template/light.py", line 214, in async_turn_on
    {"brightness": kwargs[ATTR_BRIGHTNESS]}, context=self._context)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 131, in async_run
    await self._handle_action(action, variables, context)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 210, in _handle_action
    action, variables, context)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 299, in _async_call_service
    context=context
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 89, in async_call_from_config
    domain, service_name, service_data, blocking=blocking, context=context)
  File "/usr/src/homeassistant/homeassistant/core.py", line 1130, in async_call
    processed_data = handler.schema(service_data)
  File "/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py", line 267, in __call__
    return self._compiled([], data)
  File "/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py", line 589, in validate_dict
    return base_validate(path, iteritems(data), out)
  File "/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py", line 427, in validate_mapping
    raise er.MultipleInvalid(errors)
voluptuous.error.MultipleInvalid: value must be at most 1 for dictionary value @ data['volume_level']

It’s strange that you’re seeing an exception like that. Config errors shouldn’t be causing exceptions. It should cause errors, but not exceptions.

Please post the YAML code as it exists now.

this is the configuration.yaml file:

I really appreciate your help! tnx

# Configure a default setup of Home Assistant (frontend, api, etc)
default_config:

discovery:
  enable:
    - samsung_tv

homekit:

heos:
 host: 192.168.2.4


ssdp: 
zeroconf:

climate:
  - platform: anna
    name: Anna Thermostat # optional, only if you want to use a different name
    username: smile       # optional, default username is smile
    password: rbplqgcm
    host: 192.168.2.6
    port: 80              # optional, default port is 80
    scan_interval: 10     # optional, default scan interval is 10 seconds

fan:
  - platform: mqtt
    name: "Centrale Afzuiging"
    command_topic: "ha/afzuiging"
    state_topic: "ha/afzuiging/state"
    payload_on: "ON"
    payload_off: "OFF"
    qos: 0
    retain: true
    optimistic: false
    # state_ON: "ON"
    # state_OFF: "OFF"

cover BlindsControl1:
  - platform: mqtt
    name: "MK Blinds"
    command_topic: "MK-SmartHouse/utilities/MK-BlindsControl1"
    state_topic: "MK-SmartHouse/utilities/MK-BlindsControl1/state"
    retain: true
    payload_open: "0"
    payload_close: "100"
    payload_stop: "56"
    state_open: "0"
    state_closed: "100"

light:
  - platform: template
    lights:
      woonkamer:
        friendly_name: "Woonkamer"
        value_template: "{{ is_state('switch.woonkamer', 'on') }}"
        # level_template: "{{ (states.media_player.woonkamer.attributes.volume_level|float * 255)|int }}"
        turn_on:
          service: media_player.turn_on
          data:
            entity_id: media_player.woonkamer
        turn_off:
          service: media_player.turn_off
          data:
            entity_id: media_player.woonkamer
        set_level:
          service: media_player.volume_set
          data_template:
            entity_id: media_player.woonkamer
            volume_level: "{{ (brightness / 100 * 100)|int / 100 }}"
        level_template: "{{ (state_attr('media_player.woonkamer', 'volume_level')|float * 255)|int }}"


# light:
#   - platform: template
#     lights:
#       woonkamer:
#         friendly_name: "Heos volume"
#         value_template: >-
#           {% if is_state('media_player.woonkamer', 'on') %}
#             {% if states.media_player.woonkamer.attributes.is_volume_muted %}
#               off
#             {% else %}
#               on
#             {% endif %}
#           {% else %}
#             off
#           {% endif %}
#         turn_on:
#           service: media_player.volume_mute
#           data:
#             entity_id: media_player.woonkamer
#             is_volume_muted: false
#         turn_off:
#           service: media_player.volume_mute
#           data:
#             entity_id: media_player.woonkamer
#             is_volume_muted: true
#         set_level:
#           service: media_player.volume_set
#           data_template:
#             entity_id: media_player.woonkamer
#             volume_level: "{{ (brightness / 255 * 100)|int / 100 }}"
#         level_template: >-
#           {% if is_state('media_player.woonkamer', 'on') %}
#             {{ (states.media_player.woonkamer.attributes.volume_level|float * 255)|int }}
#           {% else %}
#             0
#           {% endif %}


# light:
#   - platform: template
#     lights:
#       theater_volume:
#         friendly_name: "Heos volume"
#         value_template: >-
#           {% if is_state('media_player.woonkamer', 'on') %}
#             {% if states.media_player.woonkamer.attributes.is_volume_muted %}
#               off
#             {% else %}
#               on
#             {% endif %}
#           {% else %}
#             off
#           {% endif %}
#         icon_template: >-
#           {% if is_state('media_player.woonkamer', 'on') %}
#             {% if states.media_player.woonkamer.attributes.is_volume_muted %}
#               /local/lightbulb-off.png
#             {% else %}
#               /local/lightbulb-on.png
#             {% endif %}
#           {% else %}
#             /local/lightbulb-off.png
#           {% endif %}
#         turn_on:
#           service: media_player.volume_mute
#           data:
#             entity_id: media_player.receiver
#             is_volume_muted: false
#         turn_off:
#           service: media_player.volume_mute
#           data:
#             entity_id: media_player.receiver
#             is_volume_muted: true

# fan:
#   - platform: mqtt
#     name: "Bedroom Fan"
#     state_topic: "bedroom_fan/on/state"
#     command_topic: "bedroom_fan/on/set"
#     oscillation_state_topic: "bedroom_fan/oscillation/state"
#     oscillation_command_topic: "bedroom_fan/oscillation/set"
#     speed_state_topic: "bedroom_fan/speed/state"
#     speed_command_topic: "bedroom_fan/speed/set"
#     qos: 0
#     payload_on: "true"
#     payload_off: "false"
#     payload_oscillation_on: "true"
#     payload_oscillation_off: "false"
#     payload_low_speed: "low"
#     payload_medium_speed: "medium"
#     payload_high_speed: "high"
#     speeds:
#       - low
#       - medium
#       - high



# Uncomment this if you are using SSL/TLS, running in Docker container, etc.
http:
  base_url: kumalic.duckdns.org:8123
  ssl_certificate: /ssl/fullchain.pem
  ssl_key: /ssl/privkey.pem




# Sensors
sensor:
  # Weather prediction
  - platform: yr

# Text to speech
tts:
  - platform: google_translate

group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml

I don’t see anything that would cause that error. Is there anything in groups.yaml, automations.yaml or scripts.yaml?

Oh, wait, it’s complaining about the value of volume_level. So this doesn’t really make much sense to me:

volume_level: "{{ (brightness / 100 * 100)|int / 100 }}"

First, not sure why you would divide by 100 and then multiply by 100.

But I think the main problem is, for a light, brightness goes from 0 to 255, not to 100. And apparently the media_player.volume_set service wants a float between 0.0 and 1.0. The template you have now would result in values from 0.0 to 2.55.

EDIT: I would try this:

volume_level: "{{ brightness / 255 }}"

yesss it works now! jeeej :smiley::+1:

Only i cant turn it off or on on button klik?
Also it does not sync with homekit app?

Do you think you can help me with this, please? :relaxed:

I don’t know about those things. You should probably start a new topic to ask for help with those questions.

ok, tnx a lot!

Bringing this back up from the “dead.” This works for me, for the most part, but I’m noticing some strange behavior - and I believe it has to do with the math behind all of this. What works are even 10 base values: 20%, 40%, 60%, 80%. But everything else, does not work: 10%, 30%, 50%, 70%, 90%; let alone more precise values like: 43%, 65%, etc.

Any help? I have to think it has to do with how the template is translating the values over to brightness? Here’s my configuration:

- platform: template
  lights:
    media_zone:
      friendly_name: "Media Zone"
      value_template: "{{ is_state('media_player.media_room_denon', 'on') }}"
      turn_on:
        service: media_player.turn_on
        data:
          entity_id: media_player.media_room_denon
      turn_off:
        service: media_player.turn_off
        data:
          entity_id: media_player.media_room_denon
      set_level:
        service: media_player.volume_set
        data_template:
          entity_id: media_player.media_room_denon
          volume_level: "{{ brightness / 255 }}"
      level_template: "{{ (state_attr('media_player.media_room_denon', 'volume_level')|float * 255)|int }}"