Sonoff Ifan04 - ESPHome working code

Hi All

Does anyone have a current working ESPHome code example they can share for the iFan04 with/without buzzer & remote control working please.

Thanks

You should be able to work it out from here.

Ditched my post from yesterday as it will only add confusion

I have it working now with RF & Buzzer under ESPHome1.19.4 - I can’t take any credit for the code, its a mashup from two other users that did the work @sliwma & @netadmindave - thanks !

yaml

substitutions:
  device_name: ifan04_test
  friendly_name: iFan04-test
  userpass: ********
  wifi_pass: ********
  ssid: *********

  
wifi:
  ssid: "${ssid}"
  password: "${wifi_pass}"
  manual_ip:
   static_ip: *********
   gateway: *********
   subnet: 255.255.255.0
   
  ap:
    ssid: "${friendly_name}"
    password: "${userpass}"

captive_portal:

logger:

api:

ota:

web_server:
  port: 80
  
time:
  - platform: homeassistant
    id: homeassistant_time

esphome:
  name: ${device_name}
  platform: ESP8266
  board: esp01_1m
  includes:
    - ifan04.h
  on_boot:
    priority: 225
    # turn off the light as early as possible
    then:
      - light.turn_off: ${device_name}_light

remote_receiver:
  pin: GPIO3

binary_sensor:
  - platform: gpio
    id: button
    pin:
      number: GPIO0
    on_press:
      then:
        - light.toggle: ${device_name}_light

  - platform: remote_receiver
    name: "Buzzer"
    id: remote_buzzer
    internal: true
    raw:
      code: [-207, 104, -103, 104, -104, 103, -104, 207, -104, 103, -104, 103, -104, 104, -103, 104, -103, 104, -104, 107, -721, 105, -206, 207, -518, 105, -931, 104, -104, 103, -725, 104, -104, 103, -725, 104, -104, 103, -207, 104, -414]
    on_release:
      then:
        - switch.toggle: buzzer_dummy

  - platform: remote_receiver
    name: "Fan 0"
    id: remote_0
    raw:
      code: [-207, 104, -103, 104, -104, 103, -104, 207, -104, 103, -104, 104, -103, 104, -104, 103, -104, 105, -102, 104, -725, 104, -311, 103, -518, 104, -933, 103, -104, 104, -725, 104, -932, 104, -207, 207, -519]
    on_release:
      then:
        - fan.turn_off: ${device_name}_fan
    internal: true

  - platform: remote_receiver
    id: remote_fan1
    raw:
      code: [-207, 104, -104, 103, -104, 104, -103, 207, -104, 104, -103, 104, -104, 103, -104, 104, -103, 104, -104, 103, -726, 103, -312, 103, -518, 104, -933, 103, -104, 104, -725, 104, -103, 104, -726, 103, -104, 311, -518]
    on_release:
      then:
        - fan.turn_on:
              id: ${device_name}_fan
              speed: 1
        - if:
            condition:
              and:
                - switch.is_on: buzzer_dummy
            then:
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
    internal: true
  - platform: remote_receiver
    id: remote_fan2
    raw:
      code: [-208, 103, -104, 104, -103, 104, -103, 208, -103, 104, -104, 103, -104, 104, -103, 104, -104, 103, -104, 103, -726, 104, -310, 104, -518, 104, -933, 103, -104, 104, -725, 104, -207, 104, -622, 103, -416, 102, -415]
    on_release:
      then:
        - fan.turn_on:
              id: ${device_name}_fan
              speed: 2
        - if:
            condition:
              and:
                - switch.is_on: buzzer_dummy
            then:
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
              - delay: 50ms
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
    internal: true

  - platform: remote_receiver
    id: remote_fan3
    raw:
      code: [-207, 104, -104, 103, -104, 104, -103, 208, -103, 104, -104, 103, -104, 104, -103, 104, -104, 103, -104, 103, -726, 104, -311, 104, -518, 103, -934, 103, -103, 104, -726, 103, -104, 207, -622, 104, -103, 104, -207, 104, -415]
    on_release:
      then:
        - fan.turn_on:
              id: ${device_name}_fan
              speed: 3
        - if:
            condition:
              and:
                - switch.is_on: buzzer_dummy
            then:
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
              - delay: 50ms
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
              - delay: 50ms
              - output.turn_on: buzzer
              - delay: 50ms
              - output.turn_off: buzzer
    internal: true

  - platform: remote_receiver
    id: remote_light
    raw:
      code: [-207, 104, -103, 104, -104, 103, -104, 207, -104, 103, -104, 104, -103, 104, -103, 104, -104, 103, -104, 104, -725, 104, -311, 103, -518, 104, -933, 103, -104, 103, -726, 103, -311, 104, -518, 104, -207, 104, -103, 104, -414]
    on_release:
      then:
        - light.toggle: ${device_name}_light


output:
  - platform: custom
    type: float
    outputs:
      id: fanoutput
    lambda: |-
      auto ${device_name}_fan = new IFan04Output();
      App.register_component(${device_name}_fan);
      return {${device_name}_fan};

  - platform: gpio
    pin: GPIO9
    inverted: True
    id: light_output

  - platform: gpio
    pin: GPIO10
    id: buzzer
    inverted: true

light:
  - platform: binary
    name: "${friendly_name} Light"
    output: light_output
    id: ${device_name}_light

switch:
  - platform: template
    id: buzzer_dummy
    name: "Buzzer"
    optimistic: True

  - platform: template
    id: update_fan_speed
    optimistic: True
    turn_on_action:
      then:
        - delay: 200ms
        - if:
            condition:
              and:
                - switch.is_off: fan_relay1
                - switch.is_off: fan_relay2
                - switch.is_off: fan_relay3
            then:
              - fan.turn_off: ${device_name}_fan
        - if:
            condition:
              and:
                - switch.is_on: fan_relay1
                - switch.is_off: fan_relay2
                - switch.is_off: fan_relay3
            then:
              - fan.turn_on:
                  id: ${device_name}_fan
                  speed: 1
        - if:
            condition:
              and:
                - switch.is_on: fan_relay1
                - switch.is_on: fan_relay2
                - switch.is_off: fan_relay3
            then:
              - fan.turn_on:
                  id: ${device_name}_fan
                  speed: 2
        - if:
            condition:
              and:
                - switch.is_on: fan_relay1
                - switch.is_off: fan_relay2
                - switch.is_on: fan_relay3
            then:
              - fan.turn_on:
                  id: ${device_name}_fan
                  speed: 3
        - switch.turn_off: update_fan_speed

  - platform: gpio
    pin: GPIO14
    id: fan_relay1

  - platform: gpio
    pin: GPIO12
    id: fan_relay2

  - platform: gpio
    pin: GPIO15
    id: fan_relay3

fan:
  - platform: speed
    output: fanoutput
    id: ${device_name}_fan
    name: "${friendly_name} Fan"
    speed_count: 3

ifan04.h

#include "esphome.h"
using namespace esphome;

class IFan04Output : public Component, public FloatOutput {
  public:
    void write_state(float state) override {
      if (state < 0.3) {
        // OFF
        digitalWrite(14, LOW);
        digitalWrite(12, LOW);
        digitalWrite(15, LOW);
      } else if (state < 0.6) {
        // low speed
        digitalWrite(14, HIGH);
        digitalWrite(12, LOW);
        digitalWrite(15, LOW);
      } else if (state < 0.9) {
        // medium speed
        digitalWrite(14, HIGH);
        digitalWrite(12, HIGH);
        digitalWrite(15, LOW);
      } else {
        // high speed
        digitalWrite(14, HIGH);
        digitalWrite(12, LOW);
        digitalWrite(15, HIGH);
      }
    }
};
1 Like

Can you share a link to wear you purchased this item and if it works well without exchanging the capacitors out for US-based fans?

Really? The page I pointed to has 4 sources to buy from and specifically says

Largely identical to iFan03 with capacitors designed for 110V fans and an improved RF chip with better range.

Really happy with mine now, have a nice lovelace card for the fan and my original wall switches work the lights & fan on/off at low speed using a D1 mini. I think medium speed is a tiny bit faster but low is spot on which is where our fans run 90% of the time.

I bought direct from itead, took around 3 weeks delivery to east coast. I got 2 units.

https://itead.cc/product/sonoff-ifan03-wi-fi-ceiling-fan-and-light-controller/

Your ifan04.h should say

IFan04Output

Having said that, I can’t seem to get my remote to work at all, any tips?

Everything works fine in HA.

Your getting the red LED on the remote - Just checking you removed the plastic strip?

Pairing the remote, you kill power to the ifan unit, then return power to the device and press and hold any button on the remote within 5 seconds. and you should here a beep from the buzzer. I think on original Sonoff FW your meant to hold bottom right button in case that has any relevance

(I don’t recall hearing the buzzer myself)

1 Like

:disappointed: guess I should have read the little book lol I didn’t do the 5 second pairing thing.

Thanks for getting back to me, everything is working now.

1 Like

@mattcony Thanks for this, it works awesome!!

1 Like

I’m struggling to get the remote to work. How long did it take to pair? Did you ever hear anything out of the buzzer?

@tmoehlman Hey Tom, the way I paired the remote was to press one of the remote buttons a few times within 5 seconds of powering the ifan04 unit on, for me it worked fine the first time, and I did two of these.
The buzzer does work for me, but only when the remote is used to function the fan. The buzzer can also be turned on/off with the remote.

@sihartley Thanks Simon. I tried everything, and for some reason I could never get it to work. Finally, I flashed it with Tasmota, and was able to get the remote to work first try. Maybe I’ll give ESPHome another try at some point, but for now I think I’ll stick with Tasmota.

Tom, the reason you struggled with the remote is that remote_receiver is not the ideal approach to processing the RF commands as they are actually a serial data stream. That can be confirmed by looking at the Tasmota implementation, Tasmota/xdrv_22_sonoff_ifan.ino at e5d576b5076874def7c990ff4ee1c3db8cf8207e · arendst/Tasmota · GitHub. I was getting too many misses with the remote_receiver approach so I worked with @ssieb and he threw this together, for handling the serial I/O and turning the RF commands from the Sonoff RM433 remote into trigger actions custom_components/components/ifan04 at master · ssieb/custom_components · GitHub. I will post my YAML config in the next couple of days once I have had the ladder out and completed the install. I refactored away the custom IFan04Output class as that can be achieved with an output template

Sweet! I’ll give it a try.
Looking forward to seeing your config.

Here is the config, I don’t save state and the light does not come on with the power, you have to turn the light on yourself, which is actually one of the reasons I replaced the original Hunter remote as that would always come on after a power outage. I have stuck with the relay configuration used by @mattcony above, but I did see this article that discusses a different relay sequence.

Update: The below yaml has been superseded in this subsequent post, Sonoff Ifan04 - ESPHome working code - #21 by NigelHA

yaml

substitutions:
  name:  living-room-fan
  device_description: Sonoff iFan04-L
  friendly_name: Living Room Fan/Light

esphome:
  name: ${name}
  comment: ${device_description}
  platform: ESP8266
  board: esp01_1m
  on_boot:
    priority: 225
    # turn off the light as early as possible
    then:
      - light.turn_off: fan_light

# Disable logging on serial as it is used by the remote
logger:
  baud_rate: 0

# Enable Home Assistant API
api:

ota:
  password: !secret ota_password

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

web_server:
  port: 80
  auth:
    username: !secret web_server_username
    password: !secret web_server_password
  
external_components:
  - source: github://ssieb/custom_components
    components: [ ifan04 ]

time:
  - platform: homeassistant
    id: time_homeassistant
    on_time_sync:
      - component.update: sensor_uptime_timestamp

uart:
  tx_pin: GPIO01
  rx_pin: GPIO03
  baud_rate: 9600
#  debug:
#    sequence:
#      - lambda: UARTDebug::log_hex(direction, bytes, ',');

ifan04:
  on_fan:
    - lambda: |-
        if (speed) {
          auto call = id(the_fan).turn_on();
          call.set_speed(speed);
          call.perform();
          if (id(buzzer_dummy).state) {
            switch(speed) {
              case 3:
                id(buzzer_pin).turn_on();
                delay(50);
                id(buzzer_pin).turn_off();
                delay(50);
              case 2:
                id(buzzer_pin).turn_on();
                delay(50);
                id(buzzer_pin).turn_off();
                delay(50);
              case 1:
                id(buzzer_pin).turn_on();
                delay(50);
                id(buzzer_pin).turn_off();
            }
          }
        } else {
          auto call = id(the_fan).turn_off();
          call.perform();
        }
  on_light:
    - light.toggle: fan_light
  on_buzzer:
    - switch.toggle: buzzer_dummy

sensor:
  - platform: uptime
    id: sensor_uptime

  - platform: template
    id: sensor_uptime_timestamp
    name: "${friendly_name} Uptime"
    device_class: "timestamp"
    accuracy_decimals: 0
    update_interval: never
    lambda: |-
      static float timestamp = (
        id(time_homeassistant).utcnow().timestamp - id(sensor_uptime).state
      );
      return timestamp;

  - platform: wifi_signal
    name: ${friendly_name} Signal
    update_interval: 60s

binary_sensor:
  - platform: gpio
    id: button
    pin: GPIO0
    on_press:
      then:
        - light.toggle: fan_light

interval:
  - interval: 500ms
    then:
      - if:
          condition:
            not:
              wifi.connected:
          then:
            - light.turn_on:
                id: led1
                brightness: 100%
                transition_length: 0s
            - delay: 250ms
            - light.turn_off:
                id: led1
                transition_length: 250ms

output:
  - platform: template
    id: fanoutput
    type: float
    write_action:
      - if:
          condition:
            lambda: return (state < 0.3);
          then:
            # OFF
            - switch.turn_off: fan_relay1
            - switch.turn_off: fan_relay2
            - switch.turn_off: fan_relay3
          else:
            if:
              condition:
                lambda: return (state < 0.6);
              then:
                # low speed
                - switch.turn_on: fan_relay1
                - switch.turn_off: fan_relay2
                - switch.turn_off: fan_relay3
              else:
                if:
                  condition:
                    lambda: return (state < 0.9);
                  then:
                    # medium speed
                    - switch.turn_on: fan_relay1
                    - switch.turn_on: fan_relay2
                    - switch.turn_off: fan_relay3

                  else:
                    # high speed
                    - switch.turn_on: fan_relay1
                    - switch.turn_off: fan_relay2
                    - switch.turn_on: fan_relay3

  - platform: gpio
    id: light_relay
    pin: GPIO9
    inverted: true

  - platform: gpio
    id: buzzer_pin
    pin: GPIO10
    inverted: true

  - platform: esp8266_pwm
    id: led_pin
    pin: GPIO13
    inverted: true

light:
  - platform: binary
    id: fan_light
    name: "${friendly_name} Light"
    output: light_relay

  - platform: monochromatic
    id: led1
    output: led_pin
    default_transition_length: 0s
    restore_mode: always off

switch:
  - platform: template
    id: buzzer_dummy
    name: "Buzzer"
    optimistic: True

  - platform: gpio
    id: fan_relay1
    pin: GPIO14

  - platform: gpio
    id: fan_relay2
    pin: GPIO12

  - platform: gpio
    id: fan_relay3
    pin: GPIO15

fan:
  - platform: speed
    id: the_fan
    name: "${friendly_name} Fan"
    output: fanoutput
    speed_count: 3

Great to see others are having success with the ifan04 – i just got mine and attempted to flash, but even with switching the RX/TX around and confirming the device is getting 3.3… i can’t get esphome to recognize the device locally.

I’ve tried both booting up while pressing the big white button, and booting up without pressing the white button. The later ends up with the device beeping at me consistently for some unknown reason … but the former is how i traditionally flashed sonoff’s, but esphome doesn’t recognize the ifan04…

wondering if i’m missing a step, or if there was a hardware revision i’m unaware of… any help would be appreciated

I too struggled initially to get the device into flash mode. I tried the ESPHome flashing tool for Windows and then Tasmotizer for windows tool. It was not until I was using Tasmotizer that I realized that my new ESP flashing device wanted RX to RX and TX to TX, I did not the go back and try the ESPHome flashing tool for Windows. It is a couple weeks since I did it, but I believe I might have held the button down for the entire flashing process. I used Tasmotizer to take a backup of the original firmware just in case :slight_smile:

Any further information on the correct relay sequence? I have tested other fans without custom firmware and it seems that they toggle on more relays for low and medium speeds to help the blades start up. This thread discusses the issue on an iFan03 in tasmota, but I am not sure how that would translate to ESPHome. Something like this?

# low speed
- switch.turn_on: fan_relay1
- switch.turn_on: fan_relay2
- switch.turn_on: fan_relay3
- delay: 3s
- switch.turn_off: fan_relay2
- switch.turn_off: fan_relay3

Why is relay 1 set to on for medium and high?

The article you linked gives a very good explanation of the Tasmota code. The complicating issue is that is seems like they also discuss the impact of capacitors in series, so without knowing the individual ratings of the capacitor associated with each relay and the Physics knowledge to correctly calculate I went with the existing settings of @mattcony. The fixed delay sounds like a good workaround until you consider the transition when the fan is already running. You only really want to enable the higher speeds at startup when the fan is not already turning. I will give tracking state some thought.