Many of us struggle with automations that they use the last_changed attribute.
You know, a restart, a disconnection breaks them.
In search of a solution I came to the idea of building a blueprint to let me (and potentially other users) get the real datetime of the last change of an entity, with the help of a helper.
Prerequisites
An input_text helper to be used as our “saver” of the entities.
How it works
When setting up an automation, the blueprint asks for the entities (not more than 4 because of the 255 characters limit of the input_text state) and the helper from above.
On a state change of these entities the automation runs, saving the entity_id, state before change, last_changed timestamp before change, state after change and last_changed timestamp after change.
It automatically ignores the states: “unknown”, “unavailable”, None.
The format is as follow:
<entity_id1>,<from_state.state>=<from_state.last_changed>,<to_state.state>=<to_state.last_changed>&<entity_id2>,<from_state.state>=<from_state.last_changed>,<to_state.state>=<to_state.last_changed>
blueprint:
name: Real "last_changed" saver
description: Saves the from and to states, with the timestamps, from entities given in an input_text helper, surviving restarts and disconnections.
domain: automation
input:
source_entities:
name: Entities (max. 4, limit to 255 charactes)
description: The watched entities to pursue their real last_changed state & timestamps
selector:
entity:
multiple: true
target_list:
name: Input_text helper
description: In this list helper will be saved all the required info
selector:
target:
entity:
domain: input_text
variables:
inlist: !input 'target_list'
input_list: '{{ inlist.entity_id }}'
inent: !input 'source_entities'
trigger:
- platform: state
entity_id: !input 'source_entities'
condition:
- condition: template
value_template: >
{{ states(input_list) != None
and trigger.platform == "state"
and trigger.from_state.state != trigger.to_state.state }}
action:
- choose:
- conditions:
- condition: template
value_template: '{{ trigger is defined and trigger.platform == "state" }}'
sequence:
- service: input_text.set_value
data:
value: >
{% set list =
namespace(l=states(input_list).split('&'),n=[]) %} {% for i in
list.l %}
{% if "," in i
and i.split(",")[0] in inent %}
{% set list.n = list.n + [i] %}
{% endif %}
{% endfor %} {% set list.l = list.n %} {% if not
trigger.entity_id in list.l | join()
and not trigger.from_state.state in ["unavailable","unknown",None]
and not trigger.to_state.state in ["unavailable","unknown",None] %}
{% set list.l = list.l + [trigger.entity_id ~ "," ~ trigger.from_state.state ~ "=" ~ trigger.from_state.last_changed.replace(microsecond=0) | as_timestamp | int ~ "," ~ trigger.to_state.state ~"="~ trigger.to_state.last_changed.replace(microsecond=0) | as_timestamp | int] %}
{% else %}
{% set list.n = [] %}
{% for n in list.l %}
{% set en = n.split(',')[0] %}
{% set fs = n.split(',')[2].split('=')[0] %}
{% set fst = n.split(',')[2].split('=')[1] %}
{% set ist = iif( (trigger.from_state.state != fs
and trigger.to_state.state != fs
and not (trigger.to_state.state in ["unavailable","unknown",None]) ), true, false) %}
{% if en == trigger.entity_id
and ( ( not trigger.from_state.state in ["unavailable","unknown",None]
and not trigger.to_state.state in ["unavailable","unknown",None] )
or ist ) %}
{% set list.n = list.n + [trigger.entity_id ~ "," ~ iif(not ist, trigger.from_state.state, fs) ~ "=" ~ iif(not ist,n.split(',')[2].split('=')[1],fst) ~ "," ~ trigger.to_state.state ~"="~ trigger.to_state.last_changed.replace(microsecond=0) | as_timestamp | int] %}
{% else %}
{% set list.n = list.n + [n] %}
{% endif %}
{% endfor %}
{% set list.l = list.n %} {% endif %} {{ list.l | join("&") }}
target: !input 'target_list'
default: []
mode: queued
max: 10