Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ 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 play "Player A" # Play on specific device
bluos-controller --help # Full command reference
```

Expand Down
4 changes: 2 additions & 2 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@


def parse_bluos_host(value: Optional[str]) -> str:
"""Extract an IP/host from BluOS endpoint values like ``172.16.0.1`` or ``172.16.0.1:11000``."""
"""Extract an IP/host from BluOS endpoint values like ``192.168.1.20`` or ``192.168.1.20:11000``."""
if not value:
return ""
ip, _port = parse_endpoint(value.strip())
Expand All @@ -83,7 +83,7 @@ def parse_sync_status_root(root: ET.Element) -> tuple[str, str, List[str]]:

BluOS may expose values as root attributes or child elements. Legacy clients
assumed ``master`` was an attribute; current firmware uses ``<master>``.
Multi-zone CI players use ``ip:port`` ids (e.g. ``172.16.0.1:11010``).
Multi-zone CI players use ``ip:port`` ids (e.g. ``192.168.1.20:11010``).
"""
master_attr = root.attrib.get("master") or ""
master_elem = root.find("master")
Expand Down
10 changes: 5 additions & 5 deletions docs/API_VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ Select an input with `GET /Play?inputTypeIndex=<type>-<n>` where type is `analog
### Primary (group leader)

```xml
<SyncStatus group="Living Room Speakers+Kitchen Speakers" name="Living Room Speakers">
<slave id="172.16.10.88" port="11000"/>
<SyncStatus group="Zone One+Player A" name="Zone One">
<slave id="192.168.1.20" port="11000"/>
</SyncStatus>
```

### Slave (group follower)

```xml
<SyncStatus name="Kitchen Speakers">
<master port="11000">172.16.10.174</master>
<SyncStatus name="Player A">
<master port="11000">192.168.1.32</master>
</SyncStatus>
```

Values may include a port suffix (`172.16.10.174:11000` or `172.16.10.144:11010`). Clients should preserve the port for API calls and sync membership; bare IPs default to `11000`.
Values may include a port suffix (`192.168.1.32:11000` or `192.168.1.30:11010`). Clients should preserve the port for API calls and sync membership; bare IPs default to `11000`.

### CI secondary zones

Expand Down
64 changes: 32 additions & 32 deletions docs/README-DETAILED.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Current line: multi-zone discovery (`ip:port`, `_musc` + `_musp` for CI secondar
**Multi-Device Support:**
- Control all devices simultaneously (default)
- Target specific devices by name
- Pattern matching for multiple devices (e.g., "Room" matches "Living Room", "Bedroom")
- Pattern matching for multiple devices (e.g., "Zone" matches "Zone One", "Zone Two")

**Volume Operations:**
- Set absolute volume (0-100%)
Expand Down Expand Up @@ -123,18 +123,18 @@ Current line: multi-zone discovery (`ip:port`, `_musc` + `_musp` for CI secondar
**Example output (`sync list`):**

```text
Group: Living Room Speakers + 1 slave
Primary: Living Room Speakers (172.16.10.174)
Slave: Kitchen Speakers (172.16.10.88)
Group: Zone One + 1 slave
Primary: Zone One (192.168.1.32)
Slave: Player A (192.168.1.20)

Standalone:
Patio Speakers (172.16.10.90)
Player B (192.168.1.23)
```

**Example output (`status`, grouped primary with 7 slaves):**

```text
[1] Living Room Speakers
[1] Zone One
Status: ★ PRIMARY | Vol: 10% | Source: Tidal
Sync: 6 slaves
♫ Track: Hayling
Expand Down Expand Up @@ -218,7 +218,7 @@ Standalone:
**Universal Device Targeting:**
- All commands support operating on all devices (default behavior)
- Target specific devices by exact or partial name match
- Pattern matching for bulk operations (e.g., "Room" matches multiple devices)
- Pattern matching for bulk operations (e.g., "Zone" matches multiple devices)
- Case-insensitive device name matching
- Clear feedback showing which devices are being controlled

Expand Down Expand Up @@ -325,8 +325,8 @@ bluos-controller keychain delete
After installation, use the `bluos-controller` command:

**Note**: Most commands support controlling all devices (default) or specific devices by name. Device names can be:
- Exact match: `"Living Room"`
- Partial match: `"Room"` matches "Living Room", "Bedroom", etc.
- Exact match: `"Zone One"`
- Partial match: `"Zone"` matches "Zone One", "Zone Two", etc.
- Case-insensitive matching

### Discover Devices
Expand All @@ -342,7 +342,7 @@ bluos-controller discover
bluos-controller status

# Filter by name
bluos-controller status "Living Room"
bluos-controller status "Zone One"

# Force fresh scan
bluos-controller status --scan
Expand Down Expand Up @@ -372,7 +372,7 @@ bluos-controller volume unmute
bluos-controller volume reset

# Target specific device
bluos-controller volume 25 "Living Room"
bluos-controller volume 25 "Zone One"
```

### Playback Control
Expand All @@ -389,11 +389,11 @@ bluos-controller previous
bluos-controller toggle

# Specific device
bluos-controller play "Living Room"
bluos-controller pause "Kitchen"
bluos-controller play "Zone One"
bluos-controller pause "Player A"

# Pattern matching (multiple devices)
bluos-controller play "Room" # Matches "Living Room", "Bedroom", etc.
bluos-controller play "Zone" # Matches "Zone One", "Zone Two", etc.
```

### Queue Management
Expand All @@ -405,17 +405,17 @@ Queue commands support all devices or specific devices:
bluos-controller queue

# Show queue for specific device
bluos-controller queue "Living Room"
bluos-controller queue "Zone One"

# Clear queue on all devices
bluos-controller queue clear

# Clear queue on specific device
bluos-controller queue clear "Kitchen"
bluos-controller queue clear "Player A"

# Move track in queue (all or specific)
bluos-controller queue move 3 1 # All devices
bluos-controller queue move 3 1 "Living Room" # Specific device
bluos-controller queue move 3 1 "Zone One" # Specific device
```

### Input Source Selection
Expand All @@ -427,14 +427,14 @@ Input commands support all devices or specific devices:
bluos-controller inputs

# List inputs for specific device
bluos-controller inputs "Living Room"
bluos-controller inputs "Zone One"

# Set input on all devices
bluos-controller inputs "" "Bluetooth"

# Set input on specific device
bluos-controller inputs "Living Room" "Bluetooth"
bluos-controller inputs "Kitchen" "Optical 1"
bluos-controller inputs "Zone One" "Bluetooth"
bluos-controller inputs "Player A" "Optical 1"
```

### Bluetooth Mode Control
Expand All @@ -446,16 +446,16 @@ Bluetooth commands support all devices or specific devices:
bluos-controller bluetooth

# Show mode for specific device
bluos-controller bluetooth "Living Room"
bluos-controller bluetooth "Zone One"

# Set mode on all devices
bluos-controller bluetooth "" auto

# Set mode on specific device
bluos-controller bluetooth "Living Room" manual
bluos-controller bluetooth "Kitchen" auto
bluos-controller bluetooth "Bedroom" guest
bluos-controller bluetooth "Office" disable
bluos-controller bluetooth "Zone One" manual
bluos-controller bluetooth "Player A" auto
bluos-controller bluetooth "Zone Two" guest
bluos-controller bluetooth "Player C" disable
```

### Preset Management
Expand All @@ -467,22 +467,22 @@ Preset commands support all devices or specific devices:
bluos-controller presets

# List presets for specific device
bluos-controller presets "Living Room"
bluos-controller presets "Zone One"

# Play preset on all devices
bluos-controller presets "" 1

# Play preset on specific device
bluos-controller presets "Living Room" 1
bluos-controller presets "Zone One" 1
```

### Sync Group Management

Runtime BluOS groups use the **primary** player to attach or detach slaves.

```bash
# Create sync group (Living Room primary, Kitchen + Bedroom slaves)
bluos-controller sync create "Living Room Speakers" "Kitchen Speakers,Bedroom Speakers"
# Create sync group (Zone One primary, Player A + Zone Two slaves)
bluos-controller sync create "Zone One" "Player A,Zone Two"

# Break all runtime groups
bluos-controller sync break
Expand All @@ -491,7 +491,7 @@ bluos-controller sync break
bluos-controller sync list --scan

# Break groups involving a specific player (primary or slave)
bluos-controller sync break "Kitchen Speakers"
bluos-controller sync break "Player A"

# List sync groups and standalone players
bluos-controller sync list
Expand Down Expand Up @@ -537,13 +537,13 @@ Reboot commands support all devices or specific devices:
bluos-controller reboot

# Hard reboot specific device
bluos-controller reboot "Living Room"
bluos-controller reboot "Zone One"

# Soft reboot all devices
bluos-controller reboot --soft

# Soft reboot specific device
bluos-controller reboot --soft "Kitchen"
bluos-controller reboot --soft "Player A"
```

## Debug Mode & Logging
Expand Down
1 change: 0 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def run_tests_and_update_docs() -> None:
else:
# If not found, try common locations
possible_roots = [
Path.home() / "github" / "tbaur" / "bluos-controller",
Path.home() / "github" / "bluos-controller",
Path.home() / ".config" / "bluos-controller",
Path(__file__).parent,
Expand Down
14 changes: 7 additions & 7 deletions smoke_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def setup_mock_controller(self) -> BluOSController:
# Mock device info
mock_status1 = PlayerStatus(
ip='192.168.1.100',
name='Living Room',
name='Zone One',
model='Node',
brand='BluOS',
status='online',
Expand All @@ -73,7 +73,7 @@ def setup_mock_controller(self) -> BluOSController:

mock_status2 = PlayerStatus(
ip='192.168.1.101',
name='Bedroom',
name='Zone Two',
model='Pulse',
brand='BluOS',
status='online',
Expand Down Expand Up @@ -134,7 +134,7 @@ def test_status_filtered(self) -> None:
cli = BluOSCLI(controller)

with patch('sys.stdout', new=StringIO()) as fake_out:
cli.status(pattern='Living', json_mode=False)
cli.status(pattern='One', json_mode=False)
output = fake_out.getvalue()
assert len(output) > 0

Expand Down Expand Up @@ -463,8 +463,8 @@ def test_sync_create(self) -> None:

args = Mock()
args.action = 'create'
args.master = 'Living Room'
args.slaves = 'Bedroom'
args.master = 'Zone One'
args.slaves = 'Zone Two'
args.target = None

with patch('sys.stdout', new=StringIO()) as fake_out:
Expand All @@ -481,7 +481,7 @@ def test_sync_break(self) -> None:
args.action = 'break'
args.master = None
args.slaves = None
args.target = 'Living Room'
args.target = 'Zone One'

with patch('sys.stdout', new=StringIO()) as fake_out:
cli.sync(args)
Expand Down Expand Up @@ -534,7 +534,7 @@ def test_diagnose(self) -> None:
cli = BluOSCLI(controller)

with patch('sys.stdout', new=StringIO()) as fake_out:
cli.diagnose('Living Room')
cli.diagnose('Zone One')
output = fake_out.getvalue()
assert len(output) > 0

Expand Down
10 changes: 5 additions & 5 deletions tests/test_cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def cli(self, controller):
def mock_devices(self):
"""Create mock device statuses."""
return [
PlayerStatus(ip="192.168.1.100", name="Living Room", volume=50, state="play"),
PlayerStatus(ip="192.168.1.101", name="Kitchen", volume=30, state="pause"),
PlayerStatus(ip="192.168.1.100", name="Zone One", volume=50, state="play"),
PlayerStatus(ip="192.168.1.101", name="Player A", volume=30, state="pause"),
]

def test_discover(self, cli):
Expand Down Expand Up @@ -113,7 +113,7 @@ def test_status_pattern(self, cli, mock_devices):

with patch.object(cli.ctl, 'sync_unifi', return_value="SUCCESS:2"):
with patch('builtins.print'):
cli.status(pattern="Living", json_mode=False)
cli.status(pattern="One", json_mode=False)

def test_volume_list(self, cli, mock_devices):
"""Test volume command listing volumes."""
Expand Down Expand Up @@ -192,7 +192,7 @@ def test_soft_reboot(self, mock_input, cli, mock_devices):
def test_diagnose(self, mock_arp, cli, mock_devices):
"""Test diagnose command."""
mock_arp.return_value = "192.168.1.100 aa:bb:cc:dd:ee:ff"
target = "Living Room"
target = "Zone One"

with patch.object(cli.ctl, 'get_device_info') as mock_info:
mock_info.return_value = mock_devices[0]
Expand All @@ -212,7 +212,7 @@ def test_print_status_header(self, cli):
"""Test status header printing."""
with patch('builtins.print'):
cli._print_status_header(None, "SUCCESS:5")
cli._print_status_header("Room", "SUCCESS:5")
cli._print_status_header("Zone", "SUCCESS:5")

def test_format_connection_string(self, cli):
"""Test connection string formatting."""
Expand Down
Loading
Loading