Event trigger for `state_changed` based on the domain of the entity whose state changed

Hi! I read the docs for event triggers, for the state_changed event type, and for state objects (ibid., link therein).

Together these led me to believe that I should be able to write a trigger like the one in the simple example automation below. Basically, my understanding from the docs is that an event trigger may specify an event_data field, whose contents must match against the contents of the data field of a candidate event in order for the trigger to fire. The data of state_changed events consists of three fields: entity_id, old_state, and new_state. The latter two are state objects, which themselves contain a variety of fields, and in this case I want to match on the value of the domain field of the old_state object.

In my testing, however, this automation never triggers, even though I can confirm elsewhere that a person’s state has changed (e.g. from “Home” to “Away”). Does anybody know what I’m doing wrong?

I believe I could rewrite this as an unconditional event trigger (no event_data field) with a separate condition (probably a template) that examines the domain of the old_state, but it would seem kind of wasteful to trigger on every single state_changed event, no? And in any case, I would still like to understand why this event_data-based approach doesn’t work; if it doesn’t work by design, then perhaps the documentation could be improved to clarify this. Or more likely I’m just being dumb! Thanks for any pointers!

alias: "Notify: person changed zone"
description: ""
trigger:
  - platform: event
    event_type: state_changed
    event_data:
      old_state:
        domain: person
    variables:
      name: "{{event.data.old_state.name}}"
      whence: "{{event.data.old_state.state}}"
      whither: "{{event.data.new_state.state}}"
condition: []
action:
  - service: notify.notify
    data:
      title: "{{name}} is on the move."
      message: "{{name}} has moved from {{whence}} to {{whither}}."
mode: single

I am interested in this too

Go to Developer Tools > Events, enter state_changed in ‘Event to subscribe to’ then click 'Start Listening '.

Various events will scroll by and, based on the results from my system, domain is not present in old_state or new_state. That explains why the Event Trigger you created never triggers.

Thanks, @Taras! That must explain it. Is the documentation incorrect, then? Regarding the state_changed event type, it says:

It contains the entity identifier and both the new_state and old_state of the entity as state objects.

And then the documentation for “State Objects” quite clearly says:

state.domain: Domain of the entity. Example: light.

If the new_state and old_state fields of the state_changed event type are actually only partial state objects, I feel like the documentation should say so, and furthermore list which subset of fields will actually be present.

Or perhaps it’s a bug that state.domain, state.object_id, and other documented fields are not being populated in the state objects?

Or, perhaps it’s the case that some of the fields in the “State Objects” documentation are “optional” and not guaranteed to be present on all states? If so then I feel like that documentation could be better about highlighting which fields are optional, and furthermore what sorts of states might be expected to have them and which not. It’s all pretty confusing right now unless you go and sniff events empirically, which is kind of inconvenient to do for things like person state (I guess I would have to walk down the street or something while subscribing to “state_changed” in the dev tools on my phone).

EDIT: I suppose a fourth possibility is that some of those documented state fields are only present on state objects as returned by the states() function inside a template, which I don’t think I can use in the event_data field of my event trigger. That, too, should be documented if so, i.e., which fields are actually present in the raw state data, and which are “derived” and only present in the objects returned by states() when evaluating a template. Or perhaps the implementation of the event trigger platform should be updated to include the “full” state object as returned by states() when testing users’ event_data fields.

I’d be happy to take a crack at improving the docs myself if it were clear what the actual intention is.

It took some digging but I tracked down the author and date of that specific section of the documentation indicating old_state and new_state contain state objects.

Maybe it was correct at the time but today this is what I see:

event_type: state_changed
data:
  entity_id: sensor.dishwasher_energy_power
  old_state:
    entity_id: sensor.dishwasher_energy_power
    state: "11"
    attributes:
      state_class: none
      unit_of_measurement: W
      device_class: power
      friendly_name: dishwasher ENERGY Power
    last_changed: "2022-12-12T16:46:13.220469+00:00"
    last_updated: "2022-12-12T16:46:13.220469+00:00"
    context:
      id: 01GM3KNM74FFPHEFK0SVJCEB23
      parent_id: null
      user_id: null

If we compare it to a State Object’s properties (as per its documentation), it doesn’t include the following:

domain
object_id
name

So it appears to contain most, but not all, of a State Object’s properties.


Effectively, the reply I posted above solves the problem: it didn’t trigger because there’s no domain property.

Feel free to refine the documentation. Whatever changes you make will be vetted by a developer. If there’s any question about the need for the modification, you can point them to this topic.

1 Like

Cool, thanks for digging :slight_smile:. I wonder what’s best… to document the partial subset of properties, or to actually change the behavior of the state_changed event type to include all properties?

Changing documentation is within anyone’s reach.

Changing how the software works requires python development skills, familiarity with Home Assistant’s architecture, etc.

For anyone else trying to do this (trigger based on the domain of the entity whose state changed), here’s what worked for me.

trigger:
  - platform: event
    event_type: state_changed
condition:
  - condition: template
    value_template: >-
      {{trigger.event.data.entity_id.startswith('person.') and
      trigger.event.data.old_state is not none}}

Unfortunately it seems to be unavoidable that the trigger will fire on all state changes in the system, and it’s also unfortunate that the only way to test the domain of the entity is by using startswith(...) on the entity_id string :frowning:

Which means most of the time it will be triggered unnecessarily.

How many person entities do you have and does the quantity change often?

If the answer is ‘not many and no’ then just list the entities in a State Trigger.

How many person entities do you have and does the quantity change often?
If the answer is ‘not many and no’ then just list the entities in a State Trigger.

Yes, that is a good point. Originally I had hoped to avoid hard coding the list of entities by triggering on domain, but since that’s not possible, it’s probably best to go with a State Trigger that hard codes the entities.

It is not clear from the documentation for the State Trigger (or mentioned at all!) that I could access state objects corresponding to the previous and new states (analogous to old_state and new_state in the state_changed event type), but by checking traces empirically I determined that these are in fact available as trigger.from_state and trigger.to_state.

Here is a worked example that functions for me, without triggering on every single state change in the system :slight_smile:

alias: "WIP: Notify when person moves"
description: ""
trigger:
  - platform: state
    entity_id:
      - person.yours_truly
      - person.my_better_half
    to: null
condition:
  - condition: template
    value_template: >-
      {{trigger.from_state is not none and trigger.from_state.state !=
      trigger.to_state.state}}
action:
  - service: notify.notify
    data:
      title: "{{state_attr(trigger.entity_id, 'friendly_name')}} is on the move."
      message: >-
        {{state_attr(trigger.entity_id, 'friendly_name')}} has moved from
        {{trigger.from_state.state}} to {{trigger.to_state.state}}.
    enabled: true
mode: queued

It’s documented here, for all trigger types.


FYI
You can reference the entity’s friendly_name directly from the tiggervariable like this:

action:
  - service: notify.notify
    data:
      title: "{{ trigger.to_state.name }} is on the move."
      message: >-
        {{ trigger.to_state.name }} has moved from
        {{ trigger.from_state.state }} to {{ trigger.to_state.state }}.

Same problem here. In that case with the ‘update’ domain. There are a bunch of update entities. To do an automation that enumerate all the update entities is difficult, especially considering that new update entities appear every month.

I have the following automation that is actually working well:

alias: ActualizaciĂłn Update Disponible
description: >-
  Comprueba si hay una nueva versiĂłn de alguna de las entidades de tipo Update.
  Mejorar cuando se pueda para que el trigger no se active en cada cambio de
  estado
trigger:
  - platform: event
    event_type: state_changed
condition:
  - condition: template
    value_template: >-
      {{ trigger.event.data.new_state.domain == 'update' and
      trigger.event.data.new_state.state == "on" and
      trigger.event.data.old_state.state == "off" }}
action:
  - service: notify.telegram_casa
    data:
      message: >-
        🏡 Actualización disponible: {% if
        trigger.event.data.new_state.attributes['title'] is none %} {{
        trigger.event.data.new_state.attributes['friendly_name'] | replace('_',
        ' ') }} {% else %} {{ trigger.event.data.new_state.attributes['title'] |
        replace('_', ' ') }} {% endif %} -> {{
        trigger.event.data.new_state.attributes['latest_version'] }}
mode: parallel
max: 50

But I think its performance would be greatly improved if we can have the ‘domain’ attribute in state objects, as documentation “says”.

It’s also very busy because it’s checking each and every entity’s state-change that occurs in Home Assistant’s State Machine.

states.update is the basis for a template that can identify all entities in the update domain.

I know. I have this automation temporarily until we have a better way to do it. It would be great adding the ‘domain’ attribute to the state objects.

Yeah, but i can’t use it in the trigger of an event automation :frowning. Or at least I don’t know how

Either create a Template Sensor and use it in a State Trigger or create a Template Trigger.

I have tested with the following template trigger:

{{ states.update | selectattr('state', 'equalto', 'on') | list | length > 0 }}

But unfortunatelly it only works when it pass from 0 to 1. If there is a second update, the trigger won’t fire :frowning:

Create a Template Sensor reporting the number of update entities that are on. Use that Template Sensor in a State Trigger. Easy-peasy.

What about that?

template:
  - sensor:
      - name: Actualizaciones disponibles
        unique_id: template_sensor_actualizaciones_disponibles
        state: "{{ states.update | selectattr('state', 'equalto', 'on') | list | length }}"
        icon: "mdi:update"

alias: ActualizaciĂłn Update Disponible
description: Comprueba si hay una nueva versiĂłn de alguna de las entidades de tipo Update.
trigger:
  - platform: state
    entity_id: sensor.actualizaciones_disponibles
condition:
  - condition: template
    value_template: >-
      {{ trigger.from_state.state | int(99999) < trigger.to_state.state | int(0) }}
action:
  - service: notify.telegram_casa
    data:
      message: >-
        *ActualizaciĂłn disponible*

        {% for act in states.update | selectattr('state', 'equalto', 'on') |
        list -%}- {% if act.attributes['title'] is none %}{{ act.name |
        replace('_', ' ') }}{% else %}{{ act.attributes['title'] | replace('_',
        ' ') }}{% endif %}: {{ act.attributes['latest_version'] }}

        {% endfor %}
mode: single

Example:

imagen

It sure would be nice if we could…
Trigger off event state_changed when it matches a device_class or some other attribute of the states! Something like…

platform: event
event_type: state_changed
event_data:
  old_state:
    attributes:
      device_class: battery

(which doesn’t work)

Here’s my event:

event_type: state_changed
data:
  entity_id: sensor.4_in_1_multisensor_battery_level
  old_state:
    entity_id: sensor.4_in_1_multisensor_battery_level
    state: "89"
    attributes:
      state_class: measurement
      unit_of_measurement: "%"
      device_class: battery
      friendly_name: Landing 1 Sensor

@sermayoral

Do you know if there has anything changed in the meanwhile?
I have exactly the same need. An automation for updates.

But without an initial filter the automation gets triggered each second.

Thanks.