Display remaining seconds of timer in frontend

If you are into NR, I’d give this one a go. The only downside vs the stock HA timer is it doesn’t allow to pause by default, a workaround could be done though. For the rest, it’s better than the HA timer in every way.

Valid question, I think the bottom line for me is to save space on the UI.
I use a status line that represents all sorts of statuses, that may include a timer given a switch is on for a pre-determined amount of time. Displayed nicely in a single line on the bottom of a picture elements card.

The alternative would be to display 3 input booleans, 6 input numbers, sun rise, sun set, and the timer.

Not yet tested but this link may help for timers / lovelace:

3 Likes

This solved it perfectly for me - thanks! It updates in real time and looks good. I had to mickey around with it a bit to get it to center vertically, but it looks good.

Just in case it is useful, I just figured out how to create these templates for my timers.

In the first template, the - 20 part is the initial duration of the timer, the template would be better if I can figure out how to extract the duration and add that in automatically.

There is  {{ ( (as_timestamp(now()) - as_timestamp(states.timer.d_heat_1.last_changed)) / 60 ) | round(0) - 20 }} minutes till timer one ends.

T1 was triggered  {{ ( (as_timestamp(now()) - as_timestamp(states.timer.d_heat_1.last_changed)) / 60 ) | round(0) }} minutes ago.
2 Likes

not sure I saw the solution mentioned here yet, but for Button-card users this is what Author Romrider suggests:

needs some more work, but you get the idea.

Jan-08-2022 14-01-36

1 Like

I’m not sure what the question was now :slight_smile: but this is my timer button. (It is based on someone else’s template who will probably recognise it but I won’t name them so they don’t get tons of questions :wink:)

image

The circle gets smaller with the time remaining.

4 Likes

yep, nice, recognize the format :wink:
the circle is a nice touch indeed

will have a look there…
hmm, can not seem to find it any more in my test file. @klogg would you mind dropping the code here please. (or dm me if you fear any followup :wink: )

this must be close, but yours is clearly developed …

Schermafbeelding 2022-01-08 om 16.19.04

1 Like

Of course not :slight_smile:
It has been hacked about a fair bit from many sources so probably has remnants that aren’t strictly necessary anymore. I’ve been meaning to clean up my templates for ages but hey, they work and there are lots more interesting things to do :wink:

also I use !include not button card templates. (Although I am considering changing if I can be convinced the effort to change is worth it?).


# lovelace_gen

type: custom:button-card
aspect_ratio: {{ aspect_ratio | default('1/1') }}
entity: {{ entity }}
name: {{ name }}
icon: {{ icon }}
color: var(--paper-item-icon-color)
show_name: true
show_label: true
show_icon: true
show_state: true
show_last_changed: {{ last_changed | default(true) }}
size: 70%
tap_action:
  action: {{ tap_action | default('none') }}
styles:
  icon:
    - color: >
        [[[
          if (entity.state == 'active')
            return 'rgb(255, 165, 0)';
          else return 'unset';
        ]]]
    - opacity: 0.3
    - width: 100%
  img_cell:
    - top: 0%
    - left: 30%
    - position: absolute
    - z-index: 1
  grid:
    - grid-template-areas: '"info" "n" "s" "l"'
    - grid-template-rows: 1fr min-content min-content min-content
    - position: relative
  card:
    - padding: 10px
    - border-radius: 5px
  name:
    - z-index: 2
    - justify-self: start
    - align-self: end
    - font-weight: bold
    - font-family: Roboto 
    - font-size: {{ font_size | default('12px') }}
    - text-align: start
    - background-image: linear-gradient(to right, white 0%, white 80%, rgba(0,0,0,0))
    - -webkit-background-clip: text
    - -webkit-text-fill-color: transparent
    - position: relative
    - display: inline-block
    - width: 100%
    - align-content: start
    - text-align: start
    - text-overflow: unset
  state:
    - z-index: 2
    - justify-self: start
    - align-self: end
    - font-weight: bold
    - font-family: Roboto 
    - font-size: {{ font_size | default('12px') }}
    - text-align: start
    - background-image: linear-gradient(to right, var(--paper-item-icon-color) 0%, var(--paper-item-icon-color) 80%, rgba(0,0,0,0))
    - -webkit-background-clip: text
    - -webkit-text-fill-color: transparent
    - position: relative
    - display: inline-block
    - width: 100%
    - align-content: start
    - text-align: start
    - text-overflow: unset
  label:
    - justify-self: start
    - align-self: end
    - font-weight: bold
    - font-family: Roboto 
    - font-size: {{ font_size | default('12px') }}
    - text-align: start
    - background-image: linear-gradient(to right, var(--paper-item-icon-color) 0%, var(--paper-item-icon-color) 80%, rgba(0,0,0,0))
    - -webkit-background-clip: text
    - -webkit-text-fill-color: transparent
    - position: relative
    - display: inline-block
    - width: 100%
    - align-content: start
    - text-align: start
    - text-overflow: unset
  custom_fields:
    info:
      - z-index: 0
      - align-self: start
      - width: 40%
custom_fields:
  info: |
    [[[
      if (entity.state === 'active') {
        var start_time = new Date();
        var end_time = new Date(entity.attributes.finishes_at);
        var duration = new Date('1970-01-01T0' + entity.attributes.duration + 'z');
        duration = Math.floor(duration.getTime() / 1000);
        var remaining_time = Math.floor((end_time.getTime() - start_time.getTime()) / 1000);
        const length = 50;
        const width = 3;
        var radius = length / 2;

        radius = (length - 3) / 2;
        const circumference = radius * 2 * Math.PI;
        return `
          <svg viewBox="0 0 50 50">
            <circle cx="25" cy="25" r="${radius}" fill="none" stroke="none" opacity="0.5" stroke-width="${width}" />
            <circle style="
                transform: rotate(-90deg);
                transform-origin: 50% 50%;
                stroke-dasharray: ${circumference};
                stroke-dashoffset: ${circumference - (remaining_time / duration) * circumference};
              "
              id="c_brightness" cx="25" cy="25" r="${radius}" stroke="var(--paper-item-icon-active-color)" stroke-width="${width}" fill="none" stroke-linecap="round" />
            <text x="50%" y="54%" fill="var(--primary-text-color)" font-weight="bold" font-size="15" text-anchor="middle" alignment-baseline="middle">${"Active"}<tspan font-size="10" font-weight="normal" ></tspan>
            </text>
          </svg>
          `;
      }
    ]]]
state:
- value: 'active'
  icon: mdi:motion-sensor
  styles:
    card:
      - opacity: 1.0
      - box-shadow: 0px 0px 2px 1px var(--paper-item-icon-color)
      - animation: wiggle 0.16s 6
    name:
      - color: white
      - opacity: 1.0
    state:
      - color: gray
    label:
    - display: none
    icon:
    - transition: all 2s ease 
- value: 'idle'
  styles:
    card:
    - opacity: 0.5
    name:
    - color: var(--primary-text-color)
    state:
    - display: none
    label:
    - color: var(--primary-text-color)
- value: 'unavailable'
  styles:
    card:
    - opacity: 0.2
    name:
    - color: var(--primary-text-color)
    state:
    - color: hsl(0, 100%, 50%)
    label:
    - color: var(--primary-text-color)
extra_styles: |
  @keyframes wiggle {
    0% { transform: rotate(0deg); }
    33% { transform: rotate(10deg); }
    66% { transform: rotate(-10deg); }
    100% { transform: rotate(0deg); }
  }
2 Likes

cool, and yp does the trick, (had to replace the lovelace_gen and add some actions and showing:)

Jan-08-2022 17-32-57

must check circumference (not working at all…) but hey, this is a good start :wink: thanks!

followup

edited it to my settings elsewhere, and cleaned the code up a bit, but still no circular indication of the timer, all else works as expected. I guess something is wrong with the template, but I can not debug that.

could anyone with a keen eye please have a look what I missed:

custom_fields:
  info: |
    [[[
      if (entity.state === 'active') {
        var start_time = new Date();
        var end_time = new Date(entity.attributes.finishes_at);
        var duration = new Date('1970-01-01T0' + entity.attributes.duration + 'z');
        duration = Math.floor(duration.getTime() / 1000);
        var remaining_time = Math.floor((end_time.getTime() - start_time.getTime()) / 1000);
        const radius = 20.5;
        const circumference = radius * 2 * Math.PI;
        return `
          <svg viewBox="0 0 50 50">
            <circle cx="25" cy="25" r="${radius}"
                stroke="var(--active-color)" stroke-width=2 fill="none" stroke-linecap="round"
                style="transform: rotate(-90deg);transform-origin: 50% 50%;
                stroke-dasharray: ${circumference};
                stroke-dashoffset: ${circumference - remaining_time / duration * circumference};"/>
            <text x="50%" y="54%" fill="red" font-weight="bold" font-size="12" text-anchor="middle" alignment-baseline="middle">
              ${"Active"}
            </text>
          </svg>
        `;
      }
    ]]]
state:
  - value: active
    icon: mdi:timer-sand
    spin: true
    styles:
      card:
        - box-shadow: 0px 0px 2px 1px var(--paper-item-icon-color)
        - animation: wiggle 0.16s 6
      name:
        - color: white
      state:
        - color: gray
      label:
        - display: none
      icon:
        - transition: all 2s ease

edit

right, so it was simple… and it suddenly showed fine using

stroke-dashoffset: ${circumference - (remaining_time/duration * circumference)}

dont / and * take precedence over - by default?

then again, changing views and getting back a minute later: full circle again. What is this?

Ciao Marius, hi all,
I return back with this question because I’m not able to do a custom button with the following requirements:

  • when I push the button the action is toggle an input_boolean.scaldabagno_temporizzato
  • on the corner (top right) the countdown digits of a timer.scaldabagno

(Marius, do you remember? :cry: )

Someone can help me?
I’d tried with many type of ideas without a valid result.

Whit timer as entity is easy to do, just use entity.state but whit an input_boolean concatenated with a timer is not easy (for me, of course :slight_smile: )

Thanks

I need help! I have read everything above and everywhere else but cannot figure is what is wrong. The template says: typeError: ‘float’ object is not iterable T
The sensor study.timer_end always shows “unavailable” also when the timer.timer_study has an active state

This is the template:

sensor:

  • platform: template
    sensors:
    study.timer_end:
    friendly_name: “Time Remaning Study Timer”
    device_class: timestamp
    entity_id: timer.timer_study
    value_template: >-
    {%- set duration = state_attr(‘timer.timer_study’ , ‘duration’ ) %}
    {%- if duration in [‘none’ , ‘idle’] %}
    unavailable
    {%- else %}
    {% set h, m, s = duration.split(’:’) %}
    {% set n = now().timestamp() | list | map(‘int’) %}
    {{ (n + h|int * 60 * 60 + m|int * 60 + s|int) | timestamp_custom(’%Y-%m-%dT%H:%M:%S-00:00’, False) }}
    {%- endif -%}

The duration of the timer.timer_study is 3:00:00 which comes from a script (below) that get’s the time from another sensor (sensor.set_time) which shows 03:00:00. So this sensor has a leading 0 which is missing in the timer.timer_study states. Not sure whether this is the cause because the timer shows the correct finishing at time 3 hours from when the script ran.

this is the script:

alias: Set Temperature
sequence:

  • service: climate.set_temperature
    target:
    entity_id: ‘{{ states(’‘sensor.set_room’’) }}’
    data:
    temperature: ‘{{ states(’‘sensor.set_temperature’’) }}’
  • service: timer.start
    data:
    duration: |
    {{ states(‘sensor.set_time’) }}
    target:
    entity_id: timer.timer_study
    mode: single

@petro, Your help would be very much appreciated as I spent days to it figure out without success

Here’s the DIY version:

Take the template code and put it in the template editor under the dev tools. Reproduce the error, and then remove lines starting at the end to find out which line gives the error.

Hint: The error your getting means: You have a decimal number and something is trying to loop over it. Look for lines that have loops.

Also, please format your code using the code button or wrap the blocks in 3 backticks.

I’m really trying to get this timer in my HA but due to lack of understanting, I don’t understant (:blush:) how to get this done.
I’ve put this code in my configuration.yaml:

#Sensore Timer Riscaldamento Boost 1h
sensor:
  - platform: mqtt
    state_topic: "home/timers/timer.boost.riscaldamento/start"
    name: "Timer Start Boost Riscaldamento"
  - platform: mqtt
    state_topic: "home/timers/timer.boost.riscaldamento/duration"
    name: "Timer Durata Boost Riscaldamento"
  - platform: template
    sensors:
      timer_remaining_hms_boost_riscaldamento:
        friendly_name: "Timer - Remaining HMS - Boost Riscaldamento"
        value_template: >-
          {%- if states['sensor.timer_remaining_boost_riscaldamento'].state == 0 -%}
          00:00:00
          {%- else -%}
          {{ states['sensor.timer_remaining_boost_riscaldamento'].state | int | timestamp_custom('%H:%M:%S', 0) }}
          {%- endif -%}
        entity_id: 
          - sensor.timer_remaining_boost_riscaldamento

What next? I assume I have to create an automation, but how based on my timer here above?

On my dashboard I have a button which toggles a script that switches on the heating in all rooms for 60 minutes. Next to the name of the button I need to show the remaining time of the task (which should be shown as HH:MM:SS).

Can you please help?

Thanks so much!!!

Petro can you please help?
I’m stuck here…

Dear all, dear @123 , dear @petro,

I likewise tried to built a sensor with the remaining time of a timer as state.
However, I couldn’t handle to do so. I tried two different version I found in the community:

Version 1:

  - platform: template
    sensors:
      remaining_fahrradgarage:
        friendly_name: "Remaining Time Fahrradgarage"
        value_template: >-
          {% set f = state_attr('timer.fahrradgarage', 'finishes_at') %}
          {{ '00:00:00' if f == None else 
            (as_datetime(f) - now()).total_seconds() | timestamp_custom('%H:%M:%S', false) }}
        entity_id: 
          - timer.fahrradgarage

Version 2:

  - platform: template
    sensors:
      timer_end:
        friendly_name: "Timer End"
        device_class: timestamp
        entity_id: timer.fahrradgarage # <--- Your timer (and first line of template)
        value_template: >-
          {%- set duration = state_attr('timer.fahrradgarage', 'duration') %}
          {%- if duration in ['none', 'idle'] %}
            unavailable
          {%- else %}
            {% set h, m, s = duration.split(':') | list | map('int') %}
            {% set n = now().timestamp()  %}
            {{ (n + h * 60 * 60 + m * 60 + s) | timestamp_custom('%Y-%m-%dT%H:%M:%S-00:00', False) }}
          {%- endif -%}

This is the output when timer.fahrradgarage is running:

Version 1:
image
This looks fine, but it refreshes once a minute and the amount of seconds depends on starting time (with a timer duration of 10:00 minutes first output is always 09:59, and then e.g. 08:43, 7:43, 6:43 […] depending on starting time). Issue with this is, that I would like to trigger an automation a a certain amount of minutes (e.g. 05:00) remaining.

Version 2:
image
This provides a timestamp and not remaining time.

For my purpose the remaining time of the timer or (even better) the passed time of a timer in minutes (without seconds) would be perfect.

Regarding my self: I’m a “try and error” copy and paste coder. Please be pleasant with me :slight_smile:

Thank you a lot and all the best
Benedikt

For anyone coming to this thread, this is the way you’d do this in current version of HA.

template:
- trigger:
  - platform: time_pattern
    seconds: '/1'
  sensor:
  - name: Countdown
    state: >
      {{ state_attr('timer.test', 'finishes_at') | as_datetime - now().replace(microsecond=0) }}
    availability: >
      {{ state_attr('timer.test', 'finishes_at') is not none }}

or if you don’t want the countdown to go unavailable when the timer is not running…

template:
- trigger:
  - platform: time_pattern
    seconds: '/1'
  sensor:
  - name: Countdown
    state: >
      {% set end = state_attr('timer.test', 'finishes_at') %}
      {% if end is not none %}
        {{ end | as_datetime - now().replace(microsecond=0) }}
      {% else %}
        No Timer
      {% endif %}

Lastly, you should exclude this entity from recorder, otherwise it will write an update to your database every second that the timer is running.

7 Likes

I appreciate the updated comment, but for the life of me I cannot figure out how to use this. I tried adding one of these code blocks to the bottom of my configuration.yaml file and reloaded my yaml file and I cannot find the Countdown entity anywhere. Can you elaborate on how to use this template code?

thanks!

You paste it in configuration.yaml, restart, then you can find it in developer tools → states page.

1 Like

thanks, that worked! for future reference, how can I know if a configuration change requires a restart or not? “Template entities” is an option in the YAML config reloading, so I thought I could just reload all the YAML and it would show up that way.