Complete Guide to Motor Automation with ESPHome

Learn how to automate motors, blinds, and curtains using ESPHome firmware, from basic configuration to advanced safety features.

Rahul Verma

November 7, 2025

Complete Guide to Motor Automation with ESPHome

Automating motors opens up a world of possibilities: motorized blinds that close at sunset, curtains that open with your morning alarm, garage doors controlled by voice, and ventilation systems that respond to air quality. In this comprehensive guide, we'll explore how to build reliable motor automation using ESPHome firmware.

What is ESPHome?

ESPHome is open-source firmware for ESP8266 and ESP32 microcontrollers that makes IoT device creation incredibly simple. Instead of writing C++ code, you define device behavior in YAML configuration files.

Why ESPHome for Motor Control?

  • Native Home Assistant integration - Devices appear instantly
  • Over-the-air updates - No cables needed for firmware updates
  • Built-in safety features - Timeouts, limits, and error handling
  • Community support - Thousands of example configurations
  • Cost-effective - ESP32 boards cost ₹300-500
  • Reliable - Battle-tested in thousands of installations

Understanding Motor Control Basics

Types of Motors

DC Brushed Motors (12-24V)

  • Most common for blinds and curtains
  • Simple forward/reverse control
  • PWM speed control
  • Typical current: 0.5-3A

Stepper Motors

  • Precise position control
  • No feedback sensors needed
  • More complex but very accurate
  • Higher power requirements

AC Motors (110-240V)

  • Used in garage doors, large blinds
  • Require relay control
  • More dangerous - professional installation recommended

This guide focuses on DC brushed motors which are safe, affordable, and perfect for most automation projects.

Motor Driver Basics

DC motors need a motor driver (H-bridge) to control direction and speed:

ESP32 ────> Motor Driver ────> Motor
         (PWM signals)  (Power)

Common motor drivers:

  • L298N - 2A per channel, affordable (₹150)
  • TB6612 - 1.2A per channel, compact
  • BTS7960 - 43A, for heavy motors

The L298N is perfect for most home automation projects and is what MotorWala uses.

The MotorWala Hardware

MotorWala combines an ESP32 microcontroller with an L298N motor driver, designed specifically for blind/curtain automation:

Hardware Specifications

  • Microcontroller: ESP32-WROOM-32 (dual-core 240MHz)
  • Motor Driver: L298N Dual H-Bridge
  • Input Voltage: 12-24V DC
  • Motor Current: 2A continuous, 3A peak
  • PWM Frequency: 1000 Hz
  • Position Sensing: Dual limit switches (GPIO32, GPIO33)
  • Current Monitoring: ADC on GPIO34
  • Communication: WiFi, Home Assistant API, MQTT, HTTP

Connection Diagram

┌──────────────────────────────────────┐
│         MotorWala Board              │
│                                      │
│  Power Input:                        │
│  +12-24V ──┐                         │
│  GND ──────┤                         │
│            │                         │
│  Motor Output:                       │
│  M+ ───────┤                         │
│  M- ───────┤                         │
│            │                         │
│  Limit Switches (optional):          │
│  Upper ────┤ GPIO32                  │
│  Lower ────┤ GPIO33                  │
│            │                         │
│  Current Sensor:                     │
│  ADC ──────┤ GPIO34                  │
└──────────────────────────────────────┘

Basic ESPHome Configuration

Let's start with a minimal configuration to get a motor running:

Minimal Config

esphome:
  name: my-first-motor
  friendly_name: Bedroom Blinds
  platform: ESP32
  board: esp32dev

# WiFi connection
wifi:
  ssid: "YourWiFiNetwork"
  password: "YourPassword"

  # Fallback hotspot if WiFi fails
  ap:
    ssid: "Motor Fallback"
    password: "setup12345"

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "YOUR_32_BYTE_ENCRYPTION_KEY_HERE="

# Enable OTA updates
ota:
  password: "yourOTApassword"

# Motor driver outputs
output:
  # Forward direction
  - platform: ledc
    pin: GPIO25
    id: motor_forward
    frequency: 1000 Hz

  # Backward direction
  - platform: ledc
    pin: GPIO26
    id: motor_backward
    frequency: 1000 Hz

# Cover entity
cover:
  - platform: time_based
    name: "Bedroom Blinds"
    id: blinds

    # Actions to open
    open_action:
      - output.set_level:
          id: motor_forward
          level: 100%
      - output.set_level:
          id: motor_backward
          level: 0%

    # Actions to close
    close_action:
      - output.set_level:
          id: motor_forward
          level: 0%
      - output.set_level:
          id: motor_backward
          level: 100%

    # Actions to stop
    stop_action:
      - output.set_level:
          id: motor_forward
          level: 0%
      - output.set_level:
          id: motor_backward
          level: 0%

    # How long it takes to fully open/close
    open_duration: 30s
    close_duration: 30s

This basic configuration gives you:

  • WiFi connectivity with fallback AP
  • Home Assistant integration
  • Open, close, and stop commands
  • Position estimation based on time

Using Secrets File

Never hardcode passwords! Create a secrets.yaml file:

# secrets.yaml
wifi_ssid: "YourActualNetworkName"
wifi_password: "YourActualPassword"
api_encryption_key: "base64encodedkey32byteslong=="
ota_password: "changeThisPassword"

Then reference in main config:

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

api:
  encryption:
    key: !secret api_encryption_key

Adding Limit Switches

Limit switches prevent the motor from running past its travel limits:

binary_sensor:
  # Upper limit (fully open)
  - platform: gpio
    pin:
      number: GPIO32
      mode: INPUT_PULLUP
      inverted: true
    name: "Upper Limit"
    id: upper_limit
    on_press:
      then:
        - cover.stop: blinds
        - logger.log: "Upper limit reached"

  # Lower limit (fully closed)
  - platform: gpio
    pin:
      number: GPIO33
      mode: INPUT_PULLUP
      inverted: true
    name: "Lower Limit"
    id: lower_limit
    on_press:
      then:
        - cover.stop: blinds
        - logger.log: "Lower limit reached"

How it works:

  1. Limit switches are normally open (NO)
  2. When pressed (blind reaches end), they close
  3. ESPHome detects press and stops motor
  4. Prevents mechanical damage

Wiring limit switches:

Limit Switch ──┬── GPIO pin
               └── GND

Overcurrent Protection

Motors can draw excessive current if jammed or stalled. Protect them with current monitoring:

sensor:
  - platform: adc
    pin: GPIO34
    name: "Motor Current"
    id: motor_current
    update_interval: 100ms

    # Convert ADC reading to amperes
    filters:
      - multiply: 3.3  # ADC voltage
      - calibrate_linear:
          - 0.0 -> 0.0
          - 3.3 -> 5.0  # Max current in amps

    # Alert on high current
    on_value_range:
      # If current exceeds 2.5A
      - above: 2.5
        then:
          # Stop motor immediately
          - cover.stop: blinds
          # Log warning
          - logger.log:
              format: "OVERCURRENT! Motor stopped at %.2fA"
              args: ['x']
              level: WARN
          # Optional: send notification
          - homeassistant.event:
              event: esphome.motor_overcurrent
              data:
                device: "blinds"
                current: !lambda 'return x;'

Why overcurrent protection matters:

  • Prevents motor burnout
  • Detects mechanical jams
  • Saves expensive motors
  • Fire safety

Typical current values:

  • Normal operation: 0.5-1.5A
  • Starting surge: 2-3A (brief)
  • Jam/stall: >3A (dangerous!)

Advanced Speed Control

Control motor speed for quieter operation or energy savings:

output:
  - platform: ledc
    pin: GPIO25
    id: motor_forward
    frequency: 1000 Hz

  - platform: ledc
    pin: GPIO26
    id: motor_backward
    frequency: 1000 Hz

# Number component for speed adjustment
number:
  - platform: template
    name: "Motor Speed"
    id: motor_speed
    min_value: 30
    max_value: 100
    step: 10
    initial_value: 80
    optimistic: true
    unit_of_measurement: "%"
    icon: "mdi:speedometer"

cover:
  - platform: time_based
    name: "Bedroom Blinds"
    id: blinds

    open_action:
      # Use variable speed
      - output.set_level:
          id: motor_forward
          level: !lambda 'return id(motor_speed).state / 100.0;'
      - output.set_level:
          id: motor_backward
          level: 0%

    close_action:
      - output.set_level:
          id: motor_forward
          level: 0%
      - output.set_level:
          id: motor_backward
          level: !lambda 'return id(motor_speed).state / 100.0;'

Now you can adjust speed from Home Assistant:

  • 30% - Very quiet, slow
  • 50% - Quiet, moderate speed
  • 80% - Default, good balance
  • 100% - Maximum speed, louder

Soft Start/Stop

Prevent jerky motion with gradual acceleration:

cover:
  - platform: time_based
    name: "Bedroom Blinds"
    id: blinds

    open_action:
      # Start slowly
      - output.set_level:
          id: motor_forward
          level: 30%
      - delay: 500ms
      # Ramp up to full speed
      - output.set_level:
          id: motor_forward
          level: 100%

    stop_action:
      # Slow down before stopping
      - output.set_level:
          id: motor_forward
          level: 30%
      - output.set_level:
          id: motor_backward
          level: 30%
      - delay: 300ms
      # Full stop
      - output.turn_off: motor_forward
      - output.turn_off: motor_backward

Benefits:

  • Smoother operation
  • Less wear on mechanics
  • Quieter
  • More elegant

Position Calibration

For accurate position tracking, calibrate your motor:

button:
  - platform: template
    name: "Calibrate Motor"
    id: calibrate_button
    on_press:
      - logger.log: "Starting calibration..."

      # Fully close
      - cover.close: blinds
      - wait_until:
          binary_sensor.is_on: lower_limit
      - delay: 500ms

      # Fully open and time it
      - globals.set:
          id: calibration_start
          value: !lambda 'return millis();'
      - cover.open: blinds
      - wait_until:
          binary_sensor.is_on: upper_limit
      - lambda: |-
          uint32_t duration = millis() - id(calibration_start);
          id(open_duration_ms) = duration;
          id(close_duration_ms) = duration;
          ESP_LOGI("calibration", "Travel time: %d ms", duration);

      - logger.log: "Calibration complete!"

globals:
  - id: calibration_start
    type: uint32_t
  - id: open_duration_ms
    type: uint32_t
    initial_value: '30000'
  - id: close_duration_ms
    type: uint32_t
    initial_value: '30000'

Run calibration after installation for accurate position tracking.

Common Use Cases

Automated Morning Routine

# In Home Assistant automations.yaml
automation:
  - alias: "Wake Up - Open Blinds"
    trigger:
      - platform: time
        at: "07:00:00"
    condition:
      - condition: state
        entity_id: person.you
        state: "home"
      - condition: state
        entity_id: binary_sensor.workday
        state: "on"
    action:
      - service: cover.set_cover_position
        target:
          entity_id: cover.bedroom_blinds
        data:
          position: 50  # Partial open for gentle wake
      - delay: 00:10:00
      - service: cover.open_cover
        target:
          entity_id: cover.bedroom_blinds  # Fully open

Sun-Responsive Blinds

automation:
  - alias: "Close Blinds on Hot Afternoon"
    trigger:
      - platform: numeric_state
        entity_id: sensor.outdoor_temperature
        above: 35
      - platform: sun
        event: sunrise
        offset: "+03:00:00"
    condition:
      - condition: time
        after: "12:00:00"
        before: "18:00:00"
    action:
      - service: cover.close_cover
        target:
          entity_id: cover.south_facing_blinds

Energy Saving

automation:
  - alias: "Winter - Open Blinds for Solar Heating"
    trigger:
      - platform: sun
        event: sunrise
        offset: "+01:00:00"
    condition:
      - condition: numeric_state
        entity_id: sensor.outdoor_temperature
        below: 15
      - condition: template
        value_template: "{{ now().month in [11, 12, 1, 2] }}"
    action:
      - service: cover.open_cover
        target:
          entity_id: cover.living_room_blinds

Safety Considerations

Electrical Safety

  1. Use proper power supply

    • 12V 5A or 24V 3A minimum
    • Regulated DC supply only
    • Fused on input side
  2. Wire gauge matters

    • Motor wires: 18 AWG (1.0mm²) minimum
    • Power supply: 16 AWG (1.5mm²) recommended
    • Undersized wires = heat and fire risk
  3. Secure connections

    • Use terminal blocks, not tape
    • Double-check polarity
    • Strain relief on moving parts

Mechanical Safety

  1. Install safety limits

    • Mechanical stops prevent overtravel
    • Limit switches as backup
    • Test before connecting to Home Assistant
  2. Emergency stop

    • Physical switch to cut power
    • Accessible in emergencies
    • Test regularly
  3. Child safety

    • Secure cords away from reach
    • Consider cordless motors
    • Soft-start reduces pinch risk

Software Safety

# Watchdog timer
interval:
  - interval: 60s
    then:
      - if:
          condition:
            # If motor has been running >60 seconds
            lambda: 'return id(blinds).current_operation != COVER_OPERATION_IDLE;'
          then:
            - logger.log:
                level: ERROR
                format: "Motor running too long! Emergency stop."
            - cover.stop: blinds

