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

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?

In my automation for some reason it’s give me an error:

alias: "Notify: HVAC Filter"
description: ""
trigger:
  - platform: state
    entity_id: sensor.hvac_filter_days_left
    to: "0"
    id: Days Left HVAC
condition: []
action:
  - service: notify.mobile_app_sm_s918u
    data:
      message: Time to Replace an HVAC Filter
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.hvac_filter_clean
    data: {}
mode: single
    - name: HVAC Filter Days Left
      unique_id: 1ef0d629-911a-4a18-96a1-f0503e77b119
      #friendly_name: 'HVAC Filter Days Left'
      state: >
        {% 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: mdi:clock-end
      unit_of_measurement: 'Days'