Skip to main content

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 -10dB to +10dB

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vsslctrl-0.1.4.dev1.tar.gz (48.8 kB view details)

Uploaded Source

Built Distribution

vsslctrl-0.1.4.dev1-py3-none-any.whl (47.1 kB view details)

Uploaded Python 3

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

Hashes for vsslctrl-0.1.4.dev1.tar.gz
Algorithm Hash digest
SHA256 ba45fe32072073eb708f9b8cbe887832e71db7e8a04ac67c749e2aa49fb67db1
MD5 cd7b98cadae3cedc8c1b0bc6cffcdf91
BLAKE2b-256 ba5a3849eba3d9f4d24c60b56ea6e613338b5db4859bfe1e0fadd49a9e774f76

See more details on using hashes here.

File details

Details for the file vsslctrl-0.1.4.dev1-py3-none-any.whl.

File metadata

File hashes

Hashes for vsslctrl-0.1.4.dev1-py3-none-any.whl
Algorithm Hash digest
SHA256 44e463d56c4d557fbca962bbfa92f15fe2bdb46d12c8c2595d5f06d94874dda6
MD5 64106f29cb8060343252cb195d9ddd78
BLAKE2b-256 d674fd771241a3998d4d01513ed4b516214acd52e3882f1af9638a9d19045c32

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page