vsslctrl 0.1.11.dev1__tar.gz → 0.1.13.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/PKG-INFO +126 -90
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/README.md +124 -88
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/pyproject.toml +1 -1
- vsslctrl-0.1.13.dev1/tests/test_event_bus.py +206 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/tests/test_integration.py +319 -253
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/tests/test_settings.py +3 -2
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/tests/test_zone.py +3 -3
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/__init__.py +1 -1
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/api_alpha.py +33 -29
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/api_base.py +25 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/api_bravo.py +6 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/core.py +98 -107
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/data_structure.py +2 -1
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/event_bus.py +23 -26
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/group.py +1 -2
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/track.py +3 -4
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/transport.py +1 -1
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/utils.py +11 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/zone.py +28 -13
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl.egg-info/PKG-INFO +126 -90
- vsslctrl-0.1.11.dev1/tests/test_event_bus.py +0 -70
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/LICENSE +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/setup.cfg +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/decorators.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/device.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/discovery.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/exceptions.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/io.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl/settings.py +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl.egg-info/SOURCES.txt +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl.egg-info/dependency_links.txt +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl.egg-info/requires.txt +0 -0
- {vsslctrl-0.1.11.dev1 → vsslctrl-0.1.13.dev1}/vsslctrl.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: vsslctrl
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13.dev1
|
|
4
4
|
Summary: Package for controlling VSSL's range of streaming amplifiers
|
|
5
5
|
Author-email: vsslctrl <vsslcontrolled@proton.me>
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -21,32 +21,44 @@ Requires-Dist: zeroconf==0.132.2; extra == "optional"
|
|
|
21
21
|
|
|
22
22
|
# vsslctrl
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
## Coverage
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Tested on:
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
| Model | Software Version | Note |
|
|
31
|
+
| ------------|--------- | -------------
|
|
32
|
+
| A.3x | p15305.016.3701 |
|
|
31
33
|
|
|
32
|
-
Tested on:
|
|
33
|
-
- **A.3x** software version **p15305.016.3701**
|
|
34
34
|
|
|
35
35
|
Home Assistant [integration](https://github.com/vsslctrl/integration.home-assistant) with basic functionality working on:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
|
|
37
|
+
| Model | Software Version | User Reported |
|
|
38
|
+
| ------------|--------- | -------------
|
|
39
|
+
| A.1 | p15265.033.3703 | ✔️
|
|
40
|
+
| A.3 | p12013.141.3703 | ✔️
|
|
41
|
+
| A.3x | p15305.016.3701 |
|
|
42
|
+
| A.6x | p15305.017.3701 | ✔️
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Testers Needed
|
|
46
|
+
|
|
47
|
+
I am looking for testers with any VSSL amplifier models, please get in touch if you interested in helping. <vsslcontrolled@proton.me>
|
|
48
|
+
|
|
49
|
+
|
|
40
50
|
|
|
41
51
|
## Important
|
|
42
52
|
|
|
43
|
-
There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/getting-started/start)* running on the
|
|
53
|
+
There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/getting-started/start)* running on the network. If you dont know what this is, then you can ignore this notice.
|
|
54
|
+
|
|
55
|
+
**`vsslctrl` is not endorsed or affiliated with [VSSL](https://www.vssl.com/) in any manner.**
|
|
44
56
|
|
|
45
57
|
## TODOs
|
|
46
58
|
|
|
47
|
-
* Correct IO mapping between models and versions
|
|
48
59
|
* Better test coverage
|
|
49
|
-
* A.1(x) testing
|
|
60
|
+
* A.1(x) testing - e.g output settings
|
|
61
|
+
* More controls - e.g IR Control
|
|
50
62
|
|
|
51
63
|
## Basic Usage
|
|
52
64
|
|
|
@@ -62,9 +74,9 @@ async def main():
|
|
|
62
74
|
|
|
63
75
|
# Represents a physical VSSL amplifier
|
|
64
76
|
vssl = Vssl(DeviceModels.A1)
|
|
65
|
-
a1 = vssl.add_zone(
|
|
77
|
+
a1 = vssl.add_zone('192.168.1.10')
|
|
66
78
|
|
|
67
|
-
# Connect and
|
|
79
|
+
# Connect and initialise zone.
|
|
68
80
|
await vssl.initialise()
|
|
69
81
|
|
|
70
82
|
"""Control Examples"""
|
|
@@ -92,16 +104,15 @@ from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
|
|
|
92
104
|
async def main():
|
|
93
105
|
|
|
94
106
|
# Represents a physical VSSL amplifier
|
|
95
|
-
# If no DeviceModels is passed, vsslctrl will default to the feature set of the X series amps
|
|
96
107
|
vssl = Vssl(DeviceModels.A3X)
|
|
97
108
|
|
|
98
109
|
# Add each you wish to control
|
|
99
|
-
zone1 = vssl.add_zone(
|
|
100
|
-
zone2 = vssl.add_zone(
|
|
101
|
-
zone3 = vssl.add_zone(
|
|
102
|
-
#... up to 6 zones
|
|
110
|
+
zone1 = vssl.add_zone('192.168.1.10', ZoneIDs.ZONE_1)
|
|
111
|
+
zone2 = vssl.add_zone('192.168.1.11', ZoneIDs.ZONE_2)
|
|
112
|
+
zone3 = vssl.add_zone('192.168.1.12', ZoneIDs.ZONE_3)
|
|
113
|
+
#... up to 6 zones for A.6(x)
|
|
103
114
|
|
|
104
|
-
# Connect and
|
|
115
|
+
# Connect and initialise zones.
|
|
105
116
|
await vssl.initialise()
|
|
106
117
|
|
|
107
118
|
"""Control Examples"""
|
|
@@ -123,24 +134,106 @@ async def main():
|
|
|
123
134
|
asyncio.run(main())
|
|
124
135
|
```
|
|
125
136
|
|
|
126
|
-
|
|
137
|
+
### Device Discovery Helper
|
|
138
|
+
|
|
139
|
+
You can discover VSSL devices on the network using [mDNS](https://wikipedia.org/wiki/Multicast_DNS) / Bonjour if you have the [`zeroconf`](https://pypi.org/project/zeroconf/) package installed.
|
|
140
|
+
|
|
141
|
+
This uses airplay service string `_airplay._tcp.local.`, therefore airplay needs to available and will not work across VLANs without other provisions.
|
|
127
142
|
|
|
128
|
-
|
|
143
|
+
**Note:** This is designed to be a helper and its not recommended to be used for the initialization of the VSSL class.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import asyncio
|
|
147
|
+
from vsslctrl import Vssl
|
|
148
|
+
|
|
149
|
+
async def main():
|
|
150
|
+
|
|
151
|
+
print(await Vssl.discover())
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
{
|
|
155
|
+
'XXXXXXXXXXXX': [
|
|
156
|
+
{
|
|
157
|
+
'host': '192.168.168.25',
|
|
158
|
+
'name': 'Living Room',
|
|
159
|
+
'model': 'A1x',
|
|
160
|
+
'mac_addr': 'AA:BB:CC:DD:EE:FF',
|
|
161
|
+
'zone_id': '7',
|
|
162
|
+
'serial': 'XXXXXXXXXXXX'
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
asyncio.run(main())
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# API Functionality
|
|
174
|
+
|
|
175
|
+
Most functionality is achieved via `getters` and `setters` of the two main classes `Vssl`, `Zone`.
|
|
129
176
|
|
|
130
177
|
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:
|
|
131
178
|
|
|
132
179
|
```python
|
|
133
|
-
# Setting the
|
|
180
|
+
# Setting the zone name
|
|
134
181
|
zone1.settings.name = 'Living Room'
|
|
135
|
-
>>>
|
|
182
|
+
>>> 'Old Zone Name'
|
|
136
183
|
|
|
137
184
|
# Printing zone name
|
|
138
|
-
|
|
139
|
-
print(zone_name)
|
|
185
|
+
print(zone1.settings.name)
|
|
140
186
|
>>> 'Living Room'
|
|
141
187
|
```
|
|
142
188
|
|
|
143
|
-
**Important** in the above example, `zone1.settings.name`
|
|
189
|
+
**Important** in the above example, `zone1.settings.name` won't be set to its new value until after the VSSL device has changed the name and the `Zone` class has received confirmation feedback. If you need to wait for the value change, you can await a `[property_name]_CHANGE` events as below:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from vsslctrl.settings import ZoneSettings
|
|
193
|
+
# Setting the zone name and wait for feedback
|
|
194
|
+
future_name = vssl.event_bus.future(ZoneSettings.Events.NAME_CHANGE, zone1.id)
|
|
195
|
+
zone1.settings.name = 'Bathroom'
|
|
196
|
+
# Helper to await a future with timeout
|
|
197
|
+
new_name = await vssl.event_bus.wait_future(future_name)
|
|
198
|
+
# Printing zone name
|
|
199
|
+
print(new_name)
|
|
200
|
+
>>> 'Bathroom'
|
|
201
|
+
# or
|
|
202
|
+
print(zone1.settings.name)
|
|
203
|
+
>>> 'Bathroom'
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
# API Reference
|
|
207
|
+
|
|
208
|
+
# `DeviceModels`
|
|
209
|
+
|
|
210
|
+
A device model has to be passed to VSSL so it knows internally what features are supported by the device.
|
|
211
|
+
This might be removed in the future if we can differentiate different models from the API.
|
|
212
|
+
|
|
213
|
+
| Property | Description |
|
|
214
|
+
| ------------|--------- |
|
|
215
|
+
| `A1X` | A.1x |
|
|
216
|
+
| `A3X` | A.3x |
|
|
217
|
+
| `A6X` | A.6x |
|
|
218
|
+
| `A1` | A1 |
|
|
219
|
+
| `A3` | A3 |
|
|
220
|
+
| `A6` | A6 |
|
|
221
|
+
|
|
222
|
+
# `ZoneIDs`
|
|
223
|
+
|
|
224
|
+
A `ZoneIDs` must be passed to each `zone` you which to control and it must match the zone on the VSSL device.
|
|
225
|
+
|
|
226
|
+
If you are unsure of your `ZoneIDs` you could use the discovery helper to find out the correct mapping.
|
|
227
|
+
|
|
228
|
+
| Property | Description | A.1(x) | A.3(x) | A.6(x)
|
|
229
|
+
| ------------|--------- |-------- | -------- | -------- |
|
|
230
|
+
| `A1` | |✔️ | | |
|
|
231
|
+
| `ZONE_1` | Zone 1 | | ✔️| ✔️|
|
|
232
|
+
| `ZONE_2` | Zone 2 | | ✔️| ✔️|
|
|
233
|
+
| `ZONE_3` | Zone 3 | | ✔️| ✔️|
|
|
234
|
+
| `ZONE_4` | Zone 4 | | | ✔️|
|
|
235
|
+
| `ZONE_5` | Zone 5 | | | ✔️|
|
|
236
|
+
| `ZONE_6` | Zone 6 | | | ✔️|
|
|
144
237
|
|
|
145
238
|
|
|
146
239
|
# `Vssl`
|
|
@@ -164,7 +257,7 @@ vssl.factory_reset()
|
|
|
164
257
|
|
|
165
258
|
## `Vssl.settings`
|
|
166
259
|
|
|
167
|
-
| Property | Description | Type |
|
|
260
|
+
| Property | Description | Type | Model: Default |
|
|
168
261
|
| ---------------------- | ----------- | ----------- | ----------- |
|
|
169
262
|
| `name` | Device name | `str` |
|
|
170
263
|
| `bus_1_name` | Name of Bus 1 | `str` | <ul><li>A.1: Optical Input</li><li>A.3x/6x: Not Used</li></ul>
|
|
@@ -313,13 +406,13 @@ Input `InputRouter.Priorities` still apply.
|
|
|
313
406
|
# Change zone 1 to listen to analog input 4
|
|
314
407
|
zone1.input.source = InputRouter.Sources.ANALOG_IN_4
|
|
315
408
|
|
|
316
|
-
# Change zone 1 to perfer
|
|
409
|
+
# Change zone 1 to perfer local inputs over stream
|
|
317
410
|
zone1.input.priority = InputRouter.Priorities.LOCAL
|
|
318
411
|
```
|
|
319
412
|
|
|
320
413
|
## `Zone.group`
|
|
321
414
|
|
|
322
|
-
|
|
415
|
+
Unsupported on X series amplifiers.
|
|
323
416
|
|
|
324
417
|
| Property | Description | Type | Values |
|
|
325
418
|
| ---------------------- | ----------- | ----------- |----------- |
|
|
@@ -458,55 +551,6 @@ zone1.settings.eq.khz1_db = -2
|
|
|
458
551
|
zone1.settings.subwoofer.crossover = 100
|
|
459
552
|
```
|
|
460
553
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
## Another (Lite) Way
|
|
464
|
-
|
|
465
|
-
If you perfer to not run the complete intergration, you can send basic HEX commands to the VSSL device using [Netcat](https://nc110.sourceforge.io/) (or any network tool) on port `50002`.
|
|
466
|
-
|
|
467
|
-
| HEX | Description |
|
|
468
|
-
| ---------------------- | ----------- |
|
|
469
|
-
| `\x10\x05\x03\x0{Zone Number}\xff\x03` | Volume Up
|
|
470
|
-
| `\x10\x05\x03\x0{Zone Number}\xfe\x03` | Volume Down
|
|
471
|
-
| `\x10\x11\x02\x0{Zone Number}\x01` | Mute
|
|
472
|
-
| All commands can be found by looking [here](https://github.com/vsslctrl/vsslctrl/blob/2c43c2f2393b94bc0e062d2ab90144343eca16ef/vsslctrl/api_alpha.py) |
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
The `volume up` HEX command for `Zone 2` would be `\x10\x05\x03\x02\xff\x03`
|
|
476
|
-
|
|
477
|
-
Now send the raw HEX using Netcat to the device using this syntax:
|
|
478
|
-
|
|
479
|
-
`echo -e "{HEX Command}" | nc {IP Address} 50002`
|
|
480
|
-
|
|
481
|
-
For example to send `volume up` to `Zone 2`:
|
|
482
|
-
|
|
483
|
-
`echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002`
|
|
484
|
-
|
|
485
|
-
Home Assistant `Configuration.yaml` example:
|
|
486
|
-
|
|
487
|
-
```ymal
|
|
488
|
-
...
|
|
489
|
-
|
|
490
|
-
shell_command:
|
|
491
|
-
#Zone 1
|
|
492
|
-
vssl_zone_1_volume_up: 'echo -e "\x10\x05\x03\x01\xff\x03" | nc 192.168.1.10 50002'
|
|
493
|
-
vssl_zone_1_volume_down: 'echo -e "\x10\x05\x03\x01\xfe\x03" | nc 192.168.1.10 50002'
|
|
494
|
-
vssl_zone_1_mute: 'echo -e "\x10\x11\x02\x01\x01" | nc 192.168.1.10 50002'
|
|
495
|
-
vssl_zone_1_unmute: 'echo -e "\x10\x11\x02\x01\x00" | nc 192.168.1.10 50002'
|
|
496
|
-
#Zone 2
|
|
497
|
-
vssl_zone_2_volume_up: 'echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002'
|
|
498
|
-
vssl_zone_2_volume_down: 'echo -e "\x10\x05\x03\x02\xfe\x03" | nc 192.168.1.11 50002'
|
|
499
|
-
vssl_zone_2_mute: 'echo -e "\x10\x11\x02\x02\x01" | nc 192.168.1.11 50002'
|
|
500
|
-
vssl_zone_2_unmute: 'echo -e "\x10\x11\x02\x02\x00" | nc 192.168.1.11 50002'
|
|
501
|
-
#Zone 3
|
|
502
|
-
vssl_zone_3_volume_up: 'echo -e "\x10\x05\x03\x03\xff\x03" | nc 192.168.1.12 50002'
|
|
503
|
-
vssl_zone_3_volume_down: 'echo -e "\x10\x05\x03\x03\xfe\x03" | nc 192.168.1.12 50002'
|
|
504
|
-
vssl_zone_3_mute: 'echo -e "\x10\x11\x02\x03\x01" | nc 192.168.1.12 50002'
|
|
505
|
-
vssl_zone_3_unmute: 'echo -e "\x10\x11\x02\x03\x00" | nc 192.168.1.12 50002'
|
|
506
|
-
|
|
507
|
-
...
|
|
508
|
-
```
|
|
509
|
-
|
|
510
554
|
## Credit
|
|
511
555
|
|
|
512
556
|
Thanks to [@dj-jam](https://github.com/dj-jam) for the continued testing.
|
|
@@ -520,17 +564,9 @@ Motivation for this project was to integrate VSSLs amplifiers into [Home Assista
|
|
|
520
564
|
* Not tested on A.1x or original A series range of amplifiers (testers welcome)
|
|
521
565
|
* VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
|
|
522
566
|
* Not all sources set the volume to 0 when the zone is muted
|
|
523
|
-
* Grouping feedback is flaky on the X series amplifiers
|
|
524
567
|
* Airplay `Zone.track.progress` is not available.
|
|
525
568
|
* Cant stop a URL playback, feedback is worng at least
|
|
526
569
|
* 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
|
|
527
570
|
* `stop()` is intended to disconnect the client and pause the stream. Doesn’t always function this way, depending on stream source
|
|
528
571
|
* Occasionally a zones might stop responding to certain commands, issuing the `reboot` command generally corrects
|
|
529
572
|
|
|
530
|
-
## Future
|
|
531
|
-
|
|
532
|
-
* A.1(x) coverage i.e Bluetooth
|
|
533
|
-
* REST API / Web App
|
|
534
|
-
* Save and recall EQ
|
|
535
|
-
* IR Control
|
|
536
|
-
|
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
# vsslctrl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Coverage
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Tested on:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| Model | Software Version | Note |
|
|
10
|
+
| ------------|--------- | -------------
|
|
11
|
+
| A.3x | p15305.016.3701 |
|
|
10
12
|
|
|
11
|
-
Tested on:
|
|
12
|
-
- **A.3x** software version **p15305.016.3701**
|
|
13
13
|
|
|
14
14
|
Home Assistant [integration](https://github.com/vsslctrl/integration.home-assistant) with basic functionality working on:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
| Model | Software Version | User Reported |
|
|
17
|
+
| ------------|--------- | -------------
|
|
18
|
+
| A.1 | p15265.033.3703 | ✔️
|
|
19
|
+
| A.3 | p12013.141.3703 | ✔️
|
|
20
|
+
| A.3x | p15305.016.3701 |
|
|
21
|
+
| A.6x | p15305.017.3701 | ✔️
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Testers Needed
|
|
25
|
+
|
|
26
|
+
I am looking for testers with any VSSL amplifier models, please get in touch if you interested in helping. <vsslcontrolled@proton.me>
|
|
27
|
+
|
|
28
|
+
|
|
19
29
|
|
|
20
30
|
## Important
|
|
21
31
|
|
|
22
|
-
There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/getting-started/start)* running on the
|
|
32
|
+
There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/getting-started/start)* running on the network. If you dont know what this is, then you can ignore this notice.
|
|
33
|
+
|
|
34
|
+
**`vsslctrl` is not endorsed or affiliated with [VSSL](https://www.vssl.com/) in any manner.**
|
|
23
35
|
|
|
24
36
|
## TODOs
|
|
25
37
|
|
|
26
|
-
* Correct IO mapping between models and versions
|
|
27
38
|
* Better test coverage
|
|
28
|
-
* A.1(x) testing
|
|
39
|
+
* A.1(x) testing - e.g output settings
|
|
40
|
+
* More controls - e.g IR Control
|
|
29
41
|
|
|
30
42
|
## Basic Usage
|
|
31
43
|
|
|
@@ -41,9 +53,9 @@ async def main():
|
|
|
41
53
|
|
|
42
54
|
# Represents a physical VSSL amplifier
|
|
43
55
|
vssl = Vssl(DeviceModels.A1)
|
|
44
|
-
a1 = vssl.add_zone(
|
|
56
|
+
a1 = vssl.add_zone('192.168.1.10')
|
|
45
57
|
|
|
46
|
-
# Connect and
|
|
58
|
+
# Connect and initialise zone.
|
|
47
59
|
await vssl.initialise()
|
|
48
60
|
|
|
49
61
|
"""Control Examples"""
|
|
@@ -71,16 +83,15 @@ from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
|
|
|
71
83
|
async def main():
|
|
72
84
|
|
|
73
85
|
# Represents a physical VSSL amplifier
|
|
74
|
-
# If no DeviceModels is passed, vsslctrl will default to the feature set of the X series amps
|
|
75
86
|
vssl = Vssl(DeviceModels.A3X)
|
|
76
87
|
|
|
77
88
|
# Add each you wish to control
|
|
78
|
-
zone1 = vssl.add_zone(
|
|
79
|
-
zone2 = vssl.add_zone(
|
|
80
|
-
zone3 = vssl.add_zone(
|
|
81
|
-
#... up to 6 zones
|
|
89
|
+
zone1 = vssl.add_zone('192.168.1.10', ZoneIDs.ZONE_1)
|
|
90
|
+
zone2 = vssl.add_zone('192.168.1.11', ZoneIDs.ZONE_2)
|
|
91
|
+
zone3 = vssl.add_zone('192.168.1.12', ZoneIDs.ZONE_3)
|
|
92
|
+
#... up to 6 zones for A.6(x)
|
|
82
93
|
|
|
83
|
-
# Connect and
|
|
94
|
+
# Connect and initialise zones.
|
|
84
95
|
await vssl.initialise()
|
|
85
96
|
|
|
86
97
|
"""Control Examples"""
|
|
@@ -102,24 +113,106 @@ async def main():
|
|
|
102
113
|
asyncio.run(main())
|
|
103
114
|
```
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
### Device Discovery Helper
|
|
117
|
+
|
|
118
|
+
You can discover VSSL devices on the network using [mDNS](https://wikipedia.org/wiki/Multicast_DNS) / Bonjour if you have the [`zeroconf`](https://pypi.org/project/zeroconf/) package installed.
|
|
119
|
+
|
|
120
|
+
This uses airplay service string `_airplay._tcp.local.`, therefore airplay needs to available and will not work across VLANs without other provisions.
|
|
106
121
|
|
|
107
|
-
|
|
122
|
+
**Note:** This is designed to be a helper and its not recommended to be used for the initialization of the VSSL class.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import asyncio
|
|
126
|
+
from vsslctrl import Vssl
|
|
127
|
+
|
|
128
|
+
async def main():
|
|
129
|
+
|
|
130
|
+
print(await Vssl.discover())
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
{
|
|
134
|
+
'XXXXXXXXXXXX': [
|
|
135
|
+
{
|
|
136
|
+
'host': '192.168.168.25',
|
|
137
|
+
'name': 'Living Room',
|
|
138
|
+
'model': 'A1x',
|
|
139
|
+
'mac_addr': 'AA:BB:CC:DD:EE:FF',
|
|
140
|
+
'zone_id': '7',
|
|
141
|
+
'serial': 'XXXXXXXXXXXX'
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
asyncio.run(main())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# API Functionality
|
|
153
|
+
|
|
154
|
+
Most functionality is achieved via `getters` and `setters` of the two main classes `Vssl`, `Zone`.
|
|
108
155
|
|
|
109
156
|
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:
|
|
110
157
|
|
|
111
158
|
```python
|
|
112
|
-
# Setting the
|
|
159
|
+
# Setting the zone name
|
|
113
160
|
zone1.settings.name = 'Living Room'
|
|
114
|
-
>>>
|
|
161
|
+
>>> 'Old Zone Name'
|
|
115
162
|
|
|
116
163
|
# Printing zone name
|
|
117
|
-
|
|
118
|
-
print(zone_name)
|
|
164
|
+
print(zone1.settings.name)
|
|
119
165
|
>>> 'Living Room'
|
|
120
166
|
```
|
|
121
167
|
|
|
122
|
-
**Important** in the above example, `zone1.settings.name`
|
|
168
|
+
**Important** in the above example, `zone1.settings.name` won't be set to its new value until after the VSSL device has changed the name and the `Zone` class has received confirmation feedback. If you need to wait for the value change, you can await a `[property_name]_CHANGE` events as below:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from vsslctrl.settings import ZoneSettings
|
|
172
|
+
# Setting the zone name and wait for feedback
|
|
173
|
+
future_name = vssl.event_bus.future(ZoneSettings.Events.NAME_CHANGE, zone1.id)
|
|
174
|
+
zone1.settings.name = 'Bathroom'
|
|
175
|
+
# Helper to await a future with timeout
|
|
176
|
+
new_name = await vssl.event_bus.wait_future(future_name)
|
|
177
|
+
# Printing zone name
|
|
178
|
+
print(new_name)
|
|
179
|
+
>>> 'Bathroom'
|
|
180
|
+
# or
|
|
181
|
+
print(zone1.settings.name)
|
|
182
|
+
>>> 'Bathroom'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
# API Reference
|
|
186
|
+
|
|
187
|
+
# `DeviceModels`
|
|
188
|
+
|
|
189
|
+
A device model has to be passed to VSSL so it knows internally what features are supported by the device.
|
|
190
|
+
This might be removed in the future if we can differentiate different models from the API.
|
|
191
|
+
|
|
192
|
+
| Property | Description |
|
|
193
|
+
| ------------|--------- |
|
|
194
|
+
| `A1X` | A.1x |
|
|
195
|
+
| `A3X` | A.3x |
|
|
196
|
+
| `A6X` | A.6x |
|
|
197
|
+
| `A1` | A1 |
|
|
198
|
+
| `A3` | A3 |
|
|
199
|
+
| `A6` | A6 |
|
|
200
|
+
|
|
201
|
+
# `ZoneIDs`
|
|
202
|
+
|
|
203
|
+
A `ZoneIDs` must be passed to each `zone` you which to control and it must match the zone on the VSSL device.
|
|
204
|
+
|
|
205
|
+
If you are unsure of your `ZoneIDs` you could use the discovery helper to find out the correct mapping.
|
|
206
|
+
|
|
207
|
+
| Property | Description | A.1(x) | A.3(x) | A.6(x)
|
|
208
|
+
| ------------|--------- |-------- | -------- | -------- |
|
|
209
|
+
| `A1` | |✔️ | | |
|
|
210
|
+
| `ZONE_1` | Zone 1 | | ✔️| ✔️|
|
|
211
|
+
| `ZONE_2` | Zone 2 | | ✔️| ✔️|
|
|
212
|
+
| `ZONE_3` | Zone 3 | | ✔️| ✔️|
|
|
213
|
+
| `ZONE_4` | Zone 4 | | | ✔️|
|
|
214
|
+
| `ZONE_5` | Zone 5 | | | ✔️|
|
|
215
|
+
| `ZONE_6` | Zone 6 | | | ✔️|
|
|
123
216
|
|
|
124
217
|
|
|
125
218
|
# `Vssl`
|
|
@@ -143,7 +236,7 @@ vssl.factory_reset()
|
|
|
143
236
|
|
|
144
237
|
## `Vssl.settings`
|
|
145
238
|
|
|
146
|
-
| Property | Description | Type |
|
|
239
|
+
| Property | Description | Type | Model: Default |
|
|
147
240
|
| ---------------------- | ----------- | ----------- | ----------- |
|
|
148
241
|
| `name` | Device name | `str` |
|
|
149
242
|
| `bus_1_name` | Name of Bus 1 | `str` | <ul><li>A.1: Optical Input</li><li>A.3x/6x: Not Used</li></ul>
|
|
@@ -292,13 +385,13 @@ Input `InputRouter.Priorities` still apply.
|
|
|
292
385
|
# Change zone 1 to listen to analog input 4
|
|
293
386
|
zone1.input.source = InputRouter.Sources.ANALOG_IN_4
|
|
294
387
|
|
|
295
|
-
# Change zone 1 to perfer
|
|
388
|
+
# Change zone 1 to perfer local inputs over stream
|
|
296
389
|
zone1.input.priority = InputRouter.Priorities.LOCAL
|
|
297
390
|
```
|
|
298
391
|
|
|
299
392
|
## `Zone.group`
|
|
300
393
|
|
|
301
|
-
|
|
394
|
+
Unsupported on X series amplifiers.
|
|
302
395
|
|
|
303
396
|
| Property | Description | Type | Values |
|
|
304
397
|
| ---------------------- | ----------- | ----------- |----------- |
|
|
@@ -437,55 +530,6 @@ zone1.settings.eq.khz1_db = -2
|
|
|
437
530
|
zone1.settings.subwoofer.crossover = 100
|
|
438
531
|
```
|
|
439
532
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
## Another (Lite) Way
|
|
443
|
-
|
|
444
|
-
If you perfer to not run the complete intergration, you can send basic HEX commands to the VSSL device using [Netcat](https://nc110.sourceforge.io/) (or any network tool) on port `50002`.
|
|
445
|
-
|
|
446
|
-
| HEX | Description |
|
|
447
|
-
| ---------------------- | ----------- |
|
|
448
|
-
| `\x10\x05\x03\x0{Zone Number}\xff\x03` | Volume Up
|
|
449
|
-
| `\x10\x05\x03\x0{Zone Number}\xfe\x03` | Volume Down
|
|
450
|
-
| `\x10\x11\x02\x0{Zone Number}\x01` | Mute
|
|
451
|
-
| All commands can be found by looking [here](https://github.com/vsslctrl/vsslctrl/blob/2c43c2f2393b94bc0e062d2ab90144343eca16ef/vsslctrl/api_alpha.py) |
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
The `volume up` HEX command for `Zone 2` would be `\x10\x05\x03\x02\xff\x03`
|
|
455
|
-
|
|
456
|
-
Now send the raw HEX using Netcat to the device using this syntax:
|
|
457
|
-
|
|
458
|
-
`echo -e "{HEX Command}" | nc {IP Address} 50002`
|
|
459
|
-
|
|
460
|
-
For example to send `volume up` to `Zone 2`:
|
|
461
|
-
|
|
462
|
-
`echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002`
|
|
463
|
-
|
|
464
|
-
Home Assistant `Configuration.yaml` example:
|
|
465
|
-
|
|
466
|
-
```ymal
|
|
467
|
-
...
|
|
468
|
-
|
|
469
|
-
shell_command:
|
|
470
|
-
#Zone 1
|
|
471
|
-
vssl_zone_1_volume_up: 'echo -e "\x10\x05\x03\x01\xff\x03" | nc 192.168.1.10 50002'
|
|
472
|
-
vssl_zone_1_volume_down: 'echo -e "\x10\x05\x03\x01\xfe\x03" | nc 192.168.1.10 50002'
|
|
473
|
-
vssl_zone_1_mute: 'echo -e "\x10\x11\x02\x01\x01" | nc 192.168.1.10 50002'
|
|
474
|
-
vssl_zone_1_unmute: 'echo -e "\x10\x11\x02\x01\x00" | nc 192.168.1.10 50002'
|
|
475
|
-
#Zone 2
|
|
476
|
-
vssl_zone_2_volume_up: 'echo -e "\x10\x05\x03\x02\xff\x03" | nc 192.168.1.11 50002'
|
|
477
|
-
vssl_zone_2_volume_down: 'echo -e "\x10\x05\x03\x02\xfe\x03" | nc 192.168.1.11 50002'
|
|
478
|
-
vssl_zone_2_mute: 'echo -e "\x10\x11\x02\x02\x01" | nc 192.168.1.11 50002'
|
|
479
|
-
vssl_zone_2_unmute: 'echo -e "\x10\x11\x02\x02\x00" | nc 192.168.1.11 50002'
|
|
480
|
-
#Zone 3
|
|
481
|
-
vssl_zone_3_volume_up: 'echo -e "\x10\x05\x03\x03\xff\x03" | nc 192.168.1.12 50002'
|
|
482
|
-
vssl_zone_3_volume_down: 'echo -e "\x10\x05\x03\x03\xfe\x03" | nc 192.168.1.12 50002'
|
|
483
|
-
vssl_zone_3_mute: 'echo -e "\x10\x11\x02\x03\x01" | nc 192.168.1.12 50002'
|
|
484
|
-
vssl_zone_3_unmute: 'echo -e "\x10\x11\x02\x03\x00" | nc 192.168.1.12 50002'
|
|
485
|
-
|
|
486
|
-
...
|
|
487
|
-
```
|
|
488
|
-
|
|
489
533
|
## Credit
|
|
490
534
|
|
|
491
535
|
Thanks to [@dj-jam](https://github.com/dj-jam) for the continued testing.
|
|
@@ -499,17 +543,9 @@ Motivation for this project was to integrate VSSLs amplifiers into [Home Assista
|
|
|
499
543
|
* Not tested on A.1x or original A series range of amplifiers (testers welcome)
|
|
500
544
|
* VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
|
|
501
545
|
* Not all sources set the volume to 0 when the zone is muted
|
|
502
|
-
* Grouping feedback is flaky on the X series amplifiers
|
|
503
546
|
* Airplay `Zone.track.progress` is not available.
|
|
504
547
|
* Cant stop a URL playback, feedback is worng at least
|
|
505
548
|
* 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
|
|
506
549
|
* `stop()` is intended to disconnect the client and pause the stream. Doesn’t always function this way, depending on stream source
|
|
507
550
|
* Occasionally a zones might stop responding to certain commands, issuing the `reboot` command generally corrects
|
|
508
551
|
|
|
509
|
-
## Future
|
|
510
|
-
|
|
511
|
-
* A.1(x) coverage i.e Bluetooth
|
|
512
|
-
* REST API / Web App
|
|
513
|
-
* Save and recall EQ
|
|
514
|
-
* IR Control
|
|
515
|
-
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vsslctrl"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.13.dev1"
|
|
8
8
|
description = "Package for controlling VSSL's range of streaming amplifiers"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="vsslctrl", email="vsslcontrolled@proton.me" },
|