vsslctrl 0.1.0.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/__init__.py +4 -0
- vsslctrl/api_alpha.py +1040 -0
- vsslctrl/api_base.py +321 -0
- vsslctrl/api_bravo.py +419 -0
- vsslctrl/core.py +322 -0
- vsslctrl/data_structure.py +242 -0
- vsslctrl/decorators.py +61 -0
- vsslctrl/discovery.py +193 -0
- vsslctrl/event_bus.py +159 -0
- vsslctrl/exceptions.py +21 -0
- vsslctrl/group.py +187 -0
- vsslctrl/io.py +229 -0
- vsslctrl/settings.py +655 -0
- vsslctrl/track.py +337 -0
- vsslctrl/transport.py +242 -0
- vsslctrl/utils.py +75 -0
- vsslctrl/zone.py +452 -0
- vsslctrl-0.1.0.dev1.dist-info/LICENSE +21 -0
- vsslctrl-0.1.0.dev1.dist-info/METADATA +385 -0
- vsslctrl-0.1.0.dev1.dist-info/RECORD +22 -0
- vsslctrl-0.1.0.dev1.dist-info/WHEEL +5 -0
- vsslctrl-0.1.0.dev1.dist-info/top_level.txt +1 -0
vsslctrl/api_alpha.py
ADDED
|
@@ -0,0 +1,1040 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import struct
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from .api_base import APIBase
|
|
9
|
+
from .transport import ZoneTransport
|
|
10
|
+
from .settings import EQSettings
|
|
11
|
+
|
|
12
|
+
from .utils import hex_to_int, clamp_volume
|
|
13
|
+
from .decorators import logging_helpers
|
|
14
|
+
from .data_structure import (
|
|
15
|
+
ZoneStatusExtKeys,
|
|
16
|
+
ZoneEQStatusExtKeys,
|
|
17
|
+
ZoneRouterStatusExtKeys,
|
|
18
|
+
DeviceStatusExtKeys,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@logging_helpers()
|
|
23
|
+
class APIAlpha(APIBase):
|
|
24
|
+
TCP_PORT = 50002
|
|
25
|
+
HEADER_LENGTH = 3
|
|
26
|
+
JSON_HEADER_LENGTH = HEADER_LENGTH + 1 # action is 1 byte
|
|
27
|
+
|
|
28
|
+
def __init__(self, vssl_host: "core.Vssl", zone: "zone.Zone"):
|
|
29
|
+
super().__init__(host=zone.host, port=self.TCP_PORT)
|
|
30
|
+
|
|
31
|
+
self._log_prefix = f"Zone {zone.id}: Alpha API:"
|
|
32
|
+
|
|
33
|
+
self.vssl = vssl_host
|
|
34
|
+
self.zone = zone
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Send keep alive
|
|
38
|
+
#
|
|
39
|
+
def _send_keepalive(self):
|
|
40
|
+
self.request_action_17()
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
#
|
|
44
|
+
#
|
|
45
|
+
# Requests
|
|
46
|
+
#
|
|
47
|
+
#
|
|
48
|
+
#
|
|
49
|
+
def _add_zone_id_to_request(self, command: bytearray, index: int = 3):
|
|
50
|
+
command[index] = self.zone.id
|
|
51
|
+
return command
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# 17 [23]
|
|
55
|
+
# Keep Alive
|
|
56
|
+
#
|
|
57
|
+
def request_action_17(self):
|
|
58
|
+
self._log_debug("Requesting keep alive")
|
|
59
|
+
self.send(bytearray([16, 23, 1, 7]))
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# 00 [0] - 00 [0]
|
|
63
|
+
# Status Bus
|
|
64
|
+
#
|
|
65
|
+
def request_action_00_00(self):
|
|
66
|
+
self._log_debug("Requesting device status")
|
|
67
|
+
self.send(bytearray([16, 0, 1, 0])) # HEX: 10000100
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# 00 [0] - 08 [8]
|
|
71
|
+
# Status General
|
|
72
|
+
#
|
|
73
|
+
ZONE_STATUS = bytearray([16, 0, 1, 8])
|
|
74
|
+
|
|
75
|
+
def request_action_00_08(self):
|
|
76
|
+
self._log_debug("Requesting zone status")
|
|
77
|
+
self.send(self.ZONE_STATUS) # HEX: 10000108
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# 00 [0] - 09 [9]
|
|
81
|
+
# Status EQ
|
|
82
|
+
#
|
|
83
|
+
def request_action_00_09(self):
|
|
84
|
+
self._log_debug("Requesting EQ status")
|
|
85
|
+
self.send(bytearray([16, 0, 1, 9])) # HEX: 10000109
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# 00 [0] - 0A [10]
|
|
89
|
+
# Status Output
|
|
90
|
+
#
|
|
91
|
+
def request_action_00_0A(self):
|
|
92
|
+
self._log_debug("Requesting output status")
|
|
93
|
+
self.send(bytearray([16, 0, 1, 10])) # HEX: 1000010A
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# 00 [0] - 0B [11]
|
|
97
|
+
# Status BT (Bluetooth)
|
|
98
|
+
#
|
|
99
|
+
def request_action_00_0B(self):
|
|
100
|
+
self._log_debug("Requesting status bluetooth")
|
|
101
|
+
self.send(bytearray([16, 0, 1, 11])) # HEX: 1000010B
|
|
102
|
+
|
|
103
|
+
#
|
|
104
|
+
# 03 [3]
|
|
105
|
+
# Input Source Set
|
|
106
|
+
#
|
|
107
|
+
def request_action_03(self, src: int):
|
|
108
|
+
self._log_debug(f"Requesting to change input source to {src}")
|
|
109
|
+
command = self._add_zone_id_to_request(bytearray([16, 3, 2, 0, src]))
|
|
110
|
+
self.send(command)
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
# 04 [4]
|
|
114
|
+
# Input Source Get
|
|
115
|
+
#
|
|
116
|
+
def request_action_04(self):
|
|
117
|
+
self._log_debug("Requesting input source")
|
|
118
|
+
command = self._add_zone_id_to_request(bytearray([16, 4, 1, 0]))
|
|
119
|
+
self.send(command)
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# 05 [5]
|
|
123
|
+
# Set Volume
|
|
124
|
+
#
|
|
125
|
+
def request_action_05(self, vol: int):
|
|
126
|
+
vol = clamp_volume(vol)
|
|
127
|
+
self._log_debug(f"Requesting to set volume level: {vol}")
|
|
128
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, vol, 3]))
|
|
129
|
+
self.send(command)
|
|
130
|
+
|
|
131
|
+
#
|
|
132
|
+
# 05 [5]
|
|
133
|
+
# Volume Raise
|
|
134
|
+
#
|
|
135
|
+
def request_action_05_raise(self):
|
|
136
|
+
self._log_debug("Requesting raise volume")
|
|
137
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, 255, 3]))
|
|
138
|
+
self.send(command)
|
|
139
|
+
|
|
140
|
+
#
|
|
141
|
+
# 05 [5]
|
|
142
|
+
# Volume Lower
|
|
143
|
+
#
|
|
144
|
+
def request_action_05_lower(self):
|
|
145
|
+
self._log_debug("Requesting lower volume")
|
|
146
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, 254, 3]))
|
|
147
|
+
self.send(command)
|
|
148
|
+
|
|
149
|
+
#
|
|
150
|
+
# 05 [5]
|
|
151
|
+
# Set Default On Volume
|
|
152
|
+
#
|
|
153
|
+
def request_action_05_08(self, vol: int):
|
|
154
|
+
vol = clamp_volume(vol)
|
|
155
|
+
self._log_debug(f"Requesting to set default on volume level: {vol}")
|
|
156
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, vol, 8]))
|
|
157
|
+
self.send(command)
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# 05 [5]
|
|
161
|
+
# Set Analog Input Fixed Gain
|
|
162
|
+
#
|
|
163
|
+
def request_action_05_00(self, gain: int):
|
|
164
|
+
gain = clamp_volume(gain)
|
|
165
|
+
self._log_debug(f"Requesting to set fix analog input gain: {gain}")
|
|
166
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, gain, 0]))
|
|
167
|
+
self.send(command)
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# 05 [5]
|
|
171
|
+
# Set Max Left Volume
|
|
172
|
+
#
|
|
173
|
+
def request_action_05_01(self, vol: int):
|
|
174
|
+
vol = clamp_volume(vol)
|
|
175
|
+
self._log_debug(f"Requesting to set left max volume: {vol}")
|
|
176
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, vol, 1]))
|
|
177
|
+
self.send(command)
|
|
178
|
+
|
|
179
|
+
#
|
|
180
|
+
# 05 [5]
|
|
181
|
+
# Set Max Right Volume
|
|
182
|
+
#
|
|
183
|
+
def request_action_05_02(self, vol: int):
|
|
184
|
+
vol = clamp_volume(vol)
|
|
185
|
+
self._log_debug(f"Requesting to set right max volume: {vol}")
|
|
186
|
+
command = self._add_zone_id_to_request(bytearray([16, 5, 3, 0, vol, 2]))
|
|
187
|
+
self.send(command)
|
|
188
|
+
|
|
189
|
+
#
|
|
190
|
+
# 07 [7]
|
|
191
|
+
# Status Transport State
|
|
192
|
+
#
|
|
193
|
+
def request_action_07(self):
|
|
194
|
+
self._log_debug("Requesting status transport state")
|
|
195
|
+
self.send(bytearray([16, 7, 1, 0]))
|
|
196
|
+
|
|
197
|
+
#
|
|
198
|
+
# 0D [13]
|
|
199
|
+
# EQ
|
|
200
|
+
#
|
|
201
|
+
def request_action_0D(self, freq: "EQSettings.Freqs", value: int = 0):
|
|
202
|
+
clamped = max(90, min(value, 110))
|
|
203
|
+
self._log_debug(
|
|
204
|
+
f"Requesting to set EQ: {freq.name[1:]} ({freq.value}) to {clamped}"
|
|
205
|
+
)
|
|
206
|
+
command = self._add_zone_id_to_request(
|
|
207
|
+
bytearray([16, 13, 3, 0, freq.value, clamped])
|
|
208
|
+
)
|
|
209
|
+
self.send(command)
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# 0F [15]
|
|
213
|
+
# Output Set Mono
|
|
214
|
+
#
|
|
215
|
+
def request_action_mono_set(self, state: int):
|
|
216
|
+
self._log_debug(f"Requesting to set output to mono: {state}")
|
|
217
|
+
command = self._add_zone_id_to_request(
|
|
218
|
+
bytearray([16, 15, 2, 0, int(not not state)])
|
|
219
|
+
)
|
|
220
|
+
self.send(command)
|
|
221
|
+
|
|
222
|
+
#
|
|
223
|
+
# 11 [17]
|
|
224
|
+
# Mute
|
|
225
|
+
#
|
|
226
|
+
def request_action_11(self, state: int):
|
|
227
|
+
self._log_debug(f"Requesting to mute volume: {state}")
|
|
228
|
+
command = self._add_zone_id_to_request(
|
|
229
|
+
bytearray([16, 17, 2, 0, int(not not state)])
|
|
230
|
+
)
|
|
231
|
+
self.send(command)
|
|
232
|
+
|
|
233
|
+
#
|
|
234
|
+
# 12 [18]
|
|
235
|
+
# Status Mute
|
|
236
|
+
#
|
|
237
|
+
def request_action_12(self):
|
|
238
|
+
self._log_debug(f"Requesting status mute")
|
|
239
|
+
command = self._add_zone_id_to_request(bytearray([16, 18, 1, 0]))
|
|
240
|
+
self.send(command)
|
|
241
|
+
|
|
242
|
+
#
|
|
243
|
+
# 25 [37]
|
|
244
|
+
# Enable or Disable Zone
|
|
245
|
+
#
|
|
246
|
+
# 1 = Disable
|
|
247
|
+
# 0 = Enable
|
|
248
|
+
#
|
|
249
|
+
def request_action_25(self, disable: bool = True):
|
|
250
|
+
self._log_debug(f"Requesting disable zone: {disable}")
|
|
251
|
+
command = self._add_zone_id_to_request(
|
|
252
|
+
bytearray([16, 37, 2, 0, int(not not disable)])
|
|
253
|
+
)
|
|
254
|
+
self.send(command)
|
|
255
|
+
|
|
256
|
+
#
|
|
257
|
+
# 15 [21]
|
|
258
|
+
# Set Analog Input Name / Rename Analog Input
|
|
259
|
+
#
|
|
260
|
+
def request_action_15(self, name: str):
|
|
261
|
+
name = name.strip()
|
|
262
|
+
self._log_debug(f"Requesting to change analog input name: {name}")
|
|
263
|
+
command = bytearray([16, 21])
|
|
264
|
+
command.extend(struct.pack(">B", len(name) + 1))
|
|
265
|
+
command.extend([0]) # zone id placeholder
|
|
266
|
+
command = self._add_zone_id_to_request(command)
|
|
267
|
+
command.extend(name.encode("utf-8"))
|
|
268
|
+
self.send(command)
|
|
269
|
+
|
|
270
|
+
#
|
|
271
|
+
# 15 [21]
|
|
272
|
+
# Set Optical Input Name / Rename Optical Input
|
|
273
|
+
#
|
|
274
|
+
def request_action_15_12(self, name: str):
|
|
275
|
+
name = name.strip()
|
|
276
|
+
self._log_debug(f"Requesting to change optical input name: {name}")
|
|
277
|
+
command = bytearray([16, 21])
|
|
278
|
+
command.extend(struct.pack(">B", len(name) + 1))
|
|
279
|
+
command.extend([12])
|
|
280
|
+
command.extend(name.encode("utf-8"))
|
|
281
|
+
self.send(command)
|
|
282
|
+
|
|
283
|
+
#
|
|
284
|
+
# 18 [24]
|
|
285
|
+
# Set Device Name / Rename Device
|
|
286
|
+
#
|
|
287
|
+
def request_action_18(self, name: str):
|
|
288
|
+
name = name.strip()
|
|
289
|
+
self._log_debug(f"Requesting to change device name: {name}")
|
|
290
|
+
command = bytearray([16, 24])
|
|
291
|
+
command.extend(struct.pack(">B", len(name) + 1))
|
|
292
|
+
command.extend([7])
|
|
293
|
+
command.extend(name.encode("utf-8"))
|
|
294
|
+
self.send(command)
|
|
295
|
+
|
|
296
|
+
#
|
|
297
|
+
# 19 [25]
|
|
298
|
+
# Get Device name
|
|
299
|
+
#
|
|
300
|
+
def request_action_19(self):
|
|
301
|
+
self._log_debug(f"Requesting device name")
|
|
302
|
+
command = self._add_zone_id_to_request(bytearray([16, 25, 1, 0]))
|
|
303
|
+
self.send(command)
|
|
304
|
+
|
|
305
|
+
#
|
|
306
|
+
# 1D [29]
|
|
307
|
+
# Analog Output Set Src
|
|
308
|
+
#
|
|
309
|
+
def request_action_1D(self, src: int):
|
|
310
|
+
self._log_debug(f"Requesting to change analog ouput source to {src}")
|
|
311
|
+
command = self._add_zone_id_to_request(bytearray([16, 29, 2, 0, src]))
|
|
312
|
+
self.send(command)
|
|
313
|
+
|
|
314
|
+
def request_action_1D_router(self, ao_id: int, src: int):
|
|
315
|
+
self._log_debug(f"Requesting to change analog ouput {ao_id} source to {src}")
|
|
316
|
+
self.send(bytearray([16, 29, 2, ao_id, src]))
|
|
317
|
+
|
|
318
|
+
#
|
|
319
|
+
# 49 [73]
|
|
320
|
+
# Analog Output Fix Output Vol
|
|
321
|
+
#
|
|
322
|
+
def request_action_49(self, fix: bool):
|
|
323
|
+
self._log_debug(f"Requesting to fix analog ouput volume {fix}")
|
|
324
|
+
command = self._add_zone_id_to_request(
|
|
325
|
+
bytearray([16, 73, 2, 0, int(not not fix)])
|
|
326
|
+
)
|
|
327
|
+
self.send(command)
|
|
328
|
+
|
|
329
|
+
def request_action_49_router(self, ao_id: int, fix: bool):
|
|
330
|
+
self._log_debug(f"Requesting to fix analog ouput {ao_id} volume {fix}")
|
|
331
|
+
self.send(bytearray([16, 73, 2, ao_id, int(not not fix)]))
|
|
332
|
+
|
|
333
|
+
#
|
|
334
|
+
# 3D [61]
|
|
335
|
+
# Transport State
|
|
336
|
+
#
|
|
337
|
+
def request_action_3D(self, state: ZoneTransport.States):
|
|
338
|
+
if state == ZoneTransport.States.STOP:
|
|
339
|
+
cmd = 1
|
|
340
|
+
elif state == ZoneTransport.States.PLAY:
|
|
341
|
+
cmd = 0
|
|
342
|
+
elif state == ZoneTransport.States.PAUSE:
|
|
343
|
+
cmd = 2
|
|
344
|
+
else:
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
self._log_debug(f"Requesting transports state {state.name}, {state.value}")
|
|
348
|
+
command = self._add_zone_id_to_request(bytearray([16, 61, 2, 0, cmd]))
|
|
349
|
+
self.send(command)
|
|
350
|
+
|
|
351
|
+
#
|
|
352
|
+
# 2A [42]
|
|
353
|
+
# Status Stream Source
|
|
354
|
+
#
|
|
355
|
+
def request_action_2A(self):
|
|
356
|
+
self._log_debug(f"Requesting stream source")
|
|
357
|
+
command = self._add_zone_id_to_request(bytearray([16, 42, 1, 0]))
|
|
358
|
+
self.send(command)
|
|
359
|
+
|
|
360
|
+
#
|
|
361
|
+
# 2D [45]
|
|
362
|
+
# Disable / Enable EQ
|
|
363
|
+
#
|
|
364
|
+
def request_action_2D(self, state: int):
|
|
365
|
+
self._log_debug(f"Requesting EQ Enable: {state}")
|
|
366
|
+
command = self._add_zone_id_to_request(
|
|
367
|
+
bytearray([16, 45, 2, 0, int(not not state)])
|
|
368
|
+
)
|
|
369
|
+
self.send(command)
|
|
370
|
+
|
|
371
|
+
#
|
|
372
|
+
# 33 [51]
|
|
373
|
+
# Reboot
|
|
374
|
+
#
|
|
375
|
+
def request_action_33(self):
|
|
376
|
+
self._log_debug(f"Requesting to reboot single zone")
|
|
377
|
+
command = self._add_zone_id_to_request(bytearray([16, 51, 2, 0, 1]))
|
|
378
|
+
self.send(command)
|
|
379
|
+
|
|
380
|
+
#
|
|
381
|
+
# 33 [51]
|
|
382
|
+
# Reboot All Zones
|
|
383
|
+
#
|
|
384
|
+
def request_action_33_device(self):
|
|
385
|
+
self._log_debug(f"Requesting to reboot device")
|
|
386
|
+
self.send(bytearray([16, 51, 2, 0, 1]))
|
|
387
|
+
|
|
388
|
+
#
|
|
389
|
+
# 47 [71]
|
|
390
|
+
# Set Input Priority
|
|
391
|
+
#
|
|
392
|
+
def request_action_47(self, priority: int):
|
|
393
|
+
self._log_debug(f"Requesting to set input priority {priority}")
|
|
394
|
+
command = self._add_zone_id_to_request(bytearray([16, 71, 2, 0, priority]))
|
|
395
|
+
self.send(command)
|
|
396
|
+
|
|
397
|
+
#
|
|
398
|
+
# 4B [75]
|
|
399
|
+
# Group Add Member
|
|
400
|
+
#
|
|
401
|
+
# In other words: set the zones (zone_index) parent to this zone
|
|
402
|
+
#
|
|
403
|
+
def request_action_4B_add(self, zone_index: int):
|
|
404
|
+
self._log_debug(f"Requesting to add child zone {zone_index} to group")
|
|
405
|
+
command = self._add_zone_id_to_request(bytearray([16, 75, 2, 0, zone_index]))
|
|
406
|
+
self.send(command)
|
|
407
|
+
|
|
408
|
+
#
|
|
409
|
+
# 4B [75]
|
|
410
|
+
# Group Remove Member
|
|
411
|
+
#
|
|
412
|
+
# In other words: set the zones parent to 255
|
|
413
|
+
#
|
|
414
|
+
def request_action_4B_remove(self, zone_index: int):
|
|
415
|
+
self._log_debug(f"Requesting to remove child zone {zone_index} from group")
|
|
416
|
+
# Doesnt need a zone id
|
|
417
|
+
self.send(bytearray([16, 75, 2, 255, zone_index]))
|
|
418
|
+
|
|
419
|
+
#
|
|
420
|
+
# 4B [75]
|
|
421
|
+
# Group Dissolve
|
|
422
|
+
#
|
|
423
|
+
# In other words: set this zones childen to 255
|
|
424
|
+
#
|
|
425
|
+
def request_action_4B_dissolve(self):
|
|
426
|
+
self._log_debug(f"Requesting to Dissolve group")
|
|
427
|
+
command = self._add_zone_id_to_request(bytearray([16, 75, 2, 0, 255]))
|
|
428
|
+
self.send(command)
|
|
429
|
+
|
|
430
|
+
#
|
|
431
|
+
# 55 [85]
|
|
432
|
+
# Play URL
|
|
433
|
+
#
|
|
434
|
+
# Unexpected behaviour on A3.x - feedback is inconsistent
|
|
435
|
+
#
|
|
436
|
+
# There is a bug in the vssl that if we send the volume, it will continually send
|
|
437
|
+
# back volume updates while the url is being played. So for now, we send the current
|
|
438
|
+
# volume and it them seems to behave ok
|
|
439
|
+
#
|
|
440
|
+
#
|
|
441
|
+
""" note: the call will return immediately with either a failure message or an indication that the
|
|
442
|
+
playback has been requested. It is possible for the playback to fail (e.g. the network can't retrieve the file,
|
|
443
|
+
the file format is invalid, ...). Further status will be provided in the coming VSSL FW iterations.
|
|
444
|
+
|
|
445
|
+
note2: if this is the first time a playback has been requested then we will send a command to wake up the unit
|
|
446
|
+
and wait a few seconds before playing the file. Otherwise, if the last playback was less then 15 minutes ago
|
|
447
|
+
then we will play the clip immediately.
|
|
448
|
+
|
|
449
|
+
note3: this call allows you to play a file on 1 or all of the zones. If you want to play to a subset
|
|
450
|
+
(e.g. zone 1,2) then you will need to make two calls, one for each zone you want to play the file on.
|
|
451
|
+
|
|
452
|
+
ref: https://vssl.gitbook.io/vssl-rest-api/announcements/play-audio-file
|
|
453
|
+
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
def request_action_55(self, url: str, all_zones: bool = False):
|
|
457
|
+
string = "PLAYITEM:DIRECT:" + f"{url}"
|
|
458
|
+
|
|
459
|
+
command = bytearray([16, 85])
|
|
460
|
+
command.extend(struct.pack(">B", len(string) + 2))
|
|
461
|
+
|
|
462
|
+
# Zone 0 will play on all zones
|
|
463
|
+
command.extend([0, self.zone.volume])
|
|
464
|
+
|
|
465
|
+
if not all_zones:
|
|
466
|
+
command = self._add_zone_id_to_request(command)
|
|
467
|
+
|
|
468
|
+
command.extend(string.encode("utf-8"))
|
|
469
|
+
|
|
470
|
+
self._log_debug(f"Requesting to play file {url} cmd: {command}")
|
|
471
|
+
self.send(command)
|
|
472
|
+
|
|
473
|
+
#
|
|
474
|
+
# 4F [79]
|
|
475
|
+
# Adaptive Power - Device level Command
|
|
476
|
+
#
|
|
477
|
+
def request_action_4F(self, state=True):
|
|
478
|
+
self._log_debug(f"Requesting to set adaptive power state: {state}")
|
|
479
|
+
# Device level command (dont need zone)
|
|
480
|
+
command = bytearray([16, 79, 2, 8, int(state)])
|
|
481
|
+
self.send(command)
|
|
482
|
+
|
|
483
|
+
#
|
|
484
|
+
#
|
|
485
|
+
#
|
|
486
|
+
# Respsonses
|
|
487
|
+
#
|
|
488
|
+
#
|
|
489
|
+
#
|
|
490
|
+
|
|
491
|
+
async def _read_byte_stream(self, reader, data):
|
|
492
|
+
data += await reader.readexactly(self.HEADER_LENGTH - APIBase.FRIST_BYTE)
|
|
493
|
+
length = data[2]
|
|
494
|
+
|
|
495
|
+
data += await reader.readexactly(length)
|
|
496
|
+
|
|
497
|
+
self._log_debug(f"Response data: {data}")
|
|
498
|
+
|
|
499
|
+
if length == 1:
|
|
500
|
+
return self.response_action_confimation(data)
|
|
501
|
+
|
|
502
|
+
await self._handle_response(data)
|
|
503
|
+
|
|
504
|
+
async def _handle_response(self, response: bytes):
|
|
505
|
+
try:
|
|
506
|
+
# Convert to HEX and split into a array
|
|
507
|
+
hexl = response.hex("-").split("-")
|
|
508
|
+
action = f"response_action_{hexl[1].upper()}"
|
|
509
|
+
|
|
510
|
+
self._log_debug(f"Response action: {action}")
|
|
511
|
+
|
|
512
|
+
except Exception as error:
|
|
513
|
+
self._log_error(f"couldnt handle response: {error} | {hexl}")
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
if hasattr(self, action):
|
|
517
|
+
method = getattr(self, action)
|
|
518
|
+
if callable(method):
|
|
519
|
+
return method(hexl, response)
|
|
520
|
+
|
|
521
|
+
# Default
|
|
522
|
+
return self.response_action_default(hexl, response)
|
|
523
|
+
|
|
524
|
+
#
|
|
525
|
+
# 00 [0]
|
|
526
|
+
# Received JSON Status Data
|
|
527
|
+
#
|
|
528
|
+
def response_action_00(self, hexl: list, response: bytes):
|
|
529
|
+
try:
|
|
530
|
+
packet_length = hexl[2]
|
|
531
|
+
|
|
532
|
+
length = hex_to_int(packet_length) - 1
|
|
533
|
+
string = response[
|
|
534
|
+
self.JSON_HEADER_LENGTH : self.JSON_HEADER_LENGTH + length
|
|
535
|
+
].decode("ascii")
|
|
536
|
+
metadata = json.loads(string)
|
|
537
|
+
|
|
538
|
+
# Call a sub action
|
|
539
|
+
sub_action = f"response_action_00_{hexl[3].upper()}"
|
|
540
|
+
|
|
541
|
+
if hasattr(self, sub_action):
|
|
542
|
+
method = getattr(self, sub_action)
|
|
543
|
+
if callable(method):
|
|
544
|
+
self._log_debug(f"Calling status sub action: {sub_action}")
|
|
545
|
+
return method(metadata)
|
|
546
|
+
|
|
547
|
+
self._log_debug(f"Unknown status sub action {sub_action}")
|
|
548
|
+
|
|
549
|
+
except Exception as error:
|
|
550
|
+
self._log_error(f"Couldnt parse JSON: {error} | {hexl}")
|
|
551
|
+
|
|
552
|
+
#
|
|
553
|
+
# 00_00
|
|
554
|
+
# Device Status 00
|
|
555
|
+
#
|
|
556
|
+
# {'B1Src': '3', 'B2Src': '4', 'B3Src': '5', 'B1Nm': '', 'B2Nm': 'Optical In', 'dev': 'Device Name', 'ver': 'p15305.016.3701'}
|
|
557
|
+
#
|
|
558
|
+
def response_action_00_00(self, metadata: list):
|
|
559
|
+
self._log_debug(f"Received 00 Status: {metadata}")
|
|
560
|
+
|
|
561
|
+
# Workout how many zones we have
|
|
562
|
+
self.vssl._infer_model_zone_qty(metadata)
|
|
563
|
+
|
|
564
|
+
# Analog output source
|
|
565
|
+
key = DeviceStatusExtKeys.add_zone_to_bus_key(self.zone.id)
|
|
566
|
+
if key in metadata:
|
|
567
|
+
self.zone.analog_output._set_property("source", int(metadata[key]))
|
|
568
|
+
|
|
569
|
+
# B1Nm - Bus1 Name
|
|
570
|
+
# Not used?
|
|
571
|
+
|
|
572
|
+
# B2Nm - Bus2 Name - For A3.X this is the optical input name
|
|
573
|
+
if DeviceStatusExtKeys.OPTICAL_INPUT_NAME in metadata:
|
|
574
|
+
self.vssl.settings._set_property(
|
|
575
|
+
"optical_input_name",
|
|
576
|
+
metadata[DeviceStatusExtKeys.OPTICAL_INPUT_NAME].strip(),
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Set the device name
|
|
580
|
+
if DeviceStatusExtKeys.DEVICE_NAME in metadata:
|
|
581
|
+
self.vssl.settings._set_property(
|
|
582
|
+
"name", metadata[DeviceStatusExtKeys.DEVICE_NAME].strip()
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Set the software version
|
|
586
|
+
if DeviceStatusExtKeys.SW_VERSION in metadata and self.vssl.sw_version == None:
|
|
587
|
+
self.vssl._set_property(
|
|
588
|
+
"sw_version", metadata[DeviceStatusExtKeys.SW_VERSION].strip()
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
#
|
|
592
|
+
# 00_08
|
|
593
|
+
# Zone Status 08
|
|
594
|
+
#
|
|
595
|
+
# {'id': '1', 'ac': '0', 'mc': 'XXXXXXXXXXXX', 'vol': '20', 'mt': '0', 'pa': '0', 'rm': '0', 'ts': '14',
|
|
596
|
+
# 'alex': '14', 'nmd': '0', 'ird': '14', 'lb': '24', 'tp': '13', 'wr': '0', 'as': '0', 'rg': '0'}
|
|
597
|
+
#
|
|
598
|
+
def response_action_00_08(self, metadata: list):
|
|
599
|
+
self._log_debug(f"Received 08 Status: {metadata}")
|
|
600
|
+
|
|
601
|
+
# If the zone is not initialised, then we just return the ID and serial
|
|
602
|
+
if not self.zone.initialised:
|
|
603
|
+
# Zone Index
|
|
604
|
+
if ZoneStatusExtKeys.ID in metadata:
|
|
605
|
+
self.zone.id = int(metadata[ZoneStatusExtKeys.ID])
|
|
606
|
+
|
|
607
|
+
# Serial number and MAC address of ZONE 1
|
|
608
|
+
if ZoneStatusExtKeys.SERIAL_NUMBER in metadata:
|
|
609
|
+
# Always set VSSL first before zone
|
|
610
|
+
if self.vssl.serial == None:
|
|
611
|
+
self.vssl._set_property(
|
|
612
|
+
"serial", metadata[ZoneStatusExtKeys.SERIAL_NUMBER]
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if self.zone.serial == None:
|
|
616
|
+
self.zone._set_property(
|
|
617
|
+
"serial", metadata[ZoneStatusExtKeys.SERIAL_NUMBER]
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
# Transport state
|
|
621
|
+
if ZoneStatusExtKeys.TRANSPORT_STATE in metadata:
|
|
622
|
+
self.zone.transport._set_property(
|
|
623
|
+
"state", int(metadata[ZoneStatusExtKeys.TRANSPORT_STATE])
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# Volume
|
|
627
|
+
if ZoneStatusExtKeys.VOLUME in metadata:
|
|
628
|
+
self.zone._set_property("volume", int(metadata[ZoneStatusExtKeys.VOLUME]))
|
|
629
|
+
|
|
630
|
+
# Mute
|
|
631
|
+
if ZoneStatusExtKeys.MUTE in metadata:
|
|
632
|
+
self.zone._set_property("mute", bool(int(metadata[ZoneStatusExtKeys.MUTE])))
|
|
633
|
+
|
|
634
|
+
# Party Mode
|
|
635
|
+
# Not supported by X series?
|
|
636
|
+
if ZoneStatusExtKeys.PARTY_MODE in metadata:
|
|
637
|
+
pass
|
|
638
|
+
|
|
639
|
+
# Group Index see below
|
|
640
|
+
if ZoneStatusExtKeys.GROUP_INDEX in metadata:
|
|
641
|
+
self.zone.group._set_property(
|
|
642
|
+
"index", int(metadata[ZoneStatusExtKeys.GROUP_INDEX])
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Set Stream Source
|
|
646
|
+
if ZoneStatusExtKeys.TRACK_SOURCE in metadata:
|
|
647
|
+
self.zone.track.source = int(metadata[ZoneStatusExtKeys.TRACK_SOURCE])
|
|
648
|
+
|
|
649
|
+
# Zone Enabled (0) or Disabled (1)
|
|
650
|
+
if ZoneStatusExtKeys.DISABLED in metadata:
|
|
651
|
+
self.zone.settings._set_property(
|
|
652
|
+
"disabled", bool(int(metadata[ZoneStatusExtKeys.DISABLED]))
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
#
|
|
656
|
+
# 00_09
|
|
657
|
+
# EQ Status
|
|
658
|
+
#
|
|
659
|
+
# {'mono': '0', 'AiNm': 'Analog In 1', 'eq1': '100', 'eq2': '100', 'eq3': '100', 'eq4': '100',
|
|
660
|
+
# 'eq5': '100', 'eq6': '100', 'eq7': '100', 'voll': '75', 'volr': '75', 'vold': '0'}
|
|
661
|
+
#
|
|
662
|
+
def response_action_00_09(self, metadata: list):
|
|
663
|
+
self._log_debug(f"Received 09 Status: {metadata}")
|
|
664
|
+
|
|
665
|
+
# Mono output
|
|
666
|
+
if ZoneEQStatusExtKeys.MONO in metadata:
|
|
667
|
+
self.zone.settings._set_property(
|
|
668
|
+
"mono", int(metadata[ZoneEQStatusExtKeys.MONO])
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
# Analog Input Name
|
|
672
|
+
if ZoneEQStatusExtKeys.ANALOG_INPUT_NAME in metadata:
|
|
673
|
+
self.zone.settings.analog_input._set_property(
|
|
674
|
+
"name", metadata[ZoneEQStatusExtKeys.ANALOG_INPUT_NAME].strip()
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
self.zone.settings.eq._map_response_dict(metadata)
|
|
678
|
+
self.zone.settings.volume._map_response_dict(metadata)
|
|
679
|
+
|
|
680
|
+
#
|
|
681
|
+
# 00_0A
|
|
682
|
+
# System Status 0A (Output / Amp)
|
|
683
|
+
#
|
|
684
|
+
# {'ECO': '0', 'eqsw': '1', 'inSrc': '0', 'SP': '0', 'BF1': '0', 'BF2': '0', 'BF3': '0',
|
|
685
|
+
# 'GRM': '0', 'GRS': '255', 'Pwr': '0', 'Bvr': '1', 'fxv': '24', 'AtPwr': '1'}
|
|
686
|
+
#
|
|
687
|
+
def response_action_00_0A(self, metadata: list):
|
|
688
|
+
self._log_debug(f"Received 0A Status: {metadata}")
|
|
689
|
+
|
|
690
|
+
# EQ Switch
|
|
691
|
+
if ZoneRouterStatusExtKeys.EQ_ENABLED in metadata:
|
|
692
|
+
self.zone.settings.eq._set_property(
|
|
693
|
+
"enabled", bool(int(metadata[ZoneRouterStatusExtKeys.EQ_ENABLED]))
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
# Input Source
|
|
697
|
+
if ZoneRouterStatusExtKeys.INPUT_SOURCE in metadata:
|
|
698
|
+
self.zone.input._set_property(
|
|
699
|
+
"source", int(metadata[ZoneRouterStatusExtKeys.INPUT_SOURCE])
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
# Source Priority
|
|
703
|
+
if ZoneRouterStatusExtKeys.SOURCE_PRIORITY in metadata:
|
|
704
|
+
self.zone.input._set_property(
|
|
705
|
+
"priority", int(metadata[ZoneRouterStatusExtKeys.SOURCE_PRIORITY])
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
# Analog Output Fix Volume
|
|
709
|
+
# e.g BF1
|
|
710
|
+
key = ZoneRouterStatusExtKeys.add_zone_to_ao_fixed_volume_key(self.zone.id)
|
|
711
|
+
if key in metadata:
|
|
712
|
+
self.zone.analog_output._set_property(
|
|
713
|
+
"is_fixed_volume", bool(int(metadata[key]))
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Handle groups
|
|
717
|
+
if (
|
|
718
|
+
ZoneRouterStatusExtKeys.GROUP_MASTER in metadata
|
|
719
|
+
and ZoneRouterStatusExtKeys.GROUP_SOURCE in metadata
|
|
720
|
+
):
|
|
721
|
+
self.zone.group._set_property(
|
|
722
|
+
"source", int(metadata[ZoneRouterStatusExtKeys.GROUP_SOURCE])
|
|
723
|
+
)
|
|
724
|
+
self.zone.group._set_property(
|
|
725
|
+
"is_master", int(metadata[ZoneRouterStatusExtKeys.GROUP_MASTER])
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# Power State
|
|
729
|
+
if ZoneRouterStatusExtKeys.POWER_STATE in metadata:
|
|
730
|
+
self.vssl.settings.power._set_property(
|
|
731
|
+
"state", int(metadata[ZoneRouterStatusExtKeys.POWER_STATE])
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
# Analog input fixed gain
|
|
735
|
+
if ZoneRouterStatusExtKeys.ANALOG_INPUT_FIXED_GAIN in metadata:
|
|
736
|
+
self.zone.settings.analog_input._set_property(
|
|
737
|
+
"fixed_gain",
|
|
738
|
+
int(metadata[ZoneRouterStatusExtKeys.ANALOG_INPUT_FIXED_GAIN]),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
# Alway On power state = 0 else 1 = auto
|
|
742
|
+
if ZoneRouterStatusExtKeys.ADAPTIVE_POWER in metadata:
|
|
743
|
+
self.vssl.settings.power._set_property(
|
|
744
|
+
"adaptive", bool(int(metadata[ZoneRouterStatusExtKeys.ADAPTIVE_POWER]))
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
#
|
|
748
|
+
# 00_0B
|
|
749
|
+
# System Status 0B (Not Sure)
|
|
750
|
+
#
|
|
751
|
+
# {'IRMskL': '241', 'IRMskH': '255', 'BTSta': '0', 'Crs': '0', 'Fes': '0', 'Drk': '0'}
|
|
752
|
+
#
|
|
753
|
+
#
|
|
754
|
+
# IRMskL / IRMskH:
|
|
755
|
+
# These could potentially be related to Infrared (IR) remote control signals. "IRMskL" and "IRMskH" might represent the low and high values of the modulation frequency or pulse width for an infrared signal.
|
|
756
|
+
#
|
|
757
|
+
# BTSta:
|
|
758
|
+
# This might represent the Bluetooth status, with "0" indicating that Bluetooth is currently not active or disconnected.
|
|
759
|
+
#
|
|
760
|
+
# Crs:
|
|
761
|
+
# It could stand for "Crossfade" and may represent a setting related to crossfading between audio tracks.
|
|
762
|
+
#
|
|
763
|
+
# Fes:
|
|
764
|
+
# This might stand for "Frequency" or "Filter Effect Setting," representing a parameter related to frequency or filtering effects.
|
|
765
|
+
#
|
|
766
|
+
# Drk: ?
|
|
767
|
+
#
|
|
768
|
+
def response_action_00_0B(self, metadata: list):
|
|
769
|
+
self._log_debug(f"Received 0B Status: {metadata}")
|
|
770
|
+
pass
|
|
771
|
+
|
|
772
|
+
#
|
|
773
|
+
# 2A [42]
|
|
774
|
+
# Stream Source
|
|
775
|
+
#
|
|
776
|
+
def response_action_2A(self, hexl: list, response: bytes):
|
|
777
|
+
if hex_to_int(hexl[2]) == 2:
|
|
778
|
+
source = hex_to_int(hexl[4])
|
|
779
|
+
self._log_debug(f"Received stream source: {source}")
|
|
780
|
+
self.zone.track.source = source
|
|
781
|
+
|
|
782
|
+
#
|
|
783
|
+
# 1E [30]
|
|
784
|
+
# Received Analog Output Source Change
|
|
785
|
+
#
|
|
786
|
+
# Note: This is received on the zone which is the same as the output ID
|
|
787
|
+
#
|
|
788
|
+
def response_action_1E(self, hexl: list, response: bytes):
|
|
789
|
+
if hex_to_int(hexl[2]) == 2:
|
|
790
|
+
output = hex_to_int(hexl[3])
|
|
791
|
+
source = hex_to_int(hexl[4])
|
|
792
|
+
self._log_debug(f"Received analog output {output} source change: {source}")
|
|
793
|
+
self.zone.analog_output._set_property("source", source)
|
|
794
|
+
|
|
795
|
+
#
|
|
796
|
+
# 4A [74]
|
|
797
|
+
# Analog Output Fix Output Vol
|
|
798
|
+
#
|
|
799
|
+
# Note: This is received on the zone which is the same as the output ID
|
|
800
|
+
#
|
|
801
|
+
def response_action_4A(self, hexl: list, response: bytes):
|
|
802
|
+
if hex_to_int(hexl[2]) == 2:
|
|
803
|
+
output = hex_to_int(hexl[3])
|
|
804
|
+
state = hex_to_int(hexl[4])
|
|
805
|
+
self._log_debug(f"Received analog output {output} volume fixed: {state}")
|
|
806
|
+
self.zone.analog_output._set_property("is_fixed_volume", bool(state))
|
|
807
|
+
|
|
808
|
+
#
|
|
809
|
+
# 04 [4]
|
|
810
|
+
# Received Input Source
|
|
811
|
+
#
|
|
812
|
+
def response_action_04(self, hexl: list, response: bytes):
|
|
813
|
+
if hex_to_int(hexl[2]) == 2:
|
|
814
|
+
source = hex_to_int(hexl[4])
|
|
815
|
+
self._log_debug(f"Received input source: {source}")
|
|
816
|
+
self.zone.input._set_property("source", source)
|
|
817
|
+
|
|
818
|
+
#
|
|
819
|
+
# 06 [6]
|
|
820
|
+
# Received Volume Data
|
|
821
|
+
#
|
|
822
|
+
def response_action_06(self, hexl: list, response: bytes):
|
|
823
|
+
if hex_to_int(hexl[2]) == 3:
|
|
824
|
+
vol = hex_to_int(hexl[4])
|
|
825
|
+
vol_cmd = hex_to_int(hexl[5])
|
|
826
|
+
|
|
827
|
+
self._log_debug(f"Received volume cmd: {vol_cmd} vol: {vol}")
|
|
828
|
+
self._log_debug(f"Received volume {response.hex()}")
|
|
829
|
+
|
|
830
|
+
# Analog input fixed gain
|
|
831
|
+
if vol_cmd == 0:
|
|
832
|
+
self.zone.settings.analog_input._set_property("fixed_gain", vol)
|
|
833
|
+
|
|
834
|
+
# Max Left
|
|
835
|
+
elif vol_cmd == 1:
|
|
836
|
+
self.zone.settings.volume._set_property("max_left", vol)
|
|
837
|
+
|
|
838
|
+
# Max Right
|
|
839
|
+
elif vol_cmd == 2:
|
|
840
|
+
self.zone.settings.volume._set_property("max_right", vol)
|
|
841
|
+
|
|
842
|
+
# Normal Volume Change
|
|
843
|
+
elif vol_cmd == 3:
|
|
844
|
+
self.zone._set_property("volume", vol)
|
|
845
|
+
|
|
846
|
+
# Defaul On Volume Change
|
|
847
|
+
elif vol_cmd == 8:
|
|
848
|
+
self.zone.settings.volume._set_property("default_on", vol)
|
|
849
|
+
else:
|
|
850
|
+
self._log_debug(f"Volume Error")
|
|
851
|
+
|
|
852
|
+
#
|
|
853
|
+
# 07 [7]
|
|
854
|
+
# Transport State
|
|
855
|
+
#
|
|
856
|
+
# 0 = stop
|
|
857
|
+
# 1 = play
|
|
858
|
+
# 2 = pause
|
|
859
|
+
#
|
|
860
|
+
def response_action_07(self, hexl: list, response: bytes):
|
|
861
|
+
if hex_to_int(hexl[2]) == 2:
|
|
862
|
+
state = hex_to_int(hexl[4])
|
|
863
|
+
self._log_debug(f"Received transport state: {state}")
|
|
864
|
+
self.zone.transport._set_property("state", state)
|
|
865
|
+
|
|
866
|
+
#
|
|
867
|
+
# 0B [11]
|
|
868
|
+
# Party Mode not supported on X series (I think. TODO)
|
|
869
|
+
#
|
|
870
|
+
def response_action_0B(self, hexl: list, response: bytes):
|
|
871
|
+
pass
|
|
872
|
+
|
|
873
|
+
#
|
|
874
|
+
# 0E [14]
|
|
875
|
+
# EQ
|
|
876
|
+
#
|
|
877
|
+
def response_action_0E(self, hexl: list, response: bytes):
|
|
878
|
+
if hex_to_int(hexl[2]) == 3:
|
|
879
|
+
freq = hex_to_int(hexl[4])
|
|
880
|
+
value = hex_to_int(hexl[5])
|
|
881
|
+
self._log_debug(f"Received EQ requency:{freq} value: {value}")
|
|
882
|
+
self.zone.settings.eq._set_eq_freq(freq, value)
|
|
883
|
+
|
|
884
|
+
#
|
|
885
|
+
# 10 [16]
|
|
886
|
+
# Mono output
|
|
887
|
+
#
|
|
888
|
+
def response_action_10(self, hexl: list, response: bytes):
|
|
889
|
+
if hex_to_int(hexl[2]) == 2:
|
|
890
|
+
state = hex_to_int(hexl[4])
|
|
891
|
+
self._log_debug(f"Received mono ouput: {state}")
|
|
892
|
+
self.zone.settings._set_property("mono", state)
|
|
893
|
+
|
|
894
|
+
#
|
|
895
|
+
# 12 [18]
|
|
896
|
+
# Mute status
|
|
897
|
+
#
|
|
898
|
+
def response_action_12(self, hexl: list, response: bytes):
|
|
899
|
+
if hex_to_int(hexl[2]) == 2:
|
|
900
|
+
is_muted = bool(hex_to_int(hexl[4]))
|
|
901
|
+
self._log_debug(f"Received mute status 12: {is_muted}")
|
|
902
|
+
self.zone._set_property("mute", is_muted)
|
|
903
|
+
|
|
904
|
+
#
|
|
905
|
+
# 16 [22]
|
|
906
|
+
# Received Analog Input Name
|
|
907
|
+
#
|
|
908
|
+
# TODO, maybe this should be global with the analog outputs
|
|
909
|
+
#
|
|
910
|
+
def response_action_16(self, hexl: list, response: bytes):
|
|
911
|
+
self._log_debug(f"Received analog input name: {hexl}")
|
|
912
|
+
|
|
913
|
+
input_id = hex_to_int(hexl[3])
|
|
914
|
+
name = response[4:].decode("ascii")
|
|
915
|
+
|
|
916
|
+
if input_id == self.zone.id:
|
|
917
|
+
self._log_debug(f"Received analog input {input_id} name: {name}")
|
|
918
|
+
self.zone.settings.analog_input._set_property("name", name.strip())
|
|
919
|
+
|
|
920
|
+
# Optical Input
|
|
921
|
+
elif input_id == 12:
|
|
922
|
+
self._log_debug(f"Received optical input name: {name}")
|
|
923
|
+
self.vssl.settings._set_property("optical_input_name", name.strip())
|
|
924
|
+
|
|
925
|
+
#
|
|
926
|
+
# 17 [23]
|
|
927
|
+
# Received Keep Alive
|
|
928
|
+
#
|
|
929
|
+
def response_action_17(self, response: bytes):
|
|
930
|
+
self._log_debug(f"Z{self.zone.id} Alpha - Received keep alive: {response}")
|
|
931
|
+
# TODO
|
|
932
|
+
pass
|
|
933
|
+
|
|
934
|
+
#
|
|
935
|
+
# 19 [25]
|
|
936
|
+
# Received Device Name
|
|
937
|
+
#
|
|
938
|
+
def response_action_19(self, hexl: list, response: bytes):
|
|
939
|
+
try:
|
|
940
|
+
length = hex_to_int(hexl[2]) - 1
|
|
941
|
+
name = response[
|
|
942
|
+
self.JSON_HEADER_LENGTH : self.JSON_HEADER_LENGTH + length
|
|
943
|
+
].decode("ascii")
|
|
944
|
+
|
|
945
|
+
self._log_debug(f"Received device name: {name}")
|
|
946
|
+
|
|
947
|
+
self.vssl.settings._set_property("name", name.strip())
|
|
948
|
+
|
|
949
|
+
except Exception as error:
|
|
950
|
+
self._log_error(f"Exception occurred receiving device name: {error}")
|
|
951
|
+
|
|
952
|
+
#
|
|
953
|
+
# 26 [38]
|
|
954
|
+
# Zone Enabled / Disabled Feedback
|
|
955
|
+
#
|
|
956
|
+
def response_action_26(self, hexl: list, response: bytes):
|
|
957
|
+
if hex_to_int(hexl[2]) == 3:
|
|
958
|
+
# hexl[4] is the zone id
|
|
959
|
+
disabled = hex_to_int(hexl[5])
|
|
960
|
+
self._log_debug(f"Received zone disable: {disabled}")
|
|
961
|
+
self.zone.settings._set_property("disabled", bool(disabled))
|
|
962
|
+
|
|
963
|
+
#
|
|
964
|
+
# 2E [46]
|
|
965
|
+
# EQ Switch
|
|
966
|
+
#
|
|
967
|
+
def response_action_2E(self, hexl: list, response: bytes):
|
|
968
|
+
if hex_to_int(hexl[2]) == 2:
|
|
969
|
+
enabled = hex_to_int(hexl[4])
|
|
970
|
+
self._log_debug(f"Received EQ Switch: {enabled}")
|
|
971
|
+
self.zone.settings.eq._set_property("enabled", bool(enabled))
|
|
972
|
+
|
|
973
|
+
#
|
|
974
|
+
# 32 [50]
|
|
975
|
+
# = 'rm' key in the status object.
|
|
976
|
+
#
|
|
977
|
+
# A int is assigned to the zone when it starts playing.
|
|
978
|
+
# When a zone joins a group it will allocated the same 'rm' number.
|
|
979
|
+
#
|
|
980
|
+
# When a stream is started, a 'rm' is allocated to the zone.
|
|
981
|
+
# RM feedback is 0 when not playing and removed from a group
|
|
982
|
+
def response_action_32(self, hexl: list, response: bytes):
|
|
983
|
+
if hex_to_int(hexl[2]) == 2:
|
|
984
|
+
index = hex_to_int(hexl[4])
|
|
985
|
+
self._log_debug(f"Received group index: {index}")
|
|
986
|
+
self.zone.group._set_property("index", int(index))
|
|
987
|
+
|
|
988
|
+
#
|
|
989
|
+
# 4C [76]
|
|
990
|
+
# Group Response
|
|
991
|
+
#
|
|
992
|
+
def response_action_4C(self, hexl: list, response: bytes):
|
|
993
|
+
self._log_debug(f"Received group info: {hexl}")
|
|
994
|
+
|
|
995
|
+
if hex_to_int(hexl[2]) == 3:
|
|
996
|
+
if hex_to_int(hexl[3]) != self.zone.id:
|
|
997
|
+
self._log_warning(
|
|
998
|
+
f"Z{self.zone.id} Alpha - incorrect zone id in group response"
|
|
999
|
+
)
|
|
1000
|
+
return
|
|
1001
|
+
|
|
1002
|
+
self.zone.group._set_property("source", hex_to_int(hexl[5]))
|
|
1003
|
+
self.zone.group._set_property("is_master", hex_to_int(hexl[4]))
|
|
1004
|
+
|
|
1005
|
+
#
|
|
1006
|
+
# 48 [72]
|
|
1007
|
+
# Input Priority Feedback
|
|
1008
|
+
#
|
|
1009
|
+
def response_action_48(self, hexl: list, response: bytes):
|
|
1010
|
+
if hex_to_int(hexl[2]) == 2:
|
|
1011
|
+
priority = hex_to_int(hexl[4])
|
|
1012
|
+
self._log_debug(f"Received input priority: {priority}")
|
|
1013
|
+
self.zone.input._set_property("priority", priority)
|
|
1014
|
+
|
|
1015
|
+
#
|
|
1016
|
+
# 50 [80]
|
|
1017
|
+
# Adaptive Power Feedback
|
|
1018
|
+
#
|
|
1019
|
+
def response_action_50(self, hexl: list, response: bytes):
|
|
1020
|
+
if hex_to_int(hexl[2]) == 2:
|
|
1021
|
+
enabled = hex_to_int(hexl[4])
|
|
1022
|
+
self._log_debug(f"Received adaptive power setting: {enabled}")
|
|
1023
|
+
self.vssl.settings.power._set_property("adaptive", bool(int(enabled)))
|
|
1024
|
+
|
|
1025
|
+
#
|
|
1026
|
+
# Command confimation
|
|
1027
|
+
#
|
|
1028
|
+
def response_action_confimation(self, response: bytes):
|
|
1029
|
+
self._log_debug(
|
|
1030
|
+
f"Received command confimation: {response[1]}. Hex: {response.hex()}"
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
#
|
|
1034
|
+
# Default
|
|
1035
|
+
# Default Action
|
|
1036
|
+
#
|
|
1037
|
+
def response_action_default(self, hexl: list, response: bytes):
|
|
1038
|
+
self._log_debug(
|
|
1039
|
+
f"Received unknown command {hexl[1].upper()}. Hex: {response.hex()}"
|
|
1040
|
+
)
|