Chores - Keep track using HA

Hi! First of all, thank you for your response! Here you go:

This is the code of the chore.yaml that I created


{% set entity = 'sensor.'+sensor_name %}

type: 'custom:button-card'
name: {{name}}
entity: {{entity}}
label: >
  [[[ return variables.var.label ]]]
show_label: true
icon: >
  [[[ return variables.var.icon ]]]
custom_fields:
  status: >
    [[[ return '<span style="display: inline-block; color: white; background: '+variables.var.color+'; padding: 0 5px; border-radius: 5px;">'+variables.var.days_left+'</span>' ]]]
styles:
  grid:
    - grid-template-areas: '"i n status" "i l status"'
    - grid-template-columns: 15% 1fr 1fr
    - grid-template-rows: 1fr 1fr
  icon:
    - color: >
        [[[ return variables.var.color ]]]
  label:
    - color: var(--disabled-text-color)
    - justify-self: start
  name: 
    - justify-self: start
variables:
  var: >
    [[[
      let colors = {};
      colors["success"] = "#8BC24A";
      colors["warning"] = "#FFC107";
      colors["error"] = "#FF5252";
      colors["disabled"] = "var(--disabled-text-color)";
      
      let result = {};
      result.label = "Aufgabe erstellen";
      result.color = colors["disabled"];
      result.icon = "mdi:alert-plus";
      result.days_left = "";
      let timestamp;
      let time;
      let minutes;
      let hours;
      let days;
      
      if (states['{{entity}}']) {
        if (entity.state != 'unknown') {
          timestamp = parseInt(entity.state);
          time = (Date.now() / 1000) - timestamp;
          minutes = Math.floor(((time % 3600) / 60));
          hours = Math.floor(((time % 86400) / 3600));
          days = Math.floor((time / 86400));
          
          result.color = colors["success"];
          result.icon = "mdi:checkbox-marked-circle-outline";

          // LAST TRIGGER
          if (time < 60)
            result.label = 'weniger als 1 Minute';
          else if (days == 1)
            result.label = '1 Tag her';
          else if (days > 1)
            result.label = days+' Tage her';
          else if (hours >= 1)
            result.label = hours+' Stunden her';
          else if (hours < 1)
            result.label = minutes+(minutes > 1 ? ' Minuten' : ' Minute');
          
          // DAYS LEFT
          result.days_left = Math.round(((timestamp + ({{cycle_days|int}}*86400)) - (Date.now()/1000)) / 86400);
          if (result.days_left <= {{warning_before|int}}) {
            result.color = colors["warning"];
            result.icon = "mdi:clock-alert";
          }
          if (result.days_left <= 0) {
            result.color = colors["error"];
            result.icon = "mdi:alert-circle"
          }
          result.days_left = result.days_left + (result.days_left == 1 ? " Tag verbleibt" : " Tage verbleiben");

        } else {
          result.label = "Klicken zum Erledigen";
        }
      }
      return result;
      
    ]]]
tap_action:
  confirmation:
    text: >
      [[[
        if (!states['{{entity}}'])
          return 'Entität {{entity}} wird erstellt.'
        else
          return 'Möchten sie diese Aufgabe wirklich als erledigt markieren?'
      ]]]
  action: call-service
  service: mqtt.publish
  service_data:
    topic: >
      [[[
        if (!states['{{entity}}'])
          return 'homeassistant/sensor/{{sensor_name}}/config'
        else
          return 'homeassistant/sensor/{{sensor_name}}/state'
      ]]]
    payload: >
      [[[
        if (!states['{{entity}}'])
          return '{ "name": "{{sensor_name}}", "state_topic": "homeassistant/sensor/{{sensor_name}}/state", "value_template": "\{\{ value_json.state \}\}", "device_class": "timestamp", "json_attributes_topic": "homeassistant/sensor/{{sensor_name}}/state", "json_attributes_template": "\{\{ value_json.attributes | tojson \}\}" }'
        else
          return '{ "state":' + (Date.now() / 1000) + ', "attributes": { "cycle_days": {{cycle_days}}, "warning_before": {{warning_before}} } }'
      ]]]
    retain: true

And here is the code for the Button Card:

  - !include
    - ../../templates/household_task.yaml
    - name: Change Bed Sheets
      sensor_name: chore_change_bed_sheets
      warning_before: 1
      cycle_days: 14

(I used your code just to see if it works, but I always get the above errors.

Did you install lovelace_gen as described in my guide?

You forgot the first line in the chore.yaml file

# lovelace_gen

This is not a comment, it’s needed for lovelace gen to work.

Also these files need to be in the lovelace folder not directly in the config folder. And the include in the button needs to reference correctly to the chore.yaml file.

Thank you for your work!

I think I have all the pre-requisites installed. lovelace_gen, custom:button-card. However, I’m running into this issue after creating chores.yaml. Can you please help me?

The file is in the wrong directory, it needs to be in the lovelace directory, not in packages.

I don’t have a lovelace directory. Should i create a lovelace directory under config or should i create a lovelace directory under .storage and put the chores.yaml there?

Update 1: Tried creating lovelace directory under config and placed chores.yaml there. No luck.

@Burningstone Where should the lovelace directory be created? Please help me.

Hey Burningstone,
once again thank you for your quick replies and help!
By now I have however found a way to implement my chore-tracking using only Vanilla HA. It may be a bit complicated, but it works for me and the Lovelace is so simple, that even my children can use it ;-). If anyone is interested, I can post my Setup.

@entemery Can you please post your setup? I’m a noob and it will be a huge help to me! Thanks.

Setup coming tonight, as soon as the kids are in bed :wink:

1 Like

@entemery @Burningstone Does it still works on your side?

Home assistant complains on incoming mqttt message when “task” completed from lovelace UI.

2022-02-16 20:22:55 WARNING (MainThread) [homeassistant.components.mqtt.sensor] Invalid state message '{ "state":1645035774}' from 'homeassistant/sensor/chore_bedroom_dust_clean/state'

I believe home assistant waits on date-time string because of the defined device_class in the initial mqtt configuration message:

{
   "name": "chore_bedroom_dust_clean",
   "state_topic": "homeassistant/sensor/chore_bedroom_dust_clean/state", 
   "value_template": "{{ value_json.state }}", 
   "device_class": "timestamp" 
}

But lovelace UI actually return unix time (int)

new Date() / 1000

How does it works on your side? Am I missing any?

P.S. Without device_class(timestamp) in the fist configuration message it works fine on my side, but I’m curious about the timestamp device_class.

OK, here comes my setup as promised.
But first, just to put things into perspective let me clarify, that I have only started using HA about three weeks ago, so please see me as a total beginner in this field. I wil try to go through everything step by step, before posting the code so that you have the choice to either follow along or simply copy my code and adjust it to your needs

Here goes:
This is how it looks in the Lovelace once it is done:

Each room is organised in a vertical stack with the chores and the days until the chore is due next in horizontal stacks underneath. When the chore is due, the corresponding card lights up, and the timer goes to zero. Once the chore is completed, the card can be pressed and the timer is reset.
That’s it! No flashing lights, colors based on if the chore is due or overdue or anything like that. Nothing fancy, but it’s working. And it’s simple enough that my whole family can use it.

Now onto how I set this all up:

1. The logic

These are the things that went into my process of thinking the whole thing through:

  • Each room has different chores associated to it
  • Each chore has a status (if it is not due yet: “off”, as soon as it is due: “on”)
  • Each chore has a certain intervall when it needs to be done (every x days)

2. The Process

These are the things that I wanted the system to handle:

  • I wanted a button that would light up, once the chore is due
  • Once I press this button, the chore would be reset to “not due yet” and the chore timer would be reset

3. The Coding in HA Backend

(For the purpose of explaining I will pick the chore “water plants”, so when you copy the code just swap this with whatever chore you need)

  • since the chore has two statuses (on and off) I created an input_boolean helper: (for the noobs among you: these can be defined in the HA UI under “Settings → automations → helpers”)

  • I called mine “living_room_water_plants” which will then generate the new entity
    input_boolean.living_room_water_plants

  • The chore also has a certain interval when it needs wo be done. Since normal timers do not survive a HA restart, I decided to use the “counter helper” and so I created a counter for my chore:
    counter.living_room_water_plants

What needs to be done next is to define the time interval for the chore (e.g. every four days) and let the counter increment each day at midnight. For that we need two automations:

  1. Incrementing the counter.Living_room_water_plants each day at midnight:
- id: '1644859778285'
  alias: increment plant timer
  description: ''
  trigger:
  - platform: time
    at: 00:00
  condition: []
  action:
  - service: counter.increment
    data: {}
    target:
      entity_id:
      - counter.living_room_water_plants
  1. Setting an interval for the chore to be due:
    For now, all HA does is count the days but it still needs to know when the chore has to be due next. In my example, I water the plants every four days. That means once the counter hits “4”, I want my input.boolean to switch to “on”, indicating that the chore is due and for that we need a second automation:
- id: '1644860073290'
  alias: Living room plants due
  description: ''
  trigger:
  - platform: state
    entity_id: counter.living_room_water_plants
    to: '4'
  condition: []
  action:
  - service: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.living_room_water_plants
  mode: single
  1. Resetting the chore timer once the chore is completed:
    Already thinking about the Lovelace later… I want to press a button, which then turns the inpt_boolean back off and resets the counter to “0”, so here comes automation number 3:
- id: '1644870298228'
  alias: Living room plants done
  description: ''
  trigger:
  - platform: state
    entity_id: input_boolean.living_room_water_plants
    to: 'off'
  condition: []
  action:
  - service: counter.reset
    data: {}
    target:
      entity_id: counter.living_room_water_plants
  mode: single

3. The Coding in HA Frontend
OK, now I know that this may have been hard to follow, but bear with me… We are done with the backend configuration and can now turn to the fun part: Creating the Lovelace UI:

I Will just plainly post my example code here, which you can then copy and adjust to your needs:

views:
 - title: Chores
    path: chores
    icon: mdi:beaker-check
    badges: []
    cards:
      - type: vertical-stack
        cards:
          - type: markdown
            content: Living room
          - type: horizontal-stack
            cards:
              - type: button
                tap_action:
                  action: none
                entity: input_boolean.living_room_water_plants
                icon: mdi:flower
                name: Water plants
                hold_action:
                  action: toggle
          - type: horizontal-stack
            cards:
              - type: markdown
                content: >-
                  due in  {{ 4 - states("counter.living_room_water_plants") |
                  int}} days

This will approximately create the result that you can see above (with only te one chore displayed of course) with one exception: In the picture I used a custom button card as the headline for the rooms, but since I said that it could be done using vanilla HA, I swapped it for a markdown card - works just as well.

Hope that I could help some of you and if you have any questions - because I know that this may have been all over the place for some - feel free to post them here…

Happy coding :wink:

3 Likes

Thanks a ton for taking the time to compile and share it with us. You’re awesome.

Can you please shed some light on how and where to create the lovelace_gen template?

Trying to do so. But getting the following error.

This is exactly what happened to me too and thats why I decided to go my own route.

The error is normal, it’s coming fron the visual studio code editor. You can safely ignore it, it works fine on my side and I’m getting the same error.

1 Like

@Burningstone - Thanks a lot for your patience. I have been able to resolve most of the issues and I think this might be the last one.

I’m able to get lovelace_gen to load the UI. When i click on the cards, they create the specified MQTT sensor. The issue is that the state of the sensor is always ‘unknown’ which is causing the lovelace cards to not go from ‘disabled’ state to any other statuses like ‘Success’ or ‘Warning’ or ‘Error’. Any number of times i click on the card, it stays in the ‘disabled’ icon state.

i believe it is happening because the sensor state is at ‘unknown’. What should the status of the MQTT sensors be? Please help me.

Attaching the screenshots of what I just described.

As far as I know this is due to a recent change in the MQTT sensors. I haven’t updated HA in the last 4 months as I moved to a new house, busy at job, etc.
Maybe I’ll get some time this week to fix it.

1 Like

Thank you. Are there any other sensors that we can use instead of MQTT sensors that will retain states even after HomeAssistant restart?
I don’t mind initiating few sensors to make it work. Just didn’t know where to start.

I will be able to fix it soon, it’s relatively easy to adjust it.

I don’t know of any other sensors that retain the state without some unneccessary workarounds.

I’ve got it to work again by changing the chores.yaml to this:

# lovelace_gen

{% set entity = 'sensor.'+sensor_name %}

type: 'custom:button-card'
name: {{name}}
entity: {{entity}}
label: >
  [[[ return variables.var.label ]]]
show_label: true
icon: >
  [[[ return variables.var.icon ]]]
custom_fields:
  status: >
    [[[ return '<span style="display: inline-block; color: white; background: '+variables.var.color+'; padding: 0 5px; border-radius: 5px;">'+variables.var.days_left+'</span>' ]]]
styles:
  grid:
    - grid-template-areas: '"i n status" "i l status"'
    - grid-template-columns: 15% 1fr 1fr
    - grid-template-rows: 1fr 1fr
  icon:
    - color: >
        [[[ return variables.var.color ]]]
  label:
    - color: var(--disabled-text-color)
    - justify-self: start
  name: 
    - justify-self: start
variables:
  var: >
    [[[
      let colors = {};
      colors["success"] = "#8BC24A";
      colors["warning"] = "#FFC107";
      colors["error"] = "#FF5252";
      colors["disabled"] = "var(--disabled-text-color)";
      
      let result = {};
      result.label = "Aufgabe erstellen";
      result.color = colors["disabled"];
      result.icon = "mdi:alert-plus";
      result.days_left = "";
      let date;
      let seconds;
      let timestamp;
      let time;
      let minutes;
      let hours;
      let days;
      
      if (states['{{entity}}']) {
        if (entity.state != 'unknown') {
          date = entity.state;
          seconds = new Date(date);
          timestamp = (seconds.getTime()) / 1000;
          time = (Date.now() / 1000) - timestamp;
          minutes = Math.floor(((time % 3600) / 60));
          hours = Math.floor(((time % 86400) / 3600));
          days = Math.floor((time / 86400));
          
          result.color = colors["success"];
          result.icon = "mdi:checkbox-marked-circle-outline";

          // LAST TRIGGER
          if (time < 60)
            result.label = 'weniger als 1 Minute';
          else if (days == 1)
            result.label = '1 Tag her';
          else if (days > 1)
            result.label = days+' Tage her';
          else if (hours >= 1)
            result.label = hours+' Stunden her';
          else if (hours < 1)
            result.label = minutes+(minutes > 1 ? ' Minuten' : ' Minute');
          
          // DAYS LEFT
          result.days_left = Math.round(((timestamp + ({{cycle_days|int}}*86400)) - (Date.now()/1000)) / 86400);
          if (result.days_left <= {{warning_before|int}}) {
            result.color = colors["warning"];
            result.icon = "mdi:clock-alert";
          }
          if (result.days_left <= 0) {
            result.color = colors["error"];
            result.icon = "mdi:alert-circle"
          }
          result.days_left = result.days_left + (result.days_left == 1 ? " Tag übrig" : " Tage übrig");

        } else {
          result.label = "Klicken zum Erledigen";
        }
      }
      return result;
      
    ]]]
tap_action:
  confirmation:
    text: >
      [[[
        if (!states['{{entity}}'])
          return 'Entität {{entity}} wird erstellt.'
        else
          return '{{name}} wirklich als erledigt markieren?'
      ]]]
  action: call-service
  service: mqtt.publish
  service_data:
    topic: >
      [[[
        if (!states['{{entity}}'])
          return 'homeassistant/sensor/{{sensor_name}}/config'
        else
          return 'homeassistant/sensor/{{sensor_name}}/state'
      ]]]
    payload: >
      [[[
        var timestamp = Date.now();
        var time = new Date(timestamp).toISOString();

        if (!states['{{entity}}'])
          return '{ "name": "{{sensor_name}}", "state_topic": "homeassistant/sensor/{{sensor_name}}/state", "value_template": "\{\{ value_json.state \}\}", "device_class": "timestamp", "json_attributes_topic": "homeassistant/sensor/{{sensor_name}}/state", "json_attributes_template": "\{\{ value_json.attributes | tojson \}\}" }'
        else
          return '{ "state":"' + time + '", "attributes": { "cycle_days": {{cycle_days}}, "warning_before": {{warning_before}} } }'
      ]]]
    retain: true
1 Like

Worked like a charm! Thanks a ton for taking the time to make it work. :slight_smile: