Grouping Sonos Speakers

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:

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