Skip to content

Instantly share code, notes, and snippets.

@kdorff
Last active February 22, 2026 21:52
Show Gist options
  • Select an option

  • Save kdorff/0543ff4d8aca6a7cb0f84bad0382bcde to your computer and use it in GitHub Desktop.

Select an option

Save kdorff/0543ff4d8aca6a7cb0f84bad0382bcde to your computer and use it in GitHub Desktop.
ESPHome, LVGL Sample back-door-screen.yaml
##
## My back door screen.
## ESPHome + LVGL running on
## Freenove ESP32-S3 ESP32 S3 Display CYD 2.8 Inch IPS Capacitive Touch Screen 240x320
## https://www.amazon.com/dp/B0FSQF6FKN
##
## This was based on the configuration found
## https://github.com/celer/esphome_esp32-s3-2.8-display
##
esphome:
name: back-door-screen
friendly_name: back-door-screen
min_version: 2025.11.0
includes:
- <sstream>
name_add_mac_suffix: false
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "XXXX"
ota:
- platform: esphome
password: "XXXX"
captive_portal:
# Enable PSRAM
psram:
mode: octal
speed: 80MHz
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
use_psram: true # offload network buffers to psram
domain: .mylocal
min_auth_mode: WPA2
manual_ip:
static_ip: 192.168.1.111
gateway: 192.168.1.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Back-Door-Screen"
password: "XXXX"
########################
# I/O && Bus definitions
########################
substitutions:
device_name: "back-door-screen"
sda_pin_bus: "16" # I2C Bus A SDA
scl_pin_bus: "15" # I2C BUS A SCL
i2s_lrclk_pin: "7" # I2S LRCLK (Word Select)
i2s_bclk_pin: "5" # I2S BCLK (Bit Clock)
i2s_mclk_pin: "4" # I2S MCLK (Master Clock)
i2s_din_pin: "6" # I2S Data In (Mic)
i2s_dout_pin: "8" # I2S Data Out (Speaker)
spi_clk_pin: "12" # SPI CLK Pin
spi_mosi_pin: "11" # SPI MOSI PIN
spi_miso_pin: "13" # SPI MISO PIN
backlight_pin: "45" # Display backlight
speaker_enable_pin: "1" # Speaker Enable
battery_level_pin: "9" # Battery level
rgb_led_pin: "42"
i2c:
- id: bus
sda: GPIO${sda_pin_bus}
scl: GPIO${scl_pin_bus}
scan: true
spi:
clk_pin: GPIO${spi_clk_pin}
mosi_pin: GPIO${spi_mosi_pin}
miso_pin: GPIO${spi_miso_pin}
output:
- platform: gpio
pin: GPIO${speaker_enable_pin}
id: speaker_amp_enable
inverted: true
- platform: gpio
pin: GPIO${backlight_pin}
id: backlight
- platform: gpio
pin: GPIO2
id: boiler_output
########################
# Audio config
########################
i2s_audio:
- id: i2s_audio_bus
i2s_lrclk_pin: GPIO${i2s_lrclk_pin}
i2s_bclk_pin: GPIO${i2s_bclk_pin}
i2s_mclk_pin: GPIO${i2s_mclk_pin}
# Where I got this, the original author wasn't able
# tog get the microphone to work. I haven't tried.
audio_dac:
- platform: es8311
i2c_id: bus
id: es8311_dac
address: 0x18
bits_per_sample: 16bit
sample_rate: 16000
use_mclk: true
speaker:
- platform: i2s_audio
id: my_speaker
i2s_dout_pin: GPIO${i2s_dout_pin}
dac_type: external
sample_rate: 16000
bits_per_sample: 16bit
channel: mono
audio_dac: es8311_dac
buffer_duration: 100ms
########################
# Display and touchscreen
########################
display:
- platform: mipi_spi
model: ili9341
dc_pin: GPIO46
cs_pin: GPIO10
invert_colors: true
rotation: 90
dimensions:
width: 320
height: 240
auto_clear_enabled: false
update_interval: never
touchscreen:
- platform: ft63x6
interrupt_pin: GPIO17
transform:
swap_xy: True
mirror_y: True
on_touch:
# Log out each touch event
- lambda: |-
ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
touch.x,
touch.y,
touch.x_raw,
touch.y_raw
);
########################
# RGB LED
########################
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
pin: GPIO${rgb_led_pin}
num_leds: 1
chipset: ws2812
name: "Status LED"
id: my_status_led
# Optional: Add some fun effects
effects:
- addressable_rainbow:
name: Rainbow
- pulse:
name: Slow Pulse
########################
# Fonts
########################
font:
- file: "gfonts://Rubik@800"
id: font_door
size: 34
- file: "gfonts://Rubik@900"
id: font_time
size: 55
- file: "gfonts://Rubik@600"
id: font_temp
size: 55
- file: "gfonts://Rubik@600"
id: font_wind
size: 40
- file: "gfonts://Rubik@800"
id: font_brightness
size: 30
- file: "gfonts://Roboto"
id: large_numbers
glyphs: "0123456789°"
size: 34
########################
# LVGL UI
########################
lvgl:
bg_color: 0x0000FF
style_definitions:
- id: button_label_style
text_line_space: 0
align: center
widgets:
- obj:
width: "100%"
height: "100%"
bg_color: 0x0000FF
scrollbar_mode: "OFF"
pad_all: 0
border_width: 0
layout:
type: flex
flex_align_main: center
flex_flow: column
flex_align_track: CENTER
widgets:
- obj:
width: "100%"
height: "40%"
bg_color: 0x0000FF
scrollbar_mode: "OFF"
pad_all: 0
border_width: 0
layout:
type: flex
flex_flow: row
widgets:
- button:
id: back_door_button
align: TOP_LEFT
bg_color: 0x0000FF # Initial color
text: "Back\nDoor" # Initial text
text_align: CENTER
text_line_space: 0
width: 48%
text_font: font_door
on_press:
then:
- logger.log: "Toggling back_door_autolock"
- switch.toggle: back_door_autolock
- button:
id: front_door_button
align: TOP_RIGHT
bg_color: 0x0000FF # Initial color
text: "Front\nDoor" # Initial text
text_align: CENTER
text_line_space: 0
text_font: font_door
width: 48%
on_press:
then:
- logger.log: "Toggling front_door_autolock"
- switch.toggle: front_door_autolock
- obj:
width: 100%
height: 30%
bg_color: 0x0000FF
scrollbar_mode: "OFF"
pad_all: 0
border_width: 0
layout:
type: flex
flex_flow: row
widgets:
- label:
id: time_label
text: "Time"
width: 60%
align: CENTER
text_align: CENTER
text_color: 0xFFFFFF
text_font: font_time
- label:
id: temp_label
text: "?"
width: 40%
align: CENTER
text_align: CENTER
text_color: 0xFFFFFF
text_font: font_temp
- label:
id: wind_label
width: 100%
height: 18%
text: "12 - 14 N"
align: CENTER
text_align: CENTER
text_color: 0xFFFFFF
text_font: font_wind
########################
# Interval to Update UI
########################
interval:
- interval: 1s
then:
- lvgl.label.update:
id: time_label
text: !lambda |-
return id(esptime).now().strftime("%I:%M");
- lvgl.label.update:
id: temp_label
text: !lambda |-
auto state = id(back_yard_temperature).state;
if (isnan(state)) {
return "?°";
} else {
char buffer[10];
sprintf(buffer, "%.0f°", state);
return buffer;
}
- lvgl.label.update:
id: wind_label
text: !lambda |-
if (wind_speed->has_state() && wind_speed->state < 200) {
std::stringstream ss;
ss << "Wind " << value_accuracy_to_string(wind_speed->state, 0);
if (wind_gust_speed->has_state() && !isnan(wind_gust_speed->state)) {
ss << "-" << value_accuracy_to_string(wind_gust_speed->state, 0);
}
if (wind_direction->has_state()) {
ss << " " << wind_direction->state.c_str();
}
return ss.str();
}
else {
return "";
}
########################
# Home Assitant Integrations
########################
# this will allow home assistant to send WAV data to the device
# which enables music / text to speech. If you visit the
# device page in esphome under Settings->ESPHome->Devices
# you can click on the icon for the media player and send audio
# to this device now.
media_player:
- platform: speaker
name: None
id: external_media_player
buffer_size: 32768
announcement_pipeline:
speaker: my_speaker
format: WAV # In other examples I've seen FLAC, but I can't get it working
time:
- platform: homeassistant
id: esptime
sensor:
## Sensors from display panel
- platform: adc
pin: GPIO${battery_level_pin}
name: "Battery Level"
update_interval: 60s
## HA Sensors
- platform: homeassistant
id: back_yard_temperature
entity_id: sensor.back_yard_sensor_temperature
- platform: homeassistant
id: wind_speed
entity_id: sensor.openweathermap_wind_speed
- platform: homeassistant
id: wind_gust_speed
entity_id: sensor.openweathermap_wind_gust_speed
# Expose control of the speaker and backlight
switch:
- platform: output
name: "Speaker Amplifier"
output: speaker_amp_enable
restore_mode: ALWAYS_ON
- platform: output
name: "Backlight"
output: backlight
restore_mode: ALWAYS_ON
##
## HA sensors
##
- platform: homeassistant
id: back_door_autolock
entity_id: switch.back_door_auto_lock
restore_mode: RESTORE_DEFAULT_ON
on_state:
then:
- component.update: back_door_combined_state
- platform: homeassistant
id: front_door_autolock
entity_id: switch.front_door_auto_lock
restore_mode: RESTORE_DEFAULT_ON
on_state:
then:
- component.update: front_door_combined_state
binary_sensor:
##
## HA sensors for auto lock state and door open state
##
- platform: homeassistant
id: back_door_sensor
entity_id: binary_sensor.back_door_sensor
trigger_on_initial_state: true
on_state:
then:
- component.update: back_door_combined_state
- platform: homeassistant
id: front_door_sensor
entity_id: binary_sensor.front_door_sensor
trigger_on_initial_state: true
on_state:
then:
- component.update: front_door_combined_state
text_sensor:
- platform: homeassistant
id: wind_direction
entity_id: sensor.weather_wind_dir
## Combined state of door autolock and door open
- platform: template
name: "Back Door Autolocked + Open combined state"
id: back_door_combined_state
lambda: |-
std::string s1 = id(back_door_autolock).state ? "on" : "off";
std::string s2 = id(back_door_sensor).state ? "on" : "off";
return {s1 + ":" + s2};
update_interval: never # Updates are pushed by the on_state triggers above
on_value:
then:
## Set Button color and text based on door autolock and open states
- lvgl.button.update:
id: back_door_button
text: !lambda |-
if (id(back_door_sensor).state) {
return "Back\nDoor\nOpen";
} else {
return "Back\nDoor";
}
bg_color: !lambda |-
if (id(back_door_sensor).state) {
if (id(back_door_autolock).state) {
return lv_color_hex(0xFFFF00); // Yellow
} else {
return lv_color_hex(0xFFA500); // Orange
}
} else if (id(back_door_autolock).state) {
return lv_color_hex(0x00FF00); // Green
} else {
return lv_color_hex(0xFF0000); // Red
}
text_color: !lambda |-
if (id(back_door_sensor).state) {
if (id(back_door_autolock).state) {
return lv_color_hex(0x000000); // Black on Yellow
} else {
return lv_color_hex(0x000000); // Black on Orange
}
} else if (id(back_door_autolock).state) {
return lv_color_hex(0x000000); // Black on Green
} else {
return lv_color_hex(0xFFFFFF); // White on Red
}
## Combined state of door autolock and door open
- platform: template
name: "Front Door Autolocked + Open combined state"
id: front_door_combined_state
lambda: |-
std::string s1 = id(front_door_autolock).state ? "on" : "off";
std::string s2 = id(front_door_sensor).state ? "on" : "off";
return {s1 + ":" + s2};
# Updates are pushed by the on_state triggers above
update_interval: never
on_value:
then:
## Set Button color and text based on door autolock and open states
- lvgl.button.update:
id: front_door_button
text: !lambda |-
if (id(front_door_sensor).state) {
return "Front\nDoor\nOpen";
} else {
return "Front\nDoor";
}
bg_color: !lambda |-
if (id(front_door_sensor).state) {
if (id(front_door_autolock).state) {
return lv_color_hex(0xFFFF00); // Yellow
} else {
return lv_color_hex(0xFFA500); // Orange
}
} else if (id(front_door_autolock).state) {
return lv_color_hex(0x00FF00); // Green
} else {
return lv_color_hex(0xFF0000); // Red
}
text_color: !lambda |-
if (id(front_door_sensor).state) {
if (id(front_door_autolock).state) {
return lv_color_hex(0x000000); // Black on Yellow
} else {
return lv_color_hex(0x000000); // Black on Orange
}
} else if (id(back_door_autolock).state) {
return lv_color_hex(0x000000); // Black on Green
} else {
return lv_color_hex(0xFFFFFF); // White on Red
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment