Want a dedicated touchscreen panel to control your music without touching your phone? 🎵 This guide shows you exactly how I built an ESP32-4848S040 Media Controller with ESPHome and LVGL — a wall-mounted display that talks to Music Assistant in Home Assistant, shows album art, animates a progress arc, and lets you control playback with a tap.
The full config is open source on GitHub: HA_MediaController_ESP324848S040_EspHome_LVGL
Why Build an ESP32-4848S040 Media Controller with ESPHome and LVGL?
The ESP32-4848S040 Media Controller with ESPHome and LVGL approach gives you something phone apps and voice assistants can’t — a always-on, glanceable display that shows what’s playing right now, with physical touch controls that respond instantly. 🖥️
No app to open. No wake word to say. Just tap.
The ESP32-4848S040 is a compact all-in-one board with:
- ESP32-S3 dual-core 240MHz MCU
- 480×480 round-corner IPS display (ST7701S over RGB bus)
- GT911 capacitive touchscreen
- 8MB PSRAM octal mode
- 16MB Flash
Everything — display, touch, and compute — on one board. No soldering needed for the basic setup. 🛠️
What the ESP32-4848S040 Media Controller with ESPHome and LVGL Does

This ESP32-4848S040 Media Controller with ESPHome and LVGL handles the following:
- 🎵 Shows current track title and artist with auto-scrolling for long names
- 🖼️ Displays album art as a full-screen background, dynamically updated per track
- 🔄 Renders an animated progress arc that moves in real time as the track plays
- ▶️ Touch buttons for play/pause, previous/next track, volume up/down
- 📋 A dedicated playlists page with 4 configurable slots
- 🎨 Color and opacity of every UI element adjustable from Home Assistant — no reflash needed
- 💡 Backlight brightness controlled from HA as a standard dimmer
Key Technical Decisions
ESPHome + LVGL: Why This Combination?
ESPHome has native LVGL support since 2024. This means you define a touchscreen UI entirely in YAML — buttons, arcs, labels, images — without writing raw C++ UI code. LVGL handles touch events, scrolling labels, arc indicators — everything a media player UI needs.
ST7701S: SPI Init + RGB Pixel Bus
The display uses a split architecture that trips up many builders. The ST7701S initializes over SPI (register commands), but streams pixel data over a 16-bit parallel RGB bus. ESPHome needs both declared:
yaml
spi:
clk_pin: GPIO48
mosi_pin: GPIO47
interface: hardware
id: spihwd
display:
- platform: st7701s
spi_id: spihwd
de_pin: 18
hsync_pin: 16
vsync_pin: 17
pclk_pin: 21
data_pins:
red: [11, 12, 13, 14, 0]
green: [8, 20, 3, 46, 9, 10]
blue: [4, 5, 6, 7, 15]
Miss the spi: block and ESPHome refuses to compile. 🔧
Dynamic Album Art via HA Media Proxy
Music Assistant doesn’t expose album art as a stable URL. Home Assistant provides a media proxy endpoint that always returns the current track’s cover:
/api/media_player_proxy/media_player.your_player?token=TOKEN&cache=HASH
The cache parameter changes with every new track — HA’s cache-busting. By subscribing to entity_picture_local, the ESP32-4848S040 Media Controller with ESPHome and LVGL gets notified on every track change and fetches fresh art:
yaml
- platform: homeassistant
id: entity_picture
entity_id: ${player_entity}
attribute: entity_picture_local
on_value:
then:
- lambda: |-
if (x == "None" || x.empty()) return;
std::string full_url = "${ha_url}" + x;
id(album_art).set_url(full_url.c_str());
id(album_art).update();
The ha_url is a substitution — set it once, used everywhere. 💡
Real-Time Progress Arc Without Hammering HA
Home Assistant’s media_position attribute only updates on state transitions (pause, skip, seek) — not every second. To animate the arc smoothly, the ESP32-4848S040 Media Controller with ESPHome and LVGL runs a local 1-second interval that increments position while playing, and snaps back to the real HA value on each update:
yaml
interval:
- interval: 1s
then:
- if:
condition:
lambda: return id(player_state).state == "playing";
then:
- lambda: |-
float pos = id(media_position).state + 1.0f;
float dur = id(media_duration).state;
if (dur > 0) {
if (pos > dur) pos = dur;
id(media_position).publish_state(pos);
lv_arc_set_value(id(progress_arc),
(int)((pos / dur) * 100));
}
Smooth animation, zero extra HA requests. ✅
Cyrillic + European Characters
LVGL’s built-in Montserrat only covers basic Latin. The ESP32-4848S040 Media Controller with ESPHome and LVGL uses Roboto from Google Fonts with an explicit glyph set covering Cyrillic, French, German, Polish, Spanish, Scandinavian and more. A YAML anchor avoids repeating the glyph string four times:
yaml
font:
- file: "gfonts://Roboto"
id: roboto_16
size: 16
glyphs: &common_glyphs >-
АБВГДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯабвгдеєжзиіїйклмнопрстуфхцчшщьюя...
- file: "gfonts://Roboto"
id: roboto_36
size: 36
glyphs: *common_glyphs
Button icons (⏮ ▶ ⏭ 🔊) still use Montserrat via LV_SYMBOL_* — those are baked into LVGL.
UI Customization from Home Assistant
One of the most practical features of this ESP32-4848S040 Media Controller with ESPHome and LVGL: every color and opacity is a live HA entity. 🎛️
After flashing, Home Assistant automatically discovers:
- 7 text fields for hex colors — title, artist, volume, buttons, decoration circle, progress ring background and fill
- 4 number sliders (0–255) — opacity for album art, progress ring, decoration circle, buttons
- 1 light entity — backlight brightness dimmer
Change a color field to ff6600, display updates instantly. No reflash. Values persist across reboots via restore_value: true.
Power Supply Warning ⚡
During development the ESP32 rebooted every time I changed backlight brightness from HA. Root cause: a cheap USB charger couldn’t handle the current shift when PWM changed, causing a brownout reset.
Switching to a power bank fixed it completely. Use a proper 5V/2A supply with good voltage regulation. Cheap phone chargers are not sufficient for a display board.
Getting Started
Full setup instructions are in the README on GitHub. Quick steps:
- Clone the repo
- Fill
secrets.yamlwith WiFi credentials and API keys - Edit
substitutions— set your player entity and HA URL - Flash via ESPHome dashboard
- In HA → ESPHome integration → enable “Allow device to perform Home Assistant actions”
That last step is the most commonly missed one — without it, none of the touch buttons do anything. 😅
Related Posts
- Building a Touch-Controlled Home Assistant Dashboard on an ESP32-S3 with LVGL & ESPHome
- DIY a Smart Reverse Osmosis TDS & Flow Meter with ESPHome and ESP32-C6
- How to Display Home Assistant Sensor Data on the GeekMagic SmallTV S3
Questions or improvements? Open an issue on GitHub or drop a comment below. 👇


Leave a Reply