Use KNX switch to control media_player volume

I have been experimenting with controlling my media_player volume through a KNX switch. The approach I used was to use a dimming object in KNX. The volume adjustment catches the on/off switch events in Home Assistant, using automation to adjust the volume in steps. The long-press in KNX generates the dimming action in KNX, which I catch and use to trigger play/ pause automations.

Relevant parts from configuration.yaml:

knx:
  tunneling:
    ...
  fire_event: true
  fire_event_filter: ["3/5/*"]

automation:
  - alias: "Increase Volumer"
    trigger:
      platform: event
      event_type: knx_event
      event_data:
        address: '3/5/0'
        data: 1
    condition:
      condition: template
      value_template: "{{ (float(states('sensor.volume_achterkamer')) <= 0.9) }}"
    action:
      - service: media_player.volume_set
        data_template:
          entity_id: media_player.achterkamer
          volume_level: '{{ (float(states("sensor.volume_achterkamer")) + 0.1) | round(2) }}'
  - alias: "Decrease Volume"
    trigger:
      platform: event
      event_type: knx_event
      event_data:
        address: '3/5/0'
        data: 0
    condition:
      condition: template
      value_template: "{{ (float(states('sensor.volume_achterkamer')) > 0.1) }}"
    action:
      - service: media_player.volume_set
        data_template:
          entity_id: media_player.achterkamer
          volume_level: '{{ (float(states("sensor.volume_achterkamer")) - 0.1) | round(2) }}'
  - alias: "Media Play"
    trigger:
      platform: event
      event_type: knx_event
      event_data:
        address: '3/5/2'
        data: 9
    action:
      service: media_player.media_play
      entity_id: media_player.achterkamer
  - alias: "Media Pause"
    trigger:
      platform: event
      event_type: knx_event
      event_data:
        address: '3/5/2'
        data: 1
    action:
      service: media_player.media_pause
      entity_id: media_player.achterkamer

The result is that you need multiple presses of a button to lower the volume in discrete steps (in this case 10% of volume, but you can off-course take bigger or smaller steps), and can use the long-press for media play/pause. This works fine. In the ideal case however, I would like to have a long press continually lower/ increase the volume until the button is released. Short press can then be used for play/ pause. Is there anybody who got that working, or has any ideas for alternative approaches?

Can your switch send a telegram on press and release of the long-press? Then you could trigger a gradual volume increase with the first telegram of the long press and cancel it with the release-telegram.
Just like dimming in knx works with DPT3 - which is not supported in xknx so you’d have to use DPT1 for this (and your switch application would need to support this).

Interesting thought. I have to experiment and look up whether my switch would be able to do that. I do see the ability under “advanced options” to send stop telegram, maybe that is usable, but I will need to dig a little deeper to be sure.

So on the HA side I would set up an automation that would do timed volume change, say 10% per second, and another automation to stop that automation? Not sure yet how to do that, but will have a look around for examples (if you know of any examples, these are most welcome off-course ;-)…

After some further investigation, the switch should have the ability to do this kind of thing. I have a Jung 4194 TSM, and can configure it to send a “stop” telegram when the dimming button (long-press) is released. When listening to the knx_event this results in:

Event 2:
{
    "event_type": "knx_event",
    "data": {
        "address": "7/5/4",
        "data": 0
    },
    "origin": "LOCAL",
    "time_fired": "...",
    "context": {
         ....  
    }
}

Event 1:

{
    "event_type": "knx_event",
    "data": {
        "address": "7/5/4",
        "data": 13
    },
    "origin": "LOCAL",
    "time_fired": "...",
    "context": {
        ...
    }
}

The “13” value is the start of the dimming, the “0” is the “stop” telegram when the button is released. Now on to find how to stop an automation that is running…

It took a bit of time, but I have a second working solution now based on the
suggestion provided above. For posterity and reference the solution:

KNX configuration

Configure the push-button interface for dimming. In advanced features make sure
to activate “transmit stop telegram”. This leads to two separate objects being
created, one for switching, one for dimming. Use two separate group objects, and
link these to the push-button objects.

The result should be that short press results in a ‘0’ (‘off’) or ‘1’ (‘on’)
payload being sent to the linked group address. Long press results in a dimming
telegram being sent on the second group address. In my case when you depress
(and hold) the push-button, the data payloads were ‘13’ and ‘5’ but this may
differ depending on your exact configuration of the push button (I have not
checked that yet). When you release the button a second telegram is sent with a
data payload of ‘0’ (‘off’) is sent.

That should be all the KNX/ ETS configuration to be done.

HA configuration

First thing to do is make sure that these events from the push-button are put
directly onto the HA bus. This is done through the fire_event_filter. So in my
case the switching group address is 7/5/4 and the dimming group address is
7/5/6.

knx:
    ...
    fire_event_filter: ["7/5/*"]
    ...

Next step is to setup a flag needed for the scripts to stop the loop when the
stop telegram is received. This boolean will be used in the automations and
scripts.

input_boolean:
  volume_change_stop:
    initial: false

Now setup automations that respond to the knx_events that are captured by the
earlier fire_event_filter configuration. The automations in turn trigger
scripts that do the actual work. Important thing to note here is that the flag
to stop the scripts calling each other is set here
(input_boolean.volume_change_stop set to ‘off’).

- alias: "Volume Up"
  trigger:
    platform: event
    event_type: knx_event
    event_data:
      address: '7/5/6'
      data: 13
  action:
    - service: input_boolean.turn_off
      data:
        entity_id: input_boolean.volume_change_stop
    - service: script.turn_on
      entity_id: script.volume_up_front_room
- alias: "Volume Down"
  trigger:
    platform: event
    event_type: knx_event
    event_data:
      address: '7/5/6'
      data: 5
  action:
    - service: input_boolean.turn_off
      data:
        entity_id: input_boolean.volume_change_stop
    - service: script.turn_on
      entity_id: script.volume_down_front_room
- alias: "Stop Volume Change"
  trigger:
    platform: event
    event_type: knx_event
    event_data:
      address: '7/5/6'
      data: 0
  action:
    - service: input_boolean.turn_on
      data:
        entity_id: input_boolean.volume_change_stop

Now the actual scripts. Things to note here are that steps of 5% (0.05) are
used, and we take a min to ensure we don’t go over 100%. The main script then
calls another script to make the loop. With respect to the loop script somewhere
on this forum it was mentioned that 1) you need to explicitly stop the calling
script, 2) use a short delay and 3) then call the calling script again. In our
case we also check as a condition whether the ‘stop’ flag has been set.

script:
  volume_up_front_room:
     sequence:
      - service: media_player.volume_set
        data_template:
          entity_id: media_player.front_room
          volume_level: >
              {{ [(float(state_attr('media_player.front_room', 'volume_level')) + 0.05),1.00] | min | round(2) }}
      - service: script.turn_on
        data_template:
          entity_id: script.loop_up_front_room
  volume_down_front_room:
     sequence:
      - service: media_player.front_room
        data_template:
          entity_id: media_player.front_room
          volume_level: >
              {{ [(float(state_attr('media_player.front_room', 'volume_level')) - 0.05),0.00] | max | round(2) }}
      - service: script.turn_on
        data_template:
          entity_id: script.loop_down_front_room
  loop_up_front_room:
    sequence:
      - service: script.turn_off
        data:
          entity_id: script.volume_up_voorkamer
      - condition: state
        entity_id: input_boolean.volume_change_stop
        state: 'off'
      - delay:
          milliseconds: 400
      - service: script.turn_on
        data_template:
          entity_id: script.volume_up_front_room
  loop_down_front_room:
    sequence:
      - service: script.turn_off
        data:
          entity_id: script.volume_down_front_room
      - condition: state
        entity_id: input_boolean.volume_change_stop
        state: 'off'
      - delay:
          milliseconds: 400
      - service: script.turn_on
        data_template:
          entity_id: script.volume_down_front_room

The rate of change can be controlled by playing with the delay and step width
used to change the volume.

Potential improvements are to parametrize a number of things to reduce the
amount of code needed.

It looks to me milliseconds are not supported, it requires at least seconds, and the recommendation is not to use any value below 1 second, because it gets checked in each second anyway. Any thoughts on this?

Another small addition is that you need to switch on the repetition of the telegrams (in my case I set it to 200 ms) in the button configuration, otherwise it will only step up/down once, not contiuously.

As to the ‘13’ and ‘5’ they depend on what percentage you configure in the button. Your numbers pertain to plus minus 6% change per button press. However, the status is coming from Sonos rather than from the KNX bus, therefore the percentage set in the button has no relevance, from what I figured.

input_boolean:
volume_change_stop:
initial: false

Where to put this in ?

I put it at the same level as for example “sensor:” or “binary_sensor:” in the configuration.yml:

input_boolean:
  volume_change_stop:
    initial: false

binary_sensor:
  - platform: knx
    name: lekkage.aanrecht
    state_address: '7/3/0'
    device_class: 'moisture'

doas it even matter where I put this ? Or what have this binary_senor to do with your Sonos ?

Hi

Is it possible to control volume with a 0-255 (1 byte)

This isnt working anymore .
They Changed the Fire Event so u have to change your config a little.

See : https://www.home-assistant.io/blog/2021/02/03/release-20212/#breaking-changes

Some knx_events changed the name of event_data fields:

address renamed to destination
Added source
Added direction
Added telegramtype
Additionally:

fire_event in the knx configuration is deprecated.
fire_event_filter in the knx configuration is renamed to event_filter.
(@farmio - #44749 #45248) (knx docs)

I’m trying to put this to work, but unfortunately i’m getting a error.

ex:
Script

service: media_player.volume_set
data_template:
  entity_id: media_player.bathroom
  volume_level: |
    {{ state_attr('media_player.bathroom', 'volume_level') - 0.05 | max | round(2) }}

error message:

Error: Error rendering data template: TypeError: 'float' object is not iterable

Any help would be truly appreciated!

br
christian

max() expects a list, you are passing a number.

Thanks for your reply @farmio :+1:
I’ve messed up the original script snippet a little bit, while trying and failing…

The original snippet:

service: media_player.volume_set
data_template:
  entity_id: media_player.bathroom
  volume_level: >
    {{ [(float(state_attr('media_player.bathroom', 'volume_level')) - 0.05),1.00] | max | round(2) }}

gives this error:

Error: Error rendering data template: TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'

The entity seems to not have an attribute volume_level. Maybe use a default value if it is not available eg. when not turned on.

Thank you very much for your feedback! After some digging, it seems that the entity didn’t have an volume_level attribute, until I’ve started playback on the chromecast device :upside_down_face: but, some adjustments to the scripts and now its working! :smiley: