Minka-Aire Fan - ESPHome Controller

I wanted to share my project. I’m new to Home Assistant and this is my first real project. I decided to make my living room Minka-Aire Fan controllable from home assistant. I wanted to use a Wemos D1 mini and took the remote control for the fan. I also found a very helpful GitHub project by Aaron Nielsen where he showed how to wire each button to a relay. I’m by no means an expert here but hopefully someone can also benefit from this too.

The Minka-Aire fan remote control board uses 12V and the Wemos D1 mini and relay module uses 5V power so I found a power module with both 5V and 12V power. The fan remote used a 12V battery so I just soldered wires to the board and connected them to the power module. I wasn’t entirely sure the best way to wire the power directly to the Wemos D1 mini and my understanding is that if you by-pass the usb it also by-passes some of the power safety modules. So I found a right angle USB to dupont cable and after a slight cable modification, I was able to wire that directly to the power module. I connected the relay directly to the 5v power rather than draw from the D1 Mini to keep the D1 mini stable.

The power module wasn’t the best quality and I had to test a few of them using a multi-meter before I found one that was working correctly. The first 2 I tried were pushing out 12v on all outputs but I found one that was stable and working correctly. Using the wiring guide from Aaron Nielsen’s github, I soldered wires directly to the fan remote board and connected them to the relay. Technically, the remote has 7 different physical buttons but the fan remote’s light on and off buttons basically just toggle the light vs on and off. I wired up and connected the 7 buttons (High,Med,Low,Off,Change Rotation,Up Light, Down Light) to the relay.

After reading a few guides on ESPHome, I learned a few important things. Since the remote only needs one button to be pressed at a time, I found that interlocking could help make sure that this doesn’t happen. Since I don’t want to buttons to be pressed when the D1 mini turns on I turned on inverted for each of the switches. I also had to add a bit of a delay to each button and since the light buttons on the fan remote act like more of a toggle, I have one setup as the on and off and the 2nd setup with a longer delay that is able to dim the light when pressed.

After the wiring and coding was done, all that was left was to build a 3d printed box to house the power module, D1 min, fan remote board and 8 port relay. I had to create it in a few different pieces that connect together to fit it all but it really worked out well. I did break the lid and I’m working on printing another but nothing some electrical tape couldn’t fix.

ESPHome Code

esphome:
  name: familyroomfan
  platform: ESP8266
  board: d1_mini

# Enable Home Assistant API
switch:
  - platform: gpio
    pin: 5 #D1
    id: relay1
    inverted: yes
    interlock: &interlock_group [relay1, relay2, relay3, relay4, relay5, relay6, relay7, relay8] 
  - platform: gpio
    pin: 4 #D2
    id: relay2
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 2 #D4
    id: relay3
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 16 #D0
    id: relay4
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 14 #D5
    id: relay5
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 12 #D6
    id: relay6
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 13 #D7
    id: relay7
    inverted: yes
    interlock: *interlock_group
  - platform: gpio
    pin: 15 #D8
    id: relay8
    inverted: yes
    interlock: *interlock_group
  
- platform: template
    name: "Toggle Fan Light"
    id: FRF_ToggleFanLight
    icon: "mdi:lightbulb"
    turn_on_action:
    - switch.turn_on: relay1
    - delay: 90ms
    - switch.turn_off: relay1
    - switch.template.publish:
        id: FRF_ToggleFanLight
        state: ON
    turn_off_action:
    - switch.turn_on: relay1
    - delay: 90ms
    - switch.turn_off: relay1
    - switch.template.publish:
        id: FRF_ToggleFanLight
        state: OFF
  - platform: template
    name: "Dim Fan Light"
    icon: "mdi:lightbulb-on"
    turn_on_action:
    - switch.turn_on: relay2
    - delay: 1600ms
    - switch.turn_off: relay2
    
  - platform: template
    name: "Fan On High"
    id: FRF_FanOnHigh
    icon: "mdi:gauge-full"
    turn_on_action:
    - switch.turn_on: relay6
    - delay: 90ms
    - switch.turn_off: relay6
    - switch.template.publish:
        id: FRF_FanOnHigh
        state: ON
    - switch.template.publish:
        id: FRF_FanOnMedium
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnLow
        state: OFF
    - switch.template.publish:
        id: FRF_FanOff
        state: ON
    turn_off_action:
    - switch.toggle: FRF_FanOff
    
  - platform: template
    name: "Fan On Medium"
    icon: "mdi:gauge"
    id: FRF_FanOnMedium
    turn_on_action:
    - switch.turn_on: relay4
    - delay: 90ms
    - switch.turn_off: relay4
    - switch.template.publish:
        id: FRF_FanOnHigh
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnMedium
        state: ON
    - switch.template.publish:
        id: FRF_FanOnLow
        state: OFF
    - switch.template.publish:
        id: FRF_FanOff
        state: ON
    turn_off_action:
    - switch.toggle: FRF_FanOff
    
  - platform: template
    name: "Fan On Low"
    icon: "mdi:gauge-low"
    id: FRF_FanOnLow
    turn_on_action:
    - switch.turn_on: relay5
    - delay: 90ms
    - switch.turn_off: relay5
    - switch.template.publish:
        id: FRF_FanOnHigh
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnMedium
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnLow
        state: ON
    - switch.template.publish:
        id: FRF_FanOff
        state: ON
    turn_off_action:
    - switch.toggle: FRF_FanOff
        
  - platform: template
    name: "Fan Off"
    icon: "mdi:fan-off"
    id: FRF_FanOff
    turn_on_action:
    - switch.turn_on: relay3
    - delay: 120ms
    - switch.turn_off: relay3
    - switch.template.publish:
        id: FRF_FanOnHigh
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnMedium
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnLow
        state: OFF
    - switch.template.publish:
        id: FRF_FanOff
        state: OFF
    turn_off_action:
    - switch.turn_on: relay3
    - delay: 120ms
    - switch.turn_off: relay3
    - switch.template.publish:
        id: FRF_FanOnHigh
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnMedium
        state: OFF
    - switch.template.publish:
        id: FRF_FanOnLow
        state: OFF
    - switch.template.publish:
        id: FRF_FanOff
        state: OFF
    
  - platform: template
    name: "Fan Change Rotation"
    icon: "mdi:rotate-3d"
    turn_on_action:
    - switch.turn_on: relay7
    - delay: 90ms
    - switch.turn_off: relay7
    
binary_sensor:
  - platform: status
    name: "Family Room ESPHome Status"

List of Items:

Images

I’ve really enjoyed working on this project and its been a great way to really get to explore the power of Home Assistant and ESPHome. I probably should have kept better track of sources so if I missed linking something, please let me know. Thanks!

Edit:
Updated ESPHome code to handle On/Off status handling for a more logical experience with HomeAssistant.

2 Likes

Love this, and am going to replicate it. Thanks for the post

1 Like

For me, I have 4 fans that use this remote, so I made a universal remote for HA. So I did something very similar.

The only changes I made were simplifying some of the code you gave, and I added wires and code for setting the dipswitches on the back of the remote. I also split it between 2 ESP8266 devices (NodeMCUv2).

And here is the code (passwords removed). One is the Fan Controller and the other is the Fan Picker:

esphome:
  name: fanmaster
  platform: ESP8266
  board: nodemcuv2

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: ""

wifi:
  ssid: "IoT_1"
  password: ""

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

captive_portal:

switch:
  - platform: gpio
    pin: 5 #D1
    id: relay1
    inverted: no
    interlock: &interlock_group [relay1, relay2, relay3, relay4, relay5, relay6] 
  - platform: gpio
    pin: 4 #D2
    id: relay2
    inverted: no
    interlock: *interlock_group
  - platform: gpio
    pin: 2 #D4
    id: relay3
    inverted: no
    interlock: *interlock_group
  - platform: gpio
    pin: 16 #D0
    id: relay4
    inverted: no
    interlock: *interlock_group
  - platform: gpio
    pin: 14 #D5
    id: relay5
    inverted: no
    interlock: *interlock_group
  - platform: gpio
    pin: 12 #D6
    id: relay6
    inverted: no
    interlock: *interlock_group
  
  - platform: template
    name: "Toggle Fan Light"
    icon: "mdi:lightbulb"
    turn_on_action:
    - switch.turn_on: relay1
    - delay: 260ms
    - switch.turn_off: relay1
    
  - platform: template
    name: "Dim Fan Light"
    icon: "mdi:lightbulb-on"
    turn_on_action:
    - switch.turn_on: relay1
    - delay: 2500ms
    - switch.turn_off: relay1
    
  - platform: template
    name: "Fan On High"
    icon: "mdi:gauge-full"
    turn_on_action:
    - switch.turn_on: relay4
    - delay: 260ms
    - switch.turn_off: relay4

    
  - platform: template
    name: "Fan On Medium"
    icon: "mdi:gauge"
    turn_on_action:
    - switch.turn_on: relay3
    - delay: 260ms
    - switch.turn_off: relay3
    
  - platform: template
    name: "Fan On Low"
    icon: "mdi:gauge-low"
    turn_on_action:
    - switch.turn_on: relay2
    - delay: 260ms
    - switch.turn_off: relay2
        
  - platform: template
    name: "Fan Off"
    icon: "mdi:fan-off"
    turn_on_action:
    - switch.turn_on: relay5
    - delay: 260ms
    - switch.turn_off: relay5
    
  - platform: template
    name: "Fan Change Rotation"
    icon: "mdi:rotate-3d"
    turn_on_action:
    - switch.turn_on: relay6
    - delay: 260ms
    - switch.turn_off: relay6

    
binary_sensor:
  - platform: status
    name: "Family Room ESPHome Status"
esphome:
  name: fanpicker
  platform: ESP8266
  board: nodemcuv2

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: ""

wifi:
  ssid: "IoT_1"
  password: ""

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

captive_portal:

switch:
  - platform: gpio
    pin: D0
    id: sw1
    name: "sw1"
    icon: "mdi:switch"
    #on_turn_on: 
    #on_turn_off:
    
  - platform: gpio
    pin: D1
    id: sw2
    name: "sw2"
    icon: "mdi:switch"
    #on_turn_on: 
    #on_turn_off:
    
  - platform: gpio
    pin: D2
    id: sw3
    name: "sw3"
    icon: "mdi:switch"
    #on_turn_on: 
    #on_turn_off:

  - platform: gpio
    pin: D4
    id: sw4
    name: "sw4"
    icon: "mdi:switch"
    #on_turn_on: 
    #on_turn_off:
    
  - platform: gpio
    pin: D5
    id: sw5
    name: "sw5"
    icon: "mdi:switch"
    #on_turn_on: 
    #on_turn_off:

  - platform: template
    name: "Bedroom_Fan"
    id: Bedroom_Fan
    turn_on_action:
      - then:
        - switch.turn_on: sw1
        - switch.turn_on: sw2
        - switch.turn_on: sw3
        - switch.template.publish:
            id: Bedroom_Fan
            state: ON
    turn_off_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_off: sw2
        - switch.turn_off: sw3
        - switch.turn_off: sw4
        - switch.turn_off: sw5
        - switch.template.publish:
            id: Bedroom_Fan
            state: OFF
            
  - platform: template
    name: "Office_Fan"
    id: Office_Fan
    turn_on_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_off: sw2
        - switch.turn_off: sw3
        - switch.turn_off: sw4
        - switch.turn_off: sw5
        - switch.template.publish:
            id: Office_Fan
            state: ON
    turn_off_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_off: sw2
        - switch.turn_off: sw3
        - switch.turn_off: sw4
        - switch.turn_off: sw5
        - switch.template.publish:
            id: Office_Fan
            state: OFF
            
  - platform: template
    name: "Loft_Fan"
    id: Loft_Fan
    turn_on_action:
      - then:
        - switch.turn_on: sw1
        - switch.turn_off: sw2
        - switch.turn_on: sw3
        - switch.turn_on: sw4
        - switch.turn_on: sw5
        - switch.template.publish:
            id: Loft_Fan
            state: ON
    turn_off_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_off: sw2
        - switch.turn_off: sw3
        - switch.turn_off: sw4
        - switch.turn_off: sw5
        - switch.template.publish:
            id: Loft_Fan
            state: OFF
            
  - platform: template
    name: "LivingRoom_Fan"
    id: LivingRoom_Fan
    turn_on_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_on: sw2
        - switch.turn_on: sw3
        - switch.turn_on: sw4
        - switch.turn_on: sw5
        - switch.template.publish:
            id: LivingRoom_Fan
            state: ON
    turn_off_action:
      - then:
        - switch.turn_off: sw1
        - switch.turn_off: sw2
        - switch.turn_off: sw3
        - switch.turn_off: sw4
        - switch.turn_off: sw5
        - switch.template.publish:
            id: LivingRoom_Fan
            state: OFF

And finally the HA Dashbord View:

Of course I need to design a case and finish it up, but for now it works!

Thanks to the OP for the inspiration!!

1 Like

That’s pretty cool! Thanks for sharing! I have the light switch controller in the wall so on occasion the buttons get out of sync with the actual state of the fan, such as the light is turned on outside of the HA and I’ve found that if I press the toggle light fast enough, I can get it to reset back to normal. Or sometimes I’ll turn the fan on high using HA or Alexa and then manually turn it off which is why I have the extra template publish code for each button to keep them in sync, especially for off since that’s a good way to reset everything back to normal. Keep up the good work!

Here’s the code to make the fan show up as a fan entity.

-fan:
  - platform: template
    fans:
      living_room_fan:
        friendly_name: "Living Room Fan"
        unique_id: "living_room_fan"
        value_template: >-
            {%if states("switch.fan_on_high") == 'on' or states("switch.fan_on_medium") == 'on' or states("switch.fan_on_low") == 'on'-%}
              on
            {%- else -%}
              off
            {%- endif %}
        percentage_template: >-
             {%if states("switch.fan_on_high") == 'on' -%}
              100
             {%- elif states("switch.fan_on_medium") == 'on' -%}
              66
             {%- elif states("switch.fan_on_low") == 'on' -%}
              33
             {%- else -%}
              0
             {%- endif %}
        turn_on:
          service: switch.turn_on
          target:
            entity_id: switch.fan_on_high
        turn_off:
          service: switch.turn_off
          target:
            entity_id: switch.fan_off
        set_percentage:
          service: switch.turn_on
          target:
            entity_id: >-
              {%if percentage>0 and percentage <=33 -%}
                {% set percentage=33 %}
              {%- elif percentage>33 and percentage <=66 -%}
                {% set percentage=66 %}
              {%- elif percentage>66 -%}
                {% set percentage=100 %}
              {%- else -%}
                {% set percentage=0 %}
              {%- endif %}
              
              {%if percentage == 100 -%}
                switch.fan_on_high
              {%- elif percentage ==66 -%}
                switch.fan_on_medium
              {%- elif percentage ==33 -%}
                switch.fan_on_low
              {%- else -%}
                switch.fan_off
              {%- endif %}
        speed_count: 3

Great project. Some updated code to bring it all together for 2023. I have just finished a nearly identical setup for a Hunter Fans ceiling fan with light. Remote control looks almost identical to yours… I’m using it with just a WeMos D1 Mini and 5 outputs controlling 5 relays, all in ESPHome. On my remote, the pushbutton traces do not all share a common ground so I needed to do this 5-relay nonsense, I would have preferred direct digital output. Also making use of the new template Fan entity so it exposes to Home Assistant just as a single multi-speed fan, and a single on/off light. Note that it’s possible to dim this light based on how long you hold, but that was really flaky so I’m just doing on/off for the light.

substitutions:
  name: living-room-ceiling-fan
  friendly_name: Living Room Ceiling Fan

#ceiling fan notes:
#https://github.com/esphome/feature-requests/issues/60
#https://esphome.io/components/fan/speed.html

esphome:
  name: $name
  friendly_name: $friendly_name

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: XXXX

ota:
  password: XXXX


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password


  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Living Room Ceiling Fan Fallback"
    password: "XXXX"

captive_portal:

web_server:
  port: 80

#D4 or GPIO2 for builtin LED -- https://escapequotes.net/esp8266-wemos-d1-mini-pins-and-diagram/
status_led:
  pin: 
    number: D4
    inverted: True

time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: America/New_York

switch:
  - platform: restart
    name: "ESPHome OS Restart"

  - platform: gpio
    internal: true
    name: "Light Control"
    id: 'light_control'
    pin: D7
    icon: "mdi:ceiling-fan-light"
    interlock: &interlock_group [light_control, fan_off, fan_low, fan_medium, fan_high]
    interlock_wait_time: 2000ms
    on_turn_on:
    - delay: 260ms
    - switch.turn_off: light_control

  - platform: gpio
    internal: true
    name: "Fan Off"
    id: 'fan_off'
    pin: D6
    icon: "mdi:ceiling-fan"
    interlock: *interlock_group
    interlock_wait_time: 2000ms
    on_turn_on:
    - delay: 400ms
    - switch.turn_off: fan_off

  - platform: gpio
    internal: true
    name: "Fan High"
    id: 'fan_high'
    pin: D5
    icon: "mdi:ceiling-fan"
    interlock: *interlock_group
    interlock_wait_time: 2000ms
    on_turn_on:
    - delay: 400ms
    - switch.turn_off: fan_high

  - platform: gpio
    internal: true
    name: "Fan Medium"
    id: 'fan_medium'
    pin: D2
    icon: "mdi:ceiling-fan"
    interlock: *interlock_group
    interlock_wait_time: 2000ms
    on_turn_on:
    - delay: 400ms
    - switch.turn_off: fan_medium

  - platform: gpio
    internal: true
    name: "Fan Low"
    id: 'fan_low'
    pin: D1
    icon: "mdi:ceiling-fan"
    interlock: *interlock_group
    interlock_wait_time: 2000ms
    on_turn_on:
    - delay: 400ms
    - switch.turn_off: fan_low

sensor:
  # Uptime sensor.
  - platform: uptime
    name: "Uptime"
    internal: true
    entity_category: diagnostic

  - platform: wifi_signal
    name: "2.4GHz Signal Strength"
    update_interval: 60s
    entity_category: diagnostic


text_sensor:
  # Expose ESPHome version as sensor.
  - platform: version
    name: ESPHome Version
    entity_category: diagnostic
  # Expose WiFi information as sensors.
  - platform: wifi_info
    ip_address:
      name: IP
      entity_category: diagnostic
    ssid:
      name: SSID
      entity_category: diagnostic
    bssid:
      name: BSSID
      entity_category: diagnostic
    

#enable a 3-speed fan+off, for 4 total switches required
output:
  - platform: template
    id: fan_template_output
    type: float
    write_action:
      - then:
        - lambda: |-
            if (state > 0.9) {
              id(fan_high).turn_on();
            } else if (state > 0.6) {
              id(fan_medium).turn_on();
            } else if (state > 0.1) {
              id(fan_low).turn_on();
            } else if (state < 0.1) {
              id(fan_off).turn_on();
            }

  - platform: template
    id: light_bogus_output
    type: binary
    write_action:
      - then:
        - logger.log: "Bogus Switch State Changed"

fan:
  - platform: speed
    id: fan_fan
    output: fan_template_output
    name: "Fan"
    speed_count: 3

light:
  #includes logic to make sure repeated off commands don't toggle the real light
  # check first to see if we are in status off or on
  - platform: binary
    id: light_light
    name: "Light"
    output: light_bogus_output
    on_turn_on:
      if:
        condition:
          binary_sensor.is_off: light_status
        then:
          switch.turn_on: light_control
    on_turn_off:
      if:
        condition:
          binary_sensor.is_on: light_status
        then:
          switch.turn_on: light_control


binary_sensor:
  - platform: template
    id: light_status
    entity_category: diagnostic
    internal: True
    name: "Light Status"
    lambda: |-
      if (id(light_light).current_values.is_on()) {
        // Light is on
        return true;
      } else {
        // Light is off
        return false;
      }