The following is a solution for controlling a CentSys Centurion D5-Evo gate motor using Esphome on a Shelly Uni. This code has:
- Button for triggering normal open
- Button for triggering hold open
- Button for triggering pedestrian open
- Cover entity which reports current gate status and provides discrete buttons for opening and closing
I chose a Shelly Uni because:
- It can be powered directly from the D5’s aux output, no power conversion required
- It has two built-in potential-free outputs which are perfect for TRG and PED, no relays required
- The analog input can read the STATUS output safely, no logic level shifters or optoisolators required
- It has an external antenna which works appreciably better than the on-board antenna of most typical devkit-style boards
- I had a few lying around
I previously deployed this with a NodeMCU clone but it died after about 6 months. Since replacing it with the Shelly Uni, it’s been running for well over 12 months now and unlike before, the wifi has been rock solid.
The one quirk for supporting the CentSys D5-Evo is deciphering its status LED and translating that into gate status. The possible states are:
- Closed: steady off
- Opening: slow flash (100 pulses/min, or around 300ms high + 300ms low)
- Open: steady on
- Closing: fast flash (200 pulses/min, or around 150ms high + 150ms low)
It’s a tricky one to read because the flashing light isn’t always perfectly aligned with change of state. The first and last flashes can be truncated, so there’s a risk of misinterpretation. In order to avoid misreads, you need to verify at least two durations. This could be two contiguous high durations, or, as I have done here, measuring both the high and low duration.
After trying a few methods, I settled upon on_multi_click
combined with a script as a reliable way of discriminating between states. Every time it flashes, it sets the state based on the flash duration, then calls the script. The script begins with a delay, so as long as keeps flashing, it won’t do anything. Only once the flashing stops can the script run to completion, which sets the gate as either fully open or closed.
Here’s my YAML (excluding boilerplate stuff like api/wifi/ota):
substitutions:
pin_led: GPIO0
pin_relay_1: GPIO15 # Labelled "Out 1", connected to TRG
pin_relay_2: GPIO4 # Labelled "Out 2", connected to PED
pin_white: A0 # White wire, connected to STATUS
pin_blue: GPIO05
pin_orange: GPIO12
pin_brown: GPIO13
pin_adc_range: GPIO17
esp8266:
board: esp01_1m
status_led:
pin:
number: ${pin_led}
inverted: yes
esphome:
on_boot:
priority: 600
then:
- script.execute: completion
script:
- id: completion
mode: restart
then:
- if:
condition:
lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_OPENING;'
then:
- delay: 3s
else:
- delay: 1s
- lambda: |-
if (id(gate_status).state) {
id(gate_cover).position = COVER_OPEN;
id(gate_cover).current_operation = COVER_OPERATION_IDLE;
id(gate_cover).publish_state();
} else {
id(gate_cover).position = COVER_CLOSED;
id(gate_cover).current_operation = COVER_OPERATION_IDLE;
id(gate_cover).publish_state();
}
sensor:
- platform: adc
pin: ${pin_white}
id: analog_sensor
update_interval: 0.02s
filters:
- or:
- delta: 0.01
- throttle: 5s
binary_sensor:
- platform: analog_threshold
id: gate_status
name: "Status LED"
sensor_id: analog_sensor
threshold:
upper: 0.11
lower: 0.1
filters:
- delayed_on_off: 10ms
on_multi_click:
- timing: # OPENING
- ON for 0.2s to 0.4s # 300ms
- OFF for 0.2s to 0.4s
then:
- logger.log: "on for 200-400ms"
- lambda: |-
if (id(gate_cover).current_operation != COVER_OPERATION_OPENING) {
id(gate_cover).current_operation = COVER_OPERATION_OPENING;
id(gate_cover).publish_state();
}
- script.execute: completion
- timing: # CLOSING
- OFF for 0.1s to 0.2s # 150ms
- ON for 0.1s to 0.2s
then:
- logger.log: "off for 100-200ms"
- lambda: |-
if (id(gate_cover).current_operation != COVER_OPERATION_CLOSING) {
id(gate_cover).current_operation = COVER_OPERATION_CLOSING;
id(gate_cover).publish_state();
}
- script.execute: completion
switch:
- platform: gpio
pin:
number: ${pin_relay_1}
inverted: false
id: trg
restore_mode: ALWAYS_OFF
- platform: gpio
pin:
number: ${pin_relay_2}
inverted: false
id: ped
restore_mode: ALWAYS_OFF
button:
- platform: template
id: trigger_short
name: Trigger Short
on_press:
then:
- switch.turn_on: trg
- delay: 1 sec
- switch.turn_off: trg
- platform: template
id: trigger_long
name: Trigger Long
on_press:
then:
- switch.turn_on: trg
- delay: 5 sec
- switch.turn_off: trg
- platform: template
id: trigger_ped
name: Trigger Ped
on_press:
then:
- switch.turn_on: ped
- delay: 1 sec
- switch.turn_off: ped
cover:
- platform: template
name: "Gate"
id: gate_cover
device_class: gate
optimistic: false
has_position: true
assumed_state: false
open_action:
- button.press: trigger_long
close_action:
- button.press: trigger_short