Incremental Timer System with Bubble Card

Here’s how I use the awesome bubble card to create incremental timer control.

bubble-bump

Click on the +10 min button and it will start a timer with 10 minutes or add ten minutes if the timer is already running. When the timer starts, the fan turns on. When the timer finishes, the fan turns off. When the timer is running, you see the red cancel timer button and the remaining time card below (as shown). Keep reading to find out why it’s so ugly.

I tell my users (family) to think of the timer as an automatic off. If you no longer want an automatic off, cancel the timer with the red button. The fan will stay on. (I use a confirm popup to make sure they remember that. YMMV)

Clicking on the fan icon toggles the fan on/off. When the fan turns off, the timer is cancelled.

Notes:

  1. Everything is a switch. Two simple automations monitor all timers. When a timer starts or finishes it turns on or off a switch of the same name. (timer.my_fan controls switch.my_fan). If I want to automate a light or anything else, I simply create a template switch. A third automation cancels the timer when the switch is off. I use this system flawlessly on over fifty timers.

  2. The decluttering card is a great way to create the bubble card once and use it many times. I’m including it, but you could easily remove it.

  3. Home Assistant timers are weird. First even though timers have a remaining attribute, it does does not accurately show the remaining time on an active timer unless it is paused. Some people create entities and count them down with automations. Or, you can calculate the remaining time by subtracting the current time from the finishes_at attribute.

  4. Some cards calculate the remaining time for you. The ones I’m aware of are:

  • the entities card (but not the entity card).
  • the custom button card IIRC
  • a heading badge which is my ugly workaround that uses visibility to show when the timer is running

Have fun.

YAML to call decluttering card and heading card. Adjust timer label and duration here. Add your specific switch and timer. My timer icon is: mdi:timer-off-outline

  - type: custom:decluttering-card
    template: bubble-bump
    variables:
      - onoff-entity: switch.gym_fan # '[[onoff-entity]]'
      - timer-entity: timer.gym_fan  # '[[timer-entity]]'
      - duration-name: "+10 min"     # '[[duration-name]]'
      - duration-seconds: 600        # '[[duration-seconds]]'
# this does work but throws lots of VS Code errors
  - type: heading
    badges:
      - type: entity
        entity: timer.gym_fan
    heading: Remaining time
    heading_style: subtitle
    visibility:
      - condition: state
        entity: timer.gym_fan
        state: active

Automation to cancel specific timers. Add your specific switches.

- alias: "Cancel Timer"
  id: cancel_timers
  mode: parallel
  max: 25
  triggers:
    - trigger: state
      to: "off"
      entity_id:
        - switch.gym_fan
        # add additional switches here
        # I create copies of this animation and include about ten switches in each
  actions:
    - action: timer.cancel
      target:
        entity_id: "timer.{{ trigger.to_state.entity_id.split('.')[-1] }}"

Everything below here requires no customization.

YAML for decluttering card. Feel free to choose better colors.

  bubble-bump:
    card:
      type: custom:bubble-card
      card_type: button
      entity: '[[onoff-entity]]'
      tap_action:
        action: toggle
      button_action:
        tap_action:
          action: none
      show_state: false
      show_icon: true
      sub_button:
        - entity: '[[timer-entity]]'
          show_background: false
          visibility:
            - condition: state
              entity: '[[timer-entity]]'
              state: "active"
          tap_action:
            action: call-service
            service: timer.cancel
            target:
              entity_id: '[[timer-entity]]'
            confirmation:
              text: Switch will stay on.
        - name: '[[duration-name]]'
          show_name: true
          show_icon: false
          tap_action:
            action: call-service
            service: script.start_or_increment_timer
            data:
              timer_entity_id: '[[timer-entity]]'
              duration_seconds: '[[duration-seconds]]'
      styles: |
        .bubble-icon {
          color: ${state === 'on' ? 'var(--rju-state-on)' : ''} !important;
        }
        .bubble-button-background {
          opacity: 1 !important;
          background-color: #3d6c89 !important;
        }
        .bubble-sub-button-icon {
          --mdc-icon-size: 36px !important;
        }
        .bubble-sub-button-1 {
          color:rgb(141, 7, 29) !important;
        }
        .bubble-sub-button-2 {
          background-color: #2f8e2f !important;
        }

