Cheap Yellow ESP32 Display as a garage door button and status display

It took me a few days to get this project to this point. I think I can now call it “finished” but who knows. I benefited greatly from searching threads when trying to figure out how to write the config, but there doesn’t seem to be a lot of content pertaining to these cheap yellow displays. I would like to post my yaml so others might benefit from my work.

The cheap yellow display does the following:

  • Shows a blue “closed” garage door mdi icon when the garage is closed, with the word CLOSED at the bottom of the screen
  • Shows a yellow “open” garage door mdi icon when the garage is open, with the word OPEN at the bottom of the screen
  • Shows a yellow mdi icon of a person inside the garage when there is motion detected inside the open garage, and a black icon when the garage is closed (because the door of the closed garage icon is blue)
  • Shows a red “open” garage door mdi icon when the garage is open AND there has been no motion detected inside the garage for ten minutes, with the amount of time the open garage has been unoccupied (“stagnant”) displayed at the bottom of the screen (10 Minutes, 11 Minutes, etc.)
  • Triggers the garage door button when the screen is touched for 500ms and slowly flashes the rear blue LED one time
  • Shows not only the states OPEN and CLOSED, but also OPENING and CLOSING, based on the amount of time the door travels

I’m not an expert, so feel free to offer any advice. The display is working as I’d planned, so I am not seeking any advice, though.

captive_portal:



spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12
  - id: touch
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39



color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%

  - id: my_blue
    red: 33%
    green: 47%
    blue: 61%

  - id: my_yellow
    red: 95%
    green: 97%
    blue: 8% 

  - id: my_black
    red: 0%
    green: 0%
    blue: 0%   



image:
  - file: mdi:garage
    id: garage
    resize: 220x220

  - file: mdi:garage-open
    id: garage_open
    resize: 220x220

  - file: mdi:walk
    id: person
    resize: 50x50



number:
  - platform: template
    name: "Timer Number"
    id: timer_garage_is_unoccupied
    optimistic: true
    min_value: 9
    max_value: 1430
    step: 1
    internal: false



script:
  - id: timer
    mode: restart
    then:
      - number.increment: timer_garage_is_unoccupied
      - delay: 1min
      - script.execute: timer



binary_sensor:
  - platform: homeassistant
    name: "Garage Door Open"
    id: garage_door_open
    entity_id: binary_sensor.garage_door_sensor ### My door open/closed sensor
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_off: garage_door_open
          then:
            - binary_sensor.template.publish:
                id: garage_door_open_for_10_minutes
                state: off
            - binary_sensor.template.publish:
                id: garage_motion_is_stagnant
                state: off
          else:
            - delay: 10min
            - if:
                condition:
                  for:
                    time: 10min
                    condition:
                      binary_sensor.is_on: garage_door_open
                then:
                  - binary_sensor.template.publish:
                      id: garage_door_open_for_10_minutes
                      state: on
 


  - platform: homeassistant
    name: "Garage Motion"
    id: garage_motion
    entity_id: binary_sensor.motion_inside_garage
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_motion
          then:
            - binary_sensor.template.publish:
                id: no_motion_for_10_minutes
                state: off
            - binary_sensor.template.publish:
                id: garage_motion_is_stagnant
                state: off
          else:
            - delay: 10min
            - if:
                condition:
                  for:
                    time: 10min
                    condition:
                      binary_sensor.is_off: garage_motion
                then:
                  - binary_sensor.template.publish:
                      id: no_motion_for_10_minutes
                      state: on
                else:
                  - binary_sensor.template.publish:
                      id: no_motion_for_10_minutes
                      state: off

  - platform: template
    name: "Garage Door Button"
    id: garage_door_button_binary_sensor

  - platform: template
    name: "Garage Motion Is Stagnant"
    id: garage_motion_is_stagnant
    internal: false
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_motion_is_stagnant
          then:
            - script.execute: timer
          else:
            - script.stop: timer
            - number.set:
                id: timer_garage_is_unoccupied
                value: 9

  - platform: template  
    name: "Garage Door Open For 10 Minutes"
    id: garage_door_open_for_10_minutes
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: no_motion_for_10_minutes
          then:
            - binary_sensor.template.publish:
                id: garage_motion_is_stagnant
                state: on

  - platform: template
    name: "No Motion For 10 Minutes"
    id: no_motion_for_10_minutes
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_door_open_for_10_minutes
          then:
            - binary_sensor.template.publish:
                id: garage_motion_is_stagnant
                state: on 

  - platform: template
    name: Screen_Touch
    id: screen_touch
    internal: False
    lambda: |-
      auto touch = id(my_touchscreen)->get_touch();
      if (id(touch).state > 0) {
      return true;
      } else {
      return false;
      }



text_sensor:
  - platform: homeassistant
    id: garage_door_status
    entity_id: sensor.garage_door_esp32_garage_door_opening_open_closing_closed
    internal: true



button:
  - platform: template
    name: Garage Door Touchscreen ESP32 Button
    id: garage_door_touchscreen_esp32_button
    internal: true
    on_press:
      then:
        - lambda: |-
            id(garage_door_button_binary_sensor).publish_state(true);
        - delay: 0.5s
        - lambda: |-
            id(garage_door_button_binary_sensor).publish_state(false);
    



font:
  - file: "https://github.com/pjobson/Microsoft-365-Fonts/raw/main/S/Segoe%20UI%20Black.ttf"
    id: segoe_ui_black_font
    size: 40



display:
  - platform: ili9xxx
    model: tft 2.4
    spi_id: tft
    cs_pin: GPIO15
    dc_pin: GPIO2
    rotation: 90
    id: cyd
    lambda: |-
      if (id(garage_door_open).state) {{{
      if (id(garage_motion_is_stagnant).state) {{
      it.image(50, 0, id(garage_open), id(my_red));
      it.printf(160, 210, (segoe_ui_black_font), my_red, TextAlign::CENTER, "%.0f Minutes", id(timer_garage_is_unoccupied).state);   
      }} else {{   
      it.image(50, 0, id(garage_open), id(my_yellow));
      it.print(160, 210, (segoe_ui_black_font), my_yellow, TextAlign::CENTER, id(garage_door_status).state.c_str());     
      if (id(garage_motion).state) {
      it.image(140, 140, id(person), id(my_yellow));
      } else {   
      ;
      } 
      }}
      }}} else {{{
      it.image(50, 0, id(garage), id(my_blue));
      it.print(160, 210, (segoe_ui_black_font), my_blue, TextAlign::CENTER, "CLOSED");
      if (id(garage_motion).state) {
      it.image(140, 140, id(person), id(my_black));
      } else {   
      ;
      }
      }}}



touchscreen:
  - platform: xpt2046
    id: my_touchscreen
    display: cyd
    spi_id: touch
    cs_pin: GPIO33
    interrupt_pin: GPIO36
    update_interval: 50ms
    transform:
      mirror_x: false
      mirror_y: false
      swap_xy: false
    calibration:
      x_max: 320
      y_max: 240
    on_touch:
      then:
        - light.turn_on: 
            id: backlight
            brightness: 100%
        - delay: 0.5s
        - if:
            condition:
              - binary_sensor.is_on: screen_touch
            then:
              - light.turn_on: 
                  id: garage_door_touchscreen_esp32_rear_led
                  red: 0%
                  green: 75%
                  blue: 100%
              - button.press: garage_door_touchscreen_esp32_button
              - delay: 1.5s
              - light.dim_relative:
                  id: garage_door_touchscreen_esp32_rear_led
                  relative_brightness: -100%
                  transition_length: 1s
    on_release:
      then:
        - light.turn_on: 
            id: backlight
            brightness: 75%




output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

  - platform: ledc
    pin: GPIO4
    id: led_red
    inverted: true

  - platform: ledc
    pin: GPIO16
    id: led_green
    inverted: true

  - platform: ledc
    pin: GPIO17
    id: led_blue
    inverted: true



light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Display Backlight"
    id: backlight
    restore_mode: ALWAYS_ON
    internal: True
    on_turn_on:
      then:
        - light.turn_on:
            id: backlight
            brightness: 75%
            red: 100%
            green: 100%
            blue: 1.0

  - platform: rgb
    name: "Rear LED"
    id: garage_door_touchscreen_esp32_rear_led
    restore_mode: ALWAYS_OFF
    red: led_red
    green: led_green
    blue: led_blue
    internal: True

Edit:

Here is the yaml for the Open, Opening, Closing, Closed sensor, taken from the ESP32 that controls the relays for the garage door opener inside the garage. There might be an easier way to do this with lambda, but I’m not a lambda expert.

binary_sensor:
  - platform: homeassistant
    name: "Garage Door Sensor"
    id: garage_door_sensor
    entity_id: binary_sensor.garage_door_sensor ### My door open/closed sensor
    internal: true

  - platform: template
    name: "Garage Door Malfunction"
    id: garage_door_malfunction
    ### When this is On HA will notify me that the door has malfunctioned



button:
  - platform: template
    name: Garage Door Button
    id: garage_door_button
    icon: "mdi:garage"
    on_press:
     then:
      - switch.turn_on: garage_door_button_switch
      - if:
          condition:
            - binary_sensor.is_off: garage_door_sensor
          then:
            - text_sensor.template.publish:
                id: garage_door_opening_open_closing_closed
                state: OPENING
            - delay: 9s
            - if:
                condition:
                  - binary_sensor.is_on: garage_door_sensor
                then:
                  - text_sensor.template.publish:
                      id: garage_door_opening_open_closing_closed
                      state: OPEN
                else:
                  - text_sensor.template.publish:
                      id: garage_door_opening_open_closing_closed
                      state: CLOSED
                  - delay: 9s
                  - if:
                      condition:
                        - binary_sensor.is_on: garage_door_sensor
                      then:
                        - text_sensor.template.publish:
                            id: garage_door_opening_open_closing_closed
                            state: OPEN
                      else:
                        - text_sensor.template.publish:
                            id: garage_door_opening_open_closing_closed
                            state: CLOSED
                        - binary_sensor.template.publish:
                            id: garage_door_malfunction
                            state: On
                        - delay: 60s
                        - binary_sensor.template.publish:
                            id: garage_door_malfunction
                            state: Off
          else:
            - text_sensor.template.publish:
                id: garage_door_opening_open_closing_closed
                state: CLOSING
            - wait_until:
                condition:
                  binary_sensor.is_off: garage_door_sensor     
                timeout: 11s
            - if:
                condition:
                  - binary_sensor.is_off: garage_door_sensor
                then:
                  - text_sensor.template.publish:
                      id: garage_door_opening_open_closing_closed
                      state: CLOSED
                else:
                  - text_sensor.template.publish:
                      id: garage_door_opening_open_closing_closed
                      state: OPEN
                  - binary_sensor.template.publish:
                      id: garage_door_malfunction
                      state: On
                  - delay: 60s
                  - binary_sensor.template.publish:
                      id: garage_door_malfunction
                      state: Off
1 Like

We want piccies.

You’ve been a big help to me and many others, Nick. When you ask me for photos, you get photos.

But they’re not great photos. Actually, they’re mostly screenshots from iPhone video. I’m not a big fan of YT so I don’t post videos much. Here is the open/close process. Notice the blue LED that lights up from the back of the unit when the screen is touched for 500ms (thus, triggering the button).

And here is motion in the open garage, and then no motion for 12 minutes (red indicator starts at 10 minutes and increases at one-minute interval).

2 Likes

Cool. You wouldn’t call that ui too crowded. Great for old eyes.

Hi @toreupfeet ,

this looks great.
I have been looking for such an example for a while.

Could you please include the esp32 section. showing what board, framework, platform and other options you are using?

preferably include the whole yaml file.

Thanks,
Ghassan

When I posted the original yaml, I had tidied up some of the entity names in order to make it easy to follow.

Here is the full yaml with no such revisions.

esphome:
  name: garage-door-touchscreen-esp32
  friendly_name: Garage Door Touchscreen ESP32


esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: ### Yours will be different

ota:
  password: ### Yours will be different
  platform: esphome

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ### Yours will be different
    password: ### Yours will be different

captive_portal:



spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12
  - id: touch
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39



color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_blue
    red: 33%
    green: 47%
    blue: 61%
  - id: my_green
    red: 15%
    green: 100%
    blue: 15%
  - id: my_yellow
    red: 95%
    green: 97%
    blue: 8%   
  - id: my_black
    red: 0%
    green: 0%
    blue: 0%   



image:
  - file: mdi:garage
    id: garage
    resize: 220x220

  - file: mdi:garage-open
    id: garage_open
    resize: 220x220

  - file: mdi:walk
    id: person
    resize: 50x50

#  - file: mdi:garage-lock
#    id: garage_lock
#    resize: 220x220



number:
  - platform: template
    name: "Timer Number"
    id: timer_garage_unoccupied
    optimistic: true
    min_value: 9
    max_value: 1430
    step: 1
    internal: false



script:
  - id: timer
    mode: restart
    then:
      - number.increment: timer_garage_unoccupied
      - delay: 1min
      - script.execute: timer



binary_sensor:
  - platform: homeassistant
    name: "Garage Door Open Closed"
    id: garage_door_open_closed
    entity_id: binary_sensor.garage_door_sensor_window_door_is_open
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_off: garage_door_open_closed
          then:
            - binary_sensor.template.publish:
                id: garage_door_open_for_10_minutes
                state: off
            - binary_sensor.template.publish:
                id: garage_motion_stagnant
                state: off
          else:
            - delay: 10min
            - if:
                condition:
                  for:
                    time: 10min
                    condition:
                      binary_sensor.is_on: garage_door_open_closed
                then:
                  - binary_sensor.template.publish:
                      id: garage_door_open_for_10_minutes
                      state: on
 


  - platform: homeassistant
    name: "Garage Motion"
    id: garage_motion
    entity_id: binary_sensor.motion_inside_garage
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_motion
          then:
            - binary_sensor.template.publish:
                id: no_motion_for_10_minutes
                state: off
            - binary_sensor.template.publish:
                id: garage_motion_stagnant
                state: off
          else:
            - delay: 10min
            - if:
                condition:
                  for:
                    time: 10min
                    condition:
                      binary_sensor.is_off: garage_motion
                then:
                  - binary_sensor.template.publish:
                      id: no_motion_for_10_minutes
                      state: on
                else:
                  - binary_sensor.template.publish:
                      id: no_motion_for_10_minutes
                      state: off


  - platform: homeassistant
    id: garage_door_locked
    entity_id: switch.garage_door_esp32_garage_door_lock
    internal: true

  - platform: template
    name: "Garage Door Button"
    id: garage_door_button_binary_sensor

  - platform: template
    name: "Garage Motion Stagnant"
    id: garage_motion_stagnant
    internal: false
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_motion_stagnant
          then:
            - script.execute: timer
          else:
            - script.stop: timer
            - number.set:
                id: timer_garage_unoccupied
                value: 9

  - platform: template  
    name: "Garage Door Open For 10 Minutes"
    id: garage_door_open_for_10_minutes
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: no_motion_for_10_minutes
          then:
            - binary_sensor.template.publish:
                id: garage_motion_stagnant
                state: on

  - platform: template
    name: "No Motion For 10 Minutes"
    id: no_motion_for_10_minutes
    internal: true
    on_state: 
      then:
        if:
          condition:
            binary_sensor.is_on: garage_door_open_for_10_minutes
          then:
            - binary_sensor.template.publish:
                id: garage_motion_stagnant
                state: on 

  - platform: template
    name: Screen_Touch
    id: screen_touch
    internal: False
    lambda: |-
      auto touch = id(my_touchscreen)->get_touch();
      if (id(touch).state > 0) {
      return true;
      } else {
      return false;
      }



text_sensor:
  - platform: homeassistant
    id: garage_door_status
    entity_id: sensor.garage_door_esp32_garage_door_opening_open_closing_closed
    internal: true



button:
  - platform: template
    name: Garage Door Touchscreen ESP32 Button
    id: garage_door_touchscreen_esp32_button
    internal: true
    on_press:
      then:
        - lambda: |-
            id(garage_door_button_binary_sensor).publish_state(true);
        - delay: 0.5s
        - lambda: |-
            id(garage_door_button_binary_sensor).publish_state(false);
    



font:
  - file: "https://github.com/pjobson/Microsoft-365-Fonts/raw/main/S/Segoe%20UI%20Black.ttf"
    id: segoe_ui_black_font
    size: 40



display:
  - platform: ili9xxx
    model: tft 2.4
    spi_id: tft
    cs_pin: GPIO15
    dc_pin: GPIO2
    rotation: 90
    id: cyd
    lambda: |-
      if (id(garage_door_open_closed).state) {{{
      if (id(garage_motion_stagnant).state) {{
      it.image(50, 0, id(garage_open), id(my_red));
      it.printf(160, 210, (segoe_ui_black_font), my_red, TextAlign::CENTER, "%.0f Minutes", id(timer_garage_unoccupied).state);   
      }} else {{   
      it.image(50, 0, id(garage_open), id(my_yellow));
      it.print(160, 210, (segoe_ui_black_font), my_yellow, TextAlign::CENTER, id(garage_door_status).state.c_str());     
      if (id(garage_motion).state) {
      it.image(140, 140, id(person), id(my_yellow));
      } else {   
      ;
      } 
      }}
      }}} else {{{
      it.image(50, 0, id(garage), id(my_blue));
      it.print(160, 210, (segoe_ui_black_font), my_blue, TextAlign::CENTER, "CLOSED");
      if (id(garage_motion).state) {
      it.image(140, 140, id(person), id(my_black));
      } else {   
      ;
      }
      }}}



touchscreen:
  - platform: xpt2046
    id: my_touchscreen
    display: cyd
    spi_id: touch
    cs_pin: GPIO33
    interrupt_pin: GPIO36
    update_interval: 50ms
    transform:
      mirror_x: false
      mirror_y: false
      swap_xy: false
    calibration:
      x_max: 320
      y_max: 240
    on_touch:
      then:
        - light.turn_on: 
            id: backlight
            brightness: 100%
        - delay: 0.5s
        - if:
            condition:
              - binary_sensor.is_on: screen_touch
            then:
              - light.turn_on: 
                  id: garage_door_touchscreen_esp32_rear_led
                  red: 0%
                  green: 75%
                  blue: 100%
              - button.press: garage_door_touchscreen_esp32_button
              - if:
                  condition:
                    - binary_sensor.is_on: garage_motion_stagnant
                  then:
                    - binary_sensor.template.publish:
                        id: garage_motion_stagnant
                        state: off
              - delay: 1.5s
              - light.dim_relative:
                  id: garage_door_touchscreen_esp32_rear_led
                  relative_brightness: -100%
                  transition_length: 1s
    on_release:
      then:
        - light.turn_on: 
            id: backlight
            brightness: 75%




output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

  - platform: ledc
    pin: GPIO4
    id: led_red
    inverted: true

  - platform: ledc
    pin: GPIO16
    id: led_green
    inverted: true

  - platform: ledc
    pin: GPIO17
    id: led_blue
    inverted: true



light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Display Backlight"
    id: backlight
    restore_mode: ALWAYS_ON
    internal: True
    on_turn_on:
      then:
        - light.turn_on:
            id: backlight
            brightness: 75%
            red: 100%
            green: 100%
            blue: 1.0

  - platform: rgb
    name: "Rear LED"
    id: garage_door_touchscreen_esp32_rear_led
    restore_mode: ALWAYS_OFF
    red: led_red
    green: led_green
    blue: led_blue
    internal: True
1 Like

thanks a lot,

Now I have a working simplified “Hello world” example.

My intention is to try to use lvgl which I expect will be easier to mange the graphics.

I found othert examples doing that but either using arduino with this display, or using esphoime with another display,
i hope i can ,manage to get it working with this dsplay in esphome.

Kind regards,
Ghassan.

1 Like