Create a derivative binary sensor [Solved]

Hi,

If I have four reed switches installed on the four windows in a room say living room:

binary_sensor.window_1
binary_sensor.window_2
binary_sensor.window_3
binary_sensor.window_4

Each one of the sensor shows if a window is open or closed.

What is the best way to make a binary_sensor for living room that would show status as “open” if any of the four windows is open?

Thanks.

1 Like

Use group-

group:
  living_room_windows:
    name: Living Room Windows
    entities:
      - binary_sensor.window_1
      - binary_sensor.window_2
      - binary_sensor.window_3
      - binary_sensor.window_4

You will now have a new entity_id called group.living_room_windows. If at least one of the entities inside your group is ON, the group state will be ON.

2 Likes

@ardysusilo thanks and this turned out to be far easier than I thought. I started reading the template syntax for “if” and “or” statements and quickly realized that it is way over my head!

I’m really glad that I asked here :grinning: :+1:

If you wish, you can also create a State-based Template Sensor which will count and list entities inside your group that are on-

template:
  - sensor:
      - name: "Living Room Opened Windows"
        icon: >-
          {{ 'mdi:check-circle' if is_state('sensor.living_room_opened_windows','0') else 'mdi:alert-circle' }}
        unit_of_measurement: "entities"
        state: >-
          {{ expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | list | count }}
        attributes:
          entities: >-
            {{ expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list }}
1 Like

Ardy,

This looks promising! I get the “icon” part which is picking open or closed icon based on the current state of living_room_windows sensor.

I am little confused about the next two sections and you have used two sensors - living_room_opened_windows & living_room_windows.

Do you mind explaining the “state” and “attributes” sections in simple language?

Thanks.

The state is using expand() function to expand the content inside your group. Then, using selectattr('state', 'eq', 'on') to only select entities inside your group that are on. Lastly, using count to count how many entities are on.

For the attributes, it is similar to the state, however, instead of counting how many entities that are on, we want to get a list of entities that are on. Thereby, using list function.

See more about it below-

There are only 1 sensor and 1 group.

  1. A new sensor entity called sensor.living_room_opened_windows will be created by putting the above YAML code in your configuration.yaml. This sensor entity is used to determine the icon. If sensor.living_room_opened_windows returns 0, then it will use mdi:check-circle, otherwise it will use mdi:alert-circle.
  2. This group.living_room_windows is a group (not a sensor) that you created previously using Group integration. This is used in state and attribute to get the entities inside your group that are on (using the expand() function as well as the selectattr()).
1 Like

Excellent!

The “group” solved the part of merging sensors together to infer if there is an open window.

But I was grappling with the notification part (text or email) and wanted to include specificity such as “Window-3 in living room is open” or “window is open in the living room” instead of saying “a window is open”.

Let me try your code block and I will update this thread.

Thanks again for sharing your thoughts and insights!

Try this automation code-

trigger:
  - platform: state
    entity_id: group.living_room_windows
    to: 'on'
  - platform: state
    entity_id: person.your_name
    to: not_home
condition:
  - condition: state
    entity_id: person.your_name
    state: not_home
action:
  - repeat:
      until:
        - condition: state
          entity_id: group.living_room_windows
          state: 'off'
      sequence:
        - service: notify.mobile_app_your_name
          data:
            message: >-
              {% set x = expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list %}
              Alert! {{ x | join(', ') }} {{ 'are' if x | count > 1 else 'is' }} open in the living room.
        - delay:
            hours: 0
            minutes: 0
            seconds: 30
            milliseconds: 0

Alternative message YAML code (credit to Taras) to make the “comma” as well as “is” or “and” written correctly-

action:
  - repeat:
      until:
        - condition: state
          entity_id: group.living_room_windows
          state: 'off'
      sequence:
        - service: notify.mobile_app_your_name
          data:
            message: >-
              {% set windows = expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list | join(", ") %}
              {% set x = ', and ' if windows.split(', ') | count > 2 else ' and ' %}
              Alert! {{x.join(windows.rsplit(', ', 1))}} {{'are' if windows.split(', ') | count > 1 else 'is' }} open in the living room.
        - delay:
            hours: 0
            minutes: 0
            seconds: 30
            milliseconds: 0

@ardysusilo the automation worked like a charm and the text message includes the exact window(s) opened…thanks a lot!

This is pretty cool :sunglasses:

@ardysusilo

message: >-
              {% set windows = expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list | join(", ") %}
              {% set x = ', and ' if windows.split(', ') | count > 2 else ' and ' %}
              Alert! {{x.join(windows.rsplit(', ', 1))}} {{'are' if windows | count > 1 else 'is' }} open in the living room.

This code block proved magical, all I had to do was add more window sensors to the group. I am also amused that message always uses the right conjugation (is/are) depending on the number of window opened :sunglasses:

If I may pick your brain a little bit, please see if I got the details below correct.

{% set windows = expand('group.living_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list | join(", ") %}

This one seems to be recursing through all entities in the group and creating a comma separated list of attribute “state” conditioned if equal to “on”. Correct?

{% set x = ', and ' if windows.split(', ') | count > 2 else ' and ' %}

Is this code picking if a comma needs to be added in case more than one window is open?

Alert! {{x.join(windows.rsplit(', ', 1))}} {{'are' if windows | count > 1 else 'is' }} open in the living room.

This is generating the text message. What are function windows.split() and windows.rspilt()?

Does using “|” in template syntax just pipes passes on the output from the previous command to the next one like a standard Linux shell?

Thanks a lot, I learned a lot in this thread! :grinning:

The goal of the code is to make the list of item written correctly. As an example, if you have 2 windows that are open, it will be written Window 1 and Window 2. However, if you have more than 2, it will be written Window 1, Window 2, and Window 3.

If you paste {{ windows.split(', ') }} in Developer Tools → Template will result in ['Window 1', 'Window 2', 'Windows 3']. Notice that there are 3 elements - Window 1 is the first element, Window 2 is the second element, and Window 3 is the third element.

Next, we want to know if there are 2 or more items. If there are only 2 items, we want to combine it using and. However, if there are more than 2, we want to combine the latest one using , and.

Therefore, you use | count in {% set x = ', and ' if windows.split(', ') | count > 2 else ' and ' %}. If there are only 2 element, x will be and, if more than 2 element, x will be , and.

If you paste {{ windows.rsplit(', ', 1) }} in Developer Tools it will result in ['Window 1, Window 2', 'Window 3']. Notice that there are only 2 elements - 'Window 1, Window 2' is the first element, while 'Window 3' is the second element.

The basic syntax is string.rsplit(separator, maxsplit). By setting the maxsplit to 1, it will only return a list with 2 elements (just like the above example).

Why do we need it? Because we want to join the x just before the last element.
Therefore, we use x.join(windows.rsplit(', ', 1)) to join it.


Sorry, I made a mistake here-

It should be-
Alert! {{x.join(windows.rsplit(', ', 1))}} {{'are' if windows.split(', ') | count > 1 else 'is' }} open in the living room.

This is a very useful automation! :+1: Thanks!

However, I do have some questions:
When the automation become ‘active’ it’s keeps repeating the message (in this example) every 30 sec until the door(s) is/are closed. Is it also possible to limit the message for, let’s say, 5 times?

Would it also be possible to send a message at the moment all doors are closed again?

You can use Counted Repeat - Script Syntax to make it notify you 5 times - but this will not check the condition anymore.

To send a message at the moment all doors are closed again, you can use Wait for Trigger.

I’ve limited my text messages by adding the “count” option in the automation, here is my code for reference:

#This sends a text notification if any window is opended
- id: '1630378006279'
  alias: Alarm_test_notification
  description: ''
  trigger:
  - platform: state
    entity_id: group.all_room_windows
    to: 'on'
  condition: []
  action:
  - repeat:
      count: '01'
      sequence:
      - service: notify.sms_notification_test
        data:
          title: Alert
          message: >-
            {% set windows = expand('group.all_room_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list | join(", ") %}
            {% set x = ', and ' if windows.split(', ') | count > 2 else ' and ' %}
            Alert! {{x.join(windows.rsplit(', ', 1))}} {{'are' if windows | count > 1 else 'is' }} open in the living room.
      - delay:
          hours: 0
          minutes: 0
          seconds: 10
          milliseconds: 0
  mode: single