ESPHome Shelly UNI Garage Door Controller

Here is my Shelly UNI Garage door controller with states.
Wanted a more advanced garage opener with more states than two.

This reports 5 states with text and icons:
Fully Open
Fully Closed
Partially Open
Door is Closing
Door is Opening

Shelly UNI is very small, good specs, can be powered with 12V – 36V DC.
Hörmann ProMatic 3 operates in 24V DC. No need of extra converters, just plug & play…

Shelly downsides:

  • Difficult to flash, the pins on the board are small.
  • ADC Power measurement is not good in my oppinion.

To get this to work, I had to create a virtual sensor (template sensor) in configuration.yaml (Homeassistant).
A real cover in ESPHome just to control garagedoor from Android Auto and other frontends.
I tried to create a template cover in configuration.yaml, but I did not like it because the icon color did not work right, sometimes it changed color in wrong states, and I had to expose relay to frontend.

How:
Simple text_sensor with states from sensors, homeassistant uses these “states” in template sensor that masquerades as a real Garage Cover with icons.
States come from magnet reed switches and voltage reading from ADC.

Some small issues:
Status changes to wrong status sometimes for a short period of time while opening or closing.
ADC sensor floods log

Time to start the project!

Hardware:
1x Shelly UNI
2x Magnet Reed Switch
1x DHT22 Sensor
1x Hörmann ProMatic3
Cables
Old ATX PCIe power connector

Connections:
DHT22
UNI pin4 Yellow to DHT VCC+
UNI pin5 Blue to DHT Data
UNI pin6 Green to DHT GND-

Magnet Reed Switch
Contact sensor top - GPIO12, UNI pin8 Light Brown and UNI pin1 Red (VCC Main)
Contact sensor bottom - GPIO13, UNI pin7 Dark Brown and UNI pin1 Red (VCC Main)

Relay 1 to Hörmann 20 & 21
Relay 2 to Hörmann 20 & 23 - for partial opening - Optional

promatic-button

Power UNI from backup battery pins.
Cut out one row with 2 connectors from ATX PCIe power connector, if you don’t have better connectors.
Middle pin positive (+), pin closest to the lamp negative (-)

promatic-power

UNI pin3 White to red wire on the motor
I used thick wire and pushed it in the connector, I will make a better Wago connection “later”.
ADC sensor has to be calibrated to your motor, check the vaues when you have commented lamda “filter” disabled.

promatic-motor

Now to the Code!

ESPHome code, Flash to Shelly UNI

substitutions:
  devicename: garage-2-uni
  friendly_name: Garage 2
  update_slow: '60s'              ## Wifi, Uptime...
  dht22_update: '60s'
  button_delay: '0.7s'
  timeout_reboot: '15min'
  sensor_invert_top: 'true'       ## NO Contact set to 'true' , NC contact set to 'false'
  sensor_invert_bottom: 'true'    ## NO Contact set to 'true' , NC contact set to 'false'
  hide_relay1: 'true'             ## Hide from frontend 'true', Show in frontend 'false'
  hide_relay2: 'true'             ## Hide from frontend 'true', Show in frontend 'false'
  hide_cover: 'false'             ## Hide from frontend 'true', Show in frontend 'false'

esphome:
  name: ${devicename}
  friendly_name: $friendly_name

esp8266:
  board: esp01_1m

logger:
  level: DEBUG
  logs:
    component: ERROR

    ## Common settings
wifi:
  ssid: !secret wifi_iot_ssid
  password: !secret wifi_iot_password
  power_save_mode: none
  reboot_timeout: $timeout_reboot

  ap:
    ssid: $friendly_name Fallback Hotspot
    password: !secret fallback_ap_password

captive_portal:
web_server:
  port: 80
  auth:
    username: !secret admin_user
    password: !secret fallback_ap_password
  
api:
  encryption:
    key: !secret esphome_api_key
  reboot_timeout: $timeout_reboot
 
ota:
  password: !secret esphome_api_password

packages:
  switch: !include include/switch_reboot.yaml
  sensor: !include include/sensor_wifi_uptime.yaml
  text_sensor: !include include/text_sensor_wifi_uptime.yaml
  binary_sensor: !include include/binary_sensor_status.yaml

    #### Shelly UNI PIN Guide ####
    ## Onboard               Internal LED, Status LED       # GPIO00
    ## Onboard               Relay 2                        # GPIO04
    ## Onboard               Relay 1                        # GPIO15

    ## UNI pin1 Red          VCC IN
    ## UNI pin2 Black        GND / N     
    ## UNI pin3 White        ADC_IN, analog PIN, ADC Range  # GPIO17
    ## UNI pin4 Yellow       VCC 3.3VDC Output, VCC Sensor
    ## UNI pin5 Blue         Data, AM2301                   # GPIO05
    ## UNI pin6 Green        Internal GND
    ## UNI pin7 Light Brown  Input 1, Switch_n1             # GPIO12
    ## UNI pin8 Dark Brown   Input 2, Switch_n2             # GPIO13

light:
    ## Enable onboard LED
  - platform: status_led
    name: "Status LED"
    pin: GPIO00
    internal: true

sensor:
    ## Temperature and Humidity,
    ##     UNI pin4 Yellow to DHT VCC+
    ##     UNI pin5 Blue to DHT Data
    ##     UNI pin6 Green to DHT GND- 
  - platform: dht
    pin: GPIO05
    model: DHT22
    update_interval: $dht22_update
    temperature:
      name: "Temperature"
      id: temperature
      accuracy_decimals: 1
    humidity:
      name: "Humidity"
      id: humidity
      accuracy_decimals: 0

    ## Analog Sensor ADC_IN, Filter to states
    ##      GPIO17, UNI pin3 White ADC_IN
    ##      Values: 2 = Closing, 4 = Idle, 6 = Opening
  - platform: adc
    pin: GPIO17
    name: "Door Motion Sensor"
    id: door_motion
    update_interval: 2s
    filters:
      - multiply: 10
      - lambda: |-
          if (x > 6.6) {
            return 6;
          } else if (x < 6.1) {
            return 2;
          } else {
            return 4;
          }
    internal: true

binary_sensor:
    ## Contact sensor top
    ##     GPIO12, UNI pin8 Light Brown and UNI pin1 Red (VCC Main)
  - platform: gpio
    pin:
      number: 12
      inverted: $sensor_invert_top 
    name: "End Sensor Top"
    id: end_sensor_top
    on_state:
      then: 
        - component.update: cover_state
    internal: true

    ## Contact sensor bottom
    ##     GPIO13, UNI pin7 Dark Brown and UNI pin1 Red (VCC Main)
  - platform: gpio
    pin:
      number: 13
      inverted: $sensor_invert_bottom 
    name: "End Sensor Bottom"
    id: end_sensor_bottom
    on_state:
      then: 
        - component.update: cover_state
    internal: true

switch:
    ## Button relay 1
  - platform: gpio
    pin: GPIO15
    name: "Relay 1"
    id: relay1
    internal: $hide_relay1
    ## Prevent relay1 and relay2 from being activated at the same time.
#    interlock: &interlock_group [relay1, relay2] 
    on_turn_on:
      - delay: $button_delay
      - switch.turn_off: relay1

    ## Button relay 2
  - platform: gpio
    pin: GPIO04
    name: "Relay 2"
    id: relay2
    internal: $hide_relay2
    ## Prevent relay1 and relay2 from being activated at the same time.
#    interlock: *interlock_group 
    on_turn_on:
      - delay: $button_delay
      - switch.turn_off: relay2

text_sensor:
    ## State to HomeAssistant, to create template sensor or template cover
  - platform: template
    name: Cover State
    icon: mdi:garage
    id: cover_state
#    update_interval: 2s
    lambda: |-
      if ((id(end_sensor_bottom).state == 0) && (id(end_sensor_top).state == 1)) {
        return {"Fully Open"};
      } else if ((id(end_sensor_bottom).state == 1) && (id(end_sensor_top).state == 0)) {
        return {"Fully Closed"};
      } else if ((id(end_sensor_bottom).state == 0) && (id(end_sensor_top).state == 0) && (id(door_motion).state == 4)) {
        return {"Partially Open"};
      } else if ((id(end_sensor_bottom).state == 0) && (id(end_sensor_top).state == 0) && (id(door_motion).state == 2)) {
        return {"Door is Closing"};
      } else if ((id(end_sensor_bottom).state == 0) && (id(end_sensor_top).state == 0) && (id(door_motion).state == 6)) {
        return {"Door is Opening"};
      } else {
        return {"Loading"};
      }

cover:
    ## Garage Cover, use in Android Auto, and other frontends, cloud solutions...
  - platform: template
    device_class: garage
    name: "Door"
    id: door_2
    internal: $hide_cover
    lambda: |-
      if ((id(end_sensor_bottom).state == 1) && (id(end_sensor_top).state == 0) && (id(door_motion).state == 4)) {
        return COVER_CLOSED;
      } else {
        return COVER_OPEN;
      }
    open_action:
      - switch.turn_on: relay1
    close_action:
      - switch.turn_on: relay1

Template sensor in configuration.yaml or sensor.yaml
to include sensors from sensor.yaml add sensor: !include sensor.yaml to configuration.yaml.

 # Garage 2 State Icons and State Text
  - platform: template
    sensors:
      garage_2_cover_state:
        friendly_name: Garage 2 Cover
        # Set state based on the state of a seperate sensor
        value_template: >-
            {{ states('sensor.garage_2_uni_cover_state') }}
        # Set the icon based on state of a seperate sensor
        icon_template: >-
          {% if is_state("sensor.garage_2_cover_state", "Fully Open") -%}
            mdi:garage-open
          {% elif is_state("sensor.garage_2_cover_state", "Fully Closed") -%}
            mdi:garage
          {% elif is_state("sensor.garage_2_cover_state", "Partially Open") -%}
            mdi:garage-alert
          {% elif is_state("sensor.garage_2_cover_state", "Door is Closing") -%}
            mdi:arrow-down-thick
          {% elif is_state("sensor.garage_2_cover_state", "Door is Opening") -%}
            mdi:arrow-up-thick
          {% else -%}
            mdi:alert-circle
          {% endif %}

If you want to use cover instead of sensor you can create a cover in configuration.yaml
I was not happy with the status color change, it changed randomly.
Don’t forget to expose relay to frontend.

  - platform: template
    covers:
      garage_door:
        device_class: garage
        friendly_name: "Garage2 Door"
        open_cover:
          service: switch.toggle
          target:
            entity_id: switch.garage_2_relay_1
        close_cover:
          service: switch.toggle
          target:
            entity_id: switch.garage_2_relay_1
        stop_cover:
          service: switch.toggle
          target:
            entity_id: switch.garage_2_relay_1
        icon_template: >-
          {% if is_state("sensor.garage_2_cover_state", "Fully Open") -%}
            mdi:garage-open
          {% elif is_state("sensor.garage_2_cover_state", "Fully Closed") -%}
            mdi:garage
          {% elif is_state("sensor.garage_2_cover_state", "Partially Open") -%}
            mdi:garage-alert
          {% elif is_state("sensor.garage_2_cover_state", "Door is Closing") -%}
            mdi:arrow-down-thick
          {% elif is_state("sensor.garage_2_cover_state", "Door is Opening") -%}
            mdi:arrow-up-thick
          {% else -%}
            mdi:alert-circle
          {% endif %}
1 Like

Would it work through Shelly Integration in HA?

Maby, I did not try. I like ESPhome, no need of cloud service. More control of the device. ESPhome is integrated in Home Assistant and has similar code.

Sorry to revive this, I was wondering if this is still working for you, I am trying to replicate something similar with an esp32 and trying to wrap my head around what is being measured with this uni. As esp has an ADC that takes 3.3V max contrary to the Shelly I need to not fry it…
I’m mostly interested in the Door Motion Sensor that use an ADC, but I am wondering if it makes sense to measure from the red wire of the motor to the gnd of the esp that comes of the cable screwed into the port #5 as I was planning to power my esp from these ports #20 and #5 that provides 24V DC.
Re-reading your assembly you seem to power it from somewhere else, I will check if those two places have a common ground or not.
I am still curious that this reads 6V, isn’t it some phantom tension that is being measured and if yes maybe it still is reliable, that’s why I 'm asking :wink: