As I dug in more to this more, there is a lot of good work and creativity to get this far along. Really good work!
As you saw in my earlier post, I had some trouble with the dashboards, and even when I got them working, they weren’t formatting as nicely as was displayed in your example. I made quite a few adjustments to formatting and the templates to hopefully simplify some things and better handle some errors that were showing up because some of the new entities didn’t have values when I first setup the dashboard. I also broke out two separate cards which helped me with some testing. One of the cards is for the kid, and the other for the parent. Below are some pictures as well as the code:
To use these, find and replace the following words “Points” and “points” with whatever you are using. Then find and replace the kids name by find and replace “PAYTON”, “Payton”, and “payton” being sure to match case.
type: grid
square: false
columns: 1
cards:
- type: heading
heading: PAYTON
heading_style: title
- type: grid
square: false
columns: 2
cards:
- type: entity
entity: sensor.payton_points
unit: Points
name: PAYTON
- type: custom:mushroom-entity-card
entity: sensor.payton_highest_badge
layout: vertical
primary_info: state
secondary_info: none
fill_container: true
- type: tile
entity: sensor.payton_total_chores_completed
name: Total Chores Completed
- type: tile
entity: sensor.payton_chores_completed_today
name: Chores Completed Today
- type: custom:mini-graph-card
name: PAYTON
entities:
- entity: sensor.payton_points
hours_to_show: 168
group_by: date
smoothing: false
show:
state: false
name: false
icon: false
- type: heading
icon: ""
heading: Claim Chores (Hold to Claim)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically #}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_claim_chore_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_claim_chore_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
{%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
{%- if not secondary and state.state | float(default=0) > 0 -%}
{%- set delta = now().timestamp() - as_timestamp(state.state) -%}
{%- set secondary =
'%.0f minutes ago' % (delta / 60) if delta < 3600 else
'%.0f hours ago' % (delta / 3600) if delta < 86400 else
'%.0f days ago' % (delta / 86400)
-%}
{%- endif -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Claim Chore: ')[1],
'secondary': secondary,
'icon': state.attributes.icon,
'icon_color': (
'green' if states(sensor_id) == 'approved' else
'yellow' if states(sensor_id) == 'partial' else
'orange' if states(sensor_id) == 'claimed' else
'red' if states(sensor_id) == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Redeem Rewards (Hold to Redeem)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: >-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically
#}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_redeem_reward_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_redeem_reward_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
{%- set cost = state_attr(sensor_id, 'cost') -%}
{%- set claim_sensor_id = 'sensor.' + Kid_name | lower + '_' + sensor_suffix + '_claims' -%}
{%- set claims = states(claim_sensor_id) | string if states(claim_sensor_id) != 'unknown' else 'N/A' -%}
{%- set approvals_sensor_id = 'sensor.' + Kid_name | lower + '_' + sensor_suffix + '_approvals' -%}
{%- set approvals = states(approvals_sensor_id) | string if states(approvals_sensor_id) != 'unknown' else 'N/A' -%}
{%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') + '\n' + 'Claims: ' + claims + '\n' + 'Approved: ' + approvals-%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Redeem Reward: ')[1],
'secondary': secondary,
'multiline_secondary': 'false',
'icon': state.attributes.icon,
'icon_color': 'blue',
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{% endfor %}
sort:
method: friendly_name
type: grid
square: false
columns: 1
cards:
- type: heading
heading: PAYTON PARENT
heading_style: title
- type: grid
square: false
columns: 2
cards:
- type: entity
entity: sensor.payton_points
unit: Points
name: PAYTON
- type: custom:mushroom-entity-card
entity: sensor.payton_highest_badge
layout: vertical
primary_info: state
secondary_info: none
fill_container: true
- type: tile
entity: sensor.payton_total_chores_completed
name: Total Chores Completed
- type: tile
entity: sensor.payton_chores_completed_today
name: Chores Completed Today
- type: custom:mini-graph-card
name: PAYTON
entities:
- entity: sensor.payton_points
hours_to_show: 168
group_by: date
smoothing: false
show:
state: false
name: false
icon: false
- type: heading
heading: Chore Status
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the name dynamically #}
{% for state in states.sensor -%}
{%- if state.entity_id.startswith('sensor.' + Kid_name | lower + '_status_') -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Status - ')[1],
'secondary': (
'Approved' if state.state == 'approved' else
'Claimed' if state.state == 'claimed' else
'Pending' if state.state == 'pending' else
'Partial' if state.state == 'partial' else
'Due' if state.state == 'overdue' else
'Pending'
),
'icon': state.attributes.icon,
'icon_color': (
'green' if state.state == 'approved' else
'orange' if state.state == 'claimed' else
'grey' if state.state == 'pending' else
'yellow' if state.state == 'partial' else
'red' if state.state == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
}
}
}},
{%- endif -%}
{%- endfor %}
exclude: []
sort:
method: friendly_name
- type: heading
icon: ""
heading: Approve Chores (Hold to Approve)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: >-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically
#}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_approve_chore_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_approve_chore_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
{%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
{%- if not secondary and state.state | float(default=0) > 0 -%}
{%- set delta = now().timestamp() - as_timestamp(state.state) -%}
{%- set secondary =
'%.0f minutes ago' % (delta / 60) if delta < 3600 else
'%.0f hours ago' % (delta / 3600) if delta < 86400 else
'%.0f days ago' % (delta / 86400)
-%}
{%- endif -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Approve Chore: ')[1],
'secondary': secondary,
'icon': state.attributes.icon,
'icon_color': (
'green' if states(sensor_id) == 'approved' else
'yellow' if states(sensor_id) == 'partial' else
'orange' if states(sensor_id) == 'claimed' else
'red' if states(sensor_id) == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Disapprove Chores (Hold to Disapprove)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically #}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_disapprove_chore_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_disapprove_chore_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
{%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
{%- if not secondary and state.state | float(default=0) > 0 -%}
{%- set delta = now().timestamp() - as_timestamp(state.state) -%}
{%- set secondary =
'%.0f minutes ago' % (delta / 60) if delta < 3600 else
'%.0f hours ago' % (delta / 3600) if delta < 86400 else
'%.0f days ago' % (delta / 86400)
-%}
{%- endif -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Disapprove Chore: ')[1],
'secondary': secondary,
'icon': state.attributes.icon,
'icon_color': (
'green' if states(sensor_id) == 'approved' else
'yellow' if states(sensor_id) == 'partial' else
'orange' if states(sensor_id) == 'claimed' else
'red' if states(sensor_id) == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Approve Rewards (Hold to Approve)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically #}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_approve_reward_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_approve_reward_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
{%- set cost = state_attr(sensor_id, 'cost') -%}
{%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Approve Reward: ')[1],
'secondary': secondary,
'icon': state.attributes.icon,
'icon_color': (
'green' if states(sensor_id) == 'approved' else
'yellow' if states(sensor_id) == 'partial' else
'orange' if states(sensor_id) == 'claimed' else
'red' if states(sensor_id) == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Disapprove Rewards (Hold to Disapprove)
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: >-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically
#}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_disapprove_reward_') -%}
{%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_disapprove_reward_')[1] -%}
{%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
{%- set cost = state_attr(sensor_id, 'cost') -%}
{%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Disapprove Reward: ')[1],
'secondary': secondary,
'icon': state.attributes.icon,
'icon_color': (
'green' if states(sensor_id) == 'approved' else
'yellow' if states(sensor_id) == 'partial' else
'orange' if states(sensor_id) == 'claimed' else
'red' if states(sensor_id) == 'overdue' else
'grey'
),
'tap_action': {
'action': 'more-info'
},
'hold_action': {
'action': 'toggle'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Penalties
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically #}
{% for state in states.button -%}
{%- if state.entity_id.startswith('button.' + Kid_name | lower + '_apply_penalty_') -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' - Apply Penalty: ')[1],
'icon': state.attributes.icon,
'icon_color': 'blue',
'tap_action': {
'action': 'toggle'
},
'hold_action': {
'action': 'more-info'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: friendly_name
- type: heading
icon: ""
heading: Manual Points
heading_style: title
- type: custom:auto-entities
card:
square: false
type: grid
columns: 2
card_param: cards
filter:
template: |-
{%- set Kid_name = 'Payton' -%} {# Define the kid's name dynamically #}
{% for state in states.button -%}
{%- if state.entity_id | regex_match("^button\\." + Kid_name | lower + "_\\d+_points(?:_\\d+)?$", ignorecase=False) -%}
{{
{
'type': 'custom:mushroom-template-card',
'entity': state.entity_id,
'primary': state.attributes.friendly_name.split(Kid_name + ' ')[1],
'icon': state.attributes.icon,
'icon_color': 'blue',
'tap_action': {
'action': 'toggle'
},
'hold_action': {
'action': 'more-info'
}
}
}},
{%- endif -%}
{%- endfor %}
sort:
method: domain
numeric: true
ignore_case: false
reverse: false
grid_options:
columns: full
rows: 8
I’m guessing your time is primarily focused on the backend work at the moment, so my goal was just to make some iterative improvements that hopefully help others more easily jump in to try it out. I think it is well designed; it just takes a little while to understand concepts of how you’re handling everything. I can definitely tell there is a lot of potential for improving.
I also made some notes while I was trying it out. I will put them in this post for now, but if you are at a point you want to start logging them on github, happy to do that:
Issues
- Chore Deletion
- Problem: When a chore is deleted, the entities are not automatically removed.
- Workaround: Manually delete the entities to prevent them from appearing on dashboards.
- Date Field Requirement
- Problem: When managing chores, the date field must be clicked and selected every time. Not selecting it prevents submission, but the error is subtle and only noticeable after repeated submission attempts.
- Workaround: Ensure you select the date field during creation or updates. Consider adding a note or warning to remind users.
- Claim Chore Button
- Problem: When claiming a chore, the sensor status for that chore does not update to “claimed.” Instead, it remains as “overdue.” However, it is still possible to approve the claim. (This seems to work correctly for rewards.)
- Disapproving Chores/Rewards
- Problem: When disapproving a chore or reward, the claim count does not decrement.
- Note: This behavior might be by design and requires clarification.
- Manual Points Tracking
- Problem: Manual points are displayed correctly in the
max_points_ever
sensor, but they do not appear in the points_earned
sensors.
- Note: This might also be by design and requires clarification.
- Overdue Chore Timing
- Problem: Chores are marked as “overdue” even when the due time is later in the day (e.g., 9 PM).
Comments
- Clarity on Dates and Recurring Activities: The process for managing dates and recurring activities needs more explanation or documentation.
- Understanding
kid_state
vs global_state
: There needs to be clearer guidance on how these states are used or differentiated.
- Shared Chores:
- Observation: When setting up a chore, at least one kid must be selected. Initially, I assumed leaving it blank would create a “shared chore” for all kids.
- Suggestion: No system changes needed; however, an FAQ or description update in the form could clarify this behavior.
- Observation: I’m not sure if there is value in supporting fractional points.
- Suggestion: I notice that points in the dashboard are showing with a decimal. Unless there is a specific need to support partial points, I’d recommend limiting it to whole numbers for simplicity.
Future Feature Requests
- Ad-Hoc Chores
- Request: Allow a type of chore that doesn’t have a specific due date. Parents could assign a date later for tasks that aren’t time-sensitive (e.g., “Get your room ready for guests”).
- Auto-Approved Chores
- Request: Add an option to automatically approve certain chores. For example, chores like “Brush your teeth” could be trusted without requiring manual approval.
- Default Dashboard YAML
- Request: Considering some of the required templating is a little more complex in these dashboards, it would be worth adding a “Default” or “Initial” dashboard configuration file to you this github project so there is a common place to maintain and update it.
- Time Sensitive or Critical Chores
- Request: Mark key chores, such as feed the cat, with an attribute to identify it as a time sensitive chore that you could easily filter for and build separate alerting or notifications if it is overdue.