Hi Folks,
First of all thanks for this great blueprint. I really appreciate it.
TLDR: The volume controls fail for my Sonos speaker group exposed to HA via Music Assistant due to a type missmatch in the volume handling condition. For some reason the type of the group_members attribute for my player is a set not a list hence the checks state_attr(player, ‘group_members’) != [] return true even if the attribute itself is an empty set causing the subsequent repeat action to fail. If the conditions for the goup volume controls are expanded with an additional state_attr(player, ‘group_members’) | length > 0 statement than it begins to work. It may even work without the != [] cehck by replacing it with the | length > 0 but I haven’t tested it. I do not have a player which exposes the group_members attribute as a list instead of a set .
Longer version with steps of investigation:
I just bought a Symfonisk media controller the other day for my new (and only) Sonos kitchen speakers. A pair of Era 100s set up as a Stereo Pair in the Sonos app itself.
I use Music Assistant thus the stereo pair is not integrated into HA “directly” using the Sonos integration but I’m using the player exposed by Music Assistant.
After setting up an automation for this blueprint the volume controls were not working.
I noticed the following error message upon pressing any of the two volume controls:
Error message in Trace
Here is the part ot the code which failed:
Part of the code throwing the error
## Volume for media players supporting groups
# Volume Up Press
- conditions: "{{ trigger.payload == 'volume_up' and state_attr(player, 'group_members') != none and state_attr(player, 'group_members') != [] }}"
sequence:
- repeat:
for_each: "{{ state_attr(player, 'group_members') }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ repeat.item }}"
data:
volume_level: >-
{% set volume = state_attr(repeat.item, "volume_level") + stepsize %}
{{ 1.0 if volume > 1.0 else volume }}
I checked the state and attributes of my speaker (playing music) and noticed that the group_members attribute was empty yet in the automation during execution it entered the case where it is expected to have multiple values:
Attributes of the player
I’m not an expert Python dev but I did some googling and trial and error and discovered that the type of the attribute for me is set but the !=[ ] check only works for lists (if I’m not mistaken) thus an empty set not being equal to an empty list the condition for the goup player volume control passes. But the attribute itself is still empty so the subsequent repeat step fails with the error in the first picture.
Jinja debugging and proof
Here is the version I’m using now with the additional statements in the conditions to cover cases where the type of the attribute is a set not a list:
Note that it may be sufficient to ommit the !=[] part altogether and only use the | length > 0 to cover both list and set types but I do not have a player which exposes the attribute as a list to test this theory thus I left it in the code.
Modified blueprint version I'm using
blueprint:
# Media control via IKEA Symfonisk Gen2 - Shawsky April 2023 v1.55
# Updated April 2024 - Add additional filtering to prevent triggering from other devices/false positives. Thanks kenno.
# Updated April 2024 - Add support for device rename in upcoming Z2M release & allow legacy action for Play/Pause
# Updated September 2023 - Further updates for group/non grouped behaviour / fix issue for volume hold for grouped/non grouped. Allow 100 volume steps.
# Updated August 2023 - Resolve a volume issue for media players with no group_memeber attribute
# Updated June 2023 - Resolve filtering issue where multiple devices are in use
# Updated May 2023 - Add group volume control
# Volume changes partially based on https://gist.github.com/erkr/a437ebcb98a2b5ba2deebabd02f5ffae Eric Kreuwels
# and https://gist.github.com/alexwmaustin/2c25cfa1a0ade1ab9fc1ef0940289672 Alex Austin
name: Z2M - IKEA Symfonisk sound controller GEN2 for media HomeMade Version
description:
'Control the selected media player (and any joined to it) with an IKEA Symfonisk sound controller GEN2 via Zigbee2MQTT (Z2M)
Supports single press on media buttons and single, double and long press for the "dot" buttons.'
domain: automation
input:
remote:
name: Remote
description: The IKEA Symfonisk controller GEN2 to use
selector:
device:
filter:
- integration: mqtt
manufacturer: IKEA
model: SYMFONISK sound remote gen2 (E2123)
- integration: mqtt
manufacturer: IKEA
model: SYMFONISK sound remote, gen 2 (E2123)
base_topic:
name: Zigbee2MQTT Base mqtt topic
description: The base topic configured in Zigbee2MQTT. If you haven't changed this, leave the default here ("zigbee2mqtt")
default: zigbee2mqtt
media_player:
name: Media Player
description: The media player to control with this automation
selector:
entity:
domain: media_player
multiple: false
volume_steps:
name: Volume number of steps
description: Controls the volume scale. The default of 25 is the same scale as the Sonos app.
default: 25
selector:
number:
min: 5
max: 100
step: 5
unit_of_measurement: "Num"
mode: slider
single_dot:
name: Single Dot (Single)
description: Action to run on single dot press
default: []
selector:
action: {}
single_dot_double_press:
name: Single Dot (Double)
description: Action to run on single dot double press
default: []
selector:
action: {}
single_dot_long_press:
name: Single Dot (Long)
description: Action to run on single dot long press
default: []
selector:
action: {}
double_dot:
name: Double Dot (Single)
description: Action to run on double dot press
default: []
selector:
action: {}
double_dot_double_press:
name: Double Dot (Double)
description: Action to run on double dot double press
default: []
selector:
action: {}
double_dot_long_press:
name: Double Dot (Long)
description: Action to run on double dot long press
default: []
selector:
action: {}
mode: restart
max_exceeded: silent
trigger_variables:
base_topic: !input base_topic
controller: !input remote
trigger:
- platform: mqtt
topic: "{{ base_topic }}/+/action"
condition:
- condition: template
value_template: "{{ trigger.topic == base_topic+'/'+device_attr(controller, 'name')+'/action' }} "
action:
- variables:
controllertopic: "{{ base_topic }}/{{ device_attr(controller, 'name') }}/action"
player: !input 'media_player'
steps: !input volume_steps
stepsize: '{{ 1.0 / steps }}'
- choose:
- conditions:
- "{{ trigger.payload != ''}}"
- "{{ trigger.topic == controllertopic }}"
sequence:
- choose:
## Commands
# Play / Pause
- conditions: "{{ trigger.payload == 'toggle' or trigger.payload == 'play_pause' }}"
sequence:
- service: media_player.media_play_pause
entity_id: !input 'media_player'
# Next Track
- conditions: "{{ trigger.payload == 'track_next' }}"
sequence:
- service: media_player.media_next_track
entity_id: !input 'media_player'
# Previous Track
- conditions: "{{ trigger.payload == 'track_previous' }}"
sequence:
- service: media_player.media_previous_track
entity_id: !input 'media_player'
## Volume for media players supporting groups
# Volume Up Press
- conditions: "{{ trigger.payload == 'volume_up' and state_attr(player, 'group_members') != none and state_attr(player, 'group_members') != [] and state_attr(player, 'group_members') | length > 0 }}"
sequence:
- repeat:
for_each: "{{ state_attr(player, 'group_members') }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ repeat.item }}"
data:
volume_level: >-
{% set volume = state_attr(repeat.item, "volume_level") + stepsize %}
{{ 1.0 if volume > 1.0 else volume }}
# Volume Down Press
- conditions: "{{ trigger.payload == 'volume_down' and state_attr(player, 'group_members') != none and state_attr(player, 'group_members') != [] and state_attr(player, 'group_members') | length > 0 }}"
sequence:
- repeat:
for_each: "{{ state_attr(player, 'group_members') }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ repeat.item }}"
data:
volume_level: >-
{% set volume = state_attr(repeat.item, "volume_level") - stepsize %}
{{ 0.0 if volume < 0.0 else volume }}
# Volume Up Hold
- conditions: "{{ trigger.payload == 'volume_up_hold' and state_attr(player, 'group_members') != none and state_attr(player, 'group_members') != [] and state_attr(player, 'group_members') | length > 0 }}"
sequence:
- repeat:
for_each: "{{ state_attr(player, 'group_members') }}"
sequence:
- service: media_player.volume_up
target:
entity_id: "{{ repeat.item }}"
# Volume Down Hold
- conditions: "{{ trigger.payload == 'volume_down_hold' and state_attr(player, 'group_members') != none and state_attr(player, 'group_members') != [] and state_attr(player, 'group_members') | length > 0 }}"
sequence:
- repeat:
for_each: "{{ state_attr(player, 'group_members') }}"
sequence:
- service: media_player.volume_down
target:
entity_id: "{{ repeat.item }}"
## Volume for media players not supporting groups
# Volume Up
- conditions: "{{ trigger.payload == 'volume_up' and (state_attr(player, 'group_members') == none or state_attr(player, 'group_members') == [] or state_attr(player, 'group_members') | length == 0 ) }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ player }}"
data:
volume_level: >-
{% set volume = state_attr(player, "volume_level") + stepsize %}
{{ 1.0 if volume > 1.0 else volume }}
# Volume Down Press
- conditions: "{{ trigger.payload == 'volume_down' and (state_attr(player, 'group_members') == none or state_attr(player, 'group_members') == [] or state_attr(player, 'group_members') | length == 0) }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ player }}"
data:
volume_level: >-
{% set volume = state_attr(player, "volume_level") - stepsize %}
{{ 0.0 if volume < 0.0 else volume }}
# Volume Up Hold
- conditions: "{{ trigger.payload == 'volume_up_hold' and (state_attr(player, 'group_members') == none or state_attr(player, 'group_members') == [] or state_attr(player, 'group_members') | length == 0 ) }}"
sequence:
- service: media_player.volume_up
target:
entity_id: "{{ player }}"
# Volume Down Hold
- conditions: "{{ trigger.payload == 'volume_down_hold' and (state_attr(player, 'group_members') == none or state_attr(player, 'group_members') == [] or state_attr(player, 'group_members') | length == 0 ) }}"
sequence:
- service: media_player.volume_down
target:
entity_id: "{{ player }}"
## Actions
# Single Dot Press
- conditions: "{{ trigger.payload == 'dots_1_short_release' }}"
sequence: !input single_dot
# Single Dot Double Press
- conditions: "{{ trigger.payload == 'dots_1_double_press' }}"
sequence: !input single_dot_double_press
# Single Dot Long Press
- conditions: "{{ trigger.payload == 'dots_1_long_press' }}"
sequence: !input single_dot_long_press
# Double Dot Press
- conditions: "{{ trigger.payload == 'dots_2_short_release' }}"
sequence: !input double_dot
# Double Dot Double Press
- conditions: "{{ trigger.payload == 'dots_2_double_press' }}"
sequence: !input double_dot_double_press
# Double Dot Long Press
- conditions: "{{ trigger.payload == 'dots_2_long_press' }}"
sequence: !input double_dot_long_press