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,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))
|