# Garage door controller

> ESP32 + ESPHome controlling two garage doors with relay pulses, reed switches reporting true state, and a Bluetooth proxy thrown in for free. Talks directly to Home Assistant.

- Source: https://nabkey.com/tinkerings/garage-doors
- Author: Chris Nabkey
- Published: 2026-04-25
- Built: 2025
- Status: shipped
- Stack: ESP32, ESPHome, Home Assistant, Reed switches, LilyGO TTGO T-Relay

A small board, about the size of a deck of cards, that opens and closes two
garage doors and reports whether each one is up. It runs ESPHome
and talks to Home Assistant over the native API.

> **LilyGO TTGO T-Relay**: ESP32, four onboard relays, 4 MB flash, WiFi
> and Bluetooth, $15 from AliExpress.

## Architecture

<figure class="diagram">
  *Diagram: Garage door controller architecture: Home Assistant talks to ESPHome on a LilyGO TTGO T-Relay over native API; the ESP32 drives two relays (out) and reads two reed switches (in), one per garage door.* ([view it on nabkey.com](https://nabkey.com/tinkerings/garage-doors))
  <figcaption>Two relays out, two reed switches in. Home Assistant sees one cover entity per door whose state reflects the actual door, not what was last commanded.</figcaption>
</figure>

## Reed switches

Without state detection, a controller only knows the last command it sent.
Reed switches screwed to the doors and into the garage floor give the ESP32 the
real position, so Home Assistant shows whether each door is actually open right
now.

## ESPHome

Each door pairs a relay (output, presses the opener button) with a reed switch
(input, reads the position). A template `cover:` joins them into one entity
per door:

```yaml
cover:
  - platform: template
    name: "Left Garage Door"
    device_class: garage
    lambda: 'return id(garage_door_sensor_left).state ? COVER_OPEN : COVER_CLOSED;'
    open_action:  { script.execute: pulse_relay_left }
    close_action: { script.execute: pulse_relay_left }
    stop_action:  { script.execute: pulse_relay_left }
```

Open, close and stop all run the same script, because a real opener has one
button and it toggles. The script turns the relay on for 500 ms:

```yaml
script:
  - id: pulse_relay_left
    then:
      - switch.turn_on: garage_door_relay_left
      - delay: 500ms
      - switch.turn_off: garage_door_relay_left
```

Each reed switch has a 20 ms `delayed_on_off` filter. As a door starts moving
the magnet doesn't pass the sensor cleanly, and without the filter the state
would flicker for a fraction of a second.

## Bluetooth proxy

The board sits in a small cabinet in the garage, wired up with reused CAT5
scraps, and it's in range of two BLE temperature sensors. ESPHome's built-in
**Bluetooth proxy** lets any ESP32 forward BLE advertisements to Home
Assistant, so the same board extends BLE coverage into a corner of the house
that had none before:

```yaml
esp32_ble_tracker:
  scan_parameters:
    active: true

bluetooth_proxy:
  active: true
```

It's pretty cool how cheap and easy it is to hook a microcontroller up to
things these days.
