Overhead Garage Door Infrared Barrier controlled by ESP32 and ESPHome

I installed 2 infrared barriers on the garage opening. The low barrier detects cars / people that may happen to not block the overhead garage door’s sensor and the high barrier detects lift gate doors of SUVs that would otherwise be damaged if the overhead garage door was operated while open. The operation principle is simple… when the barriers are blocked, the default sensor is disconnected stopping the overhead garage door from closing (it will still open for obvious safety reasons, but will refuse to close or if closing, it will revert and open). In other words I have NOT made this any less secure.

While I was at it, I added a reed sensor (dry contact) to the door (screwed into the floor) so I can detect whether it is open or closed. This is way better than the zwave sensor on the door as it is faster and more reliable. I was getting really frustrated by the zwave sensor battery replacements, and the delays & failures in having lights and parking lasers turn on when the door was opening. Since I installed this, it is immediate and reliable.

I can tell the system to ignore the barriers but still report their state. I can turn power off to any one barrier and therefore ignore its state. I can manually disable the door sensor so that nobody can operate the door (we can open/close it from any spot on the globe so this opens up the chances that something is in the way as the default sensor is low to the ground (mostly meant not to squish kids… by law). And of course I can just turn it all off.

All connections are done using the COM/NC connections on the relay. Why? The power ones would be on 100% of the time… so I inverted their operation. When at rest the barriers are powered. If for any reason I want to turn one off, the relay will turn on. Saves power and the relay this way.

The relay that disables the door sensor is connected the same way… for the same reason and also to allow unplugging the system in case things were to stop working.

The ethernet wiring is not ethernet… I assigned a function/purpose to each one of the 8 wires so to make all the connections all I needed to do was wire everything in parallel. It therefore does not matter how the barriers are connected to the white splitters on the wall or to the control box.








This is the YAML I wrote… some of the stuff that looks ‘weird’ is me trying to restore settings when stuff reboots. It works, but I am not certain there wasn’t a simpler way of doing it. I have not tested some changes I made last night so there may be bugs… will update YAML if I find any.

substitutions:
  devicename: overhead-garage-door-barrier
  devicename_no_dashes: overhead_garage_door_barrier
  friendly_devicename: "Overhead Garage Door Barrier"
  device_description: "Overhead Garage Door Barrier"
  update_interval_s: "60s"
  #Only reason not to set it much longer is for wifi troubleshooting ease
  update_interval_wifi: "120s"

esphome:
  name: ${devicename}
  comment: ${device_description}
  # Automatically add the mac address to the name
  # so you can use a single firmware for all devices
  # name_add_mac_suffix: true
  on_boot:
    then:
      - lambda: |-
          id(infrared_barrier_ha).publish_state(id(infrared_barrier));
          id(disable_door_closure_ha).publish_state(id(disable_door_closure));

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename}"
    password: !secret iot_wifi_password

#Faster than DHCP. Also use if can't reach because of name change
  manual_ip:
    static_ip: 192.168.3.207
    gateway: 192.168.3.1
    subnet: 255.255.255.0
    dns1: 192.168.1.25
    dns2: 192.168.1.26

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
  use_address: 192.168.3.207
  
logger:
    baud_rate: 0 #disabled

api:
  
ota:
  
web_server:
  port: 80
  include_internal: true

# Sync time with Home Assistant
time:
  - platform: homeassistant
    id: ha_time

globals:
  - id: active_barrier
    type: int
    restore_value: no
    initial_value: '3' # 0 Disabled; 1 Low Barrier; 2 High Barrier; 3 Both Barriers
  - id: infrared_barrier
    type: bool
    restore_value: yes
    initial_value: 'true' # When false, the system has no effect on the door operation
  - id: disable_door_closure
    type: bool
    restore_value: yes
    initial_value: 'false' # When false, the the door sensor relay (#1) is on and disables the sensor

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
      update_interval: ${update_interval_wifi}
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true
  
sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: ${update_interval_wifi}
    device_class: signal_strength

binary_sensor:
  - platform: gpio
    pin:
      number: 23
      inverted: true
      mode:
        input: true
        pullup: true
    name: "${friendly_devicename}: High Barrier"
    id: high_barrier
    device_class: motion
    on_press:
      then:
        - lambda: |-
            if (id(infrared_barrier)) {
              id(delayed_barrier_blocked).execute();
            }
    on_release:
      then:
        - lambda: |-
            if (id(infrared_barrier)) {
                if (id(delayed_barrier_blocked).is_running()) {
                    ESP_LOGI("main", "Stopping disable door relay trigger timer!");
                    id(delayed_barrier_blocked).stop();
                }
                id(barrier_clear).execute();
            }

  #Just shows whether door sensor is disabled or not. "On" = disabled.
  - platform: template
    name: "${friendly_devicename}: Door Sensor Override"
    id: door_sensor_override
    lambda: !lambda |-
      if (id(relay1).state) {
        return true;
      } else {
        return false;
      }

  - platform: gpio
    pin:
      number: 27
      inverted: true
      mode:
        input: true
        pullup: true
    name: "${friendly_devicename}: Low Barrier"
    id: low_barrier
    device_class: motion
    on_press:
      then:
        - lambda: |-
            if (id(infrared_barrier)) {
              id(delayed_barrier_blocked).execute();
            }
    on_release:
      then:
        - lambda: |-
            if (id(infrared_barrier)) {
                if (id(delayed_barrier_blocked).is_running()) {
                    ESP_LOGI("main", "Stopping disable door relay trigger timer!");
                    id(delayed_barrier_blocked).stop();
                }
                id(barrier_clear).execute();
            }

  - platform: gpio
    pin:
      number: 22
      mode:
        input: true
        pullup: true
    name: "${friendly_devicename}: Door Contact"
    id: door_contact
    device_class: garage_door
    on_press:
      then:
        - output.turn_on: onboard_led
    on_release:
      then:
        - output.turn_off: onboard_led 

  - platform: gpio
    pin:
      number: 26
      inverted: true
      mode:
        input: true
        pullup: true
    name: "${friendly_devicename}: Spare Contact"
    id: spare_contact
    on_press:
      then:
        - output.turn_on: onboard_led
    on_release:
      then:
        - output.turn_off: onboard_led 
switch:
  - platform: restart
    name: "${friendly_devicename}: Restart"

  # COM-NC closes safety sensor circuit so GD Opener works normally. To disable door closing, relay is turned on thus breaking circuit.
  # GD Opener will just see it as a bad sensor.
  - platform: gpio
    pin:
      number: 21
    id: relay1
    internal: true #Do not allow direct control of relay1

  - platform: template
    name: "${friendly_devicename}: Disable Door Closure"
    id: disable_door_closure_ha
    optimistic: true
    turn_on_action:
      - globals.set:
          id: disable_door_closure
          value: 'true'
      - script.execute: disable_sensor_on
    turn_off_action:
      - globals.set:
          id: disable_door_closure
          value: 'false'
      - script.execute: disable_sensor_off


  # Sensors are wired through COM-NC of relay. To disable door, turn relay on so door sensors are disconnected disabling the door.
  - platform: gpio
    pin:
      number: 19
      inverted: true # To save relay and energy, turning off barrier will turn on relay. Inverting to make it more intuitive.
    name: "${friendly_devicename}: High Barrier Power"
    id: relay2
    restore_mode: RESTORE_DEFAULT_ON
    on_turn_on:
      then:
        - script.execute: set_active_barrier
    on_turn_off:
      then:
        - script.execute: set_active_barrier
 
  # Sensors are wired through COM-NC of relay. To disable door, turn relay on so door sensors are disconnected disabling the door.
  - platform: gpio
    pin:
      number: 18
      inverted: true # To save relay and energy, turning off barrier will turn on relay. Inverting to make it more intuitive.
    name: "${friendly_devicename}: Low Barrier Power"
    id: relay3
    restore_mode: RESTORE_DEFAULT_ON
    on_turn_on:
      then:
        - script.execute: set_active_barrier
    on_turn_off:
      then:
        - script.execute: set_active_barrier

  - platform: gpio
    pin:
      number: 5
    name: "${friendly_devicename}: Spare Relay"
    id: relay4

  - platform: template
    id: infrared_barrier_ha
    name: "${friendly_devicename}"
    optimistic: true
    turn_on_action:
      - globals.set:
          id: infrared_barrier
          value: 'true'
    turn_off_action:
      - globals.set:
          id: infrared_barrier
          value: 'false'

button:
  - platform: safe_mode
    name: "${friendly_devicename}: Restart (Safe Mode)"

#deep_sleep:
#  id: ${devicename_no_dashes}_deep_sleep
#  sleep_duration: 30min
  
#status_led:
#  pin:
#    number: 25
#    inverted: false

output:
  - id: onboard_led
    platform: gpio
    pin: 25
    inverted: true

script:
  # 0 Disabled; 1 Low Barrier (relay3); 2 High Barrier (relay2); 3 Both Barriers
  - id: set_active_barrier
    then:
      - lambda: |-
          if(id(relay2).state && id(relay3).state) {
            id(active_barrier) = 3;
          }
          if(id(relay2).state && !id(relay3).state) {
            id(active_barrier) = 2;
          }
          if(!id(relay2).state && id(relay3).state) {
            id(active_barrier) = 1;
          }
          if(!id(relay2).state && !id(relay3).state) {
            id(active_barrier) = 0;
          }
          ESP_LOGD("DEBUG", "Active Barrier: %d", int(id(active_barrier)));
  
  # Disabled barriers can't be triggered as they have no power so state can be ignored
  - id: barrier_blocked
    then:
      # 0 Disabled; 1 Low Barrier (relay3); 2 High Barrier (relay2); 3 Both Barriers
      - lambda: |-
          if(id(active_barrier) == 3) {
            if(id(low_barrier).state || id(high_barrier).state) {
              id(relay1).turn_on();
              id(onboard_led).turn_on();
            }
          };
          if(id(active_barrier) == 2) {
            if(id(high_barrier).state) {
              id(relay1).turn_on();
              id(onboard_led).turn_on();
            }
          };
          if(id(active_barrier) == 1) {
            if(id(low_barrier).state) {
              id(relay1).turn_on();
              id(onboard_led).turn_on();
            }
          };
          if(id(active_barrier) == 0) {
            ESP_LOGD("DEBUG", "Impossible condition as both barriers are disabled.");
          };

  - id: barrier_clear
    then:
      # 0 Disabled; 1 Low Barrier (relay3); 2 High Barrier (relay2); 3 Both Barriers
      - lambda: |-
          if(id(active_barrier) == 3) {
            if(!id(low_barrier).state && !id(high_barrier).state) {
              id(relay1).turn_off();
              id(onboard_led).turn_off();
            }
          };
          if(id(active_barrier) == 2) {
            if(!id(high_barrier).state) {
              id(relay1).turn_off();
              id(onboard_led).turn_off();
            }
          };
          if(id(active_barrier) == 1) {
            if(!id(low_barrier).state) {
              id(relay1).turn_off();
              id(onboard_led).turn_off();
            }
          };
          if(id(active_barrier) == 0) {
            ESP_LOGD("DEBUG", "Impossible condition as both barriers are disabled.");
          };

  - id: delayed_barrier_blocked
    then:
      - delay: 2000ms
      - script.execute: barrier_blocked

  - id: disable_sensor_on
    then:
      - lambda: |-
          id(relay1).turn_on();

  - id: disable_sensor_off
    then:
      - lambda: |-
          id(relay1).turn_off();

1 Like