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/remote/Bay.py
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
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 __future__ import annotations
|
|
9
|
+
import mx_remote.proto as proto
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
from ..proto.Constants import BayStatusMask, EdidProfile, RCType, RCAction, RCKey
|
|
13
|
+
from ..proto.BayConfig import BayConfig
|
|
14
|
+
from ..proto.Data import VolumeMuteStatus
|
|
15
|
+
from ..proto.FrameBase import FrameBase
|
|
16
|
+
from ..proto.FrameV2IPSourceSwitch import FrameV2IPSourceSwitch
|
|
17
|
+
from ..proto.FrameEDIDProfile import FrameEDIDProfile
|
|
18
|
+
from ..proto.FrameBayHide import FrameBayHide
|
|
19
|
+
from ..proto.FrameSetName import FrameSetName
|
|
20
|
+
from ..Interface import BayBase, DeviceBase, BayLink, MxrCallbacks, V2IPStreamSources, AmpZoneSettings, DeviceStatus
|
|
21
|
+
from ..Uid import MxrBayUid
|
|
22
|
+
|
|
23
|
+
_LOGGER = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
class Bay(BayBase):
|
|
26
|
+
ARC_NONE = 'Inactive'
|
|
27
|
+
ARC_HDMI = 'HDMI'
|
|
28
|
+
ARC_OPTICAL = 'optical'
|
|
29
|
+
ARC_ANALOG = 'analog'
|
|
30
|
+
|
|
31
|
+
def __init__(self, dev:DeviceBase, data:BayConfig) -> None:
|
|
32
|
+
self._dev = dev
|
|
33
|
+
self._port_number = data.port
|
|
34
|
+
self._port_name = data.bay_name
|
|
35
|
+
self._user_name = None
|
|
36
|
+
self._features = data.features
|
|
37
|
+
self._mbay_id = None
|
|
38
|
+
self._video_source = None
|
|
39
|
+
self._audio_source = None
|
|
40
|
+
self._power_status = None
|
|
41
|
+
self._faulty = None
|
|
42
|
+
self._hidden = None
|
|
43
|
+
self._poe_powered = None
|
|
44
|
+
self._hdbt_connected = None
|
|
45
|
+
self._signal_detected = None
|
|
46
|
+
self._signal_type = None
|
|
47
|
+
self._hpd_detected = None
|
|
48
|
+
self._cec_detected = None
|
|
49
|
+
self._arc = self.ARC_NONE
|
|
50
|
+
self._audio_volume = None
|
|
51
|
+
self._rc_type = None
|
|
52
|
+
self._edid_profile = None
|
|
53
|
+
self._mirror = None
|
|
54
|
+
self._decoder_disabled = None
|
|
55
|
+
self._encoder_disabled = None
|
|
56
|
+
self._status_mask = data.status
|
|
57
|
+
self._amp_settings:AmpZoneSettings|None = None
|
|
58
|
+
self._filtered = []
|
|
59
|
+
self._bay_callbacks:list[callable] = []
|
|
60
|
+
|
|
61
|
+
def register_callback(self, callback:callable) -> None:
|
|
62
|
+
'''register a callback, called when the device state changed'''
|
|
63
|
+
self._bay_callbacks.append(callback)
|
|
64
|
+
|
|
65
|
+
def unregister_callback(self, callback:callable) -> None:
|
|
66
|
+
'''unregister a callback'''
|
|
67
|
+
if callback in self._bay_callbacks:
|
|
68
|
+
self._bay_callbacks.remove(callback)
|
|
69
|
+
|
|
70
|
+
def call_callbacks(self) -> None:
|
|
71
|
+
for callback in self._bay_callbacks:
|
|
72
|
+
callback(self)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def rebooting(self) -> bool:
|
|
76
|
+
'''True if rebooting'''
|
|
77
|
+
return self.device.rebooting
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def booting(self) -> bool:
|
|
81
|
+
'''True if booting'''
|
|
82
|
+
return self.device.booting
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def status(self) -> DeviceStatus:
|
|
86
|
+
if self.online:
|
|
87
|
+
if self.rebooting:
|
|
88
|
+
return DeviceStatus.REBOOTING
|
|
89
|
+
if self.booting:
|
|
90
|
+
return DeviceStatus.BOOTING
|
|
91
|
+
if self.status_mask.encoder_disabled or self.status_mask.decoder_disabled:
|
|
92
|
+
return DeviceStatus.INACTIVE
|
|
93
|
+
return DeviceStatus.ONLINE
|
|
94
|
+
return DeviceStatus.OFFLINE
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def v2ip_source(self) -> V2IPStreamSources|None:
|
|
98
|
+
return self.device.v2ip_source(self)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def callbacks(self) -> MxrCallbacks:
|
|
102
|
+
return self.device.callbacks
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def device(self) -> DeviceBase:
|
|
106
|
+
# remote device
|
|
107
|
+
return self._dev
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def bay_uid(self) -> MxrBayUid:
|
|
111
|
+
return MxrBayUid(self.device.remote_id, self.port)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def online(self) -> bool:
|
|
115
|
+
# check whether the device to which this bay belongs is online
|
|
116
|
+
return self.device.online
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def port(self) -> int:
|
|
120
|
+
# port number used for mxremote operations
|
|
121
|
+
return self._port_number
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def bay_name(self) -> str:
|
|
125
|
+
# mbay name
|
|
126
|
+
return self._port_name
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def user_name(self) -> str:
|
|
130
|
+
# name set up by the user
|
|
131
|
+
return self._user_name if (self._user_name is not None) \
|
|
132
|
+
else self.bay_name
|
|
133
|
+
|
|
134
|
+
@user_name.setter
|
|
135
|
+
def user_name(self, val:str) -> None:
|
|
136
|
+
prev = self.user_name
|
|
137
|
+
self._user_name = val
|
|
138
|
+
if (self.user_name != prev):
|
|
139
|
+
self.callbacks.on_name_changed(self, self.user_name)
|
|
140
|
+
self.call_callbacks()
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def has_default_name(self) -> bool:
|
|
144
|
+
# default name set or custom name set
|
|
145
|
+
return (self.user_name == self.bay_name)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def bay_label(self) -> str:
|
|
149
|
+
# bay name + user name, used for logging
|
|
150
|
+
name = self.bay_name
|
|
151
|
+
user_name = self.user_name
|
|
152
|
+
if user_name != name:
|
|
153
|
+
return "{} ({})".format(name, user_name)
|
|
154
|
+
return name
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def features_mask(self) -> int:
|
|
158
|
+
# supported features for this bay (bitmask)
|
|
159
|
+
return self._features if self._features is not None else 0
|
|
160
|
+
|
|
161
|
+
@features_mask.setter
|
|
162
|
+
def features_mask(self, val:int) -> None:
|
|
163
|
+
self._features = val
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_local(self) -> bool:
|
|
167
|
+
return not self.is_v2ip_remote
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def is_v2ip_source(self) -> bool:
|
|
171
|
+
mask = self.features_mask
|
|
172
|
+
return ((mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_REMOTE) != 0) \
|
|
173
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_LOCAL) != 0)
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def is_v2ip_sink(self) -> bool:
|
|
177
|
+
mask = self.features_mask
|
|
178
|
+
return ((mask & proto.MX_BAY_FEATURE_V2IP_SINK_REMOTE) != 0) \
|
|
179
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SINK_LOCAL) != 0)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def is_v2ip_remote_sink(self) -> bool:
|
|
183
|
+
return ((self.features_mask & proto.MX_BAY_FEATURE_V2IP_SINK_REMOTE) != 0)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def is_v2ip_remote_source(self) -> bool:
|
|
187
|
+
return ((self.features_mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_REMOTE) != 0)
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def is_v2ip_remote(self) -> bool:
|
|
191
|
+
return self.is_v2ip_remote_sink or self.is_v2ip_remote_source
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def dolby_input(self) -> int:
|
|
195
|
+
# if dolby mode is set, the input bay that provides the audio source
|
|
196
|
+
features = self.features_mask
|
|
197
|
+
if (features & proto.MX_BAY_FEATURE_DOLBY):
|
|
198
|
+
# TODO fix mx_remote offset
|
|
199
|
+
return 'Input {}'.format('9') #((features >> proto.MX_BAY_FEATURE_DOLBY_IN_POS) & 0xF)
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def dolby_input_bay(self) -> BayBase:
|
|
204
|
+
db = self.dolby_input
|
|
205
|
+
if db is None:
|
|
206
|
+
return None
|
|
207
|
+
return self.device.get_by_portname(db)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def features(self) -> list[str]:
|
|
211
|
+
# list of supported features for this bay
|
|
212
|
+
rv = []
|
|
213
|
+
mask = self.features_mask
|
|
214
|
+
if (mask & proto.MX_BAY_FEATURE_HDMI_OUT):
|
|
215
|
+
rv.append('HDMI output')
|
|
216
|
+
if (mask & proto.MX_BAY_FEATURE_HDMI_IN):
|
|
217
|
+
rv.append('HDMI input')
|
|
218
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_DIG_OUT):
|
|
219
|
+
rv.append('digital audio output')
|
|
220
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_DIG_IN):
|
|
221
|
+
rv.append('digital audio input')
|
|
222
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_ANA_OUT):
|
|
223
|
+
rv.append('analog audio output')
|
|
224
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_ANA_IN):
|
|
225
|
+
rv.append('analog audio input')
|
|
226
|
+
if (mask & proto.MX_BAY_FEATURE_IR_OUT):
|
|
227
|
+
rv.append('IR transmitter')
|
|
228
|
+
if (mask & proto.MX_BAY_FEATURE_IR_IN):
|
|
229
|
+
rv.append('IR receiver')
|
|
230
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_AMP_OUT):
|
|
231
|
+
rv.append('amplifier audio output')
|
|
232
|
+
if (mask & proto.MX_BAY_FEATURE_RC_OUT):
|
|
233
|
+
rv.append('remote control out')
|
|
234
|
+
if (mask & proto.MX_BAY_FEATURE_RC_IN):
|
|
235
|
+
rv.append('remote control in')
|
|
236
|
+
if (mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_REMOTE):
|
|
237
|
+
rv.append('V2IP remote source')
|
|
238
|
+
if (mask & proto.MX_BAY_FEATURE_V2IP_SINK_REMOTE):
|
|
239
|
+
rv.append('V2IP remote sink')
|
|
240
|
+
if (mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_LOCAL):
|
|
241
|
+
rv.append('V2IP source')
|
|
242
|
+
if (mask & proto.MX_BAY_FEATURE_V2IP_SINK_LOCAL):
|
|
243
|
+
rv.append('V2IP sink')
|
|
244
|
+
if (mask & proto.MX_BAY_FEATURE_DOLBY):
|
|
245
|
+
rv.append('dolby')
|
|
246
|
+
if (mask & proto.MX_BAY_FEATURE_AUTO_OFF):
|
|
247
|
+
rv.append('auto off')
|
|
248
|
+
return rv
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def has_volume_control(self) -> bool:
|
|
252
|
+
mask = self.features_mask
|
|
253
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_ANA_OUT):
|
|
254
|
+
return True
|
|
255
|
+
if (mask & proto.MX_BAY_FEATURE_AUDIO_AMP_OUT):
|
|
256
|
+
return True
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def is_input(self) -> bool:
|
|
261
|
+
mask = self.features_mask
|
|
262
|
+
return ((mask & proto.MX_BAY_FEATURE_HDMI_IN) != 0) \
|
|
263
|
+
or ((mask & proto.MX_BAY_FEATURE_AUDIO_DIG_IN) != 0) \
|
|
264
|
+
or ((mask & proto.MX_BAY_FEATURE_AUDIO_ANA_IN) != 0) \
|
|
265
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_REMOTE) != 0) \
|
|
266
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SOURCE_LOCAL) != 0)
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def is_output(self) -> bool:
|
|
270
|
+
mask = self.features_mask
|
|
271
|
+
return ((mask & proto.MX_BAY_FEATURE_HDMI_OUT) != 0) \
|
|
272
|
+
or ((mask & proto.MX_BAY_FEATURE_AUDIO_AMP_OUT) != 0) \
|
|
273
|
+
or ((mask & proto.MX_BAY_FEATURE_AUDIO_DIG_OUT) != 0) \
|
|
274
|
+
or ((mask & proto.MX_BAY_FEATURE_AUDIO_ANA_OUT) != 0) \
|
|
275
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SINK_REMOTE) != 0) \
|
|
276
|
+
or ((mask & proto.MX_BAY_FEATURE_V2IP_SINK_LOCAL) != 0)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def mode(self) -> str:
|
|
280
|
+
# bay mode used by the web api and logging
|
|
281
|
+
if self.is_output:
|
|
282
|
+
return 'Output'
|
|
283
|
+
if self.is_input:
|
|
284
|
+
return 'Input'
|
|
285
|
+
return 'unknown'
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def other_mode(self) -> str:
|
|
289
|
+
# bay mode used by the web api and logging
|
|
290
|
+
if self.is_output:
|
|
291
|
+
return 'Input'
|
|
292
|
+
if self.is_input:
|
|
293
|
+
return 'Output'
|
|
294
|
+
return 'unknown'
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def bay(self) -> int:
|
|
298
|
+
# bay number used by the web api
|
|
299
|
+
return self._mbay_id if (self._mbay_id is not None) \
|
|
300
|
+
else int(self.bay_name[len(self.mode)+1:])
|
|
301
|
+
|
|
302
|
+
@bay.setter
|
|
303
|
+
def bay(self, val:int) -> None:
|
|
304
|
+
if self._mbay_id is None:
|
|
305
|
+
self._mbay_id = val
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def available(self) -> bool:
|
|
309
|
+
if self.faulty or self.hidden or not self.online:
|
|
310
|
+
return False
|
|
311
|
+
if self.is_hdbaset and not self.hdbt_connected:
|
|
312
|
+
return False
|
|
313
|
+
if self.device.is_amp:
|
|
314
|
+
if self.is_output:
|
|
315
|
+
return (self.bay == 0) or (self.bay >= self.device.amp_dolby_channels)
|
|
316
|
+
return (self.bay > self.device.amp_dolby_channels)
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def is_hdmi(self) -> bool:
|
|
321
|
+
# HDMI bay
|
|
322
|
+
mask = self.features_mask
|
|
323
|
+
return ((mask & proto.MX_BAY_FEATURE_HDMI_OUT) != 0) or ((mask & proto.MX_BAY_FEATURE_HDMI_IN) != 0)
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def is_hdbaset(self) -> bool:
|
|
327
|
+
#HDBaseT bay
|
|
328
|
+
# TODO add to proto
|
|
329
|
+
return self.is_hdmi and self.is_output and (self.bay < self.device.nb_hdbt)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def is_audio(self) -> bool:
|
|
333
|
+
# audio bay
|
|
334
|
+
if self.is_hdmi:
|
|
335
|
+
return False
|
|
336
|
+
mask = self.features_mask
|
|
337
|
+
return ((mask & proto.MX_BAY_FEATURE_AUDIO_DIG_OUT) != 0) or ((mask & proto.MX_BAY_FEATURE_AUDIO_DIG_IN) != 0) or \
|
|
338
|
+
((mask & proto.MX_BAY_FEATURE_AUDIO_ANA_OUT) != 0) or ((mask & proto.MX_BAY_FEATURE_AUDIO_ANA_IN) != 0) or \
|
|
339
|
+
((mask & proto.MX_BAY_FEATURE_AUDIO_AMP_OUT) != 0)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def edid_profile(self) -> EdidProfile:
|
|
343
|
+
if not self.is_hdmi or not self.is_input:
|
|
344
|
+
return None
|
|
345
|
+
return EdidProfile(self._edid_profile)
|
|
346
|
+
|
|
347
|
+
@edid_profile.setter
|
|
348
|
+
def edid_profile(self, val:int) -> None:
|
|
349
|
+
if not self.is_hdmi or not self.is_input:
|
|
350
|
+
return
|
|
351
|
+
if ((self._edid_profile is None) or (self._edid_profile != val)):
|
|
352
|
+
self._edid_profile = val
|
|
353
|
+
self.callbacks.on_edid_profile_changed(self, self.edid_profile)
|
|
354
|
+
self.call_callbacks()
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def rc_type(self) -> RCType:
|
|
358
|
+
if not self.is_hdmi or not self.is_input:
|
|
359
|
+
return None
|
|
360
|
+
return RCType(self._rc_type)
|
|
361
|
+
|
|
362
|
+
@rc_type.setter
|
|
363
|
+
def rc_type(self, val:int) -> None:
|
|
364
|
+
if not self.is_hdmi or not self.is_input:
|
|
365
|
+
return
|
|
366
|
+
if ((self._rc_type is None) or (self._rc_type != val)):
|
|
367
|
+
self._rc_type = val
|
|
368
|
+
self.callbacks.on_rc_type_changed(self, self.rc_type)
|
|
369
|
+
self.call_callbacks()
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def video_source(self) -> BayBase:
|
|
373
|
+
if not self.is_output:
|
|
374
|
+
return None
|
|
375
|
+
# current video source bay
|
|
376
|
+
return self._video_source
|
|
377
|
+
|
|
378
|
+
@video_source.setter
|
|
379
|
+
def video_source(self, source:BayBase) -> None:
|
|
380
|
+
# set the cached video source bay
|
|
381
|
+
if not self.is_output:
|
|
382
|
+
return
|
|
383
|
+
if source is None:
|
|
384
|
+
self._video_source = source
|
|
385
|
+
return
|
|
386
|
+
if (self._video_source is None) or (source != self._video_source):
|
|
387
|
+
self._video_source = source
|
|
388
|
+
self.callbacks.on_video_source_changed(self, source)
|
|
389
|
+
self.call_callbacks()
|
|
390
|
+
|
|
391
|
+
async def select_edid_profile(self, profile:EdidProfile) -> bool:
|
|
392
|
+
frame:FrameBase = FrameEDIDProfile.construct(mxr=self.device.registry, target=self.device, profile=profile)
|
|
393
|
+
if frame is not None:
|
|
394
|
+
self.device.registry.transmit(frame.frame)
|
|
395
|
+
self.edid_profile = profile
|
|
396
|
+
return True
|
|
397
|
+
return False
|
|
398
|
+
|
|
399
|
+
async def set_hidden(self, hidden:bool) -> bool:
|
|
400
|
+
frame:FrameBase = FrameBayHide.construct(mxr=self.device.registry, target=self, hidden=hidden)
|
|
401
|
+
if frame is not None:
|
|
402
|
+
self.device.registry.transmit(frame.frame)
|
|
403
|
+
self.hidden = hidden
|
|
404
|
+
return True
|
|
405
|
+
return False
|
|
406
|
+
|
|
407
|
+
async def select_audio_source(self, source:int|BayBase|str) -> bool:
|
|
408
|
+
if not self.is_v2ip_sink:
|
|
409
|
+
return False
|
|
410
|
+
if isinstance(source, int):
|
|
411
|
+
source = self.device.get_by_portnum(source)
|
|
412
|
+
frame:FrameBase = FrameV2IPSourceSwitch.construct(mxr=self.device.registry, target=self, audio=source)
|
|
413
|
+
if frame is not None:
|
|
414
|
+
self.device.registry.transmit(frame.frame)
|
|
415
|
+
return True
|
|
416
|
+
return False
|
|
417
|
+
|
|
418
|
+
async def select_video_source(self, port:int, opt:bool=True) -> bool:
|
|
419
|
+
if not self.is_output:
|
|
420
|
+
return False
|
|
421
|
+
if self.is_v2ip_sink:
|
|
422
|
+
source_bay = self.device.get_by_portnum(port)
|
|
423
|
+
if source_bay is not None:
|
|
424
|
+
frame:FrameBase = FrameV2IPSourceSwitch.construct(mxr=self.device.registry, target=self, video=source_bay)
|
|
425
|
+
if frame is not None:
|
|
426
|
+
self.device.registry.transmit(frame.frame)
|
|
427
|
+
return True
|
|
428
|
+
return await self.device.get_api(f"port/set/{port}/{self.bay}/{1 if opt else 0}") is not None
|
|
429
|
+
|
|
430
|
+
async def select_video_source_by_user_name(self, name:str, opt:bool=True) -> bool:
|
|
431
|
+
source = None
|
|
432
|
+
for _, bay in self.device.inputs.items():
|
|
433
|
+
if bay.user_name == name:
|
|
434
|
+
source = bay
|
|
435
|
+
break
|
|
436
|
+
if source is None:
|
|
437
|
+
return False
|
|
438
|
+
return await self.select_video_source(source.port, opt)
|
|
439
|
+
|
|
440
|
+
async def set_name(self, name:str) -> bool:
|
|
441
|
+
frame:FrameBase = FrameSetName.construct(mxr=self.device.registry, target=self, name=name)
|
|
442
|
+
if frame is not None:
|
|
443
|
+
self.device.registry.transmit(frame.frame)
|
|
444
|
+
self.user_name = name
|
|
445
|
+
return True
|
|
446
|
+
return False
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def audio_source(self) -> BayBase:
|
|
450
|
+
if not self.is_output:
|
|
451
|
+
return None
|
|
452
|
+
# current audio source bay
|
|
453
|
+
if self._audio_source is None:
|
|
454
|
+
return self.video_source
|
|
455
|
+
return self._audio_source
|
|
456
|
+
|
|
457
|
+
@audio_source.setter
|
|
458
|
+
def audio_source(self, source:BayBase) -> None:
|
|
459
|
+
if not self.is_output:
|
|
460
|
+
return
|
|
461
|
+
# set the cached audio source bay
|
|
462
|
+
if source is None:
|
|
463
|
+
self._audio_source = source
|
|
464
|
+
return
|
|
465
|
+
prev = self.audio_source
|
|
466
|
+
if (self._audio_source is None) or (source != self._audio_source):
|
|
467
|
+
self._audio_source = source
|
|
468
|
+
if prev != self.audio_source:
|
|
469
|
+
self.callbacks.on_audio_source_changed(self, self.audio_source)
|
|
470
|
+
self.call_callbacks()
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def powered_on(self) -> bool:
|
|
474
|
+
# connected device powered on
|
|
475
|
+
return (self._power_status is not None) and (self._power_status == 'on')
|
|
476
|
+
|
|
477
|
+
@property
|
|
478
|
+
def powered_off(self) -> bool:
|
|
479
|
+
# connected device powered off
|
|
480
|
+
return (self._power_status is not None) and (self._power_status == 'off')
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def power_status(self) -> str:
|
|
484
|
+
# device power status
|
|
485
|
+
if not self.available or self.powered_off:
|
|
486
|
+
return "off"
|
|
487
|
+
if self.powered_on:
|
|
488
|
+
return "on"
|
|
489
|
+
if self.is_hdmi:
|
|
490
|
+
if self.is_input:
|
|
491
|
+
return "on" if self.signal_detected else "off"
|
|
492
|
+
if self.is_output and not self.hpd_detected:
|
|
493
|
+
return "off"
|
|
494
|
+
if not self.signal_detected:
|
|
495
|
+
return "off"
|
|
496
|
+
if self.is_hdbaset and not self.hdbt_connected:
|
|
497
|
+
return "off"
|
|
498
|
+
elif self.is_audio:
|
|
499
|
+
if self.muted:
|
|
500
|
+
return "off"
|
|
501
|
+
return "on" if (self.signal_detected) else "off"
|
|
502
|
+
return "unknown"
|
|
503
|
+
|
|
504
|
+
@power_status.setter
|
|
505
|
+
def power_status(self, power:str) -> None:
|
|
506
|
+
prev = self.power_status
|
|
507
|
+
self._power_status = power
|
|
508
|
+
if (self.power_status != prev):
|
|
509
|
+
self.callbacks.on_power_changed(self, power)
|
|
510
|
+
self.call_callbacks()
|
|
511
|
+
|
|
512
|
+
async def tx_action(self, action:RCAction) -> bool:
|
|
513
|
+
from ..proto.FrameRCAction import FrameRCAction
|
|
514
|
+
pkt:FrameBase = FrameRCAction.construct(mxr=self.device.registry, target=self, action=action)
|
|
515
|
+
return self.device.registry.transmit(pkt.frame)
|
|
516
|
+
|
|
517
|
+
async def power_on(self) -> bool:
|
|
518
|
+
if await self.tx_action(RCAction.ACTION_POWER_ON):
|
|
519
|
+
self.power_status = 'on'
|
|
520
|
+
return True
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
async def power_off(self) -> bool:
|
|
524
|
+
if await self.tx_action(RCAction.ACTION_POWER_OFF):
|
|
525
|
+
self.power_status = 'off'
|
|
526
|
+
return True
|
|
527
|
+
return False
|
|
528
|
+
|
|
529
|
+
@property
|
|
530
|
+
def faulty(self) -> bool:
|
|
531
|
+
# bay is faulty
|
|
532
|
+
return (self._faulty is not None) and self._faulty
|
|
533
|
+
|
|
534
|
+
@faulty.setter
|
|
535
|
+
def faulty(self, val:bool) -> None:
|
|
536
|
+
prev = self.faulty
|
|
537
|
+
self._faulty = val
|
|
538
|
+
if prev != self.faulty and (prev or val):
|
|
539
|
+
self.callbacks.on_status_faulty_changed(self, val)
|
|
540
|
+
self.call_callbacks()
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
def hidden(self) -> bool:
|
|
544
|
+
# bay is hidden
|
|
545
|
+
return (self._hidden is not None) and self._hidden
|
|
546
|
+
|
|
547
|
+
@hidden.setter
|
|
548
|
+
def hidden(self, val:bool) -> None:
|
|
549
|
+
prev = self.hidden
|
|
550
|
+
self._hidden = val
|
|
551
|
+
if prev != self.hidden and (prev or val):
|
|
552
|
+
self.callbacks.on_status_hidden_changed(self, val)
|
|
553
|
+
self.call_callbacks()
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def poe_powered(self) -> bool:
|
|
557
|
+
# bay poe is powered
|
|
558
|
+
return (self._poe_powered is not None) and self._poe_powered
|
|
559
|
+
|
|
560
|
+
@poe_powered.setter
|
|
561
|
+
def poe_powered(self, val:bool) -> None:
|
|
562
|
+
prev = self.poe_powered
|
|
563
|
+
self._poe_powered = val
|
|
564
|
+
if prev != self.poe_powered and (not prev or not val):
|
|
565
|
+
self.callbacks.on_status_poe_powered_changed(self, val)
|
|
566
|
+
self.call_callbacks()
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def hdbt_connected(self) -> bool:
|
|
570
|
+
# hdbt link up
|
|
571
|
+
return (self._hdbt_connected is not None) and self._hdbt_connected
|
|
572
|
+
|
|
573
|
+
@hdbt_connected.setter
|
|
574
|
+
def hdbt_connected(self, val:bool) -> None:
|
|
575
|
+
prev = self.hdbt_connected
|
|
576
|
+
self._hdbt_connected = val
|
|
577
|
+
if prev != self.hdbt_connected:
|
|
578
|
+
self.callbacks.on_status_hdbt_connected_changed(self, val)
|
|
579
|
+
self.call_callbacks()
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def signal_detected(self) -> bool:
|
|
583
|
+
# video/audio signal detected
|
|
584
|
+
return (self._signal_detected is not None) and self._signal_detected
|
|
585
|
+
|
|
586
|
+
@signal_detected.setter
|
|
587
|
+
def signal_detected(self, val:bool) -> None:
|
|
588
|
+
prev = self.signal_detected
|
|
589
|
+
self._signal_detected = val
|
|
590
|
+
if prev != self.signal_detected:
|
|
591
|
+
self.callbacks.on_status_signal_detected_changed(self, val)
|
|
592
|
+
self.call_callbacks()
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def encoder_disabled(self) -> bool:
|
|
596
|
+
# video/audio encoder disabled
|
|
597
|
+
return (self._encoder_disabled is not None) and self._encoder_disabled
|
|
598
|
+
|
|
599
|
+
@encoder_disabled.setter
|
|
600
|
+
def encoder_disabled(self, val:bool) -> None:
|
|
601
|
+
prev = self.encoder_disabled
|
|
602
|
+
self._encoder_disabled = val
|
|
603
|
+
if prev != self.decoder_disabled:
|
|
604
|
+
self.callbacks.on_bay_update(self)
|
|
605
|
+
self.call_callbacks()
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def decoder_disabled(self) -> bool:
|
|
609
|
+
# video/audio decoder disabled
|
|
610
|
+
return (self._decoder_disabled is not None) and self._decoder_disabled
|
|
611
|
+
|
|
612
|
+
@decoder_disabled.setter
|
|
613
|
+
def decoder_disabled(self, val:bool) -> None:
|
|
614
|
+
prev = self._decoder_disabled
|
|
615
|
+
self._decoder_disabled = val
|
|
616
|
+
if prev != self.decoder_disabled:
|
|
617
|
+
self.callbacks.on_bay_update(self)
|
|
618
|
+
self.call_callbacks()
|
|
619
|
+
|
|
620
|
+
@property
|
|
621
|
+
def signal_type(self) -> str:
|
|
622
|
+
# video/audio signal type
|
|
623
|
+
return self._signal_type if (self._signal_type is not None) else 'unknown'
|
|
624
|
+
|
|
625
|
+
@signal_type.setter
|
|
626
|
+
def signal_type(self, val:str) -> None:
|
|
627
|
+
prev = self.signal_type
|
|
628
|
+
self._signal_type = val
|
|
629
|
+
if prev != self.signal_type:
|
|
630
|
+
self.callbacks.on_status_signal_type_changed(self, val)
|
|
631
|
+
self.call_callbacks()
|
|
632
|
+
|
|
633
|
+
@property
|
|
634
|
+
def hpd_detected(self) -> bool:
|
|
635
|
+
# hotplug detected
|
|
636
|
+
return (self._hpd_detected is not None) and self._hpd_detected
|
|
637
|
+
|
|
638
|
+
@hpd_detected.setter
|
|
639
|
+
def hpd_detected(self, val:bool) -> None:
|
|
640
|
+
prev = self.hpd_detected
|
|
641
|
+
self._hpd_detected = val
|
|
642
|
+
if prev != self.hpd_detected:
|
|
643
|
+
self.callbacks.on_status_hpd_detected_changed(self, val)
|
|
644
|
+
self.call_callbacks()
|
|
645
|
+
|
|
646
|
+
@property
|
|
647
|
+
def cec_detected(self) -> bool:
|
|
648
|
+
# CEC capable device detected
|
|
649
|
+
return (self._cec_detected is not None) and self._cec_detected
|
|
650
|
+
|
|
651
|
+
@cec_detected.setter
|
|
652
|
+
def cec_detected(self, val:bool) -> None:
|
|
653
|
+
prev = self.cec_detected
|
|
654
|
+
self._cec_detected = val
|
|
655
|
+
if prev != self.cec_detected:
|
|
656
|
+
self.callbacks.on_status_cec_detected_changed(self, val)
|
|
657
|
+
self.call_callbacks()
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def mirroring(self) -> str:
|
|
661
|
+
return self._mirror
|
|
662
|
+
|
|
663
|
+
@mirroring.setter
|
|
664
|
+
def mirroring(self, val) -> None:
|
|
665
|
+
prev = self.mirroring
|
|
666
|
+
self._mirror = val
|
|
667
|
+
if prev != val:
|
|
668
|
+
self.callbacks.on_mirror_status_changed(self, val)
|
|
669
|
+
self.call_callbacks()
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def filtered(self) -> str:
|
|
673
|
+
return self._filtered
|
|
674
|
+
|
|
675
|
+
@filtered.setter
|
|
676
|
+
def filtered(self, val) -> None:
|
|
677
|
+
prev = self.filtered
|
|
678
|
+
self._filtered = val
|
|
679
|
+
if prev != val:
|
|
680
|
+
self.callbacks.on_filter_status_changed(self, val)
|
|
681
|
+
self.call_callbacks()
|
|
682
|
+
|
|
683
|
+
@property
|
|
684
|
+
def arc(self) -> str:
|
|
685
|
+
# audio return channel status
|
|
686
|
+
return self._arc
|
|
687
|
+
|
|
688
|
+
@arc.setter
|
|
689
|
+
def arc(self, val:str) -> None:
|
|
690
|
+
prev = self.arc
|
|
691
|
+
self._arc = val
|
|
692
|
+
if prev != self.arc:
|
|
693
|
+
self.callbacks.on_status_arc_changed(self, val)
|
|
694
|
+
self.call_callbacks()
|
|
695
|
+
|
|
696
|
+
@property
|
|
697
|
+
def volume_status(self) -> VolumeMuteStatus:
|
|
698
|
+
# volume and mute status
|
|
699
|
+
|
|
700
|
+
# # handle amp dolby modes
|
|
701
|
+
# if self.device.is_amp:
|
|
702
|
+
# if self.is_output:
|
|
703
|
+
# if (self.bay >= self.device.amp_dolby_channels):
|
|
704
|
+
# return self.device.get_by_portname('Input {}'.format(self.bay + 1)).volume_status
|
|
705
|
+
# return self.device.get_by_portname('Input 9').volume_status
|
|
706
|
+
|
|
707
|
+
# check mx_remote links
|
|
708
|
+
primary = self.primary
|
|
709
|
+
if primary != self:
|
|
710
|
+
return primary.volume_status
|
|
711
|
+
return self._audio_volume
|
|
712
|
+
|
|
713
|
+
@volume_status.setter
|
|
714
|
+
def volume_status(self, other:VolumeMuteStatus) -> None:
|
|
715
|
+
# # handle amp dolby modes
|
|
716
|
+
# if self.device.is_amp:
|
|
717
|
+
# if self.is_output:
|
|
718
|
+
# if (self.bay >= self.device.amp_dolby_channels):
|
|
719
|
+
# self.device.get_by_portname('Input {}'.format(self.bay + 1)).volume_status = other
|
|
720
|
+
# return
|
|
721
|
+
# self.device.get_by_portname('Input 9').volume_status = other
|
|
722
|
+
# return
|
|
723
|
+
|
|
724
|
+
primary = self.primary
|
|
725
|
+
if primary != self:
|
|
726
|
+
primary.volume_status = other
|
|
727
|
+
return
|
|
728
|
+
|
|
729
|
+
changed = False
|
|
730
|
+
if self._audio_volume is None:
|
|
731
|
+
self._audio_volume = other
|
|
732
|
+
changed = True
|
|
733
|
+
else:
|
|
734
|
+
changed = self._audio_volume.update(other)
|
|
735
|
+
|
|
736
|
+
if changed:
|
|
737
|
+
self.callbacks.on_volume_changed(self, self.volume_status)
|
|
738
|
+
self.call_callbacks()
|
|
739
|
+
lbay = self.linked_bay
|
|
740
|
+
if lbay is not None:
|
|
741
|
+
self.callbacks.on_volume_changed(lbay, self.volume_status)
|
|
742
|
+
self.call_callbacks()
|
|
743
|
+
|
|
744
|
+
if self.device.is_amp and self.is_input:
|
|
745
|
+
if (self.bay == 8):
|
|
746
|
+
nb = 0
|
|
747
|
+
while nb < self.device.amp_dolby_channels:
|
|
748
|
+
self.callbacks.on_volume_changed(self.device.get_by_portname('Output {}'.format(nb + 1)), self.volume_status)
|
|
749
|
+
self.device.get_by_portname('Output {}'.format(nb + 1)).call_callbacks()
|
|
750
|
+
nb = nb + 1
|
|
751
|
+
return
|
|
752
|
+
self.callbacks.on_volume_changed(self.device.get_by_portname('Output {}'.format(self.bay + 1)), self.volume_status)
|
|
753
|
+
self.device.get_by_portname('Output {}'.format(nb + 1)).call_callbacks()
|
|
754
|
+
|
|
755
|
+
@property
|
|
756
|
+
def volume(self) -> int:
|
|
757
|
+
# current volume
|
|
758
|
+
vs = self.volume_status
|
|
759
|
+
return vs.volume if vs is not None else None
|
|
760
|
+
|
|
761
|
+
@property
|
|
762
|
+
def muted(self) -> bool:
|
|
763
|
+
# muted or not
|
|
764
|
+
vs = self.volume_status
|
|
765
|
+
return vs.muted if vs is not None else None
|
|
766
|
+
|
|
767
|
+
@property
|
|
768
|
+
def amp_settings(self) -> AmpZoneSettings|None:
|
|
769
|
+
return self._amp_settings
|
|
770
|
+
|
|
771
|
+
@amp_settings.setter
|
|
772
|
+
def amp_settings(self, settings:AmpZoneSettings) -> None:
|
|
773
|
+
changed = (self._amp_settings is None) or (self._amp_settings != settings)
|
|
774
|
+
self._amp_settings = settings
|
|
775
|
+
if changed:
|
|
776
|
+
self.callbacks.on_amp_zone_settings_changed(self, settings)
|
|
777
|
+
self.call_callbacks()
|
|
778
|
+
|
|
779
|
+
def volume_up(self) -> bool:
|
|
780
|
+
return self.volume_set(self.volume + 1)
|
|
781
|
+
|
|
782
|
+
def volume_down(self) -> bool:
|
|
783
|
+
return self.volume_set(self.volume - 1)
|
|
784
|
+
|
|
785
|
+
def volume_set(self, volume:int, muted:bool=None) -> bool:
|
|
786
|
+
''' Change the volume on the remote device '''
|
|
787
|
+
from ..proto.FrameVolumeSet import FrameVolumeSet
|
|
788
|
+
if not self.has_volume_control:
|
|
789
|
+
# remote device doesn't support volume control
|
|
790
|
+
return False
|
|
791
|
+
|
|
792
|
+
new_value = self.volume_status
|
|
793
|
+
if new_value is None:
|
|
794
|
+
# no known value, create a new one
|
|
795
|
+
new_value = VolumeMuteStatus(volume_left=volume, volume_right=volume, muted_left=(volume != 0), muted_right=(volume != 0))
|
|
796
|
+
else:
|
|
797
|
+
# update the volume
|
|
798
|
+
new_value.volume = volume
|
|
799
|
+
self.volume_status = new_value
|
|
800
|
+
|
|
801
|
+
if (self.volume is None) or (volume > self.volume):
|
|
802
|
+
# unmute
|
|
803
|
+
self.volume_status.muted = False
|
|
804
|
+
|
|
805
|
+
if muted is not None:
|
|
806
|
+
# update the mute value if provided
|
|
807
|
+
new_value.muted = muted
|
|
808
|
+
|
|
809
|
+
# send the update to the remote device
|
|
810
|
+
pkt:FrameBase = FrameVolumeSet.construct(mxr=self.device.registry, target=self, volume=new_value)
|
|
811
|
+
if self.device.registry.transmit(pkt.frame):
|
|
812
|
+
self.callbacks.on_volume_changed(bay=self, volume=new_value)
|
|
813
|
+
return True
|
|
814
|
+
return False
|
|
815
|
+
|
|
816
|
+
def mute_set(self, mute:bool) -> bool:
|
|
817
|
+
return self.volume_set(self.volume, mute)
|
|
818
|
+
|
|
819
|
+
async def send_key(self, key:int) -> bool:
|
|
820
|
+
cmd = "key/sendkey/{}/{}/{}".format(str(key), self.mode, self.bay)
|
|
821
|
+
_LOGGER.info(cmd)
|
|
822
|
+
return await self.device.get_api(cmd) is not None
|
|
823
|
+
|
|
824
|
+
@property
|
|
825
|
+
def is_primary(self) -> bool:
|
|
826
|
+
return self.device.registry.links.is_primary(self)
|
|
827
|
+
|
|
828
|
+
@property
|
|
829
|
+
def primary(self) -> BayBase:
|
|
830
|
+
# primary bay if linked. this is the source type bay for linked bays. this bay is it's own primary if not linked
|
|
831
|
+
if self.link_configured and not self.is_primary:
|
|
832
|
+
return self.link.linked_bay
|
|
833
|
+
return self
|
|
834
|
+
|
|
835
|
+
@property
|
|
836
|
+
def linked_bay(self) -> BayBase:
|
|
837
|
+
# linked bay if linked, None if not linked
|
|
838
|
+
if self.link_configured:
|
|
839
|
+
return self.link.linked_bay
|
|
840
|
+
return None
|
|
841
|
+
|
|
842
|
+
@property
|
|
843
|
+
def link(self) -> BayLink|None:
|
|
844
|
+
return self.device.registry.links.get(self)
|
|
845
|
+
|
|
846
|
+
@property
|
|
847
|
+
def link_configured(self) -> bool:
|
|
848
|
+
link = self.link
|
|
849
|
+
return (link is not None) and (link.linked)
|
|
850
|
+
|
|
851
|
+
@property
|
|
852
|
+
def link_connected(self) -> bool:
|
|
853
|
+
return self.link.connected
|
|
854
|
+
|
|
855
|
+
@property
|
|
856
|
+
def link_online(self) -> bool:
|
|
857
|
+
return self.link.online
|
|
858
|
+
|
|
859
|
+
def on_key_pressed(self, key:RCKey) -> None:
|
|
860
|
+
self.callbacks.on_key_pressed(self, key)
|
|
861
|
+
self.call_callbacks()
|
|
862
|
+
|
|
863
|
+
def on_action_received(self, action:RCAction) -> None:
|
|
864
|
+
self.callbacks.on_action_received(self, action)
|
|
865
|
+
self.call_callbacks()
|
|
866
|
+
|
|
867
|
+
def on_mxr_bay_status(self, data:BayStatusMask) -> None:
|
|
868
|
+
self.faulty = data.fault
|
|
869
|
+
self.hidden = data.hidden
|
|
870
|
+
self.poe_powered = data.powered
|
|
871
|
+
self.hdbt_connected = data.hdbt_connected
|
|
872
|
+
self.hpd_detected = data.hpd_detected
|
|
873
|
+
self.cec_detected = data.cec_detected
|
|
874
|
+
self.signal_detected = data.signal_detected
|
|
875
|
+
self.encoder_disabled = data.encoder_disabled
|
|
876
|
+
self.decoder_disabled = data.decoder_disabled
|
|
877
|
+
|
|
878
|
+
if not data.cec_detected:
|
|
879
|
+
self.power_status = 'unknown'
|
|
880
|
+
elif data.powered_on:
|
|
881
|
+
self.power_status = 'on'
|
|
882
|
+
elif data.powered_off:
|
|
883
|
+
self.power_status = 'off'
|
|
884
|
+
else:
|
|
885
|
+
self.power_status = 'unknown'
|
|
886
|
+
if data.audio_arc_hdmi:
|
|
887
|
+
self.arc = self.ARC_HDMI
|
|
888
|
+
elif data.audio_arc_optical:
|
|
889
|
+
self.arc = self.ARC_OPTICAL
|
|
890
|
+
elif data.audio_arc_analog:
|
|
891
|
+
self.arc = self.ARC_ANALOG
|
|
892
|
+
else:
|
|
893
|
+
self.arc = self.ARC_NONE
|
|
894
|
+
|
|
895
|
+
def on_mxr_bay_config(self, data:BayConfig) -> None:
|
|
896
|
+
self.features_mask = data.features
|
|
897
|
+
self.status_mask = data.status
|
|
898
|
+
self.user_name = data.user_name
|
|
899
|
+
self.bay = data.bay
|
|
900
|
+
self.on_mxr_bay_status(data.status)
|
|
901
|
+
if not data.status.signal_detected or not self.device.is_v2ip:
|
|
902
|
+
self.signal_type = data.signal_type
|
|
903
|
+
if self.is_output:
|
|
904
|
+
self.video_source = self.device.get_by_portnum(data.video_source)
|
|
905
|
+
self.audio_source = self.device.get_by_portnum(data.audio_source)
|
|
906
|
+
else:
|
|
907
|
+
self.rc_type = data.rc_type
|
|
908
|
+
self.edid_profile = data.edid_profile
|
|
909
|
+
|
|
910
|
+
def on_mxr_volume_update(self, data:VolumeMuteStatus) -> None:
|
|
911
|
+
self.volume_status = data
|
|
912
|
+
|
|
913
|
+
def __str__(self) -> str:
|
|
914
|
+
if self.is_v2ip_source:
|
|
915
|
+
if self.v2ip_source is None:
|
|
916
|
+
return f"{self.device.serial} {self.bay_label} <unknown mcast address>"
|
|
917
|
+
return f"{self.device.serial} {self.bay_label} {self.v2ip_source.video}"
|
|
918
|
+
return f"{self.device.serial} {self.bay_label}"
|
|
919
|
+
|
|
920
|
+
def __eq__(self, other:Any) -> bool:
|
|
921
|
+
return isinstance(other, BayBase) and \
|
|
922
|
+
(self.device == other.device) and \
|
|
923
|
+
(self.port == other.port)
|