Skip to content

Instantly share code, notes, and snippets.

@adrianslabu
Last active November 20, 2025 21:30
Show Gist options
  • Select an option

  • Save adrianslabu/91bf7586484f2f1b3973de915d6a3891 to your computer and use it in GitHub Desktop.

Select an option

Save adrianslabu/91bf7586484f2f1b3973de915d6a3891 to your computer and use it in GitHub Desktop.
03. Youtube - Turn a regular FAN into a SMART fan
esphome:
name: fan-gym
friendly_name: Fan Gym
on_boot:
- if:
condition:
switch.is_off: child_lock
then:
- lambda: |-
if (id(switch_rotation).state) { id(rotation).turn_on(); }
if (id(switch_speed_1).state) { id(fan_speed).publish_state(1); }
else if (id(switch_speed_2).state) { id(fan_speed).publish_state(2); }
else if (id(switch_speed_3).state) { id(fan_speed).publish_state(3); }
else { id(fan_speed).publish_state(0); }
else:
- lambda: |-
id(fan_speed).publish_state(0);
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
switch:
- platform: gpio
name: "Speed 1"
pin:
number: GPIO1
inverted: true
id: speed_1
internal: true
- platform: gpio
name: "Speed 2"
pin:
number: GPIO2
inverted: true
id: speed_2
internal: true
- platform: gpio
name: "Speed 3"
pin:
number: GPIO3
inverted: true
id: speed_3
internal: true
- platform: gpio
name: "Rotation"
pin:
number: GPIO4
inverted: true
id: rotation
icon: "mdi:rotate-360"
- platform: template
name: "Child Lock"
id: child_lock
icon: "mdi:lock"
optimistic: true
number:
- platform: template
name: "Speed"
id: fan_speed
min_value: 0
max_value: 3
step: 1
optimistic: true
icon: "mdi:fan"
on_value:
then:
- script.execute: set_fan_speed
script:
- id: set_fan_speed
mode: restart
then:
- lambda: |-
id(speed_1).turn_off(); id(speed_2).turn_off(); id(speed_3).turn_off();
- delay: 200ms
- lambda: |-
if (id(fan_speed).state == 0) { id(rotation).turn_off(); id(fan_state).publish_state("Off"); }
else if (id(fan_speed).state == 1) { id(speed_1).turn_on(); id(fan_state).publish_state("On"); }
else if (id(fan_speed).state == 2) { id(speed_2).turn_on(); id(fan_state).publish_state("On"); }
else if (id(fan_speed).state == 3) { id(speed_3).turn_on(); id(fan_state).publish_state("On"); }
text_sensor:
- platform: template
name: "Fan State"
id: fan_state
update_interval: never
binary_sensor:
- platform: gpio
pin:
number: GPIO5
mode: INPUT_PULLDOWN
id: switch_speed_1
internal: true
on_state:
- if:
condition:
switch.is_off: child_lock
then:
- lambda: |-
if (id(switch_speed_1).state) { id(fan_speed).publish_state(1); }
- delay: 100ms
- lambda: |-
if (id(switch_rotation).state) { id(rotation).turn_on(); }
else { id(rotation).turn_off(); }
- platform: gpio
pin:
number: GPIO6
mode: INPUT_PULLDOWN
id: switch_speed_2
internal: true
on_state:
- if:
condition:
switch.is_off: child_lock
then:
- lambda: |-
if (id(switch_speed_2).state) { id(fan_speed).publish_state(2); }
- delay: 100ms
- lambda: |-
if (id(switch_rotation).state) { id(rotation).turn_on(); }
else { id(rotation).turn_off(); }
- platform: gpio
pin:
number: GPIO7
mode: INPUT_PULLDOWN
id: switch_speed_3
internal: true
on_state:
- if:
condition:
switch.is_off: child_lock
then:
- lambda: |-
if (id(switch_speed_3).state) { id(fan_speed).publish_state(3); }
- delay: 100ms
- lambda: |-
if (id(switch_rotation).state) { id(rotation).turn_on(); }
else { id(rotation).turn_off(); }
- platform: gpio
pin:
number: GPIO10
mode: INPUT_PULLDOWN
id: switch_rotation
internal: true
- platform: template
id: switch_off_position
internal: true
lambda: |-
if (!id(switch_speed_1).state && !id(switch_speed_2).state && !id(switch_speed_3).state && !id(switch_rotation).state) {
return true;
} else {
return false;
}
on_state:
- if:
condition:
switch.is_off: child_lock
then:
- lambda: |-
if (x) {
id(fan_speed).publish_state(0);
}
logger:
api:
encryption:
key: "ey5yfd26+yxqREwtOwVlhEbM1eiic3biGjO/7Qs3SfQ="
ota:
- platform: esphome
password: "4734b8ad677b48692b628985192527fd"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Fan-Gym Fallback Hotspot"
password: "1VaxJ6FEF7Lj"
captive_portal:
type: custom:bubble-card
card_type: button
button_type: slider
icon: mdi:fan
force_icon: false
show_state: false
show_last_changed: false
show_attribute: false
name: Fan Gym
sub_button:
- entity: sensor.fan_gym_fan_state
show_state: true
show_icon: true
icon: mdi:fan
tap_action:
action: none
show_name: false
name: State
- show_state: true
name: Rotation
tap_action:
action: perform-action
perform_action: switch.toggle
target:
entity_id: switch.fan_gym_rotation
show_name: false
entity: switch.fan_gym_rotation
- entity: switch.fan_gym_child_lock
show_state: true
show_name: false
name: Child Lock
tap_action:
action: perform-action
perform_action: switch.toggle
target:
entity_id: switch.fan_gym_child_lock
icon: mdi:account-lock
show_icon: true
styles: >+
.bubble-sub-button-1{
background-color: ${hass.states['sensor.fan_gym_fan_state'].state == 'off' ? '' : 'var(--bubble-button-active-background)'} !important;
}
.bubble-sub-button-2{
background-color: ${hass.states['sensor.fan_gym_fan_state'].state == 'off' ? '' : 'var(--bubble-button-active-background)'} !important;
}
${icon.setAttribute("icon", hass.states['sensor.fan_gym_icon']?.state ===
'mdi:fan-off' ? 'mdi:fan-off' : hass.states['sensor.fan_gym_icon']?.state)}
button_action:
tap_action:
action: none
entity: number.fan_gym_speed
min_value: 0
max_value: 3
step: 1
tap_action:
action: none
read_only_slider: false
relative_slide: true
- platform: template
sensors:
fan_gym_icon:
value_template: >
{% set nr = states('number.fan_gym_speed') | int %}
{% if nr == 0 %}
mdi:fan-off
{% elif nr == 1 %}
mdi:fan-speed-1
{% elif nr == 2 %}
mdi:fan-speed-2
{% elif nr == 3 %}
mdi:fan-speed-3
{% endif %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment