GUITION 4" 480x480 ESP32-S3-4848S040 Smart Display with LVGL

https://www.aliexpress.us/item/3256806618705053.html

I have been seeing these a lot of Ali lately for around $27. They check all the boxes for me

  • Fast CPU
  • Lots of memory
  • Great format
  • Easy wall mount as a wall switch
  • 120v relay included
  • Does not require programming another internal device for LVGL like NSPanel

Many thanks to everybody on the ESPhome discussion board for helping me get this working. I initially tested it with the openHASP project. This works very well but you a limited to what you can do with it.

I really like the infinite flexibility of ESPhome. I think it would be easy to build a complete automation system on the is screen without Homeassistant or anything else.

I have concentrated on building a simple light switch with it although it would be easy to build a multi page panel. I used the new (BETA) LVGL library. The buttons are on a flex grid so if you remove one the other buttons float to fill in the space. This is much easier then locating the button with exact pixial location.

Over all I think this is a huge feature for ESPhome!

(05/15/2024) - This is the code for the demo button panel above. It has been updated with the fixes from @rocob below.

substitutions:
  lightbulb:     "\U000F0335"
  ceiling_light: "\U000F0769"
  floor_lamp:    "\U000F08DD"
  string_lights: "\U000F12BA"

#-------------------------------------------
# ESPHOME PLATFORM SETTINGS
#-------------------------------------------
esphome:
  name: 480screen
  friendly_name: 480screen
  platformio_options:
    board_build.flash_mode: dio

#-------------------------------------------
# ESP MCU SETTINGS for
# Guition ESP32-S3-4848S040
#-------------------------------------------
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_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y

psram:
  mode: octal
  speed: 80MHz

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret encryption_key 

ota:
  password: !secret ota_password

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

web_server:
  port: 80  
  
external_components:
 - source: github://clydebarrow/esphome@lvgl_original
   refresh: 10min
   components: [ lvgl ]
   
time:
  - platform: sntp
    id: sntp_time
    timezone: America/New_York
    servers:
     - 0.pool.ntp.org
     - 1.pool.ntp.org
     - 2.pool.ntp.org
    on_time_sync:
      - script.execute: time_update
    on_time:
      - minutes: '*'
        seconds: 0
        then:
          - script.execute: time_update

script:
  - id: time_update
    then:
      - lvgl.label.update:
          id: display_time
          text: !lambda |-
            static char time_buf[17];
            auto now = id(sntp_time).now();            
            bool is_pm = now.hour >= 12;
            int hour_12 = now.hour % 12;
            if (hour_12 == 0) {
                hour_12 = 12; // 12 AM/PM should be displayed as 12, not 0
            }
            snprintf(time_buf, sizeof(time_buf), "%02d:%02d%s", hour_12, now.minute, is_pm ? "pm" : "am");
            return time_buf; 

#-------------------------------------------
# Buttons
#-------------------------------------------
switch:  
  - platform: lvgl
    name: Kitchen
    widget: lv_button_1
    output_id: internal_relay_1
    on_turn_on:
      then:
        - lvgl.widget.update:
            id: lv_button_1_icon
            text_color: 0xFFFF00
    on_turn_off:
      then:
        - lvgl.widget.update:
            id: lv_button_1_icon
            text_color: 0xB6B6B6
  - platform: lvgl
    name: Outdoor
    widget: lv_button_2
    on_turn_on:
      then:
        - lvgl.widget.update:
            id: lv_button_2_icon
            text_color: 0xFFFF00
    on_turn_off:
      then:
        - lvgl.widget.update:
            id: lv_button_2_icon
            text_color: 0xB6B6B6            
       
#-------------------------------------------
# lvgl Buttons
#-------------------------------------------    
lvgl:
  displays:
    - display_id: my_display
  touchscreens:
    - touchscreen_id: my_touchscreen

  style_definitions:
    - id: style_line
      line_color: 0x0000FF
      line_width: 8
      line_rounded: true
    - id: date_style
      text_font: roboto24
      align: center
      text_color: 0x333333
      bg_opa: cover
      radius: 4
      pad_all: 2

  theme:
    btn:
      text_font: roboto24
      scroll_on_focus: true
      group: general
      radius: 25
      width: 150
      height: 109
      pad_left: 10px
      pad_top: 10px
      pad_bottom: 10px
      pad_right: 10px
      shadow_width: 0
      bg_color: 0x313131
      text_color: 0xB6B6B6
      checked:
        bg_color: 0xCC5E14
        text_color: 0xB6B6B6

  page_wrap: true
  pages:
    - id: main_page
      skip: true
      layout: flex
      flex_flow: COLUMN_WRAP
      width: 100%
      bg_color: 0x000000
      bg_opa: cover
      pad_all: 5
      widgets:
        - btn:
            height: 223
            checkable: true
            id: lv_button_1
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_1_icon
              - label:
                  align: bottom_left
                  text: "Kitchen"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_2
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $string_lights
                  id: lv_button_2_icon
              - label:
                  align: bottom_left
                  text: "Outdoor"
                  long_mode: dot              
        - btn:
            checkable: true
            id: lv_button_3
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $floor_lamp
                  id: lv_button_3_icon
              - label:
                  align: bottom_left
                  text: "Living Room"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_4
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_4_icon
              - label:
                  align: bottom_left
                  text: "Accent"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_5
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_5_icon
              - label:
                  align: bottom_left
                  text: "Bed Room"
                  long_mode: dot              
        - btn:
            checkable: true
            id: lv_button_6
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_6_icon
              - label:
                  id: display_time
                  align: bottom_left
                  text: "00:00am"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_7
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_7_icon
              - label:
                  align: bottom_left
                  text: "Fan"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_8
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_8_icon
              - label:
                  align: bottom_left
                  text: "Timer"
                  long_mode: dot              
        - btn:
            checkable: true
            id: lv_button_9
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_9_icon
              - label:
                  align: bottom_left
                  text: "Lamp"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_10
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_10_icon
              - label:
                  align: bottom_left
                  text: "Wall Spot"
                  long_mode: dot
        - btn:
            checkable: true
            id: lv_button_11
            widgets:
              - label:
                  text_font: light32
                  align: top_left
                  text: $lightbulb
                  id: lv_button_11_icon
              - label:
                  align: bottom_left
                  text: "Driveway"
                  long_mode: dot              

#-------------------------------------------
# Internal outputs
#-------------------------------------------
output:
    # Backlight LED
  - platform: ledc
    pin: GPIO38
    id: GPIO38
    frequency: 100Hz
    
    # Built in 240v relay
  - id: internal_relay_1
    platform: gpio
    pin: 40

#-------------------------------------------
# LIGHTS
#-------------------------------------------
light:
  - platform: monochromatic
    output: GPIO38
    name: Backlight
    id: backlight
    restore_mode: ALWAYS_ON

#-------------------------------------------
# Graphicsd and Fonts
#-------------------------------------------
image:
  - file: https://esphome.io/_images/logo.png
    id: boot_logo
    resize: 200x200
    type: RGB565
    
font:
  - file: "gfonts://Roboto"
    id: roboto24
    size: 24
    bpp: 4
    extras:
      - file: 'fonts/materialdesignicons-webfont.ttf' # http://materialdesignicons.com/cdn/7.4.47/ 
        glyphs: [
          "\U000F004B",
          "\U0000f0ed",
          "\U000F006E",
          "\U000F012C",
          "\U000F179B",
          "\U000F0748",
          "\U000F1A1B",
          "\U000F02DC",
          "\U000F0A02",
          "\U000F035F",
          "\U000F0156",
          "\U000F0C5F", 
          "\U000f0084",
          "\U000f0091",
          ]
  - file: 'fonts/materialdesignicons-webfont.ttf' # http://materialdesignicons.com/cdn/7.4.47/ 
    id: weather70
    size: 70
    bpp: 4
    glyphs: &mdi-weather-glyphs
      - "\U000F0590" # mdi-weather-cloudy
      - "\U000F0F2F" # mdi-weather-cloudy-alert
      - "\U000F0E6E" # mdi-weather-cloudy-arrow-right
      - "\U000F0591" # mdi-weather-fog
      - "\U000F0592" # mdi-weather-hail
      - "\U000F0F30" # mdi-weather-hazy
      - "\U000F0898" # mdi-weather-hurricane
      - "\U000F0593" # mdi-weather-lightning
      - "\U000F067E" # mdi-weather-lightning-rainy
      - "\U000F0594" # mdi-weather-clear-night
      - "\U000F0F31" # mdi-weather-night-partly-cloudy
      - "\U000F0595" # mdi-weather-partly-cloudy
      - "\U000F0F32" # mdi-weather-partly-lightning
      - "\U000F0F33" # mdi-weather-partly-rainy
      - "\U000F0F34" # mdi-weather-partly-snowy
      - "\U000F0F35" # mdi-weather-partly-snowy-rainy
      - "\U000F0596" # mdi-weather-pouring
      - "\U000F0597" # mdi-weather-rainy
      - "\U000F0598" # mdi-weather-snowy
      - "\U000F0F36" # mdi-weather-snowy-heavy
      - "\U000F067F" # mdi-weather-snowy-rainy
      - "\U000F0599" # mdi-weather-sunny
      - "\U000F0F37" # mdi-weather-sunny-alert
      - "\U000F14E4" # mdi-weather-sunny-off
      - "\U000F059A" # mdi-weather-sunset
      - "\U000F059B" # mdi-weather-sunset-down
      - "\U000F059C" # mdi-weather-sunset-up
      - "\U000F0F38" # mdi-weather-tornado
      - "\U000F059D" # mdi-weather-windy
      - "\U000F059E" # mdi-weather-windy-variant

  - file: 'fonts/materialdesignicons-webfont.ttf' # http://materialdesignicons.com/cdn/7.4.47/ 
    id: light32
    size: 32
    bpp: 4
    glyphs: [
      "\U000F0335", # mdi-lightbulb
      "\U000F0769", # mdi-ceiling-light
      "\U000F08DD", # mdi-floor-lamp
      "\U000F12BA", # mdi-string-lights
      ]          
      
  - file: "gfonts://Roboto"
    id: roboto10
    size: 10
    bpp: 4          

#-------------------------------------------
# Touchscreen gt911 i2c
#-------------------------------------------
i2c:
  - id: bus_a
    sda: GPIO19
    scl: GPIO45
    #frequency: 100kHz
    
touchscreen:
  platform: gt911
  transform:
    mirror_x: false
    mirror_y: false
  id: my_touchscreen
  display: my_display

  on_touch:
    - logger.log:
          format: Touch at (%d, %d)
          args: [touch.x, touch.y]
    - lambda: |-
          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
              touch.x,
              touch.y,
              touch.x_raw,
              touch.y_raw
              );

#-------------------------------------------
# Display st7701s spi
#-------------------------------------------
spi:
  - id: lcd_spi
    clk_pin: GPIO48
    mosi_pin: GPIO47
    
display:
  - platform: st7701s
    id: my_display
    update_interval: never
    auto_clear_enabled: True
    data_rate: 2MHz
    spi_mode: MODE3
    color_order: RGB
    invert_colors: false
    dimensions:
      width: 480
      height: 480
    transform:
      mirror_x: false
      mirror_y: false
    cs_pin: 39
      # reset not defined 
    de_pin: 18
    hsync_pin: 16
    vsync_pin: 17
    pclk_pin: 21
    init_sequence: 
      - 1
      - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x10] # CMD2_BKSEL_BK0
      - [0xCD, 0x00] # disable MDT flag
    pclk_frequency: 12MHz
    pclk_inverted: false
    data_pins:
      red:
        - 11         # R1
        - 12         # R2
        - 13         # R3
        - 14         # R4
        - 0          # R5
      green:
        - 8          # G0
        - 20         # G1
        - 3          # G2
        - 46         # G3
        - 9          # G4
        - 10         # G5 
      blue:
        - 4          # B1
        - 5          # B2
        - 6          # B3
        - 7          # B4
        - 15         # B5


3 Likes

Only for some places. It wouldn’t fit in half the world.

Nice though. I wonder if openhasp would work?

This is a DIY forum and you do have to be a little bit creative to mount these. :grinning: I’m in the USA and I used this box from Ali to mount it in the drywall walls we have here.

https://www.aliexpress.us/item/2251832772595217.html

Works great!

There are other types of boxes you can use in pretty much any other part of the world to get this wall mounted. It’s much better then trying to make a 3d printed case for other types of ESP32 screens that have no cases included. This screen comes apart so you can screw it to the wall and the screen hides the screws!

It does work well with openHASP. I tried that first before ESPhome. I had to build my own image but it worked fine. I really like the simplicity of programing but the integration to Home assistant is messy compared to ESPhome and the ESPhome version of LVGL (newer) is much better and way more flexible. It supports FLEX grids!

This configuration is incorrect because with this you have only achieved a color depth of 4 bits for the colors red and blue. The red and blue pins must remain as originally connected (in the correct order). It is only necessary to change the MDT flag to log 0 (command 0xCD). See https://focuslcds.com/wp-content/uploads/Drivers/ST7701S.pdf on page 276 and page 73. There is one more command to be given before this command. See the patched code section below also with the pins reversed back.

Original init sequence is https://github.com/esphome/esphome/blob/1f29023c92e8fa86ba15999d65130e213de779ff/esphome/components/st7701s/init_sequences.py, command 0xCD is in line 20 and command 0x3A is in line 137.

init_sequence: 
  - 1
  #- [0x3A, 0x50] # 16 bit pixel format. (wrong)
  #- [0x3A, 0x60] # 18 bit pixel format. (this is default configuration. No need to apply)
  - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x10] # CMD2_BKSEL_BK0
  - [0xCD, 0x00] # disable MDT flag
pclk_frequency: 12MHz
pclk_inverted: false
data_pins:
  red:
    - 11         # R1
    - 12         # R2
    - 13         # R3
    - 14         # R4
    - 0          # R5
  green:
    - 8          # G0
    - 20         # G1
    - 3          # G2
    - 46         # G3
    - 9          # G4
    - 10         # G5 
  blue:
    - 4          # B1
    - 5          # B2
    - 6          # B3
    - 7          # B4
    - 15         # B5
lambda: !lambda |-

I can post the full code later.

1 Like

Nice. I’ll look for one that I can put in an existing light socket, rather than a square 86x86 one. Thanks for the pointer.

Thanks @rocob it taken many iterations to get the code right for this little screen. I updated the OP with your changes.

1 Like

Here’s the result on mine:

1 Like

Hi @andrew_NH , could you let me have a pic of this when it’s taken apart?

And I’d be interested to see how do the back part is. The Ali link shows options for 1 relay it 3 relays. I am wondering how big it all is.

I assume in this day and are that it will work on 230v. Any info on this?

Thanks in advance.

Here you can find how to disassemble it and the GPIO for the relay.
The board for 1 relay and 3 relays is the same size.
I have both and they work very nicely in openhasp including relay control.

Many thanks @pepe59

@andrew_NH
I’ve created a second page and I’d like to use a button to go to the next page. Do you know the name of the action to call for this?

I found the answer here in the preview doc:

lvgl.page.next, lvgl.page.previous

This action changes the page to the next/previous based on the configuration (pages with their skip option enabled are…skipped). Page changes will wrap around at the end.

1 Like

Or lvgl.page.show

2 Likes

The LVGL stuff is in BETA at the moment so you need to use the BETA ESPHome documentation.

This will get you going

3 Likes

More pictures here: https://github.com/HASwitchPlate/openHASP/issues/603

2 Likes

Thank you.

keep up the good work!!!
is there a way to turn off screen when there inactivity for 30 seconds and turn it back on by touching the screen?

I’m forwarded the answer i received from @agillis GitHub

Yes at some point I’ll start adding more features to this code. In the mean time the documentation from ESPHome has information on this subject. Check out the sections “Turn off screen when idle” and also “Prevent burn-in of LCD”

1 Like