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,382 @@
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 typing import Any
10
+
11
+ MXR_PROTOCOL_VERSION = 20
12
+
13
+ MXR_DEVICE_FEATURE_IR_RX = (1 << 0)
14
+ MXR_DEVICE_FEATURE_IR_TX = (1 << 1)
15
+ MXR_DEVICE_FEATURE_CEC = (1 << 2)
16
+ MXR_DEVICE_FEATURE_V2IP_SOURCE = (1 << 3)
17
+ MXR_DEVICE_FEATURE_V2IP_SINK = (1 << 4)
18
+ MXR_DEVICE_FEATURE_VIDEO_ROUTING = (1 << 5)
19
+ MXR_DEVICE_FEATURE_AUDIO_ROUTING = (1 << 6)
20
+ MXR_DEVICE_FEATURE_VOLUME_CONTROL = (1 << 7)
21
+ MXR_DEVICE_FEATURE_AUDIO_RETURN = (1 << 8)
22
+ MXR_DEVICE_FEATURE_REMOTE_CONTROL = (1 << 9)
23
+ MXR_DEVICE_FEATURE_SETUP_COMPLETED = (1 << 10)
24
+ MXR_DEVICE_FEATURE_MESH_MASTER = (1 << 11)
25
+ MXR_DEVICE_FEATURE_STATUS_NOTIFY = (1 << 12)
26
+ MXR_DEVICE_FEATURE_STATUS_WARNING = (1 << 13)
27
+ MXR_DEVICE_FEATURE_STATUS_ERROR = (1 << 14)
28
+ MXR_DEVICE_FEATURE_STATUS_REBOOTING = (1 << 15)
29
+ MXR_DEVICE_FEATURE_MESH_MEMBER = (1 << 16)
30
+ MXR_DEVICE_FEATURE_AUDIO_AMPLIFIER = (1 << 17)
31
+ MXR_DEVICE_FEATURE_BOOTING = (1 << 18)
32
+ MXR_DEVICE_FEATURE_MANAGER = (1 << 19)
33
+ MXR_DEVICE_FEATURE_BOOT_BIT = (1 << 31)
34
+
35
+ MX_BAY_FEATURE_HDMI_OUT = (1 << 0)
36
+ MX_BAY_FEATURE_HDMI_IN = (1 << 1)
37
+ MX_BAY_FEATURE_AUDIO_DIG_OUT = (1 << 2)
38
+ MX_BAY_FEATURE_AUDIO_DIG_IN = (1 << 3)
39
+ MX_BAY_FEATURE_AUDIO_ANA_OUT = (1 << 4)
40
+ MX_BAY_FEATURE_AUDIO_ANA_IN = (1 << 5)
41
+ MX_BAY_FEATURE_IR_IN = (1 << 6)
42
+ MX_BAY_FEATURE_IR_OUT = (1 << 7)
43
+ MX_BAY_FEATURE_AUDIO_AMP_OUT = (1 << 8)
44
+ MX_BAY_FEATURE_RC_OUT = (1 << 9)
45
+ MX_BAY_FEATURE_RC_IN = (1 << 10)
46
+ MX_BAY_FEATURE_DOLBY = (1 << 11)
47
+ MX_BAY_FEATURE_AUTO_OFF = (1 << 12)
48
+ MX_BAY_FEATURE_V2IP_SOURCE_REMOTE = (1 << 13)
49
+ MX_BAY_FEATURE_V2IP_SINK_REMOTE = (1 << 14)
50
+ MX_BAY_FEATURE_V2IP_SOURCE_LOCAL = (1 << 15)
51
+ MX_BAY_FEATURE_V2IP_SINK_LOCAL = (1 << 16)
52
+ MX_BAY_FEATURE_DOLBY_IN_POS = 24
53
+
54
+ MX_LINK_FEATURE_NONE = 0
55
+ MX_LINK_FEATURE_VIDEO_HDMI = (1 << 0)
56
+ MX_LINK_FEATURE_AUDIO_OPTICAL = (1 << 1)
57
+ MX_LINK_FEATURE_AUDIO_ANALOG = (1 << 2)
58
+ MX_LINK_FEATURE_IR = (1 << 3)
59
+ MX_LINK_FEATURE_RC = (1 << 4)
60
+
61
+ class RCAction(Enum):
62
+ ACTION_POWER_TOGGLE = 0
63
+ ACTION_POWER_ON = 1
64
+ ACTION_POWER_OFF = 2
65
+ ACTION_VOLUME_DOWN = 3
66
+ ACTION_VOLUME_UP = 4
67
+ ACTION_VOLUME_MUTE = 5
68
+
69
+ class RCKey(Enum):
70
+ KEY_0 = 0
71
+ KEY_1 = 1
72
+ KEY_2 = 2
73
+ KEY_3 = 3
74
+ KEY_4 = 4
75
+ KEY_5 = 5
76
+ KEY_6 = 6
77
+ KEY_7 = 7
78
+ KEY_8 = 8
79
+ KEY_9 = 9
80
+ KEY_SELECT = 10
81
+ KEY_BACK = 11
82
+ KEY_UP = 12
83
+ KEY_DOWN = 13
84
+ KEY_LEFT = 14
85
+ KEY_RIGHT = 15
86
+ KEY_MENU = 16
87
+ KEY_CONTENT_MENU = 17
88
+ KEY_CHANNEL_UP = 18
89
+ KEY_CHANNEL_DOWN = 19
90
+ KEY_PLAY = 20
91
+ KEY_PAUSE = 21
92
+ KEY_STOP = 22
93
+ KEY_RECORD = 23
94
+ KEY_FAST_FORWARD = 24
95
+ KEY_REWIND = 25
96
+ KEY_RED = 26
97
+ KEY_GREEN = 27
98
+ KEY_YELLOW = 28
99
+ KEY_BLUE = 29
100
+ KEY_HELP = 30
101
+ KEY_INFORMATION = 31
102
+ KEY_TEXT = 32
103
+ KEY_GUIDE = 33
104
+ KEY_VIDEO_ON_DEMAND = 34
105
+ KEY_PREVIOUS_CHANNEL = 80
106
+ KEY_3D_MODE = 81
107
+ KEY_SUBTITLE = 82
108
+ KEY_SOUND_SELECT = 83
109
+ KEY_INPUT_SELECT = 84
110
+ KEY_EJECT = 85
111
+ KEY_NEXT_CHAPTER = 86
112
+ KEY_PREVIOUS_CHAPTER = 87
113
+ KEY_CUSTOM_CEC = 1280
114
+ KEY_INTERACTIVE = 128
115
+ KEY_SEARCH = 129
116
+ KEY_SKY = 130
117
+ KEY_CUSTOM_SKY = 2048
118
+
119
+ class BayStatusMask:
120
+ FAULT = (1 << 0)
121
+ HIDDEN = (1 << 1)
122
+ POWERED = (1 << 2)
123
+ SIGNAL_DETECTED = (1 << 3)
124
+ HPD_DETECTED = (1 << 4)
125
+ SIGNAL_SCRAMBLED =(1 << 5)
126
+ HDBT_CONNECTED = (1 << 6)
127
+ CEC_DETECTED = (1 << 7)
128
+ POWERED_ON = (1 << 8)
129
+ POWERED_OFF = (1 << 9)
130
+ AUDIO_ARC_HDMI = (1 << 10)
131
+ AUDIO_ARC_OPTICAL = (1 << 11)
132
+ AUDIO_ARC_ANALOG = (1 << 12)
133
+ OFFLINE = (1 << 13)
134
+ DECODER_DISABLED = (1 << 14)
135
+ ENCODER_DISABLED = (1 << 15)
136
+
137
+ def __init__(self, mask:int):
138
+ self._mask = mask
139
+
140
+ @property
141
+ def fault(self) -> bool:
142
+ # bay is faulty
143
+ return (self._mask & self.FAULT) != 0
144
+
145
+ @property
146
+ def hidden(self) -> bool:
147
+ # bay is hidden
148
+ return (self._mask & self.HIDDEN) != 0
149
+
150
+ @property
151
+ def powered(self) -> bool:
152
+ # PoE powered on
153
+ return (self._mask & self.POWERED) != 0
154
+
155
+ @property
156
+ def signal_detected(self) -> bool:
157
+ # signal detected
158
+ return (self._mask & self.SIGNAL_DETECTED) != 0
159
+
160
+ @property
161
+ def hpd_detected(self) -> bool:
162
+ # hotplug detected
163
+ return (self._mask & self.HPD_DETECTED) != 0
164
+
165
+ @property
166
+ def hdbt_connected(self) -> bool:
167
+ # HDBaseT connected
168
+ return (self._mask & self.HDBT_CONNECTED) != 0
169
+
170
+ @property
171
+ def cec_detected(self) -> bool:
172
+ # CEC capable device connected to this bay
173
+ return (self._mask & self.CEC_DETECTED) != 0
174
+
175
+ @property
176
+ def powered_on(self) -> bool:
177
+ # device is powered on
178
+ return (self._mask & self.POWERED_ON) != 0
179
+
180
+ @property
181
+ def powered_off(self) -> bool:
182
+ # device is powered off
183
+ return (self._mask & self.POWERED_OFF) != 0
184
+
185
+ @property
186
+ def audio_arc_hdmi(self) -> bool:
187
+ # HDMI ARC active
188
+ return (self._mask & self.AUDIO_ARC_HDMI) != 0
189
+
190
+ @property
191
+ def audio_arc_optical(self) -> bool:
192
+ # optical ARC active
193
+ return (self._mask & self.AUDIO_ARC_OPTICAL) != 0
194
+
195
+ @property
196
+ def audio_arc_analog(self) -> bool:
197
+ # analog ARC active
198
+ return (self._mask & self.AUDIO_ARC_ANALOG) != 0
199
+
200
+ @property
201
+ def offline(self) -> bool:
202
+ # v2ip unit offline
203
+ return (self._mask & self.OFFLINE) != 0
204
+
205
+ @property
206
+ def decoder_disabled(self) -> bool:
207
+ # v2ip decoder disabled
208
+ return (self._mask & self.DECODER_DISABLED) != 0
209
+
210
+ @property
211
+ def encoder_disabled(self) -> bool:
212
+ # v2ip decoder disabled
213
+ return (self._mask & self.ENCODER_DISABLED) != 0
214
+
215
+ def __str__(self) -> str:
216
+ rv = ""
217
+ if self.fault:
218
+ rv += " [fault]"
219
+ if self.hidden:
220
+ rv += " [hidden]"
221
+ if self.powered:
222
+ rv += " [powered]"
223
+ if self.signal_detected:
224
+ rv += " [signal]"
225
+ if self.hpd_detected:
226
+ rv += " [hpd]"
227
+ if self.hdbt_connected:
228
+ rv += " [hdbt]"
229
+ if self.cec_detected:
230
+ rv += " [cec]"
231
+ if self.powered_on:
232
+ rv += " [powered on]"
233
+ if self.powered_off:
234
+ rv += " [powered off]"
235
+ if self.audio_arc_hdmi:
236
+ rv += " [hdmi arc]"
237
+ if self.audio_arc_optical:
238
+ rv += " [optical arc]"
239
+ if self.audio_arc_analog:
240
+ rv += " [analog arc]"
241
+ if self.offline:
242
+ rv += " [offline]"
243
+ if self.decoder_disabled:
244
+ rv += " [decoder disabled]"
245
+ if self.encoder_disabled:
246
+ rv += " [encoder disabled]"
247
+ if len(rv) != 0:
248
+ return rv[1:]
249
+ return "[none]"
250
+
251
+ def __eq__(self, mask:Any) -> bool:
252
+ if isinstance(mask, int):
253
+ return self._mask == mask
254
+ if isinstance(mask, BayStatusMask):
255
+ return self._mask == mask._mask
256
+ return False
257
+
258
+ def __ne__(self, mask:Any) -> bool:
259
+ return not (self == mask)
260
+
261
+
262
+ class RCType(Enum):
263
+ IR = 0
264
+ CEC = 1
265
+ SKY_UK = 2
266
+ TIVO = 3
267
+ KODI = 4
268
+ DISH = 5
269
+ DIRECTV = 6
270
+ MX_REMOTE = 7
271
+
272
+ def __str__(self) -> str:
273
+ if self.value == RCType.IR.value:
274
+ return "IR"
275
+ if self.value == RCType.CEC.value:
276
+ return "CEC"
277
+ if self.value == RCType.SKY_UK.value:
278
+ return "Sky"
279
+ if self.value == RCType.TIVO.value:
280
+ return "TiVo"
281
+ if self.value == RCType.KODI.value:
282
+ return "Kodi"
283
+ if self.value == RCType.DISH.value:
284
+ return "Dish"
285
+ if self.value == RCType.DIRECTV.value:
286
+ return "DirecTV"
287
+ if self.value == RCType.MX_REMOTE.value:
288
+ return "MX-Remote"
289
+ return "Unknown"
290
+
291
+ def values() -> dict[int, str]:
292
+ rv:dict[int, str] = {}
293
+ for val in range(8):
294
+ rv[val] = str(RCType(val))
295
+ return rv
296
+
297
+ class EdidProfile(Enum):
298
+ def __str__(self) -> str:
299
+ if self.value == EdidProfile.TEMPLATE_1080P_STEREO.value:
300
+ return '1080p stereo'
301
+ if self.value == EdidProfile.FIXED.value:
302
+ return 'fixed'
303
+ if self.value == EdidProfile.TEMPLATE_4K.value:
304
+ return '4K'
305
+ if self.value == EdidProfile.TEMPLATE_1080P_5_1.value:
306
+ return '1080p 5.1'
307
+ if self.value == EdidProfile.TEMPLATE_720P.value:
308
+ return '720p'
309
+ if self.value == EdidProfile.TEMPLATE_1080P_7_1.value:
310
+ return '1080p 7.1'
311
+ if self.value == EdidProfile.TEMPLATE_4K_HDR_STEREO.value:
312
+ return '4K HDR Stereo'
313
+ if self.value == EdidProfile.TEMPLATE_4K_HDR_7_1.value:
314
+ return '4K HDR 7.1'
315
+ if self.value == EdidProfile.TEMPLATE_4K_HDR_AVR_ONLY.value:
316
+ return '4K HDR AVR'
317
+ if self.value == EdidProfile.LOWEST_COMMON_DENOMINATOR.value:
318
+ return 'lowest common denominator'
319
+ if self.value == EdidProfile.LOWEST_COMMON_DENOMINATOR_ALL.value:
320
+ return 'lowest common denominator (all sinks)'
321
+ if self.value == EdidProfile.TEMPLATE_4K_HDR_ATMOS.value:
322
+ return '4K HDR Dolby Atmos'
323
+ if (self.value >= EdidProfile.SINK_1.value) and (self.value <= EdidProfile.SINK_32.value):
324
+ return f'copy from sink #{self.value - EdidProfile.SINK_1.value + 1}'
325
+ return "unknown"
326
+
327
+ TEMPLATE_1080P_STEREO = 0
328
+ FIXED = 1
329
+ TEMPLATE_4K = 2
330
+ TEMPLATE_1080P_5_1 = 3
331
+ TEMPLATE_720P = 4
332
+ TEMPLATE_1080P_7_1 = 5
333
+ TEMPLATE_4K_7_1 = 6
334
+ TEMPLATE_4K_HDR_STEREO = 7
335
+ TEMPLATE_4K_HDR_7_1 = 8
336
+ TEMPLATE_4K_HDR_AVR_ONLY = 9
337
+ LOWEST_COMMON_DENOMINATOR = 10
338
+ LOWEST_COMMON_DENOMINATOR_ALL = 11
339
+ TEMPLATE_4K_HDR_ATMOS = 12
340
+ SINK_1 = 101
341
+ SINK_2 = 102
342
+ SINK_3 = 103
343
+ SINK_4 = 104
344
+ SINK_5 = 105
345
+ SINK_6 = 106
346
+ SINK_7 = 107
347
+ SINK_8 = 108
348
+ SINK_9 = 109
349
+ SINK_10 = 110
350
+ SINK_11 = 111
351
+ SINK_12 = 112
352
+ SINK_13 = 113
353
+ SINK_14 = 114
354
+ SINK_15= 115
355
+ SINK_16 = 116
356
+ SINK_17 = 117
357
+ SINK_18 = 118
358
+ SINK_19 = 119
359
+ SINK_20 = 120
360
+ SINK_21 = 121
361
+ SINK_22 = 122
362
+ SINK_23 = 123
363
+ SINK_24 = 124
364
+ SINK_25 = 125
365
+ SINK_26 = 126
366
+ SINK_27 = 127
367
+ SINK_28 = 128
368
+ SINK_29 = 129
369
+ SINK_30 = 130
370
+ SINK_31 = 131
371
+ SINK_32 = 132
372
+ UNKNOWN = 0xFFF
373
+
374
+ def values(nb_sinks:int|None=None) -> dict[int, str]:
375
+ rv:dict[int, str] = {}
376
+ for val in range(13):
377
+ rv[val] = str(EdidProfile(val))
378
+ if nb_sinks is None:
379
+ nb_sinks = 32
380
+ for val in range(101, 101 + nb_sinks):
381
+ rv[val] = str(EdidProfile(val))
382
+ return rv
@@ -0,0 +1,142 @@
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
+ class MuteStatus:
9
+ ''' mute left/right status '''
10
+ def __init__(self, val:int):
11
+ self._val = val
12
+
13
+ @property
14
+ def value(self) -> int:
15
+ return self._val
16
+
17
+ @property
18
+ def left(self) -> bool:
19
+ # left channel muted
20
+ return ((self._val & (1 << 0)) != 0)
21
+
22
+ @property
23
+ def right(self) -> bool:
24
+ # right channel muted
25
+ return ((self._val & (1 << 1)) != 0)
26
+
27
+ @property
28
+ def muted(self) -> bool:
29
+ # left or right channel muted (or both)
30
+ return (self._val != 0)
31
+
32
+ def __str__(self) -> str:
33
+ return f"left:{self.left} right:{self.right}"
34
+
35
+ class VolumeMuteStatus:
36
+ ''' volume and mute status '''
37
+ def __init__(self, volume_left:int, volume_right:int, muted_left:bool=None, muted_right:bool=None):
38
+ self._volume_left = volume_left
39
+ self._volume_right = volume_right
40
+ self._muted_left = muted_left
41
+ self._muted_right = muted_right
42
+
43
+ @property
44
+ def value(self) -> bytes:
45
+ return bytes(bytearray([
46
+ self.volume_left,
47
+ self.volume_right,
48
+ self.muted_value,
49
+ ]))
50
+
51
+ @property
52
+ def muted_value(self) -> int:
53
+ if not self.muted:
54
+ return 0
55
+ if self.muted_left and self.muted_right:
56
+ return 3
57
+ if self.muted_left:
58
+ return 1
59
+ return 2
60
+
61
+ @property
62
+ def volume(self) -> int:
63
+ # combined left/right volume %
64
+ vr = self._volume_right
65
+ if vr is None:
66
+ return self._volume_left if self._volume_left is not None else 0
67
+ return int((vr + self._volume_left) / 2.0)
68
+
69
+ @volume.setter
70
+ def volume(self, volume:int) -> None:
71
+ self.volume_left = volume
72
+ if self._volume_right is not None:
73
+ self.volume_right = volume
74
+
75
+ @property
76
+ def volume_left(self) -> int:
77
+ # left channel volume
78
+ return self._volume_left if (self._volume_left is not None) else self.volume
79
+
80
+ @volume_left.setter
81
+ def volume_left(self, volume:int) -> None:
82
+ self._volume_left = volume
83
+
84
+ @property
85
+ def volume_right(self) -> int:
86
+ # right channel volume
87
+ return self._volume_right if (self._volume_right is not None) else self.volume
88
+
89
+ @volume_right.setter
90
+ def volume_right(self, volume:int) -> None:
91
+ self._volume_right = volume
92
+
93
+ @property
94
+ def muted(self) -> bool:
95
+ # combined mute left/right status
96
+ if (self._muted_left is None) and (self._muted_right is None):
97
+ return None
98
+ return ((self._muted_left is not None) and self._muted_left) or \
99
+ ((self._muted_right is not None) and self._muted_right)
100
+
101
+ @muted.setter
102
+ def muted(self, muted:bool) -> None:
103
+ self.muted_left = muted
104
+ if self._muted_right is not None:
105
+ self.muted_right = muted
106
+
107
+ @property
108
+ def muted_left(self) -> bool:
109
+ # left channel muted
110
+ return self._muted_left if (self._muted_left is not None) else self.muted
111
+
112
+ @muted_left.setter
113
+ def muted_left(self, muted:bool) -> None:
114
+ self._muted_left = muted
115
+
116
+ @property
117
+ def muted_right(self) -> bool:
118
+ # right channel muted
119
+ return self._muted_right if (self._muted_right is not None) else self.muted
120
+
121
+ @muted_right.setter
122
+ def muted_right(self, muted:bool) -> None:
123
+ self._muted_right = muted
124
+
125
+ def update(self, other:'VolumeMuteStatus') -> bool:
126
+ changed = False
127
+ if other._volume_left is not None:
128
+ changed = changed or (self._volume_left is None) or (self._volume_left != other._volume_left)
129
+ self._volume_left = other._volume_left
130
+ if other._volume_right is not None:
131
+ changed = changed or (self._volume_right is None) or (self._volume_right != other._volume_right)
132
+ self._volume_right = other._volume_right
133
+ if other._muted_left is not None:
134
+ changed = changed or (self._muted_left is None) or (self._muted_left != other._muted_left)
135
+ self._muted_left = other._muted_left
136
+ if other._muted_right is not None:
137
+ changed = changed or (self._muted_right is None) or (self._muted_right != other._muted_right)
138
+ self._muted_right = other._muted_right
139
+ return changed
140
+
141
+ def __str__(self) -> str:
142
+ return f"volume left:{self.volume_left} right:{self.volume_right} muted:{self.muted}"
@@ -0,0 +1,168 @@
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
+ ''' Methods for creating and processing frames '''
9
+ from .FrameBase import FrameBase
10
+ from .FrameHeader import FrameHeader
11
+ from ..Interface import DeviceRegistry
12
+ import logging
13
+ import traceback
14
+
15
+ logging.basicConfig(level=logging.DEBUG)
16
+
17
+ def create_mxr_frame(uid:bytes, opcode:int, payload:bytes=None) -> bytes:
18
+ # create a new mx_remote frame for transmission
19
+ pkt = [80, 56, 1, 0 ]
20
+ pkt.extend(uid)
21
+ pkt.extend([(opcode & 0xFF), ((opcode >> 8) & 0xFF)])
22
+ if payload is None or len(payload) == 0:
23
+ pkt.extend([0, 0])
24
+ else:
25
+ l = len(payload)
26
+ pkt.extend([(l & 0xFF), ((l >> 8) & 0xFF)])
27
+ pkt.extend([payload])
28
+ return bytes(pkt)
29
+
30
+ def process_mxr_frame(mxr:DeviceRegistry, data:bytes, addr:tuple[str,int]) -> FrameBase:
31
+ # decode a (received) mx_remote frame
32
+ from .FrameHeader import FrameHeader
33
+ hdr = FrameHeader(mxr, data, addr)
34
+ if hdr is not None:
35
+ try:
36
+ # valid frame
37
+ if hdr.remote_id == mxr.uid:
38
+ logging.debug("ignore frame sent by myself")
39
+ return None
40
+ # not one of my own, process the incoming frame
41
+ return _mxr_frame_factory(hdr)
42
+ except Exception:
43
+ print(f"failed to process frame: {traceback.format_exc()}")
44
+ raise
45
+ logging.warning("frame header missing")
46
+
47
+ def _mxr_frame_factory(hdr:FrameHeader) -> FrameBase:
48
+ # create a new frame from a decoded mx_remote header
49
+ if hdr.opcode == 0x00:
50
+ from .FrameHello import FrameHello
51
+ return FrameHello(hdr)
52
+ if hdr.opcode == 0x01:
53
+ from .FrameDiscover import FrameDiscover
54
+ return FrameDiscover(hdr)
55
+ if hdr.opcode == 0x02:
56
+ from .FrameBayConfig import FrameBayConfig
57
+ return FrameBayConfig(hdr)
58
+ if hdr.opcode == 0x03:
59
+ from .FrameLinks import FrameLinks
60
+ return FrameLinks(hdr)
61
+ if hdr.opcode == 0x04:
62
+ from .FrameConnectStatus import FrameConnectStatus
63
+ return FrameConnectStatus(hdr)
64
+ if hdr.opcode == 0x05:
65
+ from .FramePowerChange import FramePowerChange
66
+ return FramePowerChange(hdr)
67
+ if hdr.opcode == 0x06:
68
+ from .FrameSignalStatus import FrameSignalStatus
69
+ return FrameSignalStatus(hdr)
70
+ if hdr.opcode == 0x07:
71
+ from .FrameEDID import FrameEDID
72
+ return FrameEDID(hdr)
73
+ if hdr.opcode == 0x08:
74
+ from .FrameRoutingChange import FrameRoutingChange
75
+ return FrameRoutingChange(hdr)
76
+ if hdr.opcode == 0x0A:
77
+ from .FrameRCIr import FrameRCIr
78
+ return FrameRCIr(hdr)
79
+ if hdr.opcode == 0x0B:
80
+ from .FrameRCKey import FrameRCKey
81
+ return FrameRCKey(hdr)
82
+ if hdr.opcode == 0x0D:
83
+ from .FrameRCAction import FrameRCAction
84
+ return FrameRCAction(hdr)
85
+ if hdr.opcode == 0x0F:
86
+ from .FrameVolumeUp import FrameVolumeUp
87
+ return FrameVolumeUp(hdr)
88
+ if hdr.opcode == 0x10:
89
+ from .FrameVolumeDown import FrameVolumeDown
90
+ return FrameVolumeDown(hdr)
91
+ if hdr.opcode == 0x12:
92
+ from .FrameVolume import FrameVolume
93
+ return FrameVolume(hdr)
94
+ if hdr.opcode == 0x14:
95
+ from .FrameVolumeSet import FrameVolumeSet
96
+ return FrameVolumeSet(hdr)
97
+ if hdr.opcode == 0x15:
98
+ from .FrameSysTemperature import FrameSysTemperature
99
+ return FrameSysTemperature(hdr)
100
+ if hdr.opcode == 0x1F:
101
+ from .FrameV2IPSourceSwitch import FrameV2IPSourceSwitch
102
+ return FrameV2IPSourceSwitch(hdr)
103
+ if hdr.opcode == 0x16:
104
+ from .FramePDUState import FramePDUState
105
+ return FramePDUState(hdr)
106
+ if hdr.opcode == 0x20:
107
+ from .FrameV2IPLink import FrameV2IPLinkStatus
108
+ return FrameV2IPLinkStatus(hdr)
109
+ if hdr.opcode == 0x22:
110
+ from .FrameSetName import FrameSetName
111
+ return FrameSetName(hdr)
112
+ if hdr.opcode == 0x23:
113
+ from .FrameBayConfigSecondary import FrameBayConfigSecondary
114
+ return FrameBayConfigSecondary(hdr)
115
+ if hdr.opcode == 0x26:
116
+ from .FrameV2IPSources import FrameV2IPSources
117
+ return FrameV2IPSources(hdr)
118
+ if hdr.opcode == 0x27:
119
+ from .FrameBayHide import FrameBayHide
120
+ return FrameBayHide(hdr)
121
+ if hdr.opcode == 0x28:
122
+ from .FrameReboot import FrameReboot
123
+ return FrameReboot(hdr)
124
+ if hdr.opcode == 0x29:
125
+ from .FrameNetworkStatus import FrameNetworkStatus
126
+ return FrameNetworkStatus(hdr)
127
+ if hdr.opcode == 0x2A:
128
+ from .FrameFirmwareVersion import FrameFirmwareVersion
129
+ return FrameFirmwareVersion(hdr)
130
+ if hdr.opcode == 0x30:
131
+ from .FrameTopology import FrameTopology
132
+ return FrameTopology(hdr)
133
+ if hdr.opcode == 0x31:
134
+ from .FrameSignalStatusNew import FrameSignalStatusNew
135
+ return FrameSignalStatusNew(hdr)
136
+ if hdr.opcode == 0x32:
137
+ from .FrameMirrorStatus import FrameMirrorStatus
138
+ return FrameMirrorStatus(hdr)
139
+ if hdr.opcode == 0x34:
140
+ from .FrameEDIDProfile import FrameEDIDProfile
141
+ return FrameEDIDProfile(hdr)
142
+ if hdr.opcode == 0x36:
143
+ from .FrameV2IPSetMaster import FrameV2IPSetMaster
144
+ return FrameV2IPSetMaster(hdr)
145
+ if hdr.opcode == 0x38:
146
+ from .FrameFilterStatus import FrameFilterStatus
147
+ return FrameFilterStatus(hdr)
148
+ if hdr.opcode == 0x39:
149
+ from .FrameBayStatus import FrameBayStatus
150
+ return FrameBayStatus(hdr)
151
+ if hdr.opcode == 0x3B:
152
+ from .FrameMeshOperation import FrameMeshOperation
153
+ return FrameMeshOperation(hdr)
154
+ if hdr.opcode == 0x3C:
155
+ from .FrameV2IPDeviceConfiguration import FrameV2IPDeviceConfiguration
156
+ return FrameV2IPDeviceConfiguration(hdr)
157
+ if hdr.opcode == 0x3D:
158
+ from .FrameAmpZoneSettings import FrameAmpZoneSettings
159
+ return FrameAmpZoneSettings(hdr)
160
+ if hdr.opcode == 0x3E:
161
+ from .FrameAmpDolbySettings import FrameAmpDolbySettings
162
+ return FrameAmpDolbySettings(hdr)
163
+ if hdr.opcode == 0x3F:
164
+ from .FrameV2IPStats import FrameV2IPStats
165
+ return FrameV2IPStats(hdr)
166
+ logging.warning(f"opcode {hdr.opcode:02X} is not processed")
167
+ return None
168
+