Refactoring Template Switches to Native Light & Fan (ADC Feedback + Pulse Logic)

Hi everyone,

I have successfully “smartified” my kitchen hood using an ESP32 and ESPHome. The hardware side is fully functional and stable.

Hi everyone,

I have successfully “smartified” my kitchen hood using an ESP32 and ESPHome. The hardware side is fully functional and stable.

My Setup:

• Control: I use OUTPUT_OPEN_DRAIN on GPIOs to simulate physical button presses (pulsing the button to toggle state).

• Feedback: Since I don’t have clean logic signals, I am reading the voltage of the hood’s LEDs using ADC sensors on the ESP32.

• Logic: If the ADC reads > 2.5V, I know the function is ON.

Current Status:

I have configured everything as switch entities in ESPHome (platform: template). I have 5 switches: Light, Power, Low, Medium, High.

Everything works perfectly: I can control the hood, and the state updates correctly in Home Assistant based on the voltage readings.

The Goal:

I want to clean up my configuration. Instead of having 5 separate switches in Home Assistant, I want to define:

  1. A native Light entity (light).

  2. A native Fan entity (fan) with 3 speeds.

The Problem:

I tried using light: platform: template and fan: platform: template, but I kept running into compilation errors or “Platform not found” issues.

Could someone help me refactor my working Switch YAML into proper Light and Fan templates?

Specific Logic Needed:

• Fan Turn Off Logic: On this specific hood, there is no dedicated “Off” command. To turn the fan OFF, I must simulate a button press on the currently active speed. (e.g., if Speed 2 is ON, pressing button 2 again turns it OFF).

Thanks

Here is my current working YAML (Switches):


esphome:
  name: hotte-cuisine01
  friendly_name: "Hotte Cuisine"

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:
  level: info

api: xxxxxxxx

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


captive_portal: {}

# ==========================================================
# 1. LECTURES ANALOGIQUES (ADC1)
# ==========================================================
sensor:
  # LIGHT - ORANGE
  - platform: adc
    pin: GPIO36
    id: adc_light
    attenuation: 12db
    update_interval: 0.1s
    filters:
      - max: { window_size: 10, send_every: 5 }

  # POWER - JAUNE
  - platform: adc
    pin: GPIO32
    id: adc_power
    attenuation: 12db
    update_interval: 0.1s
    filters:
      - max: { window_size: 10, send_every: 5 }

  # HIGH - VERT
  - platform: adc
    pin: GPIO35
    id: adc_high
    attenuation: 12db
    update_interval: 0.1s
    filters:
      - max: { window_size: 10, send_every: 5 }

  # MEDIUM - BRUN
  - platform: adc
    pin: GPIO34
    id: adc_medium
    attenuation: 12db
    update_interval: 0.1s
    filters:
      - max: { window_size: 10, send_every: 5 }

  # SLOW - ROUGE
  - platform: adc
    pin: GPIO33
    id: adc_slow
    attenuation: 12db
    update_interval: 0.1s
    filters:
      - max: { window_size: 10, send_every: 5 }

# ==========================================================
# 2. STATUS LOGIQUES (SEUILS > 2.5V = ON)
# ==========================================================
binary_sensor:
  
  - platform: template
    name: "Statut Lumière"
    id: status_light
    internal: true
    lambda: return id(adc_light).state > 2.5;
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms

  - platform: template
    name: "Statut Power"
    id: status_power
    internal: true
    lambda: return id(adc_power).state > 2.5;
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms

  - platform: template
    name: "Statut High"
    id: status_high
    internal: true
    lambda: return id(adc_high).state > 2.5;
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms

  - platform: template
    name: "Statut Medium"
    id: status_medium
    internal: true
    lambda: return id(adc_medium).state > 2.5;
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms

  - platform: template
    name: "Statut Slow"
    id: status_slow
    internal: true
    lambda: return id(adc_slow).state > 2.5;
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms

# ==========================================================
# 3. ACTIONNEURS (SWITCHS)
# ==========================================================
switch:
  # --- BOUTONS PHYSIQUES (CACHÉS) ---
  - platform: gpio
    id: btn_light_raw
    internal: true
    pin:
      number: GPIO23
      mode: OUTPUT_OPEN_DRAIN
      inverted: false
    restore_mode: ALWAYS_ON

  - platform: gpio
    id: btn_power_raw
    internal: true
    pin:
      number: GPIO18
      mode: OUTPUT_OPEN_DRAIN
      inverted: false
    restore_mode: ALWAYS_ON

  - platform: gpio
    id: btn_high_raw
    internal: true
    pin:
      number: GPIO19
      mode: OUTPUT_OPEN_DRAIN
      inverted: false
    restore_mode: ALWAYS_ON

  - platform: gpio
    id: btn_medium_raw
    internal: true
    pin:
      number: GPIO21
      mode: OUTPUT_OPEN_DRAIN
      inverted: false
    restore_mode: ALWAYS_ON

  - platform: gpio
    id: btn_slow_raw
    internal: true
    pin:
      number: GPIO22
      mode: OUTPUT_OPEN_DRAIN
      inverted: false
    restore_mode: ALWAYS_ON

  # --- INTERFACE HOME ASSISTANT (SWITCHS VISIBLES) ---
  
  # LUMIÈRE
  - platform: template
    name: "Lumière Hotte"
    id: switch_lumiere_hotte
    icon: "mdi:lightbulb"
    lambda: return id(status_light).state;
    turn_on_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_light_raw
            - delay: 200ms
            - switch.turn_on: btn_light_raw
    turn_off_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_light_raw
            - delay: 200ms
            - switch.turn_on: btn_light_raw

  # POWER
  - platform: template
    name: "Hotte Power"
    icon: "mdi:power"
    lambda: return id(status_power).state;
    turn_on_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_power_raw
            - delay: 200ms
            - switch.turn_on: btn_power_raw
    turn_off_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_power_raw
            - delay: 200ms
            - switch.turn_on: btn_power_raw

  # HIGH
  - platform: template
    name: "Hotte High"
    icon: "mdi:fan-plus"
    lambda: return id(status_high).state;
    turn_on_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_high_raw
            - delay: 200ms
            - switch.turn_on: btn_high_raw
    turn_off_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_high_raw
            - delay: 200ms
            - switch.turn_on: btn_high_raw

  # MEDIUM
  - platform: template
    name: "Hotte Medium"
    icon: "mdi:fan-speed-2"
    lambda: return id(status_medium).state;
    turn_on_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_medium_raw
            - delay: 200ms
            - switch.turn_on: btn_medium_raw
    turn_off_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_medium_raw
            - delay: 200ms
            - switch.turn_on: btn_medium_raw

  # SLOW
  - platform: template
    name: "Hotte Slow"
    icon: "mdi:fan-speed-1"
    lambda: return id(status_slow).state;
    turn_on_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_slow_raw
            - delay: 200ms
            - switch.turn_on: btn_slow_raw
    turn_off_action:
      - if:
          condition: lambda: 'return millis() > 1000;'
          then:
            - switch.turn_off: btn_slow_raw
            - delay: 200ms
            - switch.turn_on: btn_slow_raw

You can start the cleaning by using analog threshold binary sensor.

and by fixing your description posted twice…

Thanks I will have a look to that and you are right about my description (fixed).