Timer for announcing remaining seconds until event fires

i have a nice automation that notifies me which motion sensor detected motion.
i would like to add a line at the end of the notification to tell me the time remaining until the alarm siren starts sounding (which is typically 60 seconds).

i’m trying to use a timer helper for this to read the ‘’‘remaining’’’ state attribute of the timer but i found that it never changes. it only seems to show the same value as the duration attribute.

for example it always shows this state:

duration: '0:00:30'
editable: true
icon: mdi:clock
friendly_name: Alarm Trigger
finishes_at: '2022-05-14T08:00:06+00:00'
remaining: '0:00:30'

how i test:
i trigger the timer in developer tools:

service: timer.start
data:
  duration: '30'
target:
  entity_id: timer.alarm_trigger

use a script to create a notification:

alias: time remaining test
sequence:
  - service: notify.persistent_notification
    data:
      message: >-
        the time remaining is {{ state_attr('timer.alarm_trigger', 'remaining')
        }}
mode: single

is there something wrong with the timer helper?

No, that’s the way it works. The remaining value changes when the timer is paused, not while the timer is active.

You’ll need to compute the remaining time by subtracting now() from the value of finishes_at. I posted an example of it here:

However, it will only update every minute, not every second, because that’s the evaluation interval for a template employing now(). So if your requirement is to report the countdown every second, it won’t fulfill it.

oh i see… thanks for clearing that up.

i think i figured out how to use the timer helper to calculate the remaining time, and it does seem to return the number of seconds correctly.

{{ (state_attr('timer.alarm_trigger','finishes_at') | as_datetime | as_local - now()).total_seconds() | int }}

i don’t really know how i got this to work, it was really pieced together from bits of information from here and there.
i also found your example, but i couldn’t understand how to apply it. was i supposed to define this under the variables section or i was supposed to create a template section inside my automation? your code looks nicer than mine. i would like to steal it and use it for my own.

here is my automation that seems to work for me. i used if-else to use a hardcoded response if the timer helper didn’t have any value (which took me a good while to figure out why the automation didn’t
work when the helper’s state was idle).

i’m sure it will have loads of mistakes or bad practices, so please let me know!

alias: Alert Motion Detected when alarm armed home
description: ''
trigger:
  - platform: state
    entity_id:
      - binary_sensor.zone_attic_motion_open
      - binary_sensor.zone_main_door_open
      - binary_sensor.zone_kitchen_door_open
      - binary_sensor.zone_laundry_door_open
      - binary_sensor.zone_gate_open
      - binary_sensor.zone_laundry_motion_open
      - binary_sensor.zone_kitchen_boundary_open
      - binary_sensor.zone_porch_boundary_open
      - binary_sensor.zone_laundry_boundary_open
    from: 'off'
    to: 'on'
condition:
  - condition: state
    entity_id: alarm_control_panel.partition_area_1
    state: armed_home
action:
  - if:
      - condition: state
        entity_id: timer.alarm_trigger
        state: idle
    then:
      - service: timer.start
        data:
          duration: '60'
        target:
          entity_id: timer.alarm_trigger
  - service: script.announce_with_voice
    data:
      speaker_device: media_player.bedroom_2
      announce_volume: 0.35
      announcement_msg: '{{ phrases }}'
      check_sensor: '{{ wait_sensor }}'
      check_condition: '{{ wait_condition }}'
    enabled: true
mode: single
variables:
  config_location:
    binary_sensor.zone_attic_motion_open: attic motion
    binary_sensor.zone_main_door_open: main door
    binary_sensor.zone_kitchen_door_open: kitchen door
    binary_sensor.zone_laundry_door_open: laundry door
    binary_sensor.zone_1st_flr_motion_open: first floor motion
    binary_sensor.zone_gate_open: gate
    binary_sensor.zone_laundry_motion_open: laundry motion
    binary_sensor.zone_kitchen_boundary_open: kitchen boundary
    binary_sensor.zone_porch_boundary_open: porch boundary
    binary_sensor.zone_laundry_boundary_open: laundry boundary
  location_name: |
    {{ config_location.get(trigger.to_state.entity_id, 'unknown' ) }}
  phrases: >-
    {{ (
      "pardon me...",
      "excuse me...",
      "sorry to interrupt..."
    ) | random }}
    the alarm is being triggered... movement was detected by the {{ location_name }} sensor...
    {% if is_state('timer.alarm_trigger','active') %}
      alarm in {{ (state_attr('timer.alarm_trigger','finishes_at') | as_datetime | as_local - now()).total_seconds() | int }} seconds
    {% else %}
      alarm in 58 seconds.
    {% endif %}
  wait_sensor: alarm_control_panel.partition_area_1
  wait_condition: armed_home

It’s the same as the example I referred you to, minus the use of timestamp_custom (because the other application had slightly different requirements), plus the addition of a superfluous filter (as_local isn’t needed because finishes_at is an offset-aware datetime object).

{{ (state_attr('timer.alarm_trigger', 'finishes_at') | as_datetime - now()).total_seconds() | int(0) }}

Be advised that this template, unlike the referenced example, will fail if the timer isn’t active.

This one will return 0 if the timer isn’t active.

{{ (state_attr('timer.alarm_trigger', 'finishes_at') | default(now().timestamp(), true) | as_datetime - now()).total_seconds() | int(0) }}