Garage Door Opener

Table of Contents

1. Project Overview

Objective

This project aims to design and implement a smart garage door opener system that operates reliably and securely using local control. The solution is built on an ESP32 DevKitC microcontroller running ESPHome firmware, with multiple sensors to determine the door state, safety mechanisms to prevent accidental closures, and smart integration with Home Assistant and Sonos for automation and feedback.

Key Goals

  • Enable remote and automated control of a garage door.
  • Ensure accurate door state tracking using multiple sensors.
  • Provide clear pre-close alerts for occupant safety.
  • Operate independently of the cloud with local automation.
  • Seamlessly integrate with Home Assistant and Sonos.

2. Diagnostics

Purpose

Diagnostics help ensure the device is operating reliably and is correctly integrated with Home Assistant. This includes monitoring uptime, firmware version, device identity, and the ability to remotely restart the ESP32 if needed.

Diagnostic Entities

Entity NameDescription
sensor.garage_door_uptime_sensorTracks how long the device has been running
text_sensor.garage_door_esphome_versionShows the version of ESPHome firmware
button.garage_door_restart_buttonAllows manual restart of the ESP32 remotely
sensor.garage_door_wifi_signalWi-Fi signal strength in dBm
sensor.garage_door_wifi_rssiRelative Wi-Fi signal quality (RSSI)
sensor.garage_door_free_heapCurrent available heap memory on the ESP32
sensor.garage_door_restart_reasonReason for the last microcontroller reboot

Best Practices

  • Add these entities to a dedicated “Diagnostics” dashboard tab in Home Assistant.
  • Use uptime to monitor stability and catch unexpected reboots.
  • Monitor Wi-Fi signal strength (optional) to catch connectivity issues.
  • Use the restart button to recover the device if it becomes unresponsive.


3. Hardware Configuration

Microcontroller

  • ESP32 DevKitC: Chosen for its dual-core processing, integrated Wi-Fi, and ESPHome compatibility. This board provides sufficient GPIOs and power to manage all the peripherals used.

Components Overview

ComponentFunctionNotes
Relay Module (BESTEP 3V)Controls the garage door motorSimulates a button press
HC-SR04 UltrasonicMeasures distance to the doorDetects open/closed states
Reed SwitchDetects when door is fully openWired backup to ultrasonic
PIR Motion SensorSenses movement inside garageUsed for safety/interruption
Piezo BuzzerEmits warning tone before closureConfigurable beep patterns
LEDStrobes before closingSynchronized with buzzer
Sonos SpeakerPlays voice messages via HARequires Home Assistant setup
Garage Motor LightOptional light relay controlNot directly controlled yet

GPIO Pin Mapping

This table defines the GPIO pin connections used on the ESP32. Always verify pin compatibility and strapping pin warnings before flashing.

NameGPIOConnected Component
door_control_pinGPIO1Relay module
wired_sensor_pinGPIO5Reed switch
warning_beep_pinGPIO4Piezo buzzer
warning_leds_pinGPIO3LED
wireless_sensor_trigGPIO12HC-SR04 trigger
wireless_sensor_echoGPIO14HC-SR04 echo
motion_sensor_pinGPIO13PIR motion sensor
status_ledGPIO18ESP32 onboard/status LED

4. Software Configuration

Platform Details

  • ESPHome v2025.5.1: Manages sensor input/output, automations, and state tracking.
  • Home Assistant: Provides dashboard visibility, TTS alerts, and automations using ESPHome entities.

File Structure

####
# GENERAL SETTINGS
# Customize these variables to your preferences and needs
# more: https://esphome.io/guides/configuration-types.html#substitutions
substitutions:

  ####
  # NAME
  # By default, the name of the ESPHome device is "garage-door-xxxxxx" where xxxxxx is a unique identifier. The device's
  # hostname on your network is also defined by the name, defaulting to "garage-door-xxxxxx.local". Edit this variable to
  # customize the name and hostname. Note: only lowercase characters, numbers and hyphen(-) are allowed.
  name: garage-door
  friendly_name: Garage Door Opener
  project_name: parsons.garage-door-esp32
  project_version: "0.1.1"
  garage_door_cover_name: Garage Door
  switch_name: STR output

  ####
  # GARAGE DOOR OPENER MOMENTARY DURATION
  # Duration to make the relay contact closure for the garage door opener button circuit. 
  garage_door_opener_momentary_duration: 300ms

  ####
  # OPEN GARAGE DOOR DISTANCE MARGIN OF ERROR
  # The margin of error (+/-) in meters from the value above to consider the garage door in the open position.
  open_garage_door_distance_margin_of_error: "0.05"

  ####
  # GARAGE DOOR CLOSE WARNING DURATION
  # Duration to blink the warning LED and beep the buzzer before the garage door closes.
  garage_door_close_warning_duration: 5s

  warning_beep_pulse_time: 100ms
  warning_beep_pause_time: 130ms
  warning_beep_internal_only: "true"
  warning_beep_shared: "false"
  sensor_debounce_time: 200ms
  range_sensor_polling_time: 2500ms
  range_sensor_debounce_time: 3s
  blink_on_state: "true"

  ####
  # INTERNAL MAPPINGS
  door_control_pin: GPIO1 #TX
  wired_sensor_pin: GPIO5 #5
  warning_beep_pin: GPIO4 #4
  warning_leds_pin: GPIO3 #RX
  output_switch_pin: GPIO2 #2
  status_led: GPIO18 #18
  sda: GPIO6 #CLK
  scl: GPIO7 #D0
  wireless_sensor_trig_pin: GPIO12 #12
  wireless_sensor_echo_pin: GPIO14 #14

  status_led_inverted: "false"

esphome:
  name: $name
  friendly_name: $friendly_name
  name_add_mac_suffix: true
  min_version: '2024.12.0' # Includes ESP-IDF 5.1
  project:
    name: $project_name
    version: $project_version
  platformio_options:
    board_build.flash_mode: dio
  
  on_boot:
    - priority: 800.0
  then:
    - lambda: |-
        id(device_id).publish_state(get_mac_address());
        id(project_version).publish_state(ESPHOME_PROJECT_VERSION);

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_LWIP_MAX_SOCKETS: "16"

text_sensor:
  - platform: template
    name: Device ID
    id: device_id
    entity_category: diagnostic
    update_interval: never
  - platform: version
    name: ESPHome Version
    hide_timestamp: true
  - platform: template
    name: Project version
    id: project_version
    entity_category: diagnostic
    update_interval: never

sensor:
  - platform: uptime
    name: Uptime
    id: uptime_sensor
    entity_category: diagnostic

button:
  - platform: restart
    name: Restart
    id: restart_button
    entity_category: config

#### 
# OTA UPDATES
# Enables over-the-air updates
# more: https://esphome.io/components/ota.html
ota:
  - platform: esphome
    id: konnected_ota
    password: "0d332334936534534323b166bef31f5634"

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "SMSPSXteLehs/8CJaMPXW9dLTTexYNRUfySD0CnNIPQ="

wifi:
  manual_ip: 
    static_ip: 192.168.4.107
    gateway: 192.168.4.1
    subnet: 255.255.255.0
  ssid: !secret wifi_ssid
  password: !secret wifi_password

##Install package components
packages:
  garage_door_control: !include /config/packages/garage_door/garage_door_control.yaml 
  garage_door_wired_sensor: !include /config/packages/garage_door/garage_door_wired_sensor.yaml
  garage_door_wireless_sensor: !include /config/packages/garage_door/garage_door_wireless_sensor.yaml
  garage_door_motion_sensor: !include /config/packages/garage_door/garage_door_motion_sensor.yaml
  garage_door_warning_alerts: !include /config/packages/garage_door/garage_door_warning_alerts.yaml
  garage_door_cover_template: !include /config/packages/garage_door/garage_door_cover_template.yaml
  garage_door_status_led: !include /config/packages/garage_door/garage_door_status_led.yaml
  garage_door_diagnostics: !include /config/packages/garage_door/garage_door_diagnostics.yaml
  garage_door_door_state_sensor: !include /config/packages/garage_door/garage_door_state_sensor.yaml
  • Central configuration resides in garage_door.yaml
  • All logic is modularized under /config/packages/garage_door/

Software Modules and Their Roles

File NameDescription
garage_door_control.yamlDefines GPIO relay switch and template button for triggering door
garage_door_wired_sensor.yamlReed switch input, filtered for debounce
garage_door_wireless_sensor.yamlUltrasonic sensor to measure door distance
garage_door_state_sensor.yamlTemplate logic to infer door open/closed state
garage_door_warning_alerts.yamlBuzzer and LED outputs with alert patterns
garage_door_motion_sensor.yamlMotion detection using PIR sensor
garage_door_cover_template.yamlMain cover entity logic for open/close/toggle/stop
garage_door_status_led.yamlOptional ESP32 LED indicator status
garage_door_diagnostics.yamlUptime, ESPHome version, restart button

5. garage_door_diagnostics.yaml

sensor:
  - platform: uptime
    name: "Garage Door Uptime"
    id: garage_door_uptime_sensor

  - platform: wifi_signal
    name: "Garage Door WiFi Signal"
    id: garage_door_wifi_signal
    update_interval: 60s

  - platform: template
    name: "Garage Door Free Heap"
    lambda: "return (float) ESP.getFreeHeap();"
    update_interval: 60s
    unit_of_measurement: "B"
    accuracy_decimals: 0
    id: garage_door_free_heap

  - platform: template
    name: "Garage Door Restart Reason"
    lambda: "return String(esp_reset_reason()).c_str();"
    update_interval: 300s
    id: garage_door_restart_reason

text_sensor:
  - platform: version
    name: "Garage Door ESPHome Version"
    id: garage_door_esphome_version

button:
  - platform: restart
    name: "Garage Door Restart"
    id: garage_door_restart_button

6. garage_door_control.yaml

switch:
  - platform: gpio
    id: garage_door_relay
    pin: ${door_control_pin}
    restore_mode: ALWAYS_OFF
    name: "Garage Door Relay"

button:
  - platform: template
    id: garage_door_opener_button
    name: "Garage Door Button"
    on_press:
      - switch.turn_on: garage_door_relay
      - delay: ${garage_door_opener_momentary_duration}
      - switch.turn_off: garage_door_relay

7. garage_door_wired_sensor.yaml

binary_sensor:
  - platform: gpio
    pin:
      number: ${wired_sensor_pin}
      mode: INPUT_PULLUP
      inverted: true
    name: "Garage Door Wired Sensor"
    id: garage_door_wired_sensor
    device_class: opening
    filters:
      - delayed_on: ${sensor_debounce_time}
      - delayed_off: ${sensor_debounce_time}

8. garage_door_wireless_sensor.yaml

sensor:
  - platform: ultrasonic
    trigger_pin: ${wireless_sensor_trig_pin}
    echo_pin: ${wireless_sensor_echo_pin}
    name: "Garage Door Wireless Sensor"
    id: garage_door_wireless_sensor
    update_interval: ${range_sensor_polling_time}
    filters:
      - median: 5
    timeout: 2m
    unit_of_measurement: m

9. garage_door_state_sensor.yaml

binary_sensor:
  - platform: template
    name: "Garage Door Is Open"
    id: garage_door_is_open
    lambda: |-
      if (!id(garage_door_wired_sensor).has_state() || !id(garage_door_wireless_sensor).has_state()) {
        return false;
      }
      if (id(garage_door_wired_sensor).state) {
        return true;
      } else if (id(garage_door_wireless_sensor).state > (1.5 - 0.05)) {
        return true;
      } else {
        return false;
      }
    device_class: opening

10. garage_door_warning_alerts.yaml

output:
  - platform: gpio
    pin: ${warning_beep_pin}
    id: garage_door_buzzer

  - platform: gpio
    pin: ${warning_leds_pin}
    id: garage_door_strobe

script:
  - id: pre_close_warning
    mode: restart
    then:
      - repeat:
          count: 10
          then:
            - output.turn_on: garage_door_buzzer
            - output.turn_on: garage_door_strobe
            - delay: ${warning_beep_pulse_time}
            - output.turn_off: garage_door_buzzer
            - output.turn_off: garage_door_strobe
            - delay: ${warning_beep_pause_time}

11. garage_door_motion_sensor.yaml

binary_sensor:
  - platform: gpio
    pin:
      number: ${motion_sensor_pin}
      mode: INPUT
    name: "Garage Door Motion Sensor"
    id: garage_door_motion_sensor
    device_class: motion
    filters:
      - delayed_on: 50ms
      - delayed_off: 5s

12. garage_door_cover_template.yaml

cover:
  - platform: template
    name: "Garage Door"
    id: garage_door
    device_class: garage

    open_action:
      - if:
          condition:
            lambda: return id(garage_door).position == COVER_CLOSED;
          then:
            - button.press: garage_door_opener_button
            - cover.template.publish:
                id: garage_door
                current_operation: OPENING

    close_action:
      - if:
          condition:
            lambda: return id(garage_door).position == COVER_OPEN;
          then:
            - script.execute: pre_close_warning
            - delay: 5s
            - button.press: garage_door_opener_button
            - cover.template.publish:
                id: garage_door
                current_operation: CLOSING

    toggle_action:
      - if:
          condition:
            lambda: return id(garage_door).position == COVER_OPEN;
          then:
            - script.execute: pre_close_warning
            - delay: 5s
            - button.press: garage_door_opener_button
            - cover.template.publish:
                id: garage_door
                current_operation: CLOSING
          else:
            - button.press: garage_door_opener_button
            - cover.template.publish:
                id: garage_door
                current_operation: OPENING

    stop_action:
      - if:
          condition:
            not:
              lambda: return id(garage_door).current_operation == COVER_OPERATION_IDLE;
          then:
            - button.press: garage_door_opener_button
            - cover.template.publish:
                id: garage_door
                current_operation: IDLE

13. garage_door_status_led.yaml

status_led:
  pin:
    number: ${status_led}
    inverted: ${status_led_inverted}