Note the change in ESPHome - we can no longer use slashes “/” in names.
WARNING 'RC button RF/WiFi' contains '/' which is reserved as a URL path separator. Automatically replacing with 'RC button RF⁄WiFi' (Unicode FRACTION SLASH). Please update your configuration. This will become an error in ESPHome 2026.7.0.
I changed this line:
name: "RC button RF/WiFi"
to:
name: "RC button RF+WiFi"
The 2026.1 ESPHome lists breaking changes for fan lambdas, but no broken lambdas are present - you can safely upgrade.
Here’s a currently working device file; I keep everything in one file for simplicity:
substitutions:
devicename: fan-bedroom-s
friendly_devicename: Fan Bedroom (S)
ota_password: !secret ota_password_fan-bedroom-s
api_key: !secret api_key_fan-bedroom-s
wifi_ssid: !secret wifi_ssid
wifi_password: !secret wifi_password
wifi_fallback_password: !secret wifi_fallback_password
###################################
# Don't edit anything beyond here #
###################################
fan_startup_boost_time: 5s
# Morse codes for the different buzzer sounds
code_buzzer_on: "E" # .
code_buzzer_off: "I" # ..
code_fan_off: "T" # -
code_fan_1: "N" # -.
code_fan_2: "D" # -..
code_fan_3: "B" # -...
code_rc_unpair: "O" # ---
code_short: "E" # .
code_long: "T" # -
external_components:
- source:
type: git
url: https://github.com/rh1rich/esphome-ifan-remote
- source:
type: git
url: https://github.com/rh1rich/esphome-morse-code
esphome:
project:
name: "rh1rich.iFan04"
version: "1.0.0"
name: $devicename
friendly_name: $friendly_devicename
on_boot:
priority: 600
then:
- output.turn_off: buzzer
esp8266:
board: esp8285
framework:
version: latest
early_pin_init: false
restore_from_flash: true
preferences:
flash_write_interval: 5min
# Enable logging
# But disable it via UART0, because UART0 is used by the RF receiver
logger:
baud_rate: 0
# level: VERBOSE
# Enable Home Assistant API
api:
encryption:
key: "$api_key"
# Setting reboot timeout to 0 seconds to disable the automatic reboot, if no api connection (Home Assistant server).
# This is essential for offline usage.
reboot_timeout: 0s
actions:
- action: play_morse_code
variables:
text_string: string
then:
- morse_code.start:
text: !lambda 'return text_string;'
ota:
- platform: esphome
password: "$ota_password"
wifi:
ssid: "$wifi_ssid"
password: "$wifi_password"
fast_connect: yes
# Set minimum authentication mode
min_auth_mode: wpa2
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "$devicename"
password: "$wifi_fallback_password"
captive_portal:
web_server:
port: 80
version: 3
# GPIO00 ... Button
# GPIO01 ... UART0 TX
# GPIO03 ... UART0 RX (RF Remote Control)
# GPIO04 ... I2C SDA (TP11 D_RX on PCB backside)
# GPIO05 ... I2C SCL (TP10 D_TX on PCB backside)
# GPIO09 ... Light Relay (D)
# GPIO10 ... Buzzer
# GPIO12 ... Fan Relay 2 (D11, 3uF cap on IFAN04-H)
# GPIO13 ... LED
# GPIO14 ... Fan Relay 1 (D5, 2.5uF cap on IFAN04-H)
# GPIO15 ... Fan Relay 3 (D13, no cap)
status_led:
pin:
number: GPIO13
inverted: true
# UART0 (GPIO03) is used by the RF-receiver to send the received RF-commands
# to the ESP.
uart:
rx_pin: GPIO03
baud_rate: 9600
id: uart_bus
# I2C via the test points TP10 (= GPIO05) and TP11 (= GPIO04) on the PCB.
# i2c:
# sda: GPIO04
# scl: GPIO05
# frequency: 200kHz
output:
- platform: gpio
id: light_relay
pin:
number: GPIO09
inverted: true
- platform: gpio
id: buzzer
pin:
number: GPIO10
inverted: true
- platform: gpio
id: fan_relay_1
pin: GPIO14
- platform: gpio
id: fan_relay_2
pin: GPIO12
- platform: gpio
id: fan_relay_3
pin: GPIO15
- platform: template
id: fan_relays
type: float
write_action:
- if:
condition:
lambda: return (state < 0.3);
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_fan_off
- script.execute:
id: fan_speed_set
speed: 0
- if:
condition:
lambda: return ((state >= 0.3) && (state < 0.6));
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_fan_1
- script.execute:
id: fan_speed_set
speed: 1
- if:
condition:
lambda: return ((state >= 0.6) && (state < 0.9));
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_fan_2
- script.execute:
id: fan_speed_set
speed: 2
- if:
condition:
lambda: return (state >= 0.9);
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_fan_3
- script.execute:
id: fan_speed_set
speed: 3
morse_code:
output: buzzer
dit_duration: 50
light:
- platform: binary
name: 'Light'
id: light_comp
output: light_relay
fan:
- platform: speed
name: 'Fan'
id: fan_comp
output: fan_relays
speed_count: 3
switch:
- platform: template
name: 'Buzzer enabled'
id: buzzer_enabled
entity_category: config
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- morse_code.start: $code_buzzer_on
on_turn_off:
- morse_code.start: $code_buzzer_off
- platform: template
name: 'Light enabled'
id: light_enabled
entity_category: config
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
name: 'Fan startup-boost enabled'
id: fan_boost_enabled
entity_category: config
optimistic: True
restore_mode: RESTORE_DEFAULT_OFF
button:
- platform: restart
name: Restart
entity_category: diagnostic
sensor:
- platform: uptime
name: 'Uptime'
entity_category: diagnostic
unit_of_measurement: s
- platform: wifi_signal
id: wifi_signal_db
name: 'WiFi Signal dB'
entity_category: diagnostic
# Sensor for the WiFi signal strength in percent (%)
- platform: copy
id: wifi_signal_rel
source_id: wifi_signal_db
name: "WiFi Signal"
entity_category: diagnostic
filters:
- lambda: |-
return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
device_class: 'signal_strength'
# Expample for using a SHT3x sensor which is connected via the test pins TP10 (I2C SCL) and TP11 (I2C SDA) on the PCB.
# Also enable i2c component!
# - platform: sht3xd
# temperature:
# name: 'Temperature'
# humidity:
# name: 'Humidity'
binary_sensor:
- platform: gpio
name: "Button"
pin:
number: GPIO00
inverted: true
- platform: template
name: "RC button light"
id: rc_button_light
filters:
- delayed_off: 300ms
- platform: template
name: "RC button RF+WiFi"
id: rc_button_rfwifi
filters:
- delayed_off: 300ms
- platform: template
name: "RC button WiFi (long)"
id: rc_button_wifi_long
filters:
- delayed_off: 300ms
- platform: template
name: "RC button RF (long)"
id: rc_button_rf_long
filters:
- delayed_off: 300ms
# Remote commands via UART0
# RX: GPIO03
#
# The numbering of the following remote control buttons
# starts with the top left button (1) and ends with the
# bottom right button (8).
#
# 1 (Light) = (AA55) 0104 000104 (0A)
# 2 (Mute) = (AA55) 0106 000101 (09)
# 3 (Fan high) = (AA55) 0104 000103 (09)
# 4 (Fan mid) = (AA55) 0104 000102 (08)
# 5 (Fan off) = (AA55) 0104 000100 (06)
# 6 (Fan low) = (AA55) 0104 000101 (07)
# 7 (RF Clearing) = (AA55) 0101 000102 (05)
# 7 (RF Clearing 5s) = (AA55) 0107 000101 (0A) clears the connection between remote control and receiver!!
# 8 (WiFi Pairing) = (AA55) 0101 000102 (05)
# 8 (WiFi Pairing 5s) = (AA55) 0101 000101 (04)
ifan_remote:
on_command:
then:
# - logger.log:
# format: "RC command '%04X:%06X' received."
# args: [ 'command.high','command.low' ]
- if:
condition:
lambda: return (command.high == 0x0104 && command.low == 0x000100);
then:
- fan.turn_off: fan_comp
- if:
condition:
lambda: return (command.high == 0x0104 && command.low == 0x000101);
then:
- fan.turn_on:
id: fan_comp
speed: 1
- if:
condition:
lambda: return (command.high == 0x0104 && command.low == 0x000102);
then:
- fan.turn_on:
id: fan_comp
speed: 2
- if:
condition:
lambda: return (command.high == 0x0104 && command.low == 0x000103);
then:
- fan.turn_on:
id: fan_comp
speed: 3
- if:
condition:
lambda: return (command.high == 0x0104 && command.low == 0x000104);
then:
- if:
condition:
lambda: return id(light_enabled).state;
then:
- light.toggle: light_comp
else:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_short
- binary_sensor.template.publish:
id: rc_button_light
state: ON
- binary_sensor.template.publish:
id: rc_button_light
state: OFF
- if:
condition:
lambda: return (command.high == 0x0106 && command.low == 0x000101);
then:
- switch.toggle: buzzer_enabled
- if:
condition:
lambda: return (command.high == 0x0101 && command.low == 0x000102);
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_short
- binary_sensor.template.publish:
id: rc_button_rfwifi
state: ON
- binary_sensor.template.publish:
id: rc_button_rfwifi
state: OFF
- if:
condition:
lambda: return (command.high == 0x0101 && command.low == 0x000101);
then:
- if:
condition:
lambda: return id(buzzer_enabled).state;
then:
- morse_code.start: $code_long
- binary_sensor.template.publish:
id: rc_button_wifi_long
state: ON
- binary_sensor.template.publish:
id: rc_button_wifi_long
state: OFF
- if:
condition:
lambda: return (command.high == 0x0107 && command.low == 0x000101);
then:
- morse_code.start: $code_rc_unpair
- binary_sensor.template.publish:
id: rc_button_rf_long
state: ON
- binary_sensor.template.publish:
id: rc_button_rf_long
state: OFF
text_sensor:
- platform: version
name: 'ESPHome Version'
entity_category: diagnostic
- platform: wifi_info
ip_address:
name: WiFi IP Address
entity_category: diagnostic
ssid:
name: WiFi SSID
entity_category: diagnostic
mac_address:
name: WiFi MAC Address
entity_category: diagnostic
dns_address:
name: WiFi DNS Address
entity_category: diagnostic
globals:
- id: last_fan_speed
type: int
restore_value: no
initial_value: "0"
script:
- id: fan_speed_set
mode: restart
parameters:
speed: int
then:
- if:
condition:
lambda: return (speed == 0);
then:
- output.turn_off: fan_relay_1
- output.turn_off: fan_relay_2
- output.turn_off: fan_relay_3
else:
- if:
condition:
lambda: return (id(fan_boost_enabled).state && (id(last_fan_speed) == 0));
then:
- output.turn_off: fan_relay_1
- output.turn_off: fan_relay_2
- output.turn_on: fan_relay_3
- delay: $fan_startup_boost_time
- if: # enable cap 1
condition:
lambda: return (speed == 1);
then:
- output.turn_on: fan_relay_1
- output.turn_off: fan_relay_2
- output.turn_off: fan_relay_3
- if: # enable cap 1 & 2
condition:
lambda: return (speed == 2);
then:
- output.turn_on: fan_relay_1
- output.turn_on: fan_relay_2
- output.turn_off: fan_relay_3
- if: # no caps
condition:
lambda: return (speed == 3);
then:
- output.turn_off: fan_relay_1
- output.turn_off: fan_relay_2
- output.turn_on: fan_relay_3
- lambda: |-
id(last_fan_speed) = speed;