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 959+ 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
- docs/history/v1-v2.0.md — narrative history of the project’s evolution (v1 → v2, key design decisions)
- CHANGELOG.md — full version history