ESP Home 3 Speed Fan Conversion

Update:

After some time experimenting and then walking away and then trying again I have a solution.

I wanted a singular push button to toggle between states, however I was struggling with Lamda, so instead of each press moving to the next setting, I decided to use multi_click. A long press turns off the fan, and one to three quick presses turning it on and into the given fan mode. As I mostly use High, I went High, Med, Low for 1, 2, and 3 clicks respectively. See the code below.

esphome:
  name: box_fan_one
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_pass
  manual_ip:
    static_ip: x.x.x.x
    gateway: x.x.x.x
    subnet: 255.255.255.0
    
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Box Fan Fallback Hotspot"
    password: !secret ap_fallback_pass

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret api_pass

ota:
  password: !secret ota_pass

substitutions: #for the name of the fan to input below
  name: box_fan_one

binary_sensor:
  - platform: gpio # For push button onboard
    id: push_button
    pin:
      number: D2
      mode: INPUT_PULLUP
    on_multi_click:
      - timing:
        - ON for 1s to 3s
        - OFF for at least 0.2s
        then:
          - logger.log: "Long Press"
          - switch.turn_off: relay1    
          - switch.turn_off: relay2  
          - switch.turn_off: relay3  

      - timing:
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Single Press"
          - switch.turn_on: relay1

      - timing:
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Double Press"
          - switch.turn_on: relay2

      - timing:
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Triple Press"
          - switch.turn_on: relay3
          
switch:
  - platform: gpio
    name: "${name}_High"
    pin: 
      number: D5
    inverted: True
    icon: mdi:fan-speed-3
    id: relay1
    interlock: &interlock_group [relay1, relay2, relay3]
    interlock_wait_time: 300ms
            
  - platform: gpio
    name: "${name}_Medium"
    pin: 
      number: D6
    inverted: True
    icon: mdi:fan-speed-2
    id: relay2
    interlock: *interlock_group
    interlock_wait_time: 300ms
            
  - platform: gpio
    name: "${name}_Low"
    pin: 
      number: D7
    inverted: True
    icon: mdi:fan-speed-1
    id: relay3 
    interlock: *interlock_group
    interlock_wait_time: 300ms

This gave me the physical control I desired.

Next step is understanding the fan template to get this to be usable as a fan rather than three switch devices.

I hope this helps someone, if anyone has questions or improvements let me know!

2 Likes

I’m trying to create a fan using 3 relays and came across this thread. Could you please share how you created the fan entity in HA?

Never was able to do it. Its theoretically possible to do using templating. I just have each of the fan speeds available on a single row on the dashboards that I need it. I primarily use button stations around the apartment so I didn’t end up needing to fuss with it as the 3 interlocking switches works fine.

I found this post which looks like it will work. I’ll hopefully get it tested in the next few days.

That does look promising. I’ll have to take a look at augmenting my setup when I get down time. Let me know how your experience is.

I just finished my fan controller and the below ESPhome works great for 3 speeds in HA. I made some improvements from the code I previously linked to.

esphome:
  name: games_room_fan
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.0.132
    gateway: 192.168.0.1
    subnet: 255.255.255.0
  
api:

logger:

web_server:
#  port: 80

ota:
  password: !secret ota_password

binary_sensor:
  - platform: status
    name: "Games Room Fan Status"

sensor:
  - platform: wifi_signal
    name: "Games Room Fan WiFi Signal"
    update_interval: 60s

switch:
  - platform: gpio
    pin: D5
    name: "Games Room Fan Speed 1"
    id: speed1
    interlock: [speed2, speed3]
  - platform: gpio
    pin: D6
    name: "Games Room Fan Speed 2"
    id: speed2
    interlock: [speed1, speed3]
  - platform: gpio
    pin: D7
    name: "Games Room Fan Speed 3"
    id: speed3
    interlock: [speed1, speed2]

output:
  - platform: template
    id: custom_fan
    type: float 
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            # action for off
            - switch.turn_off: speed1
            - switch.turn_off: speed2
            - switch.turn_off: speed3
      - if:
          condition:
            lambda: return ((state > 0) && (state < 0.4));
          then:
            # action for speed 1
            - switch.turn_off: speed2
            - switch.turn_off: speed3
            - switch.turn_on: speed1
            
      - if:
          condition:
            lambda: return ((state > 0.4) && (state < 0.7));
          then:
            # action for speed 2
            - switch.turn_off: speed1
            - switch.turn_off: speed3
            - switch.turn_on: speed2
            
      - if:
          condition:
            lambda: return ((state > 0.7));
          then:
            # action for speed 3
            - switch.turn_off: speed1
            - switch.turn_off: speed2
            - switch.turn_on: speed3

            
fan:
  - platform: speed
    id: gamesroomfan
    output: custom_fan
    name: "Games Room Fan"
    speed_count: 3

This controller uses the standard capacitor that came with the manual fan speed controller.


6 Likes

This is spectacular, ty for sharing your code. I was able to incorporate it into my existing code to have a proper fan entity in HA. MUCH better than the three switches I had before. I also tweaked the code for the onboard button I have, now rather than triggering relays it uses the fan entity so it feeds back to HA properly. LMK if you see any glaring issues in the code but I am pleased to report that I think that this is (for now) the final stage of this project.

I don’t personally use the status sensor but I do have the RSSI and uptime (converted to human readable, don’t recall where I found that so I cant credit anyone) included as well.

esphome:
  name: boxfanone
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_pass
  manual_ip:
    static_ip: x.x.x.x  
    gateway: x.x.x.x 
    subnet: 255.255.0.0
    
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Box Fan Fallback Hotspot"
    password: !secret ap_fallback_pass

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret api_pass

ota:
  password: !secret ota_pass

substitutions: #for the name of the fan to input below
  name: Bedroom Box Fan

binary_sensor:
  - platform: gpio # For push button onboard
    id: push_button
    pin:
      number: D2
      mode: INPUT_PULLUP

    on_multi_click:
      - timing:
        - ON for 1s to 3s
        - OFF for at least 0.2s
        then:
          - logger.log: "Long Press"
          - fan.turn_off:
              id: bedroomboxfan

      - timing:
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Single Press"
          - fan.turn_on:
              id: bedroomboxfan
              speed: 3

      - timing:
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Double Press"
          - fan.turn_on:
              id: bedroomboxfan
              speed: 2

      - timing:
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at most 1s
        - ON for at most 1s
        - OFF for at least 0.2s
        then:
          - logger.log: "Triple Press"
          - fan.turn_on:
              id: bedroomboxfan
              speed: 1

switch:
  - platform: gpio
    internal: true
    name: "${name} High Relay"
    pin: 
      number: D5
    inverted: True
    icon: mdi:fan-speed-3
    id: relay3
    interlock: &interlock_group [relay1, relay2, relay3]
    interlock_wait_time: 300ms
            
  - platform: gpio
    name: "${name} Medium Relay"
    internal: true
    pin: 
      number: D6
    inverted: True
    icon: mdi:fan-speed-2
    id: relay2
    interlock: *interlock_group
    interlock_wait_time: 300ms
            
  - platform: gpio
    name: "${name} Low Relay"
    internal: true
    pin: 
      number: D7
    inverted: True
    icon: mdi:fan-speed-1
    id: relay1 
    interlock: *interlock_group
    interlock_wait_time: 300ms
    
  - platform: restart
    name: "$name Restart"


output:
  - platform: template
    id: fan_1
    type: float 
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            # action for off
            - switch.turn_off: relay1
            - switch.turn_off: relay2
            - switch.turn_off: relay3
      - if:
          condition:
            lambda: return ((state > 0) && (state < 0.4));
          then:
            # action for speed 1
            - switch.turn_off: relay2
            - switch.turn_off: relay3
            - switch.turn_on: relay1
            
      - if:
          condition:
            lambda: return ((state > 0.4) && (state < 0.7));
          then:
            # action for speed 2
            - switch.turn_off: relay1
            - switch.turn_off: relay3
            - switch.turn_on: relay2
            
      - if:
          condition:
            lambda: return ((state > 0.7));
          then:
            # action for speed 3
            - switch.turn_off: relay1
            - switch.turn_off: relay2
            - switch.turn_on: relay3

            
fan:
  - platform: speed
    id: bedroomboxfan
    output: fan_1
    name: "Bedroom Box Fan"
    speed_count: 3

text_sensor: #Human Readable Uptime
  - platform: template
    name: $name Uptime
    id: uptime_human
    icon: mdi:clock-start

sensor:
  - platform: wifi_signal
    name: "$name WiFi Signal"
    update_interval: 60s
    
  - platform: uptime #Uptime in Seconds
    name: $name Uptime Sensor
    id: uptime_sensor
    update_interval: 60s
    internal: True
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? String(days) + "d " : "") +
                (hours ? String(hours) + "h " : "") +
                (minutes ? String(minutes) + "m " : "") +
                (String(seconds) + "s")
              ).c_str();

2 Likes

Just a follow up on this fan project of mine, I have the fan working perfectly in HA but I really want to be able to use the 3 speed settings as per the old HA fan usage, not the stupid % based method.

Currently my fan seems to show both in HA, but I can only set it via %. As you can see below, the descriptive fan speeds are listed under ‘attributes’, but I can only seem to set it via %. I realise the fan speeds (descriptive) was deprecated (stupid decission)… but…

Is there any way to force the 3 speeds to be accessible as low, medium and high? (I have this for another fan running Tasmota and integrated over MQTT)

I was able to create HA entities under Tasmota based device integrated with MQTT. But that required a bit of programming. The process is described in my Tasmota Nexus 433 MHz to MQTT Gateway for Home Assistant.

Thanks. I actually got it sorted out as described in my post above. The only thing I can’t figure out is how to select the fan speeds via the description (ie: off, low, med, high) in HA. Currently I have to use a slider to select the percentage steps. I have another fan which can be selected by descriptor but that’s running Tasmota, not ESPhome so this issue is taken care of in the HA MQTT fan code.

I am really curious if you built this out just because it was an interest project? There are a handful of devices across multiple protocols that already do this in a compact little unit that you shove into the fan wiring already. Don’t take my question as criticism, just curious.

At the time I didn’t really know about the solutions, and I found a project similar to this so I just decided to try and do it myself, really didn’t think about it past that. I’m glad I did though, learned a lot about ESPHome from this project.

Edit: Most everything I build for my smarthome is 1 part functionality and 2 parts, I wonder if I can do X.

The reasoning was because the fan I’m controlling is too powerful for the off-the-shelf devices. Unfortunately I found this out the hard way because I didn’t realise the fan controller I had wouldn’t handle my fan… it let the magic smoke out.

This fan controller I made can handle much larger / higher power fans.

3 Likes

Thanks for updating your code with the fan settings. This is working perfectly for me with a three relay board that I’ll put in a pedestal fan.

Can I ask as question - for the button, how did you wire that up? Do you also pull it low/high with a resistor to something as I’m seeing the button go on/off in the logs when I’m not touching anything.

Its been a long while since I have had the box open. You should be able wire it with a resistor to stop it from floating like that. Any button wiring you like should work.

1 Like

Is there no other way to represent three states with one key? I have the same problem and find the solution on_multi_click not very attractive. I switch the fan via solid state relays but that doesn’t really matter. I am looking for a simple solution for automation…

Press button level 1
press button again level 2
press button again level 3
button again fan off

And then again from the beginning. I can’t find a solution myself.

There are ways to achieve that.

Are you referring to ESPhome switching through the relays based on a button connected to the ESPhome node? Or done via HA?

Sorry, but I can only communicate in English through the translator. I would like to achieve the automation only via ESPHome. Everything else makes no sense for a fan. Would like to switch the part even if there is no network. Would be great if you had a solution for me.

If would need to be a condition based action, based on what fan speed is currently selected. In ESPhome you can do if/then/else statements, but I’m not sure if you can do if/elseif/elseif/else or not…

There is likely a way to do this with lambda’s but I’m not skilled in writing those.

ESPhome code: (not complete)

</s> <s>button:</s> <s> - platform: gpio</s> <s> name: Fan Button</s> <s> id: button</s> <s> on_press:</s> <s> then:</s> <s> - if:</s> <s> condition:</s> <s> - switch.is_on: switch_low</s> <s> then:</s> <s> - switch.turn_off: switch_low</s> <s> - switch.turn_on: switch_med</s> <s>
Hopefully you get the idea, but I would imagine there is a more elegant way to do this with lambda’s.

EDIT: see solution from RoadlikkUK below.

Thanks for the quick reply. Unfortunately, I do not quite understand the solution. I have a button and would like to achieve a different state with each press on it. So the speed Slow Medium and Fast or Off. In C++ this would probably be a counter from 0 to 3 and the assignment via a switch-case statement. This should be possible with ESPHome! Only I do not know how…