mx-remote 2.0.0__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.
- mx_remote/Interface.py +1656 -0
- mx_remote/Uid.py +137 -0
- mx_remote/__init__.py +12 -0
- mx_remote/api/BayConfig.py +53 -0
- mx_remote/api/__init__.py +8 -0
- mx_remote/const.py +20 -0
- mx_remote/main.py +117 -0
- mx_remote/proto/BayConfig.py +104 -0
- mx_remote/proto/Constants.py +382 -0
- mx_remote/proto/Data.py +142 -0
- mx_remote/proto/Factory.py +168 -0
- mx_remote/proto/FrameAmpDolbySettings.py +51 -0
- mx_remote/proto/FrameAmpZoneSettings.py +123 -0
- mx_remote/proto/FrameBase.py +80 -0
- mx_remote/proto/FrameBayConfig.py +47 -0
- mx_remote/proto/FrameBayConfigSecondary.py +43 -0
- mx_remote/proto/FrameBayHide.py +53 -0
- mx_remote/proto/FrameBayStatus.py +51 -0
- mx_remote/proto/FrameConnectStatus.py +38 -0
- mx_remote/proto/FrameDiscover.py +21 -0
- mx_remote/proto/FrameEDID.py +20 -0
- mx_remote/proto/FrameEDIDProfile.py +24 -0
- mx_remote/proto/FrameFilterStatus.py +39 -0
- mx_remote/proto/FrameFirmwareVersion.py +40 -0
- mx_remote/proto/FrameHeader.py +92 -0
- mx_remote/proto/FrameHello.py +77 -0
- mx_remote/proto/FrameLinks.py +45 -0
- mx_remote/proto/FrameMeshOperation.py +66 -0
- mx_remote/proto/FrameMirrorStatus.py +49 -0
- mx_remote/proto/FrameNetworkStatus.py +163 -0
- mx_remote/proto/FramePDUState.py +71 -0
- mx_remote/proto/FramePowerChange.py +38 -0
- mx_remote/proto/FrameRCAction.py +50 -0
- mx_remote/proto/FrameRCIr.py +17 -0
- mx_remote/proto/FrameRCKey.py +40 -0
- mx_remote/proto/FrameReboot.py +26 -0
- mx_remote/proto/FrameRoutingChange.py +66 -0
- mx_remote/proto/FrameSetName.py +27 -0
- mx_remote/proto/FrameSignalStatus.py +46 -0
- mx_remote/proto/FrameSignalStatusNew.py +285 -0
- mx_remote/proto/FrameSysTemperature.py +50 -0
- mx_remote/proto/FrameTXRCAction.py +58 -0
- mx_remote/proto/FrameTopology.py +36 -0
- mx_remote/proto/FrameV2IPDeviceConfiguration.py +86 -0
- mx_remote/proto/FrameV2IPLink.py +16 -0
- mx_remote/proto/FrameV2IPSetMaster.py +16 -0
- mx_remote/proto/FrameV2IPSourceSwitch.py +84 -0
- mx_remote/proto/FrameV2IPSources.py +43 -0
- mx_remote/proto/FrameV2IPStats.py +55 -0
- mx_remote/proto/FrameV2IPStreamDetails.py +46 -0
- mx_remote/proto/FrameVolume.py +62 -0
- mx_remote/proto/FrameVolumeDown.py +28 -0
- mx_remote/proto/FrameVolumeSet.py +81 -0
- mx_remote/proto/FrameVolumeUp.py +28 -0
- mx_remote/proto/LinkConfig.py +121 -0
- mx_remote/proto/PDUState.py +95 -0
- mx_remote/proto/Svd.py +69 -0
- mx_remote/proto/V2IPConfig.py +71 -0
- mx_remote/proto/V2IPStats.py +126 -0
- mx_remote/proto/__init__.py +8 -0
- mx_remote/proto/svd.csv +157 -0
- mx_remote/remote/Bay.py +923 -0
- mx_remote/remote/ConnectionAsync.py +132 -0
- mx_remote/remote/Device.py +530 -0
- mx_remote/remote/Link.py +187 -0
- mx_remote/remote/PDU.py +152 -0
- mx_remote/remote/Remote.py +300 -0
- mx_remote/remote/State.py +28 -0
- mx_remote/remote/V2IP.py +83 -0
- mx_remote-2.0.0.dist-info/METADATA +90 -0
- mx_remote-2.0.0.dist-info/RECORD +74 -0
- mx_remote-2.0.0.dist-info/WHEEL +4 -0
- mx_remote-2.0.0.dist-info/entry_points.txt +2 -0
- mx_remote-2.0.0.dist-info/licenses/LICENSE +11 -0
mx_remote/Interface.py
ADDED
|
@@ -0,0 +1,1656 @@
|
|
|
1
|
+
##################################################
|
|
2
|
+
## MX Remote Python Interface ##
|
|
3
|
+
## ##
|
|
4
|
+
## author: Lars Op den Kamp (lars@opdenkamp.eu) ##
|
|
5
|
+
## copyright (c) 2024 Op den Kamp IT Solutions ##
|
|
6
|
+
##################################################
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
import ipaddress
|
|
10
|
+
import logging
|
|
11
|
+
import netifaces
|
|
12
|
+
from .proto import RCKey
|
|
13
|
+
from .proto.Constants import *
|
|
14
|
+
from .proto.Data import VolumeMuteStatus
|
|
15
|
+
from .proto.PDUState import PDUState
|
|
16
|
+
from .proto.V2IPStats import V2IPDeviceStats
|
|
17
|
+
import socket
|
|
18
|
+
import struct
|
|
19
|
+
from typing import Any
|
|
20
|
+
from .Uid import MxrDeviceUid, MxrBayUid
|
|
21
|
+
|
|
22
|
+
_LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
def mxr_valid_addresses() -> list[str]:
|
|
25
|
+
"""
|
|
26
|
+
Get the list of valid local_ip addresses that can be used
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
addresses (list[str]): list of IP addressses that can be used for the local_ip parameter
|
|
30
|
+
"""
|
|
31
|
+
addresses = []
|
|
32
|
+
for iface in netifaces.interfaces():
|
|
33
|
+
if netifaces.AF_INET in netifaces.ifaddresses(iface):
|
|
34
|
+
addr = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
|
|
35
|
+
if not ipaddress.IPv4Address(addr).is_loopback:
|
|
36
|
+
addresses.append(addr)
|
|
37
|
+
return addresses
|
|
38
|
+
|
|
39
|
+
class DeviceStatus(Enum):
|
|
40
|
+
"""
|
|
41
|
+
Status of a device on the network
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
ONLINE = 0
|
|
45
|
+
""" unit is online """
|
|
46
|
+
|
|
47
|
+
OFFLINE = 1
|
|
48
|
+
""" unit is offline """
|
|
49
|
+
|
|
50
|
+
REBOOTING = 2
|
|
51
|
+
""" unit indicated that it is going to reboot """
|
|
52
|
+
|
|
53
|
+
BOOTING = 3
|
|
54
|
+
""" unit is booting """
|
|
55
|
+
|
|
56
|
+
INACTIVE = 4
|
|
57
|
+
""" bay is inactive (V2IP encoder/decoder idle) """
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
if self.value == DeviceStatus.ONLINE.value:
|
|
61
|
+
return 'Online'
|
|
62
|
+
if self.value == DeviceStatus.OFFLINE.value:
|
|
63
|
+
return 'Offline'
|
|
64
|
+
if self.value == DeviceStatus.REBOOTING.value:
|
|
65
|
+
return 'Rebooting'
|
|
66
|
+
if self.value == DeviceStatus.BOOTING.value:
|
|
67
|
+
return 'Booting'
|
|
68
|
+
if self.value == DeviceStatus.INACTIVE.value:
|
|
69
|
+
return 'Inactive'
|
|
70
|
+
return 'Unknown'
|
|
71
|
+
|
|
72
|
+
def __repr__(self) -> str:
|
|
73
|
+
return str(self)
|
|
74
|
+
|
|
75
|
+
class AmpDolbySettings:
|
|
76
|
+
"""
|
|
77
|
+
Dolby Digital settings for an amplifier
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
mode:int
|
|
81
|
+
""" Dolby mode """
|
|
82
|
+
|
|
83
|
+
pcm_upmix:bool
|
|
84
|
+
""" PCM upmixing enabled """
|
|
85
|
+
|
|
86
|
+
class AmpZoneSettings:
|
|
87
|
+
"""
|
|
88
|
+
Zone specific settings for an amplifier input/output
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
gain_left: int
|
|
92
|
+
""" gain level left channel """
|
|
93
|
+
|
|
94
|
+
gain_right: int
|
|
95
|
+
""" gain level right channel """
|
|
96
|
+
|
|
97
|
+
volume_min: int
|
|
98
|
+
""" minimum volume level """
|
|
99
|
+
|
|
100
|
+
volume_max: int
|
|
101
|
+
""" maximum volume level """
|
|
102
|
+
|
|
103
|
+
delay_left: int
|
|
104
|
+
""" audio delay left channel (ms) """
|
|
105
|
+
|
|
106
|
+
delay_right: int
|
|
107
|
+
""" audio delay right channel (ms) """
|
|
108
|
+
|
|
109
|
+
bass: int
|
|
110
|
+
""" bass level """
|
|
111
|
+
|
|
112
|
+
treble: int
|
|
113
|
+
""" treble level """
|
|
114
|
+
|
|
115
|
+
bridged: int
|
|
116
|
+
""" bridging mode setting """
|
|
117
|
+
|
|
118
|
+
power_mode: int
|
|
119
|
+
""" auto power off setting """
|
|
120
|
+
|
|
121
|
+
power_level: int
|
|
122
|
+
""" auto power off level """
|
|
123
|
+
|
|
124
|
+
power_timeout: int
|
|
125
|
+
""" auto power off timeout """
|
|
126
|
+
|
|
127
|
+
eq_left: list[int]
|
|
128
|
+
""" equalizer left channel """
|
|
129
|
+
|
|
130
|
+
eq_right: list[int]
|
|
131
|
+
""" equalizer right channel """
|
|
132
|
+
|
|
133
|
+
class V2IPStreamSource:
|
|
134
|
+
"""
|
|
135
|
+
V2IP multicast IP address and port number
|
|
136
|
+
"""
|
|
137
|
+
def __init__(self, label:str, data:bytes) -> None:
|
|
138
|
+
if len(data) < 6:
|
|
139
|
+
raise Exception(f"invalid size: {len(data)}")
|
|
140
|
+
self._label = label
|
|
141
|
+
self._ip = int.from_bytes(data[0:4], "big")
|
|
142
|
+
self._port = int(data[5]) << 8 | int(data[4])
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def label(self) -> str:
|
|
146
|
+
""" user friendly description of this stream """
|
|
147
|
+
return self._label
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def ip(self) -> str:
|
|
151
|
+
""" multicast IP address """
|
|
152
|
+
return socket.inet_ntoa(struct.pack('!L', self._ip))
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def port(self) -> int:
|
|
156
|
+
""" UDP port number """
|
|
157
|
+
return self._port
|
|
158
|
+
|
|
159
|
+
def __eq__(self, value: object) -> bool:
|
|
160
|
+
return (str(self) == str(value))
|
|
161
|
+
|
|
162
|
+
def __str__(self) -> str:
|
|
163
|
+
return f"{self.label}={self.ip}:{self.port}"
|
|
164
|
+
|
|
165
|
+
def __repr__(self) -> str:
|
|
166
|
+
return str(self)
|
|
167
|
+
|
|
168
|
+
class V2IPStreamSources:
|
|
169
|
+
"""
|
|
170
|
+
All V2IP multicast IP addresses and port numbers used by a device
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def video(self) -> V2IPStreamSource:
|
|
176
|
+
''' video stream source '''
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
@abstractmethod
|
|
180
|
+
def audio(self) -> V2IPStreamSource:
|
|
181
|
+
''' audio stream source '''
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
@abstractmethod
|
|
185
|
+
def anc(self) -> V2IPStreamSource:
|
|
186
|
+
''' ancillary stream source '''
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def arc(self) -> V2IPStreamSource:
|
|
191
|
+
''' audio return stream source '''
|
|
192
|
+
|
|
193
|
+
class BayBase(ABC):
|
|
194
|
+
"""
|
|
195
|
+
A bay (input/output) of an mx_remote device
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
@abstractmethod
|
|
200
|
+
def status(self) -> DeviceStatus:
|
|
201
|
+
'''bay status'''
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
@abstractmethod
|
|
205
|
+
def callbacks(self) -> 'MxrCallbacks':
|
|
206
|
+
''' mx_remote callbacks '''
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
@abstractmethod
|
|
210
|
+
def device(self) -> 'DeviceBase':
|
|
211
|
+
''' device to which this bay belongs '''
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def bay_uid(self) -> MxrBayUid:
|
|
216
|
+
''' unique id of this bay '''
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
@abstractmethod
|
|
220
|
+
def port(self) -> int:
|
|
221
|
+
''' port number '''
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
@abstractmethod
|
|
225
|
+
def is_local(self) -> bool:
|
|
226
|
+
''' local or remote bay '''
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
@abstractmethod
|
|
230
|
+
def bay_name(self) -> str:
|
|
231
|
+
''' bay name for logging (mode / number)'''
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
@abstractmethod
|
|
235
|
+
def user_name(self) -> str:
|
|
236
|
+
''' name set up by the user '''
|
|
237
|
+
|
|
238
|
+
@user_name.setter
|
|
239
|
+
@abstractmethod
|
|
240
|
+
def user_name(self, val:str) -> None:
|
|
241
|
+
''' mx_remote update of the user set name '''
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
@abstractmethod
|
|
245
|
+
def has_default_name(self) -> bool:
|
|
246
|
+
''' default name not changed by the user '''
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
@abstractmethod
|
|
250
|
+
def edid_profile(self) -> EdidProfile:
|
|
251
|
+
''' edid profile used by the source '''
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
@abstractmethod
|
|
255
|
+
def bay_label(self) -> str:
|
|
256
|
+
'''user friendly label for this bay'''
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
@abstractmethod
|
|
260
|
+
def features_mask(self) -> BayStatusMask:
|
|
261
|
+
''' features/status '''
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
@abstractmethod
|
|
265
|
+
def is_v2ip_source(self) -> bool:
|
|
266
|
+
'''V2IP source device'''
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
@abstractmethod
|
|
270
|
+
def is_v2ip_sink(self) -> bool:
|
|
271
|
+
'''V2IP sink device'''
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
@abstractmethod
|
|
275
|
+
def is_v2ip_remote_sink(self) -> bool:
|
|
276
|
+
'''V2IP remote sink bay'''
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
@abstractmethod
|
|
280
|
+
def is_v2ip_remote_source(self) -> bool:
|
|
281
|
+
'''V2IP remote source bay'''
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
@abstractmethod
|
|
285
|
+
def is_v2ip_remote(self) -> bool:
|
|
286
|
+
'''V2IP remote bay'''
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
@abstractmethod
|
|
290
|
+
def dolby_input(self) -> int:
|
|
291
|
+
'''Dolby Digital input'''
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def dolby_input_bay(self) -> 'BayBase':
|
|
295
|
+
'''Dolby Digital input bay used by this audio output bay'''
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
@abstractmethod
|
|
299
|
+
def features(self) -> list[str]:
|
|
300
|
+
'''List of supported features as strings'''
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
@abstractmethod
|
|
304
|
+
def has_volume_control(self) -> bool:
|
|
305
|
+
'''Volume control supported by this bay'''
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
@abstractmethod
|
|
309
|
+
def is_input(self) -> bool:
|
|
310
|
+
'''Source bay'''
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
@abstractmethod
|
|
314
|
+
def is_output(self) -> bool:
|
|
315
|
+
'''Sink bay'''
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
@abstractmethod
|
|
319
|
+
def mode(self) -> str:
|
|
320
|
+
'''Bay mode name'''
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def other_mode(self) -> str:
|
|
324
|
+
'''Bay mode name of the opposite side (so Output if this bay is an Input)'''
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
@abstractmethod
|
|
328
|
+
def bay(self) -> int:
|
|
329
|
+
'''Bay number'''
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
@abstractmethod
|
|
333
|
+
def available(self) -> bool:
|
|
334
|
+
'''True if available'''
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
@abstractmethod
|
|
338
|
+
def is_hdmi(self) -> bool:
|
|
339
|
+
'''True if this is an HDMI input or output'''
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
@abstractmethod
|
|
343
|
+
def is_hdbaset(self) -> bool:
|
|
344
|
+
'''True if this is a HDBaseT bay'''
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
@abstractmethod
|
|
348
|
+
def is_audio(self) -> bool:
|
|
349
|
+
'''True if this is audio input or output bay'''
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
@abstractmethod
|
|
353
|
+
def video_source(self) -> 'BayBase':
|
|
354
|
+
'''Current video source (output only)'''
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
@abstractmethod
|
|
358
|
+
def audio_source(self) -> 'BayBase':
|
|
359
|
+
'''Current audio source (output only)'''
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
@abstractmethod
|
|
363
|
+
def powered_on(self) -> bool:
|
|
364
|
+
'''True the connected device supports CEC and reports that the device is powered on'''
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
@abstractmethod
|
|
368
|
+
def powered_off(self) -> bool:
|
|
369
|
+
'''True the connected device supports CEC and reports that the device is powered off'''
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
@abstractmethod
|
|
373
|
+
def power_status(self) -> str:
|
|
374
|
+
'''Power status as string'''
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
@abstractmethod
|
|
378
|
+
def faulty(self) -> bool:
|
|
379
|
+
'''True if a fault was detected'''
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
@abstractmethod
|
|
383
|
+
def hidden(self) -> bool:
|
|
384
|
+
'''True if flagged as hidden'''
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
@abstractmethod
|
|
388
|
+
def poe_powered(self) -> bool:
|
|
389
|
+
'''True if PoE has been enabled (HDBaseT only)'''
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
@abstractmethod
|
|
393
|
+
def hdbt_connected(self) -> bool:
|
|
394
|
+
'''HDBaseT receiver connected'''
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
@abstractmethod
|
|
398
|
+
def signal_detected(self) -> bool:
|
|
399
|
+
'''Video signal detected (matrix/oneip) or audio signal detected (proamp)'''
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
@abstractmethod
|
|
403
|
+
def signal_type(self) -> str:
|
|
404
|
+
'''Audio or video signal type'''
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
@abstractmethod
|
|
408
|
+
def hpd_detected(self) -> bool:
|
|
409
|
+
'''Hotplug detected'''
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
@abstractmethod
|
|
413
|
+
def cec_detected(self) -> bool:
|
|
414
|
+
'''Connected device supports HDMI-CEC'''
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
@abstractmethod
|
|
418
|
+
def mirroring(self) -> str:
|
|
419
|
+
'''The name of the bay if mirroring has been set up'''
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
@abstractmethod
|
|
423
|
+
def filtered(self) -> str:
|
|
424
|
+
'''Filtered bays'''
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
@abstractmethod
|
|
428
|
+
def arc(self) -> str:
|
|
429
|
+
'''Audio return channel status'''
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
@abstractmethod
|
|
433
|
+
def volume(self) -> int:
|
|
434
|
+
'''Current volume level (percentage)'''
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
@abstractmethod
|
|
438
|
+
def muted(self) -> bool:
|
|
439
|
+
'''True if audio has been muted'''
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
@abstractmethod
|
|
443
|
+
def online(self) -> bool:
|
|
444
|
+
'''True if online'''
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
@abstractmethod
|
|
448
|
+
def rebooting(self) -> bool:
|
|
449
|
+
'''True if rebooting'''
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
@abstractmethod
|
|
453
|
+
def booting(self) -> bool:
|
|
454
|
+
'''True if booting'''
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
@abstractmethod
|
|
458
|
+
def is_primary(self) -> bool:
|
|
459
|
+
'''True if this bay is the primary bay in a mirroring setup'''
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
@abstractmethod
|
|
463
|
+
def primary(self) -> 'BayBase':
|
|
464
|
+
'''The primary bay in a mirroring setup'''
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
@abstractmethod
|
|
468
|
+
def v2ip_source(self) -> V2IPStreamSources|None:
|
|
469
|
+
'''V2IP source address information'''
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
@abstractmethod
|
|
473
|
+
def link(self) -> 'BayLink':
|
|
474
|
+
'''mx-remote virtual link configuration (proamp<->matrix)'''
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
@abstractmethod
|
|
478
|
+
def linked_bay(self) -> 'BayBase':
|
|
479
|
+
'''linked bay if an mx-remote virtual link has been set up'''
|
|
480
|
+
|
|
481
|
+
@property
|
|
482
|
+
@abstractmethod
|
|
483
|
+
def link_configured(self) -> bool:
|
|
484
|
+
'''mx-remote virtual link configured (proamp<->matrix)'''
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
@abstractmethod
|
|
488
|
+
def link_connected(self) -> bool:
|
|
489
|
+
'''mx-remote virtual link connected (proamp<->matrix)'''
|
|
490
|
+
|
|
491
|
+
@property
|
|
492
|
+
@abstractmethod
|
|
493
|
+
def volume_status(self) -> VolumeMuteStatus:
|
|
494
|
+
'''volume and mute status'''
|
|
495
|
+
|
|
496
|
+
@property
|
|
497
|
+
@abstractmethod
|
|
498
|
+
def amp_settings(self) -> AmpZoneSettings|None:
|
|
499
|
+
'''proamp zone settings'''
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
@abstractmethod
|
|
503
|
+
def encoder_disabled(self) -> bool:
|
|
504
|
+
''' video/audio encoder disabled '''
|
|
505
|
+
|
|
506
|
+
@property
|
|
507
|
+
@abstractmethod
|
|
508
|
+
def decoder_disabled(self) -> bool:
|
|
509
|
+
''' video/audio decoder disabled '''
|
|
510
|
+
|
|
511
|
+
@abstractmethod
|
|
512
|
+
async def set_name(self, name:str) -> bool:
|
|
513
|
+
'''change the name of abay'''
|
|
514
|
+
|
|
515
|
+
@abstractmethod
|
|
516
|
+
async def select_video_source(self, port:int, opt:bool=True) -> bool:
|
|
517
|
+
'''change the video source of an output bay'''
|
|
518
|
+
|
|
519
|
+
@abstractmethod
|
|
520
|
+
async def select_video_source_by_user_name(self, name:str, opt:bool=True) -> bool:
|
|
521
|
+
'''change the video source of an output bay'''
|
|
522
|
+
|
|
523
|
+
@abstractmethod
|
|
524
|
+
async def select_audio_source(self, source:Any) -> bool:
|
|
525
|
+
'''change the audio source of an output bay'''
|
|
526
|
+
|
|
527
|
+
@abstractmethod
|
|
528
|
+
async def select_edid_profile(self, profile:EdidProfile) -> bool:
|
|
529
|
+
'''change the edid profile of an input bay'''
|
|
530
|
+
|
|
531
|
+
@abstractmethod
|
|
532
|
+
async def set_hidden(self, hidden:bool) -> bool:
|
|
533
|
+
'''change the hidden status of a bay'''
|
|
534
|
+
|
|
535
|
+
@abstractmethod
|
|
536
|
+
async def power_on(self) -> bool:
|
|
537
|
+
'''power on the remote device if CEC is supported'''
|
|
538
|
+
|
|
539
|
+
@abstractmethod
|
|
540
|
+
async def power_off(self) -> bool:
|
|
541
|
+
'''power off the remote device if CEC is supported'''
|
|
542
|
+
|
|
543
|
+
@abstractmethod
|
|
544
|
+
def volume_up(self) -> bool:
|
|
545
|
+
'''change the volume if supported'''
|
|
546
|
+
|
|
547
|
+
@abstractmethod
|
|
548
|
+
def volume_down(self) -> bool:
|
|
549
|
+
'''change the volume if supported'''
|
|
550
|
+
|
|
551
|
+
@abstractmethod
|
|
552
|
+
def volume_set(self, volume:int) -> bool:
|
|
553
|
+
'''change the volume if supported'''
|
|
554
|
+
|
|
555
|
+
@abstractmethod
|
|
556
|
+
def mute_set(self, mute:bool) -> bool:
|
|
557
|
+
'''change the mute status if supported'''
|
|
558
|
+
|
|
559
|
+
@abstractmethod
|
|
560
|
+
async def send_key(self, key:int) -> bool:
|
|
561
|
+
'''send a remote control key press to the device'''
|
|
562
|
+
|
|
563
|
+
@abstractmethod
|
|
564
|
+
def on_mxr_bay_status(self, data:BayStatusMask) -> None:
|
|
565
|
+
'''internal callback'''
|
|
566
|
+
|
|
567
|
+
@abstractmethod
|
|
568
|
+
def register_callback(self, callback:callable) -> None:
|
|
569
|
+
'''register a callback, called when the bay state changed'''
|
|
570
|
+
|
|
571
|
+
@abstractmethod
|
|
572
|
+
def unregister_callback(self, callback:callable) -> None:
|
|
573
|
+
'''unregister a callback'''
|
|
574
|
+
|
|
575
|
+
@abstractmethod
|
|
576
|
+
def call_callbacks(self) -> None:
|
|
577
|
+
'''notify callbacks that this bay has changed'''
|
|
578
|
+
|
|
579
|
+
class DeviceFeatures:
|
|
580
|
+
"""
|
|
581
|
+
Features and status of an mx_remote device
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
def __init__(self, value:int) -> None:
|
|
585
|
+
self._features = value
|
|
586
|
+
|
|
587
|
+
@property
|
|
588
|
+
def value(self) -> int:
|
|
589
|
+
return self._features
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def ir_rx(self) -> bool:
|
|
593
|
+
""" IR receive supported """
|
|
594
|
+
return ((self._features & MXR_DEVICE_FEATURE_IR_RX) != 0)
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def ir_tx(self) -> bool:
|
|
598
|
+
""" IR blast supported """
|
|
599
|
+
return ((self._features & MXR_DEVICE_FEATURE_IR_TX) != 0)
|
|
600
|
+
|
|
601
|
+
@property
|
|
602
|
+
def cec(self) -> bool:
|
|
603
|
+
""" HDMI-CEC supported """
|
|
604
|
+
return ((self._features & MXR_DEVICE_FEATURE_CEC) != 0)
|
|
605
|
+
|
|
606
|
+
@property
|
|
607
|
+
def v2ip_source(self) -> bool:
|
|
608
|
+
""" V2IP source """
|
|
609
|
+
return ((self._features & MXR_DEVICE_FEATURE_V2IP_SOURCE) != 0)
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def v2ip_sink(self) -> bool:
|
|
613
|
+
""" V2IP sink """
|
|
614
|
+
return ((self._features & MXR_DEVICE_FEATURE_V2IP_SINK) != 0)
|
|
615
|
+
|
|
616
|
+
@property
|
|
617
|
+
def video_routing(self) -> bool:
|
|
618
|
+
""" video routing supported """
|
|
619
|
+
return ((self._features & MXR_DEVICE_FEATURE_VIDEO_ROUTING) != 0)
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def audio_routing(self) -> bool:
|
|
623
|
+
""" (independent) audio routing supported """
|
|
624
|
+
return ((self._features & MXR_DEVICE_FEATURE_AUDIO_ROUTING) != 0)
|
|
625
|
+
|
|
626
|
+
@property
|
|
627
|
+
def volume_control(self) -> bool:
|
|
628
|
+
""" volume control supported """
|
|
629
|
+
return ((self._features & MXR_DEVICE_FEATURE_VOLUME_CONTROL) != 0)
|
|
630
|
+
|
|
631
|
+
@property
|
|
632
|
+
def arc(self) -> bool:
|
|
633
|
+
""" audio return channel supported """
|
|
634
|
+
return ((self._features & MXR_DEVICE_FEATURE_AUDIO_RETURN) != 0)
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def remote_control(self) -> bool:
|
|
638
|
+
""" remote contro pass through supported """
|
|
639
|
+
return ((self._features & MXR_DEVICE_FEATURE_REMOTE_CONTROL) != 0)
|
|
640
|
+
|
|
641
|
+
@property
|
|
642
|
+
def setup_completed(self) -> bool:
|
|
643
|
+
""" device setup flagged as completed """
|
|
644
|
+
return ((self._features & MXR_DEVICE_FEATURE_SETUP_COMPLETED) != 0)
|
|
645
|
+
|
|
646
|
+
@property
|
|
647
|
+
def mesh_master(self) -> bool:
|
|
648
|
+
""" master device of a V2IP mesh """
|
|
649
|
+
return ((self._features & MXR_DEVICE_FEATURE_MESH_MASTER) != 0)
|
|
650
|
+
|
|
651
|
+
@property
|
|
652
|
+
def status_notify(self) -> bool:
|
|
653
|
+
""" notification registered in system status """
|
|
654
|
+
return ((self._features & MXR_DEVICE_FEATURE_STATUS_NOTIFY) != 0)
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def status_warning(self) -> bool:
|
|
658
|
+
""" warning registered in system status """
|
|
659
|
+
return ((self._features & MXR_DEVICE_FEATURE_STATUS_WARNING) != 0)
|
|
660
|
+
|
|
661
|
+
@property
|
|
662
|
+
def status_error(self) -> bool:
|
|
663
|
+
""" error registered in system status """
|
|
664
|
+
return ((self._features & MXR_DEVICE_FEATURE_STATUS_ERROR) != 0)
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def status_rebooting(self) -> bool:
|
|
668
|
+
""" device is going to reboot """
|
|
669
|
+
return ((self._features & MXR_DEVICE_FEATURE_STATUS_REBOOTING) != 0)
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def mesh_member(self) -> bool:
|
|
673
|
+
""" member of a V2IP mesh """
|
|
674
|
+
return ((self._features & MXR_DEVICE_FEATURE_MESH_MEMBER) != 0)
|
|
675
|
+
|
|
676
|
+
@property
|
|
677
|
+
def audio_amp(self) -> bool:
|
|
678
|
+
""" audio amplifier """
|
|
679
|
+
return ((self._features & MXR_DEVICE_FEATURE_AUDIO_AMPLIFIER) != 0)
|
|
680
|
+
|
|
681
|
+
@property
|
|
682
|
+
def booting(self) -> bool:
|
|
683
|
+
""" device is booting """
|
|
684
|
+
return ((self._features & MXR_DEVICE_FEATURE_BOOTING) != 0)
|
|
685
|
+
|
|
686
|
+
@property
|
|
687
|
+
def manager(self) -> bool:
|
|
688
|
+
""" device is allowed to manage mx_remote devices """
|
|
689
|
+
return ((self._features & MXR_DEVICE_FEATURE_MANAGER) != 0)
|
|
690
|
+
|
|
691
|
+
@property
|
|
692
|
+
def boot_bit(self) -> bool:
|
|
693
|
+
""" bit that is flipped every time the device reboots """
|
|
694
|
+
return ((self._features & MXR_DEVICE_FEATURE_BOOT_BIT) != 0)
|
|
695
|
+
|
|
696
|
+
def __eq__(self, value: object) -> bool:
|
|
697
|
+
if (not isinstance(value, DeviceFeatures)):
|
|
698
|
+
return False
|
|
699
|
+
return self._features == value._features
|
|
700
|
+
|
|
701
|
+
@property
|
|
702
|
+
def features(self) -> list[str]:
|
|
703
|
+
""" supported features as list of string descriptions """
|
|
704
|
+
ft:list[str] = []
|
|
705
|
+
if self.ir_rx:
|
|
706
|
+
ft.append('IR RX')
|
|
707
|
+
if self.ir_tx:
|
|
708
|
+
ft.append('IR TX')
|
|
709
|
+
if self.cec:
|
|
710
|
+
ft.append('CEC')
|
|
711
|
+
if self.v2ip_source:
|
|
712
|
+
ft.append('V2IP source')
|
|
713
|
+
if self.v2ip_sink:
|
|
714
|
+
ft.append('V2IP sink')
|
|
715
|
+
if self.video_routing:
|
|
716
|
+
ft.append('video routing')
|
|
717
|
+
if self.audio_routing:
|
|
718
|
+
ft.append('audio routing')
|
|
719
|
+
if self.volume_control:
|
|
720
|
+
ft.append('volume control')
|
|
721
|
+
if self.arc:
|
|
722
|
+
ft.append('ARC')
|
|
723
|
+
if self.remote_control:
|
|
724
|
+
ft.append('remote control')
|
|
725
|
+
if self.setup_completed:
|
|
726
|
+
ft.append('setup completed')
|
|
727
|
+
if self.mesh_master:
|
|
728
|
+
ft.append('mesh master')
|
|
729
|
+
if self.status_notify:
|
|
730
|
+
ft.append('status notify')
|
|
731
|
+
if self.status_warning:
|
|
732
|
+
ft.append('status warning')
|
|
733
|
+
if self.status_error:
|
|
734
|
+
ft.append('status error')
|
|
735
|
+
if self.status_rebooting:
|
|
736
|
+
ft.append('status rebooting')
|
|
737
|
+
if self.mesh_member:
|
|
738
|
+
ft.append('mesh member')
|
|
739
|
+
if self.audio_amp:
|
|
740
|
+
ft.append('audio amp')
|
|
741
|
+
if self.booting:
|
|
742
|
+
ft.append('booting')
|
|
743
|
+
if self.manager:
|
|
744
|
+
ft.append('manager')
|
|
745
|
+
return ft
|
|
746
|
+
|
|
747
|
+
def __str__(self) -> str:
|
|
748
|
+
return str(self.features)
|
|
749
|
+
|
|
750
|
+
def __repr__(self) -> str:
|
|
751
|
+
return str(self)
|
|
752
|
+
|
|
753
|
+
class DeviceV2IPDetailsBase(ABC):
|
|
754
|
+
""" V2IP stream source details for a device """
|
|
755
|
+
|
|
756
|
+
@property
|
|
757
|
+
@abstractmethod
|
|
758
|
+
def has_config(self) -> bool:
|
|
759
|
+
""" configuation known """
|
|
760
|
+
|
|
761
|
+
@property
|
|
762
|
+
@abstractmethod
|
|
763
|
+
def video(self) -> V2IPStreamSource|None:
|
|
764
|
+
""" video stream source """
|
|
765
|
+
|
|
766
|
+
@property
|
|
767
|
+
@abstractmethod
|
|
768
|
+
def audio(self) -> V2IPStreamSource:
|
|
769
|
+
""" audio stream source """
|
|
770
|
+
|
|
771
|
+
@property
|
|
772
|
+
@abstractmethod
|
|
773
|
+
def anc(self) -> V2IPStreamSource:
|
|
774
|
+
""" ancillary stream source """
|
|
775
|
+
|
|
776
|
+
@property
|
|
777
|
+
@abstractmethod
|
|
778
|
+
def arc(self) -> V2IPStreamSource:
|
|
779
|
+
""" audio return channel stream source """
|
|
780
|
+
|
|
781
|
+
@property
|
|
782
|
+
@abstractmethod
|
|
783
|
+
def tx_rate(self) -> int:
|
|
784
|
+
""" transmit rate in Mbit/s """
|
|
785
|
+
|
|
786
|
+
def __eq__(self, value: object) -> bool:
|
|
787
|
+
if isinstance(value, DeviceV2IPDetailsBase):
|
|
788
|
+
return (self.video == value.video) \
|
|
789
|
+
and (self.audio == value.audio) \
|
|
790
|
+
and (self.anc == value.anc) \
|
|
791
|
+
and (self.arc == value.arc) \
|
|
792
|
+
and (self.tx_rate == value.tx_rate)
|
|
793
|
+
return False
|
|
794
|
+
|
|
795
|
+
class UtpLinkSpeed(Enum):
|
|
796
|
+
''' UTP link speed '''
|
|
797
|
+
|
|
798
|
+
UNKNOWN = 0
|
|
799
|
+
''' unknown speed '''
|
|
800
|
+
|
|
801
|
+
L_10M = 1
|
|
802
|
+
''' 10Mbit/s '''
|
|
803
|
+
|
|
804
|
+
L_100M = 2
|
|
805
|
+
''' 100Mbit/s '''
|
|
806
|
+
|
|
807
|
+
L_200M = 3
|
|
808
|
+
''' 200Mbit/s '''
|
|
809
|
+
|
|
810
|
+
L_1G = 4
|
|
811
|
+
''' 1Gbit/s '''
|
|
812
|
+
|
|
813
|
+
def __str__(self) -> str:
|
|
814
|
+
if self.value == 1:
|
|
815
|
+
return '10Mbit/s'
|
|
816
|
+
if self.value == 2:
|
|
817
|
+
return '100Mbit/s'
|
|
818
|
+
if self.value == 3:
|
|
819
|
+
return '200Mbit/s'
|
|
820
|
+
if self.value == 4:
|
|
821
|
+
return '1Gbit/s'
|
|
822
|
+
return 'Unknown'
|
|
823
|
+
|
|
824
|
+
def __repr__(self) -> str:
|
|
825
|
+
return str(self)
|
|
826
|
+
|
|
827
|
+
class UtpLinkErrorStatus(ABC):
|
|
828
|
+
''' UTP link error status bits '''
|
|
829
|
+
|
|
830
|
+
@property
|
|
831
|
+
@abstractmethod
|
|
832
|
+
def in_error(self):
|
|
833
|
+
''' rx errors detected '''
|
|
834
|
+
|
|
835
|
+
@property
|
|
836
|
+
@abstractmethod
|
|
837
|
+
def in_fcs_error(self):
|
|
838
|
+
''' rx FCS errors detected '''
|
|
839
|
+
|
|
840
|
+
@property
|
|
841
|
+
@abstractmethod
|
|
842
|
+
def in_collision(self):
|
|
843
|
+
''' rx collisions detected '''
|
|
844
|
+
|
|
845
|
+
@property
|
|
846
|
+
@abstractmethod
|
|
847
|
+
def out_deferred(self):
|
|
848
|
+
''' tx deferred detected '''
|
|
849
|
+
|
|
850
|
+
@property
|
|
851
|
+
@abstractmethod
|
|
852
|
+
def out_excessive(self):
|
|
853
|
+
''' tx excessive detected '''
|
|
854
|
+
|
|
855
|
+
@property
|
|
856
|
+
@abstractmethod
|
|
857
|
+
def polarity_error(self):
|
|
858
|
+
''' polarity differences between pairs detected '''
|
|
859
|
+
|
|
860
|
+
@property
|
|
861
|
+
@abstractmethod
|
|
862
|
+
def skew_warning(self):
|
|
863
|
+
''' clock skew > 8 detected '''
|
|
864
|
+
|
|
865
|
+
@property
|
|
866
|
+
@abstractmethod
|
|
867
|
+
def length_warning(self):
|
|
868
|
+
''' different pair lengths detected '''
|
|
869
|
+
|
|
870
|
+
class UtpCableStatus(ABC):
|
|
871
|
+
'''' UTP cable pair status '''
|
|
872
|
+
|
|
873
|
+
@property
|
|
874
|
+
@abstractmethod
|
|
875
|
+
def polarity(self) -> bool:
|
|
876
|
+
''' positive or negative polarity '''
|
|
877
|
+
|
|
878
|
+
@property
|
|
879
|
+
@abstractmethod
|
|
880
|
+
def pair(self) -> int:
|
|
881
|
+
''' pair number '''
|
|
882
|
+
|
|
883
|
+
@property
|
|
884
|
+
@abstractmethod
|
|
885
|
+
def skew(self) -> int:
|
|
886
|
+
''' detected clock skew '''
|
|
887
|
+
|
|
888
|
+
@property
|
|
889
|
+
@abstractmethod
|
|
890
|
+
def length(self) -> int:
|
|
891
|
+
''' detected length in meters '''
|
|
892
|
+
|
|
893
|
+
class NetworkPortStatus(ABC):
|
|
894
|
+
''' detailed status of a network port'''
|
|
895
|
+
|
|
896
|
+
@property
|
|
897
|
+
@abstractmethod
|
|
898
|
+
def port(self) -> int:
|
|
899
|
+
'''port number'''
|
|
900
|
+
|
|
901
|
+
@property
|
|
902
|
+
@abstractmethod
|
|
903
|
+
def errors(self) -> UtpLinkErrorStatus:
|
|
904
|
+
''' link error status '''
|
|
905
|
+
|
|
906
|
+
@property
|
|
907
|
+
@abstractmethod
|
|
908
|
+
def vct_status(self) -> list[str]:
|
|
909
|
+
''' virtual cable test results '''
|
|
910
|
+
|
|
911
|
+
@property
|
|
912
|
+
@abstractmethod
|
|
913
|
+
def link_speed(self) -> UtpLinkSpeed:
|
|
914
|
+
''' link speed '''
|
|
915
|
+
|
|
916
|
+
@property
|
|
917
|
+
@abstractmethod
|
|
918
|
+
def link_full_duplex(self) -> bool:
|
|
919
|
+
''' full duplex or half duplex '''
|
|
920
|
+
|
|
921
|
+
@property
|
|
922
|
+
@abstractmethod
|
|
923
|
+
def name(self) -> str:
|
|
924
|
+
''' description of the port '''
|
|
925
|
+
|
|
926
|
+
@property
|
|
927
|
+
@abstractmethod
|
|
928
|
+
def ip(self) -> str:
|
|
929
|
+
''' IP address '''
|
|
930
|
+
|
|
931
|
+
@property
|
|
932
|
+
@abstractmethod
|
|
933
|
+
def querier(self) -> str:
|
|
934
|
+
''' detected IGMP querier or 0.0.0.0 if not detected'''
|
|
935
|
+
|
|
936
|
+
@property
|
|
937
|
+
@abstractmethod
|
|
938
|
+
def cable_status(self) -> UtpCableStatus:
|
|
939
|
+
''' utp cable pair status '''
|
|
940
|
+
|
|
941
|
+
class DeviceBase(ABC):
|
|
942
|
+
''' an mx_remote device on the network '''
|
|
943
|
+
|
|
944
|
+
@property
|
|
945
|
+
@abstractmethod
|
|
946
|
+
def status(self) -> DeviceStatus:
|
|
947
|
+
'''device status'''
|
|
948
|
+
|
|
949
|
+
@property
|
|
950
|
+
@abstractmethod
|
|
951
|
+
def name(self) -> str:
|
|
952
|
+
'''device name'''
|
|
953
|
+
|
|
954
|
+
@abstractmethod
|
|
955
|
+
def registry(self) -> 'DeviceRegistry':
|
|
956
|
+
'''local device information registry'''
|
|
957
|
+
|
|
958
|
+
@property
|
|
959
|
+
@abstractmethod
|
|
960
|
+
def configuration_complete(self) -> bool:
|
|
961
|
+
'''check whether all configuration info for this device has been received'''
|
|
962
|
+
|
|
963
|
+
@property
|
|
964
|
+
@abstractmethod
|
|
965
|
+
def model_name(self) -> str:
|
|
966
|
+
'''Model name'''
|
|
967
|
+
|
|
968
|
+
@property
|
|
969
|
+
@abstractmethod
|
|
970
|
+
def callbacks(self) -> 'MxrCallbacks':
|
|
971
|
+
'''callbacks for this device'''
|
|
972
|
+
|
|
973
|
+
@property
|
|
974
|
+
@abstractmethod
|
|
975
|
+
def remote_id(self) -> MxrDeviceUid:
|
|
976
|
+
'''unique id'''
|
|
977
|
+
|
|
978
|
+
@property
|
|
979
|
+
@abstractmethod
|
|
980
|
+
def version(self) -> str:
|
|
981
|
+
'''firmware version'''
|
|
982
|
+
|
|
983
|
+
@property
|
|
984
|
+
@abstractmethod
|
|
985
|
+
def address(self) -> str:
|
|
986
|
+
'''IP address'''
|
|
987
|
+
|
|
988
|
+
@property
|
|
989
|
+
@abstractmethod
|
|
990
|
+
def features(self) -> DeviceFeatures:
|
|
991
|
+
'''supported features'''
|
|
992
|
+
|
|
993
|
+
@property
|
|
994
|
+
@abstractmethod
|
|
995
|
+
def serial(self) -> str:
|
|
996
|
+
'''serial number'''
|
|
997
|
+
|
|
998
|
+
@property
|
|
999
|
+
@abstractmethod
|
|
1000
|
+
def bays(self) -> dict[str, BayBase]:
|
|
1001
|
+
'''device inputs and outputs'''
|
|
1002
|
+
|
|
1003
|
+
@property
|
|
1004
|
+
@abstractmethod
|
|
1005
|
+
def inputs(self) -> dict[str, BayBase]:
|
|
1006
|
+
'''device inputs'''
|
|
1007
|
+
|
|
1008
|
+
@abstractmethod
|
|
1009
|
+
def nb_inputs(self) -> int:
|
|
1010
|
+
'''number of inputs'''
|
|
1011
|
+
|
|
1012
|
+
@property
|
|
1013
|
+
@abstractmethod
|
|
1014
|
+
def first_input(self) -> BayBase:
|
|
1015
|
+
'''the first local input'''
|
|
1016
|
+
|
|
1017
|
+
@property
|
|
1018
|
+
@abstractmethod
|
|
1019
|
+
def outputs(self) -> dict[str, BayBase]:
|
|
1020
|
+
'''device outputs'''
|
|
1021
|
+
|
|
1022
|
+
@abstractmethod
|
|
1023
|
+
def nb_outputs(self) -> int:
|
|
1024
|
+
'''number of outputs'''
|
|
1025
|
+
|
|
1026
|
+
@property
|
|
1027
|
+
@abstractmethod
|
|
1028
|
+
def first_output(self) -> BayBase:
|
|
1029
|
+
'''the first local output'''
|
|
1030
|
+
|
|
1031
|
+
@property
|
|
1032
|
+
@abstractmethod
|
|
1033
|
+
def online(self) -> bool:
|
|
1034
|
+
'''True if online'''
|
|
1035
|
+
|
|
1036
|
+
@property
|
|
1037
|
+
@abstractmethod
|
|
1038
|
+
def rebooting(self) -> bool:
|
|
1039
|
+
'''True if rebooting'''
|
|
1040
|
+
|
|
1041
|
+
@property
|
|
1042
|
+
@abstractmethod
|
|
1043
|
+
def booting(self) -> bool:
|
|
1044
|
+
'''True if booting'''
|
|
1045
|
+
|
|
1046
|
+
@property
|
|
1047
|
+
@abstractmethod
|
|
1048
|
+
def is_amp(self) -> bool:
|
|
1049
|
+
'''True if as an audio amplifier'''
|
|
1050
|
+
|
|
1051
|
+
@property
|
|
1052
|
+
@abstractmethod
|
|
1053
|
+
def amp_dolby_channels(self) -> int:
|
|
1054
|
+
'''number of dolby input channels'''
|
|
1055
|
+
|
|
1056
|
+
@property
|
|
1057
|
+
@abstractmethod
|
|
1058
|
+
def nb_hdbt(self) -> int:
|
|
1059
|
+
'''number of HDBaseT inputs and outputs'''
|
|
1060
|
+
|
|
1061
|
+
@property
|
|
1062
|
+
@abstractmethod
|
|
1063
|
+
def registry(self) -> 'DeviceRegistry':
|
|
1064
|
+
'''local device registry'''
|
|
1065
|
+
|
|
1066
|
+
@property
|
|
1067
|
+
@abstractmethod
|
|
1068
|
+
def is_v2ip(self) -> bool:
|
|
1069
|
+
'''True if this a OneIP device'''
|
|
1070
|
+
|
|
1071
|
+
@property
|
|
1072
|
+
@abstractmethod
|
|
1073
|
+
def has_local_source(self) -> bool:
|
|
1074
|
+
'''True if this device has at least 1 local source'''
|
|
1075
|
+
|
|
1076
|
+
@property
|
|
1077
|
+
@abstractmethod
|
|
1078
|
+
def has_local_sink(self) -> bool:
|
|
1079
|
+
'''True if this device has at least 1 local sink'''
|
|
1080
|
+
|
|
1081
|
+
@property
|
|
1082
|
+
@abstractmethod
|
|
1083
|
+
def is_video_matrix(self) -> bool:
|
|
1084
|
+
'''True if this device supports video matrixing'''
|
|
1085
|
+
|
|
1086
|
+
@property
|
|
1087
|
+
@abstractmethod
|
|
1088
|
+
def is_audio_matrix(self) -> bool:
|
|
1089
|
+
'''True if thie device supports audio matrixing'''
|
|
1090
|
+
|
|
1091
|
+
@property
|
|
1092
|
+
@abstractmethod
|
|
1093
|
+
def temperatures(self) -> dict[str,int]:
|
|
1094
|
+
''' temperature sensor reports '''
|
|
1095
|
+
|
|
1096
|
+
@property
|
|
1097
|
+
@abstractmethod
|
|
1098
|
+
def v2ip_sources(self) -> list[V2IPStreamSources]:
|
|
1099
|
+
'''V2IP stream source addresses'''
|
|
1100
|
+
|
|
1101
|
+
@property
|
|
1102
|
+
@abstractmethod
|
|
1103
|
+
def v2ip_stats(self) -> V2IPDeviceStats:
|
|
1104
|
+
'''V2IP encoder/decoder statistics'''
|
|
1105
|
+
|
|
1106
|
+
@property
|
|
1107
|
+
@abstractmethod
|
|
1108
|
+
def v2ip_details(self) -> DeviceV2IPDetailsBase:
|
|
1109
|
+
'''V2IP encoder/decoder configuration'''
|
|
1110
|
+
|
|
1111
|
+
@property
|
|
1112
|
+
@abstractmethod
|
|
1113
|
+
def v2ip_source_local(self) -> V2IPStreamSources|None:
|
|
1114
|
+
''' local v2ip source addresses '''
|
|
1115
|
+
|
|
1116
|
+
@property
|
|
1117
|
+
@abstractmethod
|
|
1118
|
+
def mesh_master(self) -> 'DeviceBase':
|
|
1119
|
+
'''The device that is the master device in the V2IP mesh to which this device belongs'''
|
|
1120
|
+
|
|
1121
|
+
@mesh_master.setter
|
|
1122
|
+
@abstractmethod
|
|
1123
|
+
def mesh_master(self, master:MxrDeviceUid) -> None:
|
|
1124
|
+
'''Change the master device of this device'''
|
|
1125
|
+
|
|
1126
|
+
@property
|
|
1127
|
+
@abstractmethod
|
|
1128
|
+
def is_mesh_master(self) -> bool:
|
|
1129
|
+
'''True if this device is the master device of a V2IP mesh'''
|
|
1130
|
+
|
|
1131
|
+
@property
|
|
1132
|
+
@abstractmethod
|
|
1133
|
+
def dolby_settings(self) -> AmpDolbySettings|None:
|
|
1134
|
+
'''Dolby Digital settings (proamp)'''
|
|
1135
|
+
|
|
1136
|
+
@abstractmethod
|
|
1137
|
+
def v2ip_source(self, bay:BayBase) -> V2IPStreamSources|None:
|
|
1138
|
+
'''Get the V2IP source addresses for the given bay'''
|
|
1139
|
+
|
|
1140
|
+
@abstractmethod
|
|
1141
|
+
def get_by_portnum(self, portnum: int) -> BayBase:
|
|
1142
|
+
'''Get the bay with the given number on this device'''
|
|
1143
|
+
|
|
1144
|
+
@abstractmethod
|
|
1145
|
+
def get_by_portname(self, portname: str) -> BayBase:
|
|
1146
|
+
'''Get the bay with the given port name (not user set name) on this device'''
|
|
1147
|
+
|
|
1148
|
+
@property
|
|
1149
|
+
@abstractmethod
|
|
1150
|
+
def network_status(self) -> dict[int, NetworkPortStatus]:
|
|
1151
|
+
'''network status for all ports'''
|
|
1152
|
+
|
|
1153
|
+
@abstractmethod
|
|
1154
|
+
def update_network_status(self, status:NetworkPortStatus):
|
|
1155
|
+
'''internal callback'''
|
|
1156
|
+
|
|
1157
|
+
@abstractmethod
|
|
1158
|
+
def on_link_config_received(self) -> None:
|
|
1159
|
+
'''internal callback'''
|
|
1160
|
+
|
|
1161
|
+
@abstractmethod
|
|
1162
|
+
async def get_api(self, uri:str) -> Any:
|
|
1163
|
+
'''call an HTTP API method and return the result'''
|
|
1164
|
+
|
|
1165
|
+
@abstractmethod
|
|
1166
|
+
def register_callback(self, callback:callable) -> None:
|
|
1167
|
+
'''register a callback, called when the device state changed'''
|
|
1168
|
+
|
|
1169
|
+
@abstractmethod
|
|
1170
|
+
def unregister_callback(self, callback:callable) -> None:
|
|
1171
|
+
'''unregister a callback'''
|
|
1172
|
+
|
|
1173
|
+
@abstractmethod
|
|
1174
|
+
async def reboot(self) -> bool:
|
|
1175
|
+
'''reboot this device'''
|
|
1176
|
+
|
|
1177
|
+
@abstractmethod
|
|
1178
|
+
async def mesh_promote(self) -> bool:
|
|
1179
|
+
'''promote to mesh master'''
|
|
1180
|
+
|
|
1181
|
+
@abstractmethod
|
|
1182
|
+
async def mesh_remove(self) -> bool:
|
|
1183
|
+
'''remove from mesh'''
|
|
1184
|
+
|
|
1185
|
+
@abstractmethod
|
|
1186
|
+
async def read_stats(self, enable:bool) -> bool:
|
|
1187
|
+
'''start or stop dumping stats'''
|
|
1188
|
+
|
|
1189
|
+
@abstractmethod
|
|
1190
|
+
async def get_log(self) -> str|None:
|
|
1191
|
+
'''read the log from the device and return it as string'''
|
|
1192
|
+
|
|
1193
|
+
class BayLink:
|
|
1194
|
+
''' a virtual mx_remote link between bays, like an amp output that's linked to a oneip sink '''
|
|
1195
|
+
|
|
1196
|
+
def __init__(self, registry:'DeviceRegistry', bay:BayBase, linked_serial:str, linked_bay:str, features:int) -> None:
|
|
1197
|
+
self._bay = bay
|
|
1198
|
+
self._registry = registry
|
|
1199
|
+
self._linked_serial = linked_serial
|
|
1200
|
+
self._linked_bay = linked_bay
|
|
1201
|
+
self._features = features
|
|
1202
|
+
|
|
1203
|
+
@property
|
|
1204
|
+
def serial(self) -> str:
|
|
1205
|
+
''' serial number of the linked device '''
|
|
1206
|
+
return self._linked_serial
|
|
1207
|
+
|
|
1208
|
+
@property
|
|
1209
|
+
def linked_bay_name(self) -> str:
|
|
1210
|
+
''' bay name of the linked bay '''
|
|
1211
|
+
return self._linked_bay
|
|
1212
|
+
|
|
1213
|
+
@property
|
|
1214
|
+
def bay(self) -> BayBase:
|
|
1215
|
+
''' origin bay '''
|
|
1216
|
+
return self._bay
|
|
1217
|
+
|
|
1218
|
+
@property
|
|
1219
|
+
def linked_bay(self) -> BayBase|None:
|
|
1220
|
+
''' linked bay '''
|
|
1221
|
+
if not self.linked:
|
|
1222
|
+
return None
|
|
1223
|
+
return self._registry.get_bay_by_portname(remote_id=self.serial, portname=self.linked_bay_name)
|
|
1224
|
+
|
|
1225
|
+
@property
|
|
1226
|
+
def linked(self) -> bool:
|
|
1227
|
+
''' True if a link has been set up '''
|
|
1228
|
+
return (len(self.serial) != 0) and (len(self.linked_bay_name) != 0)
|
|
1229
|
+
|
|
1230
|
+
@property
|
|
1231
|
+
def other_link(self) -> 'BayLink|None':
|
|
1232
|
+
''' the link instance of the linked bay '''
|
|
1233
|
+
return self._registry.links.get(self.linked_bay)
|
|
1234
|
+
|
|
1235
|
+
@property
|
|
1236
|
+
def connected(self) -> bool:
|
|
1237
|
+
''' True if both sides have been set up '''
|
|
1238
|
+
other_link = self.other_link
|
|
1239
|
+
return (other_link is not None) and (other_link.linked) and (other_link.serial == self.bay.device.serial) and (other_link.linked_bay_name == self.bay.bay_name)
|
|
1240
|
+
|
|
1241
|
+
@property
|
|
1242
|
+
def online(self) -> bool:
|
|
1243
|
+
''' True if both sides are online '''
|
|
1244
|
+
if self.connected:
|
|
1245
|
+
return self.bay.device.online and self.other_link.bay.device.online
|
|
1246
|
+
return False
|
|
1247
|
+
|
|
1248
|
+
@property
|
|
1249
|
+
def is_audio(self) -> bool:
|
|
1250
|
+
''' True if this is an audio link '''
|
|
1251
|
+
m = self.features_mask
|
|
1252
|
+
return (m & MX_LINK_FEATURE_AUDIO_OPTICAL) != 0 or \
|
|
1253
|
+
(m & MX_LINK_FEATURE_AUDIO_ANALOG) != 0
|
|
1254
|
+
|
|
1255
|
+
@property
|
|
1256
|
+
def is_video(self) -> bool:
|
|
1257
|
+
''' True if this is a video link '''
|
|
1258
|
+
m = self.features_mask
|
|
1259
|
+
return (m & MX_LINK_FEATURE_VIDEO_HDMI) != 0
|
|
1260
|
+
|
|
1261
|
+
@property
|
|
1262
|
+
def features(self) -> list[str]:
|
|
1263
|
+
''' supported link features as list of string '''
|
|
1264
|
+
ft = []
|
|
1265
|
+
m = self.features_mask
|
|
1266
|
+
if (m & MX_LINK_FEATURE_VIDEO_HDMI):
|
|
1267
|
+
ft.append("HDMI")
|
|
1268
|
+
if (m & MX_LINK_FEATURE_AUDIO_OPTICAL):
|
|
1269
|
+
ft.append("optical audio")
|
|
1270
|
+
if (m & MX_LINK_FEATURE_AUDIO_ANALOG):
|
|
1271
|
+
ft.append("analog audio")
|
|
1272
|
+
if (m & MX_LINK_FEATURE_IR):
|
|
1273
|
+
ft.append("IR")
|
|
1274
|
+
if (m & MX_LINK_FEATURE_RC):
|
|
1275
|
+
ft.append("RC")
|
|
1276
|
+
return ft
|
|
1277
|
+
|
|
1278
|
+
@property
|
|
1279
|
+
def features_mask(self) -> int:
|
|
1280
|
+
''' supported link features as bitmask '''
|
|
1281
|
+
if not self.connected:
|
|
1282
|
+
return 0
|
|
1283
|
+
left = self.bay.features_mask
|
|
1284
|
+
right = self.linked_bay.features_mask
|
|
1285
|
+
rv = 0
|
|
1286
|
+
if (left & MX_BAY_FEATURE_HDMI_OUT):
|
|
1287
|
+
if (right & MX_BAY_FEATURE_HDMI_IN):
|
|
1288
|
+
rv |= MX_LINK_FEATURE_VIDEO_HDMI
|
|
1289
|
+
if (left & MX_BAY_FEATURE_HDMI_IN):
|
|
1290
|
+
if (right & MX_BAY_FEATURE_HDMI_OUT):
|
|
1291
|
+
rv |= MX_LINK_FEATURE_VIDEO_HDMI
|
|
1292
|
+
if (left & MX_BAY_FEATURE_AUDIO_DIG_OUT):
|
|
1293
|
+
if (right & MX_BAY_FEATURE_AUDIO_DIG_IN):
|
|
1294
|
+
rv |= MX_LINK_FEATURE_AUDIO_OPTICAL
|
|
1295
|
+
if (left & MX_BAY_FEATURE_AUDIO_DIG_IN):
|
|
1296
|
+
if (right & MX_BAY_FEATURE_AUDIO_DIG_OUT):
|
|
1297
|
+
rv |= MX_LINK_FEATURE_AUDIO_OPTICAL
|
|
1298
|
+
if (left & MX_BAY_FEATURE_AUDIO_ANA_OUT):
|
|
1299
|
+
if (right & MX_BAY_FEATURE_AUDIO_ANA_IN):
|
|
1300
|
+
rv |= MX_LINK_FEATURE_AUDIO_ANALOG
|
|
1301
|
+
if (left & MX_BAY_FEATURE_AUDIO_ANA_IN):
|
|
1302
|
+
if (right & MX_BAY_FEATURE_AUDIO_ANA_OUT):
|
|
1303
|
+
rv |= MX_LINK_FEATURE_AUDIO_ANALOG
|
|
1304
|
+
if (left & MX_BAY_FEATURE_IR_OUT):
|
|
1305
|
+
if (right & MX_BAY_FEATURE_IR_IN):
|
|
1306
|
+
rv |= MX_LINK_FEATURE_IR
|
|
1307
|
+
if (left & MX_BAY_FEATURE_IR_IN):
|
|
1308
|
+
if (right & MX_BAY_FEATURE_IR_OUT):
|
|
1309
|
+
rv |= MX_LINK_FEATURE_IR
|
|
1310
|
+
if (left & MX_BAY_FEATURE_RC_OUT):
|
|
1311
|
+
if (right & MX_BAY_FEATURE_RC_IN):
|
|
1312
|
+
rv |= MX_LINK_FEATURE_RC
|
|
1313
|
+
if (left & MX_BAY_FEATURE_RC_IN):
|
|
1314
|
+
if (right & MX_BAY_FEATURE_RC_OUT):
|
|
1315
|
+
rv |= MX_LINK_FEATURE_RC
|
|
1316
|
+
return rv
|
|
1317
|
+
|
|
1318
|
+
def __eq__(self, value: object) -> bool:
|
|
1319
|
+
if not isinstance(value, BayLink):
|
|
1320
|
+
return False
|
|
1321
|
+
return (self.serial == value.serial) and (self.bay == value.bay) and (self.features == value.features)
|
|
1322
|
+
|
|
1323
|
+
def __str__(self) -> str:
|
|
1324
|
+
return f"{self.serial}:{self.bay}:{self.features}"
|
|
1325
|
+
|
|
1326
|
+
def __hash__(self) -> int:
|
|
1327
|
+
return hash(str(self))
|
|
1328
|
+
|
|
1329
|
+
class BayLinks:
|
|
1330
|
+
''' linked bay configurations for all devices '''
|
|
1331
|
+
|
|
1332
|
+
def __init__(self, registry:'DeviceRegistry') -> None:
|
|
1333
|
+
self._registry = registry
|
|
1334
|
+
self._links:dict[MxrBayUid, BayLink] = {}
|
|
1335
|
+
|
|
1336
|
+
@property
|
|
1337
|
+
def callbacks(self) -> 'MxrCallbacks':
|
|
1338
|
+
return self._registry.callbacks
|
|
1339
|
+
|
|
1340
|
+
@property
|
|
1341
|
+
def registry(self) -> 'DeviceRegistry':
|
|
1342
|
+
return self._registry
|
|
1343
|
+
|
|
1344
|
+
def _on_link(self, bay:BayBase, new_link:BayLink) -> None:
|
|
1345
|
+
if new_link.linked:
|
|
1346
|
+
self.callbacks.on_bay_linked(bay, new_link.serial, new_link.bay, new_link.features),
|
|
1347
|
+
other_bay = new_link.linked_bay
|
|
1348
|
+
if other_bay is not None:
|
|
1349
|
+
self.callbacks.on_bay_linked(other_bay, bay.device.serial, bay.bay_name, new_link.features)
|
|
1350
|
+
|
|
1351
|
+
def is_primary(self, bay:BayBase) -> bool:
|
|
1352
|
+
if not bay.bay_uid in self._links.keys():
|
|
1353
|
+
return True
|
|
1354
|
+
link = self._links[bay.bay_uid]
|
|
1355
|
+
other_bay = link.linked_bay
|
|
1356
|
+
if other_bay is None:
|
|
1357
|
+
return True
|
|
1358
|
+
if bay.device.is_amp != other_bay.device.is_amp:
|
|
1359
|
+
return bay.device.is_amp
|
|
1360
|
+
return str(bay.device.remote_id) < str(other_bay.device.remote_id)
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def update(self, bay:BayBase, linked_serial:str, linked_bay:str, features:int) -> None:
|
|
1364
|
+
new_link = BayLink(bay=bay, registry=self.registry, linked_serial=linked_serial, linked_bay=linked_bay, features=features)
|
|
1365
|
+
if bay.bay_uid in self._links.keys():
|
|
1366
|
+
old = self._links[bay.bay_uid]
|
|
1367
|
+
if old != new_link:
|
|
1368
|
+
if old.linked:
|
|
1369
|
+
self.callbacks.on_bay_unlinked(bay, old.serial, old.bay)
|
|
1370
|
+
old_bay = old.linked_bay
|
|
1371
|
+
if old_bay is not None:
|
|
1372
|
+
self.callbacks.on_bay_unlinked(old_bay, bay.device.serial, bay.bay_name)
|
|
1373
|
+
self._on_link(bay=bay, new_link=new_link)
|
|
1374
|
+
self._links[bay.bay_uid] = new_link
|
|
1375
|
+
else:
|
|
1376
|
+
self._on_link(bay=bay, new_link=new_link)
|
|
1377
|
+
self._links[bay.bay_uid] = new_link
|
|
1378
|
+
|
|
1379
|
+
def get(self, bay:BayBase|None) -> BayLink|None:
|
|
1380
|
+
if bay is None:
|
|
1381
|
+
return None
|
|
1382
|
+
if bay.bay_uid in self._links.keys():
|
|
1383
|
+
return self._links[bay.bay_uid]
|
|
1384
|
+
return None
|
|
1385
|
+
|
|
1386
|
+
class DeviceRegistry(ABC):
|
|
1387
|
+
''' all mx_remote devices on the network '''
|
|
1388
|
+
|
|
1389
|
+
@property
|
|
1390
|
+
@abstractmethod
|
|
1391
|
+
def local_ip(self) -> str:
|
|
1392
|
+
'''local ip address'''
|
|
1393
|
+
|
|
1394
|
+
@property
|
|
1395
|
+
@abstractmethod
|
|
1396
|
+
def broadcast(self) -> bool:
|
|
1397
|
+
'''broadcast or multicast'''
|
|
1398
|
+
|
|
1399
|
+
@property
|
|
1400
|
+
@abstractmethod
|
|
1401
|
+
def library_version(self) -> str:
|
|
1402
|
+
''' version of the mx_remote library '''
|
|
1403
|
+
|
|
1404
|
+
@property
|
|
1405
|
+
@abstractmethod
|
|
1406
|
+
def protocol_version(self) -> int:
|
|
1407
|
+
''' protocol version used by this library '''
|
|
1408
|
+
|
|
1409
|
+
@property
|
|
1410
|
+
@abstractmethod
|
|
1411
|
+
def net_protocol_version_max(self) -> int:
|
|
1412
|
+
''' highest protocol version used by devices on the network '''
|
|
1413
|
+
|
|
1414
|
+
@property
|
|
1415
|
+
@abstractmethod
|
|
1416
|
+
def net_protocol_version_min(self) -> int:
|
|
1417
|
+
''' lowest protocol version used by devices on the network '''
|
|
1418
|
+
|
|
1419
|
+
@property
|
|
1420
|
+
@abstractmethod
|
|
1421
|
+
def uid_raw(self) -> bytes:
|
|
1422
|
+
''' uid of this device as bytes '''
|
|
1423
|
+
|
|
1424
|
+
@property
|
|
1425
|
+
@abstractmethod
|
|
1426
|
+
def uid(self) -> MxrDeviceUid:
|
|
1427
|
+
''' uid of this device '''
|
|
1428
|
+
|
|
1429
|
+
@property
|
|
1430
|
+
@abstractmethod
|
|
1431
|
+
def name(self) -> str:
|
|
1432
|
+
''' device name '''
|
|
1433
|
+
|
|
1434
|
+
@property
|
|
1435
|
+
@abstractmethod
|
|
1436
|
+
def callbacks(self) -> 'MxrCallbacks':
|
|
1437
|
+
''' callbacks to call when the device is updated '''
|
|
1438
|
+
|
|
1439
|
+
@abstractmethod
|
|
1440
|
+
def transmit(self, data: bytes) -> int:
|
|
1441
|
+
''' transmit data to this device (broadcast/multicast) '''
|
|
1442
|
+
|
|
1443
|
+
@property
|
|
1444
|
+
@abstractmethod
|
|
1445
|
+
def links(self) -> BayLinks:
|
|
1446
|
+
''' linked bay configurations for all devices '''
|
|
1447
|
+
|
|
1448
|
+
@abstractmethod
|
|
1449
|
+
def get_by_serial(self, serial:str) -> DeviceBase|None:
|
|
1450
|
+
''' get a device by its serial number '''
|
|
1451
|
+
|
|
1452
|
+
@abstractmethod
|
|
1453
|
+
def get_by_uid(self, remote_id:str|MxrDeviceUid) -> DeviceBase|None:
|
|
1454
|
+
''' get a device by its unique id '''
|
|
1455
|
+
|
|
1456
|
+
@abstractmethod
|
|
1457
|
+
def get_bay_by_portnum(self, remote_id:str|MxrDeviceUid, portnum:int) -> BayBase|None:
|
|
1458
|
+
''' get a bay of a device by its unique id and port number '''
|
|
1459
|
+
|
|
1460
|
+
@abstractmethod
|
|
1461
|
+
def get_bay_by_portname(self, remote_id:str|MxrDeviceUid, portname:str) -> BayBase|None:
|
|
1462
|
+
''' get a bay of a device by its unique id and port name '''
|
|
1463
|
+
|
|
1464
|
+
@abstractmethod
|
|
1465
|
+
def get_by_stream_ip(self, ip:str, audio:bool=False) -> BayBase|None:
|
|
1466
|
+
''' get a bay of a device by its V2IP stream address '''
|
|
1467
|
+
|
|
1468
|
+
class ConnectionCallbacks(ABC):
|
|
1469
|
+
@property
|
|
1470
|
+
@abstractmethod
|
|
1471
|
+
def target_ip(self) -> str:
|
|
1472
|
+
'''target ip address'''
|
|
1473
|
+
|
|
1474
|
+
@abstractmethod
|
|
1475
|
+
def on_connection_made(self) -> None:
|
|
1476
|
+
'''called when the socket was opened'''
|
|
1477
|
+
|
|
1478
|
+
@abstractmethod
|
|
1479
|
+
def on_datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
|
1480
|
+
'''called when a datagram was received'''
|
|
1481
|
+
|
|
1482
|
+
class MxrCallbacks:
|
|
1483
|
+
''' callbacks that can be used by an external application to get notified when a status changes '''
|
|
1484
|
+
|
|
1485
|
+
def on_device_update(self, dev:DeviceBase) -> None:
|
|
1486
|
+
''' called when properties of 'dev' have been updated '''
|
|
1487
|
+
pass
|
|
1488
|
+
|
|
1489
|
+
def on_bay_update(self, bay:BayBase) -> None:
|
|
1490
|
+
''' called when properties of 'bay' have been updated '''
|
|
1491
|
+
pass
|
|
1492
|
+
|
|
1493
|
+
def on_device_config_changed(self, dev:DeviceBase) -> None:
|
|
1494
|
+
''' called when device configuration properties of 'dev' have been updated '''
|
|
1495
|
+
self.on_device_update(dev)
|
|
1496
|
+
|
|
1497
|
+
def on_device_config_complete(self, dev:DeviceBase) -> None:
|
|
1498
|
+
''' called when device configuration of 'dev' had been received fully '''
|
|
1499
|
+
_LOGGER.debug(f"{dev} configuration complete")
|
|
1500
|
+
self.on_device_update(dev)
|
|
1501
|
+
|
|
1502
|
+
def on_device_online_status_changed(self, dev:DeviceBase, online:bool) -> None:
|
|
1503
|
+
''' called when the online status of 'dev' changed '''
|
|
1504
|
+
_LOGGER.debug(f"{dev} online status changed to {online}")
|
|
1505
|
+
self.on_device_update(dev)
|
|
1506
|
+
|
|
1507
|
+
def on_bay_registered(self, bay:BayBase) -> None:
|
|
1508
|
+
''' called when a new bay was registered by mx_remote '''
|
|
1509
|
+
_LOGGER.debug(f"{bay} registered: {bay.features}")
|
|
1510
|
+
self.on_bay_update(bay)
|
|
1511
|
+
|
|
1512
|
+
def on_device_temperature_changed(self, dev:DeviceBase) -> None:
|
|
1513
|
+
''' called when the temperature values of 'dev' changed '''
|
|
1514
|
+
_LOGGER.debug(f"{dev} temperature: {dev.temperatures}")
|
|
1515
|
+
self.on_device_update(dev)
|
|
1516
|
+
|
|
1517
|
+
def on_power_changed(self, bay:BayBase, power:str) -> None:
|
|
1518
|
+
''' called when the power status of 'bay' changed '''
|
|
1519
|
+
_LOGGER.debug(f"{bay} power status {power}")
|
|
1520
|
+
self.on_bay_update(bay)
|
|
1521
|
+
|
|
1522
|
+
def on_name_changed(self, bay:BayBase, user_name:str) -> None:
|
|
1523
|
+
''' called when the name that's set up by the user of 'bay' changed '''
|
|
1524
|
+
_LOGGER.debug(f"{bay} name changed: {user_name}")
|
|
1525
|
+
self.on_bay_update(bay)
|
|
1526
|
+
|
|
1527
|
+
def on_status_signal_detected_changed(self, bay:BayBase, val:bool) -> None:
|
|
1528
|
+
''' called when the signal detect status of 'bay' changed '''
|
|
1529
|
+
lval = "signal detected" if val else "no signal"
|
|
1530
|
+
_LOGGER.debug(f"{bay} {lval}")
|
|
1531
|
+
self.on_bay_update(bay)
|
|
1532
|
+
|
|
1533
|
+
def on_status_faulty_changed(self, bay:BayBase, val:bool) -> None:
|
|
1534
|
+
''' called when the fault status of 'bay' changed '''
|
|
1535
|
+
lval = "FAULT" if val else "healthy"
|
|
1536
|
+
_LOGGER.debug(f"{bay} {lval}")
|
|
1537
|
+
self.on_bay_update(bay)
|
|
1538
|
+
|
|
1539
|
+
def on_status_hidden_changed(self, bay:BayBase, val:bool) -> None:
|
|
1540
|
+
''' called when the hidden status of 'bay' changed '''
|
|
1541
|
+
lval = "hidden" if val else "visible"
|
|
1542
|
+
_LOGGER.debug(f"{bay} {lval}")
|
|
1543
|
+
self.on_bay_update(bay)
|
|
1544
|
+
|
|
1545
|
+
def on_status_poe_powered_changed(self, bay:BayBase, val:bool) -> None:
|
|
1546
|
+
''' called when the PoE power status of 'bay' changed '''
|
|
1547
|
+
lval = "on" if val else "off"
|
|
1548
|
+
_LOGGER.debug(f"{bay} PoE {lval}")
|
|
1549
|
+
self.on_bay_update(bay)
|
|
1550
|
+
|
|
1551
|
+
def on_status_hdbt_connected_changed(self, bay:BayBase, val:bool) -> None:
|
|
1552
|
+
''' called when the HDBaseT connection status of 'bay' changed '''
|
|
1553
|
+
lval = "up" if val else "down"
|
|
1554
|
+
_LOGGER.debug(f"{bay} HDBaseT link {lval}")
|
|
1555
|
+
self.on_bay_update(bay)
|
|
1556
|
+
|
|
1557
|
+
def on_status_signal_type_changed(self, bay:BayBase, val:str) -> None:
|
|
1558
|
+
''' called when the detected signal of 'bay' changed '''
|
|
1559
|
+
_LOGGER.debug(f"{bay} signal type: {val}")
|
|
1560
|
+
self.on_bay_update(bay)
|
|
1561
|
+
|
|
1562
|
+
def on_status_hpd_detected_changed(self, bay:BayBase, val:bool) -> None:
|
|
1563
|
+
''' called when the HPD value of 'bay' changed '''
|
|
1564
|
+
lval = "detected" if val else "lost"
|
|
1565
|
+
_LOGGER.debug(f"{bay} hotplug {lval}")
|
|
1566
|
+
self.on_bay_update(bay)
|
|
1567
|
+
|
|
1568
|
+
def on_status_cec_detected_changed(self, bay:BayBase, val: bool) -> None:
|
|
1569
|
+
''' called when a CEC device was detected on 'bay' '''
|
|
1570
|
+
lval = "detected" if val else "not found"
|
|
1571
|
+
_LOGGER.debug(f"{bay} HDMI-CEC device {lval}")
|
|
1572
|
+
self.on_bay_update(bay)
|
|
1573
|
+
|
|
1574
|
+
def on_status_arc_changed(self, bay:BayBase, val:str) -> None:
|
|
1575
|
+
''' called when the audio return channel status of 'bay' changed '''
|
|
1576
|
+
_LOGGER.info(f"{bay} ARC: {val}")
|
|
1577
|
+
self.on_bay_update(bay)
|
|
1578
|
+
|
|
1579
|
+
def on_volume_changed(self, bay:BayBase, volume:VolumeMuteStatus) -> None:
|
|
1580
|
+
''' called when the volume/mute status of 'bay' changed '''
|
|
1581
|
+
muted_str = ""
|
|
1582
|
+
volume_str = ""
|
|
1583
|
+
if volume.muted is not None:
|
|
1584
|
+
muted_str = " not muted" if not volume.muted else " muted"
|
|
1585
|
+
if volume.volume is not None:
|
|
1586
|
+
volume_str = " volume {}%".format(volume.volume)
|
|
1587
|
+
_LOGGER.debug(f"{bay}{volume_str}{muted_str}")
|
|
1588
|
+
self.on_bay_update(bay)
|
|
1589
|
+
|
|
1590
|
+
def on_key_pressed(self, bay:BayBase, key:RCKey) -> None:
|
|
1591
|
+
''' called when a key press was detected on 'bay' '''
|
|
1592
|
+
_LOGGER.debug(f"{bay} key pressed: {key}")
|
|
1593
|
+
|
|
1594
|
+
def on_action_received(self, bay:BayBase, action:RCAction) -> None:
|
|
1595
|
+
''' called when a remote control action was detected on 'bay' '''
|
|
1596
|
+
_LOGGER.debug(f"{bay} action: {action}")
|
|
1597
|
+
|
|
1598
|
+
def on_video_source_changed(self, bay:BayBase, video_source:BayBase) -> None:
|
|
1599
|
+
''' called when a video source changed was detected on 'bay' '''
|
|
1600
|
+
_LOGGER.debug(f"{bay} video routed to {video_source}")
|
|
1601
|
+
self.on_bay_update(bay)
|
|
1602
|
+
|
|
1603
|
+
def on_audio_source_changed(self, bay:BayBase, audio_source:BayBase) -> None:
|
|
1604
|
+
''' called when an audio source changed was detected on 'bay' '''
|
|
1605
|
+
_LOGGER.debug(f"{bay} audio routed to {audio_source}")
|
|
1606
|
+
self.on_bay_update(bay)
|
|
1607
|
+
|
|
1608
|
+
def on_pdu_registered(self, pdu:PDUState) -> None:
|
|
1609
|
+
''' called when a Pulse-Eight PDU was detected that's connected to an mx_remote device '''
|
|
1610
|
+
_LOGGER.debug(f"{pdu.dev} pdu registered: {pdu}")
|
|
1611
|
+
|
|
1612
|
+
def on_pdu_changed(self, pdu:PDUState) -> None:
|
|
1613
|
+
''' called when a state of 'pdu' changed '''
|
|
1614
|
+
_LOGGER.debug(f"{pdu.dev} pdu: {pdu}")
|
|
1615
|
+
|
|
1616
|
+
def on_bay_linked(self, bay:BayBase, linked_serial:str, linked_bay:str, features:int) -> None:
|
|
1617
|
+
''' called when a bay link was detected '''
|
|
1618
|
+
_LOGGER.debug(f"{bay} linked to {linked_serial}:{linked_bay}")
|
|
1619
|
+
self.on_device_update(bay.device)
|
|
1620
|
+
self.on_bay_update(bay)
|
|
1621
|
+
|
|
1622
|
+
def on_bay_unlinked(self, bay:BayBase, linked_serial:str, linked_bay:str) -> None:
|
|
1623
|
+
''' called when a bay link was removed '''
|
|
1624
|
+
_LOGGER.debug(f"{bay} unlinked from {linked_serial}:{linked_bay}")
|
|
1625
|
+
self.on_device_update(bay.device)
|
|
1626
|
+
self.on_bay_update(bay)
|
|
1627
|
+
|
|
1628
|
+
def on_mirror_status_changed(self, bay:BayBase, mirror:MxrDeviceUid|None) -> None:
|
|
1629
|
+
''' called when a bay mirroring setup change was detected '''
|
|
1630
|
+
_LOGGER.debug(f"{bay} mirror {mirror}")
|
|
1631
|
+
self.on_bay_update(bay)
|
|
1632
|
+
|
|
1633
|
+
def on_filter_status_changed(self, bay:BayBase, filtered:list[MxrDeviceUid]) -> None:
|
|
1634
|
+
''' called when a bay filtering setup change was detected '''
|
|
1635
|
+
_LOGGER.debug(f"{bay} filtered {filtered}")
|
|
1636
|
+
self.on_bay_update(bay)
|
|
1637
|
+
|
|
1638
|
+
def on_edid_profile_changed(self, bay:BayBase, profile:EdidProfile) -> None:
|
|
1639
|
+
''' called when a source EDID profile was changed '''
|
|
1640
|
+
_LOGGER.debug(f"{bay} edid profile changed to {profile}")
|
|
1641
|
+
self.on_bay_update(bay)
|
|
1642
|
+
|
|
1643
|
+
def on_rc_type_changed(self, bay:BayBase, rc_type:RCType) -> None:
|
|
1644
|
+
''' called when a source remote control type was changed '''
|
|
1645
|
+
_LOGGER.debug(f"{bay} rc type changed to {rc_type}")
|
|
1646
|
+
self.on_bay_update(bay)
|
|
1647
|
+
|
|
1648
|
+
def on_amp_zone_settings_changed(self, bay:BayBase, settings:AmpZoneSettings) -> None:
|
|
1649
|
+
''' called when amp zone settings were changed '''
|
|
1650
|
+
_LOGGER.debug(f"{bay} amp zone settings changed")
|
|
1651
|
+
self.on_bay_update(bay)
|
|
1652
|
+
|
|
1653
|
+
def on_amp_dolby_settings_changed(self, device:DeviceBase, settings:AmpDolbySettings) -> None:
|
|
1654
|
+
''' called when amp dolby settings were changed '''
|
|
1655
|
+
_LOGGER.debug(f"{device} dolby settings changed")
|
|
1656
|
+
self.on_device_update(device)
|