ESP32 Garage Door / Gate Controller with Position Feedback (Reed Switch + Relay)

A lot of garage doors and sliding gates already have a simple “push-button” input that:

  • Toggles between OPEN → STOP → CLOSE → STOP
  • Or just OPEN/CLOSE depending on the controller

With an ESP32, a small relay, and a reed switch you can turn that into a fully integrated Home Assistant device:

  • Tap a button in HA to open/close the door
  • See whether it’s OPEN or CLOSED on the dashboard
  • Get notifications if it’s left open
  • Add night auto-close and other safety automations

This guide focuses on:

  • Relay pulse to the motor controller
  • Reed switch for position feedback
  • ESPHome config (plus an MQTT alternative)
  • Home Assistant cover entity and automations

1. Safety & Integration Concept

We are not directly driving the motor power.
Instead, we emulate the wall push-button:

  • Motor controller already handles:
    • Limit switches
    • Safety edges / photocells
    • Force detection, etc.

The ESP32 just:

  • Shorts the push-button terminals via relay for ~0.5 s → same as pressing the button
  • Reads a reed switch (or magnetic contact) that indicates if the door is closed

This is the safest and easiest way to integrate.

⚠️ Important: All wiring should be done with the motor power off and in accordance with the controller manual. Use only the low-voltage control terminals, never experiment directly on mains.


2. Hardware Required

  • ESP32 DevKit
  • 1 × Relay module (3.3 V compatible signal input)
  • 1 × Reed switch contact (like a door/window magnetic sensor)
  • 1 × Magnet (for the door)
  • Low-voltage wires to:
    • Motor controller’s push-button terminals
    • Reed switch mounted on the door / frame
  • 5 V or 12 V DC power supply (depending on relay / control voltage)
  • Optional:
    • Second reed switch for fully open position
    • Additional photocell / obstacle sensor (if available from controller)

3. Wiring

3.1 Relay to Motor Controller “Push-Button” Terminals

Most garage door/gate controllers have a pair of terminals marked like:

  • PUSH, START, KEY, PB, SW, etc.
  • The wall button is just a momentary switch shorting them

We will parallel the relay contacts across these two terminals.

Controller terminals: [PB1] [PB2]

Relay COM  → PB1
Relay NO   → PB2

When the relay is energized, COM–NO closes → same as pressing the button.

ESP32 → Relay module:

ESP32      Relay module
3.3V  → VCC
GND   → GND
GPIO26 → IN

If your relay board needs 5 V, power its coil from 5 V but still drive IN from 3.3 V (check it’s a low-level opto-isolated module that accepts 3.3 V).

3.2 Reed Switch for “Door Closed”

Use a magnetic contact like a door/window sensor:

  • Reed switch on the frame
  • Magnet on the door so they align when fully closed

Wire as a simple normally-open contact to ground:

ESP32 GPIO34 → one side of reed
ESP32 GND   → other side of reed

In firmware, we’ll use internal pull-up, so:

  • Door closed (magnet near) → contact closed → GPIO = LOW
  • Door open (magnet away) → contact open → GPIO = HIGH

You can invert logic if you prefer.


METHOD 1 – ESPHome Garage Door / Gate Controller

We’ll expose:

  • A switch for the relay pulse
  • A binary_sensor for door closed
  • A template cover for a nicer UI (Open/Close/Status)

4. ESPHome Base Config

esphome:
  name: esp32-garage-door
  platform: ESP32
  board: esp32dev

wifi:
  ssid: "YOUR_WIFI"
  password: "YOUR_PASSWORD"

logger:
api:
ota:

5. Relay as a Momentary Pulse (Button Emulation)

We don’t want the relay to stay on – just a quick pulse.

switch:
  - platform: gpio
    id: garage_relay
    pin: 26
    restore_mode: ALWAYS_OFF
    name: "Garage Door Pulse (Raw)"
    icon: mdi:gesture-tap
    on_turn_on:
      - delay: 500ms
      - switch.turn_off: garage_relay

You can hide this from HA later and use only the cover entity.


6. Reed Switch – Door Closed Feedback

binary_sensor:
  - platform: gpio
    id: garage_door_closed
    name: "Garage Door Closed"
    pin:
      number: 34
      mode:
        input: true
        pullup: true
    device_class: door
    filters:
      - invert: true
      # Now: closed contact (LOW) → "on" = closed

Explanation:

  • Reed closed (door closed) → pin LOW → inverted → ON (closed)
  • Reed open (door moving or open) → pin HIGH → inverted → OFF

You can tweak if you prefer the opposite.


7. Template Cover in ESPHome

Home Assistant understands covers (garage doors, gates) better than raw switches.

We create a cover that:

  • Uses the relay pulse for open/close
  • Uses the reed switch for closed detection
  • Reports open/closed/unknown accordingly
cover:
  - platform: template
    name: "Garage Door"
    id: garage_door

    device_class: garage

    # When HA asks to open:
    open_action:
      - if:
          condition:
            binary_sensor.is_on: garage_door_closed
          then:
            - switch.turn_on: garage_relay   # only pulse if currently closed

    # When HA asks to close:
    close_action:
      - if:
          condition:
            binary_sensor.is_off: garage_door_closed
          then:
            - switch.turn_on: garage_relay   # only pulse if currently open

    stop_action:
      - switch.turn_on: garage_relay         # many controllers use same button to stop

    # State logic:
    lambda: |-
      if (id(garage_door_closed).state) {
        return COVER_CLOSED;
      } else {
        // Could be open or moving; we assume OPEN if not closed
        return COVER_OPEN;
      }

You can extend this with timers to represent “opening” / “closing” states, but for most gates “open vs closed” is enough.


8. Home Assistant Dashboard

Once ESPHome is online, HA will see:

  • cover.garage_door
  • binary_sensor.garage_door_closed
  • switch.garage_door_pulse_raw (if you didn’t hide it)

8.1 Garage Cover Card

type: cover-entity
entity: cover.garage_door
name: Garage Door

or

type: entities
entities:
  - cover.garage_door
  - binary_sensor.garage_door_closed

The cover card shows Open / Close buttons and status.


9. Safety & Convenience Automations

9.1 Notification if Door Left Open

Example: notify if door is open for more than 15 minutes.

automation:
  - alias: "Garage Door Left Open Alert"
    trigger:
      - platform: state
        entity_id: binary_sensor.garage_door_closed
        to: "off"
        for: "00:15:00"
    action:
      - service: notify.mobile_app
        data:
          message: "Garage door has been open for 15 minutes!"
          title: "Garage Door Alert"

9.2 Auto-Close at Night (If Still Open)

  - alias: "Garage Door Auto-Close at 23:00"
    trigger:
      - platform: time
        at: "23:00:00"
    condition:
      - condition: state
        entity_id: binary_sensor.garage_door_closed
        state: "off"     # not closed
    action:
      - service: cover.close_cover
        target:
          entity_id: cover.garage_door

Optionally, require presence, or disable if guests are around.

9.3 Block Remote Closing If Obstacle Detected

If your controller exposes an obstacle sensor or photocell state to ESP32 (as another binary_sensor), you can:

condition:
  - condition: state
    entity_id: binary_sensor.garage_obstacle
    state: "off"

before sending the close command.


METHOD 2 – MQTT-Based Garage Controller

If you prefer raw MQTT instead of ESPHome:

  1. ESP32:
    • Relay controlled via MQTT command topic
    • Reed switch reported via MQTT state topic
  2. Home Assistant:
    • Defines:
      • mqtt.switch for relay
      • mqtt.binary_sensor for closed
      • A template cover using those

10. Home Assistant configuration.yaml (MQTT)

10.1 Relay Switch

mqtt:
  switch:
    - name: "Garage Door Relay"
      command_topic: "home/garage/relay/set"
      state_topic: "home/garage/relay/state"
      payload_on: "ON"
      payload_off: "OFF"
      retain: false

ESP32 should:

  • Treat "ON" as “pulse then OFF”
  • Publish the final state "OFF" to .../state

10.2 Reed Closed Sensor

mqtt:
  binary_sensor:
    - name: "Garage Door Closed"
      state_topic: "home/garage/closed"
      payload_on: "CLOSED"
      payload_off: "OPEN"
      device_class: door

ESP32:

  • Publishes "CLOSED" when reed is active, "OPEN" otherwise.

10.3 Template Cover Using Those

cover:
  - platform: template
    name: "Garage Door"
    device_class: garage
    open_cover:
      service: switch.turn_on
      target:
        entity_id: switch.garage_door_relay
    close_cover:
      service: switch.turn_on
      target:
        entity_id: switch.garage_door_relay
    stop_cover:
      service: switch.turn_on
      target:
        entity_id: switch.garage_door_relay
    value_template: >-
      {% if is_state('binary_sensor.garage_door_closed', 'on') %}
        closed
      {% else %}
        open
      {% endif %}

This mimics the ESPHome cover behaviour but with MQTT entities.

The same safety automations from section 9 apply.


11. Practical Tips

  • Use a manual lockout:
    Add an input_boolean.garage_remote_enabled and check it in automations so you can globally disable remote operation when working in the area.
  • Wi-Fi & reliability:
    If the node is far away (garage), make sure Wi-Fi is solid. Consider an external antenna or mesh node near the garage.
  • Local control preserved:
    The wall button remains connected and works exactly as before—your relay is just in parallel.
  • Testing:
    Test with no one under the door/gate. Verify:
    • A single pulse starts open / close
    • Another pulse stops it
    • Status updates correctly when closed.

Summary

With one ESP32, a relay and a reed switch you can turn a standard motorised garage door or gate into a proper Home Assistant cover:

  • Relay pulse emulates the physical button
  • Reed switch gives true closed/open feedback
  • ESPHome or MQTT provide the integration
  • Home Assistant adds dashboard control, notifications and safe automations

You keep the existing safety hardware and gain modern smart-home control on top.

Share your love

Newsletter Updates

Enter your email address below and subscribe to our newsletter

Leave a Reply

Your email address will not be published. Required fields are marked *