I have a doorbell camera in the UniFi Protect ecosystem. I wanted a way to show the doorbell stream on a low cost wall mounted display that integrates will with the existing home automations.

LVGL Display

I have a pair of these ESP32-S3 based screens, they are very common on aliexpress under the name “ESP32-S3 Development Board LVGL 4.0 inch 480*480 Smart Display”. I paid $40AUD or less for them. They consist of a front touch display connected to an ESP32-S3, with then an extra (optional) board that clips on the back that has a power supply and relay output. For my use case I generally dont use the relay so I can’t talk to how it performs.

Once you pick these up, you can easily flash ESPHome onto them over the USB port. Note that I always flash them without the mains connected

The ESP32-S3 is just about powerful enough to make these into a nice display unit when showing camera snapshots.

I have not found a way to show actual video feed on these units. Sadly, UniFi’s intercom viewer, which is perfect for what I want, doesnt work with just the doorbell.

This is my slightly compromised solution that has been running for months without issue.

The pipline uses the Home Assistant UniFi Protect addon to fetch the snapshot url for the video feed. Then this is processed via imgproxy to make a smaller image and resized to suit the display. This is then drawn on the display every few seconds.

To set this up I have:

  1. UniFi G4 doorbell
  2. Home Assistant Yellow
  3. Home Assistant UniFi
  4. Home NAS running imgproxy
  5. ESP32-S3 display mounted in the wall

The ESPHome configuration is as follows:


http_request:
  verify_ssl: false

text_sensor:
  - platform: homeassistant
    id: doorbell_snapshot_url
    entity_id: camera.g4_doorbell_high_resolution_channel
    attribute: entity_picture
    internal: true
    on_value:
      # Update the online_image URL whenever HA provides a new token
      then:
        - online_image.set_url:
            id: doorbell_image_source
            # Combine HA base URL with the relative path from the sensor
            url: !lambda |-
                            std::string original = "http://<Home Assistant IP>:8123" + x;
                            // Convert string to a vector of bytes
                            std::vector<uint8_t> data(original.begin(), original.end());
                            // Encode
                            std::string encoded = esphome::base64_encode(data);
                            return "http://<ImgProxyIP:Port>/unsafe/rs:fill:480:240/" + encoded + ".jpg";


# 2. Define the image source
online_image:
  - id: doorbell_image_source
    type: RGB565 
    format: jpg
    url: "http://localhost/placeholder.jpg"
    # Set to 'never' because we will trigger it manually every 10s
    update_interval: 3s
    resize: 480x240 
    on_download_finished:
      # This ensures the LVGL widget updates once the data is ready
      then:
        - lvgl.image.update:
            id: doorbell_lvgl_widget
            src: doorbell_image_source

lvgl:
  id: lvgl_main
  pages:
    - id: main_page
      layout: 4x4
      widgets:
        - container:
            id: video
            grid_cell_x_align: stretch
            grid_cell_y_align: stretch
            grid_cell_row_pos: 2
            grid_cell_column_pos: 0
            grid_cell_row_span: 2
            grid_cell_column_span: 4
            widgets:
              - image:
                  id: doorbell_lvgl_widget
                  src: doorbell_image_source
                  width: 100%
                  height: 100%
                  align: CENTER