Grouping Sonos Speakers

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

@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: