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,86 @@
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
+ from .V2IPConfig import V2IPStreamSourcesData
12
+ import socket
13
+ import struct
14
+
15
+ class V2IPStreamSource:
16
+ def __init__(self, label, data):
17
+ self._label = label
18
+ self._ip = int.from_bytes(data[0:4], "big")
19
+ self._port = int(data[5]) << 8 | int(data[4])
20
+
21
+ @property
22
+ def label(self):
23
+ return self._label
24
+
25
+ @property
26
+ def ip(self):
27
+ return socket.inet_ntoa(struct.pack('!L', self._ip))
28
+
29
+ @property
30
+ def port(self):
31
+ return self._port
32
+
33
+ def __str__(self):
34
+ return f"{self.label}={self.ip}:{self.port}"
35
+
36
+ class V2IPDeviceOptions:
37
+ def __init__(self, data:bytes) -> None:
38
+ self._tx_rate = int.from_bytes(data[0:1], "little")
39
+
40
+ @property
41
+ def tx_rate(self) -> int:
42
+ return self._tx_rate
43
+
44
+ def __str__(self) -> str:
45
+ return f"tx rate: {self.tx_rate * 10}Mb/s"
46
+
47
+ class V2IPScalingSettings:
48
+ def __init__(self, data):
49
+ self.mode = data[0:2]
50
+ self.refresh = (int(data[3]) << 8) | int(data[2])
51
+ self.flags = data[4]
52
+
53
+ class FrameV2IPDeviceConfiguration(FrameBase):
54
+ def __init__(self, header:FrameHeader):
55
+ super().__init__(header)
56
+ if len(self.payload) < 61:
57
+ raise Exception(f"invalid device config len {len(self.payload)}")
58
+ self.video = V2IPStreamSource("video", self.payload[16:22])
59
+ self.audio = V2IPStreamSource("audio", self.payload[24:30])
60
+ self.anc = V2IPStreamSource("anc", self.payload[32:38])
61
+ self.options = V2IPDeviceOptions(self.payload[40:44])
62
+ self.arc = V2IPStreamSource("arc", self.payload[48:54])
63
+ self.scaling = V2IPScalingSettings(self.payload[56:61])
64
+
65
+ @property
66
+ def target_uid(self) -> str:
67
+ return MxrDeviceUid(self.payload[0:16])
68
+
69
+ @property
70
+ def target_self(self) -> bool:
71
+ return self.remote_id == self.target_uid
72
+
73
+ def process(self) -> None:
74
+ dev = self.remote_device
75
+ if dev is None:
76
+ return
77
+ dev.v2ip_details = self.options
78
+ if dev.v2ip_sources is None:
79
+ dev.v2ip_sources = [V2IPStreamSourcesData(video=self.video, audio=self.audio, anc=self.anc)]
80
+ else:
81
+ dev.v2ip_sources[0].video = self.video
82
+ dev.v2ip_sources[0].audio = self.audio
83
+ dev.v2ip_sources[0].anc = self.anc
84
+
85
+ def __str__(self) -> str:
86
+ return f"V2IP device configuration self={self.target_self} {self.video} {self.audio} {self.anc} {self.arc} options={self.options}"
@@ -0,0 +1,16 @@
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
+
11
+ class FrameV2IPLinkStatus(FrameBase):
12
+ def __init__(self, header:FrameHeader):
13
+ super().__init__(header)
14
+
15
+ def __str__(self) -> str:
16
+ return f"v2ip r/c link status"
@@ -0,0 +1,16 @@
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
+
11
+ class FrameV2IPSetMaster(FrameBase):
12
+ def __init__(self, header:FrameHeader):
13
+ super().__init__(header)
14
+
15
+ def __str__(self) -> str:
16
+ return f"v2ip master status"
@@ -0,0 +1,84 @@
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, BayBase, DeviceRegistry
11
+ from ..Uid import MxrDeviceUid
12
+ import socket
13
+ import struct
14
+ import logging
15
+
16
+ _LOGGER = logging.getLogger(__name__)
17
+
18
+ class FrameV2IPSourceSwitch(FrameBase):
19
+ def __init__(self, header:FrameHeader):
20
+ super().__init__(header)
21
+
22
+ def construct(mxr:DeviceRegistry, target:BayBase, video:BayBase|str|None=None, audio:BayBase|str|None=None) -> FrameBase:
23
+ if video is not None:
24
+ if isinstance(audio, BayBase):
25
+ if not video.is_v2ip_source:
26
+ raise Exception(f"{video} is not a v2ip source")
27
+ if video.v2ip_source is None:
28
+ raise Exception(f"{video} v2ip addresses not known")
29
+
30
+ if audio is not None:
31
+ if isinstance(audio, BayBase):
32
+ if not audio.is_v2ip_source:
33
+ raise Exception(f"{audio} is not a v2ip source")
34
+ if audio.v2ip_source is None:
35
+ raise Exception(f"{audio} v2ip addresses not known")
36
+
37
+ payload = target.device.remote_id.byte_value
38
+ if video is not None:
39
+ ip = socket.inet_aton(video.v2ip_source.video.ip if isinstance(video, BayBase) else video)
40
+ payload += bytes([int(ip[0]), int(ip[1]), int(ip[2]), int(ip[3])])
41
+ else:
42
+ payload += bytes([0, 0, 0, 0])
43
+ if audio is not None:
44
+ ip = socket.inet_aton(audio.v2ip_source.audio.ip if isinstance(audio, BayBase) else audio)
45
+ payload += bytes([int(ip[0]), int(ip[1]), int(ip[2]), int(ip[3])])
46
+ else:
47
+ payload += bytes([0, 0, 0, 0])
48
+ return FrameBase.construct_frame(mxr=mxr, opcode=0x1F, payload=payload)
49
+
50
+ @property
51
+ def target_device(self) -> DeviceBase:
52
+ return self.mxr.get_by_uid(self.target_uid)
53
+
54
+ @property
55
+ def target_uid(self) -> MxrDeviceUid:
56
+ return MxrDeviceUid(self.payload[0:16])
57
+
58
+ @property
59
+ def video(self) -> str:
60
+ ip = int.from_bytes(self.payload[16:20], "big")
61
+ return socket.inet_ntoa(struct.pack('!L', ip))
62
+
63
+ @property
64
+ def video_bay(self) -> BayBase:
65
+ return self.mxr.get_by_stream_ip(ip=self.video, audio=False)
66
+
67
+ @property
68
+ def audio(self) -> str:
69
+ ip = int.from_bytes(self.payload[20:24], "big")
70
+ return socket.inet_ntoa(struct.pack('!L', ip))
71
+
72
+ @property
73
+ def audio_bay(self) -> BayBase:
74
+ return self.mxr.get_by_stream_ip(ip=self.audio, audio=True)
75
+
76
+ def process(self):
77
+ # update the local cache
78
+ sink_bay = self.target_device.first_output
79
+ if sink_bay is not None:
80
+ sink_bay.video_source = self.video_bay
81
+ sink_bay.audio_source = self.audio_bay
82
+
83
+ def __str__(self) -> str:
84
+ return f"V2IP source switch: {self.target_device} -> {self.video}={self.video_bay}/{self.audio}={self.audio_bay}"
@@ -0,0 +1,43 @@
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 .V2IPConfig import V2IPConfig
9
+ from .FrameBase import FrameBase
10
+ from .FrameHeader import FrameHeader
11
+ from .V2IPConfig import V2IPStreamSourcesData
12
+
13
+ class FrameV2IPSources(FrameBase):
14
+ ''' All configured v2ip sources for the device that sent this frame '''
15
+ def __init__(self, header:FrameHeader):
16
+ super().__init__(header)
17
+
18
+ @property
19
+ def nb_sources(self) -> int:
20
+ # number of sources defined in this frame
21
+ return len(self) / 40
22
+
23
+ @property
24
+ def sources(self) -> list[V2IPStreamSourcesData]:
25
+ # list of all sources defined in this frame
26
+ rv = []
27
+ srcnum = 0
28
+ while srcnum < self.nb_sources:
29
+ cfg = V2IPConfig(self, srcnum, self.payload[(srcnum*40):((srcnum+1)*40)])
30
+ rv.append(V2IPStreamSourcesData(video=cfg.video, audio=cfg.audio, anc=cfg.anc))
31
+ srcnum += 1
32
+ return rv
33
+
34
+ def process(self) -> None:
35
+ dev = self.remote_device
36
+ if dev is None:
37
+ return
38
+ self.remote_device.v2ip_sources = self.sources
39
+
40
+ def __str__(self) -> str:
41
+ if len(self.sources) > 0:
42
+ return f"{str(self.remote_device)} {len(self.sources)} v2ip sources: {self.sources[0]}"
43
+ return f"{str(self.remote_device)} 0 v2ip sources"
@@ -0,0 +1,55 @@
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 .V2IPStats import V2IPRxStats, V2IPTxStats, V2IPDeviceStats
11
+ from ..Interface import DeviceBase, DeviceRegistry
12
+
13
+ class FrameV2IPStats(FrameBase):
14
+ ''' V2IP encoder/decoder statistics '''
15
+ def __init__(self, header:FrameHeader):
16
+ super().__init__(header)
17
+
18
+ def construct(registry:DeviceRegistry, device:DeviceBase, enable:bool) -> FrameBase:
19
+ payload = device.remote_id.byte_value
20
+ payload += bytes([1]) if enable else bytes([0])
21
+ frame:FrameBase = FrameBase.construct_frame(mxr=registry, opcode=0x3F)
22
+ frame.payload = payload
23
+ return frame
24
+
25
+ @property
26
+ def tx(self) -> V2IPTxStats:
27
+ return V2IPTxStats(self.payload[0:20])
28
+
29
+ @property
30
+ def tx_per_minute(self) -> V2IPTxStats:
31
+ return V2IPTxStats(self.payload[20:40])
32
+
33
+ @property
34
+ def rx(self) -> V2IPRxStats:
35
+ return V2IPRxStats(self.payload[40:84])
36
+
37
+ @property
38
+ def rx_per_minute(self) -> V2IPRxStats:
39
+ return V2IPRxStats(self.payload[84:128])
40
+
41
+ @property
42
+ def stats(self) -> V2IPDeviceStats:
43
+ rv = V2IPDeviceStats()
44
+ rv.tx = self.tx
45
+ rv.tx_per_minute = self.tx_per_minute
46
+ rv.rx = self.rx
47
+ rv.rx_per_minute = self.rx_per_minute
48
+ return rv
49
+
50
+ def process(self) -> None:
51
+ if self.remote_device is not None:
52
+ self.remote_device.v2ip_stats = self.stats
53
+
54
+ def __str__(self) -> str:
55
+ return f"{str(self.remote_device)} v2ip stats: {self.stats}"
@@ -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 .V2IPConfig import V2IPConfig
9
+ from .FrameBase import FrameBase
10
+ from .FrameHeader import FrameHeader
11
+ from ..Interface import V2IPStreamSource
12
+
13
+ class FrameV2IPStreamDetails(FrameBase):
14
+ ''' All configured v2ip sources for the device that sent this frame '''
15
+ def __init__(self, header:FrameHeader):
16
+ super().__init__(header)
17
+ self.video = V2IPStreamSource("video", self.payload[0:6])
18
+ self.audio = V2IPStreamSource("audio", self.payload[8:14])
19
+ self.anc = V2IPStreamSource("anc", self.payload[16:22])
20
+ self.arc = V2IPStreamSource("arc", self.payload[24:30])
21
+
22
+ @property
23
+ def sources(self) -> list[V2IPConfig]:
24
+ # list of all sources defined in this frame
25
+ rv = []
26
+ srcnum = 0
27
+ while srcnum < self.nb_sources:
28
+ cfg = V2IPConfig(self, srcnum, self.payload[(srcnum*56):((srcnum+1)*56)])
29
+ rv.append(cfg)
30
+ srcnum += 1
31
+ return rv
32
+
33
+ def process(self) -> None:
34
+ dev = self.remote_device
35
+ first_in = dev.first_input
36
+ if first_in is not None:
37
+ first_in.v2ip.video = self.video
38
+ first_in.v2ip.audio = self.audio
39
+ first_in.v2ip.anc = self.anc
40
+ first_out = dev.first_output
41
+ if first_out is not None:
42
+ first_out.v2ip.arc = self.arc
43
+ #dev.on_v2ip_source_config_received()
44
+
45
+ def __str__(self) -> str:
46
+ return f"{str(self.remote_device)} v2ip stream details: {self.video} {self.audio} {self.anc} {self.arc}"
@@ -0,0 +1,62 @@
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
+ from .FrameBase import FrameBase
10
+ from .FrameHeader import FrameHeader
11
+ from .Data import VolumeMuteStatus, MuteStatus
12
+ from ..Interface import BayBase
13
+
14
+ class FrameVolume(FrameBase):
15
+ ''' bay volume change information frame '''
16
+ def __init__(self, header:FrameHeader):
17
+ super().__init__(header)
18
+
19
+ @property
20
+ def bay(self) -> BayBase:
21
+ # bay on which the volume changed
22
+ portnum = self.payload[0]
23
+ dev = self.remote_device
24
+ if dev is None:
25
+ return
26
+ return dev.get_by_portnum(portnum)
27
+
28
+ @property
29
+ def volume_left(self) -> int:
30
+ # left channel volume %
31
+ r = int(self.payload[1])
32
+ if r > 100:
33
+ return None
34
+ return r
35
+
36
+ @property
37
+ def volume_right(self) -> int:
38
+ # right channel volume %
39
+ r = int(self.payload[2])
40
+ if r > 100:
41
+ return None
42
+ return r
43
+
44
+ @property
45
+ def muted(self) -> MuteStatus:
46
+ # mute status
47
+ if len(self) < 4:
48
+ return None
49
+ return MuteStatus(self.payload[3])
50
+
51
+ def process(self) -> None:
52
+ # update the local cache
53
+ bay = self.bay
54
+ if bay is None:
55
+ return
56
+ muted = self.muted
57
+ muted_left = muted.left if (muted is not None) else None
58
+ muted_right = muted.right if (muted is not None) else None
59
+ bay.on_mxr_volume_update(VolumeMuteStatus(self.volume_left, self.volume_right, muted_left, muted_right))
60
+
61
+ def __str__(self) -> str:
62
+ return f"volume bay:{str(self.bay)} volume:{self.volume_left}/{self.volume_right} muted:{self.muted}"
@@ -0,0 +1,28 @@
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
+
11
+ class FrameVolumeDown(FrameBase):
12
+ ''' volume down pressed frame '''
13
+ def __init__(self, header:FrameHeader):
14
+ super().__init__(header)
15
+
16
+ @property
17
+ def bay(self):
18
+ portnum = self.payload[0]
19
+ dev = self.remote_device
20
+ if dev is None:
21
+ return
22
+ return dev.get_by_portnum(portnum)
23
+
24
+ def process(self):
25
+ pass
26
+
27
+ def __str__(self):
28
+ return f"volume down bay: {self.bay} (port {self.payload[0]})"
@@ -0,0 +1,81 @@
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
+ from .FrameBase import FrameBase
10
+ from .FrameHeader import FrameHeader
11
+ from .Data import VolumeMuteStatus, MuteStatus
12
+ from ..Interface import BayBase, DeviceBase, DeviceRegistry
13
+ from .FrameBase import append_payload_str
14
+ from ..Uid import MxrDeviceUid
15
+
16
+ class FrameVolumeSet(FrameBase):
17
+ ''' bay volume change information frame '''
18
+ def __init__(self, header:FrameHeader):
19
+ super().__init__(header)
20
+
21
+ def construct(mxr:DeviceRegistry, target:BayBase, volume:VolumeMuteStatus) -> FrameBase:
22
+ payload = bytearray()
23
+ payload += target.device.remote_id.byte_value
24
+ payload.append(target.port & 0xFF)
25
+ payload.append((target.port >> 8) & 0xFF)
26
+ payload += volume.value
27
+ payload += bytes([0, 0, 0]) # padding
28
+ frame:FrameBase = FrameBase.construct_frame(mxr=mxr, opcode=0x14, protocol=0x11)
29
+ frame.payload = bytes(payload)
30
+ return frame
31
+
32
+ @property
33
+ def target_device(self) -> DeviceBase:
34
+ return self.mxr.get_by_uid(self.target_uid)
35
+
36
+ @property
37
+ def target_uid(self) -> MxrDeviceUid:
38
+ return MxrDeviceUid(self.payload[0:16])
39
+
40
+ @property
41
+ def bay(self) -> BayBase:
42
+ # bay on which the volume changed
43
+ portnum = ((self.payload[17] << 8) | self.payload[16])
44
+ dev = self.remote_device
45
+ if dev is None:
46
+ return
47
+ return dev.get_by_portnum(portnum)
48
+
49
+ @property
50
+ def volume_left(self) -> int:
51
+ # left channel volume %
52
+ r = int(self.payload[18])
53
+ if r > 100:
54
+ return None
55
+ return r
56
+
57
+ @property
58
+ def volume_right(self) -> int:
59
+ # right channel volume %
60
+ r = int(self.payload[19])
61
+ if r > 100:
62
+ return None
63
+ return r
64
+
65
+ @property
66
+ def muted(self) -> MuteStatus:
67
+ # mute status
68
+ return MuteStatus(self.payload[20])
69
+
70
+ def process(self) -> None:
71
+ # update the local cache
72
+ bay = self.bay
73
+ if bay is None:
74
+ return
75
+ muted = self.muted
76
+ muted_left = muted.left if (muted is not None) else None
77
+ muted_right = muted.right if (muted is not None) else None
78
+ bay.on_mxr_volume_update(VolumeMuteStatus(self.volume_left, self.volume_right, muted_left, muted_right))
79
+
80
+ def __str__(self) -> str:
81
+ return f"volume bay:{str(self.bay)} volume:{self.volume_left}/{self.volume_right} muted:{self.muted}"
@@ -0,0 +1,28 @@
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
+
11
+ class FrameVolumeUp(FrameBase):
12
+ ''' volume up pressed frame '''
13
+ def __init__(self, header:FrameHeader):
14
+ super().__init__(header)
15
+
16
+ @property
17
+ def bay(self):
18
+ portnum = self.payload[0]
19
+ dev = self.remote_device
20
+ if dev is None:
21
+ return
22
+ return dev.get_by_portnum(portnum)
23
+
24
+ def process(self):
25
+ pass
26
+
27
+ def __str__(self):
28
+ return f"volume up bay: {self.bay} (port {self.payload[0]})"
@@ -0,0 +1,121 @@
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 typing import List
9
+ from .FrameBase import FrameBase
10
+ from ..Interface import DeviceBase, BayBase
11
+ import struct
12
+
13
+ class LinkConfig:
14
+ ''' Single link configuration '''
15
+ def __init__(self, frame:FrameBase, payload:bytes):
16
+ self.frame = frame
17
+ self.payload = payload
18
+ self._confirm:'LinkConfig'|None = None
19
+
20
+ @property
21
+ def remote_device(self) -> DeviceBase:
22
+ # device instance of the device that sent this frame
23
+ return self.frame.remote_device
24
+
25
+ @property
26
+ def remote_port(self) -> int:
27
+ # remote port number
28
+ return int(self.payload[0])
29
+
30
+ @property
31
+ def remote_bay(self) -> BayBase:
32
+ # bay instance of the bay that sent this link configuration
33
+ dev = self.remote_device
34
+ if dev is None:
35
+ return
36
+ return dev.get_by_portnum(self.remote_port)
37
+
38
+ @property
39
+ def auto_config(self) -> bool:
40
+ # auto-configuration enabled
41
+ return (int(self.payload[1]) == 1)
42
+
43
+ @property
44
+ def linked_serial(self) -> str:
45
+ # serial number of the device linked to this bay, or an empty string if not linked
46
+ return self.payload[2:18].split(b'\0',1)[0].decode('ascii')
47
+
48
+ @property
49
+ def linked_bay_name(self) -> str:
50
+ # name of the bay linked to this bay, or an empty string if not linked
51
+ return self.payload[18:34].split(b'\0',1)[0].decode('ascii')
52
+
53
+ @property
54
+ def features(self) -> int:
55
+ # supported features bitmask for this link
56
+ return struct.unpack('<L', self.payload[34:38])[0]
57
+
58
+ @property
59
+ def is_linked(self) -> bool:
60
+ # bay linked or not
61
+ return (len(self.linked_serial) != 0) and (len(self.linked_bay_name) != 0)
62
+
63
+ @property
64
+ def linked_device(self) -> DeviceBase:
65
+ # device instance of the device that's linked to this bay
66
+ if not self.is_linked:
67
+ return None
68
+ return self.frame.mxr.get_by_serial(self.linked_serial)
69
+
70
+ @property
71
+ def linked_bay(self) -> BayBase:
72
+ # bay instance of the bay that's linked to this bay
73
+ dev = self.linked_device
74
+ if dev is None:
75
+ return None
76
+ return dev.get_by_portname(self.linked_bay_name)
77
+
78
+ @property
79
+ def bays(self) -> List[BayBase]:
80
+ linked_bay = self.linked_bay
81
+ if linked_bay is not None:
82
+ return [ self.remote_bay, linked_bay ]
83
+ return [ self.remote_bay ]
84
+
85
+ @property
86
+ def connected(self) -> bool:
87
+ if not self.is_linked:
88
+ return False
89
+ return self._confirm is not None
90
+
91
+ @property
92
+ def online(self) -> bool:
93
+ if not self.is_linked:
94
+ return False
95
+ if self._confirm is None:
96
+ return False
97
+ return self.remote_bay.online and self.linked_bay.online
98
+
99
+ def is_linked_to(self, bay:BayBase) -> bool:
100
+ if self.remote_bay == bay:
101
+ return True
102
+ linked_bay = self.linked_bay
103
+ if linked_bay is None:
104
+ return False
105
+ return linked_bay == bay
106
+
107
+ def other_bay(self, bay:BayBase) -> BayBase|None:
108
+ if not self.is_linked:
109
+ return None
110
+ if self.remote_bay == bay:
111
+ return self.linked_bay
112
+ return self.remote_bay
113
+
114
+ def process(self) -> None:
115
+ # register or update this link in the local cache
116
+ self.frame.mxr.links.update(bay=self.remote_bay, linked_serial=self.linked_serial, linked_bay=self.linked_bay_name, features=self.features)
117
+
118
+ def __str__(self) -> str:
119
+ if not self.is_linked:
120
+ return "{} not linked".format(str(self.remote_bay))
121
+ return "{} link serial:{} remote bay:{} features:{}".format(str(self.remote_bay), self.linked_serial, self.linked_bay_name, str(self.features))