BlueZ Audio Receiver Daemon
Bluezard turns a headless Linux machine into a managed Bluetooth audio receiver with a clean web interface. It is Raspberry Pi friendly, but not Pi-specific: the real requirements are BlueZ, BlueALSA, ALSA, systemd/D-Bus, Python 3.11, and a Bluetooth adapter. It handles Bluetooth device management, A2DP audio routing via BlueALSA, and provides a live admin UI — without PipeWire, PulseAudio, or any desktop-session dependency.
Dashboard — Now Playing, stream metrics, Audio Pipeline
Documentation: Setup · Bluetooth · Troubleshooting · UI screenshots
- One-tap pairing — "Add device" makes the Pi visible for 3 minutes; auto-trusts after pairing
- Dashboard layout — sidebar navigation (Dashboard, Devices, Settings), Now Playing card, Audio Pipeline summary, persistent bottom player bar
- Now Playing — real-time track title, artist, album from AVRCP (Spotify, Apple Music, etc.)
- Stream metrics — active codec (with quality indicator), sample rate, BlueALSA buffer delay, and SBC throughput estimate while a device is streaming
- Playback controls — Play / Pause / Next / Previous from the web UI and bottom player bar
- Volume control — per-device ALSA volume slider, synced with external changes
- Multi-output routing — assign each device to a different ALSA output from Devices in advanced mode
- One device at a time — optionally disconnect others when a new device starts streaming (on by default)
- PWA — installable on mobile, with persistent token login and reconnecting state
- Responsive UI — desktop sidebar plus mobile-friendly layout and PWA install
- Bluetooth adapter control (power, discoverable, pairable)
- Device scanning, pairing, connecting, removing; last-seen timestamps
- Live WebSocket event streaming (D-Bus signals → browser)
- A2DP-only audio routing (HSP/HFP excluded — no accidental headset audio)
- ALSA native output — no resampling, full DAC capability preserved
- Diagnostics modal plus advanced System, Players, Audio Outputs, and Live Events sections
- REST API with interactive docs at
/api/docs - Health check endpoint at
/health(no auth required) - Optional token authentication for LAN use
- Systemd-native — auto-start, auto-restart, no root web process
- Any always-on Linux host with working Bluetooth and audio output. Raspberry Pi is the primary target, but not a hard requirement.
- A Pi 3 or newer is a practical Raspberry Pi baseline; Pi 4/5 is not required.
- 32-bit or 64-bit OS both work, as long as the OS provides Python 3.11 and BlueALSA packages.
- Built-in Bluetooth is fine. A USB Bluetooth adapter can improve range or avoid interference.
- Any ALSA playback device: USB DAC, HAT, HDMI, or built-in audio. A USB DAC/HAT is recommended for quality and reliability.
- Raspberry Pi OS Bookworm or Debian Bookworm, 32-bit or 64-bit. Other Debian-like systems can work if they provide compatible BlueZ/BlueALSA packages.
systemdand the system D-Bus, because Bluezard runs as a service and talks to BlueZ over D-Bus.- BlueZ (
bluetooth.service) and BlueALSA (bluealsa.serviceplusbluealsa-aplay). - Python 3.11+ with
venv. - ALSA utilities (
aplay,amixer) for output detection and volume. qrencodeis optional but recommended to show the phone login QR code.
The installer resolves distro package names automatically. In Debian this is
usually bluez-alsa-utils; on Raspberry Pi OS it may be bluealsa.
git clone https://github.com/MatFaes/Bluezard.git
cd Bluezard
sudo bash scripts/install.shThe install script will:
- Install all system packages
- Detect your Bluetooth adapter and ALSA output card
- Create a
bluezardsystem user - Install the Python environment in
/opt/bluezard/ - Create
/var/lib/bluezardfor UI-managed runtime state - Enable and start the services
- Display a QR code to open the web UI directly on your phone
Open: http://<bluezard-host>:8088
See docs/setup.md for the full installation guide.
sudo bash /opt/bluezard/scripts/update.shPulls the latest release from GitHub, redeploys, and restarts. Your .env and
runtime state are never touched.
cp .env.example .env
nano .env| Variable | Default | Description |
|---|---|---|
BLUEZARD_HOST |
127.0.0.1 |
Bind address. Use 0.0.0.0 for LAN access |
BLUEZARD_PORT |
8088 |
HTTP port |
BLUEZARD_TOKEN |
(empty) | Auth token (empty = no auth) |
BLUEZARD_REQUIRE_TOKEN |
false |
When a token is set, also require it for localhost. Enabled by the installer for LAN binding |
BLUEZARD_BLUEZ_ADAPTER |
hci0 |
HCI adapter name |
BLUEZARD_DEFAULT_PCM |
(empty) | Default ALSA output (e.g. plughw:C20,0) — set by install script |
BLUEZARD_STATE_DIR |
/var/lib/bluezard |
Writable runtime state directory for UI-managed settings |
BLUEZARD_EXCLUSIVE_SINK |
true |
Disconnect others when a new device starts streaming |
BLUEZARD_PLAYER_MODE |
connected |
connected runs players only while devices are connected; always_ready pre-starts players for trusted A2DP devices |
BLUEZARD_LOG_LEVEL |
INFO |
DEBUG / INFO / WARNING / ERROR |
BLUEZARD_DEBUG |
false |
Verbose debug + auto-reload |
- Open the Bluezard web UI
- On Dashboard, click + Add device — the Bluetooth adapter becomes visible for 3 minutes
- On your phone/tablet: open Bluetooth settings → select the Pi
- When pairing succeeds, Bluezard opens Devices so you can connect it
By default, Bluezard starts bluealsa-aplay only while a trusted A2DP device is connected.
Bluezard manages bluealsa-aplay processes dynamically for trusted A2DP devices. This replaces the static bluealsa-aplay.service.
- Default output: set by
BLUEZARD_DEFAULT_PCM(configured at install time) - Per-device override: use the output selector in Devices → Device details → Output
- Routing overview: Settings → Audio Outputs shows detected outputs and active overrides
- Overrides are persisted in
/var/lib/bluezard/state.jsonacross restarts
To verify which ALSA card to use:
aplay -lBluezard displays the active Bluetooth codec in the stream metrics panel (codec name with a quality indicator: green for AAC/aptX/LDAC, amber for SBC).
Codec negotiation is handled automatically by BlueALSA and the remote device — the best codec supported by both sides is selected at connection time.
| Codec | Quality | Typical devices |
|---|---|---|
| LDAC | Excellent | Sony Android devices |
| aptX HD | Very good | High-end Android |
| aptX | Good | Mid/high-end Android |
| AAC | Good | iPhone, iPad, Mac |
| SBC | Baseline | All devices (mandatory fallback) |
The bluez-alsa-utils package from Debian Bookworm only includes SBC. A dedicated install script builds BlueALSA from source and enables AAC, aptX, and aptX-HD:
sudo bash scripts/bluealsa-install.shThe script:
- Installs build dependencies (
libfdk-aac-dev,libopenaptx-dev, etc.) - Clones and compiles BlueALSA from source
- Installs binaries to
/usr/local/bin/(system package in/usr/bin/untouched) - Creates a systemd override that reads Bluezard's UI-managed codec settings
- Restarts
bluealsa.service
If you already have the sources checked out:
sudo bash scripts/bluealsa-install.sh --source=/path/to/bluez-alsaLDAC note: LDAC decoding (receiving audio from a phone) requires
libldacBT-dec, which is not available in Debian repos. LDAC is therefore not supported as an A2DP sink codec.
AAC note:
libfdk-aacis patent-encumbered, which is why Debian ships BlueALSA without it by default. The library is available in thenon-freecomponent.
Full-size captures: docs/README.md.
Dashboard — playback, stream metrics, pipeline summary
Devices — connect, pair, device details
Settings — default output and advanced diagnostics
Mobile — PWA layout with bottom player bar
Phone / Tablet ──BT A2DP──► BlueZ ──BlueZ-ALSA──► bluealsa-aplay ──ALSA──► USB DAC
│ (per-device,
D-Bus managed by
│ Bluezard)
Bluezard
(management UI)
│
Web Browser
Bluezard manages. BlueALSA plays.
bash scripts/deploy-dev.sh pi@raspi.local # fast: static + Python files
bash scripts/deploy-dev.sh pi@raspi.local --full # full pip reinstall + restart# Install system packages
sudo apt-get install -y bluetooth bluez bluez-alsa-utils \
python3 python3-venv python3-pip alsa-utils qrencode
# On Raspberry Pi OS, the BlueALSA package may be named:
# sudo apt-get install -y bluealsa
# Add your user to bluetooth and audio groups
sudo usermod -aG bluetooth,audio $USER
newgrp bluetooth
# Python setup
python3 -m venv venv
source venv/bin/activate
pip install -e .
# Copy and edit config
cp .env.example .env
# Run
bluezardInteractive docs: http://localhost:8088/api/docs
If BLUEZARD_TOKEN is set, provide it with X-Bluezard-Token, Bearer auth, or
the bluezard_token cookie. Localhost is exempt unless BLUEZARD_REQUIRE_TOKEN=true.
To re-display the phone login URL and QR code on the Pi:
bash ~/Bluezard/scripts/show-qr.sh --host rpi-player.local# Status
curl http://localhost:8088/api/status
# Health check (no auth)
curl http://localhost:8088/health
# Playback controls (play, pause, stop, next, previous)
curl -X POST http://localhost:8088/api/devices/AA:BB:CC:DD:EE:FF/media/next
# Per-device volume (0–100)
curl http://localhost:8088/api/devices/AA:BB:CC:DD:EE:FF/volume
curl -X POST http://localhost:8088/api/devices/AA:BB:CC:DD:EE:FF/volume \
-H "Content-Type: application/json" -d '{"volume": 75}'
# Per-device output routing
curl -X POST http://localhost:8088/api/devices/AA:BB:CC:DD:EE:FF/output \
-H "Content-Type: application/json" -d '{"pcm": "plughw:C20,0"}'
# Settings
curl http://localhost:8088/api/settings
curl -X POST http://localhost:8088/api/settings \
-H "Content-Type: application/json" -d '{"exclusive_sink": true}'
# Managed player processes
curl http://localhost:8088/api/players
# Live stream metrics (BlueALSA PCM — sample rate, delay, codec)
curl http://localhost:8088/api/stream/metrics
curl 'http://localhost:8088/api/stream/metrics?address=AA:BB:CC:DD:EE:FF'
# Full diagnostics
curl http://localhost:8088/api/diagnostics| Service | Purpose |
|---|---|
bluetooth.service |
BlueZ stack |
bluealsa.service |
BlueZ-ALSA bridge daemon |
bluezard.service |
Bluezard web manager + per-device audio routing |
bluealsa-aplay.serviceis not used — Bluezard managesbluealsa-aplayprocesses directly for trusted A2DP devices.
# Status
systemctl status bluezard bluealsa bluetooth
# Restart Bluezard (also restarts all audio players)
sudo systemctl restart bluezard
# Follow Bluezard logs
journalctl -fu bluezard- Bluezard binds to
127.0.0.1by default — not accessible over LAN without configuration. - No root required for the web process (
bluezardsystem user inbluetoothandaudiogroups). - Set
BLUEZARD_TOKENbefore enablingBLUEZARD_HOST=0.0.0.0for LAN access. - Token validated with
secrets.compare_digest(timing-safe). - REST and WebSocket endpoints both enforce the token when authentication is enabled.
/opt/bluezardis installed root-owned; only/var/lib/bluezardis writable by the service.- UI-managed settings are stored in
/var/lib/bluezard/state.json, not written back into.env. - No shell execution, no SQL, no file uploads, no eval.
For production LAN use, consider placing Bluezard behind an nginx reverse proxy with HTTPS.
Open the Diagnostics button in the web UI, or use the API:
In advanced mode, Settings → System also shows WebSocket, Bluetooth, BlueALSA, device, player, output override, and runtime state status.
curl http://localhost:8088/api/diagnostics | python3 -m json.tool
curl http://localhost:8088/api/diagnostics/alsa
curl http://localhost:8088/api/diagnostics/logs/bluealsaSee docs/troubleshooting.md for the full troubleshooting guide.
bluezard/
├── bluezard/
│ ├── main.py FastAPI app, routes, WebSocket
│ ├── bluez.py BlueZ D-Bus manager (dbus-next, asyncio)
│ ├── player.py Per-device bluealsa-aplay process manager
│ ├── models.py Pydantic models
│ ├── config.py Settings (pydantic-settings)
│ ├── auth.py Token authentication
│ ├── state.py Runtime state store (/var/lib/bluezard/state.json)
│ ├── websocket.py WebSocket broadcast manager
│ ├── diagnostics.py ALSA / systemd diagnostics + volume
│ ├── stream_metrics.py BlueALSA PCM metrics for the dashboard
│ ├── templates/
│ │ └── index.html Single-page web UI
│ └── static/
│ ├── style.css Dark-theme CSS (sidebar dashboard layout)
│ ├── app.js Vanilla JS frontend
│ ├── sw.js Service Worker (PWA offline shell)
│ ├── manifest.json PWA manifest
│ ├── icon-192.png PWA icon
│ ├── icon-512.png PWA icon
│ └── favicon.ico Browser icon
├── systemd/
│ └── bluezard.service
├── docs/
│ ├── README.md Documentation index and UI gallery
│ ├── setup.md
│ ├── bluetooth.md
│ ├── troubleshooting.md
│ └── screenshots/ UI screenshots (see README inside)
├── scripts/
│ ├── install.sh
│ ├── show-qr.sh
│ ├── update.sh
│ ├── uninstall.sh
│ ├── deploy-dev.sh
├── tests/
│ ├── test_auth.py
│ ├── test_player.py
│ ├── test_state_and_config.py
│ └── test_stream_metrics.py
Apache 2.0 — see LICENSE.
Issues and pull requests welcome. Please:
- Target Debian Bookworm / Python 3.11+
- Keep ALSA-native — no PulseAudio/PipeWire dependencies
- No new D-Bus parsing hacks — use typed dbus-next calls
- Test on real hardware when touching D-Bus / ALSA code



