I’ve written out a template for my chore data stored in the Grocy sensor, but as you can see, I have a ton of duplicated code in the JS templating portions of the custom button-card.
Normally, I would create a sensor but since the chores are stored as dictionaries within a list…within a list in an attribute for a single sensor, I’m not sure how I can break it out.
Example of how to access chore id 6, yes 6, with jinja templating:
{{ state_attr('sensor.grocy_chores', 'chores')[5] }}
Is there a way to pass a variable into a sensor, like I need the data for chore id 25? Call a script from within a JS template? A way to store this JS elsewhere and reference it? Should I write something in python and somehow reference it? Or am I stuck with this mess of a code?
I’ve tried searching around and I usually find what I’m looking for, but as you can imagine, searching for “home assistant” and “template” yields an insane amount of results either out of date or referencing some completely different templating feature…
chore_button:
template: base
variables:
chore_id: default
show_icon: true
show_label: true
aspect_ratio: 3/0.5
styles:
icon:
- top: 0%
- left: '-12%'
name:
- font-size: 17px
- line-height: 18px
- top: 30%
- left: 14.5%
label:
- top: 58%
- right: 5%
custom_fields:
- top: 20%
state:
- operator: template
value: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
var due_date = item[list_position]['next_estimated_execution_time'];
var one_day = 24*60*60*1000;
var today = new Date();
today.setHours(0,0,0,0)
var split_date = due_date.split(/[- :T]/)
var parsed_due_date = new Date(split_date[0], split_date[1]-1, split_date[2]);
parsed_due_date.setHours(0,0,0,0)
var difference = (parsed_due_date - today) / one_day;
return difference < -2
]]]
styles:
icon:
- color: red
- operator: template
value: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
var due_date = item[list_position]['next_estimated_execution_time'];
var one_day = 24*60*60*1000;
var today = new Date();
today.setHours(0,0,0,0)
var split_date = due_date.split(/[- :T]/)
var parsed_due_date = new Date(split_date[0], split_date[1]-1, split_date[2]);
parsed_due_date.setHours(0,0,0,0)
var difference = (parsed_due_date - today) / one_day;
return (-2 <= difference) && (difference < 0);
]]]
styles:
icon:
- color: orange
- operator: template
value: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
var due_date = item[list_position]['next_estimated_execution_time'];
var one_day = 24*60*60*1000;
var today = new Date();
today.setHours(0,0,0,0)
var split_date = due_date.split(/[- :T]/)
var parsed_due_date = new Date(split_date[0], split_date[1]-1, split_date[2]);
parsed_due_date.setHours(0,0,0,0)
var difference = (parsed_due_date - today) / one_day;
return difference == 0
]]]
styles:
icon:
- color: green
- operator: template
value: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
var due_date = item[list_position]['next_estimated_execution_time'];
var one_day = 24*60*60*1000;
var today = new Date();
today.setHours(0,0,0,0)
var split_date = due_date.split(/[- :T]/)
var parsed_due_date = new Date(split_date[0], split_date[1]-1, split_date[2]);
parsed_due_date.setHours(0,0,0,0)
var difference = (parsed_due_date - today) / one_day;
return difference > 0
]]]
styles:
card:
- color: 'rgba(255, 255, 255, 0.3)'
- background-color: 'rgba(215, 215, 215, 0.3)'
icon:
- color: 'rgba(255, 255, 255, 0.3)'
name: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
return item[list_position]['name'];
]]]
label: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
var due_date = item[list_position]['next_estimated_execution_time'];
var one_day = 24*60*60*1000;
var today = new Date();
today.setHours(0,0,0,0)
var split_date = due_date.split(/[- :T]/)
var parsed_due_date = new Date(split_date[0], split_date[1]-1, split_date[2]);
parsed_due_date.setHours(0,0,0,0)
var difference = (parsed_due_date - today) / one_day;
if (difference < -1) {
return 'Due ' + Math.round((difference * -1)) + ' days ago.';
} else if (difference == -1) {
return 'Due yesterday.';
} else if (difference == 0) {
return 'Due today.';
} else if ( difference == 1 ) {
return 'Due tomorrow.';
} else {
return 'Due in ' + Math.round(difference) + ' days.'
}
]]]
tap_action:
action: call-service
service: grocy.execute_chore
service_data:
chore_id: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
return item[list_position]['id'];
]]]
confirmation:
text: |
[[[ return "You are marking this task as completed."; ]]]
hold_action:
action: call-service
service: browser_mod.popup
service_data:
title: |
[[[
var item = states['sensor.grocy_chores'].attributes.chores
var list_position = variables.chore_id - 1
return item[list_position]['name'] ]]]
card:
type: vertical-stack
cards:
- type: markdown
content: |
[[[
var list_position = variables.chore_id - 1;
var item = states['sensor.grocy_chores'].attributes.chores;
var last_completed_date = item[list_position]['last_tracked_time'];
var split_date = last_completed_date.split(/[- :T]/);
var parsed_last_completed_date = split_date[0] + "-" + (split_date[1]) + "-" + split_date[2];
var next_due_date = item[list_position]['next_estimated_execution_time'];
var split_date = next_due_date.split(/[- :T]/);
var parsed_next_due_date = split_date[0] + "-" + (split_date[1]) + "-" + split_date[2];
var interval = item[list_position]['period_days'];
var description = item[list_position]['description'];
var last_completed_date_str = '**Last completed date:** ' + parsed_last_completed_date;
var next_due_date_str = '\n**Next due date:** ' + parsed_last_completed_date;
var interval_str = '\n**Interval:** ' + interval + ' days';
var description_str = '\n\n' + description;
return last_completed_date_str + interval_str + next_due_date_str + description_str;
]]]
Output:
Edit to add my changes:
I created a sensor using petro’s template:
chores:
friendly_name: Chores
value_template: >
{{ state_attr('sensor.grocy_chores', 'chores')|length }}
attribute_templates:
chores: |
{%- set ns = namespace(output=[]) %}
{%- for chore in state_attr('sensor.grocy_chores', 'chores') -%}
{%- set last_completed_date = chore['last_tracked_time'].date() -%}
{%- set due_date = chore['next_estimated_execution_time'].date() -%}
{%- set days_until_due = (due_date - now().date()).days -%}
{%- if days_until_due < -2 -%}
{%- set status = "Critical" -%}
{%- elif -2 <= days_until_due and days_until_due < 0 -%}
{%- set status = "Warning" -%}
{%- elif days_until_due == 0 -%}
{%- set status = "To Do" -%}
{%- else -%}
{%- set status = "Done" -%}
{%- endif -%}
{%- set ns.output = ns.output + [
'"{0}": {{ "name": "{1}", "last_completed_date": "{2}", "interval": "{3}", "due_date": "{4}", "days_until_due": "{5}", "status": "{6}" }}'.format(chore["id"], chore["name"], last_completed_date, chore["period_days"], due_date, days_until_due, status)
] %}
{%- endfor -%}
{{ '{' ~ ns.output | join(', ') ~ '}' }}
and then updated the button card template with:
chore_button:
variables:
chore_id: default
.....
state:
- operator: template
value: |
[[[
var status = states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["status"];
return status == 'Critical';
]]]
styles:
icon:
- color: red
- operator: template
value: |
[[[
var status = states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["status"];
return status == 'Warning';
]]]
styles:
icon:
- color: orange
- operator: template
value: |
[[[
var status = states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["status"];
return status == 'To Do';
]]]
styles:
icon:
- color: green
- operator: template
value: |
[[[
var status = states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["status"];
return status == 'Done';
]]]
styles:
card:
- color: 'rgba(255, 255, 255, 0.3)'
- background-color: 'rgba(215, 215, 215, 0.3)'
icon:
- color: 'rgba(255, 255, 255, 0.3)'
name: |
[[[ return states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["name"]; ]]]
label: |
[[[
var days_until_due = states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["days_until_due"];
if (days_until_due < -1) {
return 'Due ' + Math.round((days_until_due * -1)) + ' days ago.';
} else if (days_until_due == -1) {
return 'Due yesterday.';
} else if (days_until_due == 0) {
return 'Due today.';
} else if ( days_until_due == 1 ) {
return 'Due tomorrow.';
} else {
return 'Due in ' + Math.round(days_until_due) + ' days.'
}
]]]
tap_action:
action: call-service
service: grocy.execute_chore
service_data:
chore_id: |
[[[ return variables.chore_id; ]]]
confirmation:
text: |
[[[ return "You are marking this task as completed."; ]]]
hold_action:
action: call-service
service: browser_mod.popup
service_data:
title: |
[[[ return states['sensor.chores'].attributes.chores[variables.chore_id.toString()]["name"]; ]]]
card:
type: vertical-stack
cards:
- type: markdown
content: |
[[[
var chore = states['sensor.chores'].attributes.chores[variables.chore_id.toString()];
var last_completed_date_str = '**Last completed date:** ' + chore["last_completed_date"];
var interval_str = '\n**Interval:** ' + chore["interval"] + ' days';
var next_due_date_str = '\n**Next due date:** ' + chore["due_date"] + "\n";
var description = states['sensor.grocy_chores'].attributes.chores[variables.chore_id - 1]["description"]
return last_completed_date_str + interval_str + next_due_date_str + description;
]]]
I use the status variable to control what gets displayed, so the “command center” tablet looks like:
And we get a daily report sent via slack that gives us the same list.
Everything else is used to handle the buttons and user experience.