Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.
A furnace filter, a water filter, a battery in a remote: the work is rare, and a calendar reminder drifts the moment you do it on the wrong day. This pack stores the last completion in an input_datetime helper. Scanning an NFC tag stuck next to the chore writes “done” into that helper. Once a day, at a time you pick, Home Assistant compares that date to the interval. Due today and overdue take different notify actions. It does not check again until tomorrow, so it is the wrong tool for a chore you do twice in one day.
That is a different loop from bin-day reminder, which nags the evening before a pickup until you confirm, and from nagging repeatable alert, which repeats while a sensor stays in alarm. Here the only “done” signal is the tag.
One automation per chore. Entity ids are blueprint inputs (input_datetime.filter_last_done). The tag id comes from Settings → Tags after you write the tag with Companion. Do not publish a tag id.
What problem it solves
You want a phone reminder when a repeating task is actually due, and you want the clock to restart when you do the task, without opening a dashboard with wet hands.
How it works
- Last done — an
input_datetimeholds the completion timestamp. The next due date is that timestamp plus the interval in days (1 to 365). Days since and days until are derived from those two dates, using calendar dates, not “24 hours from the minute you scanned.” - Scan resets it — a
tagtrigger with that tag id callsinput_datetime.set_datetime. The default writes today. Optional rolling completion writes the previous due date instead, so doing the chore three days late does not push every future due date three days later. A 7-day chore done on day 10 is due again in 4 days with rolling on, and in 7 days with rolling off. - Daily check — a time trigger at the status-check time (pack default 09:00). If today is a skip weekday, stop. If days until due is 0, run the due action. If it is negative, run the overdue action, or the due action when you left overdue empty.
- Message variables — the notify action can use the task name, the last completed date, the due date, and the day counts. Test the action from the automation trace. A template that only works in Developer Tools is not the same renderer the action sees, so confirm
task_nameshows up in the phone notification, not as an empty string. - One check a day — missing the scan does not page you every hour. Tomorrow’s check sends overdue again until you scan. An optional completion action can send a single “done” note so you know the tag registered.
The helper survives a restart. The tag does not need to be written with a URL. Companion only needs the tag registered in Home Assistant so the scan event carries that id.
Hardware notes
- Tags. NTAG215 stickers are what Companion and the Tags UI expect for a reliable phone scan. One tag per chore, labeled in the real world. Adhesive NTAG215 stickers do not read through metal. Stick the filter tag on the plastic frame or the door of the cabinet, not on the metal air handler.
- Phone. NFC has to be on. Companion has to be allowed to handle tags. A phone that only reads tags into a different app will never fire the trigger.
- No extra radio. The tag is passive. Home Assistant sees the scan because the phone reports it. There is no ESP32 in this pack.
Snippet
The pack ships a blueprint. Inputs replace the real helper and the real tag id.
mode: single
max_exceeded: silent
trigger:
- platform: time
at: !input status_check_time
id: daily
- platform: tag
tag_id: !input nfc_tag_id
id: scan
action:
- choose:
- conditions:
- condition: trigger
id: scan
sequence:
- action: input_datetime.set_datetime
target:
entity_id: !input last_done
data:
datetime: "{{ now().strftime('%Y-%m-%d 00:00:00') }}"
- conditions:
- condition: trigger
id: daily
- condition: template
value_template: >-
{% set last = state_attr('input_datetime.filter_last_done', 'timestamp') | int(0) %}
{% set due = last + (interval_days | int * 86400) %}
{{ (due | as_datetime | as_local).date() <= now().date() }}
sequence:
- action: notify.send_message
target:
entity_id: !input notify_target
data:
message: "{{ task_name }} is due"
last_done, interval_days, and task_name are blueprint inputs (input_datetime.filter_last_done, a number of days, and a short label such as “furnace filter”). Rolling completion replaces now() with the previous due date. The overdue branch, skip weekdays, and the completion action are in the full blueprint.
Setup outline
- Create an
input_datetimefor the chore. Set it to the last time you actually did the work, or to today if you are starting fresh. Register an NFC tag in Companion and copy its id from Settings → Tags. - Import the blueprint. Fill the task name, the interval, the tag id, the helper, the daily check time, and a notify target. Leave rolling completion off until you have lived with a late scan.
- Run the daily trigger from the trace with the helper set far enough in the past that the chore is overdue. Confirm one phone notification and that the name is not blank.
- Scan the tag. The helper’s date becomes today (or the old due date, if rolling is on). Run the daily trigger again. It should not notify.
- Stick the tag where you will actually scan it while doing the chore.
Troubleshooting
- Scan does nothing. The tag id in the blueprint is not the id Settings → Tags shows, NFC is off, or another app consumed the scan. Watch the automation trace while you scan. A trace that never starts is a phone problem, not a template problem.
- Notification says the task name is empty. The action template cannot see blueprint variables unless they are exposed as automation variables. The pack puts
task_nameinvariables:. A notify body typed only in the UI action editor still needs{{ task_name }}, and you have to run the real trigger to see it. The action test button does not always load those variables. - Due a day early or late. The helper has a time component and the comparison used hours instead of dates. The pack compares calendar dates. A scan at 23:00 should still count as that date.
- Late scan pushed the next due date out. Rolling completion is off, which is the default: the interval restarts today. Turn it on if the chore has to stay on a fixed cadence.
- Pages every day after it was due. That is the overdue path. Scan the tag. Skipping Saturday in the weekday list only skips the check. It does not clear the debt. Monday will still see an overdue chore.
- Tag will not read on the machine. The sticker is on metal. Move it to plastic or wood a few centimeters away. NTAG215 and a phone antenna do not punch through a steel panel.
Credits
Based on the community blueprint Track repetitive tasks with NFC tags. This write-up is an original overview of a tag scan that resets an input_datetime, and a once-a-day check that notifies when the interval has elapsed.