Track repetitive tasks with NFC tags

An automation blueprint to track the completion status of a repetitive task.

The task can be marked “complete” by scanning an NFC tag. If a task is due (or overdue), a custom action is triggered.

This is inspired by @Futuresmarthome 's concept: https://www.youtube.com/watch?v=DhU2Jw2-op8

NOTE: This automation only checks task completion status once a day, and therefore is not useful for tasks that need to be completed multiple times in a 24-hour period.

Blueprint

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

blueprint:
  name: Track and Remind about a Task
  source_url: https://raw.githubusercontent.com/nwithan8/configs/main/home_assistant/blueprints/automations/track_and_remind_task.yaml
  description: >
    Track the completion of a repetitive task, and trigger actions when the task is due, overdue or completed.


    Based on Future Smart Home's concept: https://www.youtube.com/watch?v=DhU2Jw2-op8


    NOTE: This automation only checks task completion status once a day, and therefore
    is not useful for tasks that need to be completed multiple times in a 24-hour
    period.


    PRE-REQUISITES:

      - Create an input_datetime helper to track the last time the task was completed.
      - Create an NFC tag that will be used to mark the task as completed.
  domain: automation
  input:
    task_name:
      name: Task name
      description: >
        The name of the task to track.
      selector:
        text:
          type: text
          multiline: false
          multiple: false
    task_interval:
      name: Task interval
      description: >
        How often the task needs to be completed, in days.
      selector:
        number:
          min: 1.0
          max: 365.0
          step: 1.0
          mode: box
    nfc_tag:
      name: NFC tag
      description: >
        The ID of the NFC tag that marks the task as "completed".

        Can be found under Settings -> Tags, and then click on the relevant tag.
      selector:
        text:
          type: text
          multiline: false
          multiple: false
    date_tracker:
      name: Date tracker
      description: >
        The datetime helper that will track the task completion.
      selector:
        entity:
          filter:
            - domain:
                - input_datetime
          multiple: false
    status_check_time:
      name: Status check time
      description: >
        What time, every day, this automation will check for incomplete tasks and send notifications.
      selector:
        time: {}
    due_action:
      name: Due reminder action
      description: >
        What action to take when a reminder needs to be sent about a task due today.
        For example, send a notification to a mobile device, turn on a light, change a binary sensor, etc.


        The following variables are available to the action:

          - "task_name": The name of the task (string)
          - "task_due_date": The date the task was originally due (would be today) (string)
          - "last_completed_date": The date the task was last completed (string)
          - "days_since_last_completed": How many days since the task was last completed (integer)
          - "days_until_next_due": How many days until the task is due (would be 0) (integer)
          - "task_interval": How often the task is due (integer)
          - "task_is_due": Whether the task is due today (boolean)
          - "task_is_overdue": Whether the task is overdue (boolean)
      selector:
        action: {}
      default: []
    overdue_action:
      name: Overdue reminder action
      description: >
        What action to take when a reminder needs to be send about an overdue task.
        For example, send a notification to a mobile device, turn on a light, change a binary sensor, etc.


        If not set, overdue tasks will use the same action as above. 


        The following variables are available to the action:

          - "task_name": The name of the task (string)
          - "task_due_date": The date the task was originally due (would be in the past) (string)
          - "last_completed_date": The date the task was last completed (string)
          - "days_since_last_completed": How many days since the task was last completed (integer)
          - "days_until_next_due": How many days until the task is due (would be < 0) (integer)
          - "task_interval": How often the task is due (integer)
          - "task_is_due": Whether the task is due today (boolean) (always false)
          - "task_is_overdue": Whether the task is overdue (boolean) (always true)
      selector:
        action: {}
      default: []
    skip_days_of_week:
      name: Days of the week to skip
      description: >
        Which days of the week to skip task completion status checks.


        This has no impact on the completion or due/overdue status of the task; it simply skips checking the status on the specified days.
      selector:
        select:
          multiple: true
          options:
            - label: Monday
              value: "0"
            - label: Tuesday
              value: "1"
            - label: Wednesday
              value: "2"
            - label: Thursday
              value: "3"
            - label: Friday
              value: "4"
            - label: Saturday
              value: "5"
            - label: Sunday
              value: "6"
          sort: false
          custom_value: false
      default: []
    rolling_completion:
      name: Use rolling completion
      description: >
        If enabled, when marking a task complete, mark it completed at
        original due date rather than current date.
        For example, assume you have a task that needs to be completed every 7 days.
        After 10 days, you mark the task as completed.


        If enabled, the "completed date" will be the original due date, three days
        ago, which means it will be due again 4 days from now.


        If not enabled, the "completed date" will be today, which means it will be
        due again 7 days from now.


        This can be useful if you don't want your delay in completing the task to
        affect the next date the task is due.
      selector:
        boolean: {}
      default: false
    on_completion_action:
      name: Action upon completion
      description: >
        An optional additional action to take when the task has been completed.
        For example, send a notification to a mobile device, turn on a light, change
        a binary sensor, etc.
      default: []
      selector:
        action: {}
mode: single
max_exceeded: silent
variables:
  task_name: !input task_name
  task_interval: !input task_interval
  date_tracker: !input date_tracker
  rolling_completion: !input rolling_completion
  overdue_action: !input overdue_action
  skip_days_of_week: !input skip_days_of_week
  now_date_timestamp: >
    {{ (now().date() | as_timestamp) | int }}
  last_completed_date: >
    {{ states(date_tracker) }}
  last_completed_date_timestamp: >
    {{ state_attr(date_tracker, 'timestamp') | int }}
  next_due_date_timestamp: >
    {{ (last_completed_date_timestamp + (task_interval * 24 * 60 * 60)) }}
  next_due_date: >
    {{ next_due_date_timestamp | as_datetime }}
  task_due_date: >
    {{ next_due_date | string }}
  seconds_since_last_completed: >
    {{ now_date_timestamp - last_completed_date_timestamp }}
  days_since_last_completed: >
    {{ (seconds_since_last_completed / 24 / 60 / 60) | int }}
  seconds_until_next_due: >
    {{ next_due_date_timestamp - now_date_timestamp }}
  days_until_next_due: >
    {{ (seconds_until_next_due / 24 / 60 / 60) | int }}
  task_is_due: >
    {{ days_until_next_due == 0 }}
  task_is_overdue: >
    {{ days_until_next_due < 0 }}
trigger:
  - alias: On schedule
    id: schedule
    platform: time
    at: !input status_check_time
  - alias: When associated NFC tag is scanned
    id: tag_scan
    platform: tag
    tag_id: !input nfc_tag
action:
  - choose:
      - conditions:
          - alias: NFC tag scanned
            condition: trigger
            id: tag_scan
        sequence:
          - alias: Update date tracker to completion date
            service: input_datetime.set_datetime
            entity_id: !input date_tracker
            data:
              datetime: >
                {% if rolling_completion %}
                  {{ next_due_date_timestamp | as_datetime }}
                {% else %}
                  {{ now().date() | as_datetime }}
                {% endif %}
          - alias: Execute "on completion" action if set
            choose:
              - conditions: >
                  {{ true }}
                sequence: !input on_completion_action
      - conditions:
          - alias: At scheduled time
            condition: trigger
            id: schedule
          - alias: Today is not a skip day
            condition: template
            value_template: >
              {{ now().date().weekday() | string not in skip_days_of_week}}
        sequence:
          - choose:
              - conditions:
                  - alias: Task is due today
                    condition: template
                    value_template: >
                      {{ task_is_due }}
                sequence: !input due_action
              - conditions:
                  - alias: Task is overdue
                    condition: template
                    value_template: >
                      {{ task_is_overdue }}
                sequence:
                  - if:
                      - alias: Custom overdue action
                        condition: template
                        value_template: >
                          {{ overdue_action != [] }}
                    then:
                      - alias: Execute overdue action
                        choose:
                          - conditions: >
                              {{ true }}
                            sequence: !input overdue_action
                    else:
                      - alias: Execute due action
                        choose:
                          - conditions: >
                              {{ true }}
                            sequence: !input due_action

Install

  1. Click the “Import Blueprint” button above and follow the on-screen instructions to import the blueprint into your Home Assistant.
  2. Create the required entities:
    • Create a new “Datetime” helper to store the last time the specific task was marked “completed”
    • Create/register a new NFC tag that, when scanned, will mark the task as “completed”.
  3. Create a new automation using the blueprint (Settings → Automations & Scenes → “Create Automation” → Select “Track and Remind about a Task” from the list)
    1. Task name: The name of the task. This will be available in the due/overdue/completed actions as {{ task_name }}
    2. Task interval: How often the task needs to be completed, in days (min: 1, max: 365)
    3. NFC tag: The ID of the NFC tag (created earlier) that, when scanned, will mark the task “completed”
    4. Date tracker: The Datetime helper (created earlier) that will store a timestamp of the last time the task was marked “completed”
    5. Status check time: The time, every day, when this automation will run to check for due/overdue tasks and potentially trigger reminder actions.
    6. Due reminder action: An action (or series of actions) to take when a task is due today.
    7. Overdue reminder action: An action (or series of actions) to take when a task is overdue. If not set, will automatically use the same action(s) as Due reminder action.
    8. Days of the week to skip: Optional days of the week to skip running the status check. For example, skip checking for due/overdue tasks on weekends.
    9. Use rolling completion: When marking a task as “completed”, whether to mark it at its original due date or today. Useful if you don’t want the delay in completing the task to affect the next time the task is due.
    10. Action upon completion: Optional action(s) to take when a task is marked as “completed”.


Usage

The automation will execute every day at the set Status check time.

  • If the current day is one of the Days of the week to skip, no further action will happen.

If the Task interval has lapsed, either the Due reminder action or Overdue reminder action will trigger, depending on if the task is due today or before today.

You can mark a task as “completed” by scanning the NFC tag. Any configured Action upon completion will trigger.

Release History:

1.2.0 (September 3, 2024):

  • Fix datetime vs string parsing issues causing automation runtime issues

1.1.0 (August 26, 2024):

  • Fix logic of what gets stored in date tracker (previous completed date, not next due date)
  • Add new variables available of use in on-due and on-overdue actions.

1.0.0 (January 24, 2024):

  • Initial release
9 Likes

This is really cool, thanks for putting it together.

I’m experiencing difficulty with the {{ }} variables. I’ve filled in the Task Name field and then set the message as is written in a screenshot "{{ task_name }} is due" but when I run that action to test it, the message received by my phone is is due.

Basically the variable isn’t getting parsed/passed to the message. Am I doing something wrong?

Hmm, could you share a screenshot or the YAML of your automation config? And/or the trace of when it ran, specifically the “Changed Variables”? You should see a “task_name” variables, like this:

This is an awesome blueprint! I’ve entered in all of the required information and saved it. Now, how do I display it properly on my dashboard? I can’t seem to find a way to display it with the full task name and a descending numeric value. It’s just showing a date entry. Many thanks for everything!

Here’s my YAML (please let me know if everything looks correct or if I missed something):

alias: Task Reminder - HVAC filter change
description: >-
  Reminder every 60 days to change HVAC filters. Once NFC tag has been scanned,
  the reminder is reset.
use_blueprint:
  path: nwithan8/track_and_remind_task.yaml
  input:
    task_name: HVAC filter change
    task_interval: 60
    nfc_tag: 823c6552-e013-4483-a590-fb81af51aee8
    date_tracker: input_datetime.hvac_filter_change_date
    status_check_time: "00:09:00"
    due_action:
      - service: notify.mobile_app_pixel_7
        data:
          title: Task Reminder
          message: "{{ task_name }} is due!"
    rolling_completion: true
    overdue_action:
      - service: notify.mobile_app_pixel_7
        data:
          title: Task Overdue!
          message: "{{ task_name }} is overdue!"
    on_completion_action:
      - service: notify.mobile_app_pixel_7
        data:
          title: Task Completed!
          message: "{{ task_name }} was completed!"

Hey thanks for the quick reply!

YAML

alias: Doggie flea ointment tracker
description: ""
use_blueprint:
  path: nwithan8/track_and_remind_task.yaml
  input:
    task_name: Doggie Flea Ointment
    task_interval: 31
    nfc_tag: 2587fdf4-fff9-490b-a04d-99c5703839f8
    date_tracker: input_datetime.doggie_flea_tracker
    status_check_time: "20:00:00"
    due_action:
      - service: notify.mobile_app_me
        data:
          message: "{{ task_name }} is due today"
      - service: notify.mobile_app_iphone
        data:
          message: "{{ task_name }} is due today"
      - service: notify.mobile_app_me_galaxy_watch4
        data:
          message: Doggie flea ointment is due today
    overdue_action:
      - service: notify.mobile_app_me_s23
        data:
          message: Doggie flea ointment was due yesterday!
      - service: notify.mobile_app_iphone
        data:
          message: Doggie flea ointment was due yesterday!
      - service: notify.mobile_app_me_galaxy_watch4
        data:
          message: Doggie flea ointment was due yesterday!

Changed Variables:

this:
  entity_id: automation.doggie_flea_ointment_tracker
  state: 'on'
  attributes:
    id: '1706452946901'
    last_triggered: '2024-01-30T11:00:00.273633+00:00'
    mode: single
    current: 0
    friendly_name: Doggie flea ointment tracker
  last_changed: '2024-01-30T09:28:30.465549+00:00'
  last_updated: '2024-01-30T11:00:00.283882+00:00'
  context:
    id: 01HND0D84E70FN38XWY8YXG0H9
    parent_id: null
    user_id: null
trigger:
  id: schedule
  idx: '0'
  alias: On schedule
  platform: time
  now: '2024-01-31T20:00:00.269788+09:00'
  description: time
  entity_id: null
task_name: Doggie Flea Ointment
task_interval: 31
date_tracker: input_datetime.doggie_flea_tracker
rolling_completion: false
overdue_action:
  - service: notify.mobile_app_me_s23
    data:
      message: Doggie flea ointment was due yesterday!
  - service: notify.mobile_app_iphone
    data:
      message: Doggie flea ointment was due yesterday!
  - service: notify.mobile_app_me_galaxy_watch4
    data:
      message: Doggie flea ointment was due yesterday!
skip_days_of_week: []
days_since_last_completed: -28
task_due_date: '2024-03-30'
task_is_due: false
task_is_overdue: false

Task name variable seems to be right.

Hmm, that’s weird. I’m not able to recreate that on my end. As you can see, it populated task_name (“Test ting”) correctly for me:

This blueprint doesn’t really have a visual component to it. The task name only exists in the automation; it’s not an entity that can be visually displayed or is recorded anywhere.

This blueprint only relies on a “last completed” timestamp, so that’s the only entity you’d have to put on a dashboard, as you’ve discovered.

Nate, this is an awesome blueprint. Thank you!

1 Like

Thank you Nate! I wanted to do exactly what you made. You saved me a lot of time.

1 Like

Question: Does the “datetime” entity value persist during resets? I would love to use this to remind/track a medication administration for my dog, given every 28 days. It’s important that the countdown doesn’t get reset by changes to my config or an update/crash

It should, it’s an entity value that is written to HA’s database like any other value.

Thanks for the automation. I’m going to find it very useful!

I’m having an issue with the ‘days_since_last_completed’ vs what gets stored in the tag helper as the ‘datetime’ data. When I scan the tag, I get the ‘current date + the offset’ stored in the tag helper. This means the tag/helper now contains the date when the next event occurs.

When calculating the ‘days_since_last_completed’, it looks like the blueprint takes the date from the tag/helper and subtracts the offset, which now gives me a negative number for ‘days_since_last_completed’. It looks to me like either the current date (if I don’t care about the rolling completion), or the days since last completion should ignore the offset and just compare the date to the stored tag/helper date.

Am I reading this wrong? Am I doing something wrong?

  days_since_last_completed: '{% set last_date = states(date_tracker) | as_datetime
    %} {{ (now().date() - last_date.date()).days }}

.
.
.

    datetime: "{% if rolling_completion %}\n  {% set last_date = states(date_tracker)
          | as_datetime %}\n  {{ last_date.date() + timedelta(days=task_interval)
          }}\n{% else %}\n  {{ now().date() + timedelta(days=task_interval) }}\n{%
          endif %}\n"

The days_since_last_completed compares how many days have passed since today and the last time the task was completed (the date stored in the helper). It does not use the offset in this calculation.

I’m seeing the date stored in the tag helper get updated to be the date due, not the date last completed. This is why it’s not working for me. I’m guessing I can make it work if I change the date stored in the tag helper

I’m unclear why but this seems to have stopped working entirely. My task was due 103 days ago and is set to 45 days however the task never triggered and is still not showing as overdue.

Could well be related to the the issues others are having

I really like this idea. I had some tags laying around so created some automations and waited. But nothing happened!?

Is it a bug or did I do something wrong creating the automation?

Hey since you are looking like a guru of NFC stuff… Off topic a bit.
How can I get my automation to tell the user that the nfc has already scanned, i.e. notify phone that it has already been scanned when trying to do it again?. It’s for dbl checking meds.
Thanks

1 Like

Hm, it is working apparently because I just got a notification like I configured.

1 Like

To soon. Somehow the dates are not calculated correctly.

So when I expect a trigger it doesn’t, from the logging I see it needs to be the interval after the date in the helper before it triggers again.

But scanning the tag will set the helper to today+interval. So it is always interval * 2?

I think I have found the bug, will do some more testing to be sure.

I changed

days_since_last_completed: '{% set last_date = states(date_tracker) | as_datetime %} {{ (now().date() - last_date.date()).days }}'

to

days_since_last_completed: '{% set last_date = states(date_tracker) | as_datetime %} {{ (now().date() + timedelta(days=task_interval) - last_date.date()).days }}'
1 Like