Lunos Ventilation Fan - ESPHome

Ventilation fans are pretty common in newer buildings, at least in Germany.
We have E2 series ventilation fans from Lunos installed in our house and I wanted to get them integrated into HA. The only published way I was able to find is emulating the switch usually connected to the 5UNI/FT fan controller. However, looking into the data sheets, all Lunos fan controller, including the 5UNI/FT, can be controlled using an analog 0-10V signal. This has the advantage of having 8 speed settings available instead of 4 and being able to activate the summer ventilation setting (deactivating heat recovery).

After some trial and error, I am now using an I2C 0-10V DAC and an ESP8266 running ESPHome to control my fans in HA. Within ESPHome, I am using the SpeedFan component and two custom outputs. One custom float output for the speed state of the fans and one binary output for the oscillation state of the fans (heat recovery or no heat recovery).

Using the SpeedFan component and the two custom outputs probably is not the most elegant way to implement this, but it works :slight_smile:

Notes:
Initially I was using different PWM to 0-10V modules bought from Amazon and Aliexpress to generate the 0-10V signal, but found them to be unreliable and a pain to calibrate, since their output voltages are highly dependent on the stability of the input voltage. Since the actual input voltage can change quite significantly, depending on the load of the fans and cable length if using the 12V fan voltage to power the PWM module, I switched to the I2C DAC from DFRobot which is much more stable.

The two files below are needed if you want to copy what I did.

Custom ESPHome outputs (CustomLunosOutput10V.h):

#include "esphome.h"
using namespace esphome;
#include "DFRobot_GP8403.h"
DFRobot_GP8403 dac(&Wire,0x58);

// Custom float output for exposing Lunos float state via DAC, taking the binary oscillation state (CustomBinaryLunosFanOutput) into account
class CustomFloatLunosFanOutput : public Component, public FloatOutput {
 public:
  void setup() override {
  // set the DAC output range as 0-10V
  dac.setDACOutRange(dac.eOutputRange10V);
  }

  void write_state(float state) override {
    int DAC0 = 0;
    // fans not active and/or summer ventilation not active (heat recovery active)
    if (id(lunos_oscillating_bool) == true || state == 0) {
      // Convert state to DAC0 output in mV
      DAC0 = (4000 * state + 750);
    }
    // active summer ventilation (no heat recovery)
    else {
      // Convert state to DAC0 output in mV
      DAC0 = (4000 * state + 5750);
    }
    // set DAC0 and save value to chip
    dac.setDACOutVoltage(DAC0,0);
    // write state to global variable lunos_state_float
    id(lunos_state_float) = state;
  }
};

// Custom binary output for exposing Lunos binary oscillation state
class CustomBinaryLunosFanOutput : public Component, public BinaryOutput {
 public:
  void setup() override {
  }

  void write_state(bool oscillating) override {
    // write current oscillation state to global variable lunos_oscillating_bool
    id(lunos_oscillating_bool) = oscillating;
    int DAC0 = 0;
    // fans not active and/or summer ventilation not active (heat recovery active)
    if (id(lunos_oscillating_bool) == true || id(lunos_state_float) == 0) {
      // Convert state to DAC0 output in mV
      DAC0 = (4000 * id(lunos_state_float) + 750);
    }
    // active summer ventilation (no heat recovery)
    else {
      // Convert state to DAC0 output in mV
      DAC0 = (4000 * id(lunos_state_float) + 5750);
    }
    // set DAC0 and save value to chip
    dac.setDACOutVoltage(DAC0,0);
  }
};

ESPHome .yaml file

esphome:
  name: lunos-dac
  libraries:
    - "Wire"
    - "DFRobot_GP8403"
  includes:
    - CustomLunosOutput10V.h

!!!!!...Enter further ESPHome config here..!!!!!

i2c:
  sda: 4
  scl: 5
  scan: false
  id: i2c_bus

globals:
- id: lunos_oscillating_bool
  type: bool
  restore_value: no
  initial_value: 'true'
  
- id: lunos_state_float
  type: float
  restore_value: no
  initial_value: '0.25'

output:
- platform: custom
  type: float
  lambda: |-
    auto custom_float_lunos_fan_output = new CustomFloatLunosFanOutput();
    App.register_component(custom_float_lunos_fan_output);
    return {custom_float_lunos_fan_output};

  outputs:
    id: lunos_dac_float

- platform: custom
  type: binary
  lambda: |-
    auto custom_binary_lunos_fan_output = new CustomBinaryLunosFanOutput();
    App.register_component(custom_binary_lunos_fan_output);
    return {custom_binary_lunos_fan_output};

  outputs:
    id: lunos_oscillation_binary

fan:
  - platform: speed
    output: lunos_dac_float
    name: "Lunos"
    speed_count: 8
    oscillation_output: lunos_oscillation_binary
    
    
1 Like

Thanks for this solution. Are you using the 5 UNI/FT controller? I have the SC-FT at home and struggle finding a solution for that particular controller as most (in the US) seem to run the UNI/FT controller as well or only want to control the eGO fans.

via SC-FT/SC-RF you’ll connect a 0-10V controller to the 0-10V input on the smart comfort. The wheel should NOT be in the position that says 0-10V, but rather the correct type of fan you want to control.
Connection should look like the picture in the following post (can only embed 1 picture pr post)

Then we get to programming. You can find the correct input values from the 0-10V controller to smart comfort in the picture below. As you can see the values are close to each other so I recommend measuring the voltage on the output from the 0-10V controller as there can be 0,1-0,2V (maybe even more) difference between input and output.
100% =10V, 50%=5V and so forth.

The values on the left side are with heat recovery while the right side are without heat recovery.

NB; you need to program the 0-10V controller with set values, non-dimmable.