Last active
February 22, 2026 21:52
-
-
Save kdorff/0543ff4d8aca6a7cb0f84bad0382bcde to your computer and use it in GitHub Desktop.
ESPHome, LVGL Sample back-door-screen.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## | |
| ## 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