Display remaining seconds of timer in frontend

Good work! But I don’t see definition of sensor.timer_duration_aircon_run that comes in automation. It’s time that’s determined in UI?

Yeah looks like I missed that one, I’ve updated the original post. Its an MQTT sensor.

1 Like

I tried to adapt this template for two different timers. But I can’t get it to work.
I have this in configuration.yaml:

timer:
  rasen:
    duration: '02:23:00'
    icon: mdi:timer-outline
  beete:
    duration: '01:20:00'
    icon: mdi:timer-outline

sensor:
  - platform: time_date
    display_options:
      - 'time'
      - 'date'
      - 'date_time'
      - 'date_time_utc'
      - 'date_time_iso'
      - 'time_date'
      - 'time_utc'
      - 'beat'
  - platform: template
    sensors:
      rasen_timer_end:
        friendly_name: "Rasen Timer End"
        device_class: timestamp
        entity_id: timer.rasen # <--- Your timer (and first line of template)
        value_template: >-
          {%- set duration = state_attr('timer.rasen', '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 -%}
      beete_timer_end:
        friendly_name: "Beete Timer End"
        device_class: timestamp
        entity_id: timer.beete # <--- Your timer (and first line of template)
        value_template: >-
          {%- set duration = state_attr('timer.beete', 'duration') %}
          {%- if duration in ['none', 'idle'] %}
            unavailable
          {%- else %}
            {% set h, m, s = duration.split(':') %}
            {% set n = now().timestamp() | list | map('int') %}
            {{ (n + h * 60 * 60 + m * 60 + s) | timestamp_custom('%Y-%m-%dT%H:%M:%S+02:00', False) }}
          {%- endif -%}

Both timers work correct. I can start them an they show me the timestamp when finished.

The sensors are created but their states are unavailable.

What do I wrong?

try this

          {%- set duration = state_attr('timer.rasen', '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 -%}

Thank you very much. It was a step to the right way. The sensor is now updating … but … strange behaviour … the update is every 30 seconds. :slight_smile:
For example: If I use a 5 min timer, the display says 5 min left. After 30 seconds the display says 4 min left … and … hold on to your seat … another 30 sec later it jups to … 5 min. It goes so again and again. From 5 to 4 to 5 to 4 … Even when the timer is stopped. :face_with_raised_eyebrow:

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…