ESPHome Fan - need help

I’m trying to use ESPHome for the automation of a fan. At the moment I’m using a custom made program on a ESP8266 to do this, but I want to replace this program with ESPHome.
The problem is that I don’t know how to do this using ESPHome so any help would be appreciated.

I control the fan using Home Assistant. In Home Assistant I have an input_select (low/medium/high) with an automation that sends MQTT messages to a specific topic. The ESP module subscribes to the topic and sets some GPIO pins.

The problem is that I have to update multiple GPIO pins (multiple relays):

GPIO4 Low, GPIO5 Low 	-> Fan at low speed
GPIO4 High, GPIO5 Low 	-> Fan at medium speed
GPIO4 Low, GPIO5 High 	-> Fan at high speed

The fan cannot be turned off (default is low speed). I want to replace the MQTT part by the HA <-> EPSHome API.
I’m not sure how to do this in an ESPHome configuration. I know that I can define two switches in ESPHome and control them using HA but it would be great to have one single HA entity connected to EPSHome.
The reason for this is that the GPIOs may not be HIGH at the same time! So I would like to restrict this inside the ESPHome program.

Can anybody give me an ESPHome config example of how to connect my HA input_select to the ESPHome module?

You can’t do it.

Ideally you’ll use the fan integration. However it only supports two types of fan, binary and speed fan. The problem is speed fan has an analog output and you need three discrete outputs.

This could be tackled by a template fan however that does not exist yet.
Curiously a few hours ago somebody filled a feature request for that

In any case you can take a solution with no fan integration. You can use the interlocking option of output to prevent more than one output on at a time.

The onyl way I see is to make a custom component…
here’s an example, this is what I use for my fan:

#include "esphome.h"
using namespace esphome;

#define PIN_1 18
#define PIN_2 19
#define PIN_3 21


class MyCustomFanoutput : public Component, public FloatOutput {
 public:
  void setup() override {
    // This will be called by App.setup()
    pinMode(PIN_1, OUTPUT);
    pinMode(PIN_2, OUTPUT);
    pinMode(PIN_3, OUTPUT);
  }

  void write_state(float state) override {
     if (state == 0.33) {
       digitalWrite(PIN_1, HIGH);
       delay(25);
       digitalWrite(PIN_1, LOW);
     }
     else if (state == 0.66) {
       digitalWrite(PIN_2, HIGH);
       delay(25);
       digitalWrite(PIN_2, LOW);
     }
     else if (state == 1) {
        digitalWrite(PIN_3, HIGH);
        delay(25);
        digitalWrite(PIN_3, LOW);
     }
  }
};
2 Likes

Thanks for the code example. I’ve created my own custom component and connected it to HA. Seems to work fine now!

Happy to help.
I should post that example on the esphome website probably :slight_smile:

1 Like

This looks really interesting! Could you please also post the yaml part that refers to your ‘MyCustomFanoutput’? Thanks!

sure:

esphome:
  name: mechanische_ventilatie
  platform: ESP32
  board: esp-wrover-kit
  includes:
    - custom_fan.h

wifi:
  ssid: "SSID"
  password: "password"

# Enable logging
logger:

web_server:

# Enable Home Assistant API
api:
  password: "password"

ota:
  password: "password"
  
mqtt:
  broker: mqttip
  username: user
  password: password

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

    outputs:
      id: my_custom_fan

fan:
  - platform: speed
    output: my_custom_fan
    name: "Mechanische Ventilatie"
    command_topic: "esphome/mechanische_ventilatie/commands"
#    speed_command_topic: "esp/mv"
#    speed_state_topic: 'mechanische_ventilatie/on/state'
    state_topic: 'mechanische_ventilatie/on/state'
#    payload_on: '{"mac": "000D6F00002369B9", "cmd": "switch", "val": "on"}'
#    payload_off: '{"mac": "000D6F00002369B9", "cmd": "switch", "val": "off"}'

1 Like

I have made a version that is just using a 4-way relay board with these to control the light and the fan.

It has been very painful but works perfectly (stand alone too!) with this code.

esphome:
  name: newer_fan_test
  platform: ESP8266
  board: d1_mini
  includes:
    - ifan02.h  

wifi:
  ssid: "*********"
  password: "********"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Newer Fan Test Fallback Hotspot"
    password: "**********"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

binary_sensor:

  - platform: template
    id: led_low
    lambda: |-
      if (id(test_fan).speed == 0){
        if(id(test_fan).state){
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    on_press:
      then:
        - light.turn_on:
            id: switch_led
            brightness: 30%
        - switch.turn_on:
            id: dim_led
            
  - platform: template
    id: led_mid
    lambda: |-
      if (id(test_fan).speed == 1){
        if(id(test_fan).state){
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    on_press:
      then:
        - light.turn_on:
            id: switch_led
            brightness: 60%
        - switch.turn_on:
            id: dim_led  
            
  - platform: template
    id: led_high
    lambda: |-
      if (id(test_fan).speed == 2){
        if(id(test_fan).state){
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    on_press:
      then:
        - light.turn_on:
            id: switch_led
            brightness: 100%
        - switch.turn_on:
            id: dim_led       
            
  - platform: template
    id: led_off
    lambda: |-
      if (id(test_fan).state){
        return false;
      } else {
        return true;
      }
    on_press:
      then:
        - light.turn_off:
            id: switch_led
        - switch.turn_off:
            id: dim_led
##---light switch button--------------------------
  - platform: gpio
    pin: GPIO3
    id: light_button
    on_release:
      then:
        - switch.toggle: mancave_light
##---right rotation on the two-way switch---------
  - platform: gpio
    pin: D8
    id: up
    on_press:
      then:
        - lambda: |-
            if (id(test_fan).state) {
              if (id(test_fan).speed == 0) {
                id(fan_med).turn_on();
                ESP_LOGD("main", "Fan set to Medium");
              } else {
                if (id(test_fan).speed == 1){
                  id(fan_high).turn_on();
                  ESP_LOGD("main", "Fan set to High");
                } else {
                  id(fan_off).turn_on();
                  ESP_LOGD("main", "Fan set to Off");
                }
              }
            } else {
              id(fan_low).turn_on();
              ESP_LOGD("main", "Fan set to Low");
            }
    
  - platform: gpio
    pin: D7
    id: down  
    on_press:
      then:
        - lambda: |-
            if (id(test_fan).state) {
              if (id(test_fan).speed == 0) {
                id(fan_off).turn_on();
                ESP_LOGD("main", "Fan set to Off");
              } else {
                if (id(test_fan).speed == 1){
                  id(fan_low).turn_on();
                  ESP_LOGD("main", "Fan set to Low");
                } else {
                  id(fan_med).turn_on();
                  ESP_LOGD("main", "Fan set to Medium");
                }
              }
            } else {
              id(fan_high).turn_on();
              ESP_LOGD("main", "Fan set to High");
            }

fan:
  - platform: speed
    output: fanoutput
    id: test_fan
    name: "Fan"
    
output:
  - platform: custom
    type: float
    outputs:
      id: fanoutput
    lambda: |-
      auto test_fan = new FanOutput();
      App.register_component(test_fan);
      return {test_fan};
##-----ring light on two-way switch------    
  - platform: esp8266_pwm
    id: switch_light
    pin: D5
    
##-----ring light for light button-------
  - platform: esp8266_pwm
    id: button_light
    pin: D6      
      
switch:      
  - platform: gpio
    pin: D4
    id: relay1
    inverted: yes

  - platform: gpio
    pin: D3
    id: relay2
    inverted: yes

  - platform: gpio
    pin: D2
    id: relay3
    inverted: yes
    
  - platform: gpio
    pin: D1
    id: mancave_light
    name: "light"
    inverted: yes      
    
  - platform: template
    id: fan_off
    lambda: |-
      if (id(test_fan).state){
        return false;
      } else {
        return true;
      }
    turn_on_action:
      - fan.turn_off:
          id: test_fan
          
  - platform: template
    id: fan_low
    turn_on_action:
      - fan.turn_on:
          id: test_fan
          speed: LOW
          
  - platform: template
    id: fan_med
    turn_on_action:
      - fan.turn_on:
          id: test_fan
          speed: MEDIUM
          
  - platform: template
    id: fan_high
    turn_on_action:
      - fan.turn_on:
          id: test_fan
          speed: HIGH 
          
  - platform: template
    id: dim_led
    turn_on_action:
       - delay: 5s
       - if:
           condition:
             - switch.is_on: fan_off
           then:
             - light.turn_off:
                 id: switch_led
           else:
             - light.turn_on:
                 id: switch_led
                 brightness: 10%
             
    
light:
  - platform: monochromatic
    name: "fan switch led"
    id: switch_led
    output: switch_light    
    default_transition_length: 0.5s
    
  - platform: monochromatic
    name: "light switch led"
    id: button_led
    output: button_light    
    default_transition_length: 0.5s  

A bit of an explanation of whats going on…

The - button (or left toggle) on the “rocker” steps the fan speeds down to off and then back to high and so on, and also updates it in home assistant. The +button (or right toggle) steps the fan speeds up in the same manner.

The led ring on the fan controller is displayed at 100% on high, 60% on medium, on 30% on low and off when off. This is all well and good until you want the fan on at night but not a bright light in ya face so I have also made it dim after 5 seconds to 10% on any speed setting when it is on and again off when off.

2 Likes

Hey, can you tell me what is your modification of ifan02.h? I also use 4 relay board to control my fan.

Where did you get the button on the right?

I am trying to get the same result with a Shelly 2.5, I want to control the following fan:

GPIO4 Low, GPIO15 Low 	-> Fan at low speed
GPIO4 High, GPIO15 Low 	-> Fan at medium speed
GPIO4 High, GPIO15 High 	-> Fan at high speed

I tried the example from @WWolkers, but I cannot get it working. When turning on the fan, the ouput both go high. But I cannot get one of them to go low again.

I have the following custom component:

#include "esphome.h"
using namespace esphome;

#define PIN_1 4
#define PIN_2 15


class MyCustomFanoutput : public Component, public FloatOutput {
 public:
  void setup() override {
    // This will be called by App.setup()
    pinMode(PIN_1, OUTPUT);
    pinMode(PIN_2, OUTPUT);
  }

  void write_state(float state) override {
     if (state == 0.33) {
       digitalWrite(PIN_1, LOW);
       digitalWrite(PIN_2, LOW);
     }
     else if (state == 0.66) {
       digitalWrite(PIN_1, HIGH);
       digitalWrite(PIN_2, LOW);
     }
     else if (state == 1) {
        digitalWrite(PIN_1, HIGH);
        digitalWrite(PIN_2, HIGH);
     }
  }
};

And this is my YAML:

esphome:
  name: ventilatie
  platform: ESP8266
  board: esp01_1m
  includes:
    - custom_fan.h

wifi:
  ssid: !secret SSID
  password: !secret wifi_pass
  domain: !secret domain

  manual_ip:
    static_ip: 192.168.5.215
    gateway: 192.168.5.254
    subnet: 255.255.255.0

logger:
  
api:

ota:
  password: !secret OTA_pass


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

    outputs:
      id: my_custom_fan

fan:
  - platform: speed
    output: my_custom_fan
    name: "Ventilatie"

Can you help me with this?

I managed to create a working version without the custom component as the expansion board I wanted to use wouldn’t allow the custom component and it works perfectly!!

esphome:
  name: sx1509_newer_fan_test
  platform: ESP8266
  board: d1_mini

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Sx1509 Newer Fan Test"
    password: "***************"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

i2c:
  sda: D2
  scl: D1
  scan: True
  id: bus_a

sx1509:
  - id: sx1509_hub1
    address: 0x3E

fan:
  - platform: speed
    id: living_fan
    output: my_output_1
    name: "Living Room Fan"

output:  
  - platform: sx1509
    sx1509_id: sx1509_hub1
    id: 'button2_led_output'
    pin: 4
    inverted: true
    
  - platform: sx1509
    sx1509_id: sx1509_hub1
    id: 'button1_led_output'
    pin: 2
    inverted: true   
    
  - platform: sx1509
    sx1509_id: sx1509_hub1
    id: 'fan_led_output'
    pin: 7
    inverted: true      

  - platform: template
    id: my_output_1
    type: float
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            - switch.turn_off: relay2
            - switch.turn_off: relay3
            - switch.turn_off: relay1  
            - light.turn_off: switch_led
      - if:
          condition:
            lambda: return ((state > 0) && (state < .34));
          then:
            - switch.turn_off: relay2
            - switch.turn_off: relay3
            - switch.turn_on: relay1
            - light.turn_on:
                id: switch_led
                brightness: 30%
            - switch.turn_on: dim_led
      - if:
          condition:
            lambda: return ((state > .34) && (state < .7));
          then:
            - switch.turn_off: relay1
            - switch.turn_off: relay3
            - switch.turn_on: relay2  
            - light.turn_on:
                id: switch_led
                brightness: 60%
            - switch.turn_on: dim_led            
      - if:
          condition:
            lambda: return ((state == 1));
          then:
            - switch.turn_off: relay1
            - switch.turn_off: relay2
            - switch.turn_on: relay3 
            - light.turn_on:
                id: switch_led
                brightness: 100%
            - switch.turn_on: dim_led            
            
binary_sensor:
  - platform: gpio
    name: "SX1509 Pin #0"
    pin:
      sx1509: sx1509_hub1
      number: 3
      mode: INPUT_PULLUP
      inverted: True
    on_press: 
      then:
        - light.toggle: button2_led
   
  - platform: gpio
    name: "SX1509 Pin #2"
    pin:
      sx1509: sx1509_hub1
      number: 0
      mode: INPUT_PULLUP
      inverted: True
    on_press: 
      then:
        if:
          condition:
            switch.is_on: relay4
          then:
            - switch.turn_off: relay4
          else:
            - switch.turn_on: relay4
            - light.turn_off: button1_led
      
  - platform: gpio
    id: "fan_up"
    pin:
      sx1509: sx1509_hub1
      number: 6
      mode: INPUT_PULLUP
      inverted: True  
    on_press: 
      then:
        - lambda: |-
            if (id(living_fan).state) {
              if (id(living_fan).speed == 0) {
                id(fan_off).turn_on();
                ESP_LOGD("main", "Fan set to Off");
              } else {
                if (id(living_fan).speed == 1){
                  id(fan_low).turn_on();
                  ESP_LOGD("main", "Fan set to Low");
                } else {
                  id(fan_med).turn_on();
                  ESP_LOGD("main", "Fan set to Medium");
                }
              }
            } else {
              id(fan_high).turn_on();
              ESP_LOGD("main", "Fan set to High");
            }

      
  - platform: gpio
    id: "fan_down"
    pin:
      sx1509: sx1509_hub1
      number: 5
      mode: INPUT_PULLUP
      inverted: True  
    on_press: 
      then:
        - lambda: |-
            if (id(living_fan).state) {
              if (id(living_fan).speed == 0) {
                id(fan_med).turn_on();
                ESP_LOGD("main", "Fan set to Medium");
              } else {
                if (id(living_fan).speed == 1){
                  id(fan_high).turn_on();
                  ESP_LOGD("main", "Fan set to High");
                } else {
                  id(fan_off).turn_on();
                  ESP_LOGD("main", "Fan set to Off");
                }
              }
            } else {
              id(fan_low).turn_on();
              ESP_LOGD("main", "Fan set to Low");
            }      
        
###motion sensor
  - platform: gpio
    name: "Living Motion"
    pin:
      sx1509: sx1509_hub1
      number: 1
      mode: INPUT
      inverted: false 
    on_press: 
      then:
        - if:
            condition:
              switch.is_off: relay4
            then:
              - light.turn_on: button1_led   
              - delay: 5s
              - light.turn_off: button1_led  
        
            
switch:
  - platform: gpio
    name: "sx1509_ relay1"
    id: relay1
    pin:
      sx1509: sx1509_hub1
      number: 8
      mode: OUTPUT
      inverted: True
      
  - platform: gpio
    name: "sx1509_ relay2"
    id: relay2
    pin:
      sx1509: sx1509_hub1
      number: 9
      mode: OUTPUT
      inverted: True      

  - platform: gpio
    name: "sx1509_ relay3"
    id: relay3
    pin:
      sx1509: sx1509_hub1
      number: 10
      mode: OUTPUT
      inverted: True
      
  - platform: gpio
    name: "Living Room Light"
    id: relay4
    pin:
      sx1509: sx1509_hub1
      number: 11
      mode: OUTPUT
      inverted: True     

  - platform: template
    id: fan_off
    lambda: |-
      if (id(living_fan).state){
        return false;
      } else {
        return true;
      }
    turn_on_action:
      - fan.turn_off:
          id: living_fan  
          
  - platform: template
    id: fan_low
    turn_on_action:
      - fan.turn_on:
          id: living_fan
          speed: LOW
          
  - platform: template
    id: fan_med
    turn_on_action:
      - fan.turn_on:
          id: living_fan
          speed: MEDIUM
          
  - platform: template
    id: fan_high
    turn_on_action:
      - fan.turn_on:
          id: living_fan
          speed: HIGH           

  - platform: template
    id: dim_led
    turn_on_action:
       - delay: 5s
       - if:
           condition:
             - switch.is_on: fan_off
           then:
             - light.turn_off:
                 id: switch_led
           else:
             - light.turn_on:
                 id: switch_led
                 brightness: 15%      
      
light:
  - platform: monochromatic
    id: button1_led
    name: "Living Room Button1 Led"
    output: button1_led_output 
    default_transition_length: 0.5s
    
  - platform: monochromatic
    id: button2_led
    name: "Living Room Button2 Led"
    output: button2_led_output 
    default_transition_length: 0.5s

  - platform: monochromatic
    id: switch_led
    name: "Living Room Switch Led"
    output: fan_led_output 
    default_transition_length: 0.5s

I might create a basic straight forward example of this and upload it to the creation page as there is next to no instructions on how to create a float output for the fan.

Hi Erwin

Try this out and let me know how you go, I’m not sure how you plan to turn the fan off but according to your plan this code might work for you…

esphome:
  name: ventilatie
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret SSID
  password: !secret wifi_pass
  domain: !secret domain

  manual_ip:
    static_ip: 192.168.5.215
    gateway: 192.168.5.254
    subnet: 255.255.255.0

logger:
  
api:

ota:
  password: !secret OTA_pass


output:
  - platform: template
    id: custom_fan
    type: float 
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            # action for off
            - switch.turn_off: relay1
            - switch.turn_off: relay2  
      - if:
          condition:
            lambda: return ((state > 0) && (state < .34));
          then:
            # action for low
            - switch.turn_off: relay1
            - switch.turn_off: relay2
      - if:
          condition:
            lambda: return ((state > .34) && (state < .7));
          then:
           # action for medium
            - switch.turn_on: relay1
            - switch.turn_off: relay2             
      - if:
          condition:
            lambda: return ((state == 1));
          then:
            # action for high
            - switch.turn_on: relay1
            - switch.turn_on: relay2

fan:
  - platform: speed
    id: ventilatie
    output: custom_fan
    name: "Ventilatie"
	
switch:
  - platform: gpio
    name: "relay 1"
    id: relay1
    pin: GPIO4
      
  - platform: gpio
    name: "relay 2"
    id: relay2
    pin: GPIO15