One automation instead of 44 - Replacing entity_id with template?

Hi,

For days now I’ve been reading threads and the documentation and trying out code in the Developer Tools and VS editor.
I have a bunch of covers that I want to control with local wall switches which I have as binary sensors, doubled, since I have one binary sensor for the UP key and one for the DOWN key, for each local switch. I want the cover to open if it’s closed and up press UP, if you press UP again it should stop. Same for the closing action.
Using the assumed states for the covers should be enough to achieve this.

However, instead of writing 44 rules which are the same only changing the entities, I’d like to try and make one or two rules where it’s dynamic. When a bynary sensor is pressed, it would use a list to match the correspondent cover.
I’m in the early stages of trying to make this rule, and have already been stumped by not being able to make the trigger entity_id accept a list. From what I’ve seen and tested, this should work? I’ve tried using a single line with quotes, returning a comma separated line instead of a list, but VS always seems to give me “String does not match the pattern”.
What am I doing wrong? Any better suggestions on how to achieve this automation?

- id: "wallswitches_up"
  alias: "Wall Switch Estores e Blackouts UP"
  trigger:
    - platform: state
      entity_id: >
        {{ states.binary_sensor | map(attribute='entity_id') | select("search", "_up") | list }}
      to: "on"
  mode: paralell
  condition:
    - alias: "Lockdown mode check"
      condition: state
      entity_id: input_boolean.lockdown
      state: "off"
  action:
    - choose:
        # IF 
        - conditions:
            - condition: state
              entity_id: cover.estore_cozinha_jardim
              state: "open"
          sequence:
            - service: cover.stop_cover
              target: cover.estore_cozinha_jardim
      # ELSE 
      default:
        - service: cover.open_cover
          target:
            entity_id: cover.estore_cozinha_jardim

Thanks!

I am trying to understand the use case. But are you saying if any of the the binary_sensors marked with _up are ‘on’, then you want to trigger the automation?

Or are you trying to trigger a different entity open/stop/close based on the trigger entity?

EDIT: REmoved wrong code. :slight_smile:

1 Like

The rule is posted is just a start, eventually I’d replace the static entity_ids in it with something that gets replaced with correct values.
So let’s say I’m in the kitchen and press the UP button on the wall for the kitchen rollershutter, this rule detects a binary sensor has changed to on, then it would check a list to match the correspondent cover and it’s state, it it’s already open, issue command stop, if it’s closed issue command open. Repeats for every other room in the house, both for outdoor and indoor covers.

The statement I used on the trigger entity_id returns all the up binary switches both for the outdoor covers and indoor covers. My idea was even to one set of static lists, use a loop to match the binary sensor then use the same index number to match the cover on a different list. Something like:

{% set localup = [
'binary_sensor.bl_up_cozinha_jardim',
'binary_sensor.bl_up_gabinete',
'binary_sensor.bl_up_garagem_1',
] %}

{% set localdown = [
'binary_sensor.bl_down_cozinha_jardim',
'binary_sensor.bl_down_gabinete',
'binary_sensor.bl_down_garagem_1',
] %}

{% set localrollers = [
"cover.cortina_cozinha_jardim",
"cover.cortina_gabinete",
"cover.cortina_garagem_1",
] %}

{% set trigger = 'binary_sensor.bl_up_gabinete' %}

{% set i = 0 %}
{% for i in range(2) %}
{% if trigger == localup[i] %}
{{ localrollers[i] }}
{% endif %}
{#{{ localrollers[i] }} {{ localdown[i] }} {{ localup[i] }}
#}
{% endfor %}

The actual lists are a lot longer I just cut them down to 3 elements to post here!
Thanks for the help!

Reading again… I think you want something like this.

trigger:
  platform: event
  event_type: state_changed
condition:
    - condition: template
      value_template:  {{  trigger.event.data.new_state.domain == 'binary_sensor' 
           and trigger.event.data.entity_id.endswith('_up')
           and trigger.event.data.old_state is not none
           and trigger.event.data.old_state.state == 'off' 
           and trigger.event.data.new_state is not none
           and trigger.event.data.new_state.state == 'on' }}
...
...

Probably an event trigger is your best bet for this. Listen for state_changed and then condition out what you need.

2 Likes

So you can’t really do what you’re trying to do.

This won’t work. Not every field accepts templates and entity_id in trigger is one of those fields. You cannot use a template here, static values only.

This won’t really work either. The problem is that it will trigger only when the first binary sensor that matches that pattern turns on. If one is already on and another turns on then nothing will happen.

I did solve this in my own system but I almost hesitate to share it because its really complicated. What I did is this:

  1. I have an automation that has a list of regex patterns its interested in (like for example with yours I’d probably have binary_sensor_up: '^binary_sensor.\w+_up$' in the variables section for your case)
  2. Whenever HA starts or the entity registry changes it loops through all the regexes. For each one it builds a list of all the entities that match that ID
  3. It then compares that list to what that list used to be. What the list used to be is stored in the attributes of an input_select (more on this in a second)
  4. If that regex has changed it adds an item to the input_select’s list denoting the list of entities that match that regex have changed
  5. At the end of the automation, for each regex that has changed it writes out a YAML file into a special folder. Each file is simply a list of entity IDs that matched that regex
  6. It also writes out an index YAML file. This YAML file is a dictionatry with a field per regex and it !includes the corresponding list YAML file
  7. It then reloads all scripts, template entities, groups, input selects and (lastly) automations since that’s all the places those lists may have been referenced. Automations must be last because the current automation will end when it is reloaded

How does this work you might wonder? Well then my automations look like this:

trigger:
  platform: state
  entity_id: !include /config/common/auto/binary_sensor_up.yaml
  state: 'on'

You may not be able to use templates for entity IDs but you can use !include. So this effectively makes it dynamic. As for the index file, that is used in the special input_select I mentioned. I have this in my customize.yaml for it:

input_select.auto_lists: !include /config/common/auto/.directory.yaml

Which basically means the input select is huge. It has an attribute per regex with the value of the list of entities that matched that regex last time I calculated it. Which allows me to diff it with the current values to know when I need to write out the file. It also means I can easily reference the list of entities that matched a particular regex in templates.

I can try and put together a package if you want. I think its fairly reusable. But its seriously complex and there’s a lot of moving pieces with the files.

The other option I came up with is this:

  1. Listen for the generic state_changed event
  2. In the condition, filter out all events that don’t meet your criteria

So for your example it would look like this:

mode: parallel
max: 100
trigger:
  platform: event
  event_type: state_changed
condition:
  - "{{ trigger.event.data.entity_id is match('^binary_sensor[.].*_up$') }}"
  - "{{ trigger.event.data.new_state.state == 'on' }}"
action:
  ...

This works but I really didn’t like it personally. The problem is you are listening for every state changed event. There’s so many! That’s why max has to be like 100. And I have no idea what the performance impact is of that.

It’s also completely untraceable for debugging. Every single state_changed event will generate a trace even though most will be filtered out by the condition. Which means you have like maybe a few seconds to get to your trace before its replaced with a new one.

This bothered me enough that I came up with the super complex file based one. But if works for you it is simpler, can be done all within one automation.

Anyway those are the only two solutions I was able to come up with. If you were hoping for a simple one then sorry, there isn’t. You might want to consider looking at Node RED for these cases. It’s equivalent of the state-type trigger, the events: state node supports this. It allows you to trigger off any entity if its ID matches a substring or regex. When I migrated my automations from Node RED to HA I was generally happier but that particular feature I really miss.

EDIT: Actually the state changed one could work a bit better in your case since you’re specifically looking for when these binary sensors turned on. This should allow you to move more of the condition into the trigger like this:

mode: parallel
max: 100
trigger:
  platform: event
  event_type: state_changed
  event_data:
    new_state:
      state: 'on'
condition: "{{ trigger.event.data.entity_id is match('^binary_sensor[.].*_up$') }}"
action:
  ...

This makes it significantly more traceable since you will only get a trigger when something changes about an entity that has state on rather then every single state change.
Nope this doesn’t work.

1 Like

This is all true. I am not sure about the performance impact though. you can literally put the listener in the developer tab and see just how many there are. Its going to fire a lot and be untraceable that is a fact. I’d guess though the performance impact isn’t much if any.

Testing this it didn’t work. I read a issue about why and I cant remember. But I think its a dictionary and can’t parse it.

EDIT: IF you use the event method, i’d disable tracing entirely. ITs just not going to help you. :slight_smile: Might as well save the writing of the traces.

2 Likes

Ah crap. That’s a bummer. Actually I vaguely remember this too back when I was digging into all this before I came up with the file approach. Oh well.

1 Like

As an experiment, you might want to try the following technique I suggested to someone else in the same situation (it worked for them but YMMV).

It all hinges on the following Template Sensor:

template:
  - sensor:
      - name: Latest Binary Sensor On
        state: >
          {% set x = states.binary_sensor | selectattr('state', 'eq', 'on')
            | selectattr('entity_id', 'search', '_up') | sort(attribute='last_changed', reverse=true) | list %}
          {{ (x[0].entity_id if now() - x[0].last_changed < timedelta(seconds=2) else '') if x | count > 0 else '' }}

What it does is report the entity_id of whichever binary_sensor whose state recently changed to on (i.e. within the last 2 seconds). Otherwise it reports a blank string (for the case where the binary_sensor changed state to some value other than on).

The Template Sensor can be used in an automation using a State Trigger. It’s important that the automation contains a condition to reject when it receives a blank string (or use a not_to option).

  alias: example
  mode: queued
  trigger:
    - platform: state
      entity_id: sensor.latest_binary_sensor_on
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state != '' }}"
  action:
    ... etc ...

EDIT

Correction. Search entity_id for matching string.

1 Like

Thanks everyone for the incredible input.
You’ve given me much to study up on and test! I’ll report back!

I’ve used OpenHAB for 6 years and at the time I just wrote these rules statically because I was in a rush to get it done and working as we were finishing the house and preparing to move in. And I never updated OpenHAB because I ran into breaking changes on 2.x, since my 1.x was (still is) working perfectly never touched it again. Then a friend pushed me to try HA and I must say I’m really impressed and loving it, since I’m no rush this time, I figured I’d try to get things right or as good as I can, mostly as an opprtunity to learn the platform.

Post my results soon!

So having read all your posts it’s pretty clear my initial idea isn’t possible, but how about I just use a static list for the trigger entity_id’s and the template code for the rest. Even if it’s not as dynamic, it still beats writing the same rule 44 times.
I’ve written this and VS isn’t complaining, config tests valid and automation shows on the UI list, but I’ll only be able to test it live later today or tomorrow.
In the meanwhile here’s the full rule, this one is just for the UP&STOP motion, I’d have another one similar to this with the DOWN binary sensor list and the closed state check for the DOWN&STOP motion.
Let me know what you think, if you think it’ll work or if it can be improved?

EDIT: After testing, I had to change some things around, corrected the code below and it’s working. Two rules (only posted one), albeit a bit long but for my knowledge level I think they are ok for now, maybe someday I’ll know enough to make these a bit cleaner or more streamlined.

- id: "wallswitches_up"
  alias: "Wall Switches UP-STOP motion"
  variables:
    matchedroller: >
      {% set localup = [
      'binary_sensor.bl_up_cozinha_jardim',
      'binary_sensor.bl_up_gabinete',
      'binary_sensor.bl_up_garagem_1',
      'binary_sensor.bl_up_garagem_2',
      'binary_sensor.bl_up_garagem_3',
      'binary_sensor.bl_up_quarto_meio',
      'binary_sensor.bl_up_quarto_ponta',
      'binary_sensor.bl_up_sala_jantar',
      'binary_sensor.bl_up_sala_tv',
      'binary_sensor.bl_up_suite',
      'binary_sensor.est_up_corredor',
      'binary_sensor.est_up_cozinha_jardim',
      'binary_sensor.est_up_cozinha_rua',
      'binary_sensor.est_up_gabinete',
      'binary_sensor.est_up_lavandaria',
      'binary_sensor.est_up_quarto_ponta',
      'binary_sensor.est_up_sala_jantar',
      'binary_sensor.est_up_sala_tv',
      'binary_sensor.est_up_suite',
      'binary_sensor.est_up_wc_comum',
      'binary_sensor.est_up_wc_suite', 
      'binary_sensor.est_up_quarto_meio'
      ] %}

      {% set localrollers = [
      "cover.cortina_cozinha_jardim",
      "cover.cortina_gabinete",
      "cover.cortina_garagem_1",
      "cover.cortina_garagem_2",
      "cover.cortina_garagem_3",
      "cover.cortina_quarto_meio",
      "cover.cortina_quarto_ponta",
      "cover.cortina_sala_jantar",
      "cover.cortina_sala_tv",
      "cover.cortina_suite",
      "cover.estore_corredor",
      "cover.estore_cozinha_jardim",
      "cover.estore_cozinha_rua",
      "cover.estore_gabinete",
      "cover.estore_lavandaria",
      "cover.estore_quarto_ponta",
      "cover.estore_sala_jantar",
      "cover.estore_sala_tv",
      "cover.estore_suite",
      "cover.estore_wc_comum",
      "cover.estore_wc_suite",
      "cover.estore_quarto_meio"
      ] %}
      {% set i = 0 %}
      {% for i in range(22) %}
      {% if trigger.entity_id == localup[i] %}
      {% set roller = localrollers[i] %}
      {{ roller }}
      {% endif %}
      {% endfor %}

  trigger:
    - platform: state
      entity_id:
        - binary_sensor.bl_up_cozinha_jardim
        - binary_sensor.bl_up_gabinete
        - binary_sensor.bl_up_garagem_1
        - binary_sensor.bl_up_garagem_2
        - binary_sensor.bl_up_garagem_3
        - binary_sensor.bl_up_quarto_meio
        - binary_sensor.bl_up_quarto_ponta
        - binary_sensor.bl_up_sala_jantar
        - binary_sensor.bl_up_sala_tv
        - binary_sensor.bl_up_suite
        - binary_sensor.est_up_corredor
        - binary_sensor.est_up_cozinha_jardim
        - binary_sensor.est_up_cozinha_rua
        - binary_sensor.est_up_gabinete
        - binary_sensor.est_up_lavandaria
        - binary_sensor.est_up_quarto_ponta
        - binary_sensor.est_up_sala_jantar
        - binary_sensor.est_up_sala_tv
        - binary_sensor.est_up_suite
        - binary_sensor.est_up_wc_comum
        - binary_sensor.est_up_wc_suite
        - binary_sensor.est_up_quarto_meio
      to: "on"
  mode: restart
  condition:
    - alias: "Lockdown mode check"
      condition: state
      entity_id: input_boolean.lockdown
      state: "off"
  action:
    - choose:
        # IF
        - conditions:
            - condition: template
              value_template: "{{ is_state(matchedroller, 'open') }}"
          sequence:
            - service: cover.stop_cover
              target:
                entity_id: "{{ matchedroller }}"
      # ELSE
      default:
        - service: cover.open_cover
          target:
            entity_id: "{{ matchedroller }}"

Thanks for all your help!

Did you try what I had suggested and did it fail to work?

1 Like

Since what was causing the issue was the entity_id on the trigger and that’s just a static list of 22 sensors I just copied and pasted those so I could write and test the rest of the automation.
It’s working now as I posted above (edited my previous post), now I might try and make it better and replace the trigger static list with your statement.
I currently have 2 rules like the one posted above, one for the UP sensors and one for the DOWN sensors.
Thanks for all your help! What a great community!

I’ll interpret that as “No”.

FWIW, what I proposed wouldn’t require listing 22 binary_sensor entities twice (not even once). However if you don’t mind listing them individually then all’s well that ends well.

Seems to me it would be simpler if you wrote a blueprint for the generic use case, then invoke it 44 times for the specific instances?

So, I ran into a similar issue and this is how I solved it.

My Hue dimmer switches and smart lights are all in Zigbee2MQTT.

My naming convention is as follows:

For the lights, I either use single lights or groups and the name is basically the basis. For instance, I have 2 lights in the upper hallway. They are named:

  • Upper Hallway 1 (entity_id in HA: light.upper_hallway_1)
  • Upper Hallway 2 (entity_id in HA: light.upper_hallway_2)

I combined them in Zigbee2MQTT in a group with the name (entity_id):

  • Upper Hallway (entity_id in HA: light.upper_hallway)

The switches follow the same name:

  • If there is only one switch for a group, it would be: Upper Hallway Switch
    • In HA, there will be a sensor with the name sensor.upper_hallway_switch_action
  • If there are more than one switches, it will be: Upper Hallway Switch 1 (or 2, etc.)
    • In HA, there will be a sensor with the name sensor.upper_hallway_switch_1_action

So, I created the following automation:

- alias: Generic Hue Dimmer Switch actions
  trigger:
    - platform: event
      event_type: state_changed
  condition: "{{ '_switch_' in trigger.event.data.entity_id and '_action' in trigger.event.data.entity_id and trigger.event.data.new_state.state.endswith('_release') }}"
  action:
    service: script.hue_dimmer_action
    data:
      source_entity: "{{ trigger.event.data.entity_id }}"
      source_action: "{{ trigger.event.data.new_state.state }}"

And I created the followin script:

hue_dimmer_action:
  sequence:
    - alias: "Collect values"
      variables:
        source_entity: "{{ source_entity }}"
        source_action: "{{ source_action }}"
        target_light: "{{ source_entity | regex_replace(find='^sensor', replace='light') | regex_replace(find='_switch(_[0-9]+)*_action$', replace='') }}"
    - alias: "Perform actions"
      choose:
        - conditions:
            - condition: template
              value_template: "{{ source_action == 'on_press_release' }}"
          sequence:
            - alias: "Toggle light"
              service: light.toggle
              target:
                entity_id: "{{ target_light }}"
        - conditions:
            - condition: template
              value_template: "{{ source_action == 'up_press_release' }}"
          sequence:
            - alias: "Make light brighter"
              service: light.turn_on
              target:
                entity_id: "{{ target_light }}"
              data:
                brightness_step_pct: 10
        - conditions:
            - condition: template
              value_template: "{{ source_action == 'down_press_release' }}"
          sequence:
            - alias: "Dim light"
              service: light.turn_on
              target:
                entity_id: "{{ target_light }}"
              data:
                brightness_step_pct: -10

Now, whenever I add a switch and lightbulbs/lightbulb groups, I have to ensure the naming convention and the switch works straight away with the intended lights without having to add new automations.