Group Sonos and Ungroup Sonos Based on Presence - Scripts & Automations

I write exactly the same kind of automation with Home Assistant and I use the first player of the sonos_group list as group master which make things much easier :slight_smile:

I did the same thing. However, I improved it by setting up the second player of the sonos_group list as group master if I leave the room that is the current group master. So the group master actually leave the group. See the code of " # Update the master speaker in the group" for details

Thanks for this, but one question from a noob :slight_smile:

Where do I put all this code? I already realized the Sonos integration with the mini media player, but I am not really satisfied as the functionality is really minimized.

You need to put these as part of your automation.yaml and script.yaml and include them from your configuration.yaml like this

automation: !include automation.yaml
script: !include script.yaml

See Automation YAML - Home Assistant for reference.

You will also need to update my automation with your devices but I think you can directly use the script.

1 Like

does this still work with 2022.5? Since this update they removed sonos_group and now my own way of grouping (or at least, detecting the group) is not working anymore, and since this has been a long time since I have set it up, I am not sure how to fix this :confused:

I had this done with a custom sensor that used the sonos_group as value_template:

          {%- for player in states.media_player if player.attributes.sonos_group %}
          {%- if player.attributes.sonos_group[0] == player.entity_id %}
            {%- set slaves = player.attributes.sonos_group[1:] %}
            {{ slaves|join(",")|replace("media_player", "input_boolean") }}
          {%- endif %}
          {%- endfor %}

but in 2022.5 they changed sonos_group for group_members, and somehow neither now work with this…

The sonos_group attribute was renamed and is now called group_members. Adjust your template accordingly.

{%- for player in states.media_player if player.attributes.group_members is defined %}
  {%- if player.attributes.group_members[0] == player.entity_id %}
    {%- set slaves = player.attributes.group_members[1:] %}
    {{ slaves|join(",")|replace("media_player", "input_boolean") }}
  {%- endif %}
{%- endfor %}

thx! I tried that but it didn’t work, but seems I was missing the is defined part :slight_smile:

If you have non-Sonos media_players, states.media_player will include them. However, you will get an error if you attempt to access the group_members attribute for a media_player that doesn’t have it. That’s why the for reduces the list of media_players to only those that have the group_members attribute defined.

{%- for player in states.media_player if player.attributes.group_members is defined %}
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Another way of doing the same thing:

{%- for player in states.media_player | selectattr('attributes.group_members', 'defined') | list %}
1 Like

thx for the explanation!

Thanks guys for spotting this as well. I just updated to this month release and realised this automation was broken due to this.

Hey @relliky , is your config on github working after the update? Mine has gone to crap and i can’t workout why so will probably start from scratch if its working for you

Thanks :slight_smile:

The latest release, version 2022.8.0, eliminated service calls sonos.join and sonos.unjoin. The replacements are media_player.join and media_player.unjoin.

The script in the original post needs modification in order to work with 2022.8.0.

Hey @123 i had already updated the media_player.join options but still not wokring anymore. Im getting an error in the logs saying:

S-KC Ensuite Group Its Speaker If People Present: Error executing script. Invalid data for call_service at pos 1: extra keys not allowed @ data[‘master’]

script:
  add_sonos_into_speaker_group:
    mode: queued
    alias: Add Sonos Speaker Into the Speaker Group
    fields:
      target_player:
        description: "Sonos player name that need to be added into the group"
        example: "media_player.bedroom"
    sequence:
      - condition: template
        value_template: >
          {% if target_player is not none and target_player != false and target_player != '' %}
            true
          {% else %}
            false
          {% endif %}
      # The target player must not be playing anything
      - condition: template
        value_template: >
          {% if states(target_player) != 'playing' %}
            true
          {% else %}
            false
          {% endif %}
      # First set the target player to the same volume as the controller
      # Play:3 sounds level needs to be offset for setting up Play:1/Playbars
      - service: media_player.volume_set
        data_template:
          entity_id: >
            {% if target_player is not none %}
              {{ target_player }}
            {% endif %}
          volume_level: >
            {% for state in states.media_player if state.entity_id == 'media_player.' + states('input_select.music_controller') %}
              {% if   states('input_select.music_controller') != 'workshop' and target_player == 'media_player.workshop' %}
                {{ state.attributes.volume_level + 0.10 }}
              {% elif states('input_select.music_controller') == 'workshop' and target_player != 'media_player.workshop' %}
                {{ state.attributes.volume_level - 0.10 }}
              {% else %}
                {{ state.attributes.volume_level }}
              {% endif %}
            {% endfor %}
      # Now join the player into the group twice in case sometimes it didn't manage to join in for certain cases
      - service: media_player.join
        data_template:
          master: media_player.{{ states('input_select.music_controller') }}
          entity_id: >
            {% if target_player is not none %}
              {{ target_player }}
            {% else %}
              media_player.lounge
            {% endif %}
      - service: media_player.join
        data_template:
          master: media_player.{{ states('input_select.music_controller') }}
          entity_id: >
            {% if target_player is not none %}
              {{ target_player }}
            {% else %}
              media_player.lounge
            {% endif %}

  - alias: S-KC Kitchen Group Its Speaker If People Present
    id: "1560305343071"
    description: "automation.s_kc_kitchen_group_its_speaker_if_people_present"
    trigger:
      - platform: state
        to: "on"
        entity_id: group.kitchen
    condition:
      - condition: state
        entity_id: input_boolean.follow_music
        state: "on"
      # The controller player must be playing music (not paused)
      - condition: template
        value_template: >
          {% if states('media_player.' + states('input_select.music_controller')) == 'playing' %}
            true
          {% else %}
            false
          {% endif %}
    action:
      # Add this room speaker into the group
      - service: script.add_sonos_into_speaker_group
        data:
          target_player: media_player.kitchen
      - service: automation.turn_off
        entity_id: automation.s_kc_kitchen_group_its_speaker_if_people_present

Any ideas whats going on here?
Thanks :slight_smile:

The problem is identified in the error message:

extra keys not allowed @ data[‘master’]

master is not a valid option for media_player.join. You’re using the options that were valid for sonos.join but they’re different for media_player.join.

service: media_player.join
target:
  entity_id: "media_player.{{ states('input_select.music_controller') }}"
data:
  group_members: "{{ target_player if target_player is not none else media_player.lounge }}"

That worked! Thanks heaps :smiley:

Hello friends,

I hope you can help me. I tried to install the whole thing at HA.

I have adapted the script to my player as far as I can see. However, I keep getting an error when I run it. I’m fairly new to HA.

###########################################
# Sonos Scrips
###########################################
add_sonos_into_speaker_group:
  mode: queued
  alias: Add Sonos Speaker Into the Speaker Group
  fields:
    target_player:
      description: "Sonos player name that need to be added into the group"
      example: "media_player.master_room_sonos"
  sequence:
    - condition: template
      value_template: >
        {% if target_player is not none and target_player != false and target_player != '' %}
          true
        {% else %}
          false
        {% endif %}

    # The target player must not be playing anything
    - condition: template
      value_template: >
        {% if states(target_player) != 'playing' %}
          true
        {% else %}
          false
        {% endif %}


    # Now join the player into the group twice in case sometimes it didn't manage to join in for certain cases
    - service: media_player.join
      data_template:
        master: media_player.{{ states('input_select.music_controller') }}
        entity_id: >
          {% if target_player is not none %}
            {{ target_player }}
          {% else %}
            media_player.kuche
          {% endif %}

    - service: media_player.join
      data_template:
        master: media_player.{{ states('input_select.music_controller') }}
        entity_id: >
          {% if target_player is not none %}
            {{ target_player }}
          {% else %}
            media_player.kuche
          {% endif %}

remove_sonos_from_speaker_group:
  alias: Remove Sonos Speaker From the Speaker Group and Update the Master Speaker
  mode: queued
  fields:
    target_player:
      description: "Sonos player that need to be removed from the group"
      example: "media_player.master_room_sonos"
  sequence:
    - condition: template
      value_template: >
        {% if target_player is not none and target_player != false and target_player != '' %}
          true
        {% else %}
          false
        {% endif %}

    # The target player must be playing
    - condition: template
      value_template: >
        {% if states(target_player) == 'playing' %}
          true
        {% else %}
          false
        {% endif %}

    # The target is not the soundbar that is playing TV sound
    - condition: template
      value_template: >
        {% if target_player is not none and state_attr(target_player, 'media_title') != 'TV' %}
          true
        {% else %}
          false
        {% endif %}

    # Update the master speaker in the group
    - service: input_select.select_option
      entity_id: input_select.music_controller
      data:
        option: >
          {% set ns = namespace() %}
          {% set ns.primary_speaker   = 'none' %}
          {% set ns.secondary_speaker = 'none' %}
          {# set the pri_speaker and sec_speaker #}
          {% for speaker in state_attr(target_player, "group_members") %}
            {% if loop.index == 1 %} 
              {% set ns.primary_speaker   = speaker|regex_replace(find='media_player.', replace='', ignorecase=False) %}
            {% elif loop.index == 2 %} 
              {% set ns.secondary_speaker = speaker|regex_replace(find='media_player.', replace='', ignorecase=False) %}
            {% endif %}
          {% endfor %}

          {# use the second speaker as master speaker if target speaker is currently the master #}
          {% if target_player == ('media_player.' + ns.primary_speaker) and ns.secondary_speaker != 'none' %}
            {{ ns.secondary_speaker }}
          {% else %}
            {{ ns.primary_speaker }}
          {% endif %}

    # The target must be the slave to be removed from the group
    - condition: template
      value_template: >
        {% if target_player != 'media_player.' + states('input_select.music_controller') %}
          true
        {% else %}
          false
        {% endif %}

    - service: media_player.unjoin
      data:
        entity_id: >
          {% if target_player is not none and target_player != false and target_player != '' and target_player != 'None'%}
            {{ target_player }}
          {% else %}
            media_player.küche
          {% endif %}

pause_sonos_if_sole_speaker_group:
  alias: Pause the Sonos Speaker if it is a Sole Speaker Group
  mode: queued
  fields:
    target_player:
      description: "Sonos player that need to be paused"
      example: "media_player.master_room_sonos"
  sequence:
    - condition: template
      value_template: >
        {% if target_player is not none and target_player != false and target_player != '' %}
          true
        {% else %}
          false
        {% endif %}

    # check it is a sole speaker
    - condition: template
      value_template: >
        {% for speaker in state_attr(target_player, "group_members") %}
          {% if loop.index == 1 %}  
            {% if loop.length == 1 %} 
              true  
            {% else %}      
              false      
            {% endif %}
          {% endif %}
        {% endfor %}

    - service: media_player.pause
      data:
        entity_id: >
          {% if target_player is not none and target_player != false and target_player != '' and target_player != 'None'%}
            {{ target_player }}
          {% else %}
            media_player.kuche
          {% endif %}

alias: test123
description: ""
trigger:
  - platform: state
    entity_id:
      - input_boolean.4
    to: "on"
    id: test an
condition: []
action:
  - service: script.add_sonos_into_speaker_group
      data:
        target_player: media_player.bad
mode: single
extra keys not allowed @ data['master']. Got None required key not provided @ data['group_members']. Got None

thank you

I think (not sure) you need to outdent the data key, like this:

action:
  - service: script.add_sonos_into_speaker_group
    data:
      target_player: media_player.bad

Hey guys, so in the future here 2023 and trying to get this script to work. It’s not going well…

Can someone confirm this script still works with todays HA build? Changed sonos service calls to media_player but it’s still giving errors out. If someone has this working today I’ll keep going trying to figure this out, if not, I’ll find another solution.

I never quite understood why there is need for “master speaker variable” and it’s maintenance every 5 mins. Maybe I’m missing something, but this is my automation which does basically the same as the scripts in OP, but without that variable. It allows to set “volume offsets” for different speakers, and doesn’t join living room speaker to others when watching TV (however, it joins the other way - if you’re watching TV and go to kitchen for drink, kitchen speaker joins and you’ll hear TV there).

- id: '....'
  alias: General - Music
  description: ''
  variables:
    volume_adjustment:
      living_room_speaker: 0.6
      bedroom: 0.8
      kitchen: 1
  trigger:
  - platform: state
    entity_id: binary_sensor.living_room_motion_group
    id: living_room
    from:
    - 'on'
    - 'off'
    to:
    - 'on'
    - 'off'
    variables:
      speaker: media_player.living_room_speaker
  - platform: state
    entity_id: binary_sensor.kitchen_motion_group
    from:
    - 'on'
    - 'off'
    to:
    - 'on'
    - 'off'
    variables:
      speaker: media_player.kitchen
  - platform: state
    entity_id: binary_sensor.bedroom_motion_occupancy
    from:
    - 'on'
    - 'off'
    to:
    - 'on'
    - 'off'
    variables:
      speaker: media_player.bedroom
  condition:
  - condition: state
    entity_id: input_select.mode
    state:
    - Day
    - Evening
  - condition: or
    conditions:
    - condition: not
      conditions:
      - condition: trigger
        id: living_room
    - condition: state
      entity_id: media_player.android_tv
      state: unavailable
  action:
  - if:
    - condition: template
      value_template: '{{ trigger.to_state.state == ''on'' }}'
    then:
    - if:
      - condition: template
        value_template: '{{ states(speaker) != ''playing'' and states.media_player
          | selectattr(''attributes.group_members'', ''defined'') | selectattr(''state'',
          ''equalto'', ''playing'') | first | default(none) != None }}'
      then:
      - service: media_player.volume_set
        data:
          entity_id: '{{ speaker }}'
          volume_level: '{% set oldplayer = (states.media_player | selectattr(''attributes.group_members'',
            ''defined'') | selectattr(''state'', ''equalto'', ''playing'') | first)
            %} {{ oldplayer.attributes.volume_level * volume_adjustment[speaker.split(''.'')[1]]
            / volume_adjustment[oldplayer.object_id] }}'
      - service: media_player.join
        data:
          entity_id: '{{ (states.media_player | selectattr(''attributes.group_members'',
            ''defined'') | selectattr(''state'', ''equalto'', ''playing'') | first).attributes.group_members[0]
            }}'
          group_members: '{{ speaker }}'
    else:
    - if:
      - condition: template
        value_template: '{{ states(speaker) == ''playing'' and state_attr(speaker,
          ''group_members'') | count > 1 }}'
      then:
      - service: media_player.unjoin
        data:
          entity_id: '{{ speaker }}'
  mode: queued
  max: 10
2 Likes

Hey Tomas1, thanks. That is very helpful. I think the master variable is to keep the audio consistent throughout the rooms, so that if you walk into room 3 for example it will play the audio of the master controller and wont start playing whatever was playing last on that particular speaker, and it gets the audio levels from the master, but I agree there is better way to do that now as the media_player service seems more robust then it was 2 years ago.

Care to post your input_boolean, input_select, scripts, and automations yaml files? I’m not having any luck with what I have found thus far. Thanks in advance