I’ve been using the MQTT Garage Presence project with an Arduino to Open/Close my garage door when the car enters/exits my WiFi range. Since installing ESPHome, I decided to try using that instead. It was a lot simpler to implement. I’m not sure if it is as fast as the MQTT version, but I’ll test that later.
Assumptions:
- You have a MyQ type garage door, that detects at least the following states {open, closed, opening}.
- Your car(s) have a USB port whose power is in sync with the ignition’s power state.
- You have a motion sensor in your garage.
- You have ESPHome working with Home Assistant
- You are using an Arduino type microcontroller (with WiFi) as your car’s ESPHome “presence” device.
- You have two cars, for one garage door. This will work for single or multi-car garages, and it should be obvious how to modify the automations accordingly. So this is written for the “more difficult” case.
- You have MQTT installed
The objective of this process is for Home Assistant to open/close your garage automatically and work in conjunction with manual control of your garage doors. In other words, you can still operate your garage door with an RF remote, or physically attached button without the HA Automations fighting you. There are two uses cases to consider: leaving and arriving home. When leaving home, we would like for the garage to close as soon as possible. When arriving home, that may not be what you want. For example, you might want to leave the garage open for a few minutes in order to get things out of the car.
The ESPHome sensor works by it sending an incrementing number every second when the sensor connects to your WiFi. When that sensor becomes “unavailable” in Home Assistant, that means either the sensor lost power (the car was turned off) or the car drove away and lost connection to your WiFi.
The motion sensor that is placed inside your garage is used to determine if the ESPHome sensor (and thus your car) is “coming or going”. If the motion sensor is triggered before your garage is opened, then HA knows the car is leaving. If it is triggered after (or not at all) we know the sensor came into range and thus the car is returning. Make sure you place the motion sensor in your garage such that it will capture motion from the driver entering the car, and not from motion outside of the garage. You should also make sure you motion sensor has a timeout of at least a few minutes. Remember, the sensor has to be triggered before the garage is opened in order to set the event as “leaving”
In order to keep track of this dance, you will need to declare the following input_text
sensors. You can hard-code the long-delay
in your secrets.yaml
file if you wish. This delay is how many seconds the garage should stay open after your car “comes home”. When the automations determine whether you are arriving or leaving, esphome_garage_delay
is used to store how long HA will wait before shutting a door when it loses connection to the sensor.
long_delay:
initial: !secret garage_long_delay
esphome_garage_delay:
initial: '0'
You will also need to declare the following timer
. This will use the value stored in esphome_garage_delay
.
car_close_garage:
duration: '00:00:01'
You will also need several input_booleans
. Add or remove them for each car needed. esp_car_debug
isn’t strictly needed, but it’s useful to switch it to on
if you don’t want your garage opening/closing for some reason, such as debugging or updating your ESPhome sensor.
esphome_sensor_detected:
initial: off
esp_car_debug:
initial: off
bmw_opened_garage:
initial: off
hyundai_opened_garage:
initial: off
Create the following automations:
- alias: Vehicle Sensor in Range - turn on boolean
trigger:
platform: numeric_state
entity_id: sensor.bmw, sensor.hyundai
above: 0
action:
- service: input_boolean.turn_on
data_template:
entity_id: "input_boolean.{{trigger.to_state.attributes.esp_home_input_boolean}}"
- service: input_boolean.turn_on
entity_id: input_boolean.esphome_sensor_detected
- alias: Vehicle Sensor Unavailable - start timer to close garage
trigger:
platform: state
entity_id: sensor.bmw, sensor.hyundai
to: 'unavailable'
condition:
- condition: state
entity_id: input_boolean.esphome_sensor_detected
state: 'on'
- condition: template
value_template: '{{ states.sensor.hyundai | int == 0 }}'
- condition: template
value_template: '{{ states.sensor.bmw | int == 0 }}'
action:
- service: input_boolean.turn_off
entity_id: input_boolean.esphome_sensor_detected
- service: script.turn_on
entity_id: script.start_car_close_garage_timer
- alias: Vehicle Sensor Unavailable - cleanup
trigger:
platform: state
entity_id: sensor.bmw, sensor.hyundai
to: 'unavailable'
action:
- service: input_boolean.turn_off
data_template:
entity_id: "input_boolean.{{trigger.to_state.attributes.esp_home_input_boolean}}"
- alias: sensor_boolean_turned_on-open_garage
trigger:
platform: state
entity_id: input_boolean.esphome_sensor_detected
to: 'on'
condition:
- condition: state
entity_id: cover.garage_door_opener
state: 'closed'
action:
- service: script.turn_on
entity_id: script.car_open_garage
- alias: Garage Opened and sensors detected - quick close
trigger:
platform: state
entity_id: cover.garage_door_opener
to: 'opening'
action:
- service: input_text.set_value
data_template:
entity_id: input_text.esphome_garage_delay
value: >-
{% set long_delay = states('input_text.long_delay') %}
{% set motion_detected = states('binary_sensor.1st_garage_motion_sensor') %}
{% set vehicle_detected = states('input_boolean.esphome_sensor_detected') %}
{% if (motion_detected == 'on' or vehicle_detected == 'off') %}
{{1}}
{% else %}
{{long_delay}}
{% endif %}
- alias: Vehicle Timer Elapsed - Close Garage
trigger:
- platform: event
event_type: timer.finished
event_data:
entity_id: timer.car_close_garage
action:
- service: script.turn_on
entity_id: script.car_close_garage
- alias: garage debug
trigger:
platform: state
entity_id: cover.garage_door_opener
condition:
- condition: state
entity_id: input_text.esp_car_debug
state: 'on'
action:
- service: persistent_notification.create
data_template:
title: 'garage debug'
message: '{{trigger.to_state}}'
- alias: Garage_Closed-Cancel_Vehicle_Timer-Set_default_close_time
trigger:
platform: state
entity_id: cover.garage_door_opener
to: 'closed'
action:
- service: timer.cancel
entity_id: timer.car_close_garage
- service: input_boolean.turn_off
entity_id: input_boolean.esphome_sensor_detected
And create the following scripts
:
start_car_close_garage_timer:
sequence:
- service: timer.cancel
entity_id: timer.car_close_garage
- service: timer.start
data_template:
entity_id: timer.car_close_garage
duration: >
{{ states.input_text.esphome_garage_delay.state }}
car_open_garage:
sequence:
- condition: state
entity_id: input_boolean.esp_car_debug
state: 'off'
- service: timer.cancel
entity_id: timer.car_close_garage
- service: cover.open_cover
entity_id: cover.garage_door_opener
car_close_garage:
sequence:
- condition: state
entity_id: input_boolean.esp_car_debug
state: 'off'
- service: timer.cancel
entity_id: timer.car_close_garage
- service: cover.close_cover
entity_id: cover.garage_door_opener
Here is the YAML for the ESPHome sensor. Obviously you will need to modify this for each car you use. You should also assign a static IP as it is faster. This too needs to be unique.
esphome:
name: car_bmw
platform: ESP8266
board: d1_mini
wifi:
ssid: "MysSID"
password: "0123456789"
fast_connect: true
use_address: 172.16.68.121
reboot_timeout: 0s
manual_ip:
static_ip: 172.16.68.121
gateway: 172.16.68.1
subnet: 255.255.255.0
mqtt:
discovery: true
discovery_prefix: homeassistant
client_id: home-assistant-esphome-bmw
broker: 172.16.68.62
username: mqttuser
password: mqttpassword
reboot_timeout: 0s
keepalive: 2s
logger:
ota:
password: 'password'
binary_sensor:
- platform: status
name: "Garage Opened BMW"
id: garage_opened_bmw
sensor:
- platform: template
name: "BMW"
lambda: |-
static int num_cycles = 0;
static int prev_count = 0;
int retVal;
num_cycles += 1;
bool ha_responded = (id(garage_opened_bmw).state); // Home Assistant set binary_sensor ON
if (ha_responded) {
retVal = prev_count;
}
else
{
retVal = num_cycles;
prev_count = num_cycles;
}
return retVal;
update_interval: 1000ms