-
Notifications
You must be signed in to change notification settings - Fork 59
Super Simple Chore Tracker for Home Assistant
Under the Maintenance tab, I kept a list of chores, as shown in the middle column in this screenshot:
I made it the simplest way I could with what I knew back then since I didn't know how to script (and still doesn't).
This is designed for recurring chores with soft deadlines, as in "X amount of days since it was last done". Like watering plants, I can't do it on a strict daily or weekly schedule - I will end up overwatering it. The best I can do is to monitor the condition and do it as needed, but I should be notified if it has been a while since it was last done.
Here's the thing - a little bit on the natures of chores. Every household has their own 'laws' on when chores should be done, and there isn't a one-size-fits-all chore solution as far as I can find:
- Recurring chores with hard deadlines (e.g. "Get the exterminator every July 1st"), which is better handled by a calendar;
- One time tasks (e.g. "Fix overflowing basement toilet") which is better handled by a To Do list app;
- Habits which requires a task done within a hard regular interval (e.g. "Weight training every week") which is better handled by habit apps; or
- Collaborative chores with multiple people involved (e.g. "Refurnish the entire kitchen together") which in my opinion a Trello board is better suited.
I also don't care who did a chore, so all I needed to know is whether I haven't done a chore for too long. There are no assignees function in this tracker.
Basically, all the logic is handled by the front end. The button-card
s do the date calculations of whether a task is due or not, by comparing the last changed date of an input button with the time interval set by the corresponding input text.
Each chore has 4 states: Overdue, To Do Soon, Normal, and Done. When a chore is selected, it will reset the last done time to the current moment, and clear any Overdue or To Do Soon status and replace it with Done until the end of the day.
Change the amount of days to count since the chore is last done by long pressing a chore card to bring up the more-info card. Put any numbers for the amount of days, and put "-1" if the chore is only done as needed.
Difficulty Level: Intermediate. Some basic coding skills required.
Since this involves coding, use a code editor such as the Studio Code Server add-on for Home Assistant.
First, install button-card
to your frontend: https://github.com/custom-cards/button-card
Create the list of chores in a chores.yaml
and save it in the same folder as your configurations.yaml
:
chore_clean_kitchen_sink: # A unique ID for the chore
name: Clean Kitchen Sink # The name of the chore to be shown on the Chore List
icon: mdi:water-pump # The icon of the chore to be shown on the Chore List
chore_clean_kitchen_counter:
name: Clean Kitchen Counter
icon: mdi:countertop
chore_clean_kitchen_stove:
name: Clean Kitchen Stove
icon: mdi:stove
In configuration.yaml
, include the chores.yaml
file in configuration.yaml
by adding these two lines:
input_button: !include config/chores.yaml
input_text: !include config/chores.yaml
Then create an automation to put all the chores automatically in a group every time Home Assistant starts:
- alias: Home Assistant starts ➔ Update Group - Chores
trigger:
- platform: homeassistant
event: start
- platform: event
event_type: call_service
event_data:
domain: group
service: reload
action:
- service: group.set
data_template:
name: All Chores
object_id: chores
entities: |
{% set ns = namespace(entities=[]) %}
{% for s in states.input_button if s.object_id.startswith('chore_') %}
{% set ns.entities = ns.entities + [ s.entity_id ] %}
{% endfor %}
{{ ns.entities }}
Validate your configuration in HA Developer Tools, and then reload the YAML configuration for "Input Buttons", "Input Texts", and then once those two are reloaded, reload "Groups, Group Entities, and Notify Services".
Double check that the chores are loaded in the States tab of Developer Tools by searching for the entities such as group.chores
. There should be a list of your chores in the attributes of the entity.
The last steps involve integrating the chores to the front end.
Add a template for a chore item for button-card
. Put the following code under button_card_templates:
in your dashboard YAML. Check the button-card
ReadMe for more info.
chore_card:
# Set up variables for calculating chore due dates.
variables:
now: >-
[[[ return new Date() ]]]
last_done: >-
[[[ return new Date(entity.state) ]]]
diff: >-
[[[ return Math.round((new Date() - new Date(entity.state)) / 1000 / 60 / 60 / 24 ) ]]]
due: >-
[[[ return states[entity.entity_id.replace("input_button", "input_text")].state ]]]
triggers_update: all
show_label: true
layout: icon_name_state2nd
size: 24px
# Show when a chore is due and when it was last done.
label: |
[[[
var doneStr
if (variables.diff < 2) {
if (variables.last_done.getDay() == variables.now.getDay()) { doneStr = 'today' } else { doneStr = 'yesterday' }
} else if (isNaN(variables.diff)) {
doneStr = 'unknown'
} else {
doneStr = variables.diff + ' days ago'
}
if (variables.due > 0) {
return 'Every ' + variables.due + ' days • Last done ' + doneStr
} else { return 'As needed • Last done ' + doneStr }
]]]
# Show the chore status as a badge.
custom_fields:
badge: &field-chore-badge |
[[[
if ((variables.diff < 2) && (variables.last_done.getDay() == variables.now.getDay())) { return 'Done' }
if (variables.due > 0) {
if (variables.due < variables.diff) { return 'Overdue' }
if (variables.due < variables.diff + (variables.due / 4)) { return 'To do soon' }
}
]]]
styles:
card:
- margin: 4px 0
- padding: 4px 12px
# Show a subtle background color depending on the chore status.
- background-color: |
[[[
if ((variables.diff < 2) && (variables.last_done.getDay() == variables.now.getDay())) { return 'rgba(var(--rgb-success-color), .33)' }
if ((variables.due > 0) && (variables.due < variables.diff)) { return 'rgba(var(--rgb-error-color), .33)' } else {return 'transparent'}
]]]
grid:
- grid-template-columns: min-content 1fr min-content
- grid-template-areas: '"i n badge" "i s badge" "i l badge"'
img_cell:
- align-self: middle
- text-align: start
- padding: 8px 24px 8px 4px
# Colorize the icon depending on the chore status.
icon:
- color: |
[[[
if ((variables.diff < 2) && (variables.last_done.getDay() == variables.now.getDay())) { return 'var(--success-color)' }
if (variables.due > 0) {
if (variables.due < variables.diff) { return 'var(--error-color)' }
if (variables.due < variables.diff + (variables.due / 4)) { return 'var(--warning-color)' }
} else { return 'var(--primary-text-color)' }
]]]
name:
- align-self: middle
- justify-self: start
- font-size: var(--body-font-size)
label:
- align-self: middle
- justify-self: start
- font-size: var(--body-font-size)
- opacity: 0.66
# Colorize the badge depending on the chore status.
custom_fields:
badge:
- background: |
[[[
if ((variables.diff < 2) && (variables.last_done.getDay() == variables.now.getDay())) { return 'var(--success-color)' }
if (variables.due < variables.diff) { return 'var(--error-color)' } else { return 'var(--warning-color)' }
]]]
- padding: 2px
- line-height: 1
- font-size: var(--h6-font-size)
- font-weight: 900
- text-transform: uppercase
- border-radius: 2px
- color: var(--accent-text-color)
# Short press a chore card to set it as Done.
tap_action:
action: call-service
service: input_button.press
service_data:
entity_id: entity
# Long press a chore card to change the amount of days a chore is due at.
hold_action:
action: more-info
entity: >-
[[[ return entity.entity_id.replace("input_button", "input_text") ]]]
Finally, add the list of chores to your dashboard. The auto-entities
frontend module will make things easier if you have a lot of chores, but is optional.
- type: 'custom:auto-entities'
filter:
include:
- group: group.chores
options:
type: 'custom:button-card'
template: chore_card
card:
type: entities
title: Chores
Refresh your dashboard, and your chore list is complete.
Create template sensors to count the number of chores to be done:
template:
- trigger:
- platform: homeassistant
event: start
- platform: event
event_type: event_template_reloaded
- platform: time_pattern
minutes: "/5"
- platform: state
entity_id:
- group.chores
sensor:
- name: Chores Done
unit_of_measurement: "Tasks"
state: "OK"
attributes:
entities: >
{%- set chore_done = namespace(entities=[]) -%}
{%- for chore in expand('group.chores') -%}
{%- if (chore.state != 'unknown') -%}
{%- set input = chore.entity_id|regex_replace(find='input_button', replace='input_text') -%}
{%- set diff = ( as_timestamp(now()) - as_timestamp(chore.state) ) / 60 / 60 / 24 %}
{%- set due = states(input)|float(-1) -%}
{%- if (diff < 1) and (as_datetime(chore.state).day == now().day) -%}
{%- set chore_done.entities = chore_done.entities + [chore.entity_id] -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{{ chore_done.entities }}
- name: Chores To Do
state: "OK"
attributes:
entities: >
{%- set chore_todo = namespace(entities=[]) -%}
{%- for chore in expand('group.chores') -%}
{%- if (chore.state != 'unknown') -%}
{%- set input = chore.entity_id|regex_replace(find='input_button', replace='input_text') -%}
{%- set diff = ( as_timestamp(now()) - as_timestamp(chore.state) ) / 60 / 60 / 24 %}
{%- set due = states(input)|float(-1) -%}
{%- if due > 0 -%}
{%- if (due < diff) -%}
{%- else -%}
{%- if (due < diff + (due / 4)) -%}{%- set chore_todo.entities = chore_todo.entities + [chore.entity_id] -%}{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{{ chore_todo.entities }}
- name: Chores Overdue
state: "OK"
attributes:
entities: >
{%- set chore_overdue = namespace(entities=[]) -%}
{%- for chore in expand('group.chores') -%}
{%- if (chore.state != 'unknown') -%}
{%- set input = chore.entity_id|regex_replace(find='input_button', replace='input_text') -%}
{%- set diff = ( as_timestamp(now()) - as_timestamp(chore.state) ) / 60 / 60 / 24 %}
{%- set due = states(input)|float(-1) -%}
{%- if due > 0 -%}
{%- if (due < diff) -%}
{%- set chore_overdue.entities = chore_overdue.entities + [chore.entity_id] -%}
{%- endif -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{{ chore_overdue.entities }}