@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
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.
@devjklein: Here is a modified version that when the fan is off, will start the fan on high for 5 seconds then switch to the target speed. I also modified the relay sequence to speed 1 is relay 1 only, speed 2 is relay 2 only and speed 3 is relay 3 only.
yaml
substitutions:
name: living-room-fan
device_description: Sonoff iFan04-L
friendly_name: Living Room Fan/Light
globals:
- id: target_fan_speed
type: int
- id: start_time_offset
type: int
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
on_loop:
lambda: |-
if (id(start_time_offset) && (millis() > id(start_time_offset))) {
ESP_LOGD("IFAN04", "Delayed wakeup to select final target speed: %d", id(target_fan_speed));
auto call = id(the_fan).turn_on();
call.set_speed(id(target_fan_speed));
call.perform();
id(start_time_offset) = 0;
}
# 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
fast_connect: on
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) {
if (!id(target_fan_speed)) {
ESP_LOGD("IFAN04", "Fan currently off, starting on max reducing to %d in 5 seconds", speed);
id(target_fan_speed) = speed;
speed = 3;
id(start_time_offset) = millis() + 5000;
} else {
ESP_LOGD("IFAN04", "Fan already running, current speed (%d) new speed: (%d)", id(target_fan_speed), speed);
id(start_time_offset) = 0;
id(target_fan_speed) = speed;
}
auto call = id(the_fan).turn_on();
call.set_speed(speed);
call.perform();
if (id(buzzer_dummy).state) {
switch(id(target_fan_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 {
id(target_fan_speed) = 0;
id(start_time_offset) = 0;
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_off: fan_relay1
- switch.turn_on: fan_relay2
- switch.turn_off: fan_relay3
else:
# high speed
- switch.turn_off: 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: restart
name: "${friendly_name} Restart"
- 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
wow thanks for this!
I got mine all installed, it works, great… I just now realized the light is on/off and not a dimmer - crud.
fwiw - i’m up and running now too. I had a difficult time getting the device to burn properly. After initially not even being able to connect, my second issue was esphome would write the firmware, but it’d never start and connect to my wifi.
I found that starting with tasmotta, i was able to write the firmware and get on the wifi immidiately… tasmotta has an extra option that “erases” the device during firmware write. This seems to have resolved by issues.
I had a small secondary issue with not enough space to upload my esphome firmeware to the tasmotta burned device… until i backed down tasmotta to the “lite” / “minimal” version first… then i could swap over to my esp image no problem.
Just got around to uploading your code to my fan. It works great! I modified the start delay from 5 to 3 seconds, so it wouldn’t reach such a high speed on startup (fan is directly over seating, so max speed can be overwhelming )
I guess your fan starts spinning a lot faster than mine . Glad I could help, you provided the motivation to do something to improve the longevity of the iFan04-L device and the fan in this type of installation.
I did just notice an issue, if I start the fan from a stop with the remote, it operates as expected and toggles all relays on for a few seconds. However, if I set the fan speed in homeassistant, it appears the fan module bypasses the speed logic and goes directly to toggling on a single relay. Any thoughts on this?
The logic that is currently in the on_fan lambda needs to move to the output template. The on_fan handler is only used by the remote. I will think about how that needs to be reworked over the next few days.
@devjklein: Reworked so that the use of Home-Assistant utilizes the boost at startup as well when using the remote. I think there is a bug in the HA front-end, in that if you slide the fan speed to where you want and release it sends one set speed command and works perfectly, but if you tap the speed bar at a new speed level when the unit is off it will send the speed command twice as it powers the device on defeating the speed boost at startup.
yaml
substitutions:
name: living-room-fan
device_description: Sonoff iFan04-L
friendly_name: Living Room Fan/Light
globals:
- id: target_fan_speed
type: int
- id: start_time_offset
type: int
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
on_loop:
lambda: |-
if (id(start_time_offset) && (millis() > id(start_time_offset))) {
ESP_LOGD("IFAN04", "Setting target speed: %d", id(target_fan_speed));
auto call = id(the_fan).turn_on();
call.set_speed(id(target_fan_speed));
call.perform();
id(start_time_offset) = 0;
}
# 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
fast_connect: on
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(id(target_fan_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 {
id(target_fan_speed) = 0;
id(start_time_offset) = 0;
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:
- lambda: |-
if (state) {
int speed = int(state / 0.33);
if (!id(target_fan_speed) && (speed < 3)) {
ESP_LOGD("IFAN04", "Fan currently off, boosting speed");
id(target_fan_speed) = speed;
state = 1.0;
id(start_time_offset) = millis() + 5000; // 5 second delay
} else {
id(start_time_offset) = 0;
id(target_fan_speed) = speed;
}
}
if (state < 0.3) {
// OFF
id(target_fan_speed) = 0;
id(start_time_offset) = 0;
id(fan_relay1).turn_off();
id(fan_relay2).turn_off();
id(fan_relay3).turn_off();
} else if (state < 0.6) {
// low speed
id(fan_relay1).turn_on();
id(fan_relay2).turn_off();
id(fan_relay3).turn_off();
} else if (state < 0.9) {
// medium speed
id(fan_relay1).turn_off();
id(fan_relay2).turn_on();
id(fan_relay3).turn_off();
} else {
// high speed
id(fan_relay1).turn_off();
id(fan_relay2).turn_off();
id(fan_relay3).turn_on();
}
- 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: restart
name: "${friendly_name} Restart"
- 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
@NigelHA Thank you for your work on this. I installed four ifan04 devices yesterday and they are working great in HA. My fans old remote control modules had a feature that I would like to check if it is possible to implement on the ifan04. When power to the fans is removed by using a wall switch or because of a power outage, the fans would remember and go back to their previous state when the power is restored. For example if a fan was running on medium when it lost power, it will start back at medium speed when the power is restored. Can that be done with the ifan04 and ESPHome?
I have been working on an external component for the ifan04, that puts all of the c++ code where it belongs rather than in lambdas.
If anyone is interested
external_components:
- source: github://cpyarger/custom_components
if you want to check the code, it is in components/ifan
The next steps are to integrate ssieb’s ifan04, so that it’s all handled within the component code.
I can’t wait to try your code. Everything is working almost perfectly now, but I just noticed that my fan low and medium speeds are the same, so only low and high are really working.
Ill push my latest updates.
if you set your external components as such;
external_components:
- source: github://cpyarger/custom_components@master
refresh: 0s
Then whenever I push an update, you should get the latest on a fresh install.
I think the default refresh is 1 day
If you have any issues, or suggestions, please let me know