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 %}

EDIT: new code further down: When to open windows based on air quality - #4 by schneich

(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

Hi @Mar1us,

thank you again for your input. I took the time to re-design my code.

template sensor yaml
  ### Dach: Sensor, der anzeigt, ob gelüftet werden soll. ###
  - sensor:
    - name: "Dachzimmer lüften"
      unique_id: sensor.dachzimmer_lueften
      state: >
        {# when to open windows #}
        {# set variables #}
        {% set radon = states('sensor.esphome_dachzimmer2_radon') | int(default=None) %}
        {% set voc = states('sensor.esphome_dachzimmer2_voc') | int(default=None) %}
        {% set co2 = states('sensor.esphome_dachzimmer2_co2') | int(default=None) %}
        {% set hum = states('sensor.esphome_dachzimmer2_feuchtigkeit') | float(default=None) %}
        {% set dp_in = states('sensor.esphome_dachzimmer2_taupunkt') | float(default=None) %}
        {% set dp_out = states('sensor.dew_point_at_8400') | float(default=None) %}
        {% set temp_in = states('sensor.esphome_dachzimmer2_temperatur') | float(default=None) %}
        {% set temp_out = states('sensor.temperature_at_8400') | float(default=None) %}
        
        {# availability check #}
        {% if None 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 this.state in ['Dach sofort lüften', 'Dach lüften', 'Dach bald lüften'] %}
          mdi:window-open-variant
        {% elif this.state == 'Dach 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 == 'Dach sofort lüften' %}
            red
          {% elif this.state == 'Dach lüften' %}
            orange
          {% elif this.state == 'Dach bald lüften' %}
            amber
          {% elif this.state == 'Dach nicht lüften' %}
            disabled
          {% else %}
            pink
          {% endif %}
        radon_value: >
          {% set radon = states('sensor.esphome_dachzimmer2_radon') | int(default=None) %}
          {% if is_number( radon ) %}
            {{ radon }} Bq/m³
          {% else %}
            Sensor Wert fehlt
          {% endif %}
        radon_color: >
          {% set radon = states('sensor.esphome_dachzimmer2_radon') | int(default=None) %}
          {% if radon is none %}
            disabled
          {% elif radon < 80 %}
            green
          {% elif radon < 100 %}
            amber
          {% elif radon < 125 %}
            orange
          {% elif radon <= 150 %}
            red
          {% else %}
            pink
          {% endif %}
        co2_value: >
          {% set co2 = states('sensor.esphome_dachzimmer2_co2') | int(default=None) %}
          {% if is_number( co2 ) %}
            {{ co2 }} ppm
          {% else %}
            Sensor Wert fehlt
          {% endif %}
        co2_color: >
          {% set co2 = states('sensor.esphome_dachzimmer2_co2') | int(default=None) %}
          {% if co2 is none %}
            disabled
          {% elif co2 < 800 %}
            green
          {% elif co2 < 900 %}
            amber
          {% elif co2 < 1000 %}
            orange
          {% elif co2 >= 1000 %}
            red
          {% else %}
            disabled
          {% endif %}
        voc_value: >
          {% set voc = states('sensor.esphome_dachzimmer2_voc') | int(default=None) %}
          {% if is_number( voc ) %}
            {{ voc }} ppb
          {% else %}
            Sensor Wert fehlt
          {% endif %}
        voc_color: >
          {% set voc = states('sensor.esphome_dachzimmer2_voc') | int(default=None) %}
          {% if voc is none %}
            disabled
          {% elif voc < 250 %}
            green
          {% elif voc < 1125 %}
            amber
          {% elif voc < 2000 %}
            orange
          {% elif voc >= 2000 %}
            red
          {% else %}
            disabled
          {% endif %}
        hum_value: >
          {% set hum = states('sensor.esphome_dachzimmer2_feuchtigkeit') | float(default=None) %}
          {% if hum is none %}
            Sensor Wert fehlt
          {% else %}
            {{ hum | round(0) }}%
          {% endif %}
        hum_color: >
          {% set hum = states('sensor.esphome_dachzimmer2_feuchtigkeit') | float(default=None) %}
          {% set dp_in = states('sensor.esphome_dachzimmer2_taupunkt') | float(default=None) %}
          {% set dp_out = states('sensor.dew_point_at_8400') | float(default=None) %}
          {% if None in [hum, dp_in, dp_out] %}
            disabled
          {% elif 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 %}
        temp_value: >
          {% set temp_in = states('sensor.esphome_dachzimmer2_temperatur') | float(default=None) %}
          {% if temp_in is none %}
            Sensor Wert fehlt
          {% else %}
            {{ temp_in | round(1) }} °C
          {% endif %}
        temp_color: >
          {% set temp_in = states('sensor.esphome_dachzimmer2_temperatur') | float(default=None) %}
          {% set temp_out = states('sensor.temperature_at_8400') | float(default=None) %}
          {% if None in [temp_in, temp_out] %}
            disabled
          {% elif ( 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
          {% else %}
            disabled
          {% endif %}

This code is much better now. (I finally understood how to implement default values for numeric inputs and how to implement availablilty checks… :see_no_evil: )

dashboard yaml (based on mushroom cards)
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: "{{ state_attr('sensor.dachzimmer_lueften', 'radon_value') }}"
        icon: mdi:radioactive
        entity: sensor.esphome_dachzimmer2_radon
        tap_action:
          action: more-info
        color: "{{ state_attr('sensor.dachzimmer_lueften', 'radon_color') }}"
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: "{{ state_attr('sensor.dachzimmer_lueften', 'voc_value') }}"
        icon: mdi:molecule
        entity: sensor.esphome_dachzimmer2_voc
        tap_action:
          action: more-info
        color: "{{ state_attr('sensor.dachzimmer_lueften', 'voc_color') }}"
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: "{{ state_attr('sensor.dachzimmer_lueften', 'co2_value') }}"
        icon: mdi:molecule-co2
        entity: sensor.esphome_dachzimmer2_co2
        badge_color: ""
        badge_icon: ""
        tap_action:
          action: more-info
        color: "{{ state_attr('sensor.dachzimmer_lueften', 'co2_color') }}"
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: "{{ state_attr('sensor.dachzimmer_lueften', 'hum_value') }}"
        icon: mdi:water-percent
        entity: sensor.esphome_dachzimmer2_feuchtigkeit
        tap_action:
          action: more-info
        color: "{{ state_attr('sensor.dachzimmer_lueften', 'hum_color') }}"
        vertical: true
        features_position: bottom
      - type: custom:mushroom-template-card
        primary: ""
        secondary: "{{ state_attr('sensor.dachzimmer_lueften', 'temp_value') }}"
        icon: mdi:thermometer
        entity: sensor.esphome_dachzimmer2_temperatur
        tap_action:
          action: more-info
        color: "{{ state_attr('sensor.dachzimmer_lueften', 'temp_color') }}"
        vertical: true
        features_position: bottom
visibility:
  - condition: state
    entity: sensor.dachzimmer_lueften
    state_not: Dach nicht lüften

This code got much simpler, as I only use the template sensor attributes to get all the values.

perfect - thank you!