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:


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]]