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
|
@@ -0,0 +1,40 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from .Constants import RCKey
|
|
11
|
+
from ..Interface import BayBase
|
|
12
|
+
class FrameRCKey(FrameBase):
|
|
13
|
+
''' remote control key press or action '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def bay(self) -> BayBase:
|
|
19
|
+
# bay that received the key press
|
|
20
|
+
dev = self.remote_device
|
|
21
|
+
if dev is None:
|
|
22
|
+
return None
|
|
23
|
+
portnum = ((int(self.payload[1]) << 8) | int(self.payload[0])) if (dev.protocol >= 6) else self.payload[0]
|
|
24
|
+
return dev.get_by_portnum(portnum)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def key(self) -> RCKey:
|
|
28
|
+
# key that was received
|
|
29
|
+
dev = self.remote_device
|
|
30
|
+
if dev is None:
|
|
31
|
+
return None
|
|
32
|
+
return RCKey((int(self.payload[3]) << 8) | int(self.payload[2])) if (dev.protocol >= 6) else RCKey((int(self.payload[2]) << 8) | int(self.payload[1]))
|
|
33
|
+
|
|
34
|
+
def process(self) -> None:
|
|
35
|
+
bay = self.bay
|
|
36
|
+
if bay is not None:
|
|
37
|
+
bay.on_key_pressed(self.key)
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
return "{} key pressed: {}".format(str(self.bay), repr(self.key))
|
|
@@ -0,0 +1,26 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from ..Interface import DeviceBase, DeviceRegistry
|
|
11
|
+
|
|
12
|
+
class FrameReboot(FrameBase):
|
|
13
|
+
''' remote control key press or action '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
def construct(mxr:DeviceRegistry, target:DeviceBase) -> FrameBase:
|
|
18
|
+
frame:FrameBase = FrameBase.construct_frame(mxr=mxr, opcode=0x28)
|
|
19
|
+
frame.payload = target.remote_id.byte_value
|
|
20
|
+
return frame
|
|
21
|
+
|
|
22
|
+
def process(self) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return "reboot"
|
|
@@ -0,0 +1,66 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from ..Interface import BayBase
|
|
11
|
+
|
|
12
|
+
class FrameRoutingChange(FrameBase):
|
|
13
|
+
''' Routing change information frame '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def sink_bay(self) -> BayBase:
|
|
19
|
+
# the sink bay that changed routing
|
|
20
|
+
portnum = self.payload[0]
|
|
21
|
+
dev = self.remote_device
|
|
22
|
+
if dev is None:
|
|
23
|
+
return
|
|
24
|
+
return dev.get_by_portnum(portnum)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def selected_bay(self) -> BayBase:
|
|
28
|
+
# the (video) bay that was selected
|
|
29
|
+
portnum = self.payload[1]
|
|
30
|
+
dev = self.remote_device
|
|
31
|
+
if dev is None:
|
|
32
|
+
return
|
|
33
|
+
return dev.get_by_portnum(portnum)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def video_bay(self) -> BayBase:
|
|
37
|
+
# the new video source bay
|
|
38
|
+
portnum = self.payload[2]
|
|
39
|
+
dev = self.remote_device
|
|
40
|
+
if dev is None:
|
|
41
|
+
return
|
|
42
|
+
return dev.get_by_portnum(portnum)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def audio_bay(self) -> BayBase:
|
|
46
|
+
# the new audio source bay
|
|
47
|
+
portnum = self.payload[4]
|
|
48
|
+
dev = self.remote_device
|
|
49
|
+
if dev is None:
|
|
50
|
+
return
|
|
51
|
+
return dev.get_by_portnum(portnum)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def scrambled(self):
|
|
55
|
+
# signal scrambled or not
|
|
56
|
+
return (self.payload[3] == 1)
|
|
57
|
+
|
|
58
|
+
def process(self):
|
|
59
|
+
# update the local cache
|
|
60
|
+
sink_bay = self.sink_bay
|
|
61
|
+
if sink_bay is not None:
|
|
62
|
+
sink_bay.video_source = self.video_bay
|
|
63
|
+
sink_bay.audio_source = self.audio_bay
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return f"routing change {self.sink_bay} to {self.video_bay}"
|
|
@@ -0,0 +1,27 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from ..Interface import DeviceRegistry, BayBase
|
|
11
|
+
|
|
12
|
+
class FrameSetName(FrameBase):
|
|
13
|
+
''' Change a bay name '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
def construct(mxr:DeviceRegistry, target:BayBase, name:str) -> FrameBase:
|
|
18
|
+
if len(name) > 16:
|
|
19
|
+
name = name[:16]
|
|
20
|
+
name = name.encode(encoding='ascii')
|
|
21
|
+
payload = target.device.remote_id.byte_value + \
|
|
22
|
+
bytes([(target.port >> 0) & 0xFF, (target.port >> 8) & 0xFF]) + \
|
|
23
|
+
name
|
|
24
|
+
return FrameBase.construct_frame(mxr=mxr, opcode=0x22, payload=payload, size=40)
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
return f"Name change"
|
|
@@ -0,0 +1,46 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from ..Interface import BayBase
|
|
11
|
+
|
|
12
|
+
class FrameSignalStatus(FrameBase):
|
|
13
|
+
''' signal status changed '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def bay(self) -> BayBase:
|
|
19
|
+
portnum = self.payload[0]
|
|
20
|
+
dev = self.remote_device
|
|
21
|
+
if dev is None:
|
|
22
|
+
return None
|
|
23
|
+
return dev.get_by_portnum(portnum)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def signal(self) -> bool:
|
|
27
|
+
# signal detected
|
|
28
|
+
return (self.payload[1] == 1)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def signal_type(self) -> str:
|
|
32
|
+
# signal type description
|
|
33
|
+
if len(self) <= 2:
|
|
34
|
+
return None
|
|
35
|
+
return self.payload[2:].split(b'\0',1)[0].decode('ascii')
|
|
36
|
+
|
|
37
|
+
def process(self) -> None:
|
|
38
|
+
# update the local cache
|
|
39
|
+
bay = self.bay
|
|
40
|
+
if bay is None:
|
|
41
|
+
return
|
|
42
|
+
bay.signal_detected = self.signal
|
|
43
|
+
bay.signal_type = self.signal_type
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
return f"signal status {str(self.bay)} - detected={self.signal} type={self.signal_type}"
|
|
@@ -0,0 +1,285 @@
|
|
|
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 enum import Enum
|
|
9
|
+
from .FrameBase import FrameBase
|
|
10
|
+
from .FrameHeader import FrameHeader
|
|
11
|
+
from ..Interface import BayBase
|
|
12
|
+
from ..Uid import MxrDeviceUid
|
|
13
|
+
import struct
|
|
14
|
+
from .Svd import SvdMap, Svd
|
|
15
|
+
|
|
16
|
+
SVD = SvdMap()
|
|
17
|
+
|
|
18
|
+
class VideoColourSpace(Enum):
|
|
19
|
+
RGB = 0
|
|
20
|
+
YUV444 = 1
|
|
21
|
+
YUV422 = 2
|
|
22
|
+
YUV420 = 3
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str:
|
|
25
|
+
if self.value == 0:
|
|
26
|
+
return 'RGB'
|
|
27
|
+
if self.value == 1:
|
|
28
|
+
return '4:4:4'
|
|
29
|
+
if self.value == 2:
|
|
30
|
+
return '4:2:2'
|
|
31
|
+
if self.value == 3:
|
|
32
|
+
return '4:2:0'
|
|
33
|
+
return 'unknown'
|
|
34
|
+
|
|
35
|
+
class AvDetailsSupportFlags:
|
|
36
|
+
def __init__(self, data:int) -> None:
|
|
37
|
+
self.data = data & 0xFF
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def stream_detected(self) -> bool:
|
|
41
|
+
return (self.data & (1 << 0) != 0)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def stream_valid(self) -> bool:
|
|
45
|
+
return (self.data & (1 << 1) != 0)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def have_colour_depth(self) -> bool:
|
|
49
|
+
return (self.data & (1 << 2) != 0)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def have_avi_infoframe(self) -> bool:
|
|
53
|
+
return (self.data & (1 << 3) != 0)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def have_audio_infoframe(self) -> bool:
|
|
57
|
+
return (self.data & (1 << 4) != 0)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def have_audio_details(self) -> bool:
|
|
61
|
+
return (self.data & (1 << 5) != 0)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def have_video_details(self) -> bool:
|
|
65
|
+
return (self.data & (1 << 6) != 0)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def have_link_errors(self) -> bool:
|
|
69
|
+
return (self.data & (1 << 7) != 0)
|
|
70
|
+
|
|
71
|
+
class AvDetailsStreamFlags:
|
|
72
|
+
def __init__(self, data:int) -> None:
|
|
73
|
+
self.data = data & 0xFF
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def video_scrambled(self) -> bool:
|
|
77
|
+
return (self.data & (1 << 0) != 0)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def video_interlaced(self) -> bool:
|
|
81
|
+
return (self.data & (1 << 1) != 0)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def video_3d(self) -> bool:
|
|
85
|
+
return (self.data & (1 << 2) != 0)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def video_non_int_clock(self) -> bool:
|
|
89
|
+
return (self.data & (1 << 3) != 0)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def video_hdr(self) -> bool:
|
|
93
|
+
return (self.data & (1 << 4) != 0)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def avmute_set(self) -> bool:
|
|
97
|
+
return (self.data & (1 << 5) != 0)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def avmute_clear(self) -> bool:
|
|
101
|
+
return (self.data & (1 << 6) != 0)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def reserved(self) -> bool:
|
|
105
|
+
return (self.data & (1 << 7) != 0)
|
|
106
|
+
|
|
107
|
+
class SignalStatusAvDetailsVideo:
|
|
108
|
+
def __init__(self, data:bytes) -> None:
|
|
109
|
+
if len(data) != 16:
|
|
110
|
+
raise Exception(f"invalid length: {len(data)}")
|
|
111
|
+
self._data = data
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def svd(self) -> Svd:
|
|
115
|
+
return SVD.svd[self._data[0]] if self._data[0] != 0 else None
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def colour_space(self) -> VideoColourSpace:
|
|
119
|
+
return VideoColourSpace(self._data[1])
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def colour_depth(self) -> int:
|
|
123
|
+
return self._data[2]
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def pixels_per_clock(self) -> int:
|
|
127
|
+
return self._data[3]
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def aspect_ratio(self) -> int:
|
|
131
|
+
return self._data[4]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def format_3d(self) -> int:
|
|
135
|
+
return self._data[5]
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def samping_3d(self) -> int:
|
|
139
|
+
return self._data[6]
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def samping_position(self) -> int:
|
|
143
|
+
return self._data[7]
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def frame_rate(self) -> int:
|
|
147
|
+
return int.from_bytes(self._data[8:9], "little")
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def tmds_clock(self) -> int:
|
|
151
|
+
return int.from_bytes(self._data[10:13], "little")
|
|
152
|
+
|
|
153
|
+
def __str__(self) -> str:
|
|
154
|
+
return f"{self.svd}, rate {self.frame_rate}, tmds = {self.tmds_clock}"
|
|
155
|
+
|
|
156
|
+
class FrameSignalStatusNew(FrameBase):
|
|
157
|
+
''' signal status changed '''
|
|
158
|
+
def __init__(self, header:FrameHeader):
|
|
159
|
+
super().__init__(header)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def signal_header(self) -> bytes:
|
|
163
|
+
return self.payload[0:8]
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def signal_header_version(self) -> int:
|
|
167
|
+
if len(self.payload) < 8:
|
|
168
|
+
return 0
|
|
169
|
+
return int(self.signal_header[1]) << 8 | int(self.signal_header[0])
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def support_flags(self) -> AvDetailsSupportFlags:
|
|
173
|
+
if len(self.payload) < 8:
|
|
174
|
+
return 0
|
|
175
|
+
return AvDetailsSupportFlags(self.signal_header[2])
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def stream_flags(self) -> AvDetailsStreamFlags:
|
|
179
|
+
if len(self.payload) < 8:
|
|
180
|
+
return 0
|
|
181
|
+
return AvDetailsStreamFlags(self.signal_header[3])
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def infoframe(self) -> bytes:
|
|
185
|
+
if len(self.payload) < 24:
|
|
186
|
+
return bytes()
|
|
187
|
+
return self.payload[8:24]
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def audio(self) -> bytes:
|
|
191
|
+
if len(self.payload) < 40:
|
|
192
|
+
return bytes()
|
|
193
|
+
return self.payload[24:40]
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def video(self) -> SignalStatusAvDetailsVideo:
|
|
197
|
+
if len(self.payload) < 56:
|
|
198
|
+
return None
|
|
199
|
+
return SignalStatusAvDetailsVideo(self.payload[40:56])
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def vsync(self) -> bytes:
|
|
203
|
+
if len(self.payload) < 88:
|
|
204
|
+
return bytes()
|
|
205
|
+
return self.payload[56:88]
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def errors(self) -> bytes:
|
|
209
|
+
if len(self.payload) < 100:
|
|
210
|
+
return bytes()
|
|
211
|
+
pl = self.payload[88:100]
|
|
212
|
+
return [struct.unpack('<L', pl[0:4])[0], struct.unpack('<L', pl[4:8])[0], struct.unpack('<L', pl[8:12])[0]]
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def bay_details(self) -> bytes:
|
|
216
|
+
if len(self.payload) < 112:
|
|
217
|
+
return bytes()
|
|
218
|
+
return self.payload[100:112]
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def port_number(self) -> int:
|
|
222
|
+
if len(self.payload) < 8:
|
|
223
|
+
return 0xFF
|
|
224
|
+
#return struct.unpack('<L', self.payload[57:61])[0]
|
|
225
|
+
return (self.bay_details[1] << 8) | (self.bay_details[0])
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def bay(self) -> BayBase:
|
|
229
|
+
dev = self.remote_device
|
|
230
|
+
if dev is None:
|
|
231
|
+
return None
|
|
232
|
+
return dev.get_by_portnum(self.port_number)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def bay_name(self) -> str:
|
|
236
|
+
bay = self.bay
|
|
237
|
+
return str(bay) if bay is not None else "(Waiting For HELLO)"
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def stream_detected(self) -> bool:
|
|
241
|
+
return self.support_flags.stream_detected
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def stream_valid(self) -> bool:
|
|
245
|
+
return self.support_flags.stream_valid
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def frame_rate(self) -> float:
|
|
249
|
+
if self.stream_flags.video_non_int_clock:
|
|
250
|
+
return round(self.video.frame_rate * 1000 / 1001, 2)
|
|
251
|
+
return self.video.frame_rate
|
|
252
|
+
|
|
253
|
+
def process(self) -> None:
|
|
254
|
+
if len(self.payload) < 8:
|
|
255
|
+
return
|
|
256
|
+
if len(self.payload) < 112:
|
|
257
|
+
return
|
|
258
|
+
# update the local cache
|
|
259
|
+
bay = self.bay
|
|
260
|
+
if bay is None:
|
|
261
|
+
return
|
|
262
|
+
bay.signal_detected = self.stream_valid
|
|
263
|
+
if self.stream_valid:
|
|
264
|
+
signal_type = f'{self.video.svd.horizontal_active}x{self.video.svd.vertical_active} / {self.video.colour_space} / {self.video.colour_depth}bpp'
|
|
265
|
+
if self.stream_flags.video_interlaced:
|
|
266
|
+
signal_type += ' interlaced'
|
|
267
|
+
if self.stream_flags.video_hdr:
|
|
268
|
+
signal_type += ' HDR'
|
|
269
|
+
signal_type += f' / {self.frame_rate}Hz'
|
|
270
|
+
bay.signal_type = signal_type
|
|
271
|
+
else:
|
|
272
|
+
bay.signal_type = 'No Signal'
|
|
273
|
+
|
|
274
|
+
def __str__(self) -> str:
|
|
275
|
+
if len(self.payload) < 8:
|
|
276
|
+
return "signal status request"
|
|
277
|
+
if len(self.payload) == 16:
|
|
278
|
+
return f"signal status request for {MxrDeviceUid(self.payload)}"
|
|
279
|
+
if len(self.payload) < 112:
|
|
280
|
+
return f"signal status request len {len(self.payload)}"
|
|
281
|
+
if self.stream_valid:
|
|
282
|
+
return f"{self.bay_name} signal status - {self.video}, errors = {self.errors}"
|
|
283
|
+
if self.stream_detected:
|
|
284
|
+
return f"{self.bay_name} signal status - invalid signal detected"
|
|
285
|
+
return f"{self.bay_name} signal status - no signal"
|
|
@@ -0,0 +1,50 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
class FrameSysTemperature(FrameBase):
|
|
13
|
+
''' system temperature frame, sent every minute by devices on the network '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def nb_sensors(self) -> int:
|
|
19
|
+
# number of temperature sensor readings
|
|
20
|
+
return int(self.payload[0])
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def temperature(self) -> list[int]:
|
|
24
|
+
# list of all readings in this frame
|
|
25
|
+
rv = []
|
|
26
|
+
ptr = 0
|
|
27
|
+
while ptr < self.nb_sensors:
|
|
28
|
+
ptr = ptr + 1
|
|
29
|
+
rv.append(int(self.payload[ptr]))
|
|
30
|
+
return rv
|
|
31
|
+
|
|
32
|
+
def process(self) -> None:
|
|
33
|
+
# update the local cache
|
|
34
|
+
dev = self.mxr.get_by_uid(self.remote_id)
|
|
35
|
+
if dev is not None:
|
|
36
|
+
dev.on_mxr_temperature(self)
|
|
37
|
+
|
|
38
|
+
def __eq__(self, other: Any) -> bool:
|
|
39
|
+
return isinstance(other, FrameSysTemperature) and \
|
|
40
|
+
(self.nb_sensors == other.nb_sensors) and \
|
|
41
|
+
(self.temperature == other.temperature)
|
|
42
|
+
|
|
43
|
+
def __ne__(self, other: Any) -> bool:
|
|
44
|
+
return not isinstance(other, FrameSysTemperature) or \
|
|
45
|
+
(self.nb_sensors != other.nb_sensors) or \
|
|
46
|
+
(self.temperature != other.temperature)
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return "temperature: {}".format(str(self.temperature))
|
|
50
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from .Constants import RCAction
|
|
11
|
+
from ..Interface import BayBase, DeviceBase, DeviceRegistry
|
|
12
|
+
from ..Uid import MxrDeviceUid
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class FrameTXRCAction(FrameBase):
|
|
18
|
+
def __init__(self, header:FrameHeader):
|
|
19
|
+
super().__init__(header)
|
|
20
|
+
|
|
21
|
+
def construct(mxr:DeviceRegistry, target:BayBase, action:RCAction) -> FrameBase:
|
|
22
|
+
payload = target.device.remote_id.byte_value
|
|
23
|
+
payload += bytes([(target.port & 0xFF), ((target.port >> 8) & 0xFF), (int(action.value) & 0xFF), ((int(action.value) >> 8) & 0xFF)])
|
|
24
|
+
frame:FrameBase = FrameBase.construct_frame(mxr=mxr, opcode=0x0E)
|
|
25
|
+
frame.payload = payload
|
|
26
|
+
return frame
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def target_device(self) -> DeviceBase:
|
|
30
|
+
return self.mxr.get_by_uid(self.target_uid)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def target_uid(self) -> MxrDeviceUid:
|
|
34
|
+
return MxrDeviceUid(self.payload[0:16])
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def bay(self) -> BayBase:
|
|
38
|
+
# bay that received the key press
|
|
39
|
+
dev = self.remote_device
|
|
40
|
+
if dev is None:
|
|
41
|
+
return None
|
|
42
|
+
portnum = ((int(self.payload[17]) << 8) | int(self.payload[16]))
|
|
43
|
+
return dev.get_by_portnum(portnum)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def action(self) -> RCAction:
|
|
47
|
+
dev = self.remote_device
|
|
48
|
+
if dev is None:
|
|
49
|
+
return None
|
|
50
|
+
return RCAction(int(self.payload[20]))
|
|
51
|
+
|
|
52
|
+
def process(self) -> None:
|
|
53
|
+
bay = self.bay
|
|
54
|
+
if bay is not None:
|
|
55
|
+
bay.on_action_received(self.action)
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return f"{self.bay} action receive: {self.action}"
|
|
@@ -0,0 +1,36 @@
|
|
|
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 .FrameBase import FrameBase
|
|
9
|
+
from .FrameHeader import FrameHeader
|
|
10
|
+
from ..Uid import MxrDeviceUid
|
|
11
|
+
|
|
12
|
+
class TopologyEntry:
|
|
13
|
+
def __init__(self, uid:MxrDeviceUid, mask:int):
|
|
14
|
+
self.uid = uid
|
|
15
|
+
self.mask = mask
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"{self.uid} mask {self.mask}"
|
|
19
|
+
|
|
20
|
+
class FrameTopology(FrameBase):
|
|
21
|
+
def __init__(self, header:FrameHeader):
|
|
22
|
+
super().__init__(header)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def topology(self):
|
|
26
|
+
topo = []
|
|
27
|
+
if self.header.payload_len < 20:
|
|
28
|
+
return []
|
|
29
|
+
data = self.payload
|
|
30
|
+
while len(data) >= 20:
|
|
31
|
+
topo.append(TopologyEntry(MxrDeviceUid(data[0:16]), struct.unpack('<L', data[16:20])[0]))
|
|
32
|
+
data = data[20:]
|
|
33
|
+
return topo
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return f"{self.remote_device} topology data: {self.topology}"
|