Development & Contributing
Running Locally
Section titled “Running Locally”Requires Docker and Docker Compose. The speaker must be paired with the host before starting.
git clone https://github.com/trudenboy/sendspin-bt-bridge.gitcd sendspin-bt-bridge
docker compose up --builddocker logs -f sendspin-clientopen http://localhost:8080Without Docker (requires system Bluetooth and audio packages):
pip install -r requirements.txtpython sendspin_client.pyProject Structure
Section titled “Project Structure”sendspin_client.py # Entry point: SendspinClient + main()bluetooth_manager.py # BluetoothManager — BT connections via bluetoothctlconfig.py # Configuration, shared lock, load_config()state.py # Shared runtime state (list of SendspinClient instances)
services/ bridge_daemon.py # BridgeDaemon — runs inside each subprocess; stream events, sink routing daemon_process.py # Subprocess entry point: reads args, runs BridgeDaemon, emits JSON status bluetooth.py # Async BT helpers (D-Bus monitor) pulse.py # PulseAudio helpers (pulsectl + pactl): find sink, move sink-inputs
routes/ api.py # Core playback & volume (6 routes) api_bt.py # Bluetooth management (9 routes) api_ma.py # Music Assistant integration (10 routes) api_config.py # Configuration (9 routes) api_status.py # Status & diagnostics (8 routes) views.py # HTML page renders auth.py # Optional web UI password protection
entrypoint.sh # Docker entrypoint: D-Bus, audio initha-addon/ # Home Assistant addon configurationlxc/ # LXC install scripts (Proxmox & OpenWrt)Architecture note: each Bluetooth speaker runs as an isolated asyncio subprocess (
services/daemon_process.py) withPULSE_SINK=<bt_sink_name>in its environment. This gives every speaker its own PulseAudio context so audio routes to the correct speaker from the first sample, without anymove-sink-inputcalls at startup.
Testing
Section titled “Testing”Run pytest for automated unit tests:
python3 -m pytest tests/ -vThe test suite has 187 tests across 18 test files covering config, volume routing, device status, state management, authentication, Ingress middleware, BT services, daemon process, MA integration, and scan cooldown.
Linting
Section titled “Linting”ruff check . # Fast Python linterruff format --check . # Code formatting checkManual Test Checklist
Section titled “Manual Test Checklist”Use this checklist when making changes:
- Container starts without errors (
docker logs -f sendspin-client) - Web UI loads at
http://localhost:8080 - Bluetooth device connects and appears in the web UI
- Music Assistant detects the player
- Audio plays through the Bluetooth speaker
- Volume slider changes speaker volume
- Auto-reconnect triggers after disconnecting (~10 s)
- Config changes persist after container restart
-
/api/statusreturns valid JSON
Branching Strategy
Section titled “Branching Strategy”main— stable, always releasable- Feature branches — branch off
main, namefeat/<description>orfix/<description> - Submit a PR against
main
Reporting a Bug
Section titled “Reporting a Bug”Open an issue on GitHub. Include:
- Deployment method (Docker / HA Addon / Proxmox LXC)
- Log output
- Host OS, audio system (PipeWire/PulseAudio), Bluetooth adapter model
- Steps to reproduce
Pushing a v* tag to main automatically:
- Builds multi-platform Docker image (
linux/amd64,linux/arm64) - Publishes to
ghcr.io/trudenboy/sendspin-bt-bridge - Syncs the version to
ha-addon/config.yaml
Attribution
Section titled “Attribution”This project grew out of loryanstrant/Sendspin-client. Thanks to the Music Assistant team for the Sendspin protocol and CLI.
Further reading
Section titled “Further reading”- Architecture — deep dive into the process model, IPC protocol, audio routing, Bluetooth state machine, MA integration, authentication, and graceful degradation
- HISTORY.md — narrative history of the project’s evolution (v1 → v2, key design decisions)
- CHANGELOG.md — full version history