# Timeout protection
cover:
  - platform: time_based
    name: "Bedroom Blinds"
    id: blinds
    open_duration: 30s
    close_duration: 30s

    # Built-in timeout (130% of expected time)
    # Automatically stops if takes too long

Troubleshooting

Motor Doesn't Move

Check:

  1. Power supply voltage (12-24V)
  2. Motor connections (M+, M-)
  3. Driver board has power LED on
  4. Try direct power to motor (test if motor works)
  5. Check ESPHome logs for errors

Solutions:

# Add debug logging
logger:
  level: DEBUG
  logs:
    cover: DEBUG
    output: DEBUG

Motor Runs Backward

Quick fix: Swap M+ and M- wires

Or in software:

cover:
  - platform: time_based
    name: "Bedroom Blinds"

    # Swap open/close actions
    open_action:
      - output.set_level:
          id: motor_backward  # Changed from motor_forward
          level: 100%

Position Tracking Inaccurate

Causes:

  • Mechanical slippage
  • Variable load
  • Incorrect timing

Solution: Use limit switches for absolute positioning:

cover:
  - platform: endstop
    name: "Bedroom Blinds"

    open_action:
      - output.turn_on: motor_forward
    open_duration: 32s
    open_endstop: upper_limit

    close_action:
      - output.turn_on: motor_backward
    close_duration: 32s
    close_endstop: lower_limit

High Current Draw

Check:

  1. Motor rated voltage matches supply
  2. Mechanical binding/friction
  3. Motor bearings OK
  4. No load on motor (disconnect and test)

Monitor:

sensor:
  - platform: adc
    pin: GPIO34
    name: "Motor Current"
    on_value_range:
      - above: 3.0
        then:
          - homeassistant.service:
              service: notify.mobile_app
              data:
                message: "Motor current high: {{ x }}A"

Performance Optimization

Reduce WiFi Reconnects

wifi:
  power_save_mode: none
  fast_connect: true
  reboot_timeout: 5min

Optimize Update Intervals

sensor:
  - platform: adc
    pin: GPIO34
    name: "Motor Current"
    # Don't update too frequently
    update_interval: 100ms  # Good
    # update_interval: 10ms  # Excessive!

Use Static IP

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.150
    gateway: 192.168.1.1
    subnet: 255.255.255.0

Going Further

Add Manual Controls

binary_sensor:
  # Wall-mounted up button
  - platform: gpio
    pin:
      number: GPIO18
      mode: INPUT_PULLUP
      inverted: true
    name: "Manual Up Button"
    on_press:
      - cover.open: blinds

  # Wall-mounted down button
  - platform: gpio
    pin:
      number: GPIO19
      mode: INPUT_PULLUP
      inverted: true
    name: "Manual Down Button"
    on_press:
      - cover.close: blinds

Multiple Motors

# Control 2 motors independently
output:
  # Motor 1
  - platform: ledc
    pin: GPIO25
    id: motor1_forward
  - platform: ledc
    pin: GPIO26
    id: motor1_backward

  # Motor 2
  - platform: ledc
    pin: GPIO27
    id: motor2_forward
  - platform: ledc
    pin: GPIO14
    id: motor2_backward

cover:
  - platform: time_based
    name: "Left Curtain"
    open_action:
      - output.turn_on: motor1_forward
    # ... rest of config

  - platform: time_based
    name: "Right Curtain"
    open_action:
      - output.turn_on: motor2_forward
    # ... rest of config

Synchronized Dual Motors

# For wide blinds with 2 motors
cover:
  - platform: template
    name: "Living Room Blinds"
    id: dual_blinds

    open_action:
      - cover.open: left_motor
      - cover.open: right_motor

    close_action:
      - cover.close: left_motor
      - cover.close: right_motor

    stop_action:
      - cover.stop: left_motor
      - cover.stop: right_motor

Conclusion

Motor automation with ESPHome is powerful, flexible, and accessible. With the configurations in this guide, you can:

  • Build reliable automated blinds and curtains
  • Implement comprehensive safety features
  • Integrate seamlessly with Home Assistant
  • Customize behavior to your exact needs
  • Maintain and debug your system easily

The beauty of ESPHome is that it grows with your needs. Start simple, add features gradually, and enjoy the comfort of automated motor control.

Resources


Have questions about motor automation? Reach out on our community forum or contact support.

Share this article

Rahul Verma

Writer and automation enthusiast at Wala Works, passionate about open-source solutions and smart home technology.