Anyone using Duco Ventilation Communication Print Ethernet module?

There was something with versions of the board, responding different to request. Don’t remember completely. I believe I have the newest version.

I have the following in my yaml:

fan:
  - platform: template
    fans:
      ducobox:
        unique_id: "ducobox_ventilatie_control"
        friendly_name: "Ducobox"
        value_template: >
          {% if states('sensor.ducobox') !=  'AUTO' %}
            on
          {% else %}
            off
          {% endif %}
        turn_on:
          - service: rest_command.ducobox_mode_change
            data:
              nodeId: "1"
              state: MAN3
        turn_off:
          - service: rest_command.ducobox_mode_change
            data:
              nodeId: "1"
              state: AUTO
        preset_modes:
          - "Auto"
          - "Permanent manual 1"
          - "Permanent manual 2"
          - "Permanent manual 3"
          - "Manual 1"
          - "Manual 2"
          - "Manual 3"
        preset_mode_template: >
          {% if states('sensor.ducobox') == "CNT1" %}
            Permanent manual 1
          {% elif states('sensor.ducobox') ==  "CNT2" %}
            Permanent manual 2
          {% elif states('sensor.ducobox') == "CNT3" %}
            Permanent manual 3
          {% elif states('sensor.ducobox') ==  "MAN1" %}
            Manual 1
          {% elif states('sensor.ducobox') == "MAN2" %}
            Manual 2
          {% elif states('sensor.ducobox') ==  "MAN3" %}
            Manual 3
          {% else %}
            Auto
          {% endif %}
        set_preset_mode:
          - service: rest_command.ducobox_mode_change
            data:
              nodeId: "1"
              state: >
                {% if preset_mode == "Permanent manual 1" %}
                  CNT1
                {% elif preset_mode ==  "Permanent manual 2" %}
                  CNT2
                {% elif preset_mode == "Permanent manual 3" %}
                  CNT3
                {% elif preset_mode ==  "Manual 1" %}
                  MAN1
                {% elif preset_mode == "Manual 2" %}
                  MAN2
                {% elif preset_mode ==  "Manual 3" %}
                  MAN3
                {% else %}
                  AUTO
                {% endif %}
#duco api change commands, change the IP adres to your setup
rest_command:
  ducobox_mode_change:
    url: https://192.168.0.112/action/nodes/{{ nodeId }}
    method: POST
    headers:
      accept: "application/json"
      content_type: "application/json"
    payload: '{"Action":"SetVentilationState","Val":"{{ state }}"}'
    verify_ssl: false

@Sikerdebaard Thanks for the integration. i’ve been using this for a few months now. The only thing i’m missing is a way to control the ventilation. I would like to use an external humitity sensor in the bathroom to control the ventilation. Same remark as @wEZ00013 actually :slight_smile:

Thank you for the work already.

1 Like

For anyone using the older Communication Print module I have optimised the Implementation into Home Assistant.

I am using the .yaml with the HA Packages features.

Be sure to include the following into your configuration.yaml:

homeassistant:
  packages: !include_dir_named packages

Link to my GitHub

I have created a duco.yaml inside the packages folder:

fan:
  - platform: template
    fans:
      ducobox:
        unique_id: "ducobox"
        friendly_name: "DucoBox"
        value_template: >
          {% if states('sensor.ducobox') is defined %}
            on
          {% else %}
            off
          {% endif %}
        turn_on:
          action: rest_command.set_ducobox_mode
          data:
            state: "AUTO"
        turn_off:
          action: rest_command.set_ducobox_mode
          data:
            state: "AUTO"
        preset_modes:
          - "Auto"
          - "Away"
          - "Manual 1"
          - "Manual 1 Forced"
          - "Manual 2"
          - "Manual 2 Forced"
          - "Manual 3"
          - "Manual 3 Forced"
        preset_mode_template: >
          {% if states('sensor.ducobox') == "MAN1" %}
            Manual 1
          {% elif states('sensor.ducobox') == "CNT1" %}
            Manual 1 Forced
          {% elif states('sensor.ducobox') == "MAN2" %}
            Manual 2
          {% elif states('sensor.ducobox') == "CNT2" %}
            Manual 2 Forced
          {% elif states('sensor.ducobox') == "MAN3" %}
            Manual 3
          {% elif states('sensor.ducobox') == "CNT3" %}
            Manual 3 Forced
          {% elif states('sensor.ducobox') == "EMPT" %}
            Away
          {% else %}
            Auto
          {% endif %}
        set_preset_mode:
          - action: rest_command.set_ducobox_mode
            data:
              state: >
                {% if preset_mode ==  "Manual 1" %}
                  MAN1
                {% elif preset_mode == "Manual 1 Forced" %}
                  CNT1
                {% elif preset_mode == "Manual 2" %}
                  MAN2
                {% elif preset_mode ==  "Manual 2 Forced" %}
                  CNT2
                {% elif preset_mode ==  "Manual 3" %}
                  MAN3
                {% elif preset_mode == "Manual 3 Forced" %}
                  CNT3
                {% elif preset_mode == "Away" %}
                  EMPT
                {% else %}
                  AUTO
                {% endif %}
        percentage_template: "{{ states('sensor.ducobox_target') }}"

rest:
  - resource: http://192.168.16.18/nodeinfoget?node=1
    scan_interval: 5
    sensor:
      - name: DucoBox
        unique_id: ducobox
        value_template: "{{ value_json.state }}"
        icon: mdi:fan
        json_attributes:
          - devtype
          - location
          - state
          - mode
          - ovrl
          - swversion
          - serialnb
      - name: DucoBox temperature
        unique_id: ducobox_temperature
        value_template: "{{ value_json.temp }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox humidity
        unique_id: ducobox_humidity
        value_template: "{{ value_json.rh }}"
        device_class: humidity
        state_class: measurement
        unit_of_measurement: "%"
      - name: DucoBox target
        unique_id: ducobox_target
        value_template: "{{ value_json.trgt }}"
        state_class: measurement
        unit_of_measurement: "%"
  - resource: http://192.168.16.18/boxinfoget
    scan_interval: 9
    sensor:
      - name: DucoBox temperature ODA
        unique_id: ducobox_tempODA
        value_template: "{{ value_json.EnergyInfo.TempODA / 10 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox temperature SUP
        unique_id: ducobox_tempSUP
        value_template: "{{ value_json.EnergyInfo.TempSUP / 10}}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox temperature ETA
        unique_id: ducobox_tempETA
        value_template: "{{ value_json.EnergyInfo.TempODA /10 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox temperature EHA
        unique_id: ducobox_tempEHA
        value_template: "{{ value_json.EnergyInfo.TempSUP /10 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox bypass
        unique_id: ducobox_bypass
        value_template: "{{ value_json.EnergyInfo.BypassStatus }}"
        state_class: measurement
        unit_of_measurement: "%"
      - name: DucoBox bypass temperature
        unique_id: ducobox_bypass_temp
        value_template: "{{ value_json.EnergyInfo.BypassRequestedTemp / 10 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: DucoBox filter remaining
        unique_id: ducobox_filter
        value_template: "{{ value_json.EnergyInfo.FilterRemainingTime }}"
        device_class: duration
        unit_of_measurement: "d"
  - resource: http://192.168.16.18/nodeinfoget?node=2
    scan_interval: 30
    sensor:
      - name: Woonkamer temperature
        unique_id: woonkamer_temperature
        value_template: "{{ value_json.temp-1.5 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Woonkamer carbon_dioxide
        unique_id: woonkamer_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"
  - resource: http://192.168.16.18/nodeinfoget?node=3
    scan_interval: 30
    sensor:
      - name: Slaapkamer temperature
        unique_id: slaapkamer_temperature
        value_template: "{{ value_json.temp-0.8 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Slaapkamer carbon_dioxide
        unique_id: slaapkamer_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"
  - resource: http://192.168.16.18/nodeinfoget?node=4
    scan_interval: 30
    sensor:
      - name: Sauna temperature
        unique_id: sauna_temperature
        value_template: "{{ value_json.temp-0.8 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Sauna carbon_dioxide
        unique_id: sauna_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"
  - resource: http://192.168.16.18/nodeinfoget?node=5
    scan_interval: 30
    sensor:
      - name: Kantoor temperature
        unique_id: kantoor_temperature
        value_template: "{{ value_json.temp-1.0 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Kantoor carbon_dioxide
        unique_id: kantoor_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"
  - resource: http://192.168.16.18/nodeinfoget?node=6
    scan_interval: 30
    sensor:
      - name: Gameroom temperature
        unique_id: gameroom_temperature
        value_template: "{{ value_json.temp-0.3 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Gameroom carbon_dioxide
        unique_id: gameroom_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"
  - resource: http://192.168.16.18/nodeinfoget?node=7
    scan_interval: 30
    sensor:
      - name: Badkamer temperature
        unique_id: badkamer_temperature
        value_template: "{{ value_json.temp-1.5 }}"
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
      - name: Badkamer carbon_dioxide
        unique_id: badkamer_carbon_dioxide
        value_template: "{{ value_json.co2 }}"
        device_class: carbon_dioxide
        state_class: measurement
        unit_of_measurement: "ppm"

rest_command:
  set_ducobox_mode:
    url: "http://192.168.16.18/nodesetoperstate?node=1&value={{ state }}"

The fan on/off button switches it back to AUTO. The fan percentage shows the actual state, it cannot be used to control the DucoBox. You can set the presets and it will update the state of the Fan in HA.

Change the IP address to the IP of your Duco. You can set how many nodes you have, as you can see I have 6 sensors, node=1 is the DucoBox itself.

Keep in mind I have calibrated the temperature of some of my sensors, if your temperatures are correct you can remove the number value in the value_json.temp .

You are making this more complex then it’s implemented.

The device has a setpoint, as long as you are below the setpoint this thing will report 100%, if you are just a tiny bit above it will report 95, then 90 and so one.
This “mapping” actually relates more with how much power the sensor will ask from the unit then anything else. So if its reporting very bad quality the sensor is asking 100% power, if its reporting very good then it’s not asking 0 or default 10% power.

Regarding CO2, its setpoint is set to 800 by default but this is quite high to be honest. idealy you should be under 500, perfect world would be 200-300.

The issue is that outdoor CO2 is rising every year and now you have an average outdoor of 400. But this is average, around the globe and its expected to go up. In a city this will be higher then average so if you are unlucky you are already sucking in 550ppm making it impossible to maintain 600.

So when looking at recommendations for indoor air, the question is when it was made and for what location. So the only solution would be to read out the actual external value and regulate the internal setpoint based on that but that is not possible with Duco for the moment.

Duco has rolled out a new Public API version V2.5 which has new ModBus and REST API calls. You can now get the exact CO2 PPM values and RH %, where before you only had Indoor Air Quality. For those who have an external temperature sensor, it also show that temperature and for those with a DucoBox energy, it also shows 4 temperatures over the heat exchanger.

Also notice that the limitation for write commands has been expanded from 100 to default 200 +100 for every extra zone.

Read it in the Modbus documentation sheet.

I have implemented the ability to set via the python library listed above and then added this to my branch, which is now awaiting review Adds support for auto discovery and various other improvements by stuartp44 · Pull Request #36 · Sikerdebaard/hacs-ducobox-connector · GitHub.

@Sikerdebaard switched to your component, works like a charm and threw away my efforts :slight_smile: (and all the netatmo stuff I used to have…)

So frustating it does not work for me
I use the url http://my-ip/ and get the follwing error message
Ducobox Connectivity Board

Failed setup, will retry: 404 Client Error: Not Found for url: http://my-ip/info

I can connect throught web browser without any issue
even I can get the return output when issuing the url: http://my-ip/nodeinfoget?node=1

can t connect either

i’m having this exact same issue. I’m running what looks like the very first version of the API. Duco is a bit mysterious about firmware updates, they are not listed on the website and they want installers only to use the app.

Anyway, can someone please post an example of what the /info page looks like? Maybe we can use a fake page to load the data and then use the rest of the endpoints that are actually working.

If you have the original Communication Print see my post: Anyone using Duco Ventilation Communication Print Ethernet module? - #103 by danieldeni

If you have a different version, others in here might be able to help you.

thanks, that does the trick after i corrected the node names and ips for my situation. personally i think it’s a real shame that duco isn’t investing in updates to their software. the webinterface is a joke. and i’m not even talking about the security of that box…

Hi @here,

With the help of this thread I’ve been able to setup a REST API YAML config for my DucoBox Focus, everything works great!

However, something strange happened after a power outage. After the box rebooted, the /boxinfoget endpoint no longer returns the Performance key. I can no longer log PowerNow/PowerMax/PowerAvg (I was comparing PowerNow in a graph with an inline power meter).

Currently the endpoint only returns:

{
  "General": {
    "Time": 1764663564,
    "RFHomeID": "0x00043794",
    "InstallerState": "OPERATIONAL"
  },
  "Calibration": {
    "CalibIsValid": false,
    "CalibState": "IDLE",
    "CalibError": "0x0302"
  },
  "WeatherStation": {
    "Present": false
  }
}

Anybody any clue:

  • What could have caused the box to stop reporting performance data after a reboot?
  • How I might be able to get my data?
  • Is it due to the calibration failing?