Window sensors with Open, Tilted, Closed state

I just wanted to share my project for making window sensors a little smarter.
Usually window sensors do only have a state open or close. A window which is tilted is usually shown as open and therefore this information is not perfectly useful e.g. if you want to alarm on rain and let people know whether they need to get up from the couch or stay seated. :grinning:
Or if I leave the house for a short period of time I usually don’t care about tilted windows. However if windows are fully open and it starts raining this immediately causes flooding in our flat.

I use aqara window sensors which are mounted close to the top of the window. In order to also get the tilt status I added a second aqara sensor to each window to the very bottom of it. In case the window is tilted the top sensor is ‘open’ while the bottom sensor is ‘closed’.

On lovelace it looks like this - where the outline square means open and the triangle means tilted:
contact_sensors_01

To get started I created a dropdown helper in the helper ui for every window with four different states like this:

Furthermore I needed a new template sensor showing the correct icons depending on the state of the window. This also needs to be done for every window.

- platform: template
  sensors:
    kontaktsensor_arbeitszimmer_advanced:
      value_template: '{{ states("input_select.kontaktsensor_arbeitszimmer") }}'
      friendly_name: 'Kontaktsensor Arbeitszimmer Advanced'
      icon_template: >-
            {% if is_state('sensor.kontaktsensor_arbeitszimmer_advanced', 'Open') %}
              mdi:square-outline
            {% elif is_state('sensor.kontaktsensor_arbeitszimmer_advanced', 'Tilted') %}
              mdi:network-strength-outline
            {% elif is_state('sensor.kontaktsensor_arbeitszimmer_advanced', 'Closed') %}
              mdi:square              
            {% else %}
              mdi:alert-circle-outline
            {% endif %}

Now an automation is needed which is being triggered by every state change of any of my contact sensors and then set the right state for this particular window.

alias: Contact Sensors State
description: ''
trigger:
  - platform: state
    entity_id: binary_sensor.openclose_9
    id: Kontaktsensor Arbeitszimmer Oben
  - platform: state
    entity_id: binary_sensor.openclose_93
    id: Kontaktsensor Arbeitszimmer Unten
  - platform: state
    entity_id: binary_sensor.openclose_12
    id: Kontaktsensor Schlafzimmer Oben
  - platform: state
    entity_id: binary_sensor.openclose_94
    id: Kontaktsensor Schlafzimmer Unten
  - platform: state
    entity_id: binary_sensor.openclose_11
    id: Kontaktsensor Bad Oben
  - platform: state
    entity_id: binary_sensor.openclose_95
    id: Kontaktsensor Bad Unten
  - platform: state
    entity_id: binary_sensor.openclose_10
    id: Kontaktsensor Kuche Oben
  - platform: state
    entity_id: binary_sensor.openclose_96
    id: Kontaktsensor Kuche Unten
  - platform: state
    entity_id: binary_sensor.openclose_22
    id: Kontaktsensor Wohnzimmer links Oben
  - platform: state
    entity_id: binary_sensor.openclose_97
    id: Kontaktsensor Wohnzimmer links Unten
  - platform: state
    entity_id: binary_sensor.openclose_21
    id: Kontaktsensor Wohnzimmer rechts Oben
  - platform: state
    entity_id: binary_sensor.openclose_98
    id: Kontaktsensor Wohnzimmer rechts Unten
  - platform: state
    entity_id: binary_sensor.openclose_7
    id: Kontaktsensor Terrasse Oben
  - platform: state
    entity_id: binary_sensor.openclose_99
    id: Kontaktsensor Terrasse Unten
condition: []
action:
  - choose:
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Arbeitszimmer Oben
              - condition: trigger
                id: Kontaktsensor Arbeitszimmer Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_arbeitszimmer
            data:
              option: >
                {% if states('binary_sensor.openclose_9') == 'on' and
                states('binary_sensor.openclose_93') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_9') == 'on' and
                states('binary_sensor.openclose_93') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_9') == 'off' and
                states('binary_sensor.openclose_93') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Schlafzimmer Oben
              - condition: trigger
                id: Kontaktsensor Schlafzimmer Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_schlafzimmer
            data:
              option: >
                {% if states('binary_sensor.openclose_12') == 'on' and
                states('binary_sensor.openclose_94') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_12') == 'on' and
                states('binary_sensor.openclose_94') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_12') == 'off' and
                states('binary_sensor.openclose_94') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Bad Oben
              - condition: trigger
                id: Kontaktsensor Bad Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_bad
            data:
              option: >
                {% if states('binary_sensor.openclose_11') == 'on' and
                states('binary_sensor.openclose_95') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_11') == 'on' and
                states('binary_sensor.openclose_95') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_11') == 'off' and
                states('binary_sensor.openclose_95') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Kuche Oben
              - condition: trigger
                id: Kontaktsensor Kuche Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_kuche
            data:
              option: >
                {% if states('binary_sensor.openclose_10') == 'on' and
                states('binary_sensor.openclose_96') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_10') == 'on' and
                states('binary_sensor.openclose_96') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_10') == 'off' and
                states('binary_sensor.openclose_96') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Wohnzimmer links Oben
              - condition: trigger
                id: Kontaktsensor Wohnzimmer links Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_wohnzimmer_links
            data:
              option: >
                {% if states('binary_sensor.openclose_22') == 'on' and
                states('binary_sensor.openclose_97') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_22') == 'on' and
                states('binary_sensor.openclose_97') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_22') == 'off' and
                states('binary_sensor.openclose_97') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Wohnzimmer rechts Oben
              - condition: trigger
                id: Kontaktsensor Wohnzimmer rechts Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_wohnzimmer_rechts
            data:
              option: >
                {% if states('binary_sensor.openclose_21') == 'on' and
                states('binary_sensor.openclose_98') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_21') == 'on' and
                states('binary_sensor.openclose_98') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_21') == 'off' and
                states('binary_sensor.openclose_98') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: Kontaktsensor Terrasse Oben
              - condition: trigger
                id: Kontaktsensor Terrasse Unten
        sequence:
          - service: input_select.select_option
            target:
              entity_id: input_select.kontaktsensor_terrasse
            data:
              option: >
                {% if states('binary_sensor.openclose_7') == 'on' and
                states('binary_sensor.openclose_99') == 'on' %}
                  Open
                {% elif states('binary_sensor.openclose_7') == 'on' and
                states('binary_sensor.openclose_99') == 'off' %}
                  Tilted
                {% elif states('binary_sensor.openclose_7') == 'off' and
                states('binary_sensor.openclose_99') == 'off' %}
                  Closed
                {% else %}
                  Error
                {% endif %}
    default: []
mode: queued
max: 20

This automation is very lenghy but what it does is basically, to check which window group of sensors the trigger belongs to and then set the state according to the state combinations of both sensors.
By using variables, there may be a better (shorter) way to set up this automation.

This works extremely reliable and possible use cases are e.g.:

  • having a speaker (in my case Alexa) tell you which windows are fully open and which are only tilted when a rain sensor detects rain
  • get a notification each time leaving the house (based on presence detection) telling me the correct status of all windows and in case a window is fully open I can go back and close it.

Best
Pete

1 Like

Is there a reason you need to use input_selects and an automation for the sensors?

I was looking into something similar and used an additional template sensor for each window.

sensor:
  - platform: template
    sensors:
      kontaktsensor_arbeitszimmer_advanced:
        friendly_name: 'Kontaktsensor Arbeitszimmer Advanced'
        value_template: >-
          {% set oben = is_state('binary_sensor.openclose_9', True) %}
          {% set unten = is_state('binary_sensor.openclose_93', True) %}
          
          {% if oben and unten %}
            Open
          {% elif oben and not unten %}
            Tilted
          {% elif not oben and not unten %}
            Closed
          {% else %}
            Error
          {% endif %}      
        icon_template: >-
          {% set oben = is_state('binary_sensor.openclose_9', True) %}
          {% set unten = is_state('binary_sensor.openclose_93', True) %}
          
            {% if oben and unten %}
              mdi:square-outline
            {% elif oben and not unten %}
              mdi:network-strength-outline
            {% elif not oben and not unten %}
              mdi:square              
            {% else %}
              mdi:alert-circle-outline
            {% endif %}  

good point.

I use the automation to trigger each time the state of any contact sensor changes. Do template sensors trigger as fast/good on state changes as automations do?
If so, your approach is, of course, much leaner.

The state of the template sensor changes instantly when the original sensor changes its state.

Hi,

Today, I found some time adapting your solution to HA but I ran into a syntax? error.

        value_template: >-
          {% set oben = is_state('binary_sensor.openclose_9', True) %}
          {% set unten = is_state('binary_sensor.openclose_93', True) %}
          
          {% if oben and unten %}
            Open
          {% elif oben and not unten %}
            Tilted
          {% elif not oben and not unten %}
            Closed
          {% else %}
            Error
          {% endif %}      

This way the state doesn’t change. If I use something like this for the states:

          {% if is_state('binary_sensor.openclose_9', 'on') and is_state('binary_sensor.openclose_93', 'on') %}

instead of

{% if oben and unten %}

it does work.

any idea?

Best

I tried to follow this guide today and I think the problem is, that there were some changes to the template sensor configuration format why it is not working as it used to do. Template - Home Assistant
Below you see, what worked for me:

I have in my main configuration.yml template: !include template.yaml and I load them from this file, so if you do not do this, you have to indent everything and add it under the key template: like shown in the documentation. You do not need a trigger template, but I like clean state changes.

# template.yaml
- trigger:
    # as both window sensors do not fire at the same time, wait for a second
    # before changing the state. Other platforms triggers needed to have a state after
    # config reload or restart.
    - platform: state
      entity_id: binary_sensor.bedroom_window_sensor
      for: 00:00:01
    - platform: homeassistant
      event: start
    - platform: event
      event_type: event_template_reloaded
  sensor:
    - name: "Bedroom Window Advanced"
      state: >
        {% set lower = is_state('binary_sensor.bedroom_lower_window_sensor', 'on') %}
        {% set upper = is_state('binary_sensor.bedroom_window_sensor', 'on') %}
        {% if upper and lower %}
          Open
        {% elif upper and not lower %}
          Tilted
        {% elif not upper and not lower %}
          Closed
        {% else %}
          Error
        {% endif %}
      icon: >
        {% set lower = is_state('binary_sensor.bedroom_lower_window_sensor', 'on') %}
        {% set upper = is_state('binary_sensor.bedroom_window_sensor', 'on') %}
        {% if upper and lower %}
          mdi:square-outline
        {% elif upper and not lower %}
          mdi:network-strength-outline
        {% elif not upper and not lower %}
          mdi:square              
        {% else %}
          mdi:alert-circle-outline
        {% endif %}

Great, works for me, too. One more step to clean up my home assistant. :slight_smile:

Hi there!
Just tried it and it works perfectly…
I’ve run with this simpler version:

  - name: "South Window"
    state: >
      {% set lower = is_state('binary_sensor.south_window_lower_opening', 'on') %}
      {% set upper = is_state('binary_sensor.south_window_upper_opening', 'on') %}
      {% if lower %}
        Open
      {% elif upper %}
        Tilted
      {% else %}
        Closed
      {% endif %}
    icon: >
      {% if is_state("sensor.south_window", "Open") %}
        mdi:square-outline
      {% elif is_state("sensor.south_window", "Tilted") %}
        mdi:network-strength-outline
      {% else %}
        mdi:square
      {% endif %}

I’m sure I’ll add the trigger to delay the state change in the near future, but for now it’s working fine.

2 key points here:

  • since there’s no way that the upper sensor is closed while the lower one is open, the check is not necessary
  • to set the icon, the state of the sensor itself can be used (I wouldbe wonderful if something like “self” can be used to avoid repeating the sensor id!)

Hey guys,
I’m pretty new to HA. (Still using edomi which works quite well but now I’m looking for a modern home automation server.)
I couldn’t follow this thread, in detail what needs to be done to get this 2 sensors combined for the open, tilted and closed visualisation. My window handles are Enocean based, so I have two switches that I already mapped to binary sensors, A and B. The logic for the window state is as follows:
A B | State
0 0 | closed
0 1 | n/a
1 0 | tilted
1 1 | open

Can I configure the stuff described above via UI, or do I have to do it manually in the yaml file?
It would be great if you could please provide a complete overview/ step by step description of the latest and greatest/simplest solution.

Best regards,