My Garage Door: I have cover states Open, Opening, Closing and Closed using a D1 Mini and two reed switches plus a WS2812 LED strip indicating parking position

I have two contact sensors - one for when door is open and another for when door is closed.

esphome:
  name: garage-distance-sensor
  friendly_name: Garage Distance Sensor

esp8266:
  board: d1_mini
binary_sensor:
  - platform: gpio
    pin:
      number: D3
      mode: INPUT_PULLUP
    name: "Garage Door Open Contact Sensor"
    id: contact_sensor_open
    internal: true

  - platform: gpio
    pin:
      number: D4
      mode: INPUT_PULLUP
    name: "Garage Door Closed Contact Sensor"
    id: contact_sensor_closed
    internal: true
    lambda: |-
      if (id(contact_sensor_open).state) {
        return COVER_OPEN;
      } else if (id(contact_sensor_closed).state) {
        return COVER_CLOSED;
      } else {
        return COVER_OPERATION_OPENING;
      }

What I would like is:

  • If contact_sensor_open is ‘on’ return COVER_OPEN
  • If contact_sensor_closed is ‘on’ return COVER_CLOSED
  • If contact_sensor_open is ‘off’ and is more recent than contact_sensor_close being ‘off’ then return COVER_OPERATION_CLOSING
  • If contact_sensor_closed is ‘off’ and is more recent than contact_sensor_open being ‘off’ then return COVER_OPERATION_OPENING

I hope I made my puzzle clear! :wink:

The only data that is saved from history is accessed in this manner (you can test these in the developer template section to see if they return a valid value):

{{ states.sensor['contact_sensor_open'].last_changed }}
{{ states.sensor['contact_sensor_closed'].last_changed }}

If the above two do not work then change the number of input_datetime helpers explained as being needed below, from 2 to 4 (one for each the last time it changed to on).

The above syntax is not enough information. You would need to create at least two input_datetime helpers and write automations to populate the datetime values for each of them. One automation would be populated with the current datetime when contact_sensor_open is changed to off, and one to populate contact_sensor_closed with the current datetime when it is changed to off. If they are called:

input_datetime.contact_sensor_open_last_off
input_datetime.contact_sensor_closed_last_off

then your configuration.yaml template sensor could look like this:

sensor:
  - platform: template
    sensors:
     my_clear_puzzle:
        friendly_name: I have made your puzzle clear
        value_template: >-
          {% if is_state('contact_sensor_open','on') %}
            {{  "COVER_OPEN" }}
          {% endif %}
          {% if is_state('contact_sensor_closed','on') %}
            {{ "COVER_CLOSED" }}
          {% endif %}
          {% if is_state('contact_sensor_open','off') and float(states.sensor['contact_sensor_open'].last_changed) > float(input_datetime.contact_sensor_closed_last_off) %}
            {{ "COVER_OPERATION_CLOSING" }}
          {% endif %}
          {% if is_state('contact_sensor_closed','off') and float(states.sensor['contact_sensor_closed'].last_changed) > float(input_sensor.contact_sensor_open_last_off) %}
            {{ "COVER_OPERATION_OPENING" }}
          {% endif %}
     unique_id: my_clear_puzzle
1 Like

Thanks for your input. I’ve hobbled this template together and am working towards implementing a cover template that follows this template logic.

{% if states('binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor') == "on" or states('binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor') == "on" %}
  {{ "IDLE" }}
{% elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated > states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated %}
  {{ "COVER_OPERATION_OPENING" }}
{% else %}
  {{ "COVER_OPERATION_CLOSING" }}
{% endif %}

So far, the above code is working in Dev Tools!

Progress:

value_template: {% if states('binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor') == "on" -%}
 open
{%- elif states('binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor') == "on" -%}
 closed 
{%- elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated > states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
  opening
{%- elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated < states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
  closing
{%- endif %}

image

image

image

image

It would have been nice to accomplish this within ESPHome without having to resort to a templated cover in Home Assistant but no-go!

Here’s my solution:

cover:
  - platform: template
    covers:
      my_garage_door:
        device_class: garage
        friendly_name: "My Garage Door"
        value_template: >-
         {%- if states('binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor') == "on" -%}
            open
         {%- elif states('binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor') == "on" -%}
            closed 
         {% elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated > states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
            opening
         {%- elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated < states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
            closing
         {%- endif -%}
        icon_template: >-
          {%- if states('binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor') == "on" -%}
            kuf:fts_garage
          {%- elif states('binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor') == "on" -%}
            kuf:fts_garage_door_100
          {%- elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated > states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
            kuf:fts_garage_door_70
          {%- elif states.binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor.last_updated < states.binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor.last_updated -%}
            kuf:fts_garage_door_30
          {%- endif -%}
        open_cover:
          - condition: state
            entity_id: binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor
            state: "on"
          - service: switch.turn_on
            target:
              entity_id: switch.garage_distance_sensor_garage_door_relay
        close_cover:
          - condition: state
            entity_id: binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor
            state: "on"
          - service: switch.turn_on
            target:
              entity_id: switch.garage_distance_sensor_garage_door_relay
        stop_cover:
          - conditions:
            - condition: state
              entity_id: binary_sensor.garage_distance_sensor_garage_door_open_contact_sensor
              state: "off"
            - condition: state
              entity_id: binary_sensor.garage_distance_sensor_garage_door_closed_contact_sensor
              state: "off"
          - service: switch.turn_on
            target:
              entity_id: switch.garage_distance_sensor_garage_door_relay

image

image

image

image

ESPHome sensor config:

esphome:
  name: garage-distance-sensor
  friendly_name: Garage Distance Sensor

esp8266:
  board: d1_mini
#  framework:
#    version: 2.7.4

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "<redacted>"

ota:
  password: "<redacted>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Distance-Sensor Fallback Hotspot"
    password: "<redacted>"

captive_portal:

light:
  - platform: neopixelbus
    variant: WS2812
    pin: D5
    num_leds: 20
    type: rgb
    name: "Parking Light Indicator"
    id: parking_light_indicator
    restore_mode: ALWAYS_OFF

sensor:
  - platform: ultrasonic
    name: 'parking_sensor'
    id: parking_sensor
    trigger_pin: D1
    echo_pin: D2
    unit_of_measurement: "m"
    accuracy_decimals: 1
    update_interval: 1s
    timeout: 4.0m
    pulse_time: 10us
    filters:
      - or:
          - throttle: 300s
          - delta: 5%
    on_value:
      then: 
        - if: 
            # show yellow if getting close to correct spot 
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.50
                    above: 1.20
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 100%
                # yellow
                red: 100%
                green: 100%
                blue: 0%
        - if: 
            # Show Green when in right spot
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.20
                    above: 1.15
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 80%
                red: 0%
                green: 100%
                blue: 0%
        - if: 
            # Show Red when too far into garage
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.10
                    above: 0.00
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
        - if: 
            # turn light off when door closed 
            condition:
              - binary_sensor.is_on:
                  id: contact_sensor_closed
            then:
              light.turn_off:
                id: parking_light_indicator

#binary sensors to check if door open or closed
binary_sensor:
  - platform: gpio
    pin:
      number: D5
      mode: INPUT_PULLUP
      inverted: True
    name: "Garage Door Open Contact Sensor"
    id: contact_sensor_open
    internal: False

  - platform: gpio
    pin:
      number: D6
      mode: INPUT_PULLUP
      inverted: True
    name: "Garage Door Closed Contact Sensor"
    id: contact_sensor_closed
    internal: False

# Relay Switch to send the  the pulse to
# the garage door opener (not exposed to HA)
switch:
  - platform: gpio
    pin: D7
    name: "Garage Door Relay"
    id: relay
    internal: true

minus the following since it’s useless…

# This creates the actual garage door in HA. The state is based
# on the two contact sensors. open & close actions both simply
# toggle the relay closed/open with a 0.5s pulse simulating a manual button press.
cover:
  - platform: template
    device_class: garage
    name: "New Garage Door"
    id: template_cov
    lambda: |-
      if (id(contact_sensor_closed).state) {
        return COVER_CLOSED;
      } else if (id(contact_sensor_open).state) {
        return COVER_OPERATION_CLOSING;
      } else {
        return COVER_OPERATION_OPENING;
      }
    open_action:
      - switch.turn_on: relay
      - delay: 0.5s
      - switch.turn_off: relay
    close_action:
      - switch.turn_on: relay
      - delay: 0.5s
      - switch.turn_off: relay

I managed to figure out how to do what I wanted in ESPHome and not have to use an HA templated cover!

esphome:
  name: my_garage_door
  friendly_name: My Garage Door

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "<redacted>"

ota:
  password: "<redacted"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Distance-Sensor Fallback Hotspot"
    password: "<redacted"

captive_portal:

light:
  - platform: neopixelbus
    variant: WS2812
    pin: D5
    num_leds: 20
    type: rgb
    name: "Parking Light Indicator"
    id: parking_light_indicator
    restore_mode: ALWAYS_OFF

sensor:
  - platform: ultrasonic
    name: 'parking_sensor'
    id: parking_sensor
    trigger_pin: D1
    echo_pin: D2
    unit_of_measurement: "m"
    accuracy_decimals: 1
    update_interval: 1s
    timeout: 4.0m
    pulse_time: 10us
    filters:
      - or:
          - throttle: 300s
          - delta: 5%
    on_value:
      then: 
        - if: 
            # show yellow if getting close to correct spot 
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.50
                    above: 1.20
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 100%
                # yellow
                red: 100%
                green: 100%
                blue: 0%
        - if: 
            # Show Green when in right spot
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.20
                    above: 1.15
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 80%
                red: 0%
                green: 100%
                blue: 0%
        - if: 
            # Show Red when too far into garage
            condition:
              and:
                - sensor.in_range:
                    id: parking_sensor
                    below: 1.10
                    above: 0.00
                - binary_sensor.is_on:
                    id: contact_sensor_open
            then:
              light.turn_on:
                id: parking_light_indicator
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
        - if: 
            # turn light off when door closed 
            condition:
              - binary_sensor.is_on:
                  id: contact_sensor_closed
            then:
              light.turn_off:
                id: parking_light_indicator

# Binary sensors to check if door is open or closed
binary_sensor:
  - platform: gpio
    pin:
      number: D5
      mode: INPUT_PULLUP
      inverted: True
    name: "Garage Door Open Contact Sensor"
    id: contact_sensor_open
    internal: False
    on_press:
      - cover.template.publish:
          id: template_cov
          state: OPEN
          current_operation: IDLE
    on_release:
      - cover.template.publish:
          id: template_cov
          current_operation: CLOSING

  - platform: gpio
    pin:
      number: D6
      mode: INPUT_PULLUP
      inverted: True
    name: "Garage Door Closed Contact Sensor"
    id: contact_sensor_closed
    internal: False
    on_press:
      - cover.template.publish:
          id: template_cov
          state: CLOSED
          current_operation: IDLE
    on_release:
      - cover.template.publish:
          id: template_cov
          current_operation: OPENING

# Relay Switch to send the pulse to the garage door opener button
switch:
  - platform: gpio
    pin: D7
    name: "Garage Door Relay"
    id: relay
    internal: False
    # Toggle the relay with a 0.5s pulse simulating a manual button press
    on_turn_on:
    - delay : 500ms
    - switch.turn_off: relay
    ##
    ## To-do as this is how my door operates:
    ## If I press stop while closing it reverses; If I press stop while opening it just stops!
    ##
    ## If id(contact_sensor_closed).state) == off AND id(contact_sensor_open).state) == off AND template_cov.current_operation is CLOSING
    ## Then 
    ## cover.template.publish:
    ##   id:template_cov
    ##   current_operation: OPENING
    ##   state: CLOSED
    ##
    ## If id(contact_sensor_closed).state) == off AND id(contact_sensor_open).state) == off AND template_cov.current_operation is OPENING
    ## Then 
    ## cover.template.publish:
    ##   id:template_cov
    ##   current_operation: IDLE
    ##   state: OPEN

# This creates the actual garage door in HA. The state is based on two contact sensors. 
cover:
  - platform: template
    device_class: garage
    name: "New Garage Door"
    id: template_cov

    ## To-do: Can I disable this action if current operation is OPENING?
    open_action:
      - switch.turn_on: relay

    ## To-do: Can I disable this action if current_operation is CLOSING?
    close_action:
      - switch.turn_on: relay

    ## To-do: Can I disable this action if state is OPEN or CLOSED?
    stop_action:
      - switch.turn_on: relay

4 Likes

I didn’t realize the sensor was binary, and thought you would need to know when the sensor was last off, even when it was on, in which case “last_changed” only refers to when it was last “on” rather than “off” - but evidently that is not the case. Happy to be of help!

last_changed updates whether it changed from on to off or off to on, but I only needed to care about whether last_changed to off for open is newer than any change for closed, and vice-versa. Since I know only one switch changes to on or off at a time, the template “if HA last_changed (to off) for switch x is newer than the last_changed for switch y (which would have been to off)” and therefore did not need input_numbers to remember last_changed on-off or last_changed off-on, unless of course an HA restart occurs, which could have been an issue! The ESPHome coding I figured out though negates all of what I just said! Thanks for your input, it helped get me started!

Hey quick question… I’m new to all of this, and your solution looks like it will solve 95% of what I want to do. I’m curious if you have any insight into the last 5%.

I have a driveway gate, the controller works just like a simple garage door opener. So I have two sensors just like you have, one to indicate the gate is “fully closed” and one to indicate the gate is “fully open.” I like the logic you have to show opening and closing. I’m curious if there’s any way to indicate that there is a problem.

For example, if something is blocking the gate it has a pressure sensor, it will stop and/or kick back slightly if it hits something. My thought is… if it normally takes ~15 seconds for the gate to open or close, is it possible in HA to check the state and if it’s in Opening or Closing for more than ~20 seconds then to assume that something blocked the gate and then set a status of “error” or “blocked” or “stopped” or something like that?

Very much appreciate you sharing your work. Last question. Is there a reason you preferred the cover to be in ESPHome and not in Home Assistant?

Thanks

Um, the cover is in HA. My first solution was a templated cover based on the inputs from the D1 Mini in ESPHome. The second is a cover entity in HA that the D1 Mini in ESPHome creates directly without using a templated cover. Without using an ESPHome device you could use the HA templated cover based on binary inputs from wherever. As for the issue of being stalled/blocked, an automation triggered by the state being opening or closing for 20 seconds then do whatever. I could see using HACS Python Scripting and this set_state.py script to change the state of the cover to Error when that happens, &/or anything else you want. When the entity later updates/changes its state, Error would be replaced by the new state.

Awesome, thanks. The order of the posts made it look like your final solution was entirely in ESPHome. But thank you for the quick reply and advice.

I dont understand why you dont just use the esphome Feedback cover. If you have an open and closed binary sensor, it keeps track of the open,opening,closed,closing states very well and no need for making templates to do this. It also does a pretty good job keeping track of the actual position or % open/closed.

If you truly want to track the door states that covers the wall switch, car remotes and the obstacle sensor then you would need to use a current sensor or rotary encoder on the motor/drive train or above the door where the bar is with the spings. Short of that, your cover cant tell when the door is triggered by the obstacle sensor or a car remote untill it physical end_stop sensor is reached and then the cover updates.

The reason to put it in esphome is because esphome works independently of HA. If for whatever reason there is a breakdown with HA or the connection to HA your cover will still work. If your cover is in HA and theres a problem, lets say your out of town and a power outage causes your door to open. Having the esp be the cover and source of automations you can have it be aware of its state and close automatically. If it were in HA and the 2 cant communicate, well your door is going to be open untill HA is working again or you come home and close it.

Its always a good idea to put as much of the logic and automations on the esp directly. Splitting it up adds points of failure.

1 Like

Because I don’t know ESPHome very well! That now gives me something to do today!!

Its pretty well documented just like HA.

Thanks, I found and tested all the options and settled on cover: - platform: feedback. I created two new movement sensors which are the inverse of the state sensors:

  - platform: template
    name: "Open Movement Sensor"
    id: open_movement_sensor
    lambda:
      if (id(closed_contact_sensor).state) {
        return false;
      } else {
        return true;
      }
    
  - platform: template
    name: "Close Movement Sensor"
    id: close_movement_sensor
    lambda:
      if (id(open_contact_sensor).state) {
        return false;
      } else {
        return true;
      }

and the cover is now:

cover:
  - platform: feedback
    name: "Garage Door"
    id: endstop_cover
    device_class: garage

    open_action:
      - switch.turn_on: relay
    open_duration: 10s
    open_endstop: open_contact_sensor
    open_sensor: open_movement_sensor

    close_action:
      - switch.turn_on: relay
    close_duration: 10s
    close_endstop: closed_contact_sensor
    close_sensor: close_movement_sensor

    stop_action:
      - switch.turn_on: relay

image

image

image

image

1 Like

Its cool, right? I dont understand the purpose of the “movement sensors” is that just to report the state outside of the cover entity?

No. They or any sensor can be made internal by setting the internal flag or by not specifying a name: and thus will not be exposed to HA. Their purpose here is to trigger movement which is a timed sequence: 10s would be increments of 10% every second, 20s would be 5%, 100s 1%, etc. Without those, the cover of course toggles between open and closed but with them you see opening/closing with a percentage updated every second or whatever the update frequency is set to in ESPHome.
Open contact is on: State is Open and movement sensor is off
Open contact changes to off so Closing Movement sensor is now on & state becomes Closing
Took me a while to get the logic right! Fortunately, the sensor is still on my desk so testing was easy.

Its does all of that by default already. This is why you configure an open/close duration. If the door is closed and you open it, it calculates the % open/closed based on those time durations. It already shows you opening/closing states in real-time. Hence the name “feedback”


You have it configured wrong. You need to configure your external end_stops. Remove the open and close movement sensors and then set has_built_in_endstop: true

If you dont set that to true, the door cover will push a Stop cover command to try and stop the door by itself. You dont want that because the door already does that.

You were right about the stop cover command which I hadn’t noticed. Adding has_built_in_endstop: true solved that. However, if I remove the movement sensors I don’t see Opening/Closing xx%, only Open or Closed when my reed switches activate.