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

Mine is a little more complex. I calculate the energy cost of exchanging air, the current benefit of exchanging, and the current need. Only humidity and enthalpy - not TVOC/C02. Based on those three, I make the ventilation machine exchange air when it is most cost-effective; the most benefit for the least cost. That saves energy. Sometimes during the heating season the air outside is warm AND dry, which is when I increase ventilation. Sometimes it is cool and humid, which is when I turn it down to minimum. If it is controlled only based on the need, it will do it regardless if it is cost-effective or not - and as a result waste some energy. I’ve been developing it for a year, and it still isn’t ready. I would like it to be more elegant. But it works as I envisioned; during the few hours when the circumstances are exeptional, it powerfully takes advantage. Mostly things are just meh, and the machine idles and waits.

Here’s a graph over a few days, where you can see how the costs and benefits change quite a lot. The numbers are unitless ones (like kWh/kWh). The end result I use to set the fan speed. The grey line is the end result calculated with EnthalpyBenefit * HeatNeed + HumidityBenefit*HumidityNeed.


The spikes usually coincide with someone taking a shower. It immediately increases the need AND the benefit of ventilating.

Looks good, but how did you manage to get that into HA?,

Humidity calculations in Esphome devices or with the Thermal Comfort integration. Calculations are done with yaml template sensors. Graph is with Plotly custom component.

Would you mind sharing you YAML template?, :slight_smile:

In principle yes, but I need to collect the parts from all over the place + it’s pretty specific and thus probably of very limited use to anyone. I think I’ll rather lay out the general logic in english once I find the time.

Here’s the principle:

EnthalpyTargetAir = calculateEnthalpy(FreshAirAbsHum, InsideAirTemperature)  # function below
HeatingNeed = TargetTemperature - CurrentTemperature  # this can be negative or positive
HeatingNeed = min(HeatingNeed, 1)  # This if you want to limit the effect
HeatingNeed = max(HeatingNeed, -1)  # Same here.
HumidityNeed = TargetRH - CurrentRH  # this can be negative or positive
EnthalpyEffect = (EnthalpyFreshAir - EnthalpyTargetAir) * 0.463  # I have no idea what the magic number here is. I have forgotten
EnthalpyBenefit = HeatingNeed × EnthalpyEffect  # this makes it work in summer and winter
HumidityEffect = ExhaustAbsHum - FreshAirAbsHum  # this can be negative or positive
HumidityBenefit = HumidityEffect × HumidityNeed
TotalBenefit = HumidityBenefit + EnthalpyBenefit

Then multiply TotalBenefit with some arbitrary factor to get values between 0 - 100 to use for controlling the fan speed, and make an automation to make use of it.

With FreshAir I mean the air coming into the rooms, not outside air. With ExhaustAir I mean the air leaving the rooms before it enters the heat exchanger. I measure them from within the pipes.

Here’s my template to calculate EnthalpyTargetAir:

- sensor:
  - name: Enthalpy Inside New Air
    unique_id: 11211-2345
    unit_of_measurement: kJ/kg
    state_class: measurement
    state: >
      {% set oh = states('sensor.redundant_absolute_humidity_outside') | float(3.0) / 1000 %}
      {% set tt = states('sensor.temperature_avg_inside') | float(19.4) %}
      {{ ((1.006 * tt)  + oh * (1.84 * tt + 2501) / 1.2) | round(2) }}

You probably have to monitor the numbers to get an idea if it will work properly, and it most likely won’t work at all without you understanding what’s actually going on. The basic principle is to figure out how much it costs us in kWh to exchange a kg of air, to figure out how much humidity (in kWh) we remove by exchanging a kg of air, and then to combine the values to tell us how smart it is to exchange air.

If you want to add TVOC or other factors to the same logic, then add TVOCBenefit to the last equation. You need to balance it with the other variables by using some arbitrary coefficient. The right balance can be found by experimenting and observing

1 Like

Which part do you want me to explain?

Whats the difference;

      percentage: trigger.to_state.state

To this:

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

The second one is a Jinja2 template. The first one isn’t.

Reference: Templating

The second one, being a template, interprets trigger.to_state.state to be the name of a variable. The first one interprets it to be a literal string.

Very interesting, specially this line; HumidityBenefit = HumidityEffect × HumidityNeed

Nice approach. you should write publish something about that, and maybe create something others could also use…