Script to start_or_increment_timer. No customization needed.

start_or_increment_timer:
  alias: Start or Increment Timer
  sequence:
    - variables:
        timer_entity_id: "{{ timer_entity_id }}"
        duration_seconds_int: "{{ duration_seconds | int }}"  
        # Get finishes_at datetime and calculate remaining time in seconds
        remaining_time_seconds: >
          {% set f = state_attr(timer_entity_id, 'finishes_at') %}
          {% if f is none %}
            0
          {% else %}
            {% set diff = (as_datetime(f) - now()).total_seconds() %}
            {% if diff < 0 %}
              0  # If the calculated difference is negative, set remaining time to 0
            {% else %}
              {{ diff | int }}
            {% endif %}
          {% endif %}
        # Ensure that remaining_time_seconds is treated as an int
        remaining_time_seconds_int: "{{ remaining_time_seconds | int }}"
        new_duration_seconds: "{{ remaining_time_seconds_int + duration_seconds_int }}"  # Increment timer
    - action: timer.start
      data:
        entity_id: '{{ timer_entity_id }}'
        duration: "{{ new_duration_seconds }}"

Automation when any timer finishes. No customization needed.

- alias: "Timer Finished to Switch"
  id: timer_finished_to_switch
  mode: parallel
  max: 25
  triggers:
    - trigger: event
      event_type: timer.finished
  actions:
    - action: switch.turn_off
      data: {}
      target:
        entity_id: "switch.{{ trigger.event.data.entity_id.split('.')[-1] }}"

Automation when any timer starts. No customization needed.

- alias: "Timer Started to Switch"
  id: timer_started_to_switch
  mode: parallel
  max: 25
  triggers:
    - trigger: event
      event_type: timer.started
  conditions: []
  actions:
        - action: switch.turn_on
          data: {}
          target:
            entity_id: "switch.{{ trigger.event.data.entity_id.split('.')[-1] }}"

It’s a calculation performed in the frontend by the Entities card.

If remaining were to report a realtime value, each value-change would be recorded in the timer’s history thereby bloating the database.

There’s a way to compute the remaining time by subtracting the current time from the timer’s finishes_at attribute.

{% set f = state_attr('timer.whatever', 'finishes_at') %}
{{ '00:00:00' if f == None else 
  (as_datetime(f) - now()).total_seconds() | timestamp_custom('%H:%M:%S', false) }}

The template can be used in a Template Sensor but it will only update every minute (by virtue of the inclusion of now() whose value changes every minute).

To make it update every second takes more effort. You would need to create a Trigger-based Template Sensor with a Time Pattern Trigger configured to fire every second. Ideally, this entity should be excluded from the Recorder integration (unless you don’t mind database bloat).

Unfortunately, now we have created an entity that triggers every second even when the timer isn’t running (which is the majority of the time). Nevertheless, this is what people have done to get a realtime timer countdown (but don’t want to use an Entities card).


EDIT

You may wish consider using the technique in the template I posted, for computing the remaining time in seconds, instead of the one used in your example (which takes a longer approach by splitting the time string to convert each part in seconds, etc).

Thanks for the thoughtful clarifications. I’ve seen the triggers that fire every second but avoided that route to prevent needless overhead. I have over fifty timers and only “need” the remaining time for the ones I’m looking at.

How can that calculation be replicated in the frontend of other cards? The Custom Button card could display remaining time, and a badge does too.

Edit: I took your advice on calculating remaining time and updated the first post. Cheers.

im a little lost on where each part needs to go in home assistant trying to make the same thing but for a light.

OK, but I am not the author of this topic.

1 Like

my bad clicked on the wrong reply :grin: can you help me? is there a way to change the reply to the right person or do I have to re do the reply? new to using the form.

I leave it RichardU to answer your questions about his project.

You need to create a timer with the same name as your switch. Use HACS to download the custom bubble card, and decluttering card. Then use an editor like VS Code to insert the YAML into automations, scripts, and lovelace, assuming you have set: mode: yaml in your configuration file.

Those are all tools and concepts that will be useful for anyone who wants to fully utilize Home Assistant.