DeskWave is a dedicated ESP32-D0WD-V3 desktop music controller for Linux. A small user-level host service reads real media state through MPRIS/D-Bus, prepares album artwork, and streams authenticated updates to the controller over the local network.
The 0.1.0 software is implemented for the reference hardware and covered by
clean firmware builds, portable firmware tests, host unit/integration tests,
formatting, lint, and strict type checking. Physical assembly and hardware
behavior must still be verified on the target device with the documented
smoke test; no physical result is inferred from
the automated checks.
- Shows actual title, artist, album, artwork, player, volume, playback state, and smoothly extrapolated progress.
- Derives a restrained accent palette from each validated cover on the Linux host, then uses it for a borderless artwork glow, control tints, and progress accents on the ESP32.
- Controls play/pause, previous/next, volume, mute, seeking, shuffle, repeat, and manual MPRIS player selection where the active application supports it.
- Works with Spotify, VLC, browsers, and other Linux applications that expose the standard MPRIS interface.
- Provisions Wi-Fi through a password-protected temporary access point; no firmware rebuild is needed to change networks.
- Discovers the host with mDNS and pairs with a one-time six-digit code. All state, control, player-list, and artwork routes require the resulting random bearer token.
- Recovers from Wi-Fi loss, host restart, desktop suspend/wake, player exit, malformed messages, and artwork failures without blocking physical input.
- Keeps the last valid cover and its matching palette through artwork loading, brief MPRIS metadata gaps, pause, and short reconnects. A deliberate branded fallback replaces them only when the active track is confirmed to have no usable artwork.
- Provides Now Playing, Device, Settings, Actions, About, provisioning, pairing, reconnecting, and error screens. With no active media, the idle state becomes an animated Spotify clock using the host's local time and date. Now Playing shows synchronized lyrics when LRCLIB has timestamped lines for the track.
- Keeps the panel and song-reactive rear RGB light at their selected active brightness; inactivity never dims either output automatically.
- Acts only as a remote control and display. Audio continues playing on the selected PC or phone; DeskWave never receives or outputs the audio stream.
MPRIS player -> session D-Bus -> DeskWave Host -> HTTP/WebSocket over LAN
|
v
ESP32-D0WD-V3 -> ILI9341 display + touch controls
The host and device communicate only on the LAN. The service does not require Spotify developer credentials or a cloud account. See Architecture and Protocol for the component and wire-level design.
The following is a documentation layout diagram, not a photograph, generated render, or claim of physical-device verification:
+------------+--------------------------------+
| | TRACK TITLE |
| ARTWORK | Artist |
| | 01:42 ========----- 03:58 |
| | LYRICS |
| | previous line |
| | > current synchronized line |
| | next line |
+------------+--------------------------------+
| Host connected PREV PLAY NEXT 72% |
+---------------------------------------------+
The implemented 320×240 interface also includes responsive volume and command feedback overlays, device/player selection, settings, actions, about, idle, provisioning, pairing, reconnecting, and explicit error states. Its actual appearance and display orientation remain part of the physical smoke test.
- ESP32-D0WD-V3 Revision 3.1 board (classic ESP32 module)
- 240×320 SPI ILI9341 display used in 320×240 landscape orientation
- XPT2046-compatible resistive touch overlay
- Common-anode RGB status LED with suitable current limiting
- Stable 3.3 V logic, appropriate display power, and a shared ground
All board-specific assumptions are centralized in
firmware/include/config/hardware_config.h.
The exact reference pinout and electrical cautions are in
Wiring. Confirm the wiring before applying power.
The published firmware target is esp32-d0wd-v3, backed by PlatformIO's
esp32dev definition for the locally verified ESP32-D0WD-V3 Revision 3.1 board.
It uses the known-good ILI9341/XPT2046 wiring and touch controls: tap the footer
controls, swipe vertically to turn the encoder, and swipe horizontally for
previous/next. Build and flash it with:
.tools/bin/pio run -e esp32-d0wd-v3
.tools/bin/pio run -e esp32-d0wd-v3 -t upload --upload-port /dev/ttyUSB1On the headerless Now Playing screen, tap the footer controls directly. Tap the
tiny top-right LINK/RETRY target to open the full action palette. A phone is controllable when
it is exposed to the Linux desktop as an MPRIS player, such as through KDE
Connect; otherwise the host has no phone media session to command.
git clone https://github.com/snowpyn/deskwave.git
cd deskwaveOn a Linux desktop with Python 3.11 or newer:
./host/scripts/install.sh --enable
~/.local/bin/deskwave-host doctorThe installer creates an isolated virtual environment at
~/.local/share/deskwave/venv, installs the CLI symlink at
~/.local/bin/deskwave-host, copies a user configuration if one is absent,
and enables the hardened systemd --user service. It never requires root.
For a foreground development run instead:
python3 -m venv .venv
.venv/bin/python -m pip install -e 'host[dev]'
.venv/bin/deskwave-host runThe service listens on TCP port 8765 and advertises
_deskwave._tcp.local. over mDNS. If a firewall is active, permit trusted-LAN
access to TCP 8765 and mDNS UDP 5353. Do not expose the service to the public
internet.
python3 -m venv .tools
.tools/bin/python -m pip install platformio==6.1.19
.tools/bin/pio run -e esp32-d0wd-v3
.tools/bin/pio device list
.tools/bin/pio run -e esp32-d0wd-v3 -t upload --upload-port /dev/ttyUSB1Use the port reported for the connected ESP32-D0WD-V3 board; the Revision 3.1
board is expected on /dev/ttyUSB1 in the verified workstation setup. The
initial release deliberately uses a recoverable wired update path; unsigned OTA
updates are not implemented.
On first boot, the screen shows a unique DeskWave-xxxx access-point name and
random 12-character password.
- Connect a phone or laptop to that access point.
- Open
http://192.168.4.1if the setup page does not appear automatically. - Enter the destination Wi-Fi network and submit.
The temporary AP stops after the credentials are committed. DeskWave retries failed connections with bounded exponential backoff rather than rebooting.
Once DeskWave discovers the host, it displays a six-digit code. Approve that specific pending request on the desktop:
deskwave-host pair
deskwave-host pair 123456Use the code shown on your device. After the device collects its approval, the paired record keeps only a SHA-256 hash of the issued token; the device stores the token in its NVS settings namespace. Pending plaintext approval tokens are deleted on collection or five-minute expiry.
Launch an MPRIS-compatible application and start playback. DeskWave selects an actively playing application deterministically. On the Device screen, turn the encoder and press it to choose another detected player manually.
| Touch gesture | Action | | --- | --- | --- | | Tap Shuffle | Toggle shuffle | — | | Tap Previous / Next | Previous / next track | Hold to seek backward / forward | | Tap center Play | Play/pause | Hold to mute/unmute | | Tap More | Open/close Actions | — | | Vertical swipe | Volume ± configured step | — | | Horizontal swipe | Previous / next track | — |
On the Actions screen, tap Shuffle or Repeat to change them. Unsupported MPRIS capabilities are shown as unavailable and are never fabricated.
On the Settings screen:
- Swipe vertically to select a row.
- Tap a row to change brightness, volume step, or the default startup screen.
- Select Factory reset, then hold the center Play area to confirm.
deskwave-host run Run in the foreground
deskwave-host status Query the local health endpoint
deskwave-host devices List paired devices and last-seen times
deskwave-host pair [CODE] List or approve a pending device
deskwave-host revoke DEVICE_ID Revoke one device token
deskwave-host doctor Check config, binding, mDNS, D-Bus, and MPRIS
deskwave-host version Show host and protocol versions
Runtime locations follow the Linux XDG conventions:
| Purpose | Default path |
|---|---|
| Configuration | ~/.config/deskwave/config.toml |
| Processed artwork and palette cache | ~/.cache/deskwave/artwork/ |
| Synchronized lyrics cache | ~/.cache/deskwave/lyrics/ |
| Pairing state | ~/.local/state/deskwave/devices.sqlite3 |
See host/config.example.toml and
host/README.md for host options and service operations.
Host settings live in ~/.config/deskwave/config.toml; the installer creates a
documented starter file without overwriting an existing one. Bind address,
port, log level, preferred player, artwork size limits, lyrics lookup, and
private-artwork-host policy can be changed there. Environment overrides are listed in
host/config.example.toml.
Artwork downloading, validation, SHA-256 hashing, 320x320 normalization, palette extraction, and content-addressed caching happen on the Linux host. The device receives only the normalized JPEG and four packed theme colors; it never extracts a palette or decodes JPEG data on animation frames.
Playback metadata remains UTF-8 end to end. The firmware automatically selects its bundled proportional Japanese font for non-ASCII titles, artists, and lyric lines; long strings are shortened only at UTF-8 character boundaries, and long titles use the normal continuous marquee.
Device settings are changed on the Settings screen and stored in versioned NVS. They include brightness, default screen, and volume step. The selected brightness remains active until it is changed. The first-boot provisioning page also accepts an optional host address and port for networks where mDNS is not available; leave the address blank to use automatic discovery. Ordinary users do not need to edit firmware source for Wi-Fi, pairing, display preferences, or player selection.
For a dedicated device that must join one known network on first boot, copy
firmware/include/config/device_secrets.example.h to device_secrets.h and
fill in the private values. The real file is ignored by Git; the firmware saves
the profile to NVS. Ordinary builds retain the provisioning portal.
Smart Shuffle is intentionally not fabricated. MPRIS and Spotify's supported playback-control API expose shuffle as on/off only, so the Actions screen marks Smart Shuffle unavailable while normal shuffle and repeat remain live controls.
Firmware:
.tools/bin/pio run -e esp32-d0wd-v3 -t clean
.tools/bin/pio run -e esp32-d0wd-v3
.tools/bin/pio test -e nativeHost:
.venv/bin/ruff format --check host scripts
.venv/bin/ruff check host scripts
.venv/bin/mypy host/src
.venv/bin/pytest -q host/tests --ignore=host/tests/integration
.venv/bin/pytest -q host/tests/integrationVersion and C++ formatting:
.venv/bin/python scripts/check_version.py
find firmware -type f \( -name '*.cpp' -o -name '*.h' \) -print0 \
| xargs -0 .venv/bin/clang-format --dry-run --WerrorGitHub Actions runs the same build, format, lint, type, unit, integration, and
wheel checks on pull requests and pushes to main. The dedicated
hardware smoke test remains a physical gate and
cannot be replaced by CI.
Start with deskwave-host doctor, the user-service journal, and the status shown
on the device. The complete troubleshooting guide
covers display bring-up, controls, provisioning, discovery/firewalls, pairing,
MPRIS detection, metadata, artwork, command rejection, service startup,
suspend/wake recovery, and factory reset.
DeskWave is designed for a trusted private LAN. Pairing, WebSocket sessions, artwork, and player listing are authenticated, inputs and response sizes are bounded, pairing is rate-limited, and private-network artwork fetches are blocked by default to reduce SSRF risk. Transport is HTTP/WebSocket rather than TLS, so network confidentiality depends on the trusted LAN. See SECURITY.md before deployment.
- Linux/MPRIS is the only production host backend in
0.1.0; Windows and macOS can be added behind the existing backend abstraction. - Synchronized lyrics depend on LRCLIB coverage and a network lookup on the first play. Results, including unavailable and instrumental states, are cached locally; lyrics lookup can be disabled in the host configuration.
- The published firmware targets the locally verified ILI9341/XPT2046 ESP32-D0WD-V3 Revision 3.1 wiring profile; other boards and displays require a hardware adapter in the centralized config layer.
- Firmware updates are wired through PlatformIO. Safe signed OTA is reserved for a later release.
- Automated software verification does not prove display orientation, electrical integrity, encoder direction, RF performance, or end-to-end latency on a physical assembly. Those items are explicitly pending until the smoke-test record is completed.
The first release intentionally keeps risky or provider-specific expansion out of the production path. Candidate follow-up work includes signed and rollback-safe OTA, additional centralized hardware profiles, native Windows and macOS host backends, optional provider plugins, and measured UI/control performance data from qualified hardware. Future lyrics work can add provider plugins while retaining the bounded, timestamped device protocol and local progress-driven highlighting.
.github/workflows/ Firmware and host CI
docs/ Architecture, protocol, wiring, recovery, and test guides
firmware/ ESP32 application profiles and portable core tests
host/ Linux package, systemd unit, installer, and tests
scripts/ Version/build support
platformio.ini Reproducible firmware environments
VERSION Authoritative product version
DeskWave is licensed under the MIT License. Contributions should follow CONTRIBUTING.md.