Esphome control of CentSys D5-Evo

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

Thanks for this. I’ve been trying to do this to my gate but I couldn’t get it working.
Are you able to attach a pic of your layout on the motor.
Will the Shelly Uni Plus work as the Uni is not available?

I can’t confirm the Uni Plus will work because I don’t have one. But based on its marketed specifications, I don’t see why it wouldn’t. In the ways which matter, it seems to be identical in specification:

  • Natively supports a 12V power source
  • Two potential-free outputs for TRG and PED
  • One analog input for reading STATUS

In theory the only thing would be to make the appropriate adjustments to the YAML for its chipset, board and gpio numbers.

This gate is not for my house so I can’t easily take a photo. But I can confirm that the Uni required zero supplemental circuitry in order to work. My recollection is

  • Vcc —> Aux 12V Out
  • Gnd / N —> Com
  • Out 1A —> Trg
  • Out 1B —> Com
  • Out 2A —> Ped
  • Out 2B —> Com
  • Analog —> Status

Because there were so many connections to Com (DC ground) I used a 5-way wago connector to make it easier.