Looking for examples of "custom cover" from ESPHome.io

I found slatri’s post: Cover component - Custom cover? and he’s doing some stuff with a rotary encoder. That’s more advanced than what I’m trying to do. I want to try to build up a 4-state garage door opener that has CLOSED/OPEN/CLOSING/OPENING based on a state machine that contains two reed switches, a top and bottom. Something like this:

image

1 Like

Well, I finally got it working after 3 hours… anyone else who wants to do this should look at Cover Template Publish Action it has OPENING and CLOSING operation states. Totally doable from within ESPHome and without having to use the Custom Cover.

Here’s the source code for reference (I threw the led in there for fun):

globals:
  - id: last_door_state
    type: int
    initial_value: '0'

# last_door_states are:
# 0 = closed
# 1 = open
# 2 = opening
# 3 = closing
# 4 = error

cover:
  - platform: template
    name: Garage Door
    id: garage_door
    device_class: garage

binary_sensor:
  - platform: gpio
    pin: D5
    device_class: opening
    id: top_reed_switch
    internal: True
    on_state:
      then:
        - lambda: |-
            if (id(top_reed_switch).state and not id(bot_reed_switch).state) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_IDLE;
              id(garage_door).publish_state();
              id(last_door_state) = 1;
              auto call = id(led).turn_off();
              call.perform();
            } else if (id(bot_reed_switch).state and not id(top_reed_switch).state) { 
              id(garage_door).position = 0.0;
              id(garage_door).current_operation = COVER_OPERATION_IDLE;
              id(garage_door).publish_state();
              id(last_door_state) = 0;
              auto call = id(led).turn_off();
              call.perform();
            } else if (not id(top_reed_switch).state and (id(last_door_state) == 1 or id(last_door_state) == 4)) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_CLOSING;
              id(garage_door).publish_state();
              id(last_door_state) = 3;
              auto call = id(led).turn_on();
              call.perform();
            } else if (not id(bot_reed_switch).state and (id(last_door_state) == 0 or id(last_door_state) == 4)) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_OPENING;
              id(garage_door).publish_state();
              id(last_door_state) = 2;
              auto call = id(led).turn_on();
              call.perform();
            } else {
              id(last_door_state) = 4;
              auto call = id(led).turn_off();
              call.perform();
            }
          
  - platform: gpio
    pin: D6
    device_class: opening
    id: bot_reed_switch
    internal: True
    on_state:
      then:
        - lambda: |-
            if (id(top_reed_switch).state and not id(bot_reed_switch).state) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_IDLE;
              id(garage_door).publish_state();
              id(last_door_state) = 1;
              auto call = id(led).turn_off();
              call.perform();
            } else if (id(bot_reed_switch).state and not id(top_reed_switch).state) { 
              id(garage_door).position = 0.0;
              id(garage_door).current_operation = COVER_OPERATION_IDLE;
              id(garage_door).publish_state();
              id(last_door_state) = 0;
              auto call = id(led).turn_off();
              call.perform();
            } else if (not id(bot_reed_switch).state and (id(last_door_state) == 0 or id(last_door_state) == 4)) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_OPENING;
              id(garage_door).publish_state();
              id(last_door_state) = 2;
              auto call = id(led).turn_on();
              call.perform();
            } else if (not id(top_reed_switch).state and (id(last_door_state) == 1 or id(last_door_state) == 4)) {
              id(garage_door).position = 1.0;
              id(garage_door).current_operation = COVER_OPERATION_CLOSING;
              id(garage_door).publish_state();
              id(last_door_state) = 3;
              auto call = id(led).turn_on();
              call.perform();
            } else {
              id(last_door_state) = 4;
              auto call = id(led).turn_off();
              call.perform();
            }

output:
  - platform: gpio
    id: d4_gpio2
    pin:
      number: D4
      inverted: True

light:
  - platform: binary
    id: led
    output: d4_gpio2
5 Likes

I found this while working on a similar project. We are attempting to track the door even if it’s stopped halfway. And (hopefully) eventually even adding features like being able to tell the door to open to xx% based on it learning how long it takes for the door to open/close under normal conditions.

You are welcome to check it out on github and if you require any additional functionality that we can help with, or just have some ideas, create a feature request there.

1 Like

Thanks for sharing this. I actually converted to using it tonight over my solution (on all three of my garage door monitors). Primarily because I power cycled the stuff in the garage today and I noticed after a few hours that the default state when they powered up was set to OPEN even though all of their CLOSED reed sensors should have been registered. I noticed that your solution moves the lambda to the cover instead of using the sensors to set the state, this allows the reed switch values to be registered by the cover when the device powers up. Works like a charm!

I was going to use this code for my garage door but i dont see anywhere you’re outputting to a relay, is this purely for monitoring and not controlling?

@mikeycv, I recommend you use the code that @NeilDuToit92 shared on github. The code I posted at the top was a proof of concept to show that you could use two reed switches to get the state of the door fully opened or fully closed and represent it with an ESPHome cover.

The code on github is full blown and works better (because the correct state of the door is realized on boot). Anyway, you can see in that code where the reference to the relay exists starting on line 31:

switch:
  # The switch that turns the UP direction on
  - platform: gpio
    pin: D1
    id: garage_switch
    # If ESP reboots, do not attempt to restore switch state
    restore_mode: ALWAYS_OFF
    on_turn_on:
    - delay: 500ms
    - switch.turn_off: garage_switch

I have three garage doors and two of them use button technology that isn’t easily manipulated (cannot just short the pins to trigger a button press event). I found this awesome post from @TaperCrimp which explains how to wire in a garage door opener remote control.

I then created three separate units:

  • one that handles it’s own button and door state (fully self contained)
  • one that watches its door state and has the remote wired in with two relays for hitting two of the buttons, it publishes a service to Home Assistant for pressing the button for the other garage door
  • the last one that just monitors state, it has a template switch on it that calls a Home Assistant service to press the button on the other device.

It seems a little janky when you think about the process but in reality it’s completely transparent and the interface works like you’d expect. :slight_smile:

1 Like

Interesting read. I have been an advocator of two reed switches on a garage door for a long time. Way better feed back.

For myself I made a template sensor in Home Assistant for it.

- platform: template
  sensors:
    garage_door:
      friendly_name: 'Garage Door'
      value_template: >-
        {%- if is_state("binary_sensor.garage_closed", "on")  %}
        Garage Door Closed
        {%- elif is_state("binary_sensor.garage_open", "on") %}
        Garage Door Open
        {%- else %}
        Garage Door In Transit
        {%- endif %}

## And for the garage door cover

- platform: template
  sensors:
    garage_door_sensor:
      friendly_name: 'Garage Door Sensor'
      value_template: >-
        {%- if is_state("binary_sensor.garage_closed", "on")  %}
        closed
        {%- elif is_state("binary_sensor.garage_open", "on") %}
        open

        {%- endif %}

Downside is I still have yet to solve the log errors I get with it

2021-01-25 17:18:33 ERROR (MainThread) [homeassistant.components.template.cover] Received invalid cover is_on state: . Expected: open, opening, closed, closing, true, false

If you haven’t started dabbling in ESPHome this would be a great way to start. Highly recommend using ESPHome to handle these low-level operations as opposed to using template sensors in HA. Don’t get me wrong, template sensors from HA have their place but for ‘smarting’ a garage door opener, I don’t think anything beats ESPHome. As a tantalizer/example of what you can do, here are the controls and outputs from my center garage door opener. It’s run from a Wemos D1 Mini:

I have motion sensors on the other two and much of those outputs (like uptime, wifi strength, SSID, etc.) are templated in ESPHome so they exist on all my ESPHome devices automatically. The raw reed switch outputs are hidden from HA and only exist as the “cover status”: the icon shows you that the door is closed, opening, open, or closing – exactly as you’d hope/expect.

ESPHome FTW!

Yeh, I’m a big advocator of ESPhome. Converted from Tasmota due to Tasmota not sending current switch status after reboot of the sensor or home assistant. Status wouldn’t get updated until a state change. ESPhome did it out of the box. Before that I was using my sensors pre 2.0. Love my ESP’s.

I think I’ll be diving into throwing templates on the device as you said.

Revisiting this post again as it’s one of the only decent garage door sensor posts, and I’m trying to get a sensor working how I want it with my n00b skills.

All this code seems a little too complicated for what it needs to do. Or I’m not understanding it totally.

So from my point of view I wanted to discuss with @SpikeyGG and @NeilDuToit92 who have their heads around it.

Home Assistant cover has 4 states I want it to see. Open, Closed, Opening, Closing.

With two sensors on the door we can have logic to get that.

Open = Open switch closed
Closed = Closed switch closed
Opening = State change of Closed switch to open
Closing = State change of Open switch to open

Should it not be as simple as a lambda with states for open and closed and an on_release state for opening or closing?

I can’t seem to get the code right. and the doc’s aren’t quite the best either for what I’m trying to do.

Note, I’m only trying to deal with sensors and not any control.

This is what I was trying to do, and ESPHome didn’t like my template.

I set up a global called transit to track the logic of the sensors
1= open
2=closed
3=closing
4=opening
I was using the binary sensor to set the global data with on_set and on_release
Trying to get a template lambda to react to the numbers 1-4 with the associated situation of the door.

The binary sensor part of the code was working, the template obviously didn’t compile lol

globals:
  - id: transit
    type: int

binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO13
      inverted: false
    name: "Garage_Open"
    device_class: door
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms
      - invert:
    on_release:
      - globals.set:
         id: transit
         value: '3'
      - logger.log: " Transit = 3 = Closing"
    on_press:
      - globals.set:
         id: transit
         value: '1'
      - logger.log: " Transit = 1 = Open"  
      
      - cover.template.publish
          id: door_position_sensor 
          state: OPEN
        
  - platform: gpio
    pin: 
      number: GPIO12
      inverted: false
    name: "Garage_Closed"
    device_class: door  
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms
    on_press:
      - globals.set:
         id: transit
         value: '4'
      - logger.log: " Transit = 4 = Opening"
    on_release:
      - globals.set:
         id: transit
         value: '2'
      - logger.log: " Transit = 2 = Closed"      

sensor:

  - platform: template
    name: "Door Position Sensor"
    id: door_position_sensor
    lambda: |-
      if (id(transit)=1) {
        return COVER_OPEN;
      } 
      if (id(transit)=2) {
        return COVER_CLOSED;
      } 
      if (id(transit)=3) {
        return COVER_CLOSING;
      } 
      if (id(transit)=4) {
        return COVER_OPENING;
      } 

Been looking for this for a while.
I want a ‘passive’ cover monitor, not one that controls the door, but just tells me its state.
I long ago tapped into the door-opener’s internal controller and found a pair of traveling contactors that were happy to share their grounded/not-grounded state (floating to logic-level when not grounded) which indicates where the opener believes the door to be. A simple voltage divider makes them GPIO-compatible signals.
So I wrote my own code back then to deliver that 4-state info via MQTT, but would like to convert that to ESPHome for easier maintenance ongoing.

EDIT: Fixed missing sections of yml

To just monitor the door, in theory, you can do the following (I have not tested it as I don’t have access to devices at the moment)

  • You don’t need a global variable, you just set the door_position_sensor template directly when one of the closed_endstop or open_endstop triggers on_press and on_release
  • You don’t need to specify the gpio pins are of class door
  • You can (Optionally) set the gpio pins to internal as you don’t care about their status in HA, only the status of the template
  • You don’t need to add filters because there is no logic being triggered other than setting the sensor
  • You don’t need a lambda on the template sensor as it doesn’t need to determine it’s own value, it gets set when the GPIO changes.
binary_sensor:
  - platform: gpio
    internal: true
    pin:
      number: D5
      mode: INPUT_PULLUP
      inverted: true
    name: "Garage Closed Reed Switch"
    id: closed_endstop
    on_press:
      - logger.log: " Transit Transit = 2 = Closed"
      - sensor.template.publish:
          id: door_position_sensor 
          state: '2'
    on_release:
      - logger.log: " Transit = 3 = Closing" 
      - sensor.template.publish:
          id: door_position_sensor 
          state: '3'
  - platform: gpio
    internal: true
    pin:
      number: D6
      mode: INPUT_PULLUP
      inverted: true
    name: "Garage Open Reed Switch"
    id: open_endstop
    on_press:
      - logger.log: " Transit = 1 = Open"  
      - sensor.template.publish:
          id: door_position_sensor 
          state: '1'
    on_release:
      - logger.log: " Transit = 4 = Opening" 
      - sensor.template.publish:
          id: door_position_sensor 
          state: '4'
sensor:
  - platform: template
    name: "Door Position Sensor"
    id: door_position_sensor
1 Like

In this example, HA will have to map status 1-4 to the relevant status, you can make the 1-4 any values you want like OPENING, CLOSING, OPEN, CLOSED and it will pull those values through to HA.

Thanks @NeilDuToit92, Didn’t think ESP could be setup like you have written.

Getting help with code is one awesome thing, but I like to know what it’s doing.

I didn’t think / didn’t fully understand what the doc’s was saying, you could just set up a blank per se template sensor without defining what it does.

But it seems you can, then as you have written, the templates state is publishes a change in state when there is a automations on the GPIO.

I was trying to code assuming I could only change the state of a template sensor in a lambda / automation nested in said template sensor. Hence the whole 1-4 numbering.

I want ESPhome to send the data out how HA wants the sensor data seen for a template sensor eg
value_template
Defines a template to get the state of the cover. Valid values are open/true or opening/closing/closed/false.

So the above was typed before I did some coding, ESPhome still doesn’t like what I am doing.

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO12
      mode: INPUT_PULLUP
      inverted: false
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms
    name: "Garage Closed Sensor"
    id: closed_endstop
    on_press:
      - logger.log: "CLOSED"
      - sensor.template.publish:
          id: door_position_sensor 
          state: 'CLOSED'
    on_release:
      - logger.log: "CLOSING" 
      - sensor.template.publish:
          id: door_position_sensor 
          state: "CLOSING"

  - platform: gpio
    pin:
      number: GPIO13
      mode: INPUT_PULLUP
      inverted: false
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms      
    name: "Garage Open Sensor"
    id: open_endstop
    on_press:
      - logger.log: "OPEN"  
      - sensor.template.publish:
          id: door_position_sensor 
          state: "OPEN"
    on_release:
      - logger.log: "OPENING" 
      - sensor.template.publish:
          id: door_position_sensor 
          state: "OPENING"
        
sensor:

  - platform: template
    name: "Door Position Sensor"
    id: door_position_sensor

It gives an error on the all 4 state’s for sensor.template.publish
Eg

state: CLOSED

The error is “expected float”

Few notes from your reply before
Not keeping the binary sensor internal as I want to see the raw data from the binary sensor in HA
My reed switches needed the debouncing filter, as they were bouncing.

Well I solved that one quick.

I had set it up as a template sensor, which expects a number as its state. rather obvious now.

So that’s all fixed and now will submit a pull request as the template cover does not allow opening or closing attributes. Which it should to line up with a template cover’s possible options in HA.

Well here is how I have it working in Home Assistant.

The Current_Operation publish action will tell home assistant that its opening or closing.

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO12
      mode: INPUT
      inverted: true
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms
    name: "Garage Closed Sensor"
    id: closed_endstop
    on_press:
      - logger.log: "CLOSED"
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: IDLE
          state: 'CLOSED'
    on_release:
      - logger.log: "OPENING" 
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: OPENING
          state: "OPEN"

  - platform: gpio
    pin:
      number: GPIO13
      mode: INPUT
      inverted: true
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms      
    name: "Garage Open Sensor"
    id: open_endstop
    on_press:
      - logger.log: "OPEN"  
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: IDLE
          state: "OPEN"
    on_release:
      - logger.log: "CLOSING" 
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: CLOSING
          state: "OPEN"
     
cover:
  - platform: template
    name: "Door Position Sensor"
    id: door_position_sensor

So the next big problem is that should the ESP loose power, When it powers back up it will assume the template cover is closed, due to all the state and operation changes are changed via an action. And if that action takes place when there is no power on the ESP, well it didn’t happen.

Garage door coveres need to be bomb proof, you need to trust their states and actions 100%

(over christmas I had my garage door open uncommanded in an electrical surge. I was 400kms away, the surge also took out my wifi router)

And now what seems to be a final decent code, will report if its open or closed on power up.

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO12
      mode: INPUT
      inverted: true
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms
    name: "Garage Closed Sensor"
    id: closed_endstop
    on_press:
      - logger.log: "CLOSED"
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: IDLE
          state: 'CLOSED'
    on_release:
      - logger.log: "OPENING" 
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: OPENING
          state: "OPEN"

  - platform: gpio
    pin:
      number: GPIO13
      mode: INPUT
      inverted: true
    filters:
      - delayed_on: 1000ms  
      - delayed_off: 1000ms      
    name: "Garage Open Sensor"
    id: open_endstop
    on_press:
      - logger.log: "OPEN"  
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: IDLE
          state: "OPEN"
    on_release:
      - logger.log: "CLOSING" 
      - cover.template.publish:
          id: door_position_sensor 
          current_operation: CLOSING
          state: "OPEN"
     
cover:
  - platform: template
    name: "Door Position Sensor"
    id: door_position_sensor
    lambda: |-
      if (id(closed_endstop).state) {
        return COVER_CLOSED;
      } else {
        return COVER_OPEN;
      }    

This last code looks pretty good, have you developed it further more to allow for example positioning the cover in terms of %%?
eg: open cover to 20%?

No, It’s used on my garage door, it only has position for full open and full closed.

@SpikeyGG - any chance you can share your YAML from your garage setup? Seems you have a number of entities that mirror what I’m trying to do.