Set a timer using HA Assist

Hey Carl,

I’ve noticed a small bug in my script and fixed that. However, that’s not causing your error.

If you want a media file to be played, set timer_tts: false.
Also be sure that the timer.mp3 file is matching the filename exactly (it’s case sensitive, so Timer.mp3 won’t be played). The file should be placed in your /media folder (one up, not inside from /config).

Does this help?

Hey Don,

Ok, I think I had misunderstood the media file bit. I was thinking that the media file, and the speech would both be played on the media player of choice, but I can now see that it’s either one or the other. No probs.

As for getting the timers to work, I’ll play with a bit more tonight. For my understanding, if I only ever want to use one media player to give the timer finished response, how should this part of the package code be (the one I’m using is media_player.kitchen_display’, which is a Google Nest mini)?

timer_target: "media_player.kitchen_display"
timer_target_default: "media_player.kitchen_display"

Or should I have something else on the timer_target line?

Thanks for your help :slight_smile:

You can leave it as is if youre only using one speaker.

I added these if someone (like myself) wanted an option to play the timer in different rooms/speakers. This can be dine by assigning it to a sensor or input_text. The default value should be a media_player as backup if the sensor is empty.

So, just spit balling here. Might not be as much code as you think.

You can use the hass_entities python script to change the timer’s friendly_name.
pmazz/ps_hassio_entities: Python script to handle state and attributes of existing sensors and entities (github.com)

I could not find it in HACS. Add the following to configuration.yaml and you will then find it in HACS:

python_script:

Python Scripts - Home Assistant (home-assistant.io)

And is used like

service: python_script.hass_entities
data:
  action: set_attributes
  entity_id: "{{ timer_id }}"
  attributes:
    - friendly_name: "{{ timer_name }}"

The intent could then be something like

Set oven timer for 40 minutes

You could then use something like this to see if a timer name already exists

if states.timer | selectattr('entity_id', 'match', 'timer.timer*') | selectattr('name', 'eq', timer_name) | map(attribute='entity_id') | list | count == 0

If == 0, then it does not exist. If not = 0, then it does exist for pause and cancel (or to error on set).

To get the timer id if it exists

states.timer | selectattr('entity_id', 'match', 'timer.timer*') | selectattr('name', 'eq', timer_name) | map(attribute='entity_id') | list | first

Now, to see if you have an open timer, you could use

if states.timer | selectattr('entity_id', 'match', 'timer.timer*') | selectattr('state', 'eq', 'idle') | map(attribute='entity_id') | list | count > 0

If greater than 0, you have open timers. If = 0, you are out of timers and can return an error.

To get the first open timer id

states.timer | selectattr('entity_id', 'match', 'timer.timer*') | selectattr('state', 'eq', 'idle') | map(attribute='entity_id') | list | first

Now, for the fun part. LOL
You would probably want to rework the rest of your code. You would no longer need to do all of the checks for timer1, timer2, and timer3. And, are now only limited by the number of timers created. To add another timer, just create it and should not have any code changes.

Then, on cancel, stop, or finished, change the name back to match the saved id

service: python_script.hass_entities
data:
  action: set_attributes
  entity_id: "{{ timer_id }}"
  attributes:
    - friendly_name: "{{ timer_id | replace('timer.', '') }}"

Then, you could use auto-entites and timer-bar-card to show all of your timers in lovelace.

type: custom:auto-entities
card:
  type: custom:timer-bar-card
  name: Willow Timers
filter:
  include:
    - entity_id: timer.timer*

Which looks like:

You could also change the icon based on the current state of the timer.

Ok great.

As for the issues I was having, I couldn’t figure it out, but could see that there was an error in the Assist - Timer Reached automation, where I kept getting an error about the media player not being defined. So I ended up just migrating the automation to the UI, and adding my own actions to make it work, and now it works :slight_smile:

One other minor bug I’ve noticed is that when asking to set timers using voice, it often will use words for the numbers, rather than digits (for example, I ask it to ‘Set a timer for 1 minute’, and HA hears ‘Set a timer for one minute’). This means that the initial response is not including the time, so I often get a response something like ‘Understood, setting a timer for.’.

I’m assuming there’s something missing in the lists somewhere, to include worded numbers, as well as digits?

I’m not familiar with python (scripts), but it seems like nice working solution. I tried to use only HA core functionalities to prevent having to download other files. But looks nice!

@celebron I’m not sure why you got the error, I’ll have to look into that. About the numbers, that doesn’t happen in Dutch language for some reason. I’ll see to create a list in the English file, where ‘one’ outputs ‘1’. Thanks for pointing that out.

1 Like

Everything I posted above is HA functionalities and can be used in jinja2. I am not a python person.

Sorry, other than setting the friendly name. Not my code. Everything else is HA.

Another option is to use a helper text named similar to each timer to hold the timer name. Then, do similar stuff as I posted above.

1 Like

Hi and thanks for the great job!
I translated assist_timers.yaml in italian and it works, but check the code before making it official as I’m not a dev nor an expert.

language: "it"
intents:
  TimerStop:
    data:
      - sentences:
          - "(ferma|cancella|spegni) [il] timer {entity_id}"
      - sentences:
          - "(ferma|cancella|spegni) [il] timer"
        slots:
          entity_id: "null"
  TimerPause:
    data:
      - sentences:
          - "(pausa|interrompi) [il] timer {entity_id}"
        slots:
          timer_action: "pause"
      - sentences:
          - "(pausa|interrompi) [il] timer"
        slots:
          entity_id: "null"
          timer_action: "pause"
      - sentences:
          - "(pausa|interrompi) tutti i timer"
        slots:
          entity_id: "all"
          timer_action: "pause"
      - sentences:
          - "(riprendi|continua) [il] timer {entity_id}"
        slots:
          timer_action: "resume"
      - sentences:
          - "(riprendi|continua) [il] timer"
        slots:
          entity_id: "null"
          timer_action: "resume"
      - sentences:
          - "(riprendi|continua) tutti i timer"
        slots:
          entity_id: "all"
          timer_action: "resume"
          
  TimerDuration:
    data:
      - sentences:
          - "quanto (manca|resta|dura) (al|il) timer {entity_id}"
        slots:
          entity_id: "null"
  TimerStart:
    data:
#Completo
      - sentences:
          - "(avvia|imposta) [un] timer (di|da) {hours} (ora|ore) [e] {minutes} (minuto|minuti) [e] {seconds} secondi"
#Solo ore
      - sentences:
          - "(avvia|imposta) [(un)] timer (di|da) {hours} (ora|ore)"
        slots:
          seconds: 0
          minutes: 0
#Solo minuti
      - sentences:
          - "(avvia|imposta) [un] timer (di|da) {minutes} (minuto|minuti)"
        slots:
          seconds: 0
          hours: 0
#Solo secondi
      - sentences:
          - "(avvia|imposta) [un] timer (di|da) {seconds} (secondo|secondi)"
        slots:
          minutes: 0
          hours: 0
#Minuti e secondi
      - sentences:
          - "(avvia|imposta) [un] timer (di|da) {minutes} minuti [e] {seconds} secondi"
        slots:
          hours: 0   
#Ore e minuti
      - sentences:
          - "(avvia|imposta) [un] timer (di|da) {hours} (ora|ore) [e] {minutes} (minuto|minuti)"
        slots:
          seconds: 0
          
lists:
  entity_id:
    values:
      - in: "(primo|uno|1)"
        out: "timer.assist_timer1"
      - in: "(secondo|due|2)"
        out: "timer.assist_timer2"
      - in: "(terzo|tre|3)"
        out: "timer.assist_timer3"
      - in: "(tutti|ogni)"
        out: "all"
  seconds:
    range:
      from: 0
      to: 120    
  minutes:
    range:
      from: 0
      to: 120
  hours:
    range:
      from: 0
      to: 24
      
responses:
  intents:
    TimerStop:
      default: >
        {%- set timer_amount = states.timer 
              | selectattr('state','eq','active') 
              | selectattr('entity_id','match','timer.assist_timer*')
              | map(attribute='entity_id') 
              | list
              | length -%}
        {% set mediaplayer = namespace(entity=[]) %}
        {% for player in states.media_player %}
          {%- if ((state_attr(player.entity_id, 'media_content_id') |lower != 'none' and state_attr(player.entity_id, 'media_content_id')[:47][38:] == 'Timer.mp3') or state_attr(player.entity_id, 'media_title') | lower == 'timer') and states(player.entity_id) == 'playing' -%}
            {%- set mediaplayer.entity = player.entity_id -%}
          {% endif -%}
        {% endfor %}
        {% if mediaplayer.entity[:12] == 'media_player' %}
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) }} fermo il timer.
        {% elif timer_amount == 0 and
             (as_timestamp(now()) - as_timestamp(states.timer.assist_timer1.last_changed) > 3 and states('timer.assist_timer1') == 'idle') and 
             (as_timestamp(now()) - as_timestamp(states.timer.assist_timer2.last_changed) > 3 and states('timer.assist_timer2') == 'idle') and
             (as_timestamp(now()) - as_timestamp(states.timer.assist_timer3.last_changed) > 3 and states('timer.assist_timer3') == 'idle') %}
        Non ci sono timer attivi al momento.
        {% elif (slots_entity_id == 'timer.assist_timer1' and states('timer.assist_timer1') == 'idle') or
             (slots_entity_id == 'timer.assist_timer2' and states('timer.assist_timer2') == 'idle') or 
             (slots_entity_id == 'timer.assist_timer3' and states('timer.assist_timer3') == 'idle') %}
        Il timer non è attivo. 
        {% elif timer_amount > 1 and slots_entity_id == 'null' %}
        Ci sono diversi timer attivi.  
        {{ (["Prego specifichi a quale timer si riferisce.", "Prego di specificare quale timer.", "A quale timer si riferisce?", ""] | random) }}
        {% elif slots_entity_id == 'all' %}
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) }} Fermo tutti i timer.
        {% else %}
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) }} Timer fermato.
        {% endif %} 
    TimerPause:
      default: >
        {%- if slots.timer_action is set or slots.timer_action != "" -%}
          {%- set timer_action = slots.timer_action -%}
        {%- else -%}
          {%- set timer_action = "resume" -%}
        {%- endif -%}
        {%- set timer_amount = states.timer 
           | selectattr('state','eq','active') 
           | selectattr('entity_id','match','timer.assist_timer*')
           | map(attribute='entity_id') 
           | list
           | length -%}
        {% if timer_amount == 0 %}
        Non ci sono timer attivi.
        {% elif timer_amount > 1 and slots.entity_id == 'null' %}
        Ci sono diversi timer attivi. 
        {{ (["Prego specifichi a quale timer si riferisce.", "Prego specifichi quale timer.", "A quale timer si riferisce?", ""] | random) }}
        {% elif slots.entity_id == 'all' %}
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) }}.
        {% if timer_action == "pause" %}in pausa{% else %}riavviato{% endif %}.
        {% elif (as_timestamp(now()) - as_timestamp(states.timer.assist_timer1.last_changed) < 3 and states('timer.assist_timer1') == 'idle') or 
                (as_timestamp(now()) - as_timestamp(states.timer.assist_timer2.last_changed) < 3 and states('timer.assist_timer2') == 'idle') or
                (as_timestamp(now()) - as_timestamp(states.timer.assist_timer3.last_changed) < 3 and states('timer.assist_timer3') == 'idle') %}
        Timer {% if timer_action == "pause" %}in pausa{% else %}riavviato{% endif %}.
        {% elif (timer_amount == 1 and slots.entity_id == 'null') or
           (slots.entity_id == 'timer.assist_timer1' and states('timer.assist_timer1') != 'idle') or 
           (slots.entity_id == 'timer.assist_timer2' and states('timer.assist_timer2') != 'idle') or
           (slots.entity_id == 'timer.assist_timer3' and states('timer.assist_timer3') != 'idle') %}
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) }}  Timer {% if timer_action == "pause" %} in pausa {% else %} riavviato {% endif %}.
        {% else %}
        Questo timer non è attivo.
        {% endif %}   
    TimerDuration:
      default: >
        {%- set timer_amount = states.timer 
           | selectattr('state','eq','active') 
           | selectattr('entity_id','match','timer.assist_timer*')
           | map(attribute='entity_id') 
           | list
           | length -%}          
        {% if timer_amount == 0 %}
        Non ci sono timer attivi.
        {% else %}
          {%- if slots.entity_id != 'all' and slots.entity_id != 'null' %}
          {%- set active_timers = states.timer 
             | selectattr('state','eq','active') 
             | selectattr('entity_id','match',slots.entity_id)
             | list -%}
          {%- else%}
          {%- set active_timers = states.timer 
             | selectattr('state','eq','active') 
             | selectattr('entity_id','match','timer.assist_timer*')
             | list -%}
          {%- endif %}
          {% if active_timers|length == 0 %}
            {%- if slots.entity_id != 'all' and slots.entity_id != 'null' %} Il timer non è attivo.
            {%- else %}Non ci sono timer attivi. {%- endif %}{% elif active_timers|length > 1 %} Ci sono {{active_timers|length }} timer attivi. {% endif %}
            {% for timer in active_timers %}{%
             set timer_id = timer.entity_id %}{%
             set timer_finishes_at = state_attr(timer_id, 'finishes_at') %}{%
  
             set time_remaining = as_datetime(timer_finishes_at) - now() %}{%
             set hours_remaining = time_remaining.total_seconds() // 3600 %}{%
             set minutes_remaining = (time_remaining.total_seconds() % 3600) // 60 %}{%
             set seconds_remaining = time_remaining.total_seconds() % 60 %}{%
             
             if timer.state == "active" or timer.state == "paused" %}{%
               if slots.entity_id != timer_id 
                 %}{{ state_attr(timer_id, 'friendly_name')[9:] }} {%
                 if timer.state == "paused" %} è in pausa {% endif %} {% else %} (Mancano|Restano) {% endif %}{%
               if hours_remaining > 0 %}{{ hours_remaining | round }} ore {% endif %}{%
               if minutes_remaining == 1 %}1 minuto {% endif %}{%
               if minutes_remaining > 1 %}{{ minutes_remaining | round }} minuti {% endif %}{%
               if seconds_remaining == 1 and hours_remaining == 0%}1 secondo {% endif %}{%
               if seconds_remaining > 1 and hours_remaining == 0 %}{{ seconds_remaining | round }} secondi {% endif %}. {%
             endif %}{%
             endfor %}
        {% endif %}
    TimerStart:
      default: >
        {{ (["Capito. ", "Okei. ", "Certo. ", ""] | random) + (["Avvio un timer da ", "Timer avviato.  "] | random)}}{% if (slots.hours | int(default=0)) == 1 %} 1 ora {% elif (slots.hours | int(default=0)) > 1 %}{{ (slots.hours | int)}} ore {% endif %}{% if (slots.hours | int(default=0)) > 0 and ((slots.minutes | int(default=0)) > 0 or (slots.seconds | int(default=0)) > 0) %} e {% endif %}{% if (slots.minutes | int(default=0)) == 1 %} 1 minuto {% elif (slots.minutes | int(default=0)) > 1 %}{{ (slots.minutes | int)}}  minuti {% endif %}{% if (slots.minutes | int(default=0)) > 0 and (slots.seconds | int(default=0)) > 0 %} e {% endif %}{% if (slots.seconds | int(default=0)) == 1 %} 1 secondo {% elif (slots.seconds | int(default=0)) > 1 %}{{ (slots.seconds | int)}} secondi {% endif %}.

Hi again.
previously I claimed everything seemed to work. As a matter of fact Assist recognizes intents, gives the correct spoken answer with correct timer duration and, when timer is supposed to end, media_player(vlc_telnet integration)'s log shows that VLC service is activated by “Assist - TimerReached” automation.
Nevertheless I cannot hear anything coming out of the speaker, both tts and timer.mp3.
The speaker works well in all other circumstances, so I cannot figure out what’s going wrong.
This is my config

      ########################################################
        timer_target: "media_player.vlc_telnet"                    #Which device or room should play the timer? #NOTE: If you choose a room, ALL media_players in that room will be played.
        timer_target_default: "media_player.vlc_telnet" #Which media_player entity should be used if timer_target is none? (Used when dynamic targets are null.)
        #You can also assign a sensor.entity_id or input_text to have a dynamic room or media_player assigned. 
        #This can be usefull if, for example, you have a sensor monitoring where Assist is activated, to use it in that specific room or device.
        
        # Timer settings
        timer_tts: false                                #Set this to true if you want the timer to be a tts message. Default is true.
        timer_tts_service: "tts.speak"                 #Which TTS service to use? Default is "tts.speak".
        timer_tts_target: "tts.piper"   #Which TTS target to use, e.g. piper or ha cloud? Default is "tts.home_assistant_cloud".
        timer_tts_message: "Il timer si è concluso." #You can also assign a sensor or input_text entity_id to have a dynamic tts message.
        
        #If timer_tts is set to false, the media_location will automatically be used to play the target media file. 
        #Use filenamer timer.mp3 to be able to stop the music as well. (For playback recognition). 
        #Case sensitive (timer.mp3 vs Timer.mp3)
        timer_media_location: "/local/Media/timer.mp3" 
        
        # Timer volume
        timer_volume: 0.8                          #What volume level do you want the timer to play at? Device volume level will be restored after the timer has finished.
      ########################################################

To avoid mistakes, I copied (Media_location) from the .yaml code of a working automation, so path should be correct. As I said, also setting (timer_tts: true) gives same results.
Any idea?
Thanks again

Hi, sorry if I bother, but your answer would help me a lot.
When you migrated the automations, what part of the code did you remove from package?

I also migrated automation, but I tried deleting the automation part in package and gave me error.
For now, I just disabled theese two automations and let the ones I migrated work. And they work. But I’d like not to have unused code.
Does somebody know how to correct package to exclude automations and make it work with migrated automations?
Thanks

Hi, once I had the package installed, I found I was having problems with the automation called ‘Assist - TimerReached’. All I did was opened the automation in the automations section (UI) and clicked on the migrate note at the top of the automation. After that, I just modified the parts of the automation, that was giving me some trouble.

Here’s an example from one I haven’t migrated.

Hi and thanks for answering.
I did it too, but following “…or doesn’t have and ID” I was warned I had to delete the original automations manually. Seems that you didn’t have that warning.

Hi @Innominatapersona,

I’m not sure what the error could be. In my setup, I placed files in the /media file (next to /config, not a folder inside /config/www). Then I have to use “media_content_id: /media/local/Timer.mp3”

However, regarding tts I noticed an error in my package. I use tts.cloud_say, which works fine. Tts.speak has a different service format and therefore some information (which tts line to use) wasn’t passed.
I updated the package in github. Please update the package and try again.

1 Like

Hi and thanks!
Did you find assist_timers.yaml translation in italian few posts above? Maybe you could use it if you want to. However, that’s surely working. Also media (local) folder is surely correct, it works in migrated automation as tts.speak does. Tts could be an issue, but the same happens when not using tts.
Your job is great and this was one of the last things I had to setup to have a fully functional assistant, so thank you very much again. Nevertheless I found migrated automation more confortable to use as I could add other actions through UI. That’s why I wouldn’t like to go back to package. What I’m now struggling with is understanding how to remove the package automation part leaving the rest of the code work (now that automations are migrated and the originals disabled).
Not a big problem, it works and I can leave as it is.
Thans again

No problem, glad to help. I started this with the same idea, needed this before switching away from Google.
You can use the code in the package and add it to your own configuration;

This is the custom_intent, which activates the scripts. Add it to your configuration.yaml

intent_script: 
  TimerStart:
    async_action: false
    action:
      - service: script.assist_TimerStart
        data:
          duration: "{{hours | int(default=0)}}:{{ minutes | int(default=0) }}:{{ seconds | int(default=0) }}"
  TimerStop:
    async_action: true
    action:
      - service: script.assist_TimerStop
        data:
          entity_id: "{{ entity_id }}"
  TimerPause:
    async_action: true
    action:
      - service: script.assist_TimerPause
        data:
          entity_id: "{{ entity_id }}"
          timer_action: "{{ timer_action }}"
  TimerDuration:
    async_action: true
    action:
      - stop: ""

If you add this (and your own version of the automations and scripts), you can remove the package file.

1 Like

Hi Don, great to see you’ve added some updates to your repo.

Did you manage to look at the issue regarding asking for timers, where when asking to set a timer for 1 minute (for example), the script doesn’t know how to respond fully, and therefore gives a funny response like ‘OK, setting a timer for.’, and doesn’t add the time? Might have been something to do with the value lists?

1 Like

No, I haven’t yet but I’ve noticed it myself. Will look into that next.

1 Like

Hi @celodnb,
I’ve updated the github’s English custom_sentences/assist_timers.yaml file.
Now it should support both ‘10’ and ‘ten’ in the pipeline. Could you give it a go?

I changed the list from range 1-60 to use values instead:

  seconds:
    values:
      - in: "(one|1)"
        out: 1
      - in: "(two|2)"
        out: 2
      - in: "(three|3)"
1 Like

Hi @DonNL thanks so much for looking into this. Just did a quick test, and all looks good so far. Greatly appreciated :+1:

1 Like
I plan on setting up the entire intents as described above but was in a hurry, the below works, glad I kept all my Rhasspy stuff, not a cut and paste solution but a starting point at least. Allows pausing and reauming and outputs to a speaker when done. Also used an example above to help get it working.

alias: responce_timer_test
description: ""
trigger:
  - platform: conversation
    command:
      - >-
        (Set|start|begin) a timer for {minutes} minutes (and) {seconds}
        (seconds)
      - (Set|start|begin) a timer for {minutes} minutes
    id: Start Timer
  - platform: conversation
    command: (cancel|stop) timer
    id: Cancel Timer
  - platform: conversation
    command: Pause timer
    id: "Pause timer "
  - platform: conversation
    command:
      - "Resume timer "
    id: "Resume timer "
  - platform: state
    entity_id:
      - timer.test_timer
    to: idle
    id: Timer end
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id:
              - Start Timer
        sequence:
          - service: timer.start
            target:
              entity_id: timer.test_timer
            data:
              duration: 00:0{{ trigger.slots.minutes }}:00{{ trigger.slots.seconds }}
            enabled: true
          - set_conversation_response: >-
              I have set a timer for {{ trigger.slots.minutes }} minutes and {{
              trigger.slots.seconds }} seconds
            enabled: true
      - conditions:
          - condition: trigger
            id:
              - Cancel Timer
        sequence:
          - service: timer.cancel
            data: {}
            target:
              entity_id: timer.test_timer
      - conditions:
          - condition: trigger
            id:
              - Pause timer
        sequence:
          - service: timer.pause
            target:
              entity_id: timer.test_timer
            data: {}
      - conditions:
          - condition: trigger
            id:
              - "Resume timer "
        sequence:
          - service: timer.start
            target:
              entity_id: timer.test_timer
            data: {}
      - conditions:
          - condition: trigger
            id:
              - Timer end
        sequence:
          - service: tts.cloud_say
            data:
              entity_id: media_player.vlc_telnet
              message: >-
                (the timer finished at {{ now().strftime('%-I') }} {{
                now().strftime('%M %p') }} on {{ now().strftime('%A') }} {{
                now().strftime('%B') }} {{ now().strftime('%d') }}
              cache: true
            enabled: true
mode: single