Waveshare ESP32-S3 LCD Touch 4"

You’re great Dennis! With the first one I resolved my problem with the driver (actually, that one contains its own driver, I guess that helps, and it’s only posted yesterday?). I can now see the custom Dashboard I have in homeassistant, so the buttons and the style is exactly the same whichever screen I use.

Here is the code:

esphome:
  name: mini-screen
  friendly_name: Mini Screen

  # Boot Sequence to set backlight and make a noise to test those functions
  on_boot:
    then:
      # Turn down the backlight
      - delay: 1000ms
      - light.turn_on:
          id: display_backlight
          brightness: 50%
      - delay: 500ms
      - switch.turn_on: board_buzzer
      - delay: 1000ms
      - switch.turn_off: board_buzzer

external_components:
  - source: github://pr#10071
    components: [waveshare_io_ch32v003]
    refresh: 1h
  - source: github://pr#14193
    components: [gt911]
    refresh: 1h
  - source: github://strange-v/RemoteWebViewClient@main
    refresh: 0s
    components: [remote_webview]

esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  framework:
    type: esp-idf
    components:
      - name: "espressif/esp_websocket_client"
        ref: 1.5.0
      - name: "espressif/esp-dsp"
        ref: 1.7.0
      - name: "bitbank2/jpegdec"
        source: https://github.com/strange-v/jpegdec-esphome

  # Flash/RAM settings for high performance
  flash_size: 16MB

psram:
  mode: octal
  speed: 80MHz

logger:
  level: DEBUG

# ================================================================
# WiFi
# ================================================================
wifi:
  ssid: !secret wifi_ssid                   # Defined in secrets.yaml
  password: !secret wifi_password           # Defined in secrets.yaml

# ================================================================
# I2C Buses
# ================================================================
i2c:
  # IO Expander, I2C lines on external connector, RTC, Touchscreen
  - id: bus_a
    setup_priority: 1000
    sda: GPIO15
    scl: GPIO7
    scan: true
    frequency: 400kHz


# ================================================================
# SPI for display
# ================================================================
spi:
  - id: spi0
    clk_pin: GPIO2
    mosi_pin: GPIO1

# ================================================================
# IO Expander (New Waveshare mcu based ch32v003)
# Using external PR component until integrated.
# ================================================================
waveshare_io_ch32v003:
  - id: wave_io_hub
    setup_priority: 850
    address: 0x24
    i2c_id: bus_a


# ================================================================
# Outputs on the IO Expander
# ================================================================
output:
  # PWM output for backlight control.
  # Inverted and zero_means_zero to match Waveshare's circuitry and prevent
  # flickering at low levels.
  - platform: waveshare_io_ch32v003
    waveshare_io_ch32v003_id: wave_io_hub
    id: wave_io_pwm_output
    inverted: true
    zero_means_zero: true
    safe_pwm_levels:  # 0...255
      min_value: 0    # Default: 0
      max_value: 247  # Default: 247. Safe max for ESP32S3-Touch-LCD-7B circuitry

switch: #Need to define and use this or buzzer just stays on at boot.
  - platform: gpio
    name: "Onboard Buzzer"
    id: board_buzzer
    setup_priority: 700
    pin:
      waveshare_io_ch32v003: wave_io_hub
      number: 6
      mode:
        output: true
      inverted: False

# ================================================================
# Light (Backlight Control)
# ================================================================
light:
  - platform: monochromatic
    name: "Display Backlight"
    id: display_backlight
    output: wave_io_pwm_output
    restore_mode: ALWAYS_ON
    default_transition_length: 1s

# ================================================================
# Touchscreen (GT911)
# Don't specify the I2C address here, the driver looks for it if the interrupt pin isn't set.
# The interrupt pin on this board is either on the IO expander or not connected at all, 
# so we'll just let the driver scan for the address.
# ================================================================
touchscreen:
  - platform: gt911
    setup_priority: 600
    id: touch_panel
    i2c_id: bus_a
    display: lcd_display
    update_interval: 20ms

    intcfg_pin:
      waveshare_io_ch32v003: wave_io_hub
      number: 2

    reset_pin:
      waveshare_io_ch32v003: wave_io_hub
      number: 1

    # on_touch:
    #   - logger.log:
    #       format: "Touch at %d/%d"
    #       args: [touch.x, touch.y]

# ================================================================
# Display Driver (ST7701S via mipi_rgb)
# ================================================================
display:
  - platform: mipi_rgb
    setup_priority: 750
    model: WAVESHARE-4-480X480
    id: lcd_display
    reset_pin:
      waveshare_io_ch32v003: wave_io_hub
      number: 3
    pixel_mode: 18bit

# ================================================================
# RemoteWebView — renders HA dashboard remotely and streams to display
# ================================================================
remote_webview:
  id: rwv
  display_id: lcd_display
  touchscreen_id: touch_panel
  device_id: mini-screen
  server: "192.168.42.10:8181"   # Replace with your server hostname or IP
  url: "http://hass:8123/cc-screen"   # Replace with your HA dashboard URL
  tile_size: 64
  full_frame_tile_count: 2
  full_frame_area_threshold: 0.30
  full_frame_every: 30
  every_nth_frame: 2
  min_frame_interval: 600
  jpeg_quality: 80
  max_bytes_per_msg: 61440

# ================================================================
# Expose the dashboard URL as a Home Assistant text entity
# ================================================================
text:
  - platform: template
    id: rwv_url
    name: "Dashboard URL"
    optimistic: true
    mode: TEXT
    set_action:
      - lambda: |-
          if (!id(rwv).open_url(std::string(x.c_str()))) {
            id(rwv).set_url(std::string(x.c_str()));
            ESP_LOGI("remote_webview", "URL queued (not connected): %s", x.c_str());
          }

I’ve actually done the exact same thing! Printed an enclosure to fit the waveshare on the wall in a NEMA 2-gang wallbox, works like a charm.

Please note that my PR broke and I’ve resubmitted it. The PR has been merged so this YAML works in the base ESPHome driver in dev now. Will roll into release on next cycle.
I’m publishing yaml updates to GitHub - PedanticAvenger/Waveshare_ESP32-S3-TOUCH-LCD-4: ESPHome config and board docs for the Waveshare_ESP32-S3-TOUCH-LCD-4 (rev. 4.0 currently) as I get more things fully tested. But I’ve got touch fully working (on rev4) and the only thing I haven’t looked at yet is the battery related stuff.
Also note I’ve added some esp-idf parameters which should keep the screen from going crazy when doing OTA.

Also, the setup priorities might not be needed. They ended up in there while I was trying to figure out the root problem with the GT911 touchscreen controller. They aren’t wrong but might not be needed.

Thanks Raymond, works pretty well. Just removed the canbus stuf because it gave me grief. Also don’t seem to need the screen initialisation stuff really.

The default initialization for WAVESHARE-4-480x480 on the ESP32-S3-Touch-LCD-4 (rev 4 at least) was unstable as heck for me as it looks to have been built for the ESP32-S3-Touch-LCD-4B and is slightly different than this. There could be some manufacturing run differences so if it works without then great. I have more boards I’ll be working with soon so will see if they need it like my first batch.

If these boards all behave consistently with my initial boards then I’ll submit the init sequence as a new mipi_rgp model or abandon this. I might have needed the init sequence due to board initialization oddities introduced trying to figure everything else out.

Do you know if there is a way to turn off the powerled (without covering it or destroying it)?

Hello and thanks for your work @PedanticAvenger

even with the last version on your github repo, i had that error

  interrupt_pin: 
    
    [waveshare_io_ch32v003] is an invalid option for [interrupt_pin]. Please check the indentation.
    waveshare_io_ch32v003: wave_io_hub
    number: 2
  reset_pin: 
    waveshare_io_ch32v003: wave_io_hub
    number: 1

Could you please tell me which exact version of esphome docker are you using, that will help me a lot.

thanks by advance

I’m using dev, but it should work with current release. Looks like the external component for the gt911 isn’t getting loaded correctly. Might want to move to dev version (I don’t run docker, I have a esphome builder container for release and I build locally on my machine for dev branch building and testing.). This will be mainstream in the March esphome release so if you want to stay with your current builder update it then and you should be good.

thanks for your answer, i’ll wait for the march release

I’ve just tested with 2026.3.0-dev20260304 version and it works ! Thank again for your work :slight_smile: