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
    
    
2 Likes

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.

Iā€™ve wired up to an ESP01 (IO0 and IO2) and used the config above (with pins 4,5 specified), compiled and uploaded.

On the serial port, it does look like itā€™s booting fine and contacting my HA just fine.

[C][i2c.arduino:053]: I2C Bus:
[C][i2c.arduino:054]:   SDA Pin: GPIO4
[C][i2c.arduino:055]:   SCL Pin: GPIO5
[C][i2c.arduino:056]:   Frequency: 50000 Hz
[C][i2c.arduino:059]:   Recovery: bus successfully recovered
[C][speed.fan:016]: Speed Fan 'Lunos'
[C][speed.fan:151]:   Speed: YES
[C][speed.fan:152]:   Speed count: 8
[C][speed.fan:155]:   Oscillation: YES
[C][mdns:108]: mDNS:
[C][mdns:109]:   Hostname: lunos-dac
[C][api:138]: API Server:
[C][api:139]:   Address: lunos-dac.local:6053
[C][api:143]:   Using noise encryption: NO
[D][api:102]: Accepted 192.168.34.118
[D][api.connection:961]: Home Assistant 2024.3.0 (192.168.34.118): Connected successfully
[D][fan:021]: 'Lunos' - Setting:
[D][fan:030]:   Speed: 8
[D][fan:092]: 'Lunos' - Sending state:
[D][fan:093]:   State: ON
[D][fan:095]:   Speed: 8
[D][fan:098]:   Oscillating: NO
[D][fan:021]: 'Lunos' - Setting:
[D][fan:024]:   State: OFF
[D][fan:092]: 'Lunos' - Sending state:
[D][fan:093]:   State: OFF
[D][fan:095]:   Speed: 8
[D][fan:098]:   Oscillating: NO

[D][fan:024]:   State: ON

In my HA, I have a single control:

Screenshot 2024-09-03 at 1.09.19 PM

and I can turn the fan on/off as also shown in the log above. I was expecting to also see a control to adjust the speed but maybe I have to add the Custom controls into my config manually?

Also, regardless of what I do, I never actually see any I2C transactions on either the SDA or SCL pins (using my oscilloscope). Iā€™ve tried 4, 5, 0, 2, GPIO4, GPIO5, GPIO0, GPIO2, etc. Iā€™m pretty sure 4,5 are what should work.

No voltage coming out the DFRobot board either.

Thoughts, anyone?

Hereā€™s my yaml:

esphome:
  name: lunos-dac
  platform: ESP8266
  board: esp01
  libraries:
    - "Wire"
    - "DFRobot_GP8403"
  includes:
    - CustomLunosOutput10V.h

wifi:
  ssid: "xxx"
  password: "xxx"

logger:

api:

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

Update to my prior post. Looks like I must have just pooched the 2 GPIOs on my ESP01. I swapped it out and wired up an ESP07 I had lying around and now Iā€™m getting I2C and the DAC is switching from 0 to 4.9v which I guess is expected. Also, my DFRobot board was shipping with A0/A1/A2 set to ā€˜onā€™ so the I2C address was 0x5f instead of 0x58. Setting all switches off solved that.

@haavhaal Iā€™ve got my esphome+dfrobot DAC successfully applying voltage in the 6-10v range and my rocker switches disconnected. I would expect that the fans would run at variable speeds with no oscillation. Iā€™ve measured the voltage at the TAC pins on my 5/uni-ft and even at lower voltage like 6.9v, the fans spin at full speed and reverse direction approximately every minute. From the chart above, I am of the impression that they should spin slowly and not reverse direction frequently? In fact, the fans donā€™t change speed regardless of the voltage on the TAC pins.

I have my DAC wired to GND and 0-10v pins on the ā€œTAC connectorā€. The +12v pin on the TAC connector is not connected; I assume the +12v pin is an output.

Dial switch set to ā€˜3ā€™.

Screenshot 2024-09-15 at 8.53.05 AM

@haavhaal Iā€™m interested if you know anything about the digital T/R pins on the Lunos contrrol board, or the 6-pin connector that it uses to connect to things like the Gesture Control module?

The 0-10V input is a good option, but really only provides control over the speed of the fan and how often they swap direction (in essence, whether heat recovery is in effect or not).

Iā€™d like a little more control, specifically to manage the speed and direction of each fan independently. I have a few use cases for this, including:

  1. Experimenting with adjusting the cycle time of fan reversal based on interior and exterior temperatures to optimize for the heat core saturation;
  2. Switching both fans to exhaust mode in the event of a fire (to create a negative pressure in the building and hence help suppress the fire).

Iā€™ve not been able to find any documentation on the Lunos website, but thought Iā€™d ask before I start trying to reverse engineer a serial protocol.