SONOS TTS Script

Has anyone made this work with spotify connect?
My sonos does not continue playing with spotify connect after the tts.

2 Likes

Wow, so complex! Today the whole duration thing can be fixed by using wait_template in a manner similar to this:

script:
  tts_wait_test:
    sequence:
      - service: tts.google_say
        data:
          entity_id: media_player.your_media_player
          message: "Hello World. I am your personal assistant."

      - wait_template: >-
          {{ is_state('media_player.your_media_player', 'playing') }}
        timeout: '00:00:03'

      - wait_template: >-
          {{ not is_state('media_player.your_media_player', 'playing') }}
        timeout: '00:01:00'

      - service: tts.google_say
        data:
          entity_id: media_player.your_media_player
          message: "I waited until I finished saying the previous statement, so that I can say this one."
2 Likes

This seems to work…

########################################################
## Sonos TTS
########################################################
say:
  alias: SONOS TTS
  sequence:
    - condition: state
      entity_id: group.household
      state: 'home'

    - service: media_player.sonos_snapshot
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        with_group: yes

    - service: media_player.sonos_unjoin
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"

    - service: media_player.volume_set
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        volume_level: 0.4

    - service: tts.google_say
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        message: "{{ what }}"

    - wait_template: >-
        {{ is_state('media_player.' ~ where , 'playing') }}
      timeout: '00:00:15'

    - wait_template: >-
        {{ not is_state('media_player.' ~ where , 'playing') }}
      timeout: '00:01:00'

#    - delay:
#        seconds: 2

#    - delay: >-
#        {% set duration = states.media_player[where].attributes.media_duration %}
#        {% if duration > 0 %}
#          {% set duration = duration - 1 %}
#        {% endif %}
#        {% set seconds = duration % 60 %}
#        {% set minutes = (duration / 60)|int % 60 %}
#        {% set hours = (duration / 3600)|int %}
#        {{ "%02i:%02i:%02i"|format(hours, minutes, seconds)}}

    - service: media_player.sonos_restore
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        with_group: yes
2 Likes

This works great as far as returning to whatever was playing on the Sonos, but it seems to cut short at random points. Say I want it to say “This is a test”. The results could be:

This is a test (4 times in a row with no problems)
This is a te
This is a tes
This is a
This

With the full sentence being peppered in a few times along the way. I can’t see any cause for it.

Are your Sonos Ethernet or wifi?

They’re both. One is connected via ethernet and it is meshing with the others.
It doesn’t appear to a problem with networking though as in the web ui I can see that when I run the sonos_tts_test script the say_sonos script it calls usually gets turned off prematurely.

Here’s the code I am using:

  sonos_tts_test:
    sequence:
      - service: script.turn_on
        entity_id: script.say_sonos
        data:
          variables:
            where: 'sonos_office'
            what: 'This is a test and it is too short'

  say_sonos:
    sequence:
      - service: media_player.sonos_snapshot
        data_template:
          entity_id: "{{ 'media_player.' ~ where }}"
          with_group: yes

      - service: media_player.sonos_unjoin
        data_template:
          entity_id: "{{ 'media_player.' ~ where }}"

      - service: media_player.volume_set
        data_template:
          entity_id: "{{ 'media_player.' ~ where }}"
          volume_level: 0.4

      - service: tts.google_say
        data_template:
          entity_id: "{{ 'media_player.' ~ where }}"
          message: "{{ what }}"

      - wait_template: >-
          {{ is_state('media_player.' ~ where , 'playing') }}
        timeout: '00:00:03'

      - wait_template: >-
          {{ not is_state('media_player.' ~ where , 'playing') }}
        timeout: '00:01:00'

      - service: media_player.sonos_restore
        data_template:
          entity_id: "{{ 'media_player.' ~ where }}"
          with_group: yes
1 Like

Try changing the timeout on the 1st wait template to 30 seconds. I think what might be happening is by the time the songs starts playing the 3 seconds has passed and it’s timed out.

The first wait is to wait for sonos to start to play; the second to wait for songs to stop playing…

Good idea. 30 seconds would be FAR too long but I will experiment with increasing the delay. I might set up another sensor monitoring the state so I can see how quickly that updates.

The way I would be using this is to interrupt music (or even TV the Sonos Playbar) to give a notification. i.e. The garage door is open or the like.

Hi, I’m having trouble getting the script to work correctly, I have used code above:

when I try it I get the following error:
This is the error:
2017-07-17 20:48:56 ERROR (MainThread) [homeassistant.core] Invalid service data for media_player.sonos_snapshot: Entity ID media_player. is an invalid entity id for dictionary value @ data[‘entity_id’]. Got ‘media_player.’

As far as I can tell, none of the variables are being passed to the script - Entity ID media_player. when it should be Entity ID media_player.sonos_office I think?

And apologies in advance, I’m very new to this so may be missing something very obvious, really appreciate your help.

(Hassbian - .49)

Thanks

Yes, I suggest 30 to just test; and then fine tune to something that makes sense.

I just tried copy/pasted the last example from @mihalski but my sonos is not restoring it’s state. I tried playing a song then manually activate the script to say something, it says the text but then it never returns to the music.

However i see this in the log, don’t know it it’s related :

2017-07-17 14:01:01 WARNING (MainThread) [homeassistant.helpers.entity] Update for media_player.portable is already in progress

So i got it to work albeit the problems that @mihalski explained with the premature stop but another issue is that if i am playing a song and i am in the middle of the song then the doorbell rings and then the music goes back it starts from beginning which is not useful at all, was the idea of restore to go back and play exactly where it was left ?

I guess the problem is that i was using a Music Service (i think in this specific case i used SoundCloud or MixCloud) which i believe the snapshot/restore does not work with correctly. @maddox mentioned something about this in an old issue. The problem lies in the lower layer the SoCo library used for Sonos.

Hi,

I am curious what is the purpose of this line?

@donnib I’ve had the same problem with pandora for a while now. Each time pandora is interrupted, when it resumes, it starts a new track. If I play local media the stop/resume functionality works as expected.

Wait until the player starts; as the next wait waits for it to not play.

So once you ask it to speak; we wait until it starts to speak (3 second timeout); and then wait for it to stop speaking (with a 1 minute timeout).

My thought above was that the 3 seconds was not long enough. I am running with 15 seconds now and have yet had any stutters or other issues - it works as well as it did with the old duration computation (which also had a challenge in that the mp3 duration was returned by the media player itself so you had to wait until the sonos was playing the tts message before you knew how long to wait…)

Yes that’s what i figured out, anything local works fine

For anything streaming non-local it seems to not like being paused and resumed; your place in the current track is lost.

@Molodax Those lines are necessary because it is very common for the media player state to NOT be playing right after telling it to speak using TTS. This wait_template waits for the state to be playing before continuing. In reality, it is the absolute most important part of my example script because without it, it will completely skip the first TTS and jump to the second.

@mihalski You could set the timeout to 10 minutes if you wanted to. The timeout is in place to be a “fallback” in case the expression never returns true. Any timeout will work as long as the timeout is longer than how long it takes for your media player to change to the playing state.

2 Likes

@dale3h Ahh! That makes PERFECT sense to me now. Thanks for the clarification.

It seems that still does not solve the problem when set to 30 seconds.

It works if nothing is playing on the Sonos, but at the cost of not being able to send another voice notification until the script exits after about 30 seconds. If something IS playing it cuts the text short.

I really don’t understand what’s going on.