Command-line controller for BluOS devices on macOS. Pure Python standard library.
# from the cloned repo
./install.shThis installs to ~/.config/bluos-controller and creates a symlink at ~/local/bin/bluos-controller.
bluos-controller discover # Find devices (mDNS + CI zones)
bluos-controller status # Show all device status
bluos-controller status --scan # Force rediscovery, then status
bluos-controller volume 25 # Set volume
bluos-controller play "Kitchen" # Play on specific device
bluos-controller --help # Full command reference- Discovery — mDNS (
_musc+_muspfor CI secondary zones) and/or LSDP; players keyed asip:port; SyncStatus verified before caching - Playback — play, pause, stop, skip, previous, toggle
- Volume — absolute, relative (+/-), mute/unmute, reset to safe level
- Queue — view, clear, reorder
- Inputs — list and switch audio sources
- Bluetooth — get/set mode (manual, auto, guest, disable)
- Presets — list and play
- Sync Groups — create, break, list multi-room groups (including orphan ungroup when primary is offline)
- Diagnostics — device info, uptime, network stats
- UniFi Integration — optional network statistics from UniFi Controller
- Keychain — store API keys securely in macOS Keychain
All device commands load discovery (cache by default). status and sync accept --scan to force a rescan; other commands auto-rescan once if every cached endpoint is dead. Target all devices (default), a name, or a pattern.
NAD multi-zone players (e.g. NAD CI S2 or CI 580 V2) advertise secondary zones on _musp._tcp with non-default ports (11010+). Those zones appear as separate ip:port endpoints alongside the primary on 11000.
Stored in ~/.config/bluos-controller/config.json:
{
"DISCOVERY_METHOD": "mdns",
"DISCOVERY_TIMEOUT": "5",
"CACHE_TTL": "300",
"DEFAULT_SAFE_VOL": "14",
"UNIFI_ENABLED": "false",
"UNIFI_CONTROLLER": "",
"UNIFI_API_KEY": "",
"UNIFI_SITE": "default"
}Discovery methods: mdns (default), lsdp, or both (mDNS first, LSDP fallback).
mDNS always browses _musc._tcp (primary players) and _musp._tcp (CI secondary zones). LSDP discovers chassis IPs only (normalized to ip:11000).
Store your API key in macOS Keychain instead of plaintext config:
bluos-controller keychain set # Store key
bluos-controller keychain get # Check status
bluos-controller keychain delete # Remove keyKeychain values take precedence over config.json.
- Python 3.10+ (standard library only)
- macOS 10.15+ (uses
dns-sdanddscacheutilfor discovery)
rm -rf ~/.config/bluos-controller
rm ~/local/bin/bluos-controller- CHANGELOG — version history
- CONTRIBUTING — contribution guidelines
- SECURITY — security policy
Copyright 2026 tbaur.
Licensed under the Apache License, Version 2.0. See LICENSE.