Add more information to "context" objects

Right now context only contains an id, user_id, and parent_id. It would be very useful to have additional information available in context so that automations may react not only to a state changing but also what caused the state to change. To do this context could be expanded to include the following:

root_entity_id: the entity id that started the overall set of state changes / events
entity_chain: list of entity ids starting from root_entity_id that led to this current context

With these two fields we could support the following use cases:

  1. Only trigger an automation if a state change was by an interaction with a particular physical device. Ex: Disable motion lighting auto-off automation when someone changes light level from a physical switch but not when light level is changed by other automations.
  2. Only trigger an automation if a state change was caused by a particular other automation or script. Ex: Ignore light brightness change if the change was triggered by a circadian lighting automation.

The ability to customize context would also be useful. For example if an automation could add arbitrary data to context (like event_data but available to all things that change as a result of the automation’s actions). That would allow for significantly smarter automations that can add context about why changes are happening so that downstream consumers of changes can make better decisions.

I think this is a good idea and can greatly simplify the way to do certain things.

However, I have found that the behaviour of the current context data is not very reliable.

This user explains it quite well here.

Apparently, the way to group contexts so that they share the same id is done based on a time window of 5s. During this time, neither the id, nor the user_id, nor the parent_id, change.

This can be consistent if the parent_id and user_id are the same. But if they change, both should be respected, as well as creating a new id.

Examples with “state_changed” event:

EXPECTED BEHAVIOUR:

  • Switch on from lovelace.
    New ID, Null parent_id, current user user_id.
  • Switch off from the device itself (hardware switch).
    New ID, Null parent_id, Null user_id.
  • Switch on from an automation not related with previous actions.
    New ID, Trigger parent_id, Null user_id.

CURRENT BEHAVIOUR (Less than 5 seconds between actions):

  • Switch on from lovelace.
    New ID, Null parent_id, current user user_id.
  • Switch off from the device itself (hardware switch).
    Same ID, Null parent_id, Same user_id.
  • Switch on from an automation not related with previous actions.
    Same ID, Null parent_id, Same user_id.

So I think your proposal is very interesting, but I also think the current behaviour should be fixed first before adding new functions.

I wasn’t aware of that quirk, thanks for bringing it up, that greatly limits the usefulness of context! If that’s how it behaves right now then IMO it’s essentially broken in practice and I think that bug needs to be fixed.

Yes, I think so. Using context for anything could be completely unpredictable because of this way of grouping the events.

Thank you for opening the issue.

I was testing to try to understand how the context works, with the purpose of using it in various automations. But after seeing this problem I have discarded it for the moment.

I have learned that this would allow me to set an input variable so I could know the last time a switch was for example turned off manually -

condition:
  - "{{ trigger.to_state.context.id != none }}"
  - "{{ trigger.to_state.context.parent_id == none }}"
  - "{{ trigger.to_state.context.user_id == none }}"

And in my use case that is sufficient. But WHY do any of us have to learn these things by stumbling through them? How come all of the low-level nitty-gritty varables available within any and all automations be documentated?

1 Like

Just to continue the chain of questions:
Why the context won’t be passed over to Node RED?

1 Like

In order to add a little more color for people that might be useful to them my use case to determine how a lightr was turned off (if it is off, otherwise N/A is shown as I do not care how it ws turned on) in automations or code is actually determined by checking a state sensor I have set up in configuration yaml. Here is an example of one of them (so you can see if it was turned off physically, by the a dashboard ui click, or by an automation -

#
# Bathroom Lights (for accessing these and with the last_changed value):
# {{ states('sensor.bathroom_lights_off_context') }}
# {{ states.sensor['bathroom_lights_off_context'].last_changed }}
#
# 1. Track the 'off' context changing (has to be triggered even if turning on,
# so any change at all will cause logic to be triggered if subsequent objects
# already set as "physical" will still be updated)
#
template:
  - trigger:
      - platform: state
        entity_id: light.bathroom_shelly_1_relay
    sensor:
      - name: "Bathroom Lights Off Context"
        state: >
          {% set c_id = trigger.to_state.context.id %}
          {% set c_parent = trigger.to_state.context.parent_id %}
          {% set c_user = trigger.to_state.context.user_id %}
          {% if states('light.bathroom_shelly_1_relay') == 'on' %}
            n/a
          {% elif c_id != none and c_parent == none and c_user == none %}
            physical
          {% elif c_id != none and c_parent == none and c_user != none %}
            dashboard_ui
          {% elif c_id != none and c_parent != none and c_user == none %}
            automation
          {% else %}
            unknown
          {% endif %}
        unique_id: bathroom_lights_off_context

Then I can use the above to set a variable with the exact time (so it is saved upon a reboot) of when it was turned off, if done manually:

alias: >-
  Bathroom Lights Off Context -> If Done Manually -> Set Last Manual Off
  Timestamp
description: >-
  If the bathroom lights are turned off (manually),  then make sure the
  "last_manual_off-bathroom" timestamp is updated
trigger:
  - platform: state
    entity_id:
      - sensor.bathroom_lights_off_context
    to: physical
action:
  - service: input_text.set_value
    data:
      value: >-
        {{
        as_timestamp(states.sensor['bathroom_lights_off_context'].last_changed)
        }}
    target:
      entity_id: input_text.last_manual_off_bathroom
  - service: timer.cancel
    data: {}
    target:
      entity_id: timer.bathroom_light_timer
mode: parallel
max: 1000

So my automations to use motion to turn on lights is only executed if the person had not turned the light off manually within the last 5 minutes. This eliminates the scenario when someone turns off a wall switch light while walking out the room, only to find that it immediately goes back on (how annoying is that!).

Since I have cheap moton sensors, I programmed around issues with that by not using motion sensors to turn on lights but only to have them re-start timers - and I have separate automations that if a timer is (re)started it turns a light on (if not already on) and when the timer expires the light will turn off. It took some tweaking and is a PITA to set up but it does work very well for me. (WAF is 100)

In conclusion of course I did then set up input numnber sliders for how long the timers should be as well as an input select to enable to disbale automations etc. and it is tedious but once set up works very well.

Howeever, the bottom line is - there must be ways ti simplify this for HA users (without having to buy ridiculous presence detectrors that can know your temperature and sense you breathing etc.)

FYI, you should change those == to is and != to is not

          {% set c_id = trigger.to_state.context.id %}
          {% set c_parent = trigger.to_state.context.parent_id %}
          {% set c_user = trigger.to_state.context.user_id %}
          {% if states('light.bathroom_shelly_1_relay') == 'on' %}
            n/a
          {% elif c_id is not none and c_parent is none and c_user is none %}
            physical
          {% elif c_id is not none and c_parent is none and c_user is not none %}
            dashboard_ui
          {% elif c_id is not none and c_parent is not none and c_user is none %}
            automation
          {% else %}
            unknown
          {% endif %}

Also, if you want the user…

          {% set c_id = trigger.to_state.context.id %}
          {% set c_parent = trigger.to_state.context.parent_id %}
          {% set c_user = trigger.to_state.context.user_id %}
          {% if states('light.bathroom_shelly_1_relay') == 'on' %}
            n/a
          {% elif c_id is not none and c_parent is none and c_user is none %}
            physical
          {% elif c_id is not none and c_parent is none and c_user is not none %}
            {{ states.person | selectattr('attributes.user_id', 'defined') | selectattr('attributes.user_id', 'eq', c_user) | map(attribute='name') | list | first | default('dashboard_ui') }}
          {% elif c_id is not none and c_parent is not none and c_user is none %}
            automation
          {% else %}
            unknown
          {% endif %}

Thank you! But -

Why is “is” and “is not” better?

And - how does it know who the user is - if there’s three people ikn a room, anyone could have done it?

Read this for the is and is not
https://stackoverflow.com/questions/3257919/what-is-the-difference-between-is-none-and-none

It knows the logged in user. If the users don’t log in, it won’t know who they are. If you use the mobile app on your phone and you have separate users, you’ll see who pressed the button. Assuming you have a kiosk or tablet on the wall, you’d see the logged in user for the kiosk/tablet.

1 Like

Just push this thread again:
A bug has existed for over a year. Nobody from the developers gives feedback or takes care of it. Specifically, no parent_id is generated for automation triggers (Sun, Template, Time). This means that it is no longer possible to tell whether the trigger came from Home Assistant (Automation) or from the physical device.

Just let me know if I am wrong. But is there another option to determine if the automation was triggered by the device itself?

Is there a way to get to “triggered by” information inside an automation like in the logbook?

Here are some related issues:

Anyone is welcome to fix it, you don’t have to wait for the core development team to fix something you deem a bug. Doug Hoffman put in the fix then marked the PR as a draft and abandoned it. I have no idea why they did that.

Yes, there was a fix. But nobody cared and reviewed it. Now its marked as abandoned automatically because nobody cared. I wish I simply can reopen the pull request but now it’s restricted.