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.
Files changed (74) hide show
  1. mx_remote/Interface.py +1656 -0
  2. mx_remote/Uid.py +137 -0
  3. mx_remote/__init__.py +12 -0
  4. mx_remote/api/BayConfig.py +53 -0
  5. mx_remote/api/__init__.py +8 -0
  6. mx_remote/const.py +20 -0
  7. mx_remote/main.py +117 -0
  8. mx_remote/proto/BayConfig.py +104 -0
  9. mx_remote/proto/Constants.py +382 -0
  10. mx_remote/proto/Data.py +142 -0
  11. mx_remote/proto/Factory.py +168 -0
  12. mx_remote/proto/FrameAmpDolbySettings.py +51 -0
  13. mx_remote/proto/FrameAmpZoneSettings.py +123 -0
  14. mx_remote/proto/FrameBase.py +80 -0
  15. mx_remote/proto/FrameBayConfig.py +47 -0
  16. mx_remote/proto/FrameBayConfigSecondary.py +43 -0
  17. mx_remote/proto/FrameBayHide.py +53 -0
  18. mx_remote/proto/FrameBayStatus.py +51 -0
  19. mx_remote/proto/FrameConnectStatus.py +38 -0
  20. mx_remote/proto/FrameDiscover.py +21 -0
  21. mx_remote/proto/FrameEDID.py +20 -0
  22. mx_remote/proto/FrameEDIDProfile.py +24 -0
  23. mx_remote/proto/FrameFilterStatus.py +39 -0
  24. mx_remote/proto/FrameFirmwareVersion.py +40 -0
  25. mx_remote/proto/FrameHeader.py +92 -0
  26. mx_remote/proto/FrameHello.py +77 -0
  27. mx_remote/proto/FrameLinks.py +45 -0
  28. mx_remote/proto/FrameMeshOperation.py +66 -0
  29. mx_remote/proto/FrameMirrorStatus.py +49 -0
  30. mx_remote/proto/FrameNetworkStatus.py +163 -0
  31. mx_remote/proto/FramePDUState.py +71 -0
  32. mx_remote/proto/FramePowerChange.py +38 -0
  33. mx_remote/proto/FrameRCAction.py +50 -0
  34. mx_remote/proto/FrameRCIr.py +17 -0
  35. mx_remote/proto/FrameRCKey.py +40 -0
  36. mx_remote/proto/FrameReboot.py +26 -0
  37. mx_remote/proto/FrameRoutingChange.py +66 -0
  38. mx_remote/proto/FrameSetName.py +27 -0
  39. mx_remote/proto/FrameSignalStatus.py +46 -0
  40. mx_remote/proto/FrameSignalStatusNew.py +285 -0
  41. mx_remote/proto/FrameSysTemperature.py +50 -0
  42. mx_remote/proto/FrameTXRCAction.py +58 -0
  43. mx_remote/proto/FrameTopology.py +36 -0
  44. mx_remote/proto/FrameV2IPDeviceConfiguration.py +86 -0
  45. mx_remote/proto/FrameV2IPLink.py +16 -0
  46. mx_remote/proto/FrameV2IPSetMaster.py +16 -0
  47. mx_remote/proto/FrameV2IPSourceSwitch.py +84 -0
  48. mx_remote/proto/FrameV2IPSources.py +43 -0
  49. mx_remote/proto/FrameV2IPStats.py +55 -0
  50. mx_remote/proto/FrameV2IPStreamDetails.py +46 -0
  51. mx_remote/proto/FrameVolume.py +62 -0
  52. mx_remote/proto/FrameVolumeDown.py +28 -0
  53. mx_remote/proto/FrameVolumeSet.py +81 -0
  54. mx_remote/proto/FrameVolumeUp.py +28 -0
  55. mx_remote/proto/LinkConfig.py +121 -0
  56. mx_remote/proto/PDUState.py +95 -0
  57. mx_remote/proto/Svd.py +69 -0
  58. mx_remote/proto/V2IPConfig.py +71 -0
  59. mx_remote/proto/V2IPStats.py +126 -0
  60. mx_remote/proto/__init__.py +8 -0
  61. mx_remote/proto/svd.csv +157 -0
  62. mx_remote/remote/Bay.py +923 -0
  63. mx_remote/remote/ConnectionAsync.py +132 -0
  64. mx_remote/remote/Device.py +530 -0
  65. mx_remote/remote/Link.py +187 -0
  66. mx_remote/remote/PDU.py +152 -0
  67. mx_remote/remote/Remote.py +300 -0
  68. mx_remote/remote/State.py +28 -0
  69. mx_remote/remote/V2IP.py +83 -0
  70. mx_remote-2.0.0.dist-info/METADATA +90 -0
  71. mx_remote-2.0.0.dist-info/RECORD +74 -0
  72. mx_remote-2.0.0.dist-info/WHEEL +4 -0
  73. mx_remote-2.0.0.dist-info/entry_points.txt +2 -0
  74. 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}"