Grouping Sonos Speakers

@Darbos I looked for it too, doesn’t look like it made it into 0.67. Not sure why (I’m not the dev) but for now, you can keep running the custom component.

I appreciate it, was just curious.

@Jer78 I have been looking at this solution for some time, and now it seems that grouping capabilities made it into the 0.69 update :slight_smile: https://github.com/home-assistant/home-assistant/pull/13553

Is there a chance that you are going to revise your scripts to incorporate this grouping scheme into this nice solution? I would be first in line to do some trying and testing :slight_smile:

@Flemming Yes, it’s posted above…and as you mentioned since 0.69 you no longer need to use the custom component.

Aaah, I think i get it now. Sorry :slight_smile: i’m new to all this, running hassio with not too much messing around. I will give it a shot during the coming weekend.

Thanks <3

1 Like

@Darbos

Here’s a script for joining and unjoining speakers by voice as I promised. You’d just have to expose the scripts to Alexa, however you’d like (emulated_hue, Alexa cloud, or Node-Red)

  alexa_join_living_room:
    sequence:
      - service: media_player.sonos_join
        data_template:
          master: '{{states.sensor.sonos_group_master.state}}'
          entity_id: media_player.sonos_living_room
      - delay:
          seconds: 2
      - condition: template
        value_template: '{% if states.media_player.sonos_living_room.state != "playing" %}true{%endif%}'
      - service: media_player.media_play
        data_template:
          entity_id: '{{states.sensor.sonos_group_master.state}}'

  alexa_unjoin_living_room:
    sequence:
      - service: media_player.sonos_unjoin
        entity_id: media_player.sonos_living_room
1 Like

Any chance that you could post a Node-RED example in raw text if you have one? I just started dabbling with Node-RED and quite confused on how this all works. Thanks

@berniebl

I use the Alexa-Local plugin. Here’s the export and a pic of what it looks like…

54%20PM

[{"id":"577d210.567686","type":"api-call-service","z":"110d3f7c.322759","name":"Living Room Speaker On","server":"85fe7d0f.5ecf7","service_domain":"script","service":"turn_on","data":"{\"entity_id\":\"script.alexa_join_living_room\"}","x":450,"y":2040,"wires":[]},{"id":"dbeb0490.360678","type":"alexa-local","z":"110d3f7c.322759","devicename":"Living Room Speaker","inputtrigger":false,"x":120,"y":2060,"wires":[["de740a3c.c92e48"]]},{"id":"de740a3c.c92e48","type":"switch","z":"110d3f7c.322759","name":"Switch","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"on","vt":"str","case":true},{"t":"regex","v":"off","vt":"str","case":true}],"checkall":"true","outputs":2,"x":250,"y":2060,"wires":[["577d210.567686"],["39af6e4d.42311a"]]},{"id":"39af6e4d.42311a","type":"api-call-service","z":"110d3f7c.322759","name":"Living Room Speaker Off","server":"85fe7d0f.5ecf7","service_domain":"script","service":"turn_on","data":"{\"entity_id\":\"script.alexa_unjoin_living_room\"}","x":450,"y":2080,"wires":[]},{"id":"85fe7d0f.5ecf7","type":"server","z":"","name":"Home Assistant","url":"redacted","pass":"Redacted"}]

Perfect, thank you very much … I will give it a try tonight

Awesome, thanks again!

Wow, this is great. I implemented this and it worked right away. I feel like I can learn a lot by studying this code. Thank you!

@Jer78

I couldn’t agree more! However I obviously still have much to learn from the code because for the life of me I cannot work why my script needs to execute twice in order to perform the group/ungroup action.

I have looked over the code many times and I am sure I have copied it and tailored it to my player names correctly.

Can you please offer any suggestions?

Input Booleans (unfortunately I don’t have a swimming pool, it is a pool table :slight_smile:)

input_boolean:
  kitchen:
    name: Kitchen
  tv_room:
    name: TV Room
  dining_room:
    name: Dining Room
  pool_room:
    name: Pool Room

Input selects

input_select:
  master_sonos:
    name: Master Sonos Player
    icon: mdi:itunes
    options:
      - Kitchen
      - TV Room
      - Dining Room
      - Pool Room

Sensors

sensor:
  - platform: template
    sensors:
      sonos_join_speakers:
        entity_id:
          - media_player.tv_room
          - media_player.kitchen
          - media_player.dining_room
          - media_player.pool_room
        value_template: >-
          {% set master = states.sensor.sonos_group_master.state %}
          {% for player in states.media_player -%}
            {%- set domain, device = player.entity_id.split('.') -%}
            {%- set new_domain = "input_boolean" -%}
            {%- set switch = [new_domain,device] | join(".") -%}
            {%- if is_state(switch, 'on') and player.entity_id != master -%}
              {{player.entity_id}},
            {%- endif -%}
          {%- endfor %}

      sonos_unjoin_speakers:
        entity_id:
          - media_player.tv_room
          - media_player.kitchen
          - media_player.dining_room
          - media_player.pool_room
        value_template: >-
          {% set master = states.sensor.sonos_group_master.state %}
          {% for player in states.media_player -%}
            {%- set domain, device = player.entity_id.split('.') -%}
            {%- set new_domain = "input_boolean" -%}
            {%- set switch = [new_domain,device] | join(".") -%}
            {%- if is_state(switch, 'off') and player.entity_id != master -%}
              {{player.entity_id}},
            {%- endif -%}
          {%- endfor %}

      sonos_group_master:
        entity_id:
          - media_player.kitchen
          - media_player.tv_room
          - media_player.dining_room
          - media_player.pool_room
        value_template: >-
          {%- for player in states.media_player if player.state == 'playing' and player.attributes.sonos_group %}
            {% if player.attributes.sonos_group[0] == player.entity_id %}
              {{player.entity_id}}
            {% endif %}
          {% endfor -%}

      sonos_not_group_slaves:
        friendly_name: Not Group Slaves
        entity_id: sensor.sonos_group_slaves
        value_template: >-
          {% for player in states.media_player if player.attributes.sonos_group %}
          {% set n = player.attributes.sonos_group %}
            {%- if n|length == 1 -%}
              {{player.entity_id}},
            {%- endif -%}
          {%- endfor %}

      sonos_group_slaves:
        friendly_name: Group Slaves
        entity_id: sensor.sonos_group_master
        value_template: >-
          {%- for player in states.media_player if player.state == 'playing' and 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 %}

Automation

automation:

  - alias: 'Set Sonos States'
    initial_state: on
    trigger:
      - platform: state
        entity_id: sensor.sonos_group_slaves
      - platform: state
        entity_id: sensor.sonos_not_group_slaves
      - platform: state
        entity_id: sensor.sonos_group_master
      - platform: homeassistant
        event: start
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: '{% if states.sensor.sonos_group_slaves.state != "" and states.sensor.sonos_group_master.state != "" %}true{%endif%}'
    action:
      - service: input_select.select_option
        data_template:
          entity_id: input_select.master_sonos
          option: >-
            {% set master = states.sensor.sonos_group_master.state %}
            {% if "media_player.kitchen" in master %}Kitchen
            {% elif "media_player.tv_room" in master%}TV Room
            {% elif "media_player.dining_room" in master%}Dining Room
            {% elif "media_player.pool_room" in master%}Pool Room
            {% endif %}
      - service: input_boolean.turn_on
        data_template:
          entity_id: '{{states.sensor.sonos_group_slaves.state}}'
      - condition: template
        value_template: '{% if not is_state("sensor.sonos_not_group_slaves", "")%}true{%endif%}'
      - service: input_boolean.turn_off
        data_template:
          entity_id: '{%- set slaves = states.sensor.sonos_not_group_slaves.state -%}{{slaves[:-1]|replace("media_player","input_boolean")}}'

Script

  sonos_group_speakers:
    sequence:
      - service: media_player.sonos_join
        data_template:
          master: >-
            {% if is_state("input_select.master_sonos", "Kitchen") %}media_player.kitchen
            {% elif is_state("input_select.master_sonos", "TV Room") %}media_player.tv_room
            {% elif is_state("input_select.master_sonos", "Dining Room") %}media_player.dining_room
            {% elif is_state("input_select.master_sonos", "Pool Room") %}media_player.pool_room
            {% endif %}
          entity_id: '{%- set slaves = states.sensor.sonos_join_speakers.state -%}{{slaves[:-1]}}'

      - service: media_player.sonos_unjoin
        data_template:
          entity_id: '{%- set slaves = states.sensor.sonos_unjoin_speakers.state -%}{{slaves[:-1]}}'

@klogg So the reason why you need to perform 2 actions for the group/ungroup is because it’s basically add and delete. Let’s say you have 3 speakers and speaker 1 and 2 are already grouped but you want to use 1 and 3 instead. Executing this will run the group script to add speaker 3 and a second script to ungroup speaker 2. Hope that helps

I’m not sure.

Thanks for the reply I really appreciate it but I think you are telling me that to change a group from one set of speakers to another is a two step process which, if you are, I understand and would then mean that I wasn’t clear in describing my situation, which is:-

If I start with no speakers joined and then join two speakers together I have to execute the script twice in order for HA to perform this one action. If I then unjoin these two speakers I again have to execute the script twice for this to happen.

Watching the sonos app on my PC I see that something is happening every time I execute the script but the state of the speaker groups is always left unchanged after the first execution.

I hope that makes more sense?

EDIT: I think you can ignore this next bit about sensors as I hadn’t realised they don’t ‘sense’ anything unless they are playing and much of my testing has been done without anything playing :roll_eyes:

I still need to activate the script twice though even with music playing…


I have also now noticed the following with the sensors which doesn’t seem right?

  • sonos_group_master is almost always empty (apart from twice briefly, and I have been playing around with this a lot), and sometimes set to ‘unknown

  • sonos_group_slaves is also always empty except when set to ‘unknown’ at the same times as sonos_group_master is set to ‘unknown’.

I hope this all makes sense and thanks for your help.

Changes are welcome. Unfortunately, it has to be this way unless the component service is updated by a HA dev that when grouping the speakers it also ungroups anything not in the group. Or the second option is to write a complex condition that checks and sees if the current speakers you want to group are already in the group and then no need to run the ungroup script.

Also I haven’t used this much and there may have been some changes to the Sonos component in the last few months that also impacted how this works. I’d have to look at it more when I get some time.

I don’t know exactly what the issue is, but you have not implemented this exactly as it was outlined.

I took a quick look and found this right away:
What is is supposed to be:

sensor:
  platform: template
    sensors:
      sonos_join_speakers:
        entity_id:
          - input_boolean.sonos_living_room_slave
          - input_boolean.sonos_guest_room_slave
          - input_boolean.sonos_office_slave
          - input_boolean.sonos_master_bedroom_slave
          - input_boolean.sonos_basement_slave

What you have:

sensor:
  - platform: template
    sensors:
      sonos_join_speakers:
        entity_id:
          - media_player.tv_room
          - media_player.kitchen
          - media_player.dining_room
          - media_player.pool_room

I didn’t keep looking after this, but if this wasn’t copied correctly, then there may be other differences as well. You have media_players instead of input_booleans.

1 Like

@RonJ103

I believe you are comparing with the original version which I understand became much simplified following an update to the Sonos component in 0.69.

See here :-


@Jer78
Ok, thanks. No problem I was just a little confused by what was going on. If it is ‘by design’ then that is fine. Thanks for all the work you put into this. Who knows? Maybe I might find some time to look into that ‘complex condition’ myself. If nothing else I have learnt few things by looking at your code as my coding background is much more old school :wink:

1 Like

Hey there, complete noob here. I have been trying this in my developer hassio image and have not had any success. I used that hassio image and placed everything with the config file ( removed the call outs to automation, scripts, groups…etc.). I have debugged it to this error:

"16:47:43 WARNING (Recorder) [homeassistant.components.recorder] Ended unfinished session (id=4 from 2018-08-17 16:45:21.686237)
2018-08-17 16:47:44 WARNING (MainThread) [homeassistant.components.http] You have been advised to set http.api_password.
2018-08-17 16:48:05 WARNING (MainThread) [homeassistant.setup] Setup of config is taking over 10 seconds.
2018-08-17 16:48:21 WARNING (MainThread) [homeassistant.components.input_select] Invalid option: (possible options: Bathroom, Brandon Bedrooom)
2018-08-17 16:48:21 ERROR (MainThread) [homeassistant.core] Invalid service data for input_boolean.turn_on: Entity ID is an invalid entity id for dictionary value @ data[‘entity_id’]. Got ‘’
2018-08-17 16:48:21 ERROR (MainThread) [homeassistant.core] Invalid service data for input_boolean.turn_off: Entity ID is an invalid entity id for dictionary value @ data[‘entity_id’]. Got ‘’
"

Sorry for how ugly this is going to look, but here is the config file:

# Attempt to Change Sonos

input_boolean:
sonos_bathrooom_slave:
name: Bathroom
sonos_brandon_bedroom_slave:
name: Brandon Bedroom

input_select:
master_sonos:
name: Master Sonos Player
icon: mdi:itunes
options:
- Bathroom
- Brandon Bedrooom
#Senor
sensor:

  • platform: yr

  • platform: template
    sensors:
    sonos_join_speakers:
    entity_id:
    - input_boolean.sonos_bathroom_slave
    - input_boolean.sonos_brandon_bedroom_slave
    value_template: >-
    {% set master = states.sensor.sonos_group_master.state %}
    {% for player in states.media_player -%}
    {%- set domain, device = player.entity_id.split(’.’) -%}
    {%- set new_domain = “input_boolean” -%}
    {%- set new_device = [device, “slave”]|join("") -%}
    {%- set switch = [new_domain,new_device] | join(".") -%}
    {%- if is_state(switch, ‘on’) and player.entity_id != master -%}
    {{player.entity_id}},
    {%- endif -%}
    {%- endfor %}
    sonos_unjoin_speakers:
    entity_id:
    - input_boolean.sonos_brandon_bedroom_slave
    - input_boolean.sonos_bathroom_slave
    value_template: >-
    {% set master = states.sensor.sonos_group_master.state %}
    {% for player in states.media_player -%}
    {%- set domain, device = player.entity_id.split(’.’) -%}
    {%- set new_domain = “input_boolean” -%}
    {%- set new_device = [device, “slave”]|join("
    ") -%}
    {%- set switch = [new_domain,new_device] | join(".") -%}
    {%- if is_state(switch, ‘off’) and player.entity_id != master -%}
    {{player.entity_id}},
    {%- endif -%}
    {%- endfor %}
    sonos_not_group_slaves:
    friendly_name: Not Group Slaves
    value_template: >-
    {% for player in states.media_player if player.attributes.is_coordinator -%}
    {%- if loop.last -%}{{player.entity_id}}–
    {%- else -%}{{player.entity_id}};
    {%- endif -%}
    {%- endfor %}
    sonos_group_slaves:
    friendly_name: Group Slaves
    value_template: >-
    {% for player in states.media_player if player.state == ‘playing’ and not player.attributes.is_coordinator -%}
    {%- if loop.last -%}{{player.entity_id}}–
    {%- else -%}{{player.entity_id}};
    {%- endif -%}
    {%- endfor %}

    sonos_group_song:
    friendly_name: Group Song
    value_template: >-
    {% for player in states.media_player if player.state == ‘playing’ and not player.attributes.is_coordinator %}
    {{player.attributes.media_title}}
    {% endfor %}
    sonos_group_master:
    entity_id:
    - sensor.sonos_group_song
    value_template: >-
    {% set group_song = states.sensor.sonos_group_song.state %}
    {% for player in states.media_player if player.state == ‘playing’ and player.attributes.is_coordinator and player.attributes.media_title in group_song %}
    {{player.entity_id}}
    {% endfor %}
    #Automation
    automation:

  • alias: ‘Set Sonos States’
    initial_state: on
    trigger:

    • platform: state
      entity_id: sensor.sonos_group_slaves
    • platform: state
      entity_id: sensor.sonos_not_group_slaves
    • platform: state
      entity_id: sensor.sonos_group_master
    • platform: homeassistant
      event: start
      action:
    • service: input_select.select_option
      data_template:
      entity_id: input_select.master_sonos
      option: >-
      {% set master = states.sensor.sonos_group_master.state %}
      {% if master == “media_player.sonos_bathroom_room”%}Bathroom
      {% elif master == “media_player.sonos_brandon_bedrooom_room”%}Brandon Bedroom
      {% endif %}
    • service: input_boolean.turn_on
      data_template:
      entity_id: ‘{%- set slaves = states.sensor.sonos_group_slaves.state -%}{{slaves|replace("–", “_slave”)|replace(";", "_slave, ")|replace(“media_player”, “input_boolean”)}}’
    • service: input_boolean.turn_off
      data_template:
      entity_id: ‘{%- set slaves = states.sensor.sonos_not_group_slaves.state -%}{{slaves|replace("–", “_slave”)|replace(";", "_slave, ")|replace(“media_player”, “input_boolean”)}}’
      #Script
      script:
      sonos_group_speakers:
      sequence:
    • service: media_player.sonos_join
      data_template:
      master: >-
      {% if is_state(“input_select.master_sonos”, “Bathroom”) %}media_player.sonos_bathroom
      {% elif is_state(“input_select.master_sonos”, “Brandon Bedroom”) %}media_player.sonos_brandon_bedroom
      {% endif %}
      entity_id: ‘{%- set slaves = states.sensor.sonos_join_speakers.state -%}{{slaves[:-1]}}’
    • service: media_player.sonos_unjoin
      data_template:
      entity_id: ‘{%- set slaves = states.sensor.sonos_unjoin_speakers.state -%}{{slaves[:-1]}}’
      #Script
      group:
      sonos_grouping_options:
      view: no
      name: Group Sonos Players
      control: hidden
      entities:
    • input_select.master_sonos
    • input_boolean.sonos_bathroom_slave
    • input_boolean.sonos_brandon_bedroom_slave
    • script.sonos_group_speakers

I have verified the names of the sonos devices through the entity registry.
media_player.brandon_bedroom:
media_player.bathroom:

Thank you all in advance for the help!

(Sorry for the formatting, i’m searching now to see if I can fix that)

Hi there, complete noob here.
I’m starting the automations in my house. I have a Sonos system, which consists 1xBeam
1xSub
2xOne
but they are all grouped to surround sound and the two sonos one are set to the back speaker right and left. With the oficial application sometimes I take 1xsonos one and take it to another division of the house and then just add it back to the group as back speaker right or left.
With this setting can I do the same through the home assistant or just play simultaneously on all?

Sorry for my english,
Thanks um advance,

Best Regards,

I’m really not sure what happens when speakers are grouped together within the Sonos app as a stereo setup. Does the Sonos component show all the speakers separately as media_players?