Multi-zone single automation

I would like to fashion a single automation that fires a notification, with the name of the Zone, to keep track of when:

  • a Person leave home,
  • a Person enters a named Zone,
  • a Person exits a named Zone, and
  • a Person returns home.

This is proving surprisingly difficult.

It is extremely straightforward to fashion a notification that triggers when a Person enters or leaves the “Home” zone. What is proving difficult are the zone-to-zone notifications – when a Person is “away,” their state is changed to “not_home”, so it seems possible to trigger on that condition – however that is not working for me.

Has anyone had any luck creating an automation like this? Within similar topics, I see people hardcoding conditions for every single zone into their automation, which admittedly works, but is inelegant and difficult to scale. Ideally I think we’d all like something that took advantage of the multi-device capabilities of the Person, as well as scaled effectively over any number of Zones.

I think there are dozens of solutions available in this forum. The hard part is finding them!

Basically, don’t use zone triggers, use a state trigger, because a person entity’s state changes to the friendly name of a zone it has entered (or 'not_home' when not in any zone, or 'home' when in the Home zone.) It typically goes something like this:

- trigger:
  - platform: state
    entity_id:
    - person.PERSON1
    - person.PERSON2
    ...
  condition:
  - condition: template
    value_template: >
      {{ trigger.to_state is not none and
         (trigger.from_state is none or
          trigger.to_state.state != trigger.from_state.state) }}
  action:
  - service: notify.NOTIFIER
    data_template:
      message: >
        {% set name = trigger.to_state.name %}
        {% set zone_map = {'home': 'Home', 'not_home': 'Away'} %}
        {% set from = trigger.from_state.state 
                      if trigger.from_state is not none
                      else "unknown" %}
        {% set from = zone_map.get(from, from) %}
        {% set to = trigger.to_state.state %}
        {% set to = zone_map.get(to, to) %}
        {{ name }} moved from {{ from }} to {{ to }}
5 Likes

Phil, this is easily the most elegant way to accomplish this that I’ve seen yet. Thanks for taking the time to put this together.

Let me say back what this script does, to make sure I understand:

  • The automation is triggered on the state of the set of persons defined in entity_id.
  • The automation will continue as long as the state is not none, and as long as the to and from conditions are not identical. (why is this? to avoid errors when starting up?)
  • The automation then builds a message:
    • sets the name string from the Person who triggered the automation
    • builds a dict of zone_map to normalize the display of the Home and Away states (to avoid displaying “not_home”?)
    • sets the “from” string to be the state of the Person, before the script triggered. It then runs a get against the zone_map dict to capitalize “Home” and change not_home to “Away”. (What’s “unknown” for in this?)
    • sets the “to” string to be the state of the Person, as the script triggered. It then also runs a get against the zone_map dict to capitalize “Home” and change not_home to “Away”.
    • and then finally it assembles all the strings in a data_template.

Is that about right?

1 Like

This kind of example should be in the official docs. Nice!

1 Like

It’s triggered whenever any of the listed person entities cause a state_changed event, which will happen whenever their state, or any of their attributes, change.

The test for none is to avoid running the actions when an entity is removed (which often happens with entities that can be “reloaded”.) This is just a “best practice” even if you pretty much know that won’t happen with a given type of entity (because you never know how HA might change in the future.)

The check that the old state and new state are different is to avoid running the actions if just an attribute (such as GPS location) changes.

Yes. Also works well for other languages (by, of course, adjusting the values in the dict.)

When the person entity is first created, old_state will be none. (This can also happen if the entity is “reloaded”. Before the reload new_state will be none, and after the reload old_state will be none.) This is just one way to handle that scenario.

Everything under message is part of a single template. The last line is just one part of it.

1 Like

This helps so much. Thank you. Last dumb question: I’d like to place a map within the notification. Using this in the notification payload isn’t working, however:

latitude: '{{trigger.to_state.attributes.latitude}}'
longitude: '{{trigger.to_state.attributes.longitude}}'

What would you use to place the lat/long of the trigger person into the data_template for the notification?

Please post your whole automation, not just a snippet. It’s often the context that is the problem.

Here’s what I have so far. It all works fine, until I add the code block with the map.

- id: supertracker
  alias: Location Change
  description: Track changes in location from zone to zone
  trigger:
  - platform: state
    entity_id:
    - person.PERSON1
    - person.PERSON2
  condition:
  - condition: template
    value_template: "{{ trigger.to_state is not none and (trigger.from_state is none or trigger.to_state.state != trigger.from_state.state) }}"
  action:
  - service: notify.NOTIFYPLATFORM
    data_template:
      title: "🌎 {{trigger.to_state.name}} Location"
      message:>"{% set name = trigger.to_state.name %} 
      			{% set zone_map = {'home': 'Home', 'not_home': 'Away'} %}
      			{% set from = trigger.from_state.state
					     if trigger.from_state is not none            
        			 else 'unknown' %}
        		{% set from = zone_map.get(from, from) %}
        		{% set to = trigger.to_state.state %}
        		{% set to = zone_map.get(to, to) %}
        		{{ name }} moved from {{ from }} to {{ to }}."
      data:
        push:
          category: map
        action_data:
          latitude: "{{trigger.to_state.attributes.latitude}}"
          longitude: "{{trigger.to_state.attributes.longitude}}"
          shows_points_of_interest: true
        apns_headers:
          apns-collapse-id: location```

At the very least, this is a problem:

      message:>"{% set name = trigger.to_state.name %} 
      			{% set zone_map = {'home': 'Home', 'not_home': 'Away'} %}
      			{% set from = trigger.from_state.state
					     if trigger.from_state is not none            
        			 else 'unknown' %}
        		{% set from = zone_map.get(from, from) %}
        		{% set to = trigger.to_state.state %}
        		{% set to = zone_map.get(to, to) %}
        		{{ name }} moved from {{ from }} to {{ to }}."

Either use multi-line YAML or don’t. You can’t have it both ways.