We recently got balanced ventilation installed, and discovered that our ventilation unit has Modbus TCP/IP support. I even found a thread on the Home Assistant community talking about interfacing the C6 controller, which our unit has, into Home Assistant 😃

Here is my implementation 👇

Table of contents

Network

The ventilation unit is in the attic, and needs cabled network. Not a problem — I was already working on getting two CAT6 cables and a fiber up there 🙂

I’ll explain getting network to the attic in a future post.

Ventilation unit, next to network cabinet
MikroTik switch inside network cabinet

Home Assistant

What follows is my Home Assistant configuration, for the ventilation unit itself, templates, scripts, inputs, and automations that I use. There is a lot going on here, but if you are familiar with Home Assistant; it should make some sense to you 🙂

Modbus

Getting the actual data from the ventilation unit is done with Modbus over TCP, as configured below. I found a document listing all Modbus registers and used that to get the data I needed. I also found a very useful thread on the Home Assistant community.

Modbus addressing in Home Assistant starts at 0, but the document starts at 1. So you need to subtract 1 from all addresses: 1 –> 0, 10 –> 9, etc.

The scale and precision properties are used to convert the values received from the unit — it’s all documented on the Home Assistant Modbus integration page.

I’m pulling most values every 30 seconds, set with scan_interval, while some statistical data is pulled every 5 minutes (300 seconds).

Some values take up more than one Modbus register; for those you will need to set the count property.

modbus:
  - name: komfovent
    type: tcp
    host: 192.168.1.38
    port: 502

    switches:
      - name: "Komfovent Eco Mode"
        address: 2
        command_on: 1
        command_off: 0
        verify:

    sensors:
      - name: "Komfovent Power"
        address: 0
        scan_interval: 30
      - name: "Komfovent Eco"
        address: 2
        scan_interval: 30
      - name: "Komfovent Auto"
        address: 3
        scan_interval: 30
      - name: "Komfovent Mode"
        address: 4
        scan_interval: 30
      - name: "Komfovent Status bit mask"
        address: 899
        scan_interval: 30
      - name: "Komfovent Supply temperature 'C"
        address: 901
        scan_interval: 30
        unit_of_measurement: °C
        scale: 0.1
        precision: 1
      - name: "Komfovent Extract temperature 'C"
        address: 902
        scan_interval: 30
        unit_of_measurement: °C
        scale: 0.1
        precision: 1
      - name: "Komfovent Supply Fan Intensivity '%"
        address: 909
        scan_interval: 30
        unit_of_measurement: '%'
        scale: 0.1
        precision: 1
      - name: "Komfovent Extract Fan Intensivity '%"
        address: 910
        scan_interval: 30
        unit_of_measurement: '%'
        scale: 0.1
        precision: 1
      - name: "Komfovent Outdoor temperature 'C"
        address: 903
        scan_interval: 30
        unit_of_measurement: °C
        scale: 0.1
        precision: 1
      - name: "Komfovent Filter Impurity, %"
        address: 916
        scan_interval: 300
        unit_of_measurement: '%'
      - name: "Komfovent Heating power, W"
        address: 912
        scan_interval: 30
        unit_of_measurement: W
      - name: "Komfovent Power consumption, W"
        address: 920
        scan_interval: 30
        unit_of_measurement: W
      - name: "Komfovent Power consumption Month, kWh"
        address: 928
        scan_interval: 300
        unit_of_measurement: kWh
        count: 2
        precision: 0
        scale: 0.001
      - name: "Komfovent Heating Recovery Month, kWh"
        address: 940
        scan_interval: 300
        unit_of_measurement: kWh
        count: 2
        precision: 0
        scale: 0.001
      - name: "Komfovent Heat Recovery, W"
        address: 922
        scan_interval: 30
        unit_of_measurement: W
      - name: "Komfovent Heat exchanger efficiency, %"
        address: 923
        scan_interval: 30
        scale: 1
        precision: 0
        unit_of_measurement: '%'
      - name: "Komfovent Energy saving, %"
        address: 924
        scan_interval: 30
        unit_of_measurement: '%'

Templates

To convert some of the values received I have a few templates:

  • Komfovent Mode Text
    • Converts the received mode integer to a human readable string
  • Ventilation info
  • Komfovent status …
    • Converts the received status bit mask into boolean status states
  • Komfovent mode is …
    • Converts mode into four boolean status entities
    • Used for button states on the Lovelace dashboard
template:
  - sensor:
    - name: "Komfovent Mode Text"
      state: >
        {%- if states.sensor.komfovent_mode.state | int == 0 %}
          Standby
        {%- elif states.sensor.komfovent_mode.state | int == 1 %}
          Away
        {%- elif states.sensor.komfovent_mode.state | int == 2 %}
          Normal
        {%- elif states.sensor.komfovent_mode.state | int == 3 %}
          Intensive
        {%- elif states.sensor.komfovent_mode.state | int == 4 %}
          Boost
        {%- elif states.sensor.komfovent_mode.state | int == 5 %}
          Kitchen
        {%- elif states.sensor.komfovent_mode.state | int == 6 %}
          Fireplace
        {%- elif states.sensor.komfovent_mode.state | int == 7 %}
          Override
        {%- elif states.sensor.komfovent_mode.state | int == 8 %}
          Holiday
        {%- elif states.sensor.komfovent_mode.state | int == 9 %}
          Air quality
        {%- elif states.sensor.komfovent_mode.state | int == 10 %}
          Off
        {% else %}
          fail
        {%- endif %}        
      icon: hass:account-cog
    - name: "Ventilation info"
      state: "{{ states.sensor.komfovent_mode_text.state }}"
      attributes:
        heat: "{{ states.sensor.komfovent_heating_power_w.state }}"
        outside: "{{ states.sensor.komfovent_outdoor_temperature_c.state }}"

  - binary_sensor:
    - name: "Komfovent status Starting"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(1) > 0 }}"
    - name: "Komfovent status Stopping"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(2) > 0 }}"
    - name: "Komfovent status Fan"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(4) > 0 }}"
    - name: "Komfovent status Rotor"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(8) > 0 }}"
    - name: "Komfovent status Heating"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(16) > 0 }}"
    - name: "Komfovent status Cooling"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(32) > 0 }}"
    - name: "Komfovent status Heating denied"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(64) > 0 }}"
    - name: "Komfovent status Cooling denied"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(128) > 0 }}"
    - name: "Komfovent status Flow down"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(256) > 0 }}"
    - name: "Komfovent status Free heating"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(512) > 0 }}"
    - name: "Komfovent status Free cooling"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(1024) > 0 }}"
    - name: "Komfovent status Alarm fail"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(2048) > 0 }}"
    - name: "Komfovent status Alarm warning"
      state: "{{ states.sensor.komfovent_status_bit_mask.state | int | bitwise_and(4096) > 0 }}"
    - name: "Komfovent mode is Away"
      state: "{{ states.sensor.komfovent_mode.state | int == 1 }}"
    - name: "Komfovent mode is Normal"
      state: "{{ states.sensor.komfovent_mode.state | int == 2 }}"
    - name: "Komfovent mode is Intensive"
      state: "{{ states.sensor.komfovent_mode.state | int == 3 }}"
    - name: "Komfovent mode is Boost"
      state: "{{ states.sensor.komfovent_mode.state | int == 4 }}"

Scripts

For changing states on the ventilation unit, aka. writing registers, I use scripts:

  • Komfovent mode Away/Normal/Intensive/Boost
    • Changes the ventilation mode
  • Komfovent temperature night/day time
    • Sets night or day time temperatures
    • Temperature is set per mode, so three registers are written; normal, intensive, and fireplace mode
    • There is a one second delay between each mode register write
script:
  komfovent_set_away:
    alias: Komfovent mode Away
    sequence:
      - service: modbus.write_register
        data:
          address: 4
          hub: komfovent
          unit: 1
          value: 1
  komfovent_set_normal:
    alias: Komfovent mode Normal
    sequence:
      - service: modbus.write_register
        data:
          address: 4
          hub: komfovent
          unit: 1
          value: 2
  komfovent_set_intensive:
    alias: Komfovent mode Intensive
    sequence:
      - service: modbus.write_register
        data:
          address: 4
          hub: komfovent
          unit: 1
          value: 3
  komfovent_set_boost:
    alias: Komfovent mode Boost
    sequence:
      - service: modbus.write_register
        data:
          address: 4
          hub: komfovent
          unit: 1
          value: 4

  komfovent_set_night_temp:
    alias: Komfovent temperature night time
    variables:
      temperature: "{{ ((states.input_number.ventilation_set_night_temp.state | float) * 10) | int }}"
    sequence:
      - service: modbus.write_register
        data:
          address: 109 # Normal
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"
      - delay: 1
      - service: modbus.write_register
        data:
          address: 115 # Intensive
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"
      - delay: 1
      - service: modbus.write_register
        data:
          address: 134 # Fireplace
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"
  komfovent_set_day_temp:
    alias: Komfovent temperature day time
    variables:
      temperature: "{{ ((states.input_number.ventilation_set_day_temp.state | float) * 10) | int }}"
    sequence:
      - service: modbus.write_register
        data:
          address: 109 # Normal
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"
      - delay: 1
      - service: modbus.write_register
        data:
          address: 115 # Intensive
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"
      - delay: 1
      - service: modbus.write_register
        data:
          address: 134 # Fireplace
          hub: komfovent
          unit: 1
          value: "{{ temperature }}"

Inputs

I use a few inputs for the scripts and automations:

  • Komfovent auto away
    • A manual switch to enable/disable the auto away automation
  • Komfovent away/normal/night/day time
    • Time inputs for changing the mode, and adjusting set temperatures
  • Komfovent temperature night/day time
    • Temperature inputs for night and day time
    • Range from 15 –> 25, with steps of 0,1
input_boolean:
  ventilation_auto_away:
    name: Komfovent auto away
    icon: mdi:calendar-clock

input_datetime:
  ventilation_set_away_time:
    name: Komfovent away time
    has_date: false
    has_time: true
    icon: mdi:home-export-outline
  ventilation_set_normal_time:
    name: Komfovent normal time
    has_date: false
    has_time: true
    icon: mdi:home-account
  ventilation_set_night_time:
    name: Komfovent night time
    has_date: false
    has_time: true
    icon: mdi:weather-night
  ventilation_set_day_time:
    name: Komfovent day time
    has_date: false
    has_time: true
    icon: mdi:weather-sunny

input_number:
  ventilation_set_night_temp:
    name: Komfovent temperature night time
    min: 15
    max: 25
    step: 0.1
    unit_of_measurement: °C
    icon: mdi:weather-night
  ventilation_set_day_temp:
    name: Komfovent temperature day time
    min: 15
    max: 25
    step: 0.1
    icon: mdi:weather-sunny
    unit_of_measurement: °C

Automations

Automations is really where the magic happens 🚀

Ventilation mode Intensive/Normal

We normally run the system in Normal mode, but if the CO₂ level in either the living room, or on the 2nd floor, goes above 1000 ppm for 15 minutes; the mode is changed to Intensive. Meaning that the fans increase from 50% to 70%.

After the CO₂ level drops below 1000 ppm, in both area, for 15 minutes; it returns to Normal mode.

To minimize interference with other modes; it only changes if the current mode is either Normal or Intensive, respectively.

Ventilation auto Away/Normal

On weekday mornings, the system is put in Away mode. Then; in the afternoon it is set back to Normal.

Both away and normal times are set by input_datetime entities, currently set to 8:00 and 15:00.

But, during Away mode; if the CO₂ level in either the living room, or on the 2nd floor, goes above 800 ppm; it will immediately change back to Normal mode. This prevents bad air quality if people are home during a weekday.

To minimize interference with other modes; it only changes if the current mode is either Normal or Away, respectively.

Ventilation auto time Night/Day

I’m experimenting with lowering the temperature during the night, and on weekdays when the house is empty.

Both times and temperatures are adjustable with input_datetime and input_number entities. Currently I have it set for 20°C at night, and 22° at day time. Night time is set to start at 23:00, and end at 9:00.

But since Away mode is active during weekdays, and the temperature is set per mode, the day time temperature doesn’t become active until Normal mode is set, which currently happens at 15:00.

- id: '1635867520743'
  alias: Ventilation mode Intensive
  description: ''
  trigger:
  - platform: numeric_state
    entity_id: sensor.netatmo_stue_co2
    above: '1000'
    for:
      hours: 0
      minutes: 15
      seconds: 0
      milliseconds: 0
  - platform: numeric_state
    entity_id: sensor.netatmo_2_etg_co2
    above: '1000'
    for:
      hours: 0
      minutes: 15
      seconds: 0
      milliseconds: 0
  condition:
  - condition: state
    entity_id: sensor.komfovent_mode
    state: '2'
  action:
  - service: script.komfovent_set_intensive
  mode: single

- id: '1635867596635'
  alias: Ventilation mode Normal
  description: ''
  trigger:
  - platform: numeric_state
    entity_id: sensor.netatmo_stue_co2
    below: '1000'
    for:
      hours: 0
      minutes: 15
      seconds: 0
      milliseconds: 0
  - platform: numeric_state
    entity_id: sensor.netatmo_2_etg_co2
    below: '1000'
    for:
      hours: 0
      minutes: 15
      seconds: 0
      milliseconds: 0
  condition:
  - condition: and
    conditions:
    - condition: numeric_state
      entity_id: sensor.netatmo_stue_co2
      below: '1000'
    - condition: numeric_state
      entity_id: sensor.netatmo_2_etg_co2
      below: '1000'
    - condition: state
      entity_id: sensor.komfovent_mode
      state: '3'
  action:
  - service: script.komfovent_set_normal
  mode: single

- id: '1635968110386'
  alias: Ventilation auto Away
  description: ''
  trigger:
  - platform: time
    at: input_datetime.ventilation_set_away_time
  condition:
  - condition: and
    conditions:
    - condition: state
      entity_id: input_boolean.ventilation_auto_away
      state: 'on'
    - condition: time
      weekday:
      - mon
      - tue
      - wed
      - thu
      - fri
    - condition: state
      entity_id: sensor.komfovent_mode
      state: '2'
  action:
  - service: script.komfovent_set_away
  mode: single

- id: '1635968206887'
  alias: Ventilation auto Normal
  description: ''
  trigger:
  - platform: time
    at: input_datetime.ventilation_set_normal_time
  - platform: numeric_state
    entity_id: sensor.netatmo_stue_co2
    above: '800'
    for:
      hours: 0
      minutes: 0
      seconds: 0
      milliseconds: 0
  - platform: numeric_state
    entity_id: sensor.netatmo_2_etg_co2
    above: '800'
    for:
      hours: 0
      minutes: 0
      seconds: 0
      milliseconds: 0
  condition:
  - condition: and
    conditions:
    - condition: state
      entity_id: input_boolean.ventilation_auto_away
      state: 'on'
    - condition: time
      weekday:
      - mon
      - tue
      - wed
      - thu
      - fri
    - condition: state
      entity_id: sensor.komfovent_mode
      state: '1'
  action:
  - service: script.komfovent_set_normal
  mode: single

- id: '1636644751808'
  alias: Ventilation auto time Night
  description: ''
  trigger:
  - platform: time
    at: input_datetime.ventilation_set_night_time
  condition:
  - condition: state
    entity_id: input_boolean.ventilation_auto_away
    state: 'on'
  action:
  - service: script.komfovent_set_night_temp
  mode: single

- id: '1636644785089'
  alias: Ventilation auto time Day
  description: ''
  trigger:
  - platform: time
    at: input_datetime.ventilation_set_day_time
  condition:
  - condition: state
    entity_id: input_boolean.ventilation_auto_away
    state: 'on'
  action:
  - service: script.komfovent_set_day_temp
  mode: single

Lovelace

Lovelace Climate dashboard

Below is the configuration for my Ventilation card. I won’t go into details here — but looking at the image above, and the configuration below, you should get an understanding of what is happening.

The image has one entity that is not present in the configuration below; Fireplace timer. This will be described in more details in the next blog post 🙂
type: vertical-stack
title: Ventilation
cards:
  - type: horizontal-stack
    cards:
      - type: entity
        entity: sensor.komfovent_mode_text
        name: Mode
      - type: entity
        entity: sensor.komfovent_heat_recovery_w
        icon: mdi:refresh-circle
        name: Recovery
  - type: horizontal-stack
    cards:
      - type: gauge
        entity: sensor.komfovent_heating_power_w
        min: 0
        max: 1000
        name: Heating power
      - type: gauge
        entity: sensor.komfovent_energy_saving
        min: 0
        max: 100
        name: Energy saving
      - type: gauge
        entity: sensor.komfovent_heat_exchanger_efficiency
        min: 0
        max: 100
        name: Exchanger eff.
  - type: horizontal-stack
    cards:
      - type: button
        icon: hass:home-export-outline
        name: Away
        tap_action:
          action: more-info
        show_state: false
        hold_action:
          action: call-service
          service: script.komfovent_set_away
          service_data: {}
          target: {}
        entity: binary_sensor.komfovent_mode_is_away
      - type: button
        tap_action:
          action: more-info
        hold_action:
          action: call-service
          service: script.komfovent_set_normal
          service_data: {}
          target: {}
        icon: hass:home-account
        name: Normal
        entity: binary_sensor.komfovent_mode_is_normal
      - type: button
        tap_action:
          action: more-info
        icon: hass:weather-windy
        hold_action:
          action: call-service
          service: script.komfovent_set_intensive
          service_data: {}
          target: {}
        name: Intensive
        entity: binary_sensor.komfovent_mode_is_intensive
      - type: button
        tap_action:
          action: more-info
        name: Boost
        icon: hass:fan
        hold_action:
          action: call-service
          service: script.komfovent_set_boost
          service_data: {}
          target: {}
        show_icon: true
        show_name: true
        entity: binary_sensor.komfovent_mode_is_boost
  - type: entities
    entities:
      - entity: switch.komfovent_eco_mode
        name: Economy
        icon: mdi:piggy-bank-outline
      - entity: sensor.komfovent_supply_temperature_c
        name: Supply temperature
        icon: hass:home-import-outline
      - entity: sensor.komfovent_extract_temperature_c
        name: Exhaust temperature
        icon: hass:home-export-outline
      - entity: sensor.komfovent_outdoor_temperature_c
        name: Outdoor temperature
        icon: hass:weather-partly-cloudy
      - entity: sensor.komfovent_supply_fan_intensivity
        name: Supply fan
        icon: hass:home-import-outline
      - entity: sensor.komfovent_extract_fan_intensivity
        name: Exhaust fan
        icon: hass:home-export-outline
      - entity: sensor.komfovent_filter_impurity
        name: Filter clogging
        icon: hass:air-filter
      - entity: sensor.komfovent_power_consumption_w
        name: Power consumption
        icon: mdi:transmission-tower-export
      - entity: sensor.komfovent_power_consumption_month_kwh
        icon: mdi:calendar-end
        name: Power usage, month
      - entity: sensor.komfovent_heating_recovery_month_kwh
        icon: mdi:calendar-refresh
        name: Heat recovery, month
    state_color: false
    show_header_toggle: false
  - type: entity-filter
    entities:
      - entity: binary_sensor.komfovent_status_starting
        name: Starting
        icon: hass:play
      - entity: binary_sensor.komfovent_status_stopping
        name: Stopping
        icon: hass:stop
      - entity: binary_sensor.komfovent_status_fan
        name: Fan
        icon: hass:fan
      - entity: binary_sensor.komfovent_status_rotor
        name: Rotor
        icon: hass:autorenew
      - entity: binary_sensor.komfovent_status_heating
        name: Heating
        icon: hass:radiator
      - entity: binary_sensor.komfovent_status_cooling
        name: Cooling
      - entity: binary_sensor.komfovent_status_heating_denied
        name: Heat denied
        icon: hass:radiator-off
      - entity: binary_sensor.komfovent_status_cooling_denied
        name: Cooling denied
      - entity: binary_sensor.komfovent_status_flow_down
        name: Low flow
        icon: hass:fan-minus
      - entity: binary_sensor.komfovent_status_free_heating
        name: Free heat
        icon: hass:thermometer-plus
      - entity: binary_sensor.komfovent_status_free_cooling
        name: Free cooling
        icon: hass:thermometer-minus
      - entity: binary_sensor.komfovent_status_alarm_fail
        name: Fail
        icon: hass:alert
      - entity: binary_sensor.komfovent_status_alarm_warning
        name: Warning
        icon: hass:alert
    state_filter:
      - 'on'
    card:
      type: glance
      state_color: false
      show_state: false
      title: Status
  - type: entities
    entities:
      - entity: input_boolean.ventilation_auto_away
        name: Enable
        icon: hass:calendar-clock
      - entity: input_datetime.ventilation_set_away_time
        name: Away mode (weekdays)
        icon: hass:home-export-outline
      - entity: input_datetime.ventilation_set_normal_time
        name: Normal mode (weekdays)
        icon: hass:home-account
    title: Scheduling

Dakboard

Dakboard screenshot

We have a Dakboard digital calendar in the kitchen, where I also show some data from Home Assistant.

From the ventilation system; three values are shown:

  • Mode, in a human readable string
  • Power used by the electric heater, in watts
  • Outside supply air temperature

I have a blog post where I explain how I get data from Home Assistant, into Dakboard.

Ending note

Interfacing the ventilation system with Home Assistant has opened up so many possibilities, and allows me to use the system in clever ways 🙂 I will explore further in future blog posts 🖖