How to Openhab Vent-Axia Sentinel Kinetic

Hello all,

I recently made my home Vent-Axia ventilation system smarter with openhab and wanted to share the solution for other newbies who might want to do the same.

Problem statement:
I have a Vent-Axia Sentinel Kinetic V (438342). It is a nice whole apartment ventilation system with three main modes - low, normal and boost ventilation. There is also a way to program a schedule for when the system goes to normal or boost mode. Finally there is a button that allows to switch between modes.

This is all very nice but very little of it is automated. You are basically stuck with some average good enough ventilation level that can cause several problems.:

  1. If set up too low a bathroom without a window (like mine) can take too long to ventilate and cause molding
  2. if set too high in the winter it results in very low apartment humidity (lower 20s %) due to the integrated heat exchange

I wanted a solution where i can set the ventilation to very low and it would automatically boost when taking a shower or other trigger conditions occur. The system itself has a bunch of accessories like humidity sensor, CO2 sensor and wireless trigger buttons but i found it too cumbersome to find a supplier and even worse wire them all.

Solution:
In my solution i use a single proportional input set up as a CO2 sensor from 100 to 2000 ppm - i lie to the system. In fact what i have connected is a Qubino DAC which i control over Zwave and i make the rules in Openhab collecting input from NetAtmo and Zwave sensors.

Step by step
You will need:

  1. Zwave controller and the Qubino DAC to generate the fake 0-10 V proportional input
    1.1. A simple alternative is a switch either Zwave or anything else you can make work in openhab
  2. Some sensor you want to use as a trigger or function input - I use two humidity measurements and a CO2
    2.1. this could also be an on/off light sensor, motion sensor, door contact etc.
  3. a rule in openhab combining your sensor input and sending the message to the Qubino DAC.

Step 1: connect the Qubino DAC to one of the proportional inputs of the Vent-Axia
Using the manuals available online (for ex. https://www.vent-axia.com/product/sentinel-kinetic-v ) connect the Qubino DAC to one of the proportional inputs - teh Vent-Axia already provides the supply 24V and expects the 0-10V from the DAC.

Using the same manual program the chosen proportional input to CO2 with range 200-2000 ppm. This is the widest range available from all options and allows us to make the vent-axia very stupid and openhab very smart :).

I also use the following setting:

  1. Low ventilation - 8% - effectively not used
  2. Normal ventilation - 10%
  3. Boost 50%

The vent-axia calculated proportional ventilation from normal to high and ignores low settings.

Step 2: Define your items
Pair your sensors and DAC according to the instructions for teh Zwave binding and whatever else you might want to use.
In my case this looks like this:

Group        Apt_Ventilation_Triggers      "Ventilation"                 <flow>           (Apt_Facility)                             [""]

Number       Apt_LR_Humidity               "Humidity [%.1f %%]"          <humidity>       (Apt_Facility,Apt_Ventilation_Triggers)    [""]   {channel="netatmo:NAMain:fe908471:70ee50369ad0:Humidity"}
Number       Apt_LR_CO2                    "Carbon Dioxide [%.1f ppm]"   <carbondioxide>  (Apt_Facility,Apt_Ventilation_Triggers)    [""]   {channel="netatmo:NAMain:fe908471:70ee50369ad0:Co2"}
Number       Apt_BTH_Humidity              "Humidity [%.1f %%]"          <humidity>       (Apt_Facility,Apt_Ventilation_Triggers)    [""]   {channel="zwave:device:06ed5ae0:node8:sensor_relhumidity"}
Switch       Apt_Vent_Manual_Override      "30 Mins Manual Boost"        <high_flow>      (Apt_Facility,Apt_Ventilation_Triggers)    [""]   {expire="30m,command=OFF" }

Dimmer       Apt_Ventilation_DAC           "Ventilation DAC [%d %%]"     <heating>        (Apt_Facility)                             [""]   {channel="zwave:device:06ed5ae0:node4:switch_dimmer"}

Number       Apt_DAC_Voltage               "DAC Voltage [%.2f V]"        <energy>         (Apt_Facility)
Number       Apt_Ventilation_Flow          "Ventilation [%d %%]"         <fan>            (Apt_Facility)
Number       Apt_Initial_Manual_Boost      "Initial Manual Boost"        <fan>            (Apt_Ventilation)

The group at the top is used to define which items are triggers for the ventilation rule to be re-evaluated. As can be seen this is rather random selection of inputs from the second bunch of four items. The living room humidity and CO2 come from the NetAtmo binding, the bathroom humidity comes from the Zwave sensor and the last manual switch is a virtual item used only in the app GUI to manually trigger 30 mins boost. The manual trigger uses the Expire binding to define auto-off after 30 mins and here is the place to change the timer if needed.

The next item is the Zwave Qubino dimmer used to control the vent-axia.

The last two items are virtual items holding the calculated DAC voltage and the calculated ventilation flow in percent. This is nice to see in the GUI.

Step 3 - define the rules

I use the following rule to make the necessary calculations and submit the desired ventilation result.

import java.Math

val NORMAL_VENT = 10.0 // Vent-Axia Normal setting
val BOOST_VENT = 50.0 // Vent-Axia Boost setting
val MIN_LEVEL = 10.0 // Vent-Axia Normal setting for the proportional input
val MAX_LEVEL = 100.0 // Vent-Axia Boost setting for the proportional input

val HUMIDITY_THRESHOLD = 60.0
val HUMIDITY_HIGH_LIMIT = 80.0

val CO2_THRESHOLD = 700
val CO2_HIGH_LIMIT = 1000

rule "update Apt_Ventilation_DAC"
when
    Member of Apt_Ventilation_Triggers received update or Member of Apt_Ventilation_Triggers received command
then
    var FINAL = 0.0
    var VOLTAGE = 0.0
    var FLOW = 0.0
    var MANUAL = 0.0
    var temp_val_1 = 0.0
    var temp_val_2 = 0.0
    var temp_val_3 = 0.0

    val dimVal = Apt_Ventilation_DAC.state as Number
    val float dimAsFloat = dimVal.floatValue

    var FLOW_SLOPE = (BOOST_VENT - NORMAL_VENT) / (MAX_LEVEL - MIN_LEVEL)
    var FLOW_CONST = NORMAL_VENT - (FLOW_SLOPE*MIN_LEVEL)

    if (Apt_Vent_Manual_Override.state == ON) {
        if (Apt_Initial_Manual_Boost.state == 1) {
            MANUAL = 100.0
            Apt_Initial_Manual_Boost.postUpdate(0)
        }
        else {
            MANUAL = dimAsFloat
            Apt_Initial_Manual_Boost.postUpdate(0)
        }
    }
    else {
        var HUMIDITY_SLOPE = (MAX_LEVEL - MIN_LEVEL) / (HUMIDITY_HIGH_LIMIT - HUMIDITY_THRESHOLD)
        var HUMIDITY_CONST = MIN_LEVEL - (HUMIDITY_SLOPE*HUMIDITY_THRESHOLD)
        
        var Number Apt_LR_Humidity_Val = 0.0
        if (Apt_LR_Humidity.state!=NULL) Apt_LR_Humidity_Val = Apt_LR_Humidity.state as Number

        if (Apt_LR_Humidity_Val < HUMIDITY_THRESHOLD) temp_val_1 = 0.0
        else temp_val_1 = HUMIDITY_SLOPE * (Apt_LR_Humidity_Val.floatValue) + HUMIDITY_CONST
        
        if (temp_val_1>100) temp_val_1 = 100.0

        var Number Apt_BTH_Humidity_Val = 0.0
        if (Apt_BTH_Humidity.state!=NULL) Apt_BTH_Humidity_Val = Apt_BTH_Humidity.state as Number

        if (Apt_BTH_Humidity_Val < HUMIDITY_THRESHOLD) temp_val_2 = 0.0
        else temp_val_2 = HUMIDITY_SLOPE * (Apt_BTH_Humidity_Val.floatValue) + HUMIDITY_CONST
        
        if (temp_val_2>100) temp_val_2 = 100.0

        var CO2_SLOPE = (MAX_LEVEL-MIN_LEVEL)/(CO2_HIGH_LIMIT-CO2_THRESHOLD)
        var CO2_CONST = MIN_LEVEL - CO2_SLOPE*CO2_THRESHOLD
        var Number Apt_LR_CO2_Val = 0.0

        if (Apt_LR_CO2.state!=NULL) Apt_LR_CO2_Val = Apt_LR_CO2.state as Number
        
        if (Apt_LR_CO2_Val < CO2_THRESHOLD) temp_val_3 = 0.0
        else temp_val_3 = CO2_SLOPE * (Apt_LR_CO2_Val.floatValue) + CO2_CONST
        
        if (temp_val_3>100) temp_val_3 = 100.0

        Apt_Initial_Manual_Boost.postUpdate(1)
    }

    FINAL = Math.max(Math.max(temp_val_1, Math.max(temp_val_2,temp_val_3)),MANUAL)

    VOLTAGE = FINAL / 10.0

    if (FINAL==0.0) FLOW = NORMAL_VENT
    else FLOW = FLOW_SLOPE * FINAL + FLOW_CONST

    Apt_DAC_Voltage.postUpdate(VOLTAGE)
    Apt_Ventilation_Flow.postUpdate(FLOW)
    Apt_Ventilation_DAC.sendCommand(FINAL)

end

At the top is the java import to support taking of a max between two values.
Following are the definitions from the Vent-axia settings. The min and max levels correspond to 200-2000 ppm of the CO2 proportional input. This is also equivalent to 1-10V. This means that the Vent-Axia will not react to any input lower than 1 V and ventilation will be kept to Low/Normal level. This is also why i selected the CO2 input as the widest - for example teh humidity is 25%-90% limiting the range from 2.5V to 9V.

Following are the humidity and CO2 thresholds - you can freely adjust these. The ventilation will be kept Normal at 10% below the threshold and will be full speed boosted at 50% at the high limit. Above the high limit it stays at max 50% boost.

The rule starts with declaration that any update (typically sensors) or command (typically switches) to the ventilation triggers will re-evaluate the conditions.

At the beginning of the rule i reset a bunch of variables

Then the ventilation flow is defined as a linear function slope and constant based on the programmed Vent-Axia parameters.

The next IF statements checks if the trigger came from the manual boost switch. If yes it goes directly to max boost of 50% ventilation and skips the sensors. This also allows to manually boost to max and then adjust the DAC to some other manual value. As long as the timer has not expired any sensor updates will be ignored and the ventilation will be set manually.

In the else part i calculate the linear functions of the humidity for both sensors and the CO2 based on the defined limits above. For each sensor i get an estimate on the DAC value.

Finally i take a max of those and only apply the highest since this is worst case scenario and update the virtual items for display purposes.

Step 4 (Optional) - configure persistence and make grafana plots
Simply follow this post: https://community.openhab.org/t/influxdb-grafana-persistence-and-graphing/13761#graph-as-openhab-webview-js

Step 5 (Optional) - show on sitemaps
I used this sitemap code to do so:

	Frame label="Facility Insight" icon="settings" {
		Group item=Apt_Facility {
			Frame label="Ventilation System" {
				Switch      item=Apt_Vent_Manual_Override   label="30 Min. Manual Boost"                                 icon="high_flow"
				Text        item=Apt_Ventilation_Flow       label="Air Flow [%d %%]"                                     icon="fan"
				Slider      item=Apt_Ventilation_DAC        label="Ventilation DAC [%d %%]"                              icon="heating"         sendFrequency= 50
				Text        item=Apt_DAC_Voltage            label="DAC Voltage [%.2f V]"                                 icon="energy"
				Text        item=Apt_LR_Humidity            label="Living Room Humidity [%.1f %%]"                       valuecolor=[Apt_LR_Humidity=="NULL"="lightgray",Apt_LR_Humidity>=70="red",>60="orange",>=40="green",>=30="orange",<30="red"]
				Text        item=Apt_BTH_Humidity           label="Bathroom Humidity [%.1f %%]"                          valuecolor=[Apt_BTH_Humidity=="NULL"="lightgray",Apt_BTH_Humidity>=70="red",>60="orange",>=40="green",>=30="orange",<30="red"]
				Text        item=Apt_LR_CO2                                                                              valuecolor=[Apt_LR_CO2=="NULL"="lightgray",Apt_LR_CO2>=800="red",>700="orange",>=600="yellow",<600="green"]
				Webview url="/static/ventilation.html" height=12

The final result is:

System details:
I use:

  1. Openhab 2.4 running in a docker container on Synology NAS
  2. mariadb that comes with the synology NAS
  3. grafana running in a docker container and set up with iframes as described here
  4. Netatmo home weather station for humidity and CO2 measurements
  5. Aeotec Multisensor 6 for bathroom humidity
  6. Qubino 24V DAC for proportional input - (http://qubino.com/products/flush-dimmer0-10v/)
  7. Zwave controller - https://aeotec.com/z-wave-usb-stick
3 Likes