Tim's blah blah blah

Optimizing Itho HRU 350 control via MQTT and Home Assistant

(Updated: )

I have an Itho HRU 350 in my home, which is CO₂ controlled, but unfortunately it’s a very crude & noisy control algorithm. Here I document my approach to improve this. I also include humidity and bypass state checks to ensure the house is cooled & dehumidified appropriately.

Current situation & goal

Out of the box, the Itho HRU 350 (and other ventilation boxes) run at low speed (~700 rpm in my case) until the CO₂ level gets too high (>1200 ppm in my case), when it spins up to a rather noisy ~3300 rpm. When CO₂ is below 1000 ppm it slows down again. This is a rather blunt scheme that we can easily improve.

Goal: get more gradual control on the ventilation box, specifically:

  1. CO₂ <500ppm: run at auto setting (~700 rpm)
  2. CO₂ 500-800ppm: run at medium settinfg (~1400 rpm)
  3. CO₂ 800-1100ppm: run at autonight setting (~1800 rpm)
  4. CO₂ >1100ppm: run at high setting (~2900 rpm)

Update: additionally I want the ventilation to not go below a certain level in case of cooling or dehumidifying the house:

  1. If Itho bypass valve is open (house is cooling down): don’t go below autonight setting
  2. If max humidity is > 60 (house is moist): don’t go below autonight setting

Hardware

I got a pre-built ESP8266 board from Arjen Hiemstra at NRG.watch (nrgwatch.nl). I tried to solder on the CC1101 myself and failed, so would recommend to save the frustration and spend 5 EUR to have it pre-soldered.

Connecting the board to the Itho HRU was done with a 15cm straight 8p UTP cable.

Ventilator calibration

My ventilator box runs at the following speeds & power consumption levels before calibration:

Itho HRU 350 rpm-power response

Because the power goes with the cube of speed (engineeringtoolbox.com), it can be worthwhile to see what maximum speed you need. In my case I reduced the highest setting a bit using the potentio meter on the box.

Itho HRU 350 controls are on the left lower side of the unit.

Itho HRU 350 controls are on the left lower side of the unit.

Itho HRU 350 controls have two potentiometers labeled I and II to set the low and high fan speeds respectively.

Itho HRU 350 controls have two potentiometers labeled I and II to set the low and high fan speeds respectively.

After calibration:

Understanding Itho bypass control

The Itho HRU has a bypass valve which opens to disable the heat recovery. This can be useful in summer with cooler nights to cool down the house. The control algorithm is a bit opaque, and reverse-engineered here (tweakers.net), here (tweakers.net), and [here](https://gathering.tweakers.net/forum/list_message/71246692#71246692 (tweakers.net).

The Itho HRU has two temperature sensors:

Bypass opens when:

  1. Summerday (K_min) == 1
  2. par 105. BypassTimer == 0
  3. [Supply temp (C) - Exhaust temp (C) ] > par.14, de “Offset bypass regulation (0.1*K)”.

Summerday is set to 1 when:

  1. par.104 SummerCounter (K_min) > par.12: “Summer day time (K*hr) (default “*60 = 5*60 = 300
  2. par.104 SummerCounter (K_min) is increased when Exhaust temperature > par.10: “Summer temp (C)” [default 19C], and is evaluated every minute

Bypass closes when:

  1. Supply temp (C) < par.13 Wanted temp bypass regulation (°C) [default 17C] OR
  2. Supply temp (C) > Exhaust temp (C) (i.e. when it’s too warm outside to cool)

It’s unclear how these parameters work:

  1. par.105 BypassTimer (min)
  2. par.15 Max bypass open time (hrs)

Software

I got the software from Arjen Hiemstra’s IthoWifi (github.com) and combining it with Home Assistant as documented on the Non-CVE units (github.com), MQTT integration (github.com), and Home Assistant (github.com) wiki pages.

Configure module

On the module’s config page:

  1. Set ‘Home Assistant MQTT Discovery’ to off
  2. Set ‘Normalize keys’ to on
  3. Set up virtual remote. It’s important to set this up in order for the module to send the right commands. See details on remote types here (github.com).

Configure Itho

On the ‘Itho settings’ page you can query the >100 settings and try to optimize the unit yourself. I haven’t found any documentation, and setting names are quite cryptic, but I did find:

  1. Increase ‘autonight’ speed - this is useful because autonight also triggers higher speed on high CO₂
    1. Set ‘80 - Number of floors (floor)’ to max of 2.
    2. Set ‘81 - Inhabitants (inhab)’ to max of 3.
  2. Tweak high/low CO₂ threshold
    1. Set ‘78 - PoorCo2Quality (ppm)’ to lowest value 1501
    2. Set ‘79 - GoodCo2Quality (ppm)’ to mid value 1000

Configure Home Assistant

Add config to Home Assistant. Ensure you merge this config with existing mqtt/sensor configuration (e.g. you should only have one ‘mqtt’ key, Home Assistant will give an error if you don’t.)

mqtt:
  sensor:
    - name: "itho_hru_co2"
      unique_id: "itho_hru_co2"
      state_topic: "itho/ithostatus"
      value_template: "{{ value_json['highest-received-co2-value_ppm'] }}"
      unit_of_measurement: "ppm"
      device_class: carbon_dioxide 
      state_class: 'measurement' 
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
    - name: "itho_hru_exhaust_fan_actual"
      unique_id: "itho_hru_exhaust_fan_actual"
      state_topic: "itho/ithostatus"
      unit_of_measurement: "rpm"
      value_template: "{{ value_json['exhaust-fan-actual_rpm'] }}"
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
    - name: "itho_hru_supply_fan_actual"
      unique_id: "itho_hru_supply_fan_actual"
      state_topic: "itho/ithostatus"
      unit_of_measurement: "rpm"
      value_template: "{{ value_json['supply-fan-actual_rpm'] }}"
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
    - name: "itho_hru_exhaust_temp"
      unique_id: "itho_hru_exhaust_temp"
      state_topic: "itho/ithostatus"
      unit_of_measurement: "°C"
      device_class: temperature
      value_template: "{{ value_json['exhaust-temp_c'] }}"
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
    - name: "itho_hru_supply_temp"
      unique_id: "itho_hru_supply_temp"
      state_topic: "itho/ithostatus"
      unit_of_measurement: "°C"
      device_class: temperature
      value_template: "{{ value_json['supply-temp_c'] }}"
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
  binary_sensor:
    - name: "itho_hru_bypass"
      unique_id: "itho_hru_bypass"
      state_topic: "itho/ithostatus"
      value_template: "{{ value_json['bypass-position'] }}"
      payload_off: 0
      payload_on: 1
      device_class: opening
      device: { identifiers: ["mqtt", "nrg-itho-28ac"] }
  fan:
    - name: "itho_hru_fan"
      device:
        identifiers: ["mqtt", "nrg-itho-28ac"]
        model: ITHO Wifi Add-on
        name: itho_hru_fan_device
          #availability_topic: itho/state          
      unique_id: itho_hru_fan
      state_topic: itho/lwt
      payload_on: "online"
      payload_off: "offline"
        #state_value_template: '{% if value == "online" %}ON{% else %}OFF{% endif %}'
        #      json_attributes_topic: itho/ithostatus
      command_topic: "itho/cmd"
      preset_mode_state_topic: "itho/ithostatus"
      preset_mode_command_template: "{ vremote: '{{ value }}'}"
      preset_mode_value_template: >
       {% set am = value_json['actual-mode'] | int %}
         {% if am == 1 %}
           low
         {% elif am == 2 %}
           medium
         {% elif am == 3 %}
           high
         {% elif am == 13 %}
            timer
         {% elif am == 24 %}
           auto
         {% elif am == 25 %}
           autonight
         {% else %}
           {{ am }}
         {% endif %}       
      preset_mode_command_topic: "itho/cmd"
      preset_modes:
       - "low"
       - "medium"
       - "high"
       - "auto"
       - "autonight"
       - "timer1"
       - "timer2"
       - "timer3"

Test via Home Assistant Developer tools -> Services

  1. fan.set_preset_mode ‘high’ - works
  2. fan.set_preset_mode ‘medium’ - works
  3. fan.set_preset_mode ’low’ - works
  4. fan.set_preset_mode ‘autonight’ - works

Automate controls

Goal

Now that we can control the Itho unit, it’s time to implement our improved control algorithm:

Goal:

  1. Ensure that time-average CO₂ stays as close to outdoor level as possible at comfortable ventilation
    1. Ensure that CO₂ never exceeds 1000 ppm for long
    2. Ensure quiet operation of ventilator –> max rpm ~2500
  2. Ensure ventilation never goes below autonight when humidity is above 65%
  3. Ensure ventilation never goes below autonight when bypass is open

Algorithm:

  1. CO₂ >1100ppm: run at high setting (~2900 rpm)
  2. CO₂ 800-1100ppm: run at autonight setting (~1800 rpm)
  3. Bypass open: run at autonight setting (~1800 rpm)
  4. High humidity: run at autonight setting (~1800 rpm)
  5. CO₂ 500-800ppm: run at medium settinfg (~1400 rpm)
  6. CO₂ <500ppm: run at auto setting (~700 rpm)

Boundary conditions:

  1. Don’t change speed unnecessarily –> only trigger when level changed for more than X minutes
  2. Don’t override timer functions used for e.g. getting rid of humidity –> use states.fan.itho_hru_350.attributes.preset_mode != timer1/timer2/timer3

Home Assistant implementation

There’s a few approaches that could work:

  1. Set up timer job, check every 5 min and set mode according to CO₂ - Pro: easy Con: no ’low pass filter’ / could trigger at spiky value
  2. Run automation on CO₂ level and change when it crosses a boundary (i.e. platform: numeric_state) - Pro: can use ‘for’ setting to prevent spurious triggers.
  3. Use value_template to check from_state & to_state. Pro: triggers only on threshold crossing. Con: does not have ’low pass filter’ / could trigger at spiky value
  4. Use wait_for_trigger to return to low speed when ventilation is done. Pro: works nicely for 2 levels. Con: only for 2 levels?
  5. Use filter integration (home-assistant.io) to filter the underlying sensor value, combine with timer automation? Pro: easy, works, robust Con: sends commands more than necessary, overrides user setting

In the end I used option 2 from above, which has now been running well for a few weeks. I split the automation into 4 levels which gives me more fine-grained control (continuous control would still be better but somehow Itho decided against this for the HRU 350). To also trigger on humidity and bypass level, I added a numeric_state trigger for humidity, and a trigger on any change of bypass.

To actuate the Itho unit, I check the conditions one by one, stopping at the lowest ventilation speed allowed.

alias: Itho control override+
description: Control Itho manually based on CO2, humidity, and bypass
trigger:
  - id: auto
    platform: numeric_state
    entity_id: sensor.max_co2
    for:
      hours: 0
      minutes: 5
      seconds: 0
    below: 500
  - id: medium
    platform: numeric_state
    entity_id: sensor.max_co2
    for:
      hours: 0
      minutes: 5
      seconds: 0
    above: 500
    below: 800
  - id: autonight
    platform: numeric_state
    entity_id:
      - sensor.max_co2
    for:
      hours: 0
      minutes: 5
      seconds: 0
    above: 800
    below: 1000
  - id: high
    platform: numeric_state
    entity_id:
      - sensor.max_co2
    for:
      hours: 0
      minutes: 5
      seconds: 0
    above: 1000
  - platform: state
    entity_id:
      - binary_sensor.itho_hru_bypass
  - id: humidity_high
    platform: numeric_state
    entity_id:
      - sensor.max_humidity
    for:
      hours: 0
      minutes: 5
      seconds: 0
    above: 60
  - id: humidity_normal
    platform: numeric_state
    entity_id:
      - sensor.max_humidity
    for:
      hours: 0
      minutes: 5
      seconds: 0
    below: 60
condition:
  - condition: not
    conditions:
      - condition: state
        entity_id: fan.itho_hru_350
        attribute: preset_mode
        state:
          - timer1
          - timer2
          - timer3
action:
  - service: notify.notify
    metadata: {}
    data:
      message: |
        {% if trigger.id == "high" %}
          "high trigger "{{ trigger.id }}
        {% elif trigger.id == "autonight" %}
          "autonight trigger" {{ trigger.id }}
        {% elif states("binary_sensor.itho_hru_bypass") == "on" %}
          "bypass open trigger autonight" {{ trigger.id }}
        {% elif (states("sensor.max_humidity")|default('55')|int > 60) %}
          "humid trigger autonight" {{ trigger.id }}
        {% elif trigger.id == "medium" %}
          "medium trigger" {{ trigger.id }}
        {% elif trigger.id == "auto" %}
          "auto trigger" {{ trigger.id }}
        {% else %}
          "unknown trigger, setting to auto" {{ trigger.id }}
        {% endif %}        
mode: single

#Diy #ESP8266 #Home-Assistant #Home-Improvement #Smarthome