Sonos group speaker and play based on defined group-helpers - Help!

Hello,
I have a test automation here that is failing here:

Entity ID group.media_player.basement_all is an invalid entity ID @ data[‘group_members’][0]

The idea is once that sensor triggers presence, then media_player.office would join all the basement speakers as defined in the helper group I’ve created separately with media_player.basement_all as its name.

I’m uncertain if I can have a helper group (media_player.basement_all) which I have created under helper in my code where I do - but I’m sort of at my wit’s end!

alias: Speaker test 2
description: ""
trigger:
  - type: occupied
    platform: device
    device_id: 3047c3c98f572a2822e8248e1ac7c651
    entity_id: binary_sensor.office_mm_wave_sensor_58492e_presence
    domain: binary_sensor
condition:
  - condition: or
    conditions:
      - condition: state
        entity_id: media_player.upstairs
        state: playing
      - condition: state
        entity_id: media_player.main_floor
        state: playing
      - condition: state
        entity_id: media_player.basement_all
        state: playing
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: media_player.basement_all
            state: playing
        sequence:
          - service: sonos.snapshot
            data:
              entity_id: media_player.office
              value: media_player.basement_all
          - service: media_player.join
            data:
              group_members:
                - group.media_player.basement_all
            target:
              entity_id: media_player.office
          - delay: "00:00:02"
          - service: sonos.restore
            data:
              entity_id: media_player.office
mode: single

I also used

media_player.basement_all

instead of

group.media_player.basement_all

and still a no go.
The automation is quite primitive but unless this part works then I can’t make it more complicated.

Any help is appreciated - many thanks.

I don’t use Sonos… so I have no way to check this, but the value for group_members should be a list of media player entities. Try extracting them using the entity_id attribute in a template.

service: media_player.join
data:
  group_members: "{{ state_attr('group.media_player.basement_all', 'entity_id') }}"
target:
  entity_id: media_player.office
1 Like

Thank for your reply. So I disabled the snapshot and restore just to see if it would simply join - and no cigar. With your changes. Automation actually finished without errors but office speaker did not join in:

alias: Speaker test 2
description: ""
trigger:
  - type: occupied
    platform: device
    device_id: 3047c3c98f572a2822e8248e1ac7c651
    entity_id: binary_sensor.office_mm_wave_sensor_58492e_presence
    domain: binary_sensor
condition:
  - condition: or
    conditions:
      - condition: state
        entity_id: media_player.upstairs
        state: playing
      - condition: state
        entity_id: media_player.main_floor
        state: playing
      - condition: state
        entity_id: media_player.basement_all
        state: playing
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: media_player.basement_all
            state: playing
        sequence:
          - service: sonos.snapshot
            data:
              entity_id: media_player.office
              value: media_player.basement_all
            enabled: false
          - service: media_player.join
            data:
              group_members: "{{ state_attr('group.media_player.basement_all', 'entity_id') }}"
            target:
              entity_id: media_player.office
          - delay: "00:00:02"
          - service: sonos.restore
            data:
              entity_id: media_player.office
            enabled: false
mode: single

Are you sure your entity IDs are correct? I didn’t catch it before, but group.media_player.basement_all is not a standard entity ID. A media player group setup in the UI helper menu would start with media_player whereas a “old-style” group set up in yaml configuration would start with group.

The error message is complaining about the fact you failed to supply the group_members option with a list of the media_player entities you want to join together as a group. You gave it a group entity and that’s invalid.

In addition, the documentation for the sonos.snapshot service call doesn’t show an option named value. It simply requires the entity_id of a media_player entity.

If want to snapshot several media_players that are already joined in a group, you simply specify the group’s master media_player (it’s the first media_player entity listed in the group_members attribute).

Incorrect entity reference:

group_members: "{{ state_attr('group.media_player.basement_all', 'entity_id') }}"
                               ^^^^^^^^^^^^^^^^^^

Also, what is your automation’s ultimate goal? Because currently it seems to go through a few actions that ultimately achieve nothing:

  • Create snapshot
  • Join some media_players
  • Wait 2 seconds
  • Restore from snapshot

Okay! thank you @123 and @Didgeridrew for your replies. Drew, I had misunderstood how to format the group in the template and had written group before it because I thought I had to. As Soon as I removed group, that was fine and it worked. Taras, I have have also misunderstood what the snapshot does, as it now seems to just pause and you’re right, do nothing. My goal was to:

  1. create snapshot (which in my understanding is basically see what’s happening, what’s not, but maybe I’m wrong there)
  2. Have “office” join the basement group
  3. Wait 2 seconds (I believe this is for syncing properly, but again, I could be wrong)
  4. Have the group + office resume playing whatever was playing.

Nothing is happening because I don’t think they are resuming playback. In my mind, since I never paused playback I didn’t have to resume it, but I guess I do, right?
It’s entirely possible that I’m going about this the wrong way too, but as mentioned it’s just a test automation, once it works and I know which codes to use (and more importantly not use) I can scale and write it better.

sonos.restore puts everything back to the way it was in the 1st step when the snapshot was recorded. In other words, before any media_players were joined.

If you simply want to join one media_player to another (or to an existing collection of joined media_players) simply use media_player.join. You only need to use sonos.snapshot if your intention is put everything back to its original state.

I suggest you review the documentation for the Sonos integration, especially its service calls.

Thanks - so the only reason I was using sonos.snapshot was that Sonos integration mentions that “Take a snapshot of what is currently playing on one or more speakers;” which is how I thought I’d save the queue.

I removed .snapshot and also .restore, but unfortunately the music simply stops when office joins the rest of the desired speakers with the queue becoming empty. To be more clear, it stops right away, and does not finish the current song either and the queue empties immediately.

I’ve tried adding in:

  1. sonos.play_queue
  2. and separately media_player.select_source (hoping the queue was preserved)
  3. and media_player. media_play (but a little confused here as the page requires a media_content_type and media_content_id which would be dynamic from whatever is coming out of a speaker group at that time.)

Did you read the note in the documentation? The one that states the queue is not recorded by the snapshot?

I know that a Sonos speaker can be joined to an existing set of Sonos speakers that are currently playing something without interruption. I do it all the time with the Sonos Voice Assistant (‘Play here too’ ). That means their capable of it.

Having said that, I have never had a need to replicate it in an automation so I can’t tell you the exact steps to take.

What I do have is a script designed to play announcements to a desired collection of speakers. It involves taking a snapshot of the current master speaker, pausing whatever it’s currently playing (if anything), unjoining whatever is currently joined together, joining together a new collection of speakers, adjusting volume, playing the announcement, then restoring the snapshot.

The main difference from your requirements is that the script purposely pauses the currently playing track in order to play an announcement on a different collection of speakers and then continues playing the original track on the original collection of speakers.

Okay so mini-update:

  1. I tried doing what I wanted to do in Node-RED and it works, queue is preserved and music doesn’t stop, but scaling it up on Node-RED means learning a whole new platform, and I don’t know, something is keeping me in visual-editor-YAML combo. But now I know it can be done!

  2. I modified the code, and now the speakers actually resume playing after one joins. Hurray! But…

  3. … the problem is clearing queue remains:

action:
  - choose:
      - conditions:
          - condition: state
            entity_id: media_player.basement_all
            state: playing
        sequence:
          - service: sonos.remove_from_queue
            entity_id: media_player.office
            enabled: false
          - service: media_player.join
            data:
              group_members: "{{ state_attr('media_player.basement_all', 'entity_id') }}"
            target:
              entity_id: media_player.office
          - delay:
              seconds: 2
          - service: media_player.media_play
            entity_id: media_player.basement_all
mode: single

Note it also didn’t work without the sonos.remove_from_queue service either. I was just testing things out. Appreciate more input if anyone else has any ideas. :slight_smile:

Not sure why you’re exploring the use of sonos.remove_from_queue when all that’s needed is media_player.join.

I used the following script to test the ability of adding a media_player to an existing group of media_players (that are playing music) without interrupting what they’re currently playing. It worked perfectly.

  add_to_group:
    alias: add_to_group
    sequence:
    - variables:
        existing_group_members: "{{ state_attr('media_player.one', 'group_members') }}"
        new_group_member: media_player.three
    - service: media_player.join
      target:
        entity_id: "{{ existing_group_members | first }}"
      data:
        group_members: "{{ existing_group_members + [new_group_member] }}"
    mode: single

Explanation:

  • Current conditions: media_player.one and media_player.two are joined and playing music.

  • I want to join media_player.three to media_player.one and media_player.two without interrupting their playback.

  • To achieve the goal, the entity_id option, of media_player.join, should be assigned the entity_id of the group’s master media_player. The master is the first media_player listed in a media_player’s group_members attribute.

  • The group_members option, of media_player.join, should contain a list of all desired media_players. In other words, it must be a list of all three media_players. We use a template to append media_player.three to the other two media_players.


In your case, if media_player.basement_all is currently joined to other media_players and they’re all playing music, and your goal is to join media_player.office to them, then here’s how:

    - variables:
        existing_group_members: "{{ state_attr('media_player.basement_all', 'group_members') }}"
        new_group_member: media_player.office
    - service: media_player.join
      target:
        entity_id: "{{ existing_group_members | first }}"
      data:
        group_members: "{{ existing_group_members + [new_group_member] }}"
1 Like

Many thanks for your new code and explanation. Quick question - if I have media_player.basement_all defined as a helper in HA, should it be recognized here? Because as it wasn’t working, with a traces error: Error rendering service target template: TypeError: 'NoneType' object is not iterable I added this to see what was going on:

          - service: persistent_notification.create
            data:
              title: Debug Info
              message: >
                existing_group_members: {{ existing_group_members }},
                new_group_member: {{ new_group_member }}

and found out that HA was returning:

I then replaced group_members here existing_group_members: "{{ state_attr('media_player.basement_all', 'group_members') }}" with entity_id and got the correct notification:

But the exact same behaviour - it grouped, and stoped playing anything with a cleared queue.

Is this exactly how it appears in your automation? Because the templates are missing outer quotes. I’m surprised it works without outer quotes.

          - service: persistent_notification.create
            data:
              title: Debug Info
              message: >
                existing_group_members: {{ existing_group_members }},
                new_group_member: {{ new_group_member }}

A media_player entity isn’t a helper.

What exactly is media_player.basement_all?

Yes, this is exactly how it appears, and it does work!

A media_player entity isn’t a helper.

What exactly is media_player.basement_all?

This is what media_player.basement_all is:

More info:

My other speaker groups in the original code are of the same variety set up here as helpers.

Thanks, I understand what you did now. You created a Media Player Group entity via the Helper menu.

Back to your original question:

The answer is no.

When I posted my example above, I assumed media_player.basement_all is a standard media_player entity (in this case, an entity representing a single Sonos speaker that may or may not be joined with others).

media_player.basement_all is a Media Player Group and it doesn’t have a group_members attribute. It has an entity_id attribute which doesn’t work the same way as a group_members attribute.

  • group_members lists all the media_players that are joined. The first one in the list is the master.

  • entity_id lists all the media_players that are in the Media Player Group and nothing more (i.e. it’s not an indication that they’re joined).

tl:dr
A Media Player Group is simply a group of media_players and has no relation to a joined group of media_players.

1 Like

Ah alright - many thanks for this explanation.

Right, so if I understand correctly, I have 3 options, and correct me where I am wrong where they are not options:

  1. I could declare each of my speakers as part of a variable at the top of the automation, such that for example basement1 and basement2 are part of the variable called basement_all and call that up in the code you wrote;
  2. I could, just try and join office to one of the media_player.basement speakers (here just using a single speaker as a host) even though I had originally started playing to a group and see how it reacts;
  3. I could change the code such that I am joining the office to the helpers as I have created above but I am still experiencing the clearing queue problem; or
  4. I could also join a speaker to a group of other speakers while writing out the name of all other speakers, but I wanted to avoid this while creating the media player group - because as this grows, it’s going to get very confusing and untidy.

The reason why I created the helpers is for scaling this automation. I eventually want to basically walk around the house and have different sensors allow different speakers to join in, or un-join depending. So I thought having groups would make things easier.

Thanks again.

Okay! Mini update! I decided to choose route 2 above and it worked! YAY! Finally! The speaker joins one of the speakers within the basement_all group but the whole of the group is then retained and it continues to play while retaining the queue. Okay, excellent. Now, two questions:

  1. With code below, I am trying to unjoin the basement_all speakers after a set period where the sensor keeps detecting presence. Currently, it doesn’t like it.
description: ""
trigger:
  - type: occupied
    platform: device
    device_id: 3047c3c98f572a2822e8248e1ac7c651
    entity_id: binary_sensor.office_mm_wave_sensor_58492e_presence
    domain: binary_sensor
    for:
      hours: 0
      minutes: 0
      seconds: 0
condition:
  - condition: or
    conditions:
      - condition: state
        entity_id: media_player.upstairs
        state: playing
      - condition: state
        entity_id: media_player.main_floor
        state: playing
      - condition: state
        entity_id: media_player.basement_all
        state: playing
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: media_player.basement_all
            state: playing
        sequence:
          - variables:
              existing_group_members: "{{ state_attr('media_player.basement', 'group_members') }}"
              new_group_member: media_player.office
          - service: persistent_notification.create
            data:
              title: Debug Info
              message: >
                existing_group_members: {{ existing_group_members }},
                new_group_member: {{ new_group_member }}
          - service: media_player.join
            target:
              entity_id: "{{ existing_group_members | first }}"
            data:
              group_members: "{{ existing_group_members + [new_group_member] }}"
  - delay:
      hours: 0
      minutes: 0
      seconds: 45
      milliseconds: 0
  - if:
      - condition: state
        entity_id: binary_sensor.office_mm_wave_sensor_58492e_presence
        state: "on"
        for:
          hours: 0
          minutes: 0
          seconds: 0
    then:
      - service: media_player.unjoin
        data:
          group_members: "{{ state_attr('media_player.basement_all', 'entity_id') }}"
mode: single

and 2) I think it may be better to use something like:

trigger:
    - platform: state
      entity_id: 
        - media_player.entity_id_1
        - media_player.entity_id_2
      to: 'playing'
  action:
    - variables:
        playing_media_players: >
          {{ states.media_player
          | selectattr('state', 'eq', 'playing')
          | map(attribute='entity_id')
          | list }}

… to create a variable containing all the speakers that are currently playing and then apply the following logic:
2a) is (let’s say for example) media_player.office currently playing while the sensor detects presence?
2b) if not, join the above variable
2c) wait for 45s and only continue playing in office.

Maybe variables aren’t the way to go here, but happy to hear thoughts.

Reference material for your project: