This is an extremely fast little screen. It seems like a good one for ESPHome. It has dual noise canceling microphones for Voice Assistant.
We now have a working config here
This is an extremely fast little screen. It seems like a good one for ESPHome. It has dual noise canceling microphones for Voice Assistant.
We now have a working config here
I haven’t spotted any post - looks like a serious bit of hardware.
If all you need is a 7 inch display with adequate resolution for 7 inch, this one has tons of support:
But if you can get the one you posted running that would be interesting…
That’s a nice screen but it does not have built in dimming for the backlight LED. This is a requirement for me. There are a lot of hardware mods to get it working.
There is a good thread on this form for that screen already
Let’s keep this thread about the Waveshare ESP32-P4-WIFI6-Touch-LCD-7B only so as not to create confusion.
The Discord team got it working!!
logger:
hardware_uart: USB_SERIAL_JTAG
api:
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
esphome:
name: waveshare-esp32-p4-touch-lcd-7b
friendly_name: waveshare-esp32-p4-wifi6-touch-lcd-7b
external_components:
- source: github://pr#11886
components: [mipi_dsi]
refresh: 1h
esp32:
variant: esp32p4
flash_size: 16MB
cpu_frequency: 360MHz
framework:
type: esp-idf
advanced:
enable_idf_experimental_features: true
esp32_hosted:
variant: ESP32C6
reset_pin: GPIO54
cmd_pin: GPIO19
clk_pin: GPIO18
d0_pin: GPIO14
d1_pin: GPIO15
d2_pin: GPIO16
d3_pin: GPIO17
active_high: true
psram:
mode: hex
speed: 200MHz
preferences:
flash_write_interval: 5min
esp_ldo:
- voltage: 2.5V
channel: 3
# -------------------------------------------
# Touchscreen gt911 i2c
# -------------------------------------------
i2c:
- id: bus_a
sda: GPIO07
scl: GPIO08
frequency: 400kHz
touchscreen:
- platform: gt911
id: my_touchscreen
i2c_id: bus_a
reset_pin: GPIO23
update_interval: 50ms
transform:
swap_xy: false
mirror_x: false
mirror_y: false
# on_touch:
# - logger.log:
# format: Touch at (%d, %d)
# args: [touch.x, touch.y]
# - lambda: |-
# ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
# touch.x,
# touch.y,
# touch.x_raw,
# touch.y_raw
# );
# -------------------------------------------
# Backlight
# -------------------------------------------
output:
- platform: ledc
id: gpio_backlight_pwm
pin: GPIO32
inverted: true
frequency: 1000Hz
light:
- platform: monochromatic
output: gpio_backlight_pwm
name: Display Backlight
icon: mdi:lightbulb-on
id: display_backlight
restore_mode: ALWAYS_ON
default_transition_length: 250ms
# -------------------------------------------
# Display
# -------------------------------------------
display:
- platform: mipi_dsi
id: my_display
model: WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B
rotation: 180
update_interval: never
auto_clear_enabled: false
dimensions:
width: 1024
height: 600
# LVGL settings required for this display
lvgl:
buffer_size: 100%
byte_order: little_endianweb_server:
port: 80
version: 3
logger:
hardware_uart: USB_SERIAL_JTAG
api:
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
esphome:
name: waveshare-esp32-p4-touch-lcd-7b
friendly_name: waveshare-esp32-p4-wifi6-touch-lcd-7b
external_components:
- source: github://pr#11886
components: [mipi_dsi]
refresh: 1h
esp32:
variant: esp32p4
flash_size: 16MB
cpu_frequency: 360MHz
framework:
type: esp-idf
advanced:
enable_idf_experimental_features: true
esp32_hosted:
variant: ESP32C6
reset_pin: GPIO54
cmd_pin: GPIO19
clk_pin: GPIO18
d0_pin: GPIO14
d1_pin: GPIO15
d2_pin: GPIO16
d3_pin: GPIO17
active_high: true
psram:
mode: hex
speed: 200MHz
preferences:
flash_write_interval: 5min
esp_ldo:
- voltage: 2.5V
channel: 3
# -------------------------------------------
# Touchscreen gt911 i2c
# -------------------------------------------
i2c:
- id: bus_a
sda: GPIO07
scl: GPIO08
frequency: 400kHz
touchscreen:
- platform: gt911
id: my_touchscreen
i2c_id: bus_a
reset_pin: GPIO23
update_interval: 50ms
transform:
swap_xy: false
mirror_x: false
mirror_y: false
# on_touch:
# - logger.log:
# format: Touch at (%d, %d)
# args: [touch.x, touch.y]
# - lambda: |-
# ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
# touch.x,
# touch.y,
# touch.x_raw,
# touch.y_raw
# );
# -------------------------------------------
# Backlight
# -------------------------------------------
output:
- platform: ledc
id: gpio_backlight_pwm
pin: GPIO32
inverted: true
frequency: 1000Hz
light:
- platform: monochromatic
output: gpio_backlight_pwm
name: Display Backlight
icon: mdi:lightbulb-on
id: display_backlight
restore_mode: ALWAYS_ON
default_transition_length: 250ms
# -------------------------------------------
# Display
# -------------------------------------------
display:
- platform: mipi_dsi
id: my_display
model: WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B
rotation: 180
update_interval: never
auto_clear_enabled: false
dimensions:
width: 1024
height: 600
# LVGL settings required for this display
lvgl:
buffer_size: 100%
byte_order: little_endian
I solved the problem. I just have to add “password: !secret ota_password in ota.yaml”
> `ota:
> - platform: web_server
> - platform: esphome
> id: ota_state
> password: !secret ota_password`
Hi @andrew_NH Andrew i looked at your github and i tried several times to get your code up and running i always got the error message “NFO Package configuration completed successfully
INFO Package configuration completed successfully
INFO Successfully compiled program.
INFO Connecting to 192.168.178.53 port 3232…
INFO Connected to 192.168.178.53
INFO Uploading /data/build/waveshare-esp32-s3-touch-lcd-7/.pioenvs/waveshare-esp32-s3-touch-lcd-7/firmware.bin (1773856 bytes)
ERROR ESP requests password, but no password given!” but i did not set any password for ota but beside that there is an ota password in my secrets.yaml. so my question is where is the part where your configuration is asking for an ota password? Thanks in advance for an hint ![]()
Nothing in my repo has a password set. But you can set one if you want. It is not required.
yes, i got it working more or less but it seems that the waveshare ESP 32 Touch LCD 7B (without P4 and wifi 6) in general a bit buggy. By the way, is there any documantatation how your modules are working and how to bring them up to an display to like the light buttons, the weather? I’ve tried but got nothing to work.
Yeah sure everything you need to know on how to set it all up is here in the readme file.
Start with the demo that matches your screen.
The code is very well documented so you can see the lines that control the buttons. Just change them to match a light you have in your HA setup.
example just change the entity_id to match a switch you have. That’s it.
# Button 2 - Standard light button with state updates
- button: !include
file: esphome-modular-lvgl-buttons/buttons/switch_button.yaml
vars:
uid: button_2
height: $button_height_single
text: Button 2
icon: $mdi_lightbulb
entity_id: "switch.athom_smart_plug_v3_50ebc0_switch"
I think over all the waveshare-esp32-s3-touch-lcd-7B is a very nice screen. It’s a bit slow to do swipes but other then that it works great!
Hey, thank you so much for your response. I’ve now managed to get the display running, and the page with the buttons is also showing up.
What I haven’t quite figured out yet is how to integrate individual modules, for example for the weather. Do I need to add a new page in the main configuration under LVGL to display the weather data and weather icons? From what I understand, the pages are added in the main configuration under LVGL & Pages. I just don’t have any idea how I need to insert the individual weather YAML, or what the LVGL code should look like. Would it be possible to provide the code for the weather module? That way, I could understand the structure based on the example and adapt it to my configuration – that would really help me a lot. I’m unfortunately not yet deep enough into LVGL and ESP Home to understand it right away.
I know these are a lot of questions, but I hope you can give me a little nudge in the right direction.
When compiling the firmware, there were a few deprecation warnings regarding the select option.
config/esphome/esphome-modular-lvgl-buttons/common/theme_style.yaml: In lambda function:
/config/esphome/esphome-modular-lvgl-buttons/common/theme_style.yaml:57:29: warning: 'esphome::select::Select::state' is deprecated: Use current_option() instead of .state. Will be removed in 2026.5.0 [-Wdeprecated-declarations]
57 | - lambda: 'return id(current_theme).state == "Dark";'
| ^~~~~
In file included from src/esphome/core/application.h:72,
from src/esphome/components/api/api_frame_helper.h:13,
from src/esphome/components/api/api_connection.h:5,
from src/esphome.h:3,
from src/main.cpp:3:
src/esphome/components/select/select.h:39:15: note: declared here
39 | std::string state{};
| ^~~~~
/config/esphome/esphome-modular-lvgl-buttons/common/theme_style.yaml:57:29: warning: 'esphome::select::Select::state' is deprecated: Use current_option() instead of .state. Will be removed in 2026.5.0 [-Wdeprecated-declarations]
57 | - lambda: 'return id(current_theme).state == "Dark";'
| ^~~~~
src/esphome/components/select/select.h:39:15: note: declared here
39 | std::string state{};
| ^~~~~
/config/esphome/esphome-modular-lvgl-buttons/common/theme_style.yaml:57:29: warning: 'esphome::select::Select::state' is deprecated: Use current_option() instead of .state. Will be removed in 2026.5.0 [-Wdeprecated-declarations]
57 | - lambda: 'return id(current_theme).state == "Dark";'
| ^~~~~
src/esphome/components/select/select.h:39:15: note: declared here
39 | std::string state{};
| ^~~~~... and so on
I have corrected these, as you can see below. The control also works with these changes; I have tested them.
Thank you very much in advance for your help
And here the Code snippets to prevent the deprecated messeages
in theme_style.yaml:
select:
- platform: template
name: "Current Theme"
id: current_theme
icon: mdi:theme-light-dark
optimistic: true
options:
- Dark
- Light
initial_option: Dark
on_value:
then:
- if:
condition:
- lambda: 'return id(current_theme).current_option == "Dark";'
then:
- lvgl.style.update:
id: page_style
bg_color: black
text_color: white
- lvgl.widget.redraw
- if:
condition:
- lambda: 'return id(current_theme).current_option == "Light";'
then:
- lvgl.style.update:
id: page_style
bg_color: white
text_color: black
- lvgl.widget.redraw
and the part in backlignt_time.yaml:
lvgl:
on_idle:
timeout: $touchscreen_idle_timeout
then:
- logger.log: "LVGL is idle"
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Day";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_daytime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Evening";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_nighttime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Night";'
then:
- light.turn_off: display_backlight
- lvgl.pause:
show_snow: true
# Wake on screen touch
touchscreen:
on_touch:
then:
- logger.log: "LVGL is active"
- lvgl.resume
- lvgl.widget.redraw
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Day";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_daytime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Evening";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_nighttime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Night";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_nighttime_brightness}
select:
- platform: template
name: "Brightness mode"
id: brightness_mode
icon: mdi:television-shimmer
optimistic: true
options:
- Day
- Evening
- Night
initial_option: Day
on_value:
then:
- lvgl.resume
- lvgl.widget.redraw
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Day";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_daytime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Evening";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_nighttime_brightness}
- if:
condition:
- lambda: 'return id(brightness_mode).current_option() == "Night";'
then:
- light.turn_on:
id: display_backlight
brightness: ${touchscreen_nighttime_brightness}
My code is quite flexible you can make a new page with the weather or add it to an existing page.
I have examples of both here
This one shows both the weather and a tide clock on the same screen.
I was annoyed with the display remaining on/dim when the backlight is turned off. Unfortunately, turning the display on/off by adjusting the voltage of the LDO caused the HSYNC/VSYNC (and other parameters) to be incorrect when turned back on.
The ESPHome mipi_dsi component does not implement the on/off commands for the display, and the handles necessary to send the MIPI DCS commands via lambdas are protected. So I created a class to access the mipi_dsi protected members by creating it in the same namespace and using inheritance.
This has been working for me without issue, so I thought I would share what I have so far.
Prerequisites: This requires a configured
mipi_dsidisplay in your ESPHome configuration.
Save the following code to a file (e.g., includes/mipi_dsi_ext.h).
#pragma once
#include "esphome/components/mipi_dsi/mipi_dsi.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_mipi_dsi.h"
namespace esphome {
namespace mipi_dsi {
// Standard MIPI DCS commands
#define MIPI_DCS_SLEEP_IN 0x10
#define MIPI_DCS_SLEEP_OUT 0x11
#define MIPI_DCS_DISPLAY_OFF 0x28
#define MIPI_DCS_DISPLAY_ON 0x29
// Access protected members by being in the same namespace and using inheritance
class MIPI_DSI_Ext : public MIPI_DSI {
public:
// Static accessors that cast any MIPI_DSI* to access protected members
static esp_lcd_panel_handle_t get_handle(MIPI_DSI *d) {
return static_cast<MIPI_DSI_Ext*>(d)->handle_;
}
static esp_lcd_dsi_bus_handle_t get_bus_handle(MIPI_DSI *d) {
return static_cast<MIPI_DSI_Ext*>(d)->bus_handle_;
}
static esp_lcd_panel_io_handle_t get_io_handle(MIPI_DSI *d) {
return static_cast<MIPI_DSI_Ext*>(d)->io_handle_;
}
static const std::vector<uint8_t>& get_init_sequence(MIPI_DSI *d) {
return static_cast<MIPI_DSI_Ext*>(d)->init_sequence_;
}
static GPIOPin* get_reset_pin(MIPI_DSI *d) {
return static_cast<MIPI_DSI_Ext*>(d)->reset_pin_;
}
// Send a DCS command with no parameters
static esp_err_t send_dcs_cmd(MIPI_DSI *d, uint8_t cmd) {
auto io = static_cast<MIPI_DSI_Ext*>(d)->io_handle_;
if (io == nullptr) {
ESP_LOGE("mipi_dsi_ext", "IO handle is null");
return ESP_ERR_INVALID_STATE;
}
return esp_lcd_panel_io_tx_param(io, cmd, nullptr, 0);
}
// Send a DCS command with parameters
static esp_err_t send_dcs_cmd(MIPI_DSI *d, uint8_t cmd, const uint8_t *data, size_t len) {
auto io = static_cast<MIPI_DSI_Ext*>(d)->io_handle_;
if (io == nullptr) {
ESP_LOGE("mipi_dsi_ext", "IO handle is null");
return ESP_ERR_INVALID_STATE;
}
return esp_lcd_panel_io_tx_param(io, cmd, data, len);
}
// Turn display on/off using MIPI DCS commands
static void set_display_on(MIPI_DSI *d, bool on) {
esp_err_t ret;
if (on) {
ret = send_dcs_cmd(d, MIPI_DCS_SLEEP_OUT);
if (ret != ESP_OK) {
ESP_LOGE("mipi_dsi_ext", "Sleep out failed: %s", esp_err_to_name(ret));
return;
}
vTaskDelay(pdMS_TO_TICKS(120)); // Wait for sleep out
ret = send_dcs_cmd(d, MIPI_DCS_DISPLAY_ON);
if (ret != ESP_OK) {
ESP_LOGE("mipi_dsi_ext", "Display on failed: %s", esp_err_to_name(ret));
return;
}
ESP_LOGI("mipi_dsi_ext", "Display ON");
} else {
ret = send_dcs_cmd(d, MIPI_DCS_DISPLAY_OFF);
if (ret != ESP_OK) {
ESP_LOGE("mipi_dsi_ext", "Display off failed: %s", esp_err_to_name(ret));
return;
}
vTaskDelay(pdMS_TO_TICKS(20));
ret = send_dcs_cmd(d, MIPI_DCS_SLEEP_IN);
if (ret != ESP_OK) {
ESP_LOGE("mipi_dsi_ext", "Sleep in failed: %s", esp_err_to_name(ret));
return;
}
ESP_LOGI("mipi_dsi_ext", "Display OFF (sleep mode)");
}
}
// Hardware reset using the reset pin
static void hw_reset(MIPI_DSI *d) {
auto *ext = static_cast<MIPI_DSI_Ext*>(d);
auto reset_pin = ext->reset_pin_;
if (reset_pin == nullptr) {
ESP_LOGW("mipi_dsi_ext", "No reset pin configured");
return;
}
ESP_LOGI("mipi_dsi_ext", "Performing hardware reset");
reset_pin->digital_write(false);
vTaskDelay(pdMS_TO_TICKS(10));
reset_pin->digital_write(true);
vTaskDelay(pdMS_TO_TICKS(120)); // Wait for panel to initialize
}
// Send the stored init sequence
static void send_init_sequence(MIPI_DSI *d) {
auto *ext = static_cast<MIPI_DSI_Ext*>(d);
auto io = ext->io_handle_;
if (io == nullptr) {
ESP_LOGE("mipi_dsi_ext", "IO handle is null");
return;
}
const auto &seq = ext->init_sequence_;
ESP_LOGI("mipi_dsi_ext", "Sending init sequence (%d bytes)", seq.size());
size_t i = 0;
while (i < seq.size()) {
uint8_t cmd = seq[i++];
if (i >= seq.size()) break;
uint8_t len = seq[i++];
if (len == 0xFF) {
// Delay marker
if (i >= seq.size()) break;
uint8_t delay_ms = seq[i++];
ESP_LOGD("mipi_dsi_ext", "Delay %d ms", delay_ms);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
} else {
ESP_LOGD("mipi_dsi_ext", "CMD 0x%02X, len=%d", cmd, len);
if (i + len > seq.size()) break;
esp_lcd_panel_io_tx_param(io, cmd, len > 0 ? &seq[i] : nullptr, len);
i += len;
}
}
ESP_LOGI("mipi_dsi_ext", "Init sequence complete");
}
// Full reinitialize: HW reset + init sequence + display on
static void reinit_panel(MIPI_DSI *d) {
ESP_LOGI("mipi_dsi_ext", "Reinitializing panel...");
hw_reset(d);
send_init_sequence(d);
set_display_on(d, true);
ESP_LOGI("mipi_dsi_ext", "Panel reinitialized");
}
};
} // namespace mipi_dsi
} // namespace esphome
Include the file in your configuration:
esphome:
includes:
- includes/mipi_dsi_ext.h # or your chosen path
Add the set_display_on functions to the on_turn_on/off triggers for your backlight (replace my_display with your display’s ID)
light:
- platform: monochromatic
output: backlight_pwm
name: "Display Backlight"
id: light_display_backlight
gamma_correct: 1.0
restore_mode: ALWAYS_ON
default_transition_length: 1s
on_turn_on:
- lambda: |-
mipi_dsi::MIPI_DSI_Ext::set_display_on(id(my_display), true);
on_turn_off:
- lambda: |-
mipi_dsi::MIPI_DSI_Ext::set_display_on(id(my_display), false);
(Optional) Add the off command to trigger before OTA updates so the screen doesn’t flicker during the process:
ota:
- platform: esphome
on_begin:
then:
- lambda: |-
mipi_dsi::MIPI_DSI_Ext::set_display_on(id(my_display), false);
Sorry for the delay to answere but i was a bit busy because of my job.
Really thank you more then a million times. That pushed me in the right direction and i got it working. Again thanks a lot for your support and your hard work which you have done for the esphome-modular-lvgl-buttons repo.
That is really nice. Do you want to add a feature request to ESPHome? I think this would be a great upgrade.
Awesome - got it working thanks to this; I changed the board to variant, the same as I did with the first gen Waveshare touch 7 inch.
Has anyone got the display working with RGB rather than RGB565? I’m not sure how this works - but looking at Add WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B driver the init sequence seems to be a lot smaller than the others and I wondered if that could be preventing it from using RGB.
I did some digging and found the EK79007 datasheet from the EK79007 readme which has a decent description of how to use the init sequence.
Probably barking up the wrong tree and I’ve missed something obvious (not the first time lol)
A big gotcha for me was that the image: component doesn’t inherit the LVGLs endiness.
Edit: The espressif page for the device I believe is: waveshare/esp32_p4_wifi6_touch_lcd_7b • v1.0.2 • ESP Component Registry but @andrew_NH to confirm this is the exact version etc.
Second Edit: I’ve successfully built and flashed the LVGL demo from the waveshare github repo to see if that reveals anything.
Thanks,
Charlie
Just an update - I was completely barking up the wrong tree with this.
24bit mode is only supported in lvgl@9 - and the settings for using the display are:
pixel_mode: 24bit
color_depth: 24
Thanks,
Charlie
By any chance, did anyone manage to make the audio work?
I got to a point where I hear something, but the sound is heavily distorted.
…and no mic.
What I have seen from the schematics is that the codec and mic are on the same i2c bus.
Found a repo that extends the audio module of esphome with pipelines and, according to the docs… It /should/ work… But did not get that far.
32bit should work with LVGL 8.4 (I played with it before on other devices), but ESPHome’s mipi_dsp driver only supports 16 or 24 at the moment.
Have you tried the audio configuration from here?
I just tried the Media Player from Home Assistant using those settings and it seemed to work well. The mic was also receiving data, but I haven’t done much testing with it yet.
RGB888 is new for lvgl 9: Changes in master (v9 development) · Issue #4011 · lvgl/lvgl · GitHub
Also from https://forum.lvgl.io/t/color-depth-24-is-deprecated/10618/2:
Actually a 24 bit color depth was never truly supported. Earlier it meant (X)RGB8888.
However, in v9 native rendering to RGB888 format will be supported.
Not sure if it made it into [lvgl] Migrate to library v9.4.0 by clydebarrow · Pull Request #12312 · esphome/esphome · GitHub or not.
Also - doing a quick scan, there’s 32bit for one display driver for lvgl.