Skip to content

Instantly share code, notes, and snippets.

@clydebarrow
Last active April 7, 2024 20:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clydebarrow/ef89e9a93bd44771483b9144ae9042a1 to your computer and use it in GitHub Desktop.
Save clydebarrow/ef89e9a93bd44771483b9144ae9042a1 to your computer and use it in GitHub Desktop.
ESPHome LVGL samples
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml
/wifi.yaml
time:
- platform: sntp
id: time_comp
on_time_sync:
then:
- script.execute: time_update
script:
- id: time_update
then:
- lvgl.indicator.line.update:
id: minute_hand
value: !lambda |-
return id(time_comp).now().minute;
- lvgl.indicator.line.update:
id: hour_hand
value: !lambda |-
auto now = id(time_comp).now();
return std::fmod(now.hour, 12) * 60 + now.minute;
- lvgl.label.update:
id: date_label
text:
format: "%s %2d"
args:
- '(new const char *[12]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"})[id(time_comp).now().month-1]'
- 'id(time_comp).now().day_of_month'
- lvgl.label.update:
id: day_label
text:
format: "%s"
args:
- '(new const char *[7]{"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"})[id(time_comp).now().day_of_week-1]'
font:
- file: "gfonts://Roboto"
id: roboto10
size: 10
bpp: 4
interval:
- interval: 1min
then:
- script.execute: time_update
lvgl:
style_definitions:
- id: date_style
text_font: roboto10
align: center
text_color: 0x000000
bg_opa: cover
radius: 4
pad_all: 2
widgets:
- obj: # Clock container
height: 170
width: 170
align: center
pad_all: 4
widgets:
- meter: # Gradient color arc
height: 100%
width: 100%
align: center
bg_color: 0
scales:
angle_range: 360
rotation: 255
range_from: 0
range_to: 12
ticks:
width: 35
count: 13
length: 8
indicators:
- ticks:
local: true
start_value: 0
end_value: 12
color_start: 0xFF0000
color_end: 0x0000FF
- meter:
height: 100%
width: 100%
align: center
bg_opa: TRANSP
text_color: 0xFFFFFF
scales:
- ticks:
width: 1
count: 61
length: 10
color: 0xFFFFFF
range_from: 0
range_to: 60
angle_range: 360
rotation: 270
indicators:
- line:
id: minute_hand
width: 3
color: 0xE0E0E0
r_mod: -1
-
angle_range: 330
rotation: 300
range_from: 1
range_to: 12
ticks:
width: 1
count: 12
length: 1
major:
stride: 1
width: 4
length: 8
color: 0xC0C0C0
label_gap: 6
- angle_range: 360
rotation: 270
range_from: 0
range_to: 720
indicators:
- line:
id: hour_hand
width: 4
color: 0xA0A0A0
r_mod: -20
- label:
styles: date_style
id: day_label
y: -20
- label:
id: date_label
styles: date_style
y: +20
---
substitutions:
name: indicator
friendly_name: "Indicator LVGL"
esphome:
name: indicator
friendly_name: "${friendly_name}"
name_add_mac_suffix: false
platformio_options:
build_unflags: -Werror=all
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
# Required to get the latest RGB driver
platform_version: 6.3.2
version: 5.0.2
# Required to achieve sufficient PSRAM bandwidth
sdkconfig_options:
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
CONFIG_ESP32S3_DATA_CACHE_64KB: y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
CONFIG_SPIRAM_RODATA: y
wifi: !include wifi.yaml
external_components:
- source: github://clydebarrow/esphome@lvgl
refresh: 10min
components: [lvgl, font, display, ft5x06]
- source: github://pr#5872
refresh: 10min
components: [st7701s]
packages:
lvgl: !include lvgl-package.yaml
psram:
mode: octal
speed: 80MHz
logger:
level: DEBUG
hardware_uart: UART0
output:
- platform: ledc
pin:
number: GPIO45
ignore_strapping_warning: true
id: ledc_gpio4
frequency: 100Hz
light:
- platform: monochromatic
output: ledc_gpio4
name: Display Backlight
id: display_backlight
restore_mode: ALWAYS_ON
spi:
- id: lcd_spi
clk_pin: 41
mosi_pin: 48
i2c:
sda: 39
scl: 40
scan: false
id: bus_a
# I/O expander. Touchscreen RESET and INTERRUPT are not used.
pca9554:
- id: p_c_a
pin_count: 16
address: 0x20
touchscreen:
- platform: ft5x06
id: ft5x06_touchscreen
transform:
mirror_x: true
mirror_y: true
on_release:
then:
- if:
condition: lvgl.is_paused
then:
- light.turn_on: display_backlight
- lvgl.resume:
- lvgl.widget.redraw:
display:
- platform: st7701s
auto_clear_enabled: false
update_interval: never
spi_mode: MODE3
color_order: RGB
dimensions:
width: 480
height: 480
invert_colors: true
transform:
mirror_x: true
mirror_y: true
cs_pin:
pca9554: p_c_a
number: 4
reset_pin:
pca9554: p_c_a
number: 5
de_pin: 18
hsync_pin: 16
vsync_pin: 17
pclk_pin: 21
# Init sequence can be one of the defaults and/or custom sequences.
init_sequence:
- 1 # select canned init sequence number 1
# Custom sequences are an array, first byte is command, the rest are data.
- [ 0xE0, 0x1F ] # Set sunlight readable enhancement
data_pins:
red:
- 4 #r1
- 3 #r2
- 2 #r3
- 1 #r4
- 0 #r5
green:
- 10 #g0
- 9 #g1
- 8 #g2
- 7 #g3
- 6 #g4
- 5 #g5
blue:
- 15 #b1
- 14 #b2
- 13 #b3
- 12 #b4
- 11 #b5
lvgl:
touchscreens: ft5x06_touchscreen
on_idle:
timeout: 120s
then:
- logger.log: "LVGL is idle"
- lvgl.pause:
- light.turn_off:
id: display_backlight
transition_length: 5s
---
# A random collection of LVGL stuff.
substitutions:
light_recessed: "\U000F179B"
wall_sconce_round: "\U000F0748"
gas_burner: "\U000F1A1B"
home_icon: "\U000F02DC"
menu_left: "\U000F0A02"
menu_right: "\U000F035F"
close: "\U000F0156"
delete: "\U000F01B4"
backspace: "\U000F006E"
check: "\U000F012C"
arrow_down: "\U000F004B"
esphome:
on_boot:
then:
- light.turn_on:
id: lv_light
brightness: 100%
red: 100%
green: 0
blue: 0
#sensor:
# - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
# name: "WiFi Signal dB"
# id: wifi_signal_db
# update_interval: 20s
# entity_category: "diagnostic"
# on_value:
# then:
# - lvgl.label.update:
# id: wifi_label
# text: !lambda |-
# static char buf[10];
# snprintf(buf, 10, "%.0fdBm", id(wifi_signal_db).get_state());
# return buf;
light:
- platform: lvgl
name: LVGL LED
id: lv_light
led: lv_led
effects:
- pulse:
name: "Slow Pulse"
# transition_length: 1s # defaults to 1s
update_interval: 2s
- strobe:
- strobe:
name: Strobe Effect With Custom Values
colors:
- state: true
brightness: 100%
red: 100%
green: 90%
blue: 0%
duration: 500ms
- state: false
duration: 250ms
- state: true
brightness: 100%
red: 0%
green: 100%
blue: 0%
duration: 500ms
number:
- platform: lvgl
widget: lv_slider
id: lvgl_slider_sensor
name: LVGL Slider
- platform: lvgl
widget: lv_arc
id: lvgl_arc_sensor
name: LVGL Arc
- platform: lvgl
widget: lv_bar
id: lvgl_bar_sensor
name: LVGL Bar
# on_value:
# then:
# - if:
# condition:
# number.in_range:
# id: lvgl_arc_sensor
# below: 50
# then:
# - lvgl.slider.update:
# id: lv_slider
# knob:
# bg_color: 0xFF0000
# - lvgl.img.update:
# id: lv_img
# src: cat_image
# else:
# - lvgl.slider.update:
# id: lv_slider
# knob:
# bg_color: 0x00FF00
# - lvgl.img.update:
# id: lv_img
# src: dog_img
binary_sensor:
- platform: lvgl
name: Pressbutton
widget: lv_page1_button
publish_initial_state: true
- platform: lvgl
name: ButtonMatrix button
widget: button_a
- platform: lvgl
id: switch_d
name: Matrix switch D
widget: button_d
on_click:
then:
- lvgl.page.previous:
animation: move_right
time: 600ms
- platform: lvgl
id: button_checker
name: LVGL button
widget: lv_button
on_state:
then:
- lvgl.checkbox.update:
id: lv_checkbox
state:
checked: !lambda return x;
text: Unchecked
- platform: lvgl
name: LVGL checkbox
widget: lv_checkbox
font:
- file: "gfonts://Roboto"
id: roboto20
size: 20
extras:
- file: 'materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B",
"\U0000f0ed",
"\U000F006E",
"\U000F012C",
"\U000F179B",
"\U000F0748",
"\U000F1A1B",
"\U000F02DC",
"\U000F0A02",
"\U000F035F",
"\U000F0156",
"\U000F0C5F",
"\U000f0084",
"\U000f0091",
]
- file: "gfonts://Roboto"
id: roboto10
size: 10
bpp: 4
extras:
- file: 'materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B",
"\U0000f0ed",
"\U000F006E",
"\U000F012C",
"\U000F179B",
"\U000F0748",
"\U000F1A1B",
"\U000F02DC",
"\U000F0A02",
"\U000F035F",
"\U000F0156",
"\U000F0C5F",
"\U000f0084",
"\U000f0091",
]
- file: "gfonts://Helvetica"
id: helv20
bpp: 2
size: 20
extras:
- file: 'materialdesignicons-webfont.ttf'
glyphs: [
"\U000F004B",
"\U0000f0ed",
"\U000F006E",
"\U000F012C",
"\U000F179B",
"\U000F0748",
"\U000F1A1B",
"\U000F02DC",
"\U000F0A02",
"\U000F035F",
"\U000F0156",
"\U000F0C5F",
"\U000f0084",
"\U000f0091",
]
- file: 'gfonts://Roboto Condensed'
id: roboto42
size: 28
bpp: 4
glyphs: 'aAbBcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ'
- file: montserrat.ttf
id: mont20
glyphs: 'aAbBDcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ0'
size: 20
bpp: 4
image:
- file: cat.jpg
id: cat_image
resize: 100x100
type: RGB565
- file: cat.jpg
id: disp_bg
resize: 200x200
type: RGB565
- file: dog.jpg
id: dog_img
resize: 100x100
type: RGB565
color:
- id: color_back
hex: FF0000
- id: color_blue
hex: 0000FF
select:
- platform: lvgl
widget: lv_dropdown
name: LVGL Dropdown
- platform: lvgl
widget: lv_roller
name: LVGL Roller
switch:
- platform: lvgl
name: Button switch
widget: lv_button
on_turn_on:
then:
- lvgl.widget.disable: lv_arc
- lvgl.widget.hide: button_c
on_turn_off:
then:
- lvgl.widget.show: button_c
- lvgl.widget.enable: lv_arc
- platform: lvgl
name: Matrix switch
widget: button_a
- platform: lvgl
name: Switch switch
widget: lv_switch
lvgl:
buffer_size: 100%
#default_font: roboto20
msgboxes:
- id: message_box
close_button: true
title: Messagebox
body:
text: This is a sample messagebox
bg_color: 0x808080
buttons:
- id: msgbox_close
text: close
- id: msgbox_apply
text: "Close"
on_click:
then:
- lvgl.widget.hide: message_box
on_idle:
- timeout: 10s
then:
- logger.log: idle timeout
- if:
condition:
lvgl.is_idle:
timeout: 5s
then:
- logger.log: LVGL is idle
- timeout: 15s
then:
- logger.log: idle 15s timeout
#- lvgl.pause:
#- light.turn_off:
# id: display_backlight
# transition_length: 5s
log_level: INFO
color_depth: 16
disp_bg_image: disp_bg
bg_opa: TRANSP
style_definitions:
- id: style_line
line_color: color_blue
line_width: 8
line_rounded: true
- id: date_style
text_font: roboto10
align: center
text_color: 0x000000
bg_opa: cover
radius: 4
pad_all: 2
theme:
arc:
scroll_on_focus: true
group: general
slider:
scroll_on_focus: true
group: general
btn:
text_font: roboto10
scroll_on_focus: true
group: general
border_width: 2
border_side: left
outline_pad: 6
text_color: 0x000000
checked:
text_color: 0xFF0000
pressed:
border_color: 0xFF0000
text_color: 0xFFFFFF
focused:
border_color: 0x00FF00
checkbox:
border_color: 0xC0C0C0
indicator:
text_font: montserrat_14
checked:
bg_color: 0xC0C0C0
top_layer:
text_font: helv20
widgets:
btnmatrix:
width: 100%
height: 10%
align: bottom_mid
pad_all: 5
outline_width: 0
shadow_width: 0
id: top_layer
rows:
- buttons:
- id: top_prev
text: "${menu_left}"
width: 1
on_click:
then:
lvgl.page.previous:
- id: top_home
text: "${home_icon}"
width: 1
on_click:
then:
lvgl.page.show: main_page
- id: top_next
text: "${menu_right}"
width: 1
on_click:
then:
lvgl.page.next:
page_wrap: true
pages:
- id: main_page
skip: true
layout: flex
width: 100%
bg_color: 0x808080
bg_opa: TRANSP
pad_all: 10
widgets:
- btn:
text_color: 0x000000
pressed:
border_color: color_blue
text_color: 0xFFFFFF
checkable: true
id: lv_button
pad_left: 20px
pad_top: 20px
pad_bottom: 8px
pad_right: 8px
widgets:
- img:
src: cat_image
bg_opa: TRANSP
align: bottom_right
- label:
styles: date_style
align: bottom_right
x: -20
y: -20
text: "Label text"
long_mode: dot
id: wifi_label
- checkbox:
scroll_on_focus: true
group: general
id: lv_checkbox
align: center
text: Checkbox
- switch:
id: lv_switch
- bar:
id: lv_bar
width: 50px
on_value:
then:
- logger.log:
format: "Bar value is %f"
args: [ x ]
- arc:
id: lv_arc
adjustable: true
#on_release:
#then:
#- lambda: |-
#esph_log_d("lvgl", "on_release %f", x);
on_value:
then:
- logger.log:
format: "Arc value is %f"
args: [ x ]
group: general
scroll_on_focus: true
value: 75
min_value: 1
max_value: 100
arc_color: 0xFF0000
indicator:
arc_color: 0xF000FF
pressed:
arc_color: 0xFFFF00
focused:
arc_color: 0x808080
- slider:
knob:
focused:
bg_color: 0x808080
group: general
scroll_on_focus: true
id: lv_slider
height: 30%
align: center
width: 20
min_value: 0
max_value: 50
on_value:
- logger.log:
format: "Value on_value: %.0f"
args: [ 'x' ]
on_release:
- logger.log:
format: "Value on_release: %.0f"
args: [ 'x' ]
- dropdown:
indicator:
text_font: helv20
id: lv_dropdown
options:
- First
- Second
- Third
selected_index: 2
dir: top
symbol: ${arrow_down}
dropdown_list:
bg_color: color_blue
text_color: 0x800000
- label:
text_font: helv20
outline_width: 1
outline_color: 0xFFFFFF
text: "XX \U000f0084 \U0000f0ed XX" #mdi-battery-charging
- label:
outline_width: 1
outline_color: 0xFFFFFF
text_font: montserrat_20
text: 'aAbBDcCxXyYzZáÁéÉíÍóÓőŐúÚűŰ0'
- id: page_1
layout: flex
pad_all: 10
widgets:
- obj:
width: 100%
height: 50px
bg_color: 0xFFFFFF
outline_width: 0
border_width: 2
border_color: 0x000000
- label:
height: size_content
width: 150px
long_mode: scroll_circular
text: 'Page 1 label with very long text wrapping lines'
- btn:
on_click:
then:
- logger.log: Button clicked
- lvgl.widget.show: message_box
width: size_content
id: change_btnm
widgets:
- label:
width: 100px
long_mode: scroll
text: Disable button matrix
on_press:
then:
- lvgl.widget.update:
id: b_matrix
state:
disabled: true
- lambda: |-
static const char * new_matrix[] = {"A","bbb","\n","ccc",LV_SYMBOL_PREV,"\n","E",NULL};
lv_btnmatrix_set_map(id(b_matrix)->obj, new_matrix);
text_color: 0x000000
align: center
- btn:
bg_color: 0x80FF00
width: size_content
id: lv_page1_button0
on_click:
then:
- lvgl.pause:
show_snow: true
widgets:
- label:
bg_color: 0x80ffff
width: 100px
long_mode: scroll
text: Disable button matrix
- btn:
bg_color: 0xFF80FF
width: size_content
id: lv_page1_button1
on_click:
then:
- lvgl.widget.show:
id: b_matrix
widgets:
- label:
text_font: helv20
bg_color: 0xFF80FF
width: 100px
long_mode: scroll
text: Disable button matrix
- btn:
on_click:
then:
- logger.log: Button clicked
- lvgl.widget.show: message_box
width: size_content
id: lv_page1_button
widgets:
- label:
width: 100px
long_mode: scroll
text: "Show message box"
- roller:
id: lv_roller
options:
- Jan
- Feb
- Mar
- Apr
- May
- Jun
- Jul
- Aug
- Sep
- Oct
- Nov
- Dec
selected_index: 2
- led:
id: lv_led
color: 0xFF0000
brightness: 50%
on_long_press:
then:
- lvgl.page.show: main_page
- btnmatrix:
items:
checked:
bg_color: 0xFFFF00
id: b_matrix
rows:
- buttons:
- id: button_a
text: ${home_icon}
width: 2
control:
checkable: true
- id: button_b
text: B
width: 1
on_click:
then:
- lvgl.page.previous:
control:
hidden: false
- buttons:
- id: button_c
text: C
control:
checkable: false
- id: button_d
text: ${menu_left}
on_long_press:
then:
logger.log: Long pressed
on_long_press_repeat:
then:
logger.log: Long pressed repeated
- buttons:
- id: button_e
text: E
width: 2
control:
checked: true
- id: page_2
layout: flex
widgets:
- line:
styles: style_line
align: center
points:
- 5, 5
- 70, 70
- 120, 10
- 180, 60
- 240, 10
- obj:
width: size_content
height: size_content
widgets:
- btnmatrix:
align: BOTTOM_MID
items:
disabled:
bg_color: 0x00FFFF
align: center
id: keypad_matrix
disabled:
bg_color: 0x00FF00
rows:
- buttons:
- text: 1
key_code: "1"
- text: 2
key_code: "2"
- text: 3
- buttons:
- text: 4
- text: 5
- text: 6
key_code: 0x36
- buttons:
- text: 7
- text: 8
- text: 9
- buttons:
- text: ${backspace}
key_code: "*"
- text: 0
- text: ${check}
key_code: '#'
- label:
text_align: center
height: 100
id: pin_code
align: TOP_MID
- img:
x: 10
y: 200
src: cat_image
id: img_id
radius: 11
clip_corner: true
- label:
id: lbl_cathome
align_to:
id: img_id
x: 0
y: 10
align: OUT_BOTTOM_MID
text: 'Home'
text_color: 0x00FF00
- btn:
x: 100
y: 200
widgets:
- label:
text: Disable keypad
on_press:
then:
- lvgl.btnmatrix.update:
id: keypad_matrix
state:
disabled: true
items:
bg_color: 0x0000ff
- lvgl.spinner.update:
id: spinner_id
indicator:
arc_color: 0x0000FF
- btn:
x: 100
y: 240
widgets:
- label:
text: Enable keypad
on_press:
then:
- lvgl.btnmatrix.update:
id: keypad_matrix
state:
disabled: false
items:
bg_color: 0xf0f0f0
- spinner:
id: spinner_id
indicator:
arc_color: 0xF000FF
x: 200
y: 400
height: 100
width: 100
spin_time: 2s
arc_length: 60deg
- id: page_3
layout: flex
widgets:
- obj:
scrollbar_mode: "off"
pad_all: 0
width: 230
max_width: 230
height: 40
bg_opa: 0
border_width: 1
widgets:
- checkbox:
id: chk_stat_6
text: "Pretty long blah blah blah to go wider than obj width"
x: 5
y: 5
key_collector:
- id: pincode_reader
source_id: keypad_matrix
min_length: 4
max_length: 4
end_keys: "#"
end_key_required: true
back_keys: "*"
allowed_keys: "0123456789S"
timeout: 10s
on_progress:
- lvgl.label.update:
id: pin_code
text: !lambda 'return x.c_str();'
border_color: 0
- logger.log:
format: "input progress: '%s', started by '%c'"
args: [ 'x.c_str()', "(start == 0 ? '~' : start)" ]
on_result:
- logger.log:
format: "input result: '%s', started by '%c', ended by '%c'"
args: [ 'x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)" ]
- lambda: id(text_sensor).publish_state(x);
on_timeout:
- logger.log:
format: "input timeout: '%s', started by '%c'"
args: [ 'x.c_str()', "(start == 0 ? '~' : start)" ]
text:
- platform: template
id: text_sensor
name: "Template text"
optimistic: true
#min_length: 0
#max_length: 100
mode: text
This file has been truncated, but you can view the full file.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

@clowrey
Copy link

clowrey commented Mar 22, 2024

Ooooh this example is amazing!! I hope this is linked to from the final documentation for this component :) I've got my power meter display in a mostly functional state :) just added multiple pages after finding this example. I just set it to change to the next page upon any touchscreen touch. Is there a way to detect swipes and have the pages swipe left right? not actually necessary would just look cool..
esp32-2424s012_1 (Small)

@clowrey
Copy link

clowrey commented Mar 22, 2024

And just wanted to thank you again! - this LVGL component is revolutionary for ESPhome..

@clydebarrow
Copy link
Author

Nice! Swipes and other gestures are likely to come, plenty of other stuff to do yet.

@Expaso
Copy link

Expaso commented Apr 7, 2024

@clowrey , @clydebarrow

I build this with modified versions of the LVGL compoennt (and a modified display driver):

Check https://www.youtube.com/shorts/McAPOdtXWlw and https://youtu.be/RJwAdNKXpX8?si=0WGuvMw0ej2FJtB9&t=57

I am more then happy to help if you like. My work was mostly around optimizing Touch-interfacing icm LVGL, and framerate optimizations to make LVGL lightning fast.

Totally perfect for rich touchscreen interfaces with all it's bells and whisles, swiped, onscreen-keyboards etc.

@clydebarrow
Copy link
Author

PRs are welcome. Raise against my LVGL branch: https://github.com/clydebarrow/esphome/tree/lvgl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment