I’m trying to build a device using CrowPanel 5.0 that will allow me to control two pumps based on temperature sensors.
Sadly, I’m unable to get stable LVGL results, as the screen is jumping every time the sensor value changes.
I’ve searched over the forum and on ESPHome docs, even tried using AI to get stable, non-jumping content, but even I’m not using any animations or graphics I’m still getting those jumps.
Has anyone successfully used CrowPanels from Elecrow with ESPHome? Are there any special settings I should apply in esp32, esphome, psram sections, or in the display?
Below is my entire yaml code:
substitutions:
device_name: thermo
device_friendly_name: Termostat
color_grey: "0xa5a5a5"
color_warning: "0xd2691e"
color_dark_grey: "0x282828"
color_darker_grey: "0x212121"
color_accent: "0x18bcf2"
color_white: "0xFFFFFF"
color_black: "0x000000"
# Material Design Icons
mdi_wifi: "\U000F05A9"
mdi_wifi_off: "\U000F05AA"
mdi_cog: "\U000F0493"
mdi_home: "\U000F02DC"
mdi_plus: "\U000F11EC"
mdi_minus: "\U000F1639"
mdi_delta: "\U000F01C2"
esphome:
name: ${device_name}
friendly_name: ${device_friendly_name}
platformio_options:
board_build.esp-idf.memory_type: qio_opi
board_build.flash_mode: dio
on_boot:
priority: 600
then:
#- delay: 1s
#- component.update: temp_cwu
- delay: 1s
- script.execute: turn_on_screen_light
- delay: 3s
- script.execute: ui_show
esp32:
#board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
sdkconfig_options:
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
CONFIG_ESP32S3_DATA_CACHE_64KB: y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
CONFIG_SPIRAM_RODATA: y
psram:
mode: octal
speed: 80MHz
logger:
# Enable Home Assistant API
api:
encryption:
key: "xxx"
on_client_connected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.label.update:
id: ha_status_icon_label
text_color: ${color_white}
text: ${mdi_wifi}
- script.execute: update_date_time_label
on_client_disconnected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.label.update:
id: ha_status_icon_label
text_color: ${color_warning}
text: ${mdi_wifi_off}
- lvgl.label.update:
id: date_time_label
text: "Disconnected"
ota:
- platform: esphome
password: "xxx"
wifi:
networks:
- ssid: xxx
password: xxx
- ssid: xxx
password: xxx
ap:
ssid: "Thermo Hotspot"
password: "xxx"
captive_portal:
output:
- platform: ledc
pin: 2
frequency: 1220
id: backlight_pwm
light:
- platform: monochromatic # Define a monochromatic, dimmable light for the backlight
output: backlight_pwm
name: ${device_name} Display Backlight
id: backlight
icon: mdi:brightness-percent
default_transition_length: 500ms
restore_mode: RESTORE_AND_ON
# on_turn_on:
# then:
# - script.execute: turn_on_screen_light
on_turn_off:
then:
- lvgl.pause:
display:
- platform: rpi_dpi_rgb
id: main_display
color_order: RGB
invert_colors: true
update_interval: never
auto_clear_enabled: false # takes 2.8 seconds to clear the display
#pclk_frequency: 15MHz # Elecrow: cfg.freq_write = 15000000 :contentReference[oaicite:4]{index=4}
pclk_inverted: true # Elecrow: cfg.pclk_active_neg = 1 :contentReference[oaicite:5]{index=5}
hsync_front_porch: 8 # Elecrow :contentReference[oaicite:6]{index=6}
hsync_pulse_width: 4 # Elecrow :contentReference[oaicite:7]{index=7}
hsync_back_porch: 43 # Elecrow :contentReference[oaicite:8]{index=8}
vsync_front_porch: 8 # Elecrow :contentReference[oaicite:9]{index=9}
vsync_pulse_width: 4 # Elecrow :contentReference[oaicite:10]{index=10}
vsync_back_porch: 12 # Elecrow :contentReference[oaicite:11]{index=11}
dimensions:
width: 800
height: 480
de_pin: 40
hsync_pin: 39
vsync_pin: 41
pclk_pin: 0
pclk_frequency: 12MHz
data_pins:
red: [45, 48, 47, 21, 14]
green: [5, 6, 7, 15, 16, 4]
blue: [8, 3, 46, 9, 1]
i2c:
sda: GPIO19
scl: GPIO20
scan: true
one_wire:
- platform: gpio
pin: GPIO38
# DS18B20 sensors
sensor:
- platform: dallas_temp
id: temp_cwu
name: "Temperatura CWU"
address: 0xc80661d4465cae28
resolution: 11
update_interval: 10s
unit_of_measurement: "°C"
icon: "mdi:thermometer-water"
device_class: "temperature"
state_class: "measurement"
on_value:
- lvgl.label.update:
id: val_text
text:
format: "%.1f °C"
args: [ 'x' ]
switch:
- platform: gpio
pin: GPIO43
id: relay_cwu
name: "Pompa CWU"
- platform: gpio
pin: GPIO44
id: relay_co
name: "Pompa CO"
number:
# Display Backlight Brightness Adjust
- platform: template
name: "BackLight Brightness"
optimistic: true
id: backlight_brightness
icon: mdi:brightness-percent
unit_of_measurement: "%"
initial_value: 80
restore_value: true
min_value: 5
max_value: 100
step: 5
mode: slider
# Screen Timeout Adjust
- platform: template
name: "Screen Timeout"
optimistic: true
id: screen_timeout
icon: mdi:timer
unit_of_measurement: "s"
initial_value: 300
restore_value: true
min_value: 10
max_value: 300
step: 5
mode: slider
- platform: template
name: "CWU: delta min"
id: delta_cwu_min
unit_of_measurement: "°C"
min_value: 0
max_value: 30
step: 1
optimistic: true
restore_value: true
initial_value: 2
climate:
- platform: thermostat
id: cl_co
name: "CO (pokój)"
# tymczasowo użyj temp_cwu jako sensor, dopóki nie dodasz czujnika pokoju
# docelowo: sensor: temp_pokoj
sensor: temp_cwu
min_heating_off_time: 120s
min_heating_run_time: 120s
min_idle_time: 60s
heat_action:
- switch.turn_on: relay_co
idle_action:
- switch.turn_off: relay_co
- platform: thermostat
id: cl_cwu
name: "CWU (bojler)"
sensor: temp_cwu
min_heating_off_time: 60s
min_heating_run_time: 60s
min_idle_time: 30s
heat_action:
- switch.turn_on: relay_cwu
idle_action:
- switch.turn_off: relay_cwu
touchscreen:
platform: gt911
on_touch:
- script.execute: turn_on_screen_light
globals:
- id: random_int_var
type: int
restore_value: no
initial_value: '0'
time:
- platform: homeassistant
id: hass_time
on_time:
- seconds: /1 # Update every second
then:
- script.execute: update_date_time_label
binary_sensor:
- platform: lvgl
widget: button1
name: Button
on_press: # genrate a random number between -10 and +10 and update meter
then:
# - lambda: |-
# id(random_int_var) = (rand() % 20) - 10;
# - lvgl.indicator.update:
# id: power_meter_input
# value: !lambda return id(random_int_var);
# - lvgl.label.update:
# id: power_kw
# text:
# format: "%dkW"
# args: [ 'id(random_int_var)' ]
script:
# Screen Light Script
- id: turn_on_screen_light
mode: restart
then:
- lvgl.resume:
- lambda: |-
// Force LVGL to redraw the whole active screen to clear "snow"/artifacts
lv_obj_invalidate(lv_scr_act());
lv_refr_now(NULL);
- light.turn_on:
id: backlight
brightness: !lambda return id(backlight_brightness).state / 100;
transition_length: 1s
- id: idle_screen_off
mode: restart
then:
- light.turn_on:
id: backlight
brightness: 50%
transition_length: 5s
- delay: 30s
- light.turn_on:
id: backlight
brightness: 35%
transition_length: 5s
- delay: 30s
- light.turn_off:
id: backlight
- lvgl.pause:
show_snow: true
- id: update_date_time_label
then:
- lvgl.label.update:
id: date_time_label
text: !lambda |-
static const char * const day_names[] = {"NIE", "PON", "WTO", "ŚRO", "CZW", "PIĄ", "SOB"};
static char buf[14]; // Buffer for "XXX 12:59:59"
auto now = id(hass_time).now();
snprintf(buf, sizeof(buf), "%s %02d:%02d:%02d",
day_names[now.day_of_week-1],
now.hour,
now.minute,
now.second);
return buf;
- id: ui_show
mode: restart
then:
- lvgl.widget.show: header_bar
- lvgl.page.show: main_page
- delay: 50ms
- lvgl.widget.hide: boot_screen
- id: ui_refresh_settings_values
mode: restart
then:
- lvgl.label.update:
id: lbl_co_value
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.0f", id(cl_co).target_temperature);
return buf;
- lvgl.label.update:
id: lbl_cwu_value
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.0f", id(cl_cwu).target_temperature);
return buf;
- lvgl.label.update:
id: lbl_delta_value
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.0f", id(delta_cwu_min).state);
return buf;
font:
- file: 'fonts/roboto-400-v1.ttf'
glyphs: ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZŚĄ°.:/+-%–'
id: roboto_med
size: 24
bpp: 4
extras:
- file: "fonts/materialdesignicons-webfont.ttf"
glyphs:
- ${mdi_wifi}
- ${mdi_wifi_off}
- ${mdi_cog}
- ${mdi_home}
- ${mdi_plus}
- ${mdi_minus}
- ${mdi_delta}
- file: 'fonts/roboto-400-v1.ttf'
glyphs: ' -0123456789.°CWUO'
id: roboto_huge
size: 50
bpp: 4
extras:
- file: "fonts/materialdesignicons-webfont.ttf"
glyphs:
- ${mdi_delta}
interval:
- interval: 2s
then:
- script.execute: ui_refresh_settings_values
lvgl:
on_idle:
- timeout: !lambda return id(screen_timeout).state * 1000;
then:
- script.execute: idle_screen_off
color_depth: 16
bg_color: 0xF6F6F6
text_color: ${color_white}
scrollbar_mode: "OFF"
default_font: roboto_med
align: center
buffer_size: 12%
theme:
label:
text_font: roboto_med # set all your labels to use your custom defined font
button:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
pressed: # set some button colors to be different in pressed state
bg_color: 0x006699
bg_grad_color: 0x00334d
checked: # set some button colors to be different in checked state
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0xfff300
buttonmatrix:
bg_opa: TRANSP
border_color: 0x0077b3
border_width: 0
text_color: 0xFFFFFF
pad_all: 0
items: # set all your buttonmatrix buttons to use your custom defined styles and font
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
text_font: roboto_med
pressed:
bg_color: 0x006699
bg_grad_color: 0x00334d
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0x005580
switch:
bg_color: 0xC0C0C0
bg_grad_color: 0xb0b0b0
bg_grad_dir: VER
bg_opa: COVER
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0xFFFFFF
bg_grad_color: 0xC0C0C0
bg_grad_dir: VER
bg_opa: COVER
slider:
border_width: 1
border_opa: 15%
bg_color: 0xcccaca
bg_opa: 15%
indicator:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
style_definitions:
- id: header_footer
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_opa: TRANSP
radius: 0
pad_all: 0
pad_row: 0
pad_column: 0
border_color: 0x0077b3
text_color: 0xFFFFFF
width: 100%
height: 50
- id: meter_style
border_width: 0
outline_width: 0
align: center
bg_color: 0
- id: title_style
text_font: MONTSERRAT_40
align: center
text_color: 0xFFFFFF
bg_opa: TRANSP
bg_color: 0
radius: 4
pad_all: 2
- id: detail_style
text_font: MONTSERRAT_18
align: center
text_color: 0xFFFFFF
bg_opa: TRANSP
bg_color: 0
radius: 4
pad_all: 2
top_layer:
widgets:
- obj:
id: header_bar
hidden: true
align: top_mid
width: 100%
height: 60
styles: header_footer
widgets:
- label:
id: date_time_label
text: "Connecting..."
align: left_mid
x: 10
text_color: ${color_white}
- label:
id: title_label
text: "USTAWIENIA"
align: center
text_color: ${color_white}
hidden: true
- label:
id: ha_status_icon_label
text: ${mdi_wifi_off} # Ikona statusu połączenia
align: right_mid
x: -10
text_color: ${color_warning}
- button:
id: btn_home
hidden: true
align: right_mid
x: -120
widgets:
- label:
align: center
text: ${mdi_home} # Ikona strzałki w lewo
on_press:
then:
- lvgl.widget.hide: btn_home
- lvgl.widget.show: btn_settings
- lvgl.page.show: main_page
- lvgl.widget.hide: title_label
- button:
id: btn_settings
align: right_mid
x: -60
widgets:
- label:
align: center
text: ${mdi_cog} # Ikona trybika
on_press:
then:
- lvgl.widget.show: btn_home
- lvgl.widget.hide: btn_settings
- lvgl.page.show: settings_page
- lvgl.widget.show: title_label
- script.execute: ui_refresh_settings_values
- obj:
id: boot_screen
x: 0
y: 0
width: 100%
height: 100%
bg_color: ${color_darker_grey}
bg_opa: COVER
radius: 0
pad_all: 0
border_width: 0
widgets:
- spinner:
align: CENTER
height: 50
width: 50
spin_time: 1s
arc_length: 60deg
arc_width: 8
indicator:
arc_color: ${color_accent}
arc_width: 8
pages:
- id: main_page
widgets:
- obj:
id: main_content
x: 0
y: 60
width: 800
height: 420
pad_all: 0
border_width: 0
bg_opa: TRANSP
widgets:
- button: # Button1
id: button1
height: 100
width: 200
x: 20
y: 40
border_width: 0
outline_width: 0
align: TOP_LEFT
checkable: true
widgets:
- label:
align: center
text: "CWU"
on_press:
then:
- switch.toggle: relay_cwu
- button: # Button2
id: button2
height: 100
width: 200
x: 20
y: 160
border_width: 0
outline_width: 0
align: TOP_LEFT
checkable: true
widgets:
- label:
align: center
text: "CO"
on_press:
then:
- switch.toggle: relay_co
- label:
id: val_text
text_font: roboto_huge
align: TOP_LEFT
text: "-- °C"
x: 20
y: 280
height: 100
width: 200
- id: settings_page
widgets:
- obj:
width: 165
height: 100
x: 40
y: 100
border_opa: TRANSP
bg_opa: TRANSP
widgets:
- label:
text: "CO"
align: center
text_font: roboto_huge
text_color: ${color_black}
- button:
id: b2
width: 165
height: 100
x: 225
y: 100
widgets:
- label:
align: CENTER
text: ${mdi_minus}
on_press:
then:
- climate.control:
id: cl_co
target_temperature: !lambda "return id(cl_co).target_temperature - 1.0;"
- script.execute: ui_refresh_settings_values
- button:
id: b3
width: 165
height: 100
x: 410
y: 100
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
radius: 4
widgets:
- label:
id: lbl_co_value
align: CENTER
text: "--"
- button:
id: b4
width: 165
height: 100
x: 595
y: 100
widgets:
- label:
align: CENTER
text: ${mdi_plus}
on_press:
then:
- climate.control:
id: cl_co
target_temperature: !lambda "return id(cl_co).target_temperature + 1.0;"
- script.execute: ui_refresh_settings_values
- obj:
width: 165
height: 100
x: 40
y: 220
border_opa: TRANSP
bg_opa: TRANSP
widgets:
- label:
text: "CWU"
align: center
text_font: roboto_huge
text_color: ${color_black}
- button:
id: b6
width: 165
height: 100
x: 225
y: 220
widgets:
- label:
align: CENTER
text: ${mdi_minus}
- button:
id: b7
width: 165
height: 100
x: 410
y: 220
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
radius: 4
widgets:
- label:
id: lbl_cwu_value
align: CENTER
text: "--"
- button:
id: b8
width: 165
height: 100
x: 595
y: 220
widgets:
- label:
align: CENTER
text: ${mdi_plus}
- obj:
width: 165
height: 100
x: 40
y: 340
border_opa: TRANSP
bg_opa: TRANSP
widgets:
- label:
text: ${mdi_delta}
align: center
text_font: roboto_huge
text_color: ${color_black}
- button:
id: b10
width: 165
height: 100
x: 225
y: 340
widgets:
- label:
align: CENTER
text: ${mdi_minus}
on_press:
then:
- number.set:
id: delta_cwu_min
value: !lambda "return id(delta_cwu_min).state - 1.0;"
- script.execute: ui_refresh_settings_values
- button:
id: b11
width: 165
height: 100
x: 410
y: 340
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
radius: 4
widgets:
- label:
id: lbl_delta_value
align: CENTER
text: "--"
- button:
id: b12
width: 165
height: 100
x: 595
y: 340
widgets:
- label:
align: CENTER
text: ${mdi_plus}
on_press:
then:
- number.set:
id: delta_cwu_min
value: !lambda "return id(delta_cwu_min).state + 1.0;"
- script.execute: ui_refresh_settings_values