When to open windows based on air quality

Hi everybody,

I have developed a little template sensor, which shows me, when it’s time to open the windows and get some fresh air into our house. It is based on several other air quality and climate sensors.

I have tried to come up with a set of rules that

  • make sure the humidity does not get too high in the house
  • radiation based on Radon does not get too high
  • insight temperature should not get too high in summer

This is, how it looks like in my dashboard. (-> based on mushroom cards)

this is the yaml of the card
type: vertical-stack
cards:
  - type: custom:mushroom-template-card
    primary: "{{states('sensor.dachzimmer_lueften')}}"
    secondary: ""
    icon: " {{ state_attr('sensor.dachzimmer_lueften', 'icon') }}"
    entity: sensor.dachzimmer_lueften
    tap_action:
      action: more-info
    color: " {{ state_attr('sensor.dachzimmer_lueften', 'color') }}"
    features_position: bottom
  - type: horizontal-stack
    cards:
      - type: custom:mushroom-template-card
        primary: ""
        secondary: >-
          {% set radon = int(states('sensor.esphome_dachzimmer2_radon'), 'Sensor
          Wert fehlt') %}


          {% if is_number( radon ) %}

          {{ radon }} Bq/m³

          {% else %}

          {{ radon }}

          {% endif %}
        icon: mdi:radioactive
        entity: sensor.esphome_dachzimmer2_radon
        tap_action:
          action: more-info
        color: >-
          {% set radon = int(states('sensor.esphome_dachzimmer2_radon'), 'Sensor
          Wert fehlt') %}


          {% if radon <80 %}

          green

          {% elif radon <100 %}

          amber

          {% elif radon <125 %}

          orange

          {% elif radon <=150 %}

          red

          {% elif radon >150 %}

          pink

          {% endif  %}
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: >-
          {% set voc = int(states('sensor.esphome_dachzimmer2_voc'), 'Sensor
          Wert fehlt') %}

          {% if is_number( voc ) %}

          {{voc}} ppb

          {% else %}

          {{voc}}

          {% endif %}
        icon: mdi:molecule
        icon_color: >-
          {% set voc = int(states('sensor.esphome_dachzimmer2_voc'), 'Sensor
          Wert fehlt') %}


          {% if voc < 250 %}

          green

          {% elif voc < 1125 %}

          amber

          {% elif voc < 2000 %}

          orange

          {% elif voc <= 2875 %}

          red

          {% elif voc > 2875 %}

          pink

          {% endif  %}
        entity: sensor.esphome_dachzimmer2_voc
        layout: vertical
        tap_action:
          action: more-info
      - type: custom:mushroom-template-card
        primary: ""
        secondary: >-
          {% set co2 = int(states('sensor.esphome_dachzimmer2_co2'), 'Sensor
          Wert fehlt')  %}


          {% if is_number(co2) %}

          {{ co2 }} ppm

          {% else %}

          {{ co2 }}

          {% endif %}
        icon: mdi:molecule-co2
        entity: sensor.esphome_dachzimmer2_co2
        icon_color: >-
          {% set co2 = int(states('sensor.esphome_dachzimmer2_co2'), 'Sensor
          Wert fehlt')  %}


          {% if co2 < 800 %}

          green

          {% elif co2 < 900 %}

          amber

          {% elif co2 < 1000 %}

          orange

          {% elif co2 <= 1250 %}

          red

          {% elif co2 > 1250 %}

          pink

          {% endif  %}
        layout: vertical
        badge_color: ""
        badge_icon: ""
        tap_action:
          action: more-info
      - type: custom:mushroom-template-card
        primary: ""
        secondary: >-
          {% set hum = float(states('sensor.esphome_dachzimmer2_feuchtigkeit'),
          'Sensor Wert fehlt') %}


          {% if is_number( hum ) %}

          {{ hum | round(0) }} %

          {% else %}

          {{ hum }}

          {% endif %}
        icon: mdi:water-percent
        entity: sensor.esphome_dachzimmer2_feuchtigkeit
        tap_action:
          action: more-info
        color: >-
          {% set dp_out = float(states('sensor.dew_point_at_8400'), 'Sensor Wert
          fehlt') %}

          {% set dp_in = float(states('sensor.esphome_dachzimmer2_taupunkt'),
          'Sensor Wert fehlt') %}

          {% set hum = float(states('sensor.esphome_dachzimmer2_feuchtigkeit'),
          'Sensor Wert fehlt') %}


          {% if dp_out * 1.1 < dp_in and hum < 55 %}

          green

          {% elif dp_out * 1.1 < dp_in and hum < 60 %}

          amber

          {% elif dp_out * 1.075 < dp_in and hum < 65 %}

          orange

          {% elif dp_out * 1.05 < dp_in and hum <= 70 %}

          red

          {% elif dp_out * 1.05 < dp_in and hum > 70 %}

          pink

          {% else %}

          disabled

          {% endif  %}
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: >-
          {% set temp_in = float(
          states('sensor.esphome_dachzimmer2_temperatur') | round(1), 'Sensor
          Wert fehlt' ) %}


          {% if is_number(temp_in) %}

          {{ temp_in }} °C

          {% else %}

          {{ temp_in }}

          {% endif %}
        icon: mdi:thermometer
        entity: sensor.esphome_dachzimmer2_temperatur
        tap_action:
          action: more-info
        color: >-
          {% set temp_in = float(
          states('sensor.esphome_dachzimmer2_temperatur'), 'Sensor Wert fehlt' )
          %}

          {% set temp_out = float( states('sensor.temperature_at_8400'), 'Sensor
          Wert fehlt' ) %}


          {% if ( temp_in > 23.5 and temp_in > temp_out * 1.1 ) or ( temp_in <
          19.5 and temp_out > temp_in * 1.1 ) %}

          red

          {% elif ( temp_in > 23.5 and temp_in > temp_out * 1.075 ) or ( temp_in
          < 19.5 and temp_out > temp_in * 1.075 ) %}

          orange

          {% elif ( temp_in > 23.5 and temp_in > temp_out * 1.05 ) or ( temp_in
          < 19.5 and temp_out > temp_in * 1.05 ) %}

          amber

          {% elif ( temp_in <= 23.5 and temp_in > 19.5 ) %}

          green

          {% elif temp_in == 'Sensor Wert fehlt' or temp_out == 'Sensor Wert
          fehlt' %}

          disabled

          {% else %}

          disabled

          {% endif %}
        vertical: true
        features_position: bottom
this is the template sensor

### Dach: Sensor, der anzeigt, ob gelüftet werden soll. ###
  - sensor:
    - name: "Dachzimmer lüften"
      unique_id: sensor.dachzimmer_lueften
      state: >
        {# Lüften-Sensor #}
          {# Variablen definieren #}
          {% set radon = int( states('sensor.esphome_dachzimmer2_radon'), 'Sensor Wert fehlt' ) %}
          {% set voc = int( states('sensor.esphome_dachzimmer2_voc'), 'Sensor Wert fehlt' ) %}
          {% set co2 = int( states('sensor.esphome_dachzimmer2_co2'), 'Sensor Wert fehlt' ) %}
          {% set hum = float( states('sensor.esphome_dachzimmer2_feuchtigkeit'), 'Sensor Wert fehlt' ) %}
          {% set dp_in = float( states('sensor.esphome_dachzimmer2_taupunkt'), 'Sensor Wert fehlt' ) %}
          {% set dp_out = float( states('sensor.dew_point_at_8400'), 'Sensor Wert fehlt' ) %}
          {% set temp_in = float( states('sensor.esphome_dachzimmer2_temperatur'), 'Sensor Wert fehlt' ) %}
          {% set temp_out = float( states('sensor.temperature_at_8400'), 'Sensor Wert fehlt' ) %}
          
          {# availability check #}
          {% if 'Sensor Wert fehlt' in [ radon, voc, co2, hum, dp_in, dp_out, temp_in, temp_out ] %}
            Sensor Wert fehlt
          {% else %}
        
              {# Lüften-Sensor berechnen #}
              {% if  ( radon >= 125 )
                or ( voc >= 2000 )
                or ( 
                      ( hum > 70 or co2 >= 1000 or ( temp_in > 23.5 and temp_in > temp_out * 1.1 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.1 ) ) 
                  and ( dp_out >=0 and dp_in > dp_out * 1.05 or dp_out < 0 and dp_in > dp_out * 0.95 ) 
                ) %}
              Dach sofort lüften
        
              {% elif  ( radon >= 100 )
                or ( voc >= 1125 )
                or ( 
                      ( hum > 65 or co2 >= 900 or ( temp_in > 23.5 and temp_in > temp_out * 1.075 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.075 ) ) 
                  and ( dp_out >=0 and dp_in > dp_out * 1.075 or dp_out < 0 and dp_in > dp_out * 0.925 ) 
                ) %}
              Dach lüften
        
              {% elif  ( radon >= 85 )
                or ( voc >= 250 )
                or ( 
                      ( hum > 60 or co2 >= 800 or ( temp_in > 23.5 and temp_in > temp_out * 1.05 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.05 ) ) 
                  and ( dp_out >=0 and dp_in > dp_out * 1.1 or dp_out < 0 and dp_in > dp_out * 0.9 ) 
                ) %}
              Dach bald lüften
        
              {% else %}
              Dach nicht lüften
        
              {% endif %}
          {% endif %}
      icon: >
        {% if is_state('sensor.dachzimmer_lueften', 'Dach sofort lüften') or is_state('sensor.dachzimmer_lueften', 'Dach lüften') or is_state('sensor.dachzimmer_lueften', 'Dach bald lüften') %}
            mdi:window-open-variant
        {% elif is_state('sensor.dachzimmer_lueften', 'Dach nicht lüften') %}
            mdi:window-closed-variant
        {% elif is_state('sensor.dachzimmer_lueften', 'Sensor Wert fehlt')  %}
            mdi:alert-outline
        {% else %}
            mdi:help-circle-outline
        {% endif %}
      attributes:
        color: >
          {% if is_state('sensor.dachzimmer_lueften', 'Dach sofort lüften') %}
            red
          {% elif is_state('sensor.dachzimmer_lueften', 'Dach lüften') %}
            orange
          {% elif is_state('sensor.dachzimmer_lueften', 'Dach bald lüften') %}
            amber
          {% elif is_state('sensor.dachzimmer_lueften', 'Dach nicht lüften') %}
            disabled
          {% else %}
            pink
          {% endif %}

(Please take my appologies for the german comments and sensors names.)

Let me know, what you think. Do you like it? Have you got a more elegant way to show something similar?

1 Like

Hey @schneich,
many thanks for your effort here. I was searching for this kind of a while.
I updated the template a bit for my needs and also added the calculations in the template so my frontend don´t need to handle this.

Template
template:
  - sensor:
    - name: "Kinderzimmer lueften"
      unique_id: "09015a4a-40e4-4b1b-95fa-39f955efdb97"
      state: >
        {# Lüften-Sensor #}
          {# Variablen definieren #}
          {% set co2 = states('sensor.kinderzimmer_apollomsr2_co2') | int(default=None) %}
          {% set hum = states('sensor.kinderzimmer_raumthermostat_humidity') | float(default=None) %}
          {% set dp_in = states('sensor.kinderzimmer_thermal_comfort_dew_point') | float(default=None) %}
          {% set dp_out = states('sensor.aussen_thermal_comfort_taupunkt') | float(default=None) %}
          {% set temp_in = states('sensor.kinderzimmer_raumthermostat_temperature') | float(default=None) %}
          {% set temp_out = states('sensor.temperatur_aussen') | float(default=None) %}
          {# availability check #}
          {% if None in [ co2, hum, dp_in, dp_out, temp_in, temp_out ] %}
            Sensor Wert fehlt
          {% else %}
            {# Lüften-Sensor berechnen (ohne Radon / VOC) #}
            {% if  
                  ( hum > 70 or co2 >= 1500 
                    or ( temp_in > 23.5 and temp_in > temp_out * 1.1 ) 
                    or ( temp_in < 19.5 and temp_out > temp_in * 1.1 )
                  )
                and (
                    (dp_out >= 0 and dp_in > dp_out * 1.05)
                    or (dp_out < 0 and dp_in > dp_out * 0.95)
                )
            %}
              Kinderzimmer sofort lüften
            {% elif  
                  ( hum > 65 or co2 >= 1000 
                    or ( temp_in > 23.5 and temp_in > temp_out * 1.075 ) 
                    or ( temp_in < 19.5 and temp_out > temp_in * 1.075 )
                  )
                and (
                    (dp_out >= 0 and dp_in > dp_out * 1.075)
                    or (dp_out < 0 and dp_in > dp_out * 0.925)
                )
            %}
              Kinderzimmer lüften
            {% elif  
                  ( hum > 60
                    or ( temp_in > 23.5 and temp_in > temp_out * 1.05 ) 
                    or ( temp_in < 19.5 and temp_out > temp_in * 1.05 )
                  )
                and (
                    (dp_out >= 0 and dp_in > dp_out * 1.1)
                    or (dp_out < 0 and dp_in > dp_out * 0.9)
                )
            %}
              Kinderzimmer bald lüften
            {% else %}
              Kinderzimmer nicht lüften
            {% endif %}
          {% endif %}
      icon: >
        {% if this.state in ['Kinderzimmer sofort lüften', 'Kinderzimmer lüften', 'Kinderzimmer bald lüften'] %}
          mdi:window-open-variant
        {% elif this.state == 'Kinderzimmer nicht lüften' %}
          mdi:window-closed-variant
        {% elif this.state == 'Sensor Wert fehlt' %}
          mdi:alert-outline
        {% else %}
          mdi:help-circle-outline
        {% endif %}
      attributes:
        color: >
          {% if this.state == 'Kinderzimmer sofort lüften' %}
            red
          {% elif this.state == 'Kinderzimmer lüften' %}
            orange
          {% elif this.state == 'Kinderzimmer bald lüften' %}
            amber
          {% elif this.state == 'Kinderzimmer nicht lüften' %}
            green
          {% else %}
            pink
          {% endif %}
        co2_value: >
          {% set co2 = int(states('sensor.kinderzimmer_apollomsr2_co2'), 'Sensor Wert fehlt') %}
          {% if is_number(co2) %}
            {{ co2 }} ppm
          {% else %}
            {{ co2 }}
          {% endif %}
        co2_color: >
          {% set co2 = int(states('sensor.kinderzimmer_apollomsr2_co2'), 'Sensor Wert fehlt') %}
          {% if co2 < 1000 %}
            green
          {% elif co2 < 1500 %}
            orange
          {% elif co2 >= 1500 %}
            red
          {% endif  %}
        humidity_value: >
          {% set hum = float(states('sensor.kinderzimmer_raumthermostat_humidity'), 'Sensor Wert fehlt') %}
          {% if is_number( hum ) %}
            {{ hum | round(0) }} %
          {% else %}
            {{ hum }}
          {% endif %}
        humidity_color: >
          {% set dp_out = float(states('sensor.aussen_thermal_comfort_taupunkt'), 'Sensor Wert fehlt') %}
          {% set dp_in = float(states('sensor.kinderzimmer_thermal_comfort_dew_point'), 'Sensor Wert fehlt') %}
          {% set hum = float(states('sensor.kinderzimmer_raumthermostat_humidity'), 'Sensor Wert fehlt') %}
          {% if dp_out * 1.1 < dp_in and hum < 55 %}
            green
          {% elif dp_out * 1.1 < dp_in and hum < 60 %}
            amber
          {% elif dp_out * 1.075 < dp_in and hum < 65 %}
            orange
          {% elif dp_out * 1.05 < dp_in and hum <= 70 %}
            red
          {% elif dp_out * 1.05 < dp_in and hum > 70 %}
            pink
          {% else %}
            disabled
          {% endif  %}
        temperature_value: >
          {% set temp_in = float(states('sensor.kinderzimmer_raumthermostat_temperature') | round(1), 'Sensor Wert fehlt' ) %}
          {% if is_number(temp_in) %}
            {{ temp_in }} °C
          {% else %}
            {{ temp_in }}
          {% endif %}
        temperature_color: >
          {% set temp_in = float(states('sensor.kinderzimmer_raumthermostat_temperature'), 'Sensor Wert fehlt' ) %}
          {% set temp_out = float( states('sensor.temperatur_aussen'), 'Sensor Wert fehlt' ) %}
          {% if ( temp_in > 23.5 and temp_in > temp_out * 1.1 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.1 ) %}
            red
          {% elif ( temp_in > 23.5 and temp_in > temp_out * 1.075 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.075 ) %}
            orange
          {% elif ( temp_in > 23.5 and temp_in > temp_out * 1.05 ) or ( temp_in < 19.5 and temp_out > temp_in * 1.05 ) %}
            amber
          {% elif ( temp_in <= 23.5 and temp_in > 19.5 ) %}
            green
          {% elif temp_in == 'Sensor Wert fehlt' or temp_out == 'Sensor Wert fehlt' %}
            disabled
          {% else %}
            disabled
          {% endif %}

Hi @Mar1us ,

thank you for your template. I like the idea of adding more attributes, so that the code in the dashobard cards can be simplified. Very nice.
I wanted to add an attribute to manage the visibility of the card. Unfortunately, dashboard card visibility can not (yet?) be managed by attributes…

take care,
Chris