Recurring Task Notification with datetime calculations (HVAC Filter Replacement Reminder)

Background: I like to replace my HVAC filter every 90 days or so… but it usually ends up actually happening way past the “or so” :wink:
In my setup, on each day past the “due date” I receive a notification to alert me that it’s time to change the filter. After I change it, I physically press a zigbee smart button that’s attached to my HVAC unit (located just above the filter slot) to restart the countdown. I also use this same method to track the refilling of supplement containers, toothbrushes replacement, contact lens age - this could apply to many different recurring tasks. I’d love to hear about your setups!

I’m using a zigbee-based smart button to restart the countdown after I’ve completed the task - I won’t be covering how to add a smart button to HA in this guide. The smart button in my setup is simply the switch that I use to reset the countdown - you could use any other type of physical input that HA can read, even just a script or otherwise virtual button to perform the reset if you aren’t using smart buttons.

  1. To set up the HA configuration, first create the following 3 Helpers (Configuration -> Helpers -> Add Helper).
  • Toggle; named “HVAC Filter Clean”.
    This toggle/input_boolean is used as a status indicator for Lovelace dashboard. When toggled to the On position in the UI, the filter is clean. You could also use this input_boolean in combination with a custom template to make your dashboard display the words “Clean” and “Dirty” next to filter status (not covered in this guide).

  • Number; named “HVAC Filter Days Until Old”, with the unit of measurement configured for Days.
    This number/input_number allows you to choose the number of days before the filter expires. Later in this guide you’ll set the Number to the desired number of days before the filter is considered “Dirty”.

  • Date and/or Time; named “HVAC Filter Date Last Changed”.
    This date/input_datetime gets set to now() when you replace the filter, and is used in combination with the current date to calculate the filter’s age and expiry.

  1. Next is the fun(?) part - working with datetime calculations within Home Assistant. Most of my time spent on this project was working on these formulas. Add the following to your configuration.yaml file:
sensor:
  - platform: template
    sensors:
      hvac_filter_change_date:
        friendly_name: 'HVAC Filter Change Date'
        value_template: "{{ strptime(states('input_datetime.hvac_filter_date_last_changed'), '%Y-%m-%d %H:%M:%S') + timedelta(days=states('input_number.hvac_filter_days_until_old') | int) }}"
        icon_template: mdi:calendar-refresh-outline
      hvac_filter_days_left:
        friendly_name: 'HVAC Filter Days Left'
        value_template: >
          {% set hvac_filter_change_date = strptime(states('input_datetime.hvac_filter_date_last_changed'), '%Y-%m-%d %H:%M:%S') + timedelta(days=states('input_number.hvac_filter_days_until_old') | int) %}
          {{ (as_timestamp(hvac_filter_change_date) / 86400 - as_timestamp(now()) / 86400) | int  }}
        icon_template: mdi:clock-end
        unit_of_measurement: 'Days'
      hvac_filter_age:
        friendly_name: 'HVAC Filter Age'
        value_template: "{{ (( as_timestamp(now()) - (states.input_datetime.hvac_filter_date_last_changed.attributes.timestamp)) | int /60/1440) | round(0) }}"
        icon_template: mdi:clock-start
        unit_of_measurement: 'Days'
  1. Restart Home Assistant so that your newly created sensors are available.

  2. Create a new Automation (Configuration -> Automations -> Add Automation) named “Status: HVAC Filter Changed”. This Automation is responsible for setting the status back to “Clean” and also setting the “date last changed” to today. This is the automation that will be executed when you press your physical smart button to restart the countdown process. Configure this Automation as follows (or if you’re comfortable with pasting the YAML, I’ve provided it at the bottom of this step):

  • Add a Trigger of type “State”. Choose the entity that you want to use as your smart button, the one you’ll physically press after you’ve changed the filter - in my case, it is: sensor.hvac_filter_smart_button_action. In the “To” field, mine is configured for “release” (note this is case sensitive).

  • Add an Action of type “Call Service”. The Service to call is input_boolean.turn_on, and the entity to target is input_boolean.hvac_filter_clean.

  • Add one more Action but don’t choose a type - for this one, we’ll need to use the raw YAML editor in order to allow us to access the data_template capability. Click the three vertical dots on the upper right of the new Action card and then click Edit in YAML. Paste the following code:

    service: input_datetime.set_datetime
    data_template:
      date: '{{ now().strftime(''%Y-%m-%d'') }}'
    target:
      entity_id: input_datetime.hvac_filter_date_last_changed
    
  • Click Save

Full YAML:

alias: 'Status: HVAC Filter Changed'
description: ''
trigger:
  - platform: state
    to: release
    entity_id: sensor.hvac_filter_smart_button_action
condition: []
action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.hvac_filter_clean
  - service: input_datetime.set_datetime
    data_template:
      date: '{{ now().strftime(''%Y-%m-%d'') }}'
    target:
      entity_id: input_datetime.hvac_filter_date_last_changed
mode: single
  1. Create a new Automation (Configuration -> Automations -> Add Automation) named “Notify: HVAC Filter”. This Automation is responsible for changing the filter status to “Dirty” and also notifying you that it’s time to replace the filter. This is the automation that will be executed when the countdown process hits 0 days. Configure this Automation as follows (or if you’re comfortable with pasting the YAML, I’ve provided it at the bottom of this step):
  • Add a Trigger of type “State”. Choose the entity sensor.hvac_filter_days_left. In the “To” field, enter “0” (number zero).

  • Add an Action of type “Call Service”. The Service to call is input_boolean.turn_off, and the entity to target is input_boolean.hvac_filter_clean.

  • Add another Action of type “Call Service”. The Service to call is notify.YOUR_NOTIFY_SERVICE. Configure the notification Title and Message as you see fit.

  • Click Save

Full YAML:

alias: 'Notify: HVAC Filter'
description: ''
trigger:
  - platform: state
    entity_id: sensor.hvac_filter_days_left
    to: '0'
condition: []
action:
  - service: notify.YOUR_NOTIFY_SERVICE
    data:
      title: HVAC Filter
      message: 'HVAC Filter needs replaced. '
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.hvac_filter_clean
mode: single

  1. On your desired Lovelace Dashboard, add a new Entities List card containing the following 6 entities:

    input_boolean.hvac_filter_clean
    input_datetime.hvac_filter_date_last_changed
    input_number.hvac_filter_days_until_old
    sensor.hvac_filter_change_date
    sensor.hvac_filter_age
    sensor.hvac_filter_days_left
    
  2. Set “HVAC Filter Days Until Old” to your desired number of days to replace.

  3. Replace your filter and then press your smart button or other monitored input device.

Regards,
Chris

7 Likes

I LITERALLY just finished building a reminder framework in HA last night after the better part of a week. After not finding exactly what I wanted, I was inspired by these forum posts and started coding:

Since mine was far more general, I started off with 2 goals: Fully functional without automations (through the UI), and no additional custom components beyond the multiple-entity-row I was already using. After getting it up and running, I scrapped #2 as the amount of code I was adding to lovelace was unsustainable, so I added this to make the job cleaner and to make it far easier to add new items to track:

It is basically blueprints for lovelace code, which is super helpful and seems like the kind of thing that could be a first party feature in a future release.

Mine basically works as follows. I have 2 types of helpers, a “last” and a “next”, depending on how I track the events. If it is something that does not occur on a rigid schedule, it is a “last” event, and is set to the date/time of when it last occurred, but can still display a due date based on an interval. The “next” event is either a date/time or a mileage counter for a vehicle.

So far, I have no notification or automation code at all for reminders, I was planning on using Blueprints for that once I got familiar. Events are compared against an interval value to say when they are due or overdue, for vehicle mileage this is an additional helper of the current mileage, for date/time this is done in the UI template. The important part, and the reason I did the templated code, is due to the custom time display I am using:


Reminders2
Reminders3

I still want to change the way they look on screen, on a phone the text is cutoff from the date/time value for long event names, but I want the same indented layout format. You can tap the value to change it manually, or long-press to run the script and change it automatically. I also need to get it to preload the markdown card code since it sometimes does not display correctly unless you go back and forth from the main lovelace tab

The humidifier filters are seasonal so I used a last changed template for those, as I did with the supplements, the mileage and furnace filter are next change templates. I have 3 helper scripts to reset the values from the UI, one for last/next date/time with an optional offset in seconds, one for next date/time with an offset in months, and one for miles.

The month one was HARD, since python timedelta does not support months, but I figured out a way to convert the state value to a python datetime object. This one allows me to reset the value to a specific amount of months past the original due date, regardless of how many days are in a month, very useful for quarterly events like the furnace filter change.

alias: Mileage Increment Next
sequence:
  - service: input_number.set_value
    data:
      value: |
        {{ states(source_mileage)|int + (interval)|int }}
      entity_id: '{{ entity }}'
mode: single
alias: Set Timedate
sequence:
  - service: input_datetime.set_datetime
    data:
      timestamp: '{{ now().timestamp() + timedate }}'
      entity_id: '{{ entity }}'
mode: single
alias: Timedate Increment Months
sequence:
  - service: input_datetime.set_datetime
    data:
      datetime: >
        {%- set dtobj=now().fromtimestamp(as_timestamp(states(entity))) %}
        {%- set sub_months=((dtobj.year)*12+(dtobj.month+timedate-1)) %}
        {%- set new_months=((sub_months) % 12)+1 %}
        {%- set new_year=((sub_months - new_months + 1)/12) | int %}
        {{ dtobj.replace(month=new_months).replace(year=new_year) }}
      entity_id: '{{ entity }}'
mode: single

These are some examples of the simplified code that uses the decluttering templates
I have it setup with changeable defaults for both function and display to be extendable for a variety of use cases

type: 'custom:decluttering-card'
template: next_event
variables:
  - entity: input_datetime.next_event_furnace_air_filter
  - name: Furnace Air Filter
  - service: script.script_timedate_increment_months
  - icon: air-filter
  - interval: 3
  - interval_text: Interval 3 months
type: 'custom:decluttering-card'
template: next_event
variables:
  - entity: input_datetime.next_event_richie_allergy_pill
  - name: Next Zyzal
  - icon: pill
  - interval: 86400
  - interval_text: Interval 24 hours
type: 'custom:decluttering-card'
template: last_event
variables:
  - entity: input_datetime.last_event_richie_vitamins_d3
  - name: Last D3
  - icon: pill
  - overdue_text: Last taken
type: 'custom:decluttering-card'
template: next_mileage
variables:
  - entity: input_number.vehicle_next_mileage_mazda_cx5_engine_oil
  - name: Next Mileage - Oil Change
  - source_mileage: input_number.vehicle_current_mileage_mazda_cx5
  - interval: 6000

And here are the actual templates that make the above work

decluttering_templates:
  last_event:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - service: script.script_set_timedate
      - comments: Resets the timer of a Last event to now
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  timedate: 0
                confirmation:
                  text: Reset Timestamp?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set ts_period = [[interval]] %} {%- set ts_event =
                as_timestamp(states.[[entity]].state) %} {%- set ts_now =
                as_timestamp(now())|round(0) %} {%- set ts_delta = (ts_now -
                ts_event) %} {%- set ts_xdiff = (ts_period - ts_delta)|abs %} {%
                if ts_delta < ts_period %}[[next_text]] {% else
                %}[[overdue_text]] {% endif %}{% if ts_xdiff >= 604800 %}{{
                (ts_xdiff // 604800) | int }} w, {{ (ts_xdiff % 604800 // 86400)
                | int }} d {% elif ts_xdiff >= 86400 %}{{ (ts_xdiff % 604800 //
                86400) | int }} d, {{ (ts_xdiff % 86400 // 3600) | int }} h {%
                elif ts_xdiff >= 3600 %}{{ (ts_xdiff % 86400 // 3600) | int }}
                h, {{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 600
                %}{{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 60
                %}{{ (ts_xdiff % 3600 // 60) | int }} m, {{ (ts_xdiff % 60) |
                int }} s {% else %}{{ (ts_xdiff % 60) | int }} s {% endif %}

                [[interval_text]]
                
  next_event:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - service: script.script_set_timedate
      - comments: Resets the timer of a Next event to now+interval
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              secondary_info: last-changed
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  timedate: '[[interval]]'
                confirmation:
                  text: Reset Timestamp?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set ts_period = 0 %} {%- set ts_event =
                as_timestamp(states.[[entity]].state) %} {%- set ts_now =
                as_timestamp(now())|round(0) %} {%- set ts_delta = (ts_now -
                ts_event) %} {%- set ts_xdiff = (ts_period - ts_delta)|abs %} {%
                if ts_delta < ts_period %}[[next_text]] {% else
                %}[[overdue_text]] {% endif %}{% if ts_xdiff >= 604800 %}{{
                (ts_xdiff // 604800) | int }} w, {{ (ts_xdiff % 604800 // 86400)
                | int }} d {% elif ts_xdiff >= 86400 %}{{ (ts_xdiff % 604800 //
                86400) | int }} d, {{ (ts_xdiff % 86400 // 3600) | int }} h {%
                elif ts_xdiff >= 3600 %}{{ (ts_xdiff % 86400 // 3600) | int }}
                h, {{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 600
                %}{{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 60
                %}{{ (ts_xdiff % 3600 // 60) | int }} m, {{ (ts_xdiff % 60) |
                int }} s {% else %}{{ (ts_xdiff % 60) | int }} s {% endif %}

                [[interval_text]]
                
  next_mileage:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - service: script.script_set_next_mileage
      - unit: miles
      - comments: Sets a mileage value to an input mileage
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              secondary_info: last-changed
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  source_mileage: '[[source_mileage]]'
                  interval: '[[interval]]'
                confirmation:
                  text: Reset Mileage?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set mi_source = states.[[source_mileage]].state|int %} {%-
                set mi_target = states.[[entity]].state|int %} {%- set mi_xdiff
                = (mi_target - mi_source)|abs %} {% if mi_source < mi_target
                %}[[next_text]] {% else %}[[overdue_text]] {% endif %} {{
                mi_xdiff }} [[unit]]

                Due every [[interval]] [[unit]]
4 Likes

So you celebrated new year 1969-1970 with taking a vitamin pill?

LOL, no the recently added helper has a default unix timestamp value of 0, and I have not yet reset the value, so it displays as so, I have about 8 things that say that right now as I have only just started adding more helpers

1 Like

I decided to play with conditional formatting using card-mod, to outline the cards if they are near-due (yellow) or overdue (red). Near due warning time is configurable, code is not done yet but the wife thinks it is far easier to see when something needs attention

1 Like

Thanks for sharing! Keep up the great work!

1 Like

Have you posted the formatting code to outline the cards?

Not yet, there were code issues in the core frontend that prevented proper use of the editor so some things were on hold. I think it is in pretty good shape now, I have not run into any issues that I know of.

You might need card-mod added in HACS or custom integrations for it to work, I have it installed but I am not sure if that is what is changing the style of the custom stack-in card. Here is the new template code:

decluttering_templates:
  last_event:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - service: script.script_set_timedate
      - comments: Resets the timer of a Last event to now
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  timedate: 0
                confirmation:
                  text: Reset Timestamp?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set ts_period = [[interval]] %} {%- set ts_event =
                as_timestamp(states.[[entity]].state) %} {%- set ts_now =
                as_timestamp(now())|round(0) %} {%- set ts_delta = (ts_now -
                ts_event) %} {%- set ts_xdiff = (ts_period - ts_delta)|abs %} {%
                if ts_delta < ts_period %}[[next_text]] {% else
                %}[[overdue_text]] {% endif %}{% if ts_xdiff >= 604800 %}{{
                (ts_xdiff // 604800) | int }} w, {{ (ts_xdiff % 604800 // 86400)
                | int }} d {% elif ts_xdiff >= 86400 %}{{ (ts_xdiff % 604800 //
                86400) | int }} d, {{ (ts_xdiff % 86400 // 3600) | int }} h {%
                elif ts_xdiff >= 3600 %}{{ (ts_xdiff % 86400 // 3600) | int }}
                h, {{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 600
                %}{{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 60
                %}{{ (ts_xdiff % 3600 // 60) | int }} m, {{ (ts_xdiff % 60) |
                int }} s {% else %}{{ (ts_xdiff % 60) | int }} s {% endif %}

                [[interval_text]]
      style: |
        {%- set ts_period = [[interval]] %}
        {%- set ts_event = as_timestamp(states.[[entity]].state) %}
        {%- set ts_now = as_timestamp(now())|round(0) %}
        {%- set ts_delta = (ts_now - ts_event) %}
        ha-card {
          border: solid 2px {% if (ts_period == 0) or (ts_delta < ts_period) %}var(--card-background-color) {% else %}red {% endif %};
        }
  next_event:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - almost_due_secs: 2000
      - service: script.script_set_timedate
      - comments: Resets the timer of a Next event to now+interval
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              secondary_info: last-changed
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  timedate: '[[interval]]'
                confirmation:
                  text: Reset Timestamp?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set ts_period = 0 %} {%- set ts_event =
                as_timestamp(states.[[entity]].state) %} {%- set ts_now =
                as_timestamp(now())|round(0) %} {%- set ts_delta = (ts_now -
                ts_event) %} {%- set ts_xdiff = (ts_period - ts_delta)|abs %} {%
                if ts_delta < ts_period %}[[next_text]] {% else
                %}[[overdue_text]] {% endif %}{% if ts_xdiff >= 604800 %}{{
                (ts_xdiff // 604800) | int }} w, {{ (ts_xdiff % 604800 // 86400)
                | int }} d {% elif ts_xdiff >= 86400 %}{{ (ts_xdiff % 604800 //
                86400) | int }} d, {{ (ts_xdiff % 86400 // 3600) | int }} h {%
                elif ts_xdiff >= 3600 %}{{ (ts_xdiff % 86400 // 3600) | int }}
                h, {{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 600
                %}{{ (ts_xdiff % 3600 // 60) | int }} m {% elif ts_xdiff >= 60
                %}{{ (ts_xdiff % 3600 // 60) | int }} m, {{ (ts_xdiff % 60) |
                int }} s {% else %}{{ (ts_xdiff % 60) | int }} s {% endif %}

                [[interval_text]]
      style: |
        {%- set ts_event = as_timestamp(states.[[entity]].state) %}
        {%- set ts_now = as_timestamp(now())|round(0) %}
        {%- set ts_delta = (ts_now - ts_event) %}
        ha-card {
          border: solid 2px {% if (ts_delta >= 0) %}red {% elif ts_delta > (-1)*[[almost_due_secs]] %}yellow {% else %}var(--card-background-color) {% endif %};
        }
  next_mileage:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - interval_text: ''
      - almost_due_units: 500
      - service: script.script_set_next_mileage
      - unit: miles
      - comments: Sets a mileage value to an input mileage
    card:
      type: 'custom:stack-in-card'
      mode: vertical
      keep:
        background: true
      cards:
        - type: entities
          entities:
            - entity: '[[entity]]'
              type: 'custom:multiple-entity-row'
              icon: 'mdi:[[icon]]'
              name: '[[name]]'
              secondary_info: last-changed
              hold_action:
                action: call-service
                service: '[[service]]'
                service_data:
                  entity: '[[entity]]'
                  source_mileage: '[[source_mileage]]'
                  interval: '[[interval]]'
                confirmation:
                  text: Reset Mileage?
            - type: 'custom:hui-markdown-card'
              content: >-
                {%- set mi_source = states.[[source_mileage]].state|int %} {%-
                set mi_target = states.[[entity]].state|int %} {%- set mi_xdiff
                = (mi_target - mi_source)|abs %} {% if mi_source < mi_target
                %}[[next_text]] {% else %}[[overdue_text]] {% endif %} {{
                mi_xdiff }} [[unit]]

                Due every [[interval]] [[unit]]
      style: |
        {%- set mi_source = states.[[source_mileage]].state|int %}
        {%- set mi_target = states.[[entity]].state|int %}
        {%- set mi_delta = (mi_source - mi_target) %}
        ha-card {
          border: solid 2px {% if (mi_delta >= 0) %}red {% elif mi_delta > (-1)*[[almost_due_units]] %}yellow {% else %}var(--card-background-color) {% endif %};
        }

The additional variables that add the almost due yellow border are almost_due_secs and almost_due_units, the time defaults to 2000s in the “next event” template, which is the only time based template using it above.

2 Likes

Any Updates on this project?

When the template button entity is release (hopefully, VERY soon) it will be perfect for the momentary “reset” on date stamps used in these types of automations. I want to build one of these too for changing water filters and cleaning HVAC filters but using a switch makes me feel dirty… :smiley:

I made a simple one like this (I’ll be updating it to button template when it’s released):

input_datetime:
  water_filters_last_replaced:
    name: Water Filters Last Replaced
    has_date: true
    has_time: false
    icon: mdi:water-circle

sensor:
  - platform: template
    sensors:
      water_filters_age:
        friendly_name: Water Filters Age
        value_template: "{{ (( as_timestamp(now()) - (states.input_datetime.water_filters_last_replaced.attributes.timestamp)) | int /60/1440) | round(0) }}"
        icon_template: mdi:clock-start
        unit_of_measurement: 'Days'
        
input_boolean:
  reset_water_filters_last_replaced:
    name: Reset Water Filters Last Replaced
    icon: mdi:water-sync

automation:
  - id: reset_water_filters_last_replaced
    alias: Reset Water Filters Last Replaced
    initial_state: true
    trigger:
      - platform: state
        entity_id: input_boolean.reset_water_filters_last_replaced
        to: "on"
    action:
      - service: input_datetime.set_datetime
        target:
          entity_id: input_datetime.water_filters_last_replaced
        data:
          timestamp: "{{ now().timestamp() }}"
      - service: input_boolean.turn_off
        target:
          entity_id: input_boolean.reset_water_filters_last_replaced

It looks like this:
image

4 Likes

Hi,

At home, our 4 kids have different tasks. The kids arrive on wednesday (2 of them) in the odd weeks and on friday (in the odd weeks).
There are 4 tasks (like empty the dishwasher) etc… and every kid has another task, every week. So actually, every 4 weeks they will do the same tasks.
I already created input_select fields with the name of the task & with 4 options (name of the kids). Now I want to setup a schedule starting today (because it is wednesday today) and repeat the tasks for 1 week every 4 weeks.

So to be clear:
Wednesday to wednesday for kid 1 : Dishwasher
Wednesday to wednesday for kid 2 : clean kitchen

And this needs to happen every 4 weeks (because kid 1 will have again task 1)

Is it possible to arrange that with the scheduler card ?

Outcome : I want, based on the scheduler, create a page with an overview of the 4 kids and picture-entity of the task they need to do

Thanks a lot in advance !!!

Kr,

Bart

I tried to implement your code, but I keep getting the error “custom element doesn’t exists: hui-markdown-card”.
So I tried to replace it with type: markdown, but same type of error. Which is strange, since it is documented here: Markdown Card - Home Assistant

Do I miss anything?

You need to install HACS and then use that to install the custom card. Do a Google search for “home assistant HACS” and you should be able to figure it out.

Alright, thanks! I did install all the other custom cards and read about the hui stuff is superseded. So I tried to do it with the new, integrated solution “markdown” but as stated above there is an error. What I did then is just append that card into the custom “stack-in-card” instead of having it be part of the “entities”-card, which threw the error. With that change I got it running, however it doesn’t look that pretty (although it is close to the pictures). I’ll tinker on it and if I get a more streamline card, share it here for everyone else to try :slight_smile:

edit here we go. Result:
image
template (for the custom declutter card):

rec_event:
    default:
      - icon: calendar-clock
      - next_text: Next due in
      - overdue_text: Overdue by
      - almost_due_offset: 1
      - unit_sec: 86400 #=1d 
      - interval_text: ''
      - service: script.script_set_timedate
      - comments: Resets the timer of last event to now
    card:
      type: custom:mushroom-template-card
      entity: '[[entity]]'
      icon: mdi:[[icon]]
      icon_color: |-
        {%- set ts_period = [[interval]]*[[almost_due_offset]]*[[unit_sec]] %}
        {%- set ts_event = as_timestamp(states.[[entity]].state) %}
        {%- set ts_now = as_timestamp(now())|round(0) %}
        {%- set ts_delta = (ts_now - ts_event) %}
        {% if (ts_period != 0) and (ts_delta > ts_period) %}red {% elif (ts_period-ts_delta) < [[almost_due_offset]]*[[unit_sec]] %}orange {% endif %}
      primary: '[[name]]'
      layout: horizontal
      tap_action:
        action: call-service
        service: '[[service]]'
        service_data:
          entity: '[[entity]]'
          timedate: 0
        confirmation:
          text: Reset the timer of last event to now?
      secondary: >-
        {%- set ts_period = [[interval]]*[[unit_sec]] %} {%- set ts_event =
        as_timestamp(states.[[entity]].state) %} {%- set ts_now =
        as_timestamp(now())|round(0) %} {%- set ts_delta = (ts_now - ts_event)
        %} {%- set ts_xdiff = (ts_period - ts_delta)|abs %} {% if ts_delta <
        ts_period %}[[next_text]] {% else %}[[overdue_text]] {% endif %}{% if
        ts_xdiff >= 604800 %}{{ (ts_xdiff // 604800) | int }}w, {{ (ts_xdiff %
        604800 // 86400) | int }}d {% elif ts_xdiff >= 86400 %}{{ (ts_xdiff %
        604800 // 86400) | int }}d {% elif ts_xdiff >= 3600 %}{{ (ts_xdiff %
        86400 // 3600) | int }}h {% elif ts_xdiff >= 600 %}{{ (ts_xdiff % 3600
        // 60) | int }}m {% elif ts_xdiff >= 60 %}{{ (ts_xdiff % 3600 // 60) |
        int }}m, {{ (ts_xdiff % 60) | int }}s {% else %}{{ (ts_xdiff % 60) | int
        }}s {% endif %} [[interval_text]].
      style: |-
        {%- set ts_period = [[interval]]*86400 %}
        {%- set ts_event = as_timestamp(states.[[entity]].state) %}
        {%- set ts_now = as_timestamp(now())|round(0) %}
        {%- set ts_delta = (ts_now - ts_event) %}
        ha-card {
          border: solid 2px {% if (ts_period != 0) and (ts_delta > ts_period) %}red {% elif (ts_period-ts_delta) < [[almost_due_offset]]*[[unit_sec]] %}orange {% elif (ts_period == 0) or (ts_delta < ts_period) %}var(--card-background-color) {% else %}red {% endif %};
        }

the actual card-code:

      - type: custom:decluttering-card
        template: rec_event
        variables:
          - entity: input_datetime.recurring_last_vacuum
          - name: Snoopy
          - icon: robot-vacuum
          - interval: 7
          - interval_text: (due every week)

I have to say though, that with this template, I’m missing the actual date to be displayed on the right. Maybe I’ll add it to the title or somewhere else.

2 Likes

To be honest, I don’t know how you would be able to do this in a dynamic way in an easy way. But what could be a shortcut for your application, is doing some images with all possible scenarios and let them rotate?

Card looks great and Ive stolen your code and template. however would you mind sharing the script to reset the date?

Has anybody managed to create a bar/gauge based on the next due date?

@weemaba999 any progress?
Otherwise you can try to (ab)use the area section of a task to assign it to a person (in stead of a location).

I created this article about how I handle my recurring tasks Home Assistant dashboard: Chores | vd Brink Home Automations
This is only without dynamic persons assigning but if you can manage to script it to assign the the task to the right person/area, the auto-entities card can probably also filter on area. Then you can create a list for each person.

1 Like

Sorry for the late reply. Not looking in here on a regular basis.
So here is my script_set_timedate script for the cards:

alias: Set Timedate
sequence:
  - service: input_datetime.set_datetime
    data:
      timestamp: >-
        {{ now().timestamp() + (timedate | default(0)) * (unit | default(86400))
        }}
      entity_id: "{{ entity }}"
mode: single

Screenshot (7)
Not sure where I went wrong but this doesn’t look right to me. Any idea what I need to fix?