# Waveshare ESP32-S3-Touch-LCD-4B — doorbell JPEG viewer # Copy this file AND the components/ folder into your ESPHome config directory. # Pair with the resizer proxy in this repo. # # Touch cycles: stream A (default) -> stream B -> off -> stream A ... # # Uses a custom fast_jpeg component (RGB565 block decode + DMA blit). # Stock online_image JPEG is too slow for ~5 fps. substitutions: name: doorbell-display friendly_name: Doorbell Display stream_a_url: "http://10.0.1.195:8080/doorbell/?size=480" stream_b_url: "http://10.0.1.195:8080/oprit/?size=480" external_components: - source: type: local path: components esphome: name: ${name} friendly_name: ${friendly_name} min_version: "2025.2.0" on_boot: - priority: 800 then: - lambda: |- auto *io = id(ext_io); io->pin_mode(0, esphome::gpio::FLAG_OUTPUT); io->pin_mode(1, esphome::gpio::FLAG_OUTPUT); io->pin_mode(2, esphome::gpio::FLAG_OUTPUT); io->pin_mode(7, esphome::gpio::FLAG_OUTPUT); io->digital_write(0, true); io->digital_write(1, false); io->digital_write(2, false); io->digital_write(7, false); - delay: 120ms - lambda: |- id(ext_io)->digital_write(7, true); - priority: 600 then: - lambda: |- id(touch_state) = 0; id(doorbell_jpg).set_url("${stream_a_url}"); id(doorbell_jpg).set_enabled(true); - light.turn_on: id: backlight brightness: 100% esp32: board: esp32-s3-devkitc-1 variant: esp32s3 flash_size: 16MB framework: type: esp-idf sdkconfig_options: CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y" CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y" CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y" CONFIG_SPIRAM_RODATA: "y" advanced: include_builtin_idf_components: - esp_http_client psram: mode: octal speed: 80MHz logger: logs: fast_jpeg: INFO api: ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password power_save_mode: none i2c: sda: GPIO47 scl: GPIO48 frequency: 400kHz scan: true pca9554: - id: ext_io address: 0x20 pin_count: 8 globals: - id: touch_state type: int restore_value: false initial_value: "0" script: - id: cycle_stream mode: restart then: - lambda: |- id(touch_state) = (id(touch_state) + 1) % 3; - if: condition: lambda: "return id(touch_state) == 0;" then: - lambda: |- id(doorbell_jpg).set_url("${stream_a_url}"); id(doorbell_jpg).set_enabled(true); - light.turn_on: id: backlight brightness: 100% - if: condition: lambda: "return id(touch_state) == 1;" then: - lambda: |- id(doorbell_jpg).set_url("${stream_b_url}"); id(doorbell_jpg).set_enabled(true); - light.turn_on: id: backlight brightness: 100% - if: condition: lambda: "return id(touch_state) == 2;" then: - lambda: "id(doorbell_jpg).set_enabled(false);" - light.turn_off: backlight spi: - id: disp_spi interface: software clk_pin: pca9554: ext_io number: 2 mode: output: true mosi_pin: pca9554: ext_io number: 1 mode: output: true output: - platform: ledc id: backlight_pwm inverted: true pin: GPIO4 light: - platform: monochromatic id: backlight name: "Backlight" output: backlight_pwm restore_mode: ALWAYS_ON default_transition_length: 0s on_turn_on: - lambda: |- if (id(touch_state) != 2) { id(doorbell_jpg).set_enabled(true); } on_turn_off: - lambda: id(doorbell_jpg).set_enabled(false); # Simple on/off for Home Assistant automations (light still exposes brightness). switch: - platform: template name: "Screen" id: screen_power icon: mdi:monitor lambda: |- return id(backlight).current_values.is_on(); turn_on_action: - lambda: |- id(touch_state) = 0; id(doorbell_jpg).set_url("${stream_a_url}"); id(doorbell_jpg).set_enabled(true); - light.turn_on: id: backlight brightness: 100% turn_off_action: - lambda: |- id(touch_state) = 2; id(doorbell_jpg).set_enabled(false); - light.turn_off: backlight touchscreen: - platform: gt911 id: touch display: lcd update_interval: 50ms reset_pin: pca9554: ext_io number: 5 mode: output: true # INT is on the IO expander; used for address selection, polled for touches. interrupt_pin: pca9554: ext_io number: 6 mode: input: true on_touch: - script.execute: cycle_stream display: - platform: mipi_rgb id: lcd model: WAVESHARE-4-480x480 dimensions: 480x480 color_order: RGB auto_clear_enabled: false update_interval: never spi_id: disp_spi cs_pin: pca9554: ext_io number: 0 mode: output: true reset_pin: pca9554: ext_io number: 7 mode: output: true de_pin: GPIO17 hsync_pin: number: GPIO46 ignore_strapping_warning: true vsync_pin: number: GPIO3 ignore_strapping_warning: true pclk_pin: GPIO9 pclk_inverted: false data_pins: red: [GPIO10, GPIO11, GPIO12, GPIO13, GPIO14] green: [GPIO21, GPIO8, GPIO18, GPIO45, GPIO38, GPIO39] blue: [GPIO40, GPIO41, GPIO42, GPIO2, GPIO1] init_sequence: - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x10] - [0xC0, 0x3B, 0x00] - [0xC1, 0x0D, 0x02] - [0xC2, 0x21, 0x08] - [0xCD, 0x08] - [0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18] - [0xB1, 0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18] - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x11] - [0xB0, 0x60] - [0xB1, 0x30] - [0xB2, 0x87] - [0xB3, 0x80] - [0xB5, 0x49] - [0xB7, 0x85] - [0xB8, 0x21] - [0xC1, 0x78] - [0xC2, 0x78] - delay 20ms - [0xE0, 0x00, 0x1B, 0x02] - [0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44] - [0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00] - [0xE3, 0x00, 0x00, 0x11, 0x11] - [0xE4, 0x44, 0x44] - [0xE5, 0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0] - [0xE6, 0x00, 0x00, 0x11, 0x11] - [0xE7, 0x44, 0x44] - [0xE8, 0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0] - [0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40] - [0xEC, 0x3C, 0x00] - [0xED, 0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA] - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x00] - [0x36, 0x00] - [0x3A, 0x66] fast_jpeg: id: doorbell_jpg url: ${stream_a_url} display_id: lcd update_interval: 200ms # If colours look swapped, set big_endian: false big_endian: true