TFT: switch to LVGL, add XPT2046 touch

This commit is contained in:
randy committed 2026-09-28 23:13:00 +02:00
1 parent 6ca813fb64
commit b523963944
1 file changed
+222 -125
+222 -125
View File
@@ -10,9 +10,10 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
platformio_options: on_boot:
board_build.flash_mode: dio - priority: -100
then:
- lvgl.page.show: status_page
esp32: esp32:
variant: esp32c3 variant: esp32c3
@@ -23,39 +24,118 @@ packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml ipv6_only: !include common/ipv6_only.yaml
logger: api:
logs: # status page until Home Assistant connects (log viewers don't subscribe to states)
component: ERROR on_client_connected:
- delay: 2s
# GPIO1 UART TX - if:
# GPIO3 UART RX condition:
# GPIO4 I2C SDA api.connected:
# GPIO5 I2C SCL state_subscription_only: true
# GPIO12 DISPLAY DIO then:
# GPIO12 DISPLAY CLOCK - lvgl.page.show: main_page
# GPIO14 BUZZER on_client_disconnected:
# GPIO16 RGB - if:
condition:
not:
api.connected:
state_subscription_only: true
then:
- lvgl.page.show: status_page
spi: spi:
clk_pin: 4 clk_pin: 4
mosi_pin: 5 mosi_pin: 5
miso_pin: 6 miso_pin: 6
color: display:
- id: my_green - platform: mipi_spi
hex: 00ff00 id: main_display
- id: my_magenta model: ILI9341
hex: ff00ff cs_pin: 7
- id: my_red dc_pin: 8
hex: ff0000 reset_pin: 10
- id: my_white invert_colors: false
hex: ffffff data_rate: 40MHz
- id: status_ok update_interval: never
hex: 4dd966 auto_clear_enabled: false
- id: status_wait
hex: ffb300 touchscreen:
- id: status_dim - platform: xpt2046
hex: 8c8c8c display: main_display
cs_pin: 0
interrupt_pin: 1
transform:
mirror_x: true
calibration:
x_min: 330
x_max: 3620
y_min: 230
y_max: 3680
script:
- id: update_status
then:
- lambda: |-
// same colors as the other status screens: ok green, waiting orange, details gray
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
lv_label_set_text(label, text.c_str());
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
};
const bool wifi_ok = wifi::global_wifi_component->is_connected();
set(id(status_wifi),
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
: std::string("WiFi: connecting..."),
wifi_ok ? OK : WAIT);
// IPv6 first (skip link-local), then IPv4
std::string ips;
if (wifi_ok) {
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
if (!ips.empty())
ips += "\n";
ips += buf;
}
}
}
lv_label_set_text(id(status_ips), ips.c_str());
if (ips.empty())
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
else
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
const bool api_ok = api::global_api_server->is_connected();
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
api_ok ? OK : WAIT);
const uint32_t up = millis() / 1000;
lv_label_set_text(id(status_footer),
str_sprintf("ESPHome %s\nup %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
interval:
- interval: 1s
then:
- lvgl.label.update:
id: clock_label
text: !lambda |-
auto now = id(ha_time).now();
if (!now.is_valid())
return std::string("--:--");
return now.strftime("%H:%M");
- if:
condition:
lvgl.page.is_showing: status_page
then:
- script.execute: update_status
image: image:
- platform: file - platform: file
@@ -66,103 +146,120 @@ image:
transparency: alpha_channel transparency: alpha_channel
font: font:
- file: "gfonts://Roboto@500"
id: font_clock
size: 80
glyphs: "0123456789:-"
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
id: font_default id: font_title
size: 20
- file: "gfonts://B612"
id: b612_24
size: 24
- file: "gfonts://B612"
id: b612_16
size: 16
- file: "gfonts://B612"
id: b612_12
size: 12
- file: "gfonts://B612 Mono"
id: b612_24_mono
size: 24
- file: "gfonts://B612 Mono"
id: b612_16_mono
size: 16
- file: "gfonts://B612 Mono"
id: b612_12_mono
size: 12
- file: "gfonts://Roboto"
id: roboto_32
size: 32
- file: "gfonts://Roboto"
id: roboto_24
size: 24 size: 24
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
id: roboto_16 id: font_status
size: 16 size: 14
- file: "gfonts://Roboto"
id: roboto_12
size: 12
- file: "gfonts://Roboto Mono"
id: roboto_32_mono
size: 32
- file: "gfonts://Roboto Mono"
id: roboto_24_mono
size: 24
- file: "gfonts://Roboto Mono"
id: roboto_16_mono
size: 16
- file: "gfonts://Roboto Mono"
id: roboto_12_mono
size: 12
lvgl:
log_level: WARN
color_depth: 16
bg_color: 0x000000
text_color: 0xFFFFFF
theme:
button:
bg_color: 0x2B2F36
radius: 12
checked:
bg_color: 0xE08A00
buttonmatrix:
bg_opa: TRANSP
border_width: 0
items:
bg_color: 0x2B2F36
text_color: 0xFFFFFF
radius: 12
checked:
bg_color: 0xE08A00
top_layer:
widgets:
# same navigation bar as on the other LVGL displays
- buttonmatrix:
id: nav_bar
align: bottom_mid
width: 100%
height: 48
pad_all: 4
pad_column: 6
items:
text_font: montserrat_20
rows:
- buttons:
- id: page_prev
text: ""
on_press:
then:
lvgl.page.previous:
- id: page_home
text: ""
on_press:
then:
lvgl.page.show: main_page
- id: page_next
text: ""
on_press:
then:
lvgl.page.next:
pages:
# 1: clock
- id: main_page
widgets:
- label:
id: clock_label
align: CENTER
y: -24
text_font: font_clock
text: "--:--"
display: # 2: status (shown until Home Assistant connects)
- platform: mipi_spi - id: status_page
model: ILI9341 on_load:
cs_pin: 7 - script.execute: update_status
dc_pin: 8 widgets:
reset_pin: 10 - image:
invert_colors: false src: esphome_logo
lambda: |- align: TOP_MID
it.fill(Color::BLACK); y: 8
- label:
// status screen until Home Assistant connects (240x320) align: TOP_MID
if (!api::global_api_server->is_connected_with_state_subscription()) { y: 78
const int cx = it.get_width() / 2; text_font: font_title
it.image(cx, 48, id(esphome_logo), ImageAlign::CENTER); text: "${name}"
it.print(cx, 100, id(roboto_24), id(my_white), TextAlign::CENTER, "${name}"); - obj:
x: 8
int y = 130; y: 116
if (wifi::global_wifi_component->is_connected()) { width: 224
it.printf(8, y, id(roboto_16), id(status_ok), "WiFi: connected (%d dBm)", height: SIZE_CONTENT
wifi::global_wifi_component->wifi_rssi()); bg_opa: TRANSP
y += 22; border_width: 0
// IPv6 first (skip link-local), then IPv4 pad_all: 0
char buf[network::IP_ADDRESS_BUFFER_SIZE]; scrollbar_mode: "OFF"
for (bool v6 : {true, false}) { layout:
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) { type: flex
if (!ip.is_set() || ip.is_ip6() != v6 || y > 200) flex_flow: column
continue; pad_row: 6
ip.str_to(buf); widgets:
if (v6 && strncmp(buf, "fe80", 4) == 0) - label:
continue; id: status_wifi
it.print(8, y, id(roboto_12), id(status_dim), buf); text_font: font_status
y += 16; text: ""
} - label:
} id: status_ips
} else { text_font: font_status
it.print(8, y, id(roboto_16), id(status_wait), "WiFi: connecting..."); text_color: 0x8C8C8C
y += 22; text: ""
} - label:
y += 8; id: status_ha
it.print(8, y, id(roboto_16), api::global_api_server->is_connected() ? id(status_ok) : id(status_wait), text_font: font_status
api::global_api_server->is_connected() ? "Home Assistant: connected" : "Home Assistant: waiting..."); text: ""
- label:
it.printf(8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_LEFT, "ESPHome %s", id: status_footer
ESPHOME_VERSION); text_font: font_status
it.printf(it.get_width() - 8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_RIGHT, text_color: 0x8C8C8C
"up %us", (unsigned) (millis() / 1000)); text: ""
return;
}
it.print(0, 0, id(b612_12), id(my_white), TextAlign::TOP_LEFT, "HELLO WORLD!");
it.print(0, 14, id(b612_12), id(my_green), TextAlign::TOP_LEFT, "HELLO WORLD!");
it.print(0, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!");
it.print(0, 42, id(b612_12), id(my_red), TextAlign::TOP_LEFT, "HELLO WORLD!");