Grouping Sonos Speakers

UPDATE: This project isn’t really supported going forward but it does work on my setup as of Jan 11, 2019. However, this is a better solution using the Mini Media Player component that I’d much prefer to recommend over here.

I finally got this working pretty well with some help from @petro so I thought I’d share in case anyone else is looking to have grouping capabilities of their Sonos speakers in HA.

1 - You’ll want to use a similar naming convention for your sonos speakers. Since you can set these up using the entity_registry I use media_player.sonos_room_name.

2 - Next, you’ll need to manually create input_booleans for every Sonos Speaker you want to control in groups. Just add _slave onto the end. Example media_player.sonos_living_room becomes input_boolean.sonos_living_room_slave:

  input_boolean: 
    sonos_master_bedroom_slave:
      name: Master Bedroom
    sonos_guest_room_slave:
      name: Guest Room
    sonos_living_room_slave:
      name: Living Room
    sonos_basement_slave:
      name: Basement
    sonos_office_slave:
      name: Office

3 - Make an input_select with your speakers, this will act as selecting the master.

input_select:
  master_sonos:
    name: Master Sonos Player
    icon: mdi:itunes
    options:
      - Living Room
      - Basement
      - Master Bedroom
      - Guest Room
      - Office

4 - add the following sensors and under the entity_id’s add the input_booleans you created in the previous step. The rest of the sensor is not hardcoded so you shouldn’t have to modify anything else.

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
        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_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
        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 %}

5 - Now we will create an automation that gets the state based on the sensors and turns the input_booleans on or off depending on the current state. You’ll just have to modify the master_sonos with your speakers and options from the input_select.

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_living_room"%}Living Room
            {% elif master == "media_player.sonos_guest_room"%}Guest Room
            {% elif master == "media_player.sonos_master_bedroom"%}Master Bedroom
            {% elif master == "media_player.sonos_office"%}Office
            {% elif master == "media_player.sonos_basement"%}Basement
            {% 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")}}'

6 - Next step is to make a script that you’ll execute when you manually turn on or off speakers. Again, you’ll have to edit the master with your options in the input_select.

script:
  sonos_group_speakers:
    sequence:
      - service: media_player.sonos_join
        data_template:
          master: >-
            {% if is_state("input_select.master_sonos", "Living Room") %}media_player.sonos_living_room
            {% elif is_state("input_select.master_sonos", "Basement") %}media_player.sonos_basement
            {% elif is_state("input_select.master_sonos", "Guest Room") %}media_player.sonos_guest_room
            {% elif is_state("input_select.master_sonos", "Master Bedroom") %}media_player.sonos_master_bedroom
            {% elif is_state("input_select.master_sonos", "Office") %}media_player.sonos_office
            {% 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]}}'

7 - Finally, add a group with everything.

group:
  sonos_grouping_options:
    view: no
    name: Group Sonos Players
    control: hidden
    entities:
      - input_select.master_sonos
      - input_boolean.sonos_basement_slave
      - input_boolean.sonos_living_room_slave
      - input_boolean.sonos_master_bedroom_slave
      - input_boolean.sonos_guest_room_slave
      - input_boolean.sonos_office_slave
      - script.sonos_group_speakers
18 Likes

I like it! I plan on using this below some custom ui tiles I have set up to play Sonos favorites. If I can figure out how to get the custom ui display ‘currently playing’ along with some basic controls I think it will make a great solution as opposed to having 5x media players!

So what does this exactly do? I don’t own sonos so I’m not sure how they operate and this looks quite interesting. Also, looking at the UI, I’m wondering if you could add a relative master volume control and a static master volume control. Relative meaning it moves all volume sliders in relation to their current postion, and static meaning that it sets them all to the same value.

So right now with the Sonos component, you can join and unjoin speakers with a service but the problem is, you can’t tell which speakers are currently in a group. There’s only an attribute called is_cooridinator. It will be true if it’s in a group and the master or if it’s not in a group and playing on its own. So the only real way to figure out a group is to first search for players that is_coordinator is false, then get the song name, finally find the one that’s true playing the same song. It’s a real roundabout way of doing this but currently the only way given the current state of this component. Hopefully one day it’s not needed but for now…it’s nice to control groups from HA. As for a master volume, maybe once you know which players are in the group send a volume control +/- to all the speakers. But as for reading the current state of mixed volumes might be difficult.

Is this because of the naming of the input_boolean’s?

@Coolie1101
Yes, but you could name the input_booleans the same and not have to add _slave to the end. I just already had some names that.

Also You may want to wait before adding this as there is some changes coming to the Sonos component soon. Probably in the next release that will impact this project. It is a little simpler after the update.

Okay.

I’m having a little trouble understanding the following statement.

Isn’t the names pulled from the players when added to HA?

I’m having difficulty having the volume sliders showing… just about everything else shows up though.

@joshluster See this thread for volumes

1 Like

@Coolie1101 Yes, the media_players are pulled from HA, but the way it’s set up is the sensors look for media_players with the same name as the input_booleans. You can change the name to whatever you’d like but you’d need to modify the sensor so it matches them too.

In other words, if your Sonos player is called media_player.living_room, this template will look for an input_boolean.living_room_slave.

Got it, thanks.

Very cool! Nice work. Any chance you mind sharing how you got the sonos images for icons?

any idea what im missing here? my media_players are named like media_player.basement.

I am getting some errors like it is not getting the entity id for some reason. Ive been starring at this trying to find where it is messing up and cant narrow it down. I believe it is in the sensor. Since your were named a little differnet im wondering if i missed a change somewhere. I have a lot of other things tied to those entity names and it would be too much to change like you have. If you have a minute to take a look it would be appreciated.

Sensor:
- platform: template
  sensors:
    sonos_join_speakers:
      entity_id:
        - input_boolean.living_room_slave
        - input_boolean.bedroom_slave
        - input_boolean.basement_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.living_room_slave
        - input_boolean.bedroom_slave
        - input_boolean.basement_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 %}


script:
sonos_group_speakers:
  sequence:
    - service: media_player.sonos_join
      data_template:
        master: >-
          {% if is_state("input_select.master_sonos", "Living Room") %}media_player.living_room
          {% elif is_state("input_select.master_sonos", "Basement") %}media_player.basement
          {% elif is_state("input_select.master_sonos", "Bedroom") %}media_player.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]}}'


automations:
- 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.living_room"%}Living Room
          {% elif master == "media_player.bedroom"%}Bedroom
          {% elif master == "media_player.basement"%}Basement
          {% 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")}}'


input_select:
master_sonos:
  name: Master Sonos Player
  icon: mdi:itunes
  options:
    - Living Room
    - Basement
    - Bedroom

@darbos at first glance I can’t see anything wrong. What errors are you getting? It would be helpful to see those. Also, do you plan on updating right away to 0.67 when it comes? Because there are changes coming to the Sonos component with the grouping and you’ll have to change this if you plan on updating.

lol sorry about that. I meant to include them, too much pasting in forgot :slight_smile:

Thats good to know I appreciate it. It really depends on what is released with the new updates if I go to them. If it something useful for me then I usally do them, or Ill take a snapshot in vmware, upgrade and see how it goes then if i need to I can revert. I am on 65.6 right now, I tried 66.0 but I just had that small bug with my ecobee, I believe they fixed it in 66.1 but I havent upgraded. Now knowing that sonos will be upated in 67 Ill just hold off and upgrade to upgrade until then. Still great work here!

I do have some family and friends coming in town this weekend and was trying to show off :slight_smile:

If you see something pop out Id appreciate it, otherwise I can hang tight I dont want to waste your time.

IndexError: list index out of range
2018-04-11 19:10:56 WARNING (MainThread) [homeassistant.components.input_select] Invalid option:  (possible options: Select Master, Living Room, Basement, Bedroom)
2018-04-11 19:10:56 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 ''

@Darbos Ok, yeah that error came up for me too. Here’s what I suggest, if you’re interested for something this weekend. Create a custom component using the latest sonos.py

Put this in .homeassistant/custom_components/media_player/ directory. If you don’t have it, create it. This way you can stay at 65.6 but use the latest Sonos.

Then here’s my latest yaml using this update. It’s a bit simplier.

Template Sensors:

  sonos_join_speakers:
    entity_id:
      - media_player.living_room
      - media_player.bedroom
      - media_player.basement
    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.living_room
      - media_player.bedroom
      - media_player.basement
    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.living_room
      - media_player.bedroom
      - media_player.basement
    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 %}

Use these Input Booleans instead:

  living_room:
    name: Living Room
  basement:
    name: Basement
  bedroom:
    name: Bedroom

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.living_room" in master %}Living Room
            {% elif "media_player.bedroom" in master%}Bedroom
            {% elif "media_player.basement" in master%}Basement
            {% 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")}}'

Input Select:

  master_sonos:
    name: Master Sonos Player
    icon: mdi:itunes
    options:
      - Living Room
      - Basement
      - Bedroom

Script:

  sonos_group_speakers:
    sequence:
      - service: media_player.sonos_join
        data_template:
          master: >-
            {% if is_state("input_select.master_sonos", "Living Room") %}media_player.living_room
            {% elif is_state("input_select.master_sonos", "Basement") %}media_player.basement
            {% elif is_state("input_select.master_sonos", "Bedroom") %}media_player.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]}}'

@Darbos and here’s the images I use for Sonos speakers. Put them in your www directory and reference them to your input_booleans in the customize section like this:

input_boolean.living_room:
  entity_picture: /local/sonos_playbar.png

sonos_one_blksonos_onesonos_playbarsonos_play_1sonos_playbasesonos_connect_new

man your awesome. took the time to write it with my players and all, thank you very much!

Its working now I really appreciate it! I see now in the attributes it shows you sonos_group now. that is great!

@Darbos Perfect. Let me know if you see any issues.

1 Like

Hey I was looking through the 67.0 notes and the sonos component page but dont see a mention of any changes to the grouping abilities? Did it not make it into 67? Thanks