Package for controlling VSSL amplifiers
Project description
vsslctrl
Package for controlling VSSL range of streaming amplifiers.
vsslctrl
is not endorsed or affiliated with VSSL in any manner.
Motovation for this project was to intergrate VSSLs amplifiers into Home Assistant and have control over different subnets (not mDNS dependant)
I am looking for testers with any VSSL amplifier models, please get in touch if you interested in helping.
Important
Only tested on a VSSL A.3x software version p15305.016.3701.
Warning: no VSSL Agent should be running on the same network. If you dont know what this is, then you can probably ignore this notice.
TODOs
- Test on other models (hardware needed)
- Home Assistant integration. In progress, here
Function scoping to supported feature / models(needs tests)- Better test coverage
Basic Usage
vsslctrl
needs to be running inside a asyncio event loop.
import asyncio
from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
async def main():
# Represents a physical VSSL amplifier
vssl = Vssl()
"""Optional to init Vssl with a device model
vssl = Vssl(DeviceModels.A3X)
If no DeviceModels is passed, vsslctrl will default to the feature set of the X series amps
"""
# Add each you wish to control
zone1 = vssl.add_zone(ZoneIDs.ZONE_1, '192.168.1.10')
zone2 = vssl.add_zone(ZoneIDs.ZONE_2, '192.168.1.11')
zone3 = vssl.add_zone(ZoneIDs.ZONE_3, '192.168.1.12')
#... up to 6 zones
# Connect and initiate zones.
await vssl.initialise()
"""Control Examples"""
# Print zone1 name
print(zone1.settings.name)
# Set zone2 volume to 25%
zone2.volume = 25
# Pause zone3
zone3.pause()
# or zone3.transport.pause()
# Print zone1 track name
print(zone1.track.name)
# Shutdown and disconnect all zones
await vssl.shutdown()
asyncio.run(main())
API
Most functionality is achived via getters
and setters
of the two main classes Vssl
, Zone
.
The classes will update the physical VSSL device when setting a property and once feedback has been received, the classes internal state will be updated. For example:
# Setting the zones name
zone1.settings.name = 'Living Room'
>>> None
# Printing zone name
zone_name = zone1.settings.name
print(zone_name)
>>> 'Living Room'
Important in the above example, zone1.settings.name
wont be set to its new value until after the VSSL device has changed the name and the Zone
class has received confimation feedback. If you need to wait for the value change, you can await a [property_name]_CHANGE
events.
Vssl
Property | Description | Type |
---|---|---|
sw_version |
Software version | str readonly |
serial |
Serial number | str readonly |
model |
Device Model | int readonly |
reboot() |
Reboot all zones | func |
"""Example"""
# Reboot all zones
vssl.reboot()
Vssl.settings
Property | Description | Type |
---|---|---|
name |
Device name | str |
optical_input_name |
Name of the optical input | str |
"""Example"""
# Setting device name
vssl.settings.name = 'My House'
# Setting optical input name
vssl.settings.optical_input_name = 'Optical Input 1'
Vssl.settings.power
Property | Description | Type | Values |
---|---|---|---|
state |
Power state | int readonly |
VsslPowerSettings.States |
adaptive |
Power adaptive | bool |
"""Example"""
# Setting power adaptive
vssl.settings.power.adaptive = True
Zone
Property | Description | Type | Values |
---|---|---|---|
id |
Zone number / ID | int readonly |
ZoneIDs |
host |
IP address | str readonly |
|
volume |
Volume | int |
0...100 |
volume_raise([step=1]) |
Raise volume by step |
func |
step: int 1...100 |
volume_lower([step=1]) |
Lower volume by step |
func |
step: int 1...100 |
mute |
Volume muted | bool |
|
mute_toggle() |
Mute / Unmute | func |
|
play() |
Play | func |
|
stop() |
Stop | func |
|
pause() |
Pause | func |
|
next() |
Next track | func |
|
prev() |
Begining of track or previous track | func |
|
reboot() |
Reboot zone | func |
|
play_url([url], [all_zones]) |
Play a URL | func |
url: str , all_zones: bool |
"""Examples"""
# Set volume to 50%
zone1.volume = 50
# Raise volume by 5%
zone1.volume_raise(5)
# Mute
zone1.mute = True
# Toggle mute
zone1.mute_toggle()
# Pause transport
zone1.pause()
# Next track
zone1.next()
# Play a URL on this zone1
zone1.play_url('http://soundbible.com/grab.php?id=2217&type=mp3')
# Play a URL on all zones
zone1.play_url('http://soundbible.com/grab.php?id=2217&type=mp3', True)
Zone.transport
A VSSL amplifier can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
Property | Description | Type | Values |
---|---|---|---|
state |
Transport state. i.e Play, Stop, Pause | int |
ZoneTransport.States |
play() |
Play | func |
|
stop() |
Stop | func |
|
pause() |
Pause | func |
|
next() |
Next track | func |
|
prev() |
Begining of track or previous track | func |
|
is_playing |
Is the zone playing | bool readonly |
|
is_stopped |
Is the zone stopped | bool readonly |
|
is_pasued |
Is the zone pasued | bool readonly |
|
is_repeat |
Repeat state. i.e all, one, off | int readonly |
ZoneTransport.Repeat |
is_shuffle |
Is shuffle enabled | bool readonly |
|
has_next |
Is the next button enabled | bool readonly |
|
has_prev |
Is the prev button enabled | bool readonly |
"""Example"""
# Pause the stream
zone1.transport.pause()
# or
zone1.transport.state = ZoneTransport.States.PAUSE
Zone.track
- Not all sources have complete metadata - missing value will be set to defaults.
- Airplay track position is not avaiable.
Property | Description | Type | Values |
---|---|---|---|
title |
Title | str readonly |
|
album |
Album | str readonly |
|
artist |
Artist | str readonly |
|
genre |
Genre | str readonly |
|
duration |
Length in miliseconds (ms) | int readonly |
|
progress |
Current position in miliseconds (ms) | int readonly |
|
cover_art_url |
URL to cover art | str readonly |
|
source |
Track source e.g Spotify | int readonly |
TrackMetadata.Sources |
url |
URL of file or track | str readonly |
Zone.input
Property | Description | Type | Values |
---|---|---|---|
source |
Change input source | int |
InputRouter.Sources |
priority |
Change input priority. Stream or analog in higher priority | int |
InputRouter.Priorities |
"""Example"""
# Change zone 1 to listen to analog input 4
zone1.input.source = InputRouter.Sources.ANALOG_IN_4
# Change zone 1 to perfer analog input (local) over stream
zone1.input.priority = InputRouter.Priorities.LOCAL
Zone.group
Working on A.3x but offically unsupported in x series amplifiers.
Property | Description | Type | Values |
---|---|---|---|
source |
Zone ID of group master / source | int readonly |
ZoneIDs |
is_master |
This zone is the group master | bool readonly |
|
add_member() |
Add zone to group / create group | func |
ZoneIDs |
remove_member() |
Remove zone from group | func |
ZoneIDs |
dissolve() |
Dissolve group / remove all members | func |
|
leave() |
Leave the group if a member | func |
"""Examples"""
# Add group 2 to a group with zone 1 as master
zone1.group.add_member(ZoneIDs.ZONE_2)
# Remove zone 2 from group
zone2.group.leave() # or
zone1.group.remove_member(ZoneIDs.ZONE_2)
# If zone 1 is a master, remove all members
zone1.group.dissolve()
Zone.analog_output
Property | Description | Type | Values |
---|---|---|---|
source |
Where the AO is routed from. i.e a zone, optical input or off | int |
AnalogOutput.Sources |
is_fixed_volume |
Fix the output volume. Output wont respond to volume control | bool readonly |
|
is_fixed_volume_toggle() |
Toggle fixed volume | func |
"""Examples"""
# Change analog output of zone1 to be outputting the optical input
zone1.analog_output.source = AnalogOutput.Sources.OPTICAL_IN
# Change analog output of zone1 to be outputting the zone 2 source (whatever zone 2 is using as a source)
zone1.analog_output.source = AnalogOutput.Sources.ZONE_2
# Fix the analog output volume.
zone1.analog_output.is_fixed_volume = True
Zone.settings
Property | Description | Type | Values |
---|---|---|---|
name |
Name | str |
|
disabled |
Disable the zone | bool |
|
disabled_toggle() |
disable / enable | func |
|
mono |
Set output to mono or stereo | int |
ZoneSettings.StereoMono |
mono_toggle() |
Toggle mono or stereo | func |
"""Examples"""
# Set name
zone1.settings.name = 'Living Room'
# Disable Zone
zone1.disabled = True
# Toggle mono output
zone1.mono_toggle()
Zone.settings.analog_input
Property | Description | Type | Values |
---|---|---|---|
name |
Name | str |
|
fixed_gain |
Fix the input gain to a specific value | int |
0...100 |
"""Examples"""
# Change zone1 analog input name
zone1.settings.analog_input.name = 'BluRay Player'
# Fix zone1 analog input gain to 50%.
zone1.settings.analog_input.fixed_gain = 50
Zone.settings.volume
Property | Description | Type | Values |
---|---|---|---|
default_on |
Default on volume | int |
0...100 |
max_left |
Max volume left channel | int |
0...100 |
max_right |
Max volume right channel | int |
0...100 |
"""Examples"""
# Set default on volume to 50%
zone1.settings.volume.default_on = 50
# Set maximum volume for left channel to 75%
zone1.settings.volume.default_on = 75
Zone.settings.eq
Property | Description | Type | Values |
---|---|---|---|
enabled |
Enable / disable EQ | bool |
EQ to be set in decibel using a range -10
dB to +10
dB
Property | Description | Type | Values |
---|---|---|---|
hz60_db |
60Hz | int |
-10...10 |
hz200_db |
200Hz | int |
-10...10 |
hz500_db |
500Hz | int |
-10...10 |
khz1_db |
1kHz | int |
-10...10 |
khz4_db |
4kHz | int |
-10...10 |
khz8_db |
8kHz | int |
-10...10 |
khz15_db |
15kHz | int |
-10...10 |
"""Examples"""
# Set 1kHz EQ to -2
zone1.settings.eq.khz1_db = -2
Another (Lite) Way
If you perfer to not run the complete intergration, you can send basic HEX commands to the VSSL device using Netcat (or ant network tool) on port 50002
.
HEX | Description |
---|---|
\x10\x05\x03\x0{Zone Number}\xff\x03 |
Volume Up |
\x10\x05\x03\x0{Zone Number}\xfe\x03 |
Volume Down |
\x10\x11\x02\x0{Zone Number}\x01 |
Mute |
All commands can be found by looking here |
The volume up
HEX command for Zone 2
would be \x10\x05\x03\x02\xff\x03
Now send the raw HEX using Netcat to the device using this syntax:
echo -e "{HEX Command}" | nc {IP Address} 50002
For example to send volume up
to Zone 2
:
echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002
Home Assistant Configuration.yaml
example:
...
shell_command:
#Zone 1
vssl_zone_1_volume_up: 'echo -e "\x10\x05\x03\x01\xff\x03" | nc 192.168.1.10 50002'
vssl_zone_1_volume_down: 'echo -e "\x10\x05\x03\x01\xfe\x03" | nc 192.168.1.10 50002'
vssl_zone_1_mute: 'echo -e "\x10\x11\x02\x01\x01" | nc 192.168.1.10 50002'
vssl_zone_1_unmute: 'echo -e "\x10\x11\x02\x01\x00" | nc 192.168.1.10 50002'
#Zone 2
vssl_zone_2_volume_up: 'echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002'
vssl_zone_2_volume_down: 'echo -e "\x10\x05\x03\x02\xfe\x03" | nc 192.168.1.11 50002'
vssl_zone_2_mute: 'echo -e "\x10\x11\x02\x02\x01" | nc 192.168.1.11 50002'
vssl_zone_2_unmute: 'echo -e "\x10\x11\x02\x02\x00" | nc 192.168.1.11 50002'
#Zone 3
vssl_zone_3_volume_up: 'echo -e "\x10\x05\x03\x03\xff\x03" | nc 192.168.1.12 50002'
vssl_zone_3_volume_down: 'echo -e "\x10\x05\x03\x03\xfe\x03" | nc 192.168.1.12 50002'
vssl_zone_3_mute: 'echo -e "\x10\x11\x02\x03\x01" | nc 192.168.1.12 50002'
vssl_zone_3_unmute: 'echo -e "\x10\x11\x02\x03\x00" | nc 192.168.1.12 50002'
...
Credit
The VSSL API was reverse engineered using Wireshark, VSSLs native "legacy" iOS app and their deprecated vsslagent.
Known Issues & Limitiations
- Tested on VSSL A.3x software version p15305.016.3701
- Not tested on A.1x, A.6.x or original A series range of amplifiers (testers welcome)
- VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
- Not all sources set the volume to 0 when the zone is muted
- Grouping feedback is flaky on the X series amplifiers
- Cant stop a URL playback, feedback is worng at least
- VSSL likes to cache old track metadata. For example when playing a URL after Spotify, often the device will respond with the previous (Spotify) tracks metadata
stop()
is intended to disconnect the client and pause the stream. Doesnt always function this way, depending on stream source- Occasionally a zones might stop responding to certain commands, issuing the
reboot
command generally corrects
Future
- REST API / Web App
- Save and recall EQ
- A.1(x) coverage i.e Bluetooth
- IR Control
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file vsslctrl-0.1.4.dev1.tar.gz
.
File metadata
- Download URL: vsslctrl-0.1.4.dev1.tar.gz
- Upload date:
- Size: 48.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.9.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ba45fe32072073eb708f9b8cbe887832e71db7e8a04ac67c749e2aa49fb67db1 |
|
MD5 | cd7b98cadae3cedc8c1b0bc6cffcdf91 |
|
BLAKE2b-256 | ba5a3849eba3d9f4d24c60b56ea6e613338b5db4859bfe1e0fadd49a9e774f76 |
File details
Details for the file vsslctrl-0.1.4.dev1-py3-none-any.whl
.
File metadata
- Download URL: vsslctrl-0.1.4.dev1-py3-none-any.whl
- Upload date:
- Size: 47.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.9.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 44e463d56c4d557fbca962bbfa92f15fe2bdb46d12c8c2595d5f06d94874dda6 |
|
MD5 | 64106f29cb8060343252cb195d9ddd78 |
|
BLAKE2b-256 | d674fd771241a3998d4d01513ed4b516214acd52e3882f1af9638a9d19045c32 |