Hope someone here could help me with a contact sensor/ lock automation (all doors in 1 automation if possible)

OK, that’s fine. We can use that (instead of “door”) as long as all three of the doors have the same value for their device_class and no other binary_sensors have it. I’ll wait for you to confirm that before I proceed to suggest how to modify the template to take advantage of it.

Yes, it’s possible to manually add attributes to an existing entity. For example, the integration that created the binary_sensors shown in your screenshot didn’t create a device_class for some of them. That was the decision of the integration’s author. However it’s possible to manually add it and any other custom attribute you want … but it’s a manual step and wouldn’t happen automatically if the integration were to add an additional door lock.

@123 Thank you for the clarification. I understand, yes, so to me, that makes my code attempt no good.

I will end up moving to yours as this is a big issue to me.

So the question now becomes since the boltchecked locks are named as below (to me these are virtual/ shadow locks):
lock.boltchecked_front_door
lock.boltchecked_back_door
lock.boltchecked_garage_entry

and the actual physical locks are named as below:
lock.front_door_lock
lock.back_door_lock
lock.garage_entry_door_lock

For clarity for other reading this thread after us. Will my boltchecked entities cause issues for when using your code? Or since they are not followed by _lock, I should be fine.

I have a feeling, I know what you are thinking about now. I will say that at some point, I think it is possible that I will add window sensors to my Home Assistant setup and I imagine they will be the standard contact sensors that will probably come into Home Assistant as the “opening” device class. (Entity id will always contain “window” as the last word of the entity, more than likely) Would now be the time to start doing what you are thinking and assigning different device classes to entities? Or could I make it work in the future just based off of proper entity naming.–> I’m sorry that I don’t know very much so all I’m really able to do is tell someone what I see/ am thinking on my end and hopefully learn from what they do or show me.

Oh Lord, I didn’t even know that was possible but I see what you are talking about now.

I follow you, well to be honest, I don’t (for now at least) expect to use any of the entities created by the keymaster integration in any automations. So below are screenshots of the vanilla (expected things I would use in an automation surrounding the doors)

So I have 3 doors. A front door, back door and garage entry door. Below are screenshots of my states tab that I believe will help you know what is in my setup. The “boltchecked” lock entities, I really don’t want to be used or “hit”/ affected by any automations. The only reason, I really added the keymaster integration was so that I could schedule codes from home assistant to the app but I’m afraid that the “boltchecked” lock is integrated in other ways that I don’t know of so I’m afraid to go nuclear and just delete it.

@123 Thank you for your help once we realized my attempt was a pumpkin. :frowning:

The existence of any entity whose object_id ends with _lock will be included by the template that produces the doors variable in my example. That means the entities with “boltchecked” will also be included and that’s undesirable for this application.

That’s why I asked my previous question. I need to know if the three binary_sensors we want to use (representing the doors) all have opening for their device_class and no other entity has the same value for its device_class.

That’s a reasonable assumption but, as you may already know, presents a problem for complying with one of your design requirements. You don’t want to tweak this automation every time you add a new door lock or window or whatever. That means we can’t simply select entities based on the value “opening” because it might use windows too.

It might also mean that rejecting locks containing “boltchecked” might be insufficient in the future? Perhaps some other ‘sibling’ entity will be created containing yet another phrase that will need to be rejected.

I realize you don’t want to hard-code the doors and locks into the automation but I can tell you it helps to simplify it substantially. Besides, just how many more door locks do you plan to add in the future?

They do and yes, no other entity in my HA setup right now has “opening” has it’s device class.

Now I follow you and agree.

I am seeing what you are saying.

Currently, none. Where I am new to HA, I honestly thought HA was powerful enough to build things that could self “heal” ( as in using a variable to design for future possibilities, etc.). That was my biggest reason for the potential requirements. Also, in my short time with home assistant, I have seen myself making a change to an entity id in the GUI to then find out that the change didn’t automatically go into every instance in which I had used it in a GUI automation and propagate the entity id change automatically for me (I just figured it would, maybe that is wrong of me). I believe that is what has me want to build everything modularly at this point. Can doors/ locks be added to a group and then a person would only have to modify information about the group in order to fix all of the automations that have been created with those items? – I guess I am just trying to make things where at the most, I have to modify a file in 1 place rather than have to go through and modify a bunch of individual automations etc.

I am ok with whatever ideas you have, I was just hoping to make maintaining things a one place change situation.

Thank you for the conversation. I believe I follow you on everything. Hope you can give me your thoughts on the above.

The boltchecked items don’t end with “_lock” but I follow you in general on everything you’ve said. (And in all honesty, they probably should have, I just named them stupidly, so this is a void comment anyway)

It is but it may require careful consideration to make it completely predictable regardless of what new entities may be added in the future. The challenge here is that your application relies on pairs of entities (binary_sensor and lock) and the only tenuous connection between them is that a portion of their names are identical. A fair bit of string filtering is needed to ensure it rejects unwanted entities (which complicates the template).

It definitely won’t do that. You’re changing what’s used to refer to the entity and it doesn’t ripple through everywhere it may be referenced in automations, scripts, scenes, etc.

Sorry, I explained that incorrectly; it’s a lock entity so it’ll get included by a template that uses states.lock (like the one that reports doors by basing itself on locks). The template would need to filter out any entity whose object_id started with ‘boltchecked’. That would work fine unless, some time in the future, a new lock entity was added, unrelated to doors, that started with some other phrase than ‘boltchecked’.

I understand what you are saying.

I see. I think that was a dream I had. :slight_smile:

Now I follow you.

I think I understand you now.

So then the question for me becomes (based on what was stated in post 15):

What would be my best bet in utilizing your code with the “boltchecked” items existing in my setup (which don’t currently end with “_lock” in the name, but I can change them to end with “_lock” as the last of the name. The problem with this still becomes, I believe the notification will spit these boltchecked items out into the final notification. Or am I wrong with that last statement?

I keep getting the sense that these boltchecked lock items are a pretty big issue with what I am trying to do and I don’t really know how to get your code to ignore them if possible.

Also, you were speaking of I should consider changing the device class of my contact sensors that are currently showing “opening”, how do I do that and what is the purpose? Is that so, they can be more customized to what they do and so that I can access them individually through their own device class). I never really liked how they report on- off, is this also something that can be fixed with your thoughts, as in open-closed. Or I am best to not mess with that?

Repost of code being discussed is below for other visitors:

alias: 'Example of door and lock monitor'
trigger:
- platform: time
  at: '22:00:00'
- platform: state
  entity_id: group.family
  from: 'home'
  to: 'not_home'
  for: '00:00:10'
condition: []
action:
- variables:
    doors: >
      {{ states.lock | map(attribute='object_id')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}
    open_doors: >
      {{ expand(doors) | selectattr('state', 'eq', 'on')
        | map(attribute='name') | list }}
    closed_doors_unlocked: >
      {{ expand(expand(doors) | selectattr('state', 'eq', 'off')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*$)', 'lock.\\1_lock') | list)
        | selectattr('state', 'eq', 'unlocked')
        | map(attribute='entity_id') | list }}
- if: '{{ open_doors | count > 0 }}'
  then:
  - service: notify.mobile_app_weston
    data:
      title: DOORS ARE OPEN
      message: "The following door{{ iif(open_doors | count > 1, 's are', ' is') }} open: {{ open_doors | join('\n- ') }}"
- if: '{{ closed_doors_unlocked | count > 0 }}'
  then:
  - service: lock.lock
    target:
      entity_id: '{{ closed_doors_unlocked }}'
  - service: notify.mobile_app_weston
    data:
      title: DOORS WERE LEFT UNLOCKED (AWAY LOCK)
      message: "Locked the following: {{ closed_doors_unlocked | map(attribute='name') | list | join('\n- ') }}"

I suggest you change the doors template from this:

- variables:
    doors: >
      {{ states.lock | map(attribute='object_id')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}

to this:

- variables:
    doors: >
      {{ states.lock | rejectattr('object_id', 'match', 'boltchecked')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}

It rejects any lock entity whose object_id begins with ‘boltchecked’. That should be adequate to pare the list of locks down to only the three that you want.

Here’s the entire, revised automation:

alias: 'Example of door and lock monitor'
trigger:
- platform: time
  at: '22:00:00'
- platform: state
  entity_id: group.family
  from: 'home'
  to: 'not_home'
  for: '00:00:10'
condition: []
action:
- variables:
    doors: >
      {{ states.lock | rejectattr('object_id', 'match', 'boltchecked')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}
    open_doors: >
      {{ expand(doors) | selectattr('state', 'eq', 'on')
        | map(attribute='name') | list }}
    closed_doors_unlocked: >
      {{ expand(expand(doors) | selectattr('state', 'eq', 'off')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*$)', 'lock.\\1_lock') | list)
        | selectattr('state', 'eq', 'unlocked')
        | map(attribute='entity_id') | list }}
- if: '{{ open_doors | count > 0 }}'
  then:
  - service: notify.mobile_app_weston
    data:
      title: DOORS ARE OPEN
      message: "The following door{{ iif(open_doors | count > 1, 's are', ' is') }} open: {{ open_doors | join('\n- ') }}"
- if: '{{ closed_doors_unlocked | count > 0 }}'
  then:
  - service: lock.lock
    target:
      entity_id: '{{ closed_doors_unlocked }}'
  - service: notify.mobile_app_weston
    data:
      title: DOORS WERE LEFT UNLOCKED (AWAY LOCK)
      message: "Locked the following: {{ closed_doors_unlocked | map(attribute='name') | list | join('\n- ') }}"

EDIT

Correction. Change order of filters in doors variable.

@123 You make things look easy. I didn’t know that you could do a match and reject like that. Again, I’ll be honest. I don’t know much yet when it comes to HA but hopefully I will get there. I am learning more everyday for sure.

This makes sense once I see it.

Thank you for sharing your automations with me and your knowledge of how to get it to work with my setup. I’m sorry, I wasn’t much help but I really do appreciate you helping me with this one.

@123 Could you possibly help me trouble shoot what happened tonight with this automation. It seems the automation ran properly because when I go to my logbook, I have this line item showing (so I had 1 door unlocked tonight apparently):

Garage Entry Door Lock was locked by (ACTION- AUTOMATIC- SECURITY- NOTIFICATION) Doors Lock when Away and at Night
10:00:05 PM - 5 minutes ago

But when the text message came through (all it had was a header with no listing of this lock in the message).

Can you think of anything that could be happening or is there anything else I can get for you to be able to help me troubleshoot it?

Thank you for any help you can provide.

Below is what I’m getting when I paste the doors section of your code into the template editor.

UndefinedError: 'str object' has no attribute 'object_id'

When I pasted what you originally gave me as our first attempt, it returned a list of locks. That makes me think their is an issue with this:

    doors: >
      {{ states.lock | map(attribute='object_id')
        | rejectattr('object_id', 'match', 'boltchecked')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}

But I don’t know what I’m doing so even if there is, I don’t really know what to look for in order to fix it.

Well that one’s my fault. I added the rejectattr filter in the wrong place. It should be placed immediately after states.lock and not after the map filter. I have corrected the order of the filters in the example posted above.


You can test the automation at any time by clicking the Run Action button located next to the automation’s name in the list of automations. When you do that, it skips the automation’s trigger: and condition: sections and only executes its action: section. That’s fine for this particular automation (but not for one that has conditions or references the trigger variable somewhere in its condition or action because it will be undefined).

Oh, ok, I didn’t know that.

Thank you for letting me know this. I saw it but was afraid to click it. I felt funny waiting until bedtime last night to check it. haha

Thank you helping me with all of this. It was a little too difficult for me right at the moment with my skillset. I have learned alot from you!

No rush on this at all but can you help me understand some of your code? (It is pasted below)

        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}

I don’t understand this. The strange syntax, “.\1” and "(^.*$)

How do you know when to use “object_id” rather than “entiity_id” in the maps and filters? I have used entity_id before but never “object_id”.

And the last thing I would like to ask is, what is the best way for me to learn the stuff you used if I am not a programmer but do understand alot of what you’re doing? I don’t feel like I can find any good resources and I don’t feel like perusing the forums is going to be a way for me to learn the generalities quickly (I’m thinking more like school textbook if possible for quick/ condensed learning)

That syntax is indisputably strange and is part of what is known as “regular expression” or the abbreviation “regex”. It’s a very terse (and cryptic) way of expressing commands to manipulate text. The regex_replace filter is for replacing one string of text with something else but with great precision and flexibility.

This is known as a regex pattern

(^.*)_lock$

It is designed to match text that begins with anything but must end with the string _lock.

  • The $ symbol has special meaning (i.e. it doesn’t try to match a literal dollar sign). It means _lock must be located at the end of the string. Regex employs many characters with special meaning.
  • This ^.* means to match any text starting from the beginning of the string.
  • The surrounding parentheses here (^.*) will keep a copy of whatever text it finds using ^.*. It’s called a ‘capture group’.

So if the string is back_door_lock then it will be a match for the given regex pattern and it will keep a copy of back_door.

The second argument in regex_replace is this

binary_sensor.\\1

It represents the text that will be used to replace the matched text found by the regex pattern.

  • This \1 is a reference to the text that was copied by the regex pattern. Think of it like “variable number one” or ‘the first capture group’.
  • The backslash symbol has special meaning for regex. It is used to escape another character’s meaning. For example if I want to match a literal $ character, I have to escape it’s meaning otherwise regex will assume I want to use it to indicate the end of the string. To escape its meaning you simply prepend a backslash like this \$ and now the dollar sign is just a dollar sign and no longer means “end of string”.
  • We want regex to understand that \1 represents the first capture group and not an attempt to escape the meaning of the number one. So we prepend a backslash \\1 to make it clear we want the first capture group.

So if the string is back_door_lock it will be replaced by binary_sensor.back_door because the first capture group contains back_door.

Regex is a powerful but complex tool and one of the best ways to learn about it is to experiment with it using an interactive tool like regex101.com.

Given this entity_id:

lock.back_door_lock

Its domain is lock and its object_id is back_door_lock.

If I need to manipulate the text in back_door_lock then it’s more efficient to reference it by object_id rather than by entity_id which contains text (the word lock followed by a period) that I am not interested in using.

You’re ahead of the game if you understand my explanations. :slightly_smiling_face:

It’s difficult to point you to a single comprehensive reference but I suggest starting with the Templating section of the documentation.

@123 Thank you for spending time with me to get this automation working on my setup. I believe something still isn’t quite right. The “open door” sections seems to be working properly but on the “closed but unlocked”, I seem to be getting push notifications indicating the locking of a door but the list is blank. The last instance, I got a push notification that said

“…Locked the following:” with no doors listed.

I ran a second test with 2 doors unlocked and it produced the below (no doors listed but “-” on the second line.

“…Locked the following:
-”

Thank you for any guidance you can give. I thought we had it all taken care of but it seems there’s still a little bit of an issue floating around. I am using your edited code post from post 30. Thank you @123 for the time you have spent with me, I’m sorry we keep running into issues.

Currently, the front door is unlocked and closed in my home and below is what the template editor is showing for what is called the closed_doors_unlocked variable in the automation.

Where I’m not real good at this yet. I don’t know what the issue could be.

Also, this is a secondary thing but I’m starting to get the sense that the unlocked door list is going to say.

Locked the following:
Front Door Lock
Back Door Lock

Is there a way we can get it to say?

Locked the following:
Front Door
Back Door

And definitely buying you a coffee, because I feel like a royal pain. I hate asking for help but I’m just not functional yet on this platform. Thank you for helping me again.

EDIT:

So below is my attempt at fixing the door listing on the “closed but unlocked”
I keep getting a "UndefinedError: ‘str object’ has no attribute ‘state’

The error popped up after typing in the last block of code on the screen. (It has something to do with regex_replace outputting a string rather than an object I believe but I don’t know what to do with it.)

Also, I ended up getting the blank notification fixed with the below fix. I don’t like that I had to do it this way and I imagine you will be able to find the original issue (the hope is that I don’t have to use my “hack code” below.

Essentially I ended up adding another variable to get myself to the name attribute and added that to the notification in the actions. Something tells me I shouldn’t have to do that though and you will catch the actual problem in the original code. The changes are highlighted below (these can be compared to your original code in post 30)

Failing to report the lock’s name, is due to a missing expand filter. The closed_doors_unlocked variable contains a simple list of lock entity_ids and must be expanded in order for the map filter to get a lock entity’s name property.

The lock names all end with the word “Lock”. The replace filter can be used to simply replace the string Lock with nothing. However, we must nest the replace filter within the map filter. Why? Because it will apply replace to each item within the list of lock names.

Here’s the corrected template:

  - service: notify.mobile_app_weston
    data:
      title: DOORS WERE LEFT UNLOCKED (AWAY LOCK)
      message: "Locked the following: {{ expand(closed_doors_unlocked) | map(attribute='name') | map('replace', ' Lock', '') | list | join('\n- ') }}"

For your convenience, here’s the revised automation:

alias: 'Example of door and lock monitor'
trigger:
- platform: time
  at: '22:00:00'
- platform: state
  entity_id: group.family
  from: 'home'
  to: 'not_home'
  for: '00:00:10'
condition: []
action:
- variables:
    doors: >
      {{ states.lock | rejectattr('object_id', 'match', 'boltchecked')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*)_lock$', 'binary_sensor.\\1') | list }}
    open_doors: >
      {{ expand(doors) | selectattr('state', 'eq', 'on')
        | map(attribute='name') | list }}
    closed_doors_unlocked: >
      {{ expand(expand(doors) | selectattr('state', 'eq', 'off')
        | map(attribute='object_id')
        | map('regex_replace', '(^.*$)', 'lock.\\1_lock') | list)
        | selectattr('state', 'eq', 'unlocked')
        | map(attribute='entity_id') | list }}
- if: '{{ open_doors | count > 0 }}'
  then:
  - service: notify.mobile_app_weston
    data:
      title: DOORS ARE OPEN
      message: "The following door{{ iif(open_doors | count > 1, 's are', ' is') }} open: {{ open_doors | join('\n- ') }}"
- if: '{{ closed_doors_unlocked | count > 0 }}'
  then:
  - service: lock.lock
    target:
      entity_id: '{{ closed_doors_unlocked }}'
  - service: notify.mobile_app_weston
    data:
      title: DOORS WERE LEFT UNLOCKED (AWAY LOCK)
      message: "Locked the following: {{ expand(closed_doors_unlocked) | map(attribute='name') | map('replace', ' Lock', '') | list | join('\n- ') }}"

NOTE

For future reference, the following two Template Conditions are equivalent:

Example 1

- if:
    - condition: template
      value_template: '{{ open_doors | count > 0 }}'
  then:

Example 2

- if: '{{ open_doors | count > 0 }}'
  then:

The second example is a Template Condition in shorthand notation.

Did my suggestions for using expand and replace correct the reported issues or is there more to fix?

@123, sorry for not getting back to here over the weekend to post a response to what you were helping me with.

I believe it is perfectly fixed now. I am very happy with it.

Also, meant to tell you last week, thank you for how you don’t just give an answer to a problem but you go a step further and take time to actually explain why things need/ have to be done the way you are doing them. I know in your position, being as knowledgeable as you are with the coding, that it would be so much faster to just say “hey, here it is” but I really do appreciate you actually explaining the why on things.

I would like to understand all of this stuff better so that sometime soon, I can be reasonably self sufficient and the way you communicate makes that much easier for me and others I’m sure.

You will be coffeed soon. I guess coffee can be turned into a verb. haha. Thank you @123!!!

1 Like

Glad to hear it and thank you for the coffees!

One last thing, please consider marking my post above with the Solution tag (only the topic’s author can select and mark one post in their topic to represent the Solution). It will automatically place a check-mark next to the topic’s title which signals to other users that this topic has been resolved. This helps users find answers to similar questions.

For more information, refer to guideline 21 in the FAQ.

Ahh, I follow you. I’m sorry about that. Consider it done. I need to do that on a few other threads also when I get some free time. Thank you again @123!!

1 Like