vsslctrl 0.1.4.dev1__py3-none-any.whl → 0.1.6.dev1__py3-none-any.whl
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/api_alpha.py +8 -6
- vsslctrl/api_base.py +13 -0
- vsslctrl/api_bravo.py +5 -3
- vsslctrl/core.py +1 -1
- vsslctrl/data_structure.py +187 -39
- vsslctrl/io.py +1 -1
- vsslctrl/utils.py +13 -0
- vsslctrl/zone.py +5 -0
- {vsslctrl-0.1.4.dev1.dist-info → vsslctrl-0.1.6.dev1.dist-info}/METADATA +51 -49
- vsslctrl-0.1.6.dev1.dist-info/RECORD +23 -0
- {vsslctrl-0.1.4.dev1.dist-info → vsslctrl-0.1.6.dev1.dist-info}/WHEEL +1 -1
- vsslctrl-0.1.4.dev1.dist-info/RECORD +0 -23
- {vsslctrl-0.1.4.dev1.dist-info → vsslctrl-0.1.6.dev1.dist-info}/LICENSE +0 -0
- {vsslctrl-0.1.4.dev1.dist-info → vsslctrl-0.1.6.dev1.dist-info}/top_level.txt +0 -0
vsslctrl/api_alpha.py
CHANGED
|
@@ -9,7 +9,7 @@ from .api_base import APIBase
|
|
|
9
9
|
from .transport import ZoneTransport
|
|
10
10
|
from .settings import EQSettings
|
|
11
11
|
|
|
12
|
-
from .utils import hex_to_int, clamp_volume
|
|
12
|
+
from .utils import hex_to_int, clamp_volume, hex_to_bytearray_string
|
|
13
13
|
from .decorators import logging_helpers
|
|
14
14
|
from .data_structure import (
|
|
15
15
|
ZoneStatusExtKeys,
|
|
@@ -199,7 +199,7 @@ class APIAlpha(APIBase):
|
|
|
199
199
|
# EQ
|
|
200
200
|
#
|
|
201
201
|
def request_action_0D(self, freq: "EQSettings.Freqs", value: int = 0):
|
|
202
|
-
clamped = max(
|
|
202
|
+
clamped = max(EQSettings.MIN_VALUE, min(value, EQSettings.MAX_VALUE))
|
|
203
203
|
self._log_debug(
|
|
204
204
|
f"Requesting to set EQ: {freq.name[1:]} ({freq.value}) to {clamped}"
|
|
205
205
|
)
|
|
@@ -924,9 +924,9 @@ class APIAlpha(APIBase):
|
|
|
924
924
|
|
|
925
925
|
#
|
|
926
926
|
# 17 [23]
|
|
927
|
-
#
|
|
927
|
+
# Keep Alive
|
|
928
928
|
#
|
|
929
|
-
def response_action_17(self, response: bytes):
|
|
929
|
+
def response_action_17(self, hexl: list, response: bytes):
|
|
930
930
|
self._log_debug(f"Z{self.zone.id} Alpha - Received keep alive: {response}")
|
|
931
931
|
# TODO
|
|
932
932
|
pass
|
|
@@ -1026,8 +1026,9 @@ class APIAlpha(APIBase):
|
|
|
1026
1026
|
# Command confimation
|
|
1027
1027
|
#
|
|
1028
1028
|
def response_action_confimation(self, response: bytes):
|
|
1029
|
+
cmd = response.hex()
|
|
1029
1030
|
self._log_debug(
|
|
1030
|
-
f"Received command confimation: {
|
|
1031
|
+
f"Received command confimation: {hex_to_bytearray_string(cmd)} Hex: {cmd}"
|
|
1031
1032
|
)
|
|
1032
1033
|
|
|
1033
1034
|
#
|
|
@@ -1035,6 +1036,7 @@ class APIAlpha(APIBase):
|
|
|
1035
1036
|
# Default Action
|
|
1036
1037
|
#
|
|
1037
1038
|
def response_action_default(self, hexl: list, response: bytes):
|
|
1039
|
+
cmd = response.hex()
|
|
1038
1040
|
self._log_debug(
|
|
1039
|
-
f"Received unknown command {
|
|
1041
|
+
f"Received unknown command: {hex_to_bytearray_string(cmd)} Hex: {cmd}"
|
|
1040
1042
|
)
|
vsslctrl/api_base.py
CHANGED
|
@@ -158,6 +158,19 @@ class APIBase(ABC):
|
|
|
158
158
|
|
|
159
159
|
except asyncio.CancelledError:
|
|
160
160
|
self._log_debug(f"writer close timeout")
|
|
161
|
+
except asyncio.TimeoutError as e:
|
|
162
|
+
self._log_error(f"Timeout while closing connection: {e}")
|
|
163
|
+
# Handle the timeout: log, retry, or take other actions
|
|
164
|
+
except ConnectionResetError as e:
|
|
165
|
+
self._log_error(f"Connection reset by peer: {e}")
|
|
166
|
+
# Handle the error: log, retry, or take other actions
|
|
167
|
+
except Exception as e:
|
|
168
|
+
self._log_error(f"Unexpected error occurred while disconnecting: {e}")
|
|
169
|
+
# Handle unexpected errors
|
|
170
|
+
finally:
|
|
171
|
+
self._writer = None
|
|
172
|
+
|
|
173
|
+
self._reader = None
|
|
161
174
|
|
|
162
175
|
self._log_info(f"{self.host}:{self.port}: disconnected")
|
|
163
176
|
|
vsslctrl/api_bravo.py
CHANGED
|
@@ -5,7 +5,7 @@ import struct
|
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
7
|
from .api_base import APIBase
|
|
8
|
-
from .utils import hex_to_int
|
|
8
|
+
from .utils import hex_to_int, hex_to_bytearray_string
|
|
9
9
|
from .decorators import logging_helpers
|
|
10
10
|
from .data_structure import TrackMetadataExtKeys
|
|
11
11
|
|
|
@@ -407,8 +407,10 @@ class APIBravo(APIBase):
|
|
|
407
407
|
#
|
|
408
408
|
def response_action_70(self, hexl: list, response: bytes):
|
|
409
409
|
length = 4 if hexl[0] == 16 else 10
|
|
410
|
-
cmd =
|
|
411
|
-
self._log_debug(
|
|
410
|
+
cmd = response[length:].hex()
|
|
411
|
+
self._log_debug(
|
|
412
|
+
f"Received command confimation: {hex_to_bytearray_string(cmd)} Hex: {cmd}"
|
|
413
|
+
)
|
|
412
414
|
|
|
413
415
|
#
|
|
414
416
|
# Default
|
vsslctrl/core.py
CHANGED
|
@@ -69,7 +69,7 @@ class Vssl:
|
|
|
69
69
|
self.Events.MODEL_CHANGE, self.ENTITY_ID
|
|
70
70
|
)
|
|
71
71
|
|
|
72
|
-
# Lets make sure the zone is initialised, otherwsie we fail all
|
|
72
|
+
# Lets make sure the zone is initialised, otherwsie we fail for all zones
|
|
73
73
|
await first_zone.initialise()
|
|
74
74
|
|
|
75
75
|
# Only continue after we have a model
|
vsslctrl/data_structure.py
CHANGED
|
@@ -43,12 +43,35 @@ class ZoneIDs(VsslIntEnum):
|
|
|
43
43
|
ZONE_6 = 6
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
"""
|
|
47
|
+
JSON Structure
|
|
48
|
+
|
|
49
|
+
DO NOT CHANGE - VSSL Defined
|
|
50
|
+
|
|
51
|
+
A3.x
|
|
52
|
+
{
|
|
53
|
+
"B1Src": "3",
|
|
54
|
+
"B2Src": "4",
|
|
55
|
+
"B3Src": "5",
|
|
56
|
+
"B1Nm": "",
|
|
57
|
+
"B2Nm": "Optical In",
|
|
58
|
+
"dev": "Device Name",
|
|
59
|
+
"ver": "p15305.016.3701"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
A6.x
|
|
63
|
+
{
|
|
64
|
+
"B1Src": "3",
|
|
65
|
+
"B2Src": "4",
|
|
66
|
+
"B3Src": "5",
|
|
67
|
+
"B4Src": "6",
|
|
68
|
+
"B5Src": "7",
|
|
69
|
+
"B6Src": "8",
|
|
70
|
+
"B1Nm": "",
|
|
71
|
+
"B2Nm": "",
|
|
72
|
+
"dev": "VSSL A.6x",
|
|
73
|
+
"ver": "p15305.017.3701"
|
|
74
|
+
}
|
|
52
75
|
"""
|
|
53
76
|
|
|
54
77
|
|
|
@@ -69,13 +92,50 @@ class DeviceStatusExtKeys:
|
|
|
69
92
|
return f"B{zone_id}Src"
|
|
70
93
|
|
|
71
94
|
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
"""
|
|
96
|
+
JSON Structure
|
|
97
|
+
|
|
98
|
+
DO NOT CHANGE - VSSL Defined
|
|
99
|
+
|
|
100
|
+
A3.x
|
|
101
|
+
{
|
|
102
|
+
"id": "1",
|
|
103
|
+
"ac": "0",
|
|
104
|
+
"mc": "XXXXXXXXXXXX",
|
|
105
|
+
"vol": "20",
|
|
106
|
+
"mt": "0",
|
|
107
|
+
"pa": "0",
|
|
108
|
+
"rm": "0",
|
|
109
|
+
"ts": "14",
|
|
110
|
+
"alex": "14",
|
|
111
|
+
"nmd": "0",
|
|
112
|
+
"ird": "14",
|
|
113
|
+
"lb": "24",
|
|
114
|
+
"tp": "13",
|
|
115
|
+
"wr": "0",
|
|
116
|
+
"as": "0",
|
|
117
|
+
"rg": "0"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
A6.x
|
|
121
|
+
{
|
|
122
|
+
"id": "1",
|
|
123
|
+
"ac": "0",
|
|
124
|
+
"mc": "XXXXXXXXXXXX",
|
|
125
|
+
"vol": "50",
|
|
126
|
+
"mt": "0",
|
|
127
|
+
"pa": "0",
|
|
128
|
+
"rm": "0",
|
|
129
|
+
"ts": "0",
|
|
130
|
+
"alex": "126",
|
|
131
|
+
"nmd": "0",
|
|
132
|
+
"ird": "255",
|
|
133
|
+
"lb": "17",
|
|
134
|
+
"tp": "16",
|
|
135
|
+
"wr": "0",
|
|
136
|
+
"as": "0",
|
|
137
|
+
"rg": "0"
|
|
138
|
+
}
|
|
79
139
|
"""
|
|
80
140
|
|
|
81
141
|
|
|
@@ -91,13 +151,42 @@ class ZoneStatusExtKeys:
|
|
|
91
151
|
DISABLED = "wr"
|
|
92
152
|
|
|
93
153
|
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
154
|
+
"""
|
|
155
|
+
JSON Structure
|
|
156
|
+
|
|
157
|
+
DO NOT CHANGE - VSSL Defined
|
|
158
|
+
|
|
159
|
+
A3.x
|
|
160
|
+
{
|
|
161
|
+
"mono": "0",
|
|
162
|
+
"AiNm": "Analog In 1",
|
|
163
|
+
"eq1": "100",
|
|
164
|
+
"eq2": "100",
|
|
165
|
+
"eq3": "100",
|
|
166
|
+
"eq4": "100",
|
|
167
|
+
"eq5": "100",
|
|
168
|
+
"eq6": "100",
|
|
169
|
+
"eq7": "100",
|
|
170
|
+
"voll": "75",
|
|
171
|
+
"volr": "75",
|
|
172
|
+
"vold": "0"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
A6.x
|
|
176
|
+
{
|
|
177
|
+
"mono": "0",
|
|
178
|
+
"AiNm": "",
|
|
179
|
+
"eq1": "100",
|
|
180
|
+
"eq2": "100",
|
|
181
|
+
"eq3": "100",
|
|
182
|
+
"eq4": "100",
|
|
183
|
+
"eq5": "100",
|
|
184
|
+
"eq6": "100",
|
|
185
|
+
"eq7": "100",
|
|
186
|
+
"voll": "75",
|
|
187
|
+
"volr": "75",
|
|
188
|
+
"vold": "0"
|
|
189
|
+
}
|
|
101
190
|
"""
|
|
102
191
|
|
|
103
192
|
|
|
@@ -116,13 +205,47 @@ class ZoneEQStatusExtKeys:
|
|
|
116
205
|
VOL_DEFAULT_ON = "vold"
|
|
117
206
|
|
|
118
207
|
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
208
|
+
"""
|
|
209
|
+
JSON Structure
|
|
210
|
+
|
|
211
|
+
DO NOT CHANGE - VSSL Defined
|
|
212
|
+
|
|
213
|
+
A3.x
|
|
214
|
+
{
|
|
215
|
+
"ECO": "0",
|
|
216
|
+
"eqsw": "1",
|
|
217
|
+
"inSrc": "0",
|
|
218
|
+
"SP": "0",
|
|
219
|
+
"BF1": "0",
|
|
220
|
+
"BF2": "0",
|
|
221
|
+
"BF3": "0",
|
|
222
|
+
"GRM": "0",
|
|
223
|
+
"GRS": "255",
|
|
224
|
+
"Pwr": "0",
|
|
225
|
+
"Bvr": "1",
|
|
226
|
+
"fxv": "24",
|
|
227
|
+
"AtPwr": "1"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
A6.x
|
|
231
|
+
{
|
|
232
|
+
"ECO": "0",
|
|
233
|
+
"eqsw": "1",
|
|
234
|
+
"inSrc": "0",
|
|
235
|
+
"SP": "0",
|
|
236
|
+
"BF1": "0",
|
|
237
|
+
"BF2": "0",
|
|
238
|
+
"BF3": "0",
|
|
239
|
+
"BF4": "0",
|
|
240
|
+
"BF5": "0",
|
|
241
|
+
"BF6": "0",
|
|
242
|
+
"GRM": "0",
|
|
243
|
+
"GRS": "255",
|
|
244
|
+
"Pwr": "0",
|
|
245
|
+
"Bvr": "2",
|
|
246
|
+
"fxv": "25",
|
|
247
|
+
"AtPwr": "1"
|
|
248
|
+
}
|
|
126
249
|
"""
|
|
127
250
|
|
|
128
251
|
|
|
@@ -130,7 +253,12 @@ class ZoneRouterStatusExtKeys:
|
|
|
130
253
|
EQ_ENABLED = "eqsw"
|
|
131
254
|
INPUT_SOURCE = "inSrc"
|
|
132
255
|
SOURCE_PRIORITY = "SP"
|
|
133
|
-
|
|
256
|
+
ANALOG_OUTPUT_1_FIXED_VOLUME = "BF1"
|
|
257
|
+
ANALOG_OUTPUT_2_FIXED_VOLUME = "BF2"
|
|
258
|
+
ANALOG_OUTPUT_3_FIXED_VOLUME = "BF3"
|
|
259
|
+
ANALOG_OUTPUT_4_FIXED_VOLUME = "BF4"
|
|
260
|
+
ANALOG_OUTPUT_5_FIXED_VOLUME = "BF5"
|
|
261
|
+
ANALOG_OUTPUT_6_FIXED_VOLUME = "BF6"
|
|
134
262
|
GROUP_SOURCE = "GRS"
|
|
135
263
|
GROUP_MASTER = "GRM"
|
|
136
264
|
POWER_STATE = "Pwr"
|
|
@@ -142,17 +270,37 @@ class ZoneRouterStatusExtKeys:
|
|
|
142
270
|
return f"BF{zone_id}"
|
|
143
271
|
|
|
144
272
|
|
|
145
|
-
"""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
273
|
+
"""
|
|
274
|
+
JSON Structure
|
|
275
|
+
|
|
276
|
+
DO NOT CHANGE - VSSL Defined
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
"Album": "International Skankers",
|
|
280
|
+
"Artist": "Ashkabad",
|
|
281
|
+
"BitDepth": 16,
|
|
282
|
+
"BitRate": "320000",
|
|
283
|
+
"CoverArtUrl": "https://i.scdn.co/image/ab67616d0000b2730cbb03a339c6ffd18d10eab2",
|
|
284
|
+
"Current Source": 4,
|
|
285
|
+
"Current_time": -1,
|
|
286
|
+
"DSDType": "",
|
|
287
|
+
"Fav": False,
|
|
288
|
+
"FileSize": 0,
|
|
289
|
+
"Genre": "",
|
|
290
|
+
"Index": 0,
|
|
291
|
+
"Mime": "Ogg",
|
|
292
|
+
"Next": False,
|
|
293
|
+
"PlayState": 0,
|
|
294
|
+
"PlayUrl": "spotify:track:0IHTiLO5qBYhf7Hmn0UDBN",
|
|
295
|
+
"Prev": False,
|
|
296
|
+
"Repeat": 0,
|
|
297
|
+
"SampleRate": "44100",
|
|
298
|
+
"Seek": False,
|
|
299
|
+
"Shuffle": 0,
|
|
300
|
+
"SinglePlay": False,
|
|
301
|
+
"TotalTime": 203087,
|
|
302
|
+
"TrackName": "Beijing"
|
|
303
|
+
}
|
|
156
304
|
"""
|
|
157
305
|
|
|
158
306
|
|
vsslctrl/io.py
CHANGED
|
@@ -139,7 +139,7 @@ class AnalogOutput(ZoneDataClass):
|
|
|
139
139
|
self.zone = zone
|
|
140
140
|
|
|
141
141
|
self._is_fixed_volume = False
|
|
142
|
-
self._source = self.Sources(zone.id +
|
|
142
|
+
self._source = self.Sources(zone.id + 2)
|
|
143
143
|
|
|
144
144
|
#
|
|
145
145
|
# Analog Output Fix Volume. Output wont respond to volume control
|
vsslctrl/utils.py
CHANGED
|
@@ -38,6 +38,19 @@ def group_list_by_property(input_list, property_key):
|
|
|
38
38
|
return grouped_dict
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
#
|
|
42
|
+
# Logging Helper to show a command in bytearray([]) syntax
|
|
43
|
+
#
|
|
44
|
+
def hex_to_bytearray_string(hex_string):
|
|
45
|
+
# Convert the hex string to a bytearray
|
|
46
|
+
byte_array = bytearray.fromhex(hex_string)
|
|
47
|
+
|
|
48
|
+
# Create the bytearray string representation
|
|
49
|
+
bytearray_str = f'bytearray([{", ".join(map(str, byte_array))}])'
|
|
50
|
+
|
|
51
|
+
return bytearray_str
|
|
52
|
+
|
|
53
|
+
|
|
41
54
|
#
|
|
42
55
|
# Repeat Timer
|
|
43
56
|
#
|
vsslctrl/zone.py
CHANGED
|
@@ -267,6 +267,11 @@ class Zone:
|
|
|
267
267
|
def _set_mac_addr(self, mac: str):
|
|
268
268
|
mac = mac.strip()
|
|
269
269
|
if mac != self.mac_addr:
|
|
270
|
+
# Strip Wlan0: from beginging of string
|
|
271
|
+
# Original A series amps had this prefix
|
|
272
|
+
if mac.startswith("Wlan0:"):
|
|
273
|
+
mac = mac[len("Wlan0:") :]
|
|
274
|
+
|
|
270
275
|
# Define the regular expression pattern for a MAC address
|
|
271
276
|
mac_pattern = re.compile(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
|
|
272
277
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vsslctrl
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary: Package for controlling VSSL amplifiers
|
|
3
|
+
Version: 0.1.6.dev1
|
|
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
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -20,29 +20,30 @@ Provides-Extra: optional
|
|
|
20
20
|
Requires-Dist: zeroconf ==0.132.2 ; extra == 'optional'
|
|
21
21
|
|
|
22
22
|
# vsslctrl
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
Package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
|
|
24
25
|
|
|
25
26
|
**`vsslctrl` is not endorsed or affiliated with [VSSL](https://www.vssl.com/) in any manner.**
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
## Help
|
|
29
|
+
|
|
30
|
+
I am looking for testers with any VSSL amplifier models, please get in touch if you interested in helping. <vsslcontrolled@proton.me>
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
Tested on:
|
|
33
|
+
- Test suite run on a VSSL **A.3x** software version **p15305.016.3701**
|
|
34
|
+
- The Home Assistant [integration](https://github.com/vsslctrl/integration.home-assistant) is reported working on a **A.6x** software version **p15305.017.3701**
|
|
30
35
|
|
|
31
|
-
Important
|
|
32
|
-
-----------
|
|
33
|
-
Only tested on a VSSL **A.3x** software version **p15305.016.3701**.
|
|
36
|
+
## Important
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/getting-started/start)* running on the same network. If you dont know what this is, then you can ignore this notice.
|
|
36
39
|
|
|
37
40
|
## TODOs
|
|
38
41
|
|
|
39
|
-
*
|
|
40
|
-
* Home Assistant integration. In progress, [here](https://github.com/vsslctrl/integration.home-assistant)
|
|
41
|
-
* ~~Function scoping to supported feature / models~~ (needs tests)
|
|
42
|
+
* **A1(.x)** specific control e.g sub crossover, bluetooth
|
|
42
43
|
* Better test coverage
|
|
43
44
|
|
|
44
|
-
Basic Usage
|
|
45
|
-
|
|
45
|
+
## Basic Usage
|
|
46
|
+
|
|
46
47
|
`vsslctrl` needs to be running inside a **[asyncio](https://docs.python.org/3/library/asyncio.html)** event loop.
|
|
47
48
|
|
|
48
49
|
```python
|
|
@@ -89,8 +90,7 @@ async def main():
|
|
|
89
90
|
asyncio.run(main())
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
API
|
|
93
|
-
-----------
|
|
93
|
+
# API
|
|
94
94
|
|
|
95
95
|
Most functionality is achived via `getters` and `setters` of the two main classes `Vssl`, `Zone`.
|
|
96
96
|
|
|
@@ -225,7 +225,7 @@ zone1.transport.state = ZoneTransport.States.PAUSE
|
|
|
225
225
|
## `Zone.track`
|
|
226
226
|
|
|
227
227
|
* Not all sources have complete metadata - missing value will be set to defaults.
|
|
228
|
-
* Airplay track
|
|
228
|
+
* Airplay track `progress` is not avaiable.
|
|
229
229
|
|
|
230
230
|
| Property | Description | Type | Values |
|
|
231
231
|
| ---------------------- | ----------- | ----------- |----------- |
|
|
@@ -282,10 +282,10 @@ zone1.group.dissolve()
|
|
|
282
282
|
|
|
283
283
|
## `Zone.analog_output`
|
|
284
284
|
|
|
285
|
-
| Property | Description | Type | Values |
|
|
286
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
287
|
-
| `source` | Where the AO is routed from. i.e a zone, optical input or off | `int` | `AnalogOutput.Sources`
|
|
288
|
-
| `is_fixed_volume` | Fix the output volume. Output wont respond to volume control | `bool`
|
|
285
|
+
| Property | Description | Type | Values | Default |
|
|
286
|
+
| ---------------------- | ----------- | ----------- |----------- |----------- |
|
|
287
|
+
| `source` | Where the AO is routed from. i.e a zone, optical input or off | `int` | `AnalogOutput.Sources` | `Off`
|
|
288
|
+
| `is_fixed_volume` | Fix the output volume. Output wont respond to volume control | `bool` | |`False`
|
|
289
289
|
| `is_fixed_volume_toggle()` | Toggle fixed volume | `func` |
|
|
290
290
|
|
|
291
291
|
```python
|
|
@@ -302,12 +302,12 @@ zone1.analog_output.is_fixed_volume = True
|
|
|
302
302
|
|
|
303
303
|
## `Zone.settings`
|
|
304
304
|
|
|
305
|
-
| Property | Description | Type | Values |
|
|
306
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
305
|
+
| Property | Description | Type | Values | Default |
|
|
306
|
+
| ---------------------- | ----------- | ----------- |----------- |----------- |
|
|
307
307
|
| `name` | Name | `str` |
|
|
308
|
-
| `disabled` | Disable the zone | `bool`
|
|
308
|
+
| `disabled` | Disable the zone | `bool` || `False`
|
|
309
309
|
| `disabled_toggle()` | disable / enable | `func` |
|
|
310
|
-
| `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono`
|
|
310
|
+
| `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono` | `Stereo`
|
|
311
311
|
| `mono_toggle()` | Toggle mono or stereo | `func` |
|
|
312
312
|
|
|
313
313
|
```python
|
|
@@ -322,10 +322,10 @@ zone1.mono_toggle()
|
|
|
322
322
|
|
|
323
323
|
## `Zone.settings.analog_input`
|
|
324
324
|
|
|
325
|
-
| Property | Description | Type | Values |
|
|
326
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
325
|
+
| Property | Description | Type | Values | Default |
|
|
326
|
+
| ---------------------- | ----------- | ----------- |----------- |----------- |
|
|
327
327
|
| `name` | Name | `str` |
|
|
328
|
-
| `fixed_gain` | Fix the input gain to a specific value |`int` | `0...100`
|
|
328
|
+
| `fixed_gain` | Fix the input gain to a specific value |`int` | `0...100` | `0` is disabled or variable gain
|
|
329
329
|
|
|
330
330
|
```python
|
|
331
331
|
"""Examples"""
|
|
@@ -339,11 +339,11 @@ zone1.settings.analog_input.fixed_gain = 50
|
|
|
339
339
|
|
|
340
340
|
## `Zone.settings.volume`
|
|
341
341
|
|
|
342
|
-
| Property | Description | Type | Values |
|
|
343
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
344
|
-
| `default_on` | Default on volume | `int` | `0...100`
|
|
345
|
-
| `max_left` | Max volume left channel | `int` | `0...100`
|
|
346
|
-
| `max_right` | Max volume right channel | `int` | `0...100`
|
|
342
|
+
| Property | Description | Type | Values | Default |
|
|
343
|
+
| ---------------------- | ----------- | ----------- |----------- |----------- |
|
|
344
|
+
| `default_on` | Default on volume | `int` | `0...100` | `0` is disabled
|
|
345
|
+
| `max_left` | Max volume left channel | `int` | `0...100` | `75`
|
|
346
|
+
| `max_right` | Max volume right channel | `int` | `0...100` | `75`
|
|
347
347
|
|
|
348
348
|
```python
|
|
349
349
|
"""Examples"""
|
|
@@ -355,21 +355,21 @@ zone1.settings.volume.default_on = 75
|
|
|
355
355
|
|
|
356
356
|
## `Zone.settings.eq`
|
|
357
357
|
|
|
358
|
-
| Property | Description | Type | Values |
|
|
359
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
360
|
-
| `enabled` | Enable / disable EQ | `bool`
|
|
358
|
+
| Property | Description | Type | Values | Default |
|
|
359
|
+
| ---------------------- | ----------- | ----------- |----------- |----------- |
|
|
360
|
+
| `enabled` | Enable / disable EQ | `bool` | | `False`
|
|
361
361
|
|
|
362
362
|
EQ to be set in [decibel](https://en.wikipedia.org/wiki/Decibel) using a range `-10`dB to `+10`dB
|
|
363
363
|
|
|
364
|
-
| Property | Description | Type | Values |
|
|
365
|
-
| ---------------------- | ----------- | ----------- |----------- |
|
|
366
|
-
| `hz60_db` | 60Hz | `int` | `-10...10`
|
|
367
|
-
| `hz200_db` | 200Hz | `int` | `-10...10`
|
|
368
|
-
| `hz500_db` | 500Hz | `int` | `-10...10`
|
|
369
|
-
| `khz1_db` | 1kHz | `int` | `-10...10`
|
|
370
|
-
| `khz4_db` | 4kHz | `int` | `-10...10`
|
|
371
|
-
| `khz8_db` | 8kHz | `int` | `-10...10`
|
|
372
|
-
| `khz15_db` | 15kHz | `int` | `-10...10`
|
|
364
|
+
| Property | Description | Type | Values | Default |
|
|
365
|
+
| ---------------------- | ----------- | ----------- |----------- | ----------- |
|
|
366
|
+
| `hz60_db` | 60Hz | `int` | `-10...10` | `0`
|
|
367
|
+
| `hz200_db` | 200Hz | `int` | `-10...10` | `0`
|
|
368
|
+
| `hz500_db` | 500Hz | `int` | `-10...10` | `0`
|
|
369
|
+
| `khz1_db` | 1kHz | `int` | `-10...10` | `0`
|
|
370
|
+
| `khz4_db` | 4kHz | `int` | `-10...10` | `0`
|
|
371
|
+
| `khz8_db` | 8kHz | `int` | `-10...10` | `0`
|
|
372
|
+
| `khz15_db` | 15kHz | `int` | `-10...10` | `0`
|
|
373
373
|
|
|
374
374
|
```python
|
|
375
375
|
"""Examples"""
|
|
@@ -379,7 +379,7 @@ zone1.settings.eq.khz1_db = -2
|
|
|
379
379
|
|
|
380
380
|
## Another (Lite) Way
|
|
381
381
|
|
|
382
|
-
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
|
|
382
|
+
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`.
|
|
383
383
|
|
|
384
384
|
| HEX | Description |
|
|
385
385
|
| ---------------------- | ----------- |
|
|
@@ -428,13 +428,15 @@ shell_command:
|
|
|
428
428
|
|
|
429
429
|
The VSSL API was reverse engineered using Wireshark, VSSLs native "legacy" iOS app and their deprecated [vsslagent](https://vssl.gitbook.io/vssl-rest-api/getting-started/start).
|
|
430
430
|
|
|
431
|
+
Motovation for this project was to intergrate VSSLs amplifiers into [Home Assistant](https://www.home-assistant.io/) and have control over different subnets (not mDNS dependant)
|
|
432
|
+
|
|
431
433
|
## Known Issues & Limitiations
|
|
432
434
|
|
|
433
|
-
*
|
|
434
|
-
* Not tested on A.1x, A.6.x or original A series range of amplifiers (testers welcome)
|
|
435
|
+
* Not tested on A.1x or original A series range of amplifiers (testers welcome)
|
|
435
436
|
* VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
|
|
436
437
|
* Not all sources set the volume to 0 when the zone is muted
|
|
437
438
|
* Grouping feedback is flaky on the X series amplifiers
|
|
439
|
+
* Airplay `Zone.track.progress` is not avaiable.
|
|
438
440
|
* Cant stop a URL playback, feedback is worng at least
|
|
439
441
|
* 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
|
|
440
442
|
* `stop()` is intended to disconnect the client and pause the stream. Doesnt always function this way, depending on stream source
|
|
@@ -442,8 +444,8 @@ The VSSL API was reverse engineered using Wireshark, VSSLs native "legacy" iOS a
|
|
|
442
444
|
|
|
443
445
|
## Future
|
|
444
446
|
|
|
447
|
+
* A.1(x) coverage i.e Bluetooth and subwoofer control
|
|
445
448
|
* REST API / Web App
|
|
446
449
|
* Save and recall EQ
|
|
447
|
-
* A.1(x) coverage i.e Bluetooth
|
|
448
450
|
* IR Control
|
|
449
451
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
vsslctrl/__init__.py,sha256=W7cljoE7L3DukAVr_TEqkxFycYrUbJiqaHrOlbPkmKU,145
|
|
2
|
+
vsslctrl/api_alpha.py,sha256=xJLTSEBWWKu5L9GLUbBv8LibZROEffOENc3vc22E1tw,33344
|
|
3
|
+
vsslctrl/api_base.py,sha256=a80CRtUkYK2OwXQOL8bP-YOvt4YE-ALd-VONza_gjK8,9623
|
|
4
|
+
vsslctrl/api_bravo.py,sha256=pchZN16KIvdSrqkI1WU9PA9IEmxkyyP-XifxpRC0jDE,12283
|
|
5
|
+
vsslctrl/core.py,sha256=13EEax6H3OWlkjIP49-l5z1nNyZQtb-BC_DwtVGPJuQ,8833
|
|
6
|
+
vsslctrl/data_structure.py,sha256=Gq1Ip3A-sr9Ber571SPmdh9pEgOOXnG96gIenqx0dFE,7683
|
|
7
|
+
vsslctrl/decorators.py,sha256=lG8cYSSA-fNvdcp_90lv58tj50bF6iPoigLKcucWSSI,1707
|
|
8
|
+
vsslctrl/device.py,sha256=P4sYiibPPFp6iDuF93-OKIsWBL_9vtIxBPDc5uRwvbA,3418
|
|
9
|
+
vsslctrl/discovery.py,sha256=81uA3rbm-X_CR0Q0LsCQJyCs7638Bb-mgqNZWqV_dq4,5572
|
|
10
|
+
vsslctrl/event_bus.py,sha256=9liAEJnZfthJwy5O-qc3pDIWRBv5AcV19PjMCwH_niU,5014
|
|
11
|
+
vsslctrl/exceptions.py,sha256=EEfNlu--9PMIidj3V3ZB8c7p3My8siqKw7YIOjPFaqE,419
|
|
12
|
+
vsslctrl/group.py,sha256=B7lmJb6DIwXGnMyTKsp-JfRWVCVRFQYzlvS71tutfx4,5368
|
|
13
|
+
vsslctrl/io.py,sha256=crzz5-vsdQfVFBL9a1Btle8mq5zx41SsVSt4N5cWPaE,6048
|
|
14
|
+
vsslctrl/settings.py,sha256=6zZUf2y-JvmEz6aREMZqxjTptMtVJFO6dGkXdCxR1NU,16587
|
|
15
|
+
vsslctrl/track.py,sha256=_lQNHYe8ZBm3v1lehFLjZruF5-Rz8PGWuQN-zISBgwc,10136
|
|
16
|
+
vsslctrl/transport.py,sha256=XJFy0kl9y7hm3YV4_9ElBrq3u6--_k73UKJqKdO6Rqg,6993
|
|
17
|
+
vsslctrl/utils.py,sha256=FWOMHM_sl1Cp1xab-V6b-A5B7qRmDN67hMMxV4ReWCk,2087
|
|
18
|
+
vsslctrl/zone.py,sha256=94Rq_IX9HObTx2nxdVlxs_ZnNrBqrewBA7ksatwT_78,12911
|
|
19
|
+
vsslctrl-0.1.6.dev1.dist-info/LICENSE,sha256=67Ekj8PIxjEvkPd7L3GYJ8dyvn1EC4sPM3q3FKtpN6w,1065
|
|
20
|
+
vsslctrl-0.1.6.dev1.dist-info/METADATA,sha256=7SK_zgiNWPIQJ7H55_fXjHbMdDVKcfjZ_10HUX802X8,16640
|
|
21
|
+
vsslctrl-0.1.6.dev1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
22
|
+
vsslctrl-0.1.6.dev1.dist-info/top_level.txt,sha256=MRycO_RKageNX27UZ2bLJDMlfeWTQzUZlsd3hS-0u3Y,9
|
|
23
|
+
vsslctrl-0.1.6.dev1.dist-info/RECORD,,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
vsslctrl/__init__.py,sha256=W7cljoE7L3DukAVr_TEqkxFycYrUbJiqaHrOlbPkmKU,145
|
|
2
|
-
vsslctrl/api_alpha.py,sha256=wsrwc4owIlRZFJK40evdRDh-iY1ZOxuCjDfsy2xHlt8,33216
|
|
3
|
-
vsslctrl/api_base.py,sha256=BpDjKTopnWr9UXTPxOywri_2ndfV7m0MYiFk1nz4514,8998
|
|
4
|
-
vsslctrl/api_bravo.py,sha256=HW-toa38umSpKAij_zxyypsMoU8S_AXpAbBmAaRqgow,12196
|
|
5
|
-
vsslctrl/core.py,sha256=cMMfrZzSDtIO2J3__EsA6aAWsGVuxSWkfkC8byXpQSY,8823
|
|
6
|
-
vsslctrl/data_structure.py,sha256=S9Zp9aGHMztdRII1DTVuD4Spx6z9_AjwHaco9zjQ0OA,6245
|
|
7
|
-
vsslctrl/decorators.py,sha256=lG8cYSSA-fNvdcp_90lv58tj50bF6iPoigLKcucWSSI,1707
|
|
8
|
-
vsslctrl/device.py,sha256=P4sYiibPPFp6iDuF93-OKIsWBL_9vtIxBPDc5uRwvbA,3418
|
|
9
|
-
vsslctrl/discovery.py,sha256=81uA3rbm-X_CR0Q0LsCQJyCs7638Bb-mgqNZWqV_dq4,5572
|
|
10
|
-
vsslctrl/event_bus.py,sha256=9liAEJnZfthJwy5O-qc3pDIWRBv5AcV19PjMCwH_niU,5014
|
|
11
|
-
vsslctrl/exceptions.py,sha256=EEfNlu--9PMIidj3V3ZB8c7p3My8siqKw7YIOjPFaqE,419
|
|
12
|
-
vsslctrl/group.py,sha256=B7lmJb6DIwXGnMyTKsp-JfRWVCVRFQYzlvS71tutfx4,5368
|
|
13
|
-
vsslctrl/io.py,sha256=Tnxk3hKf5G4pGDWvDlgH25DKXnESdX8S-3ecQOuXueU,6048
|
|
14
|
-
vsslctrl/settings.py,sha256=6zZUf2y-JvmEz6aREMZqxjTptMtVJFO6dGkXdCxR1NU,16587
|
|
15
|
-
vsslctrl/track.py,sha256=_lQNHYe8ZBm3v1lehFLjZruF5-Rz8PGWuQN-zISBgwc,10136
|
|
16
|
-
vsslctrl/transport.py,sha256=XJFy0kl9y7hm3YV4_9ElBrq3u6--_k73UKJqKdO6Rqg,6993
|
|
17
|
-
vsslctrl/utils.py,sha256=CqipPCffDjXCJ00NHAABOI886xRMyvnqkp4tr8i9uhg,1744
|
|
18
|
-
vsslctrl/zone.py,sha256=5UOgIslYLxEnZ9cjQc5nCgJF1ENlLMsoKcbjORW3hQk,12721
|
|
19
|
-
vsslctrl-0.1.4.dev1.dist-info/LICENSE,sha256=67Ekj8PIxjEvkPd7L3GYJ8dyvn1EC4sPM3q3FKtpN6w,1065
|
|
20
|
-
vsslctrl-0.1.4.dev1.dist-info/METADATA,sha256=XZq2ZiALLsD7l_D1HHsHZouN_x9ee0DA1BUgkz5AKOo,16311
|
|
21
|
-
vsslctrl-0.1.4.dev1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
22
|
-
vsslctrl-0.1.4.dev1.dist-info/top_level.txt,sha256=MRycO_RKageNX27UZ2bLJDMlfeWTQzUZlsd3hS-0u3Y,9
|
|
23
|
-
vsslctrl-0.1.4.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|