I would like to understand why an automation was triggered in the first place. It seems, contexts are just made for this. But I have not found out how this can be done.
My concrete use case is:
When I come home, this triggers a device tracker state change.
This state change triggers an automation to disarm the alarm.
The changed alarm state triggers an automation to notify me that “the alarm was disarmed”.
I want to notify “the alarm was disarmed, because someone came home”.
Is there a way? Contexts seem to be very limited when used in templates. But the info I want to use is there in the logbook…
Thanks for the suggestion! I checked and I don’t think there’s something useful in there. It says which state has changed (alarm) but that is not the original state change I am after (the device tracker)…
Here is what is available in the automations trigger variables:
The trigger should include what the device tracker is.
What is your automation?
Perhaps you can use the trigger I’d as the persons name kind of like what the example at the bottom is doing
As the actual trigger to change the alarm state is before that, I don’t think it will be easy to do this in a consistent way. I think you should set a helper in the automation that sets the alarm state. Because in the automation after that, you would only see that the automation to set the alarm state was the context. But that is not what you wanted to know. Then, in the notification, if the context was the automation to change the alarm state, you can get at the details of why the automation did it.
Also, besides the user, the context refers to things in the database that I think are not readyly accessable for the automation itself, but I may be mistaken. Otherwise, traversing up parent might get you the information you need.
So even if you use trigger id’s in the previous automation, it is still needed to check if the automation that set the helper has recently fired. Unless the user information shows it was a manual operation.
Thank you all for your hints and suggestions. I’m known to exaggerate, so I created a universal helper for such questions. My newest template sensor stores a cache of recently fired events with their context IDs.
YAML code
template:
- trigger:
- platform: event
event_type: '*'
sensor:
- name: Event Cache
unique_id: event_cache
state: listening
attributes:
timeout: >
{{ this.attributes.get('timeout', '0:00:10') }}
cache: >
{# GET CURRENT STATE OF CACHE #}
{% set cache = this.attributes.get('cache', {}) %}
{# DO NOT UPDATE CACHE FOR CHANGES OF THE VERY CACHE #}
{# ALSO DO NOT OVERWRITE EARLIER EVENTS #}
{% if trigger.event.data.get('entity_id') == this.entity_id
or trigger.event.context.id in cache %}
{{ cache }}
{% else %}
{# USE A MACRO TO FLEXIBLY AND SAFELY CONVERT OBJECTS TO DICTIONARIES #}
{% macro object_as_dict(object, type=None) %}
{# THE STATE_CHANGED EVENT MIGHT CONTAIN UNSAFE ATTRIBUTES #}
{% if type == 'event' and object.event_type == 'state_changed' %} {% set object = {
'event_type': 'state_changed',
'data': {
'entity_id': object.data.entity_id,
'old_state': object_as_dict(object.data.old_state, type='state') | from_json,
'new_state': object_as_dict(object.data.new_state, type='state') | from_json,
},
'time_fired': object.time_fired.isoformat(),
'context': object.context.as_dict(),
} %}
{# A STATE CAN CONTAIN UNSAFE ATTRIBUTES #}
{% elif type == 'state' %}
{% set object = object.as_dict() %}
{% set object = {
'entity_id': object.entity_id,
'state': object.state,
'attributes': object_as_dict(object.attributes if 'attributes' in object else {}, type='attributes') | from_json,
'last_changed': object.last_changed,
'last_updated': object.last_updated,
'context': object.context,
} %}
{# HANDLE ONLY SELECTED ATTRIBUTES #}
{% elif type == 'attributes' %}
{% set object =
dict(dict(
**{'icon': object.icon} if 'icon' in object else {}),
**{'friendly_name': object.friendly_name} if 'friendly_name' in object else {})
%}
{# JUST USE as_dict() PER DEFAULT #}
{% else %}
{% set object = object.as_dict() %}
{% endif %}
{{ object | to_json }}
{% endmacro %}
{# EXTEND CURRENT CACHE WITH NEW EVENT AND STRIP OLD CACHE ENTRIES #}
{% set cache =
dict(
dict(
cache,
**{
trigger.event.context.id: object_as_dict(trigger.event, type='event') | from_json
}
).items()
| selectattr(
'1.time_fired',
'>=',
( utcnow()
- this.attributes.get('timeout', '0:01:00')
| as_timedelta
).isoformat()
)
)
%}
{{ cache }}
{% endif %}
recorder:
exclude:
entities:
- sensor.event_cache
The sensor can then be used to trace the event that actually triggered something.
YAML code
variables:
cause: >-
{%- set cache = state_attr('sensor.event_cache', 'cache') or {} %}
{%- set ctx = trigger.to_state.context %}
{%- set ctx = cache
. get(ctx.parent_id or ctx.id, {})
. get('context', ctx) %}
{%- set ctx = cache
. get(ctx.parent_id or ctx.id, {})
. get('context', ctx) %}
{%- set ctx = cache
. get(ctx.parent_id or ctx.id, {})
. get('context', ctx) %}
{%- set evt = cache.get(ctx.id, trigger) %}
{%- if evt.event_type == 'state_changed' -%}
{%- set state = evt.data.new_state -%}
{{ state.attributes.friendly_name }} wechselte zu {{ state.state }}
{%- elif evt.event_type == 'call_service' and evt.context.user_id is not
none -%}
Manuelle Änderung
{%- else -%}
unbekannt ({{ evt | string }})
{%- endif -%}
It’s not perfect yet and anyways far from ideal (as it’s a template), but it seems to work so far. I also opened a PR to put the information in the docs, that the context is not accessible by the user.
Then you should do that in your second step because it is monitoring the device_tracker’s state.
This state change triggers an automation to disarm the alarm.
Your third step is monitoring the alarm state, not the device_tracker.
The changed alarm state triggers an automation to notify me that “the alarm was disarmed”.
Therefore that automation’s context object will be for the alarm state, not the device_tracker.
That hasn’t been my experience, provided you understand how it works and why. context
What I see here is that you submitted changes to the documentation despite only having a limited understanding of the context object’s purpose and how to use it (as it was intended) to determine who or what triggered the automation.
This may help to shed more light on how one can use the context object.
As to the constructive hint about in which automation to put the notification: I understand that the information is readily available in the first automation. However, I also notify for all other reasons. So, if I were to put the notifications in all places where the reason is readily available, that would cause a lot of duplication. Also, at least the manual disarm from the UI changes the alarm state directly. So, I would still have the notification in the second automation, together with some filtering based on the context. It might be opinionated, but I don’t like that.
Thanks for pointing me to your previous post. I had read this earlier and found it unfortunate that such information is only in some unauthoritative forum post and not in the documentation itself. That’s why I try to clarify the official documentation.
I am completely open to improvements. So, please, feel free to review the PR. I have even considered to include your thruth table. But, to my experience, your table is not complete. Since some triggers do not contain a context (e.g. time triggers), the parent_id will be None in the automation actions.