A wall panel that only shows what is playing is fine — until you want the live queue, your playlists and the room’s lights on the same screen. This project is a Music Assistant ESP32 media controller packaged so anyone can install it: a HACS-ready Home Assistant integration plus a maintained ESPHome package for the ESP32-S3-4848S040. 🎵
The Music Assistant ESP32 media controller shows album art, a live progress arc, a bounded queue, playlists and four room controls on a 480×480 touchscreen — and every installation-specific value is picked in the Home Assistant UI, not hardcoded in firmware. Full source: music-assistant-esp32s34848s040-controller
What This Music Assistant ESP32 Media Controller Adds 🧭
My earlier ESP32-4848S040 panel covered the essentials: track title and artist, album art, an animated progress arc, playback buttons and fixed playlist slots, all restyled live from Home Assistant. This build keeps that screen and adds the parts that need real data out of Music Assistant.
The Music Assistant ESP32 media controller ships as:
- 🧩 A custom integration (
media_controller) installed through HACS - ⚙️ Config Flow and Options Flow instead of YAML editing
- 📦 A remote ESPHome package — your device YAML is ~40 lines, not 2,329
- 📃 A live queue page and a real playlist list pulled from your Music Assistant library
- 💡 Four optional room controls — Light 1, Light 2, Fan and AC
Hardware Stays Exactly the Same 🛠️
The Music Assistant ESP32 media controller runs on the ESP32-S3-4848S040: ESP32-S3, 16 MB flash, octal PSRAM at 80 MHz, an ST7701S 480×480 RGB panel and a GT911 capacitive touchscreen.
⚠️ Treat the display block as hardware-critical. The ST7701S init sequence, RGB/SPI/I²C pin maps, sync porches and the 12 MHz pixel clock are the values that actually work on this panel. Changing a porch or the clock is the fastest way to get a rolling or blank screen. If you want the deep dive on why those numbers matter, I covered the display bus in the original LVGL dashboard build.
Architecture: One Integration, One Package 🧩
The Music Assistant ESP32 media controller integration does not open its own Music Assistant connection. It resolves the config entry from the entity registry and reuses the client the official integration already owns:
registry_entry = er.async_get(hass).async_get(player_entity_id)
if registry_entry.platform != "music_assistant":
raise MusicAssistantUnavailable(...)
config_entry = hass.config_entries.async_get_entry(
registry_entry.config_entry_id)
One connection, one credential, zero hardcoded UUIDs. That single decision is what makes this Music Assistant ESP32 media controller installable by someone who is not me. 🙂
The Bounded Queue Window 📦
An ESP32 cannot hold a 500-track queue. It never gets one. The Music Assistant ESP32 media controller requests a fixed window around the current item:
DEFAULT_QUEUE_WINDOW_BEFORE = 5
DEFAULT_QUEUE_WINDOW_SIZE = 50
QUEUE_REFRESH_DELAY = 3.0
PLAYLIST_REFRESH_INTERVAL = timedelta(hours=6)
A queue refresh fires only when media_title actually changes, then waits three seconds. Skip five tracks quickly and four of those waits get cancelled — a generation counter plus an asyncio.Lock guarantee no overlapping requests and no stale payload overwriting a newer one.
Proxy Entities for Room Controls 💡
The switches page of the Music Assistant ESP32 media controller handles Light 1, Light 2, Fan and AC. Rather than baking your entity IDs into firmware, the integration creates four proxy entities that mirror whatever you picked in the UI. Remap a light in Options Flow and the panel follows — no reflash. An unconfigured or unavailable proxy simply reports unavailable while everything else keeps working. If you run grouped lights, my notes on syncing two Matter lights pair well with this page.
Wake Touch That Doesn’t Press a Button ✋
When the screen sleeps, LVGL is paused — not just dimmed. The wake touch is consumed:
on_touch:
- if:
condition:
lambda: return !id(backlight).remote_values.is_on();
then:
- light.turn_on: backlight
on_release:
- if:
condition:
lvgl.is_paused:
then:
- lvgl.resume:
LVGL resumes on release, so the gesture that woke the panel can never click, long-press, swipe, skip a track or toggle your AC. Grabbing the panel in the dark stops being a gamble.
Prerequisites ✅
Before you flash anything, the Music Assistant ESP32 media controller expects four things to already be in place:
- A current Home Assistant install with HACS
- The official Music Assistant integration with at least one exposed
media_player - ESPHome for validation, compilation and flashing
- A dedicated Home Assistant long-lived access token for the REST transport — created in the next section
Secrets: What !secret Actually Means 🔑
The device YAML below is full of lines like ha_token: !secret media_controller_ha_token. That is not a placeholder you replace with a value — !secret is an ESPHome directive that says look this name up in secrets.yaml. The credential lives in that one file, and the config you paste, share or screenshot only ever contains the key name.
In the ESPHome Device Builder add-on, open the three-dot menu in the top-right corner and pick Secrets. On disk it is /config/esphome/secrets.yaml; from the CLI it sits next to your device YAML. The Music Assistant ESP32 media controller needs five entries there:
wifi_ssid: "YourNetwork"
wifi_password: "your-wifi-password"
media_controller_api_encryption_key: "base64-key-from-esphome"
media_controller_ota_password: "any-password-you-choose"
media_controller_ha_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
⚠️ Keep using your existing global secrets.yaml. Do not create a second one just for the Music Assistant ESP32 media controller. If your Wi-Fi keys are already named something else, rename the !secret references in the device YAML — do not rename working keys that your other ESPHome devices depend on.
Where Each Value Comes From
- Wi-Fi — your own network credentials, nothing special
- API encryption key — ESPHome Device Builder generates one when you create the device; copy it straight into
secrets.yaml - OTA password — you invent it; it is what protects wireless updates afterwards
- Home Assistant token — a long-lived access token, created in Home Assistant itself
To create that token: click your user name at the bottom of the Home Assistant sidebar, open the Security tab, scroll to Long-lived access tokens and choose Create token. Name it something you will recognise later, like media-controller.
⚠️ The token is shown exactly once. Copy it into secrets.yaml immediately — close that dialog without copying and your only option is deleting the token and creating a new one. Make it dedicated: one token for this panel, so you can revoke it from the same screen without breaking anything else you built.
⚠️ Never commit secrets.yaml to Git.
Worth knowing before you commit to this build: that token lives on the panel itself, because the Music Assistant ESP32 media controller fetches the queue, the playlists and the album art over the Home Assistant REST API. The ESP32 only ever sends it to the local ha_url you configure — never anywhere else — so keep the panel on a network you trust, the same as any other device holding a key to your Home Assistant.
Installing the Music Assistant ESP32 Media Controller ⚙️
There are two halves to a working Music Assistant ESP32 media controller: a Home Assistant integration and an ESPHome device. Do them in this order — the firmware needs entity IDs that do not exist until the integration has run.
Step 1 — Add the Music Assistant ESP32 Media Controller Repository to HACS
- Open HACS in the Home Assistant sidebar.
- Click the three-dot menu in the top-right corner and choose Custom repositories.
- Paste
https://github.com/VahaC/music-assistant-esp32s34848s040-controllerinto the repository field. - Set Type to Integration and click Add.
- Close the dialog, search HACS for Media Controller, open it and click Download.
⚠️ Restart Home Assistant before continuing. A freshly downloaded custom integration is not loaded until you do, and the next step will simply not find it. Settings → System → the power icon in the top-right → Restart Home Assistant.
Step 2 — Configure the Integration
Go to Settings → Devices & services → + Add integration and search for Media Controller. This single form is where the Music Assistant ESP32 media controller learns what it controls:
- Music Assistant player (required) — the dropdown only offers
media_playerentities that Music Assistant provides. If it is empty, Music Assistant is not set up yet or has no exposed player. - Light 1 and Light 2 (optional) — any
lightentity. - Fan and AC (optional) — these accept
switchentities, because they are meant for smart sockets. Afanorclimateentity will not show up here.

Leave any room control blank if you do not have one — the matching button on the panel simply reports unavailable. Submitting creates a device called Media Controller – your player name.
If you see “Select a media player provided by Music Assistant”, you picked a player from another integration — a Chromecast or a Sonos entity, for example, rather than the Music Assistant version of it.
Step 3 — Copy the Queue and Playlists Entity IDs
Open the device that was just created. Two entities on that page are the ones the firmware genuinely cannot run without: the Queue and Playlists sensors. The ESP32 pulls both over the REST API, so their exact entity IDs have to go into the device YAML. Click each one and copy its ID.

The Light 1, Light 2, Fan and AC entities on the same page are proxies, and they are optional. Their IDs only matter if you actually mapped room controls in Step 2 — the Music Assistant ESP32 media controller pulls nothing for them over REST, it just forwards taps to whatever those substitutions point at. Leave them unmapped and they simply stay unavailable while the rest of the panel carries on.
⚠️ Do not reuse the example IDs from this post. Home Assistant generates them from your device name, so yours will differ. This matters because a wrong queue_entity never throws an error — you just get a permanently empty queue page and no clue why.
Step 4 — Create the ESPHome Device
The firmware half of the Music Assistant ESP32 media controller begins life as an ordinary ESPHome device:
- Open ESPHome Device Builder and click + New device.
- Name it
media-controllerand continue. - Choose ESP32-S3 as the device type.
- Skip the install prompt — click Skip for now.
⚠️ Grab the API encryption key before you overwrite anything. The wizard generates it and writes it into the new device YAML. Open Edit, copy that key into secrets.yaml as media_controller_api_encryption_key, and only then replace the file contents. Lose it at this point and you have to re-adopt the device later.
Step 5 — Paste the Device Configuration
Still in Edit, delete everything the wizard generated and paste this instead. This whole file is your side of the Music Assistant ESP32 media controller — the rest is downloaded for you:
substitutions:
device_name: media-controller
friendly_name: Media Controller
player_entity: media_player.your_music_assistant_player
queue_entity: sensor.your_controller_queue
playlists_entity: sensor.your_controller_playlists
light1_entity: light.your_controller_light_1
light2_entity: light.your_controller_light_2
fan_entity: switch.your_controller_fan
ac_entity: switch.your_controller_ac
ha_url: "http://homeassistant.local:8123"
ha_token: !secret media_controller_ha_token
packages:
media_controller:
url: https://github.com/VahaC/music-assistant-esp32s34848s040-controller
ref: main
files:
- firmware/media-controller.yaml
refresh: 1h
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret media_controller_api_encryption_key
ota:
- platform: esphome
password: !secret media_controller_ota_password
Now edit the substitutions at the top. Replace player_entity with the same Music Assistant player you chose in Step 2, and queue_entity plus playlists_entity with the two IDs you copied in Step 3. Fill in the four room-control lines only if you actually mapped Light 1, Light 2, Fan or AC — otherwise leave them exactly as they are. Set ha_url to the address the panel will use — an IP such as http://192.168.1.10:8123 is more reliable than homeassistant.local on many networks.
⚠️ Never paste the 2,329-line firmware file into Home Assistant. The packages: block downloads it and all PNG assets at compile time. Remote ESPHome packages cannot resolve !secret, which is exactly why Wi-Fi, API encryption, OTA and the token stay in your small local file.
⚠️ Never change device_name on a device you already adopted. Home Assistant treats the new name as a completely different device, and you end up with a duplicate plus a pile of orphaned entities to clean up.
Step 6 — Validate and Flash Over USB
Time to build and flash the Music Assistant ESP32 media controller firmware:
- Save the file, then use the device card’s three-dot menu → Validate. Fix anything red before going further — a typo in an entity ID will not be caught here, but a YAML mistake will.
- Connect the board to your computer with USB-C.
- Three-dot menu → Install → Plug into this computer. Chrome or Edge is required for the browser flasher; Firefox and Safari will not offer the serial port.
- Wait. The first build pulls ESP-IDF and compiles LVGL, so ten minutes or more is normal. Later builds are much faster.
⚠️ Power the board properly. A weak phone charger can brown-out an ESP32-S3 driving a backlit 480×480 panel, which looks exactly like a firmware crash. Use a 5V supply that can hold 2A.
Every update after this one goes over the air: three-dot menu → Install → Wirelessly. No cable, no taking the panel off the wall.
Step 7 — Adopt It and Enable Actions
Home Assistant discovers the Music Assistant ESP32 media controller on its own. Go to Settings → Devices & services, find the ESPHome discovery card and click Configure. Paste the API encryption key if you are asked for it.
⚠️ This is the step everybody misses. Open the ESPHome entry for the device, click Configure, and tick “Allow the device to perform Home Assistant actions”. Every control on the panel — play, pause, next, volume, playlists, queue jumps, lights, fan, AC — is a Home Assistant action call. Without that checkbox the screen looks perfect and absolutely nothing responds to touch.
Restyling the Music Assistant ESP32 Media Controller from Home Assistant 🎛️
Adoption gives you a second Home Assistant device for the Music Assistant ESP32 media controller, this one owned by the ESPHome integration and named after your device_name. It reports its firmware version and MAC, and it carries every visual setting of the panel as a live entity. Change one and the screen updates immediately — no recompile, no reflash, no getting up.

Colours
Seven text fields, each taking a six-digit hex value with no leading #: Title Color, Artist Color, Volume Color, Buttons Color, Decoration Color, Progress Ring Color and Progress Ring Fill Color. Type ff6600 into Buttons Color and the buttons are orange before you finish blinking.
Opacity and Timing
Four sliders run from 0 to 255 — Album Art Opacity, Progress Ring Opacity, Decoration Opacity and Buttons Opacity — which is how you tune the album art from a faint wash to a full-bleed background. Screen Timeout is a fifth slider, adjustable from 5 to 120 seconds with a default of 15.
The Music Assistant ESP32 media controller also exposes a Backlight entity, and it is a genuine light in Home Assistant. Dim it on a schedule, kill it when you leave the house, or drive it from a presence sensor like any other lamp.
Every one of these values survives a reboot — they are stored on the device, not pushed down from Home Assistant at boot.
When the Screen Actually Sleeps 😴
Screen Timeout is easy to misread, so it is worth being precise: the backlight only switches off while nothing is playing. The check needs two things at once — the player must not be in the playing state, and the timeout must have elapsed since the last touch.
In practice that means the Music Assistant ESP32 media controller stays lit for the entire album, however long it runs, and only begins counting down once you pause or the queue ends. Touching the screen restarts the countdown. If you are wondering why your panel never sleeps, check whether the music simply never stopped.
First Boot: What You Should See 🔍
Start playing something in Music Assistant and watch the panel. A healthy Music Assistant ESP32 media controller settles into this within a few seconds of power-up:
- A clean 480×480 image — no rolling, torn edges or colour noise
- Track title, artist and album art, updating on every track change
- A progress arc that moves second by second instead of jumping when you pause or skip
- Swipe left or right on the player screen and it flips to the room controls; swipe again to come back
- Tap a queue entry and playback jumps to it — the rest of the queue stays intact
Getting Around the Four Pages 👆
Navigation is worth knowing before you start poking at the panel. The player screen and the room-controls screen sit a swipe apart: flick left or right and it slides across, flick again and you are back. Both directions do the same thing, so you never have to remember which way you came from.
The queue and playlist pages are deliberately off that swipe path. The Music Assistant ESP32 media controller gives each of them a dedicated button on the player screen, and each page has its own back button — so a stray swipe while you are scrolling a long queue cannot throw you onto a different page.
If Something Looks Wrong 🧯
Nearly every Music Assistant ESP32 media controller problem falls into one of these five:
- Queue or playlists page is empty — almost always a wrong
queue_entity/playlists_entitysubstitution or a bad token. The ESPHome log shows the HTTP status the panel got back. - Album art never appears — check that
ha_urlis reachable from the panel, not just from your laptop. - The first tap after waking does nothing — that one is deliberate, see the wake-touch section above.
If the queue ever looks stale after fiddling with Music Assistant directly, call the media_controller.refresh action and it repopulates immediately.
Related Posts
- Build an ESP32-4848S040 Media Controller with ESPHome and LVGL That Actually Works
- 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
Built your own Music Assistant ESP32 media controller, broke it, or improved it? Open an issue on GitHub or drop a comment below. 👇
