Skip to content

zmsaunders/TuneoutDisplay

Repository files navigation

TuneoutDisplay

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.

Full Disclosure

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.


Hardware

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)

Features

  • 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_jarvis by 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

Repo Structure

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

Setup

Prerequisites

  • 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)

1. Run the configuration script

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.sh

The 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 playermusic-assistant, caldera, or both
  • 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.

Kernel is pinned to 6.12

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 player notes

  • Music Assistant (Sendspin) needs no extra steps — the player appears in MA 2.7+ automatically and routes through the seeed_media softvol 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) via ALSA_CONFIG_PATH so 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 at seeed_media. Caldera also self-updates in the background, so its version is not pinned by this build.

2. Add the voice assistant to Home Assistant

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).

3. Verify MQTT device

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.

4. Add the Lovelace control card (optional)

The custom card gives you volume, brightness, voice status, and a mute toggle in any HA dashboard.

  1. Copy lovelace/smart-display-card.js to /config/www/ on your HA instance
  2. In HA go to Settings → Dashboards → ⋮ → Resources → Add
    • URL: /local/smart-display-card.js
    • Type: JavaScript module
  3. 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  # optional

Find 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.

5. Add swipe navigation between dashboard views (optional)

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.


Dashboard refresh (MQTT)

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 reload

Recovering 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_reload

The 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.


Services

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-scroll

Follow live voice pipeline logs:

journalctl -u linux-voice-assistant -f

Audio Architecture

Hardware: 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 Volumeamixer -c seeed2micvoicec cset "name=TTS Volume" 80%
  • Media Volumeamixer -c seeed2micvoicec cset "name=Media Volume" 80%

Note: pipewire-alsa must 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.


Customisation

Wake word

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.

Mic sensitivity

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).

Touch scroll speed

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-scroll

Kiosk URL

Re-run ./configure.sh and enter a new kiosk URL at the prompt, or edit ~/.config/labwc/autostart directly.


Troubleshooting

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 reboot

MQTT 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 -f for 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


License

This project is licensed under the terms of the GNU General Public License v3.0. See the LICENSE.txt file for details.

About

A personal project for raspberry pi powered smart displays that work with home assistant's voice pipeline

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors