SONOS TTS Script

Hi guys, I’ve been fidling with this script to use my sonos system as you do. I’ve found a solution for the duration issue. The problem is: the expected format for the duration is HH:MM:SS but this script was sending H:M:S.

The main change is in the duration string formatting (HH:MM:SS):

{{ “%02i:%02i:%02i”|format(hours, minutes, seconds)}}

The full script below:

sonos_tts:
  alias: Sonos Text To Speech
  sequence:
    - service: media_player.sonos_snapshot
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
    - service: tts.google_say
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        message: "{{ what }}"
    - delay:
        seconds: 1
    - 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 }}"

Now the script works pretty well, and its even faster to resume the sonos state!

Good luck!

3 Likes

UPDATE:

My script fixes the time format issue but it takes too long to restore the media player. Im seeing loads of entries in the logs like these:

2017-06-03 11:57:40 WARNING (MainThread) [homeassistant.helpers.entity] Update for media_player.bureau is already in progress

It seems to me that media_player.sonos_restore is taking too long, maybe because the home assistant node is in a different network and creaing upnp issues… ill try moving the HASS to the same neetwork.

Finally. MOved the HASS node to the local lan and this is working much better.

Just got this working with a combination of the above scripts. For anyone that wants it, this script works perfectly for me, no error logs and it removes the speaker from the group before doing the TTS. Thanks for all the comments and hard work!

sonos_tts:
  alias: Sonos Text To Speech
  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.70
    - service: tts.google_say
      data_template:
        entity_id: "{{ 'media_player.' ~ where }}"
        message: "{{ what }}"
# Workout the length of the media
    - delay:
        seconds: 1
    - 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
3 Likes

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