A countertop smart display built on Raspberry Pi with a Home Assistant kiosk, always-on wake word detection, voice pipeline, a choice of music backends (Music Assistant and/or Caldera), and full HA device integration via MQTT.
This is a project I work on in the evenings, and the initial build and scripting was created in conjunction with Claude AI. I make no guarantees or warranties, and suggest you read through the code base if you aren't comfortable executing code written by an AI agent. I've made every effort to review it and guide it, but as a side project my biggest concern was having something functional and re-usable. I am 100% open to any pull requests or changes anyone wants to submit.
| Component | Details |
|---|---|
| SBC | Raspberry Pi 4B (1 or 2 GB RAM) |
| Microphone / Audio | KEYESTUDIO ReSpeaker 2-Mic Pi HAT (WM8960 codec) |
| Display | Raspberry Pi Official 7" DSI Touchscreen |
| Speaker | 3W 8Ω |
| OS | Raspberry Pi OS 64-bit (Trixie / Debian 13), kernel 6.12.x |
| Compositor | labwc (Wayland) |
- HA Lovelace kiosk — Chromium in kiosk mode, launches automatically after boot and waits for HA to be reachable before opening
- Wake word detection — OpenWakeWord via Linux Voice Assistant (
hey_jarvisby default, configurable) - Voice pipeline — LVA connects to HA via the ESPHome integration; includes mute control directly from HA
- Choice of music backend — install Music Assistant (Sendspin native player, appears automatically in MA 2.7+), Caldera headless (Plex-controlled), or both as independent players
- MQTT dashboard refresh — reload the kiosk page or navigate it to any URL over MQTT; recovers displays after an HA reboot and doubles as a way to push test dashboards
- MQTT auto-discovery — Device registers itself in HA with Voice Volume, Media Volume, Brightness, Mic Sensitivity, a Reload Dashboard button, and a Dashboard URL text box — no YAML needed
- Touch scrolling — Daemon translates touchscreen swipe gestures into scroll-wheel events for labwc/Wayland
- Independent volume channels — TTS/voice and media are separate ALSA softvol streams, each with its own HA slider
- Per-device mic tuning — Mic sensitivity is adjustable from HA, persists across reboots, useful for different room sizes and placements
configure.sh # Main setup script — run once on a fresh install (idempotent, re-runnable)
mqtt-bridge.py # MQTT auto-discovery bridge for HA device entities
touch-scroll.py # Touch-to-scroll daemon (uinput virtual device)
lovelace/
smart-display-card.js # Custom Lovelace card (copy to HA /config/www/)
ha-configuration.md # Full HA config reference
CLAUDE.md # Technical reference for AI-assisted development
- Fresh Raspberry Pi OS 64-bit (Trixie) install
- Pi connected to your network
- Home Assistant running with:
- ESPHome integration installed
- MQTT integration (Mosquitto) installed and configured
- Music Assistant 2.7+ (optional, for the Sendspin player)
- Plex account + a reachable Plex Media Server (optional, for the Caldera player)
Clone this repo onto the Pi and run the setup script as your normal user (not root):
git clone https://github.com/YOUR_USERNAME/TuneoutDisplay.git
cd TuneoutDisplay
chmod +x configure.sh
./configure.shThe script prompts you for:
- Device name (used as the HA device name and Music Assistant player name)
- Home Assistant URL
- Wake word model name
- Music player —
music-assistant,caldera, orboth - Caldera ALSA output device (only if Caldera is selected — defaults to
seeed_media) - Lovelace kiosk URL (optional — skip to set up kiosk manually later)
- MQTT broker host, port, username, and password
Settings are saved after the first run — re-running the script will pre-fill all prompts with your previous values, so you only need to change what's different.
The script installs and configures everything automatically, then offers to reboot when done.
The ReSpeaker driver (seeed-voicecard) is an out-of-tree DKMS module that breaks
on newer kernels — 6.18 changed several ASoC APIs and will fail the build mid-upgrade,
leaving apt half-broken. To keep every display identical and reproducible,
configure.sh holds the kernel at the current 6.12 series before upgrading, so
re-running the script is always safe. Userspace still gets updates; only the kernel
is frozen.
This assumes you flash displays from a 6.12-era Raspberry Pi OS image. If a device
has already moved to a newer kernel, re-image it to bring it back to the standard
state — an in-place downgrade isn't reliable on Raspberry Pi. To move the whole fleet
forward later, validate the driver on the new kernel on one device first, then lift
the hold (sudo apt-mark unhold linux-image-rpi-v8 …).
-
Music Assistant (Sendspin) needs no extra steps — the player appears in MA 2.7+ automatically and routes through the
seeed_mediasoftvol device. -
Caldera requires two manual steps the script can't automate, printed at the end of its install section:
~/caldera-music/caldera-music --login # one-time Plex device login systemctl --user enable --now caldera-music # start it after logging in
Because Caldera's ALSA-device selection couldn't be verified, the build forces its audio onto your chosen device (default
seeed_media) viaALSA_CONFIG_PATHso it shares the dmix path with voice/TTS. Verify after first play: start Plexamp audio and confirm voice/TTS still works simultaneously. If Caldera seizes the card alone, check for a native output-device setting in its config and point it atseeed_media. Caldera also self-updates in the background, so its version is not pinned by this build.
After reboot, in HA go to:
Settings → Devices & Services → Add Integration → ESPHome
| Field | Value |
|---|---|
| Host | <hostname>.local |
| Port | 6053 |
Once added, open the device and click "Set Up Voice Assistant" to assign it to a voice pipeline (Whisper STT + Piper TTS recommended).
In HA go to Settings → Devices & Services → MQTT and look for your device name. It should appear automatically with these entities:
- Voice Volume (number)
- Media Volume (number)
- Brightness (number)
- Mic Sensitivity (number)
- Reload Dashboard (button)
- Dashboard URL (text)
If it doesn't appear, check that MQTT discovery is enabled in the MQTT integration settings.
The custom card gives you volume, brightness, voice status, and a mute toggle in any HA dashboard.
- Copy
lovelace/smart-display-card.jsto/config/www/on your HA instance - In HA go to Settings → Dashboards → ⋮ → Resources → Add
- URL:
/local/smart-display-card.js - Type: JavaScript module
- URL:
- Add the card to a dashboard:
type: custom:smart-display-card
name: My Display
satellite_entity: assist_satellite.YOUR_DEVICE
tts_volume_entity: number.YOUR_DEVICE_tts_volume
media_volume_entity: number.YOUR_DEVICE_media_volume
brightness_entity: number.YOUR_DEVICE_brightness
mute_entity: switch.YOUR_DEVICE_mute # optional — enables chip tap-to-mute
mic_gain_entity: number.YOUR_DEVICE_mic_gain # optionalFind your exact entity IDs under Developer Tools → States and search for your device name.
The status chip in the card header shows the current pipeline state (Standby / Listening / Responding / Muted) and acts as a mute toggle when mute_entity is configured — tap to mute, tap again to unmute.
Install Swipe Navigation from HACS (Frontend section), then add /hacsfiles/swipe-navigation/swipe-navigation.js as a Lovelace resource. No card config needed — it activates automatically on all views.
The kiosk Chromium launches with --remote-debugging-port=9222, and the MQTT
bridge drives it over the Chrome DevTools Protocol. This lets you reload the page
or send it to a new URL without unplugging anything — the original reason being
that displays lose their HA connection during an HA update and otherwise need a
physical reboot.
Two HA entities are created automatically: a Reload Dashboard button and a Dashboard URL text box. You can also publish to the raw command topic:
Topic: smart-display/<device_id>/dashboard/set
Payloads:
reload reload the current page (ignores cache)
home navigate back to the configured kiosk URL
http://homeassistant.local:8123/lovelace/test navigate to any URL
Example with mosquitto_pub:
mosquitto_pub -h <broker> -u <user> -P <pass> \
-t 'smart-display/<device_id>/dashboard/set' -m reloadRecovering automatically after an HA reboot. If your broker is HA's Mosquitto add-on, it is also offline during the reboot, so the reload must fire after HA returns. Add an HA automation that reloads the displays on startup:
automation:
- alias: "Reload smart displays after HA restart"
trigger:
- platform: homeassistant
event: start
action:
- delay: "00:00:30" # give the frontend time to come up
- action: button.press
target:
entity_id: button.<device_id>_dashboard_reloadThe debugging port binds to localhost only, but the build adds
--remote-allow-origins=*so the local bridge can complete the WebSocket handshake. Anyone with shell access to the Pi could drive the browser — fine for a kiosk appliance on a trusted LAN, worth noting if your threat model is stricter.
All services are managed by systemd and start automatically on boot.
| Service | Description |
|---|---|
linux-voice-assistant |
Wake word detection and voice pipeline (ESPHome protocol) |
sendspin |
Music Assistant native player (only if music player is music-assistant/both) |
caldera-music (user) |
Caldera headless Plex player (only if music player is caldera/both; runs as a --user service) |
smart-display-audio-init |
Restores ALSA mixer state after seeed DKMS module loads |
smart-display-mqtt |
MQTT bridge for HA auto-discovery + dashboard refresh |
smart-display-touch-scroll |
Translates touchscreen swipe gestures into scroll-wheel events |
Check all service status:
sudo systemctl status linux-voice-assistant sendspin \
smart-display-audio-init smart-display-mqtt smart-display-touch-scrollFollow live voice pipeline logs:
journalctl -u linux-voice-assistant -fHardware: WM8960 (seeed2micvoicec)
│
▼
seeed_dmix ← ALSA dmix (allows multiple simultaneous writers)
│
seeed_shared ← plug over dmix (general use)
┌─┴─┐
seeed_tts seeed_media ← softvol streams (independent volume controls)
│ │
LVA / mpv Sendspin
(voice/TTS) (music)
Volume controls:
- TTS Volume —
amixer -c seeed2micvoicec cset "name=TTS Volume" 80% - Media Volume —
amixer -c seeed2micvoicec cset "name=Media Volume" 80%
Note:
pipewire-alsamust not be installed — it intercepts ALSA calls at the library level and prevents dmix from working. The setup script explicitly removes it. PipeWire is used only for microphone input.
Re-run ./configure.sh and enter a different wake word model name at the prompt. Supported models include hey_jarvis, ok_nabu, hey_mycroft, and others from OpenWakeWord.
Adjust the Mic Sensitivity slider in HA (the MQTT entity). Higher values boost the microphone preamplifier, improving far-field wake word detection. The value persists across reboots. Default is 63% (0 dB on the WM8960 Capture PGA).
Edit /usr/local/bin/touch-scroll.py and adjust TICKS_PER_SCREEN (higher = faster scroll), then restart the service:
sudo systemctl restart smart-display-touch-scrollRe-run ./configure.sh and enter a new kiosk URL at the prompt, or edit ~/.config/labwc/autostart directly.
Audio settings don't persist after reboot
The seeed DKMS module loads after alsa-restore runs. The smart-display-audio-init service handles this — check its status and logs. Speaker volume is also re-applied in ~/.config/labwc/autostart as a safety net.
"No MCLK configured" in dmesg / aplay fails
The seeed-voicecard DKMS module was built for a different kernel than the one currently running (common after apt full-upgrade). Fix:
sudo dkms build -m seeed-voicecard -v 0.3 -k $(uname -r) --force
sudo dkms install -m seeed-voicecard -v 0.3 -k $(uname -r) --force
sudo rebootMQTT entities don't appear in HA
- Check credentials in
/etc/smart-display/mqtt.env - Verify MQTT discovery is enabled in HA's MQTT integration settings
- Check
journalctl -u smart-display-mqtt -ffor connection errors
Voice assistant not discovered by HA after reboot
Ensure the ESPHome integration is installed in HA and add the device via Settings → Devices & Services → ESPHome using <hostname>.local port 6053.
Touch scrolling not working
Check the daemon is running: systemctl status smart-display-touch-scroll
View logs: journalctl -u smart-display-touch-scroll -f
This project is licensed under the terms of the GNU General Public License v3.0. See the LICENSE.txt file for details.