# ============================================================================= # Intent Scripts for Timer Voice Command Routing # ============================================================================= # Add to configuration.yaml: intent_script: !include intent_scripts.yaml # # These scripts intercept the standard Assist timer intents and route them # to HA timer entities based on preferred_area_id from the voice assistant. # # DIRECT AREA ROUTING: # Instead of maintaining a device mapping dictionary, this implementation # uses the area ID directly to construct entity names: # - Area ID: kitchen -> timer.kitchen # - Area ID: bedroom -> timer.bedroom # # This means you only need to: # 1. Create a timer entity named after the area (timer.) # 2. Create helper entities named after the area # 3. Assign your voice assistant device to that area in Home Assistant # # ============================================================================= # ----------------------------------------------------------------------------- # HassStartTimer - Start a new timer # ----------------------------------------------------------------------------- HassStartTimer: async_action: false action: - variables: total_seconds: "{{ (hours | default(0) | int) * 3600 + (minutes | default(0) | int) * 60 + (seconds | default(0) | int) }}" # Direct area routing - no mapping dictionary needed! area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" timer_entity: "{{ 'timer.' ~ area_id if area_id else 'none' }}" duration_formatted: >- {% set total = (hours | default(0) | int) * 3600 + (minutes | default(0) | int) * 60 + (seconds | default(0) | int) %} {% set h = (total // 3600) | int %} {% set m = ((total % 3600) // 60) | int %} {% set s = (total % 60) | int %} {{ '%02d:%02d:%02d' | format(h, m, s) }} - choose: - conditions: - condition: template value_template: "{{ timer_entity != 'none' and states(timer_entity) is not none }}" sequence: - service: timer.start target: entity_id: "{{ timer_entity }}" data: duration: "{{ duration_formatted }}" speech: text: > {% set total_seconds = (hours | default(0) | int) * 3600 + (minutes | default(0) | int) * 60 + (seconds | default(0) | int) %} {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% set timer_entity = 'timer.' ~ area_id if area_id else 'none' %} {% if not area_id or states(timer_entity) is none %} I couldn't determine which timer to start. Please make sure your device is assigned to an area with a configured timer. {% else %} {% set m = (total_seconds // 60) | int %} {% set s = total_seconds % 60 %} {% if m > 0 and s > 0 %} Timer set for {{ m }} minute{{ 's' if m != 1 else '' }} and {{ s }} second{{ 's' if s != 1 else '' }}. {% elif m > 0 %} Timer set for {{ m }} minute{{ 's' if m != 1 else '' }}. {% else %} Timer set for {{ s }} second{{ 's' if s != 1 else '' }}. {% endif %} {% endif %} # ----------------------------------------------------------------------------- # HassCancelTimer - Cancel an active or paused timer # ----------------------------------------------------------------------------- HassCancelTimer: async_action: false action: - variables: area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" timer_entity: "{{ 'timer.' ~ area_id if area_id else 'none' }}" timer_state_before: "{{ states('timer.' ~ area_id) if area_id else 'unknown' }}" helper_entity: "{{ 'input_text.' ~ area_id ~ '_timer_previous_state' if area_id else 'none' }}" # Store the pre-action state in helper - choose: - conditions: - condition: template value_template: "{{ helper_entity != 'none' and states(helper_entity) is not none }}" sequence: - service: input_text.set_value target: entity_id: "{{ helper_entity }}" data: value: "{{ timer_state_before }}" # Cancel if active or paused - choose: - conditions: - condition: template value_template: "{{ timer_entity != 'none' and timer_state_before in ['active', 'paused'] }}" sequence: - service: timer.cancel target: entity_id: "{{ timer_entity }}" speech: text: > {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% set helper_entity = 'input_text.' ~ area_id ~ '_timer_previous_state' %} {% set timer_state_before = states(helper_entity) if area_id and states(helper_entity) is not none else 'unknown' %} {% if not area_id %} I couldn't determine which timer to cancel. {% elif timer_state_before in ['active', 'paused'] %} Timer cancelled. {% else %} There's no active timer to cancel. {% endif %} # ----------------------------------------------------------------------------- # HassPauseTimer - Pause an active timer # ----------------------------------------------------------------------------- HassPauseTimer: async_action: false action: - variables: area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" timer_entity: "{{ 'timer.' ~ area_id if area_id else 'none' }}" timer_state_before: "{{ states('timer.' ~ area_id) if area_id else 'unknown' }}" helper_entity: "{{ 'input_text.' ~ area_id ~ '_timer_previous_state' if area_id else 'none' }}" # Store the pre-action state in helper - choose: - conditions: - condition: template value_template: "{{ helper_entity != 'none' and states(helper_entity) is not none }}" sequence: - service: input_text.set_value target: entity_id: "{{ helper_entity }}" data: value: "{{ timer_state_before }}" # Pause only if active - choose: - conditions: - condition: template value_template: "{{ timer_entity != 'none' and timer_state_before == 'active' }}" sequence: - service: timer.pause target: entity_id: "{{ timer_entity }}" speech: text: > {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% set helper_entity = 'input_text.' ~ area_id ~ '_timer_previous_state' %} {% set timer_state_before = states(helper_entity) if area_id and states(helper_entity) is not none else 'unknown' %} {% if not area_id %} I couldn't determine which timer to pause. {% elif timer_state_before == 'active' %} Timer paused. {% elif timer_state_before == 'paused' %} Timer is already paused. {% else %} There's no active timer to pause. {% endif %} # ----------------------------------------------------------------------------- # HassUnpauseTimer - Resume a paused timer # ----------------------------------------------------------------------------- HassUnpauseTimer: async_action: false action: - variables: area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" timer_entity: "{{ 'timer.' ~ area_id if area_id else 'none' }}" timer_state_before: "{{ states('timer.' ~ area_id) if area_id else 'unknown' }}" helper_entity: "{{ 'input_text.' ~ area_id ~ '_timer_previous_state' if area_id else 'none' }}" # Store the pre-action state in helper - choose: - conditions: - condition: template value_template: "{{ helper_entity != 'none' and states(helper_entity) is not none }}" sequence: - service: input_text.set_value target: entity_id: "{{ helper_entity }}" data: value: "{{ timer_state_before }}" # Resume only if paused - choose: - conditions: - condition: template value_template: "{{ timer_entity != 'none' and timer_state_before == 'paused' }}" sequence: - service: timer.start target: entity_id: "{{ timer_entity }}" speech: text: > {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% set helper_entity = 'input_text.' ~ area_id ~ '_timer_previous_state' %} {% set timer_state_before = states(helper_entity) if area_id and states(helper_entity) is not none else 'unknown' %} {% if not area_id %} I couldn't determine which timer to resume. {% elif timer_state_before == 'paused' %} Timer resumed. {% elif timer_state_before == 'active' %} Timer is already running. {% else %} There's no paused timer to resume. {% endif %} # ----------------------------------------------------------------------------- # HassTimerStatus - Query timer status # ----------------------------------------------------------------------------- HassTimerStatus: async_action: false action: - variables: area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" speech: text: > {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% if not area_id %} I couldn't determine which timer to check. {% else %} {% set timer = states.timer[area_id] %} {% if timer is none %} No timer is configured for this area. {% elif timer.state == 'active' and timer.attributes.finishes_at is defined %} {% set finish = as_datetime(timer.attributes.finishes_at) %} {% set remaining = (finish - now()).total_seconds() | int %} {% set remaining = [remaining, 0] | max %} {% set m = (remaining // 60) | int %} {% set s = remaining % 60 %} {% if m > 0 and s > 0 %} {{ m }} minute{{ 's' if m != 1 else '' }} and {{ s }} second{{ 's' if s != 1 else '' }} remaining. {% elif m > 0 %} {{ m }} minute{{ 's' if m != 1 else '' }} remaining. {% elif s > 0 %} {{ s }} second{{ 's' if s != 1 else '' }} remaining. {% else %} Less than a second remaining. {% endif %} {% elif timer.state == 'paused' and timer.attributes.remaining is defined %} {% set parts = timer.attributes.remaining.split(':') %} {% set remaining = (parts[0] | int) * 3600 + (parts[1] | int) * 60 + (parts[2] | float | int) %} {% set m = (remaining // 60) | int %} {% set s = remaining % 60 %} Timer is paused with {%- if m > 0 and s > 0 -%} {{ ' ' }}{{ m }} minute{{ 's' if m != 1 else '' }} and {{ s }} second{{ 's' if s != 1 else '' }} {%- elif m > 0 -%} {{ ' ' }}{{ m }} minute{{ 's' if m != 1 else '' }} {%- else -%} {{ ' ' }}{{ s }} second{{ 's' if s != 1 else '' }} {%- endif %} remaining. {% else %} No timer is currently set. {% endif %} {% endif %} # ----------------------------------------------------------------------------- # RestartLocalTimer - Restart timer on local device with original duration # ----------------------------------------------------------------------------- # Example phrases: # - "Restart the timer" # - "Restart my timer" # - "Start the timer over" # - "Reset the timer" # ----------------------------------------------------------------------------- RestartLocalTimer: async_action: false action: - variables: area_id: "{{ preferred_area_id | default('') | lower | replace(' ', '_') }}" timer_entity: "{{ 'timer.' ~ area_id if area_id else 'none' }}" timer_state_before: "{{ states('timer.' ~ area_id) if area_id else 'unknown' }}" timer_duration: "{{ state_attr('timer.' ~ area_id, 'duration') if area_id else '00:00:00' }}" helper_entity: "{{ 'input_text.' ~ area_id ~ '_timer_previous_state' if area_id else 'none' }}" # Store the pre-action state in helper - choose: - conditions: - condition: template value_template: "{{ helper_entity != 'none' and states(helper_entity) is not none }}" sequence: - service: input_text.set_value target: entity_id: "{{ helper_entity }}" data: value: "{{ timer_state_before }}" # Restart if active or paused (has a duration to restart with) - choose: - conditions: - condition: template value_template: "{{ timer_entity != 'none' and timer_state_before in ['active', 'paused'] and timer_duration not in ['00:00:00', None, ''] }}" sequence: - service: timer.start target: entity_id: "{{ timer_entity }}" data: duration: "{{ timer_duration }}" speech: text: > {% set area_id = preferred_area_id | default('') | lower | replace(' ', '_') %} {% set helper_entity = 'input_text.' ~ area_id ~ '_timer_previous_state' %} {% set timer_state_before = states(helper_entity) if area_id and states(helper_entity) is not none else 'unknown' %} {% set timer_duration = state_attr('timer.' ~ area_id, 'duration') if area_id else '00:00:00' %} {% if not area_id %} I couldn't determine which timer to restart. {% elif timer_state_before not in ['active', 'paused'] %} There's no active timer to restart. {% elif timer_duration in ['00:00:00', None, ''] %} The timer has no duration to restart with. {% else %} {% set parts = timer_duration.split(':') %} {% set total_seconds = (parts[0] | int) * 3600 + (parts[1] | int) * 60 + (parts[2] | float | int) %} {% set m = (total_seconds // 60) | int %} {% set s = total_seconds % 60 %} Timer restarted {%- if m > 0 and s > 0 -%} {{ ' ' }}for {{ m }} minute{{ 's' if m != 1 else '' }} and {{ s }} second{{ 's' if s != 1 else '' }}. {%- elif m > 0 -%} {{ ' ' }}for {{ m }} minute{{ 's' if m != 1 else '' }}. {%- elif s > 0 -%} {{ ' ' }}for {{ s }} second{{ 's' if s != 1 else '' }}. {%- else -%} . {%- endif %} {% endif %}