Scripting / automation question, set ventilation depending on TVOC and Humidity

I have three air quality sensors;
-One TVOC level meter
-Two humidity meters

I want to set the level of ventilation depending on the TVOC / Humidity level like so;

percentage ; TVOC ; humidity

ventilation off ; 0-64 ; < 60
ventilation 25% ; 65-220 ; 61-70
ventilation 50% ; 220-660; 71-80
ventilation 75% ; 660-2200; 81-90
ventilation 100% ; 2200-+; 91+

If one of the humidity meters is between 71-80, the ventiation has to be set to 50%.

I’ve got something going, but these are 5 seperate automations, and in each automation I’ve had to create conditions to exclude every other automation…

Must be a better way to achieve this, Oh brave HA wizards, please give me guidance … :slight_smile:

I think this approach is best; convert all three to percentage ventilation, and then take the highest of the three.

1 Like

Create a Trigger-based Template Sensor that monitors the TVOC and humidity sensors. It computes and reports the ventilation level.

template:
  - trigger:
      - platform: state
        entity_id:
          - sensor.tvoc
          - sensor.humidity1
          - sensor.humidity2
    sensor:
      - name: Ventilation
        state: >
          {% set tvoc = states('sensor.tvoc') | float(0) %}
          {% set h = ['sensor.humidity1', 'sensor.humidity2'] | map('states') | map('float', 0) | max %}
          {% set ventilation =
            { tvoc <= 64 and h <= 60: 0,
              64 < tvoc <= 220 and 60 < h <= 70: 25,
              220 < tvoc <= 660 and 70 < h <= 80: 50,
              660 < tvoc <= 2200 and 80 < h <= 90: 75,
              tvoc > 2200 and h > 90: 100 } %}
          {{ ventilation.get(true, 0) }}

Create an automation with a State Trigger that monitors sensor.ventilation.

alias: example
trigger:
  - platform: state
    entity_id: sensor.ventilation
condition: []
action:
  ... whatever service call and entity you are using for ventilation control ...
  ... the ventilation value will be in trigger.to_state.state ...

EDIT

Clarification of requirements: select max value of the two humidity sensors, not average.

Replaced average with max in template.

Great reply, I’m gonna try that.

One thing, the higest of the two humidity ‘wins’ and would require the ventilation to be set to its corresponding level. So if humidity1 is 30, and humidity2 is 95, ventilation must be set to 100%.

One more thing, tried to look it up myself, but template documentation doesn’t actually write how to create one. It does say what should be in there, as actual YAML, but not where to place that. Should it go in your configuration.yaml?

Thanks for the clarification. I have replaced average with max in the example posted above.

Yes. However, if you already have a line like this in configuration.yaml

template: !include templates.yaml

then it should go into the templates.yaml file (but remove the example’s first line containing the word template: because that’s not needed within templates.yaml).

Great stuff, did all that, but something’s still wrong…

My TVOC is 260, but the sensor.ventilation is 0…

I think it might be because non of the conditions apply, since;
h1 = 50
h2 = 51
tvoc = 260

Is it Jinja2 syntax?, I don’t fully understand it… Should the AND be a OR, and would it still be correct since you … well I don’t understand the syntax…

That’s correct, none of the conditions you specified handles the combination of tvoc = 260 and humidity = 51. Therefore the template I created reports 0 for such a situation (as opposed to failing with an error).

You may need to redesign how you intend to convert the combination of TVOC and humidity into a ventilation level.

The variable ventilation is a dictionary. It contains key-value pairs. The left side of the colon is the key, the right side is the value.

In the following key-value pair, the value of the tvoc variable must be greater than 64 and less than or equal to 220 and the value of the variableh must be greater than 60 and less than 70. If they are, then the key reports true and its associated ventilation level is 25.

64 < tvoc <= 220 and 60 < h <= 70: 25

I think this is the way;
First take the highest from the two humidity’s and convert that into a percentage setting for the ventilation.

Then convert the TVOC level to a percentage level for the ventilation.

From the two ventilation values, pick the highest…

Something like;

      {% set tvoc = states('sensor.lumi_lumi_airmonitor_acn01_voc_level') | float(0) %}
      {% set h = ['sensor.lumi_lumi_weather_humidity', 'sensor.lumi_lumi_airmonitor_acn01_humidity'] | map('states') | map('float', 0) | max %}
      {% set pTVOC = 
        { tvoc <= 64: 0,
          64 < tvoc <= 220: 25,
          220 < tvoc <= 660: 50,
          660 < tvoc <= 2200: 75,
          tvoc > 2200: 100 } %}
      {% set pHumidity = 
        { h <= 60: 0,
          60 < h <= 70: 25,
          70 < h <= 80: 50,
          80 < h <= 90: 75,
          h > 90: 100 } %}

      {% set ventilation =
        { [pTVOC,pHumidity] | max %}
        
            
        } %}
      {{ ventilation.get(true, 0) }}

Aren’t the reported humidity values already between 0 and 100? In other words, there’s no conversion required.

What formula do you have in mind to convert TVOC into a percentage (i.e. a value between 0 and 100)? Or is the plan to use a dictionary?

I want to map the percentage of humidy to a certian level of ventilation. Target humidity is below 60 percent.

Same goes for the TVOC; tvoc <= 64: 0,
64 < tvoc <= 220: 25,
220 < tvoc <= 660: 50,
660 < tvoc <= 2200: 75,
tvoc > 2200: 100

template:
  - trigger:
      - platform: state
        entity_id:
          - sensor.lumi_lumi_airmonitor_acn01_voc_level
          - sensor.lumi_lumi_weather_humidity
          - sensor.lumi_lumi_airmonitor_acn01_humidity
    sensor:
      - name: Ventilation
        state: >
          {% set tvoc = states('sensor.lumi_lumi_airmonitor_acn01_voc_level') | float(0) %}
          {% set h = ['sensor.lumi_lumi_weather_humidity', 'sensor.lumi_lumi_airmonitor_acn01_humidity']
            | map('states') | map('float', 0) | max %}
          {% set pTVOC = 
            { tvoc <= 64: 0,
              64 < tvoc <= 220: 25,
              220 < tvoc <= 660: 50,
              660 < tvoc <= 2200: 75,
              tvoc > 2200: 100 }.get(true, 0) %}
          {% set pHumidity = 
            { h <= 60: 0,
              60 < h <= 70: 25,
              70 < h <= 80: 50,
              80 < h <= 90: 75,
              h > 90: 100 }.get(true, 0) %}
          {{ [pTVOC, pHumidity] | max }}

EDIT

Correction. Fixed value supplied to the get methods.

Seems like there is still something wrong, is there anyway to debug this?, it always returns 0…

:confused:

Sorry, that’s due to my mistake. I have corrected the example above.

Taras, you are truly a wizard!, thanks a lot for this fine piece of code!, much appriciated!

1 Like

The sensor.ventilation now gets the correct value. But I’m having some trouble getting the automation going. I think I did exactly as you told. But apparently I’m doing something wrong. Been troubleshooting for an hour now, can’t get it to work.

When I try to;

alias: Set ventilation speed
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.ventilation
condition: []
action:
  - service: fan.set_percentage
    data:
      percentage: trigger.to_state.state
    target:
      device_id: 71aadc68cb536e18989982a5a33f2d27
mode: single

I get;

* Error while executing automation automation.set_ventilation_speed: extra keys not allowed @ data['sensor.ventilation']
* Error while executing automation automation.set_ventilation_speed: Error rendering data template: Result is not a Dictionary
* Error while executing automation automation.set_ventilation_speed: expected int for dictionary value @ data['percentage']

Do I need to cast the integer to a dictionairy? and how?

When I use a static number, it works;

  percentage: 43

Maybe it’s because the value is Null / nothing?

Change this:

      percentage: trigger.to_state.state

To this:

      percentage: '{{ trigger.to_state.state }}'

That worked, apparently I was very close, since I tried

 "{{ trigger.to_state.state }}"

Thanks again!

Sorry to bother you again. I’m trying to understand. What did you just do?, I did some coding in the past, so you can talk in coding terms…

If the absolute humidity outside is higher than inside, you will get a positive feedback loop which only ends when the air outside gets drier. I recommend thinking about how to avoid it. Maybe force-set your pHumidity to 60 if absolute humidity outside is higher than inside.

I like to contribute towards dehumidifing the outside air!

Joking aside, I have tought about it, how do you solve that?