ESP32-4848S040 Media Controller with ESPHome and LVGL

Build an ESP32-4848S040 Media Controller with ESPHome and LVGL That Actually Works

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

ESP32-4848S040 Media Controller with ESPHome and LVGL

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:

  1. Clone the repo
  2. Fill secrets.yaml with WiFi credentials and API keys
  3. Edit substitutions — set your player entity and HA URL
  4. Flash via ESPHome dashboard
  5. 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


Questions or improvements? Open an issue on GitHub or drop a comment below. 👇

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.