Record and update current "on" time each time entity turns to specific state

Hi everybody,

I have found some great tips on these forums trying to work with timestamps and time formats, but I cannot get this to work. Tempered with it for a couple of days now, but never get the result I want.

There is history_stats, which might be able to do what I need, but either I cannot configure it correctly, or it is just not meant for this.

use case

We have one single vacuum entity for two floors in the house. It is a Neato Botvac D7, which can be integrated in Home Assistant without any issues. Due to the fact that it needs to run (at least) twice per day (=once per floor), we need to manually carry it up the stairs once the ground floor is done.

It would be nice to have an overview about how long the current cleaning cycle has been so far, so that we can estimate how much longer it is going to take. Times vary depending on if it actually cleans all rooms, or of some doors are shutā€¦

attempts

So I tried to write now() to an input_datetime (has_time: true, has_date: false) when the deviceā€™s status changes from anything other than cleaning to cleaning. I then have an automation that will run every 2 minutes (time_pattern), checks as condition whether the state of the entity is still cleaning, then it is supposed to write a value that is now() - <start_time> to another input_datetime.

I have tested countless templates in developer tools, but I just wonā€™t receive the value I need. Letā€™s say I initiate cleaning at 12:34:20 and ten minutes pass; it is now 12:44:22, and the input_datetime entity ought to equal 00:10:00. I have not posted my different approaches in order to not add extra confusion to this (but I can if anybody here thinks that it helps).

Basically, I am looking for the correct template that subtracts the start time of the vacuum (previously set via input_datetime) from the current time / now().

I tried converting both to timestamps, then substract one from the other, then convert the result back to timestamp_custom, but even though I sometimes do get values, there must be something wrong with the template, as it does not show the actual time that the robot has been vacuuming.

Considering history_stats, it will display the correct time the robot has been vacuuming, but only the first time. I have not found a way to reset it back to zero, so the second time the vacuum starts cleaning, I get whatever the value had been previously plus the current vacuuming time. (I got the actual minutes by {{ states("sensor.cleaning_time") * 60 }}, as the sensor reports something like 0.38 hours, which has to be converted).

What I need is a sensor displaying hours and minutes (seconds optional, as it is only updated every two minutes, anyway) starting when the vacuum entity switches to cleaning, and resets when it switches to itā€™s ready stateā€¦ what do you suggest I do?

Thank you for your ideas :slight_smile:

to make it easier I would set an input_datetime that has both date & time when the cleaning starts.

Then you can convert both now() & the input_datetime to timestamps and subtract the two of those. Then convert the result back to a timestamp_custom for display.

This is pretty much the usage case proposed under : -

With methods and option outlined.

So, I do ā€˜somethingā€™ like this - in order to accurate guage the ā€˜durationā€™ entities have been ON for - regardless if thereā€™s a reboot.

First I use the HASS Variables custom component: https://github.com/rogro82/hass-variables This is similar to input_* inherent HA entities, but I started using this component, it works for me, so I stuck with itā€¦

Anyhow, hereā€™s what I do to check ā€˜durationā€™ of a device being ā€˜onā€™:

  1. Create a variable to save the ā€˜onā€™ time
front_gate_open:
  value: '0'
  restore: true
  1. Create scripts to turn ā€˜onā€™ the devices I want to measure. I could have written an automation I suppose as wellā€¦ This writes current time to this variable when it changes to ā€˜onā€™
front_gate_open:
  sequence:
  - condition: state
    entity_id: binary_sensor.front_gate_contact
    state: 'off'
  - service: switch.toggle
    entity_id: switch.front_gate
  - service: variable.set_variable
    data:
      variable: front_gate_open
      value_template: "{{ now().timestamp() }}"
  1. Create a sensor to calculate the duration itā€™s been on
    front_gate_last_open:
      value_template: >
        {% set elapsed = (as_timestamp(states('sensor.date_time').replace(',','')) - states('variable.front_gate_open') | int ) %}
        {% set days = (elapsed / 86400) | int %}
        {% set hours= ((elapsed  % 86400) / 3600) | int %}
        {% set mins = ((elapsed  % 3600) / 60) | int %}
        {% set secs = elapsed | int % 60 %}
        {% if days > 0 %} {{days}}d {%if hours < 10 %}0{%endif%}{{hours}}:{%if mins< 10 %}0{%endif%}{{mins}}
        {% elif hours > 0 %} {% if hours < 10 %}0{%endif%}{{hours}}:{%if mins< 10 %}0{%endif%}{{mins}}
        {% elif mins > 0 %} {{mins}}m
        {% else %} {{secs}}s {% endif %}
  1. Use ui-lovelace.yaml (along with custom:multiple-entity-row card & card-mod to style it) to conditionally show when this device is on, and the duration itā€™s been on for
          - type: conditional
            conditions:
              - entity: binary_sensor.front_gate_contact
                state_not: "off"
            card:
              type: entities
              style: |
                ha-card #states {
                  margin-bottom: -5px !important;
                  padding-top: 5px !important;
                  padding-bottom: 0px;
                }
              entities:
                - entity: switch.front_drive_gate
                  type: custom:multiple-entity-row
                  name: Front Gate
                  state_color: true
                  toggle: false
                  show_state: false
                  entities:
                    - entity: switch.front_drive_gate
                      toggle: true
                      name: false
                      tap_action:
                        action: call-service
                        service: script.turn_on
                        service_data:
                          entity_id: script.front_gate_close
                    - entity: sensor.front_gate_last_open
                      name: Duration

End result:
image
Might be a little bit convoluted, but itā€™s what works for me :slight_smile:

On a similar/side note: The wife asked me to measure my sonā€™s Xbox Time, so I had the thought to create 10 or so variables xbox_on_1, xbox_on_2, etcā€¦ and an input_number. Each time the xbox turned on Iā€™d write the start time to a variable (xbox_on_1 for example). When it turned off Iā€™d calc the time it was on and over-write the variable w/ the duration (in milliseconds). Then Iā€™d add 1 to the input_number and next time it turned on use xbox_on_2 (using a if/else logic in a script before writing), rinse and repeat. Then I could calc total time in a day / week - creating an automation to ZERO these variables and input_number out at midnight, or EOW.

Suppose you could also do xbox_1_on and xbox_1_off and use date / times and calc that way. Both a little kludgy, but would/could/should still ā€˜workā€™

Anyhow, hope it helps.

1 Like

Thank you.
While I am sure that your solution works, it is too complex to just copy and paste; I like to understand (or rather, learn) what I am doing, so I might take your example and re-write it from scratch, using it as a referenceā€¦ so that Iā€™ll say what does what exactly.

Before I read this reply, I started testing Appdaemon for this. I wanted to improve on that, anyway, so perhaps it can help as well. I really like your variable.set_variable approach, as I must have missed variables completely, and usually always used things such as input_datetime or similar instead of just variablesā€¦

Have you tried my solutionā€¦?

Not yet. I am sure that it works, but I meant that I donā€™t just want to copy and paste it without understanding it. So Iā€™ll try it when I have some time to understand what everything in your solution does, and then implement it :slight_smile:

I think you are thinking I am someone else. I didnā€™t post anything for you to copy and paste. I only gave you a suggestion on what to try.

Oh yeah, sorry. I have indeed tried your solution, but there must be something wrong with my automation. It worked the first time (comparing two input_datetime entites that had both date and time), but then the input_datetime I used to store the ā€œvacuum startedā€ value would always be 1970-01-01 00:00:00, even though the actual start value should have been written to it.

I am not sure why this is, but also not sure whether I should (or want to) realize this with Home Assistant alone, or just AppDaemon.

I just finished another test. I must still be setting the time in Home Assistant incorrectly; when I write to sensor sensor.adt_medusa_start when the entity starts vacuuming via AppDaemon, then use this sensor with this template value_template: '{{ (as_timestamp(now()) - states.sensor.adt_medusa_start.attributes["letzter_start"]) | timestamp_custom("%H:%M:%S", false) }}', it seems like I do get the correct value. When I do the same in Home Assistant (with has_date and has_time), I donā€™t get a valid value.

Iā€™ll keep looking into this, though I am not sure whether itā€™d be better to do this within AppDaemon or Home Assistant alone.

Just to be 100% sure.
The X (the item in question) starts, you write the current time/date to an input time/date ? Say X_timedate_start
Then when it stops you either write the stop time to another, subtract the two, then as you say convert the result with timestamp_custom("%H:%M:%S", false) ?
That should be fine
Can you post your current automations ?

Also post the state and attributes of the sensor.adt_medusa_start entity.

@Mutt this is not about when it stops. I would like to know for how long it has currently been running while it is running. This entity should update every x seconds while the trigger entity is running. When it stops, it can reset. The entity itself will (finally) report this it stops (as attributes clean_start and clean_stop, but that is too late. I want to observe the current run time so that I can estimate how much longer it will run for. If runtime was always about the same, I could use a timer, but depending on the floor the vacuum is on, the runtime can vary, and there are other factors, such as open/closed doors, which have an impact on how long the device will run for.

My code below might be all a bit messed up since I started trying to do this in AppDaemon AppDaemon, because I also changed the automation all the time. But what I originally had in there was an automation to write now() to an input_datetime when the entity would change to cleaning. Then itā€™d check every 30s whether the entity was still cleaning, and if so, it would update the ā€œresult sensorā€ (input_datetime.medusa_laufzeit with this template '{{ (as_timestamp(now()) - states.sensor.adt_medusa_start.attributes["letzter_start"]) | timestamp_custom("%H:%M:%S", false) }}'.

input_datetime:
  medusa_startzeit:
    has_date: true
    has_time: true
    name: Medusa letzter Start
  medusa_laufzeit:
    has_date: true
    has_time: true
    name: Medusa Laufzeit

automation:
  alias: "[Vacuum] Medusa Laufzeit"
  trigger:
    - platform: time_pattern
      seconds: 30
  condition:
    condition: state
    entity_id: "vacuum.medusa"
    state: "cleaning"
  action:
    - service: homeassistant.update_entity
      entity_id: sensor.medusa_laufzeit
  alias: "[Vacuum] Medusa Startzeit"
  trigger:
    - platform: state
      entity_id: vacuum.medusa
      to: "cleaning"
  action:
    - service: input_datetime.set_datetime
      entity_id: input_datetime.medusa_startzeit
      data_template:
        datetime: '{{ state_attr("sensor.adt_medusa_start", "letzter_start") }}'

sensor:
  - platform: template
    sensors:
      medusa_laufzeit:
        friendly_name: "Medusa Laufzeit"
        unit_of_measurement: "h"
        value_template: '{{ (as_timestamp(now()) - states.sensor.adt_medusa_start.attributes["letzter_start"]) | timestamp_custom("%H:%M:%S", false) }}'

@finity sensor.adt_medusa_start has on/off (just because I needed an actual state, it is not used for anything), and the attribute letzter_start: 1591286239.565594 (this is the last value it had, but basically it is the equivalent of what AppDaemon returns for time.time(), which should be a regular timestamp).

Both input_datetime entities that I created within Home Assistant now have both date and time.

I believe you have your automation section messed up. try this:

automation:
  - alias: "[Vacuum] Medusa Laufzeit"
    trigger:
      - platform: time_pattern
        seconds: 30
    condition:
      condition: state
      entity_id: vacuum.medusa
      state: "cleaning"
    action:
      - service: homeassistant.update_entity
        entity_id: sensor.medusa_laufzeit
  
  - alias: "[Vacuum] Medusa Startzeit"
    trigger:
      - platform: state
        entity_id: vacuum.medusa
        to: "cleaning"
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.medusa_startzeit
        data_template:
          datetime: '{{ state_attr("sensor.adt_medusa_start", "letzter_start") }}'

one question I have is why are you using ā€œfalseā€ for the custom timestamp? that will convert the timestamp output to UTC time (wonā€™t respect your timezone). So unless you know you need it I would remove it (defaults to true) or just set it to true explicitly.

Otherwise I think every thing else looks ok.

Obviously I donā€™t have you entities along with their attributes so if there is something messed up there I wouldnā€™t be able to see it.

Thank you. I believe the automation works correctly nowā€¦ However, it also seems that homeassistant.update_entity does not do anything.

{{ states("sensor.medusa_laufzeit") }} => 00:00:00
{{ (as_timestamp(now()) - states.sensor.adt_medusa_start.attributes["letzter_start"]) | timestamp_custom("%H:%M", false) }}=> 00:03

The sensor should output this as well (I changed "%H:%M:%S" to "%H:%M").

To be honest, I only use false because I had seen it done like this in an example somebody gave me when trying something else with time values. I guess in this case it does not matter (because as long as both values are treated like this, I still get the correct difference between the time values), but I will make sure to use it for anything else time related Iā€™ll do in the future :slight_smile:

update: ok, there must be something with the automationā€¦ when I manually update the sensor via `homeassistant.update_entity it will show the correct stateā€¦

i missed one more thing.

the time_pattern trigger wonā€™t do what you think it will the way you have it set up.

try it like this:

trigger:
  - platform: time_pattern
    seconds: "/30"
1 Like

Dā€™oh! Of courseā€¦ I canā€™t believe I missed this for so long. Thank you so much!

I will test it this afternoon when the robot runs (instead of now manually changing states). But since everything else is correct, I am sure that it will work now =)