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,92 @@
|
|
|
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 ..Uid import MxrDeviceUid
|
|
9
|
+
from ..Interface import DeviceRegistry
|
|
10
|
+
|
|
11
|
+
class FrameHeader:
|
|
12
|
+
''' Header of an mx_remote frame '''
|
|
13
|
+
def __init__(self, mxr:DeviceRegistry, data: bytes, addr: tuple[str, int]):
|
|
14
|
+
self._mxr = mxr
|
|
15
|
+
self.data = data
|
|
16
|
+
self.addr = addr
|
|
17
|
+
if len(data) < 24:
|
|
18
|
+
raise Exception(f'invalid mx_remote frame (length = {len(data)})')
|
|
19
|
+
if (data[0] != 80) or (data[1] != 56):
|
|
20
|
+
raise Exception(f'invalid mx_remote frame (header = {int(data[0])}:{int(data[1])})')
|
|
21
|
+
|
|
22
|
+
def construct(mxr:DeviceRegistry, opcode:int, protocol:int=1) -> 'FrameHeader':
|
|
23
|
+
# create a new mx_remote frame for transmission
|
|
24
|
+
pkt = [80, 56, protocol, 0 ]
|
|
25
|
+
pkt.extend(mxr.uid_raw)
|
|
26
|
+
pkt.extend([(opcode & 0xFF), ((opcode >> 8) & 0xFF)])
|
|
27
|
+
pkt.extend([0, 0])
|
|
28
|
+
return FrameHeader(mxr, bytes(pkt), ("", 0))
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def mxr(self) -> DeviceRegistry:
|
|
32
|
+
return self._mxr
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def protocol(self) -> int:
|
|
36
|
+
# frame protocol version
|
|
37
|
+
if len(self) < 4:
|
|
38
|
+
return 255
|
|
39
|
+
return (int(self.data[3]) << 8) | int(self.data[2])
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def remote_id(self) -> MxrDeviceUid:
|
|
43
|
+
# unique id of the device that sent this frame
|
|
44
|
+
return MxrDeviceUid(self.data[4:20])
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def remote_id_raw(self) -> bytes:
|
|
48
|
+
# unique id of the device that sent this frame
|
|
49
|
+
if len(self) < 20:
|
|
50
|
+
return None
|
|
51
|
+
return self.data[4:20]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def opcode(self) -> int:
|
|
55
|
+
# command opcode
|
|
56
|
+
if len(self) < 22:
|
|
57
|
+
return -1
|
|
58
|
+
return (int(self.data[21]) << 8) | int(self.data[20])
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def payload_len(self) -> int:
|
|
62
|
+
# number of payload bytes
|
|
63
|
+
if len(self) < 24:
|
|
64
|
+
return 0
|
|
65
|
+
return (int(self.data[23]) << 8) | int(self.data[22])
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def payload(self) -> bytes:
|
|
69
|
+
# frame payload bytes
|
|
70
|
+
if len(self) < 25:
|
|
71
|
+
return None
|
|
72
|
+
return self.data[24:]
|
|
73
|
+
|
|
74
|
+
@payload.setter
|
|
75
|
+
def payload(self, val:bytes) -> None:
|
|
76
|
+
data = list(self.data[0:24])
|
|
77
|
+
if (val is None) or (len(val) == 0):
|
|
78
|
+
data[22] = 0
|
|
79
|
+
data[23] = 0
|
|
80
|
+
else:
|
|
81
|
+
l = len(val)
|
|
82
|
+
data[22] = (l & 0xFF)
|
|
83
|
+
data[23] = ((l >> 8) & 0xFF)
|
|
84
|
+
data += val
|
|
85
|
+
self.data = bytes(data)
|
|
86
|
+
|
|
87
|
+
def __len__(self) -> int:
|
|
88
|
+
# number of bytes in this frame
|
|
89
|
+
return len(self.data)
|
|
90
|
+
|
|
91
|
+
def __str__(self) -> str:
|
|
92
|
+
return f"proto: {self.protocol} op: {self.opcode} len: {self.payload_len}"
|
|
@@ -0,0 +1,77 @@
|
|
|
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 ..const import __version__
|
|
9
|
+
from .Constants import MXR_PROTOCOL_VERSION, MXR_DEVICE_FEATURE_MANAGER
|
|
10
|
+
from .FrameBase import FrameBase, append_payload_str
|
|
11
|
+
from .FrameHeader import FrameHeader
|
|
12
|
+
from ..Interface import DeviceRegistry, DeviceFeatures
|
|
13
|
+
import struct
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
class FrameHello(FrameBase):
|
|
17
|
+
''' Hello frame, sent by devices to advertise themselves on the network '''
|
|
18
|
+
def __init__(self, header:FrameHeader):
|
|
19
|
+
super().__init__(header)
|
|
20
|
+
|
|
21
|
+
def construct(mxr:DeviceRegistry) -> FrameBase:
|
|
22
|
+
payload = [ (MXR_PROTOCOL_VERSION & 0xFF), ((MXR_PROTOCOL_VERSION >> 8) & 0xFF) ]
|
|
23
|
+
payload = append_payload_str(payload=payload, value=mxr.name, sz=16)
|
|
24
|
+
payload = append_payload_str(payload=payload, value="P9SN00000000", sz=16)
|
|
25
|
+
payload = append_payload_str(payload=payload, value=__version__, sz=16)
|
|
26
|
+
features = MXR_DEVICE_FEATURE_MANAGER
|
|
27
|
+
payload += [ (features >> 0) & 0xFF, (features >> 8) & 0xFF, (features >> 16) & 0xFF, (features >> 24) & 0xFF ]
|
|
28
|
+
return FrameBase.construct_frame(mxr=mxr, opcode=0, payload=payload)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def supported_protocol(self) -> int:
|
|
32
|
+
# supported protocol version, which may be higher than this frame's protocol version
|
|
33
|
+
return (int(self.payload[1]) << 8) | int(self.payload[0])
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def device_name(self) -> str:
|
|
37
|
+
# device name
|
|
38
|
+
return self.payload[2:18].split(b'\0',1)[0].decode('ascii')
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def serial(self) -> str:
|
|
42
|
+
# device serial
|
|
43
|
+
return self.payload[18:34].split(b'\0',1)[0].decode('ascii')
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def version(self) -> str:
|
|
47
|
+
# firmware version
|
|
48
|
+
return self.payload[34:50].split(b'\0',1)[0].decode('ascii')
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def features(self) -> DeviceFeatures:
|
|
52
|
+
# supported features bitmask
|
|
53
|
+
return DeviceFeatures(struct.unpack('<L', self.payload[50:54])[0])
|
|
54
|
+
|
|
55
|
+
def process(self) -> None:
|
|
56
|
+
# register or update this device in the local cache
|
|
57
|
+
self.mxr.on_mxr_hello(self)
|
|
58
|
+
|
|
59
|
+
def __eq__(self, other: Any) -> bool:
|
|
60
|
+
return isinstance(other, FrameHello) and \
|
|
61
|
+
(self.protocol == other.protocol) and \
|
|
62
|
+
(self.device_name == other.device_name) and \
|
|
63
|
+
(self.serial == other.serial) and \
|
|
64
|
+
(self.version == other.version) and \
|
|
65
|
+
(self.features == other.features)
|
|
66
|
+
|
|
67
|
+
def __ne__(self, other: Any) -> bool:
|
|
68
|
+
return not isinstance(other, FrameHello) or \
|
|
69
|
+
(self.protocol != other.protocol) or \
|
|
70
|
+
(self.device_name != other.device_name) or \
|
|
71
|
+
(self.serial != other.serial) or \
|
|
72
|
+
(self.version != other.version) or \
|
|
73
|
+
(self.features != other.features)
|
|
74
|
+
|
|
75
|
+
def __str__(self) -> str:
|
|
76
|
+
return f"hello name:{self.device_name} serial:{self.serial} version:{self.version} features/status: {self.features} uid: {self.header.remote_id}"
|
|
77
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
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 .LinkConfig import LinkConfig
|
|
9
|
+
from .FrameBase import FrameBase
|
|
10
|
+
from .FrameHeader import FrameHeader
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
class FrameLinks(FrameBase):
|
|
14
|
+
''' All configured links for the device that sent this frame '''
|
|
15
|
+
def __init__(self, header:FrameHeader):
|
|
16
|
+
super().__init__(header)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def nb_links(self) -> int:
|
|
20
|
+
# number of links defined in this frame
|
|
21
|
+
return len(self) / 38
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def links(self) -> list[LinkConfig]:
|
|
25
|
+
# list of all links defined in this frame
|
|
26
|
+
rv = []
|
|
27
|
+
linknum = 0
|
|
28
|
+
while linknum < self.nb_links:
|
|
29
|
+
link = LinkConfig(self, self.payload[(linknum*38):((linknum+1)*38)])
|
|
30
|
+
rv.append(link)
|
|
31
|
+
linknum += 1
|
|
32
|
+
return rv
|
|
33
|
+
|
|
34
|
+
def process(self) -> None:
|
|
35
|
+
# update the cached link status
|
|
36
|
+
dev = self.remote_device
|
|
37
|
+
if dev is None:
|
|
38
|
+
logging.debug("not processing link config - hello not received")
|
|
39
|
+
return
|
|
40
|
+
for link in self.links:
|
|
41
|
+
link.process()
|
|
42
|
+
dev.on_link_config_received()
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return f"{self.remote_device} links config"
|
|
@@ -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 DeviceRegistry, DeviceBase, MxrDeviceUid
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
class MeshOperation(Enum):
|
|
14
|
+
REGISTER = 0
|
|
15
|
+
UNREGISTER = 1
|
|
16
|
+
REPLACE = 2
|
|
17
|
+
REGENERATE_ADDRESSES = 3
|
|
18
|
+
PROMOTE_MASTER = 4
|
|
19
|
+
REPORT_MEMBERSHIP = 0xFF
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
if self.value == MeshOperation.REGISTER.value:
|
|
23
|
+
return "register"
|
|
24
|
+
if self.value == MeshOperation.UNREGISTER.value:
|
|
25
|
+
return "unregister"
|
|
26
|
+
if self.value == MeshOperation.REPLACE.value:
|
|
27
|
+
return "replace"
|
|
28
|
+
if self.value == MeshOperation.REGENERATE_ADDRESSES.value:
|
|
29
|
+
return "regenerate addresses"
|
|
30
|
+
if self.value == MeshOperation.PROMOTE_MASTER.value:
|
|
31
|
+
return "promote master"
|
|
32
|
+
if self.value == MeshOperation.REPORT_MEMBERSHIP.value:
|
|
33
|
+
return "report membership"
|
|
34
|
+
return "unknown"
|
|
35
|
+
|
|
36
|
+
class FrameMeshOperation(FrameBase):
|
|
37
|
+
''' Mesh operation '''
|
|
38
|
+
def __init__(self, header:FrameHeader):
|
|
39
|
+
super().__init__(header)
|
|
40
|
+
|
|
41
|
+
def construct(mxr:DeviceRegistry, operation:MeshOperation, target:DeviceBase, option:DeviceBase|None) -> FrameBase:
|
|
42
|
+
payload = bytes([operation.value, 0, 0, 0]) + target.remote_id.byte_value
|
|
43
|
+
if option is not None:
|
|
44
|
+
payload += option.remote_id.byte_value
|
|
45
|
+
else:
|
|
46
|
+
payload += bytes([0 for _ in range(16)])
|
|
47
|
+
return FrameBase.construct_frame(mxr=mxr, opcode=0x3B, payload=payload)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def operation(self) -> MeshOperation:
|
|
51
|
+
return MeshOperation(self.payload[0])
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def target_uid(self) -> MxrDeviceUid:
|
|
55
|
+
return MxrDeviceUid(value=self.payload[4:20])
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def parameter(self) -> MxrDeviceUid:
|
|
59
|
+
return MxrDeviceUid(value=self.payload[20:36])
|
|
60
|
+
|
|
61
|
+
def process(self) -> None:
|
|
62
|
+
if (self.operation == MeshOperation.REPORT_MEMBERSHIP):
|
|
63
|
+
self.remote_device.mesh_master = self.target_uid
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return f"Mesh operation: {str(self.operation)}"
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
|
11
|
+
from ..Uid import MxrDeviceUid
|
|
12
|
+
|
|
13
|
+
class FrameMirrorStatus(FrameBase):
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def target(self) -> MxrDeviceUid:
|
|
19
|
+
if len(self.payload) < 16:
|
|
20
|
+
return None
|
|
21
|
+
return MxrDeviceUid(self.payload[0:16])
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_own(self) -> bool:
|
|
25
|
+
dev = self.remote_device
|
|
26
|
+
if dev is None:
|
|
27
|
+
return False
|
|
28
|
+
return (dev.remote_id == self.target)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def master(self) -> MxrDeviceUid:
|
|
32
|
+
if len(self.payload) < 32:
|
|
33
|
+
return None
|
|
34
|
+
return MxrDeviceUid(self.payload[16:32])
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def master_dev(self) -> DeviceBase:
|
|
38
|
+
return self.mxr.get_by_uid(self.master)
|
|
39
|
+
|
|
40
|
+
def process(self) -> None:
|
|
41
|
+
dev = self.remote_device
|
|
42
|
+
if dev is None:
|
|
43
|
+
return False
|
|
44
|
+
if self.is_own and len(dev.outputs) > 0:
|
|
45
|
+
first_out = dev.outputs[list(dev.outputs.keys())[0]]
|
|
46
|
+
first_out.mirroring = self.master_dev
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return f"{self.remote_device} mirroring status: {self.master_dev}"
|
|
@@ -0,0 +1,163 @@
|
|
|
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 NetworkPortStatus, UtpLinkErrorStatus, UtpLinkSpeed, UtpCableStatus
|
|
11
|
+
import socket
|
|
12
|
+
import struct
|
|
13
|
+
|
|
14
|
+
class UtpCableStatusImpl(UtpCableStatus):
|
|
15
|
+
def __init__(self, data):
|
|
16
|
+
self._data = data
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def polarity(self) -> bool:
|
|
20
|
+
return (self._data[0] == 1)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def pair(self) -> int:
|
|
24
|
+
return (self._data[1])
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def skew(self) -> int:
|
|
28
|
+
return struct.unpack('<L', self._data[4:8])[0]
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def length(self) -> int:
|
|
32
|
+
return struct.unpack('<L', self._data[8:12])[0]
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return f"pair {self.pair} polarity {self.polarity} skew {self.skew}"
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return str(self)
|
|
39
|
+
|
|
40
|
+
class UtpLinkErrorStatusImpl(UtpLinkErrorStatus):
|
|
41
|
+
def __init__(self, data):
|
|
42
|
+
self._data = data
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def in_error(self) -> bool:
|
|
46
|
+
return ((self._data & (1 << 0)) != 0)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def in_fcs_error(self) -> bool:
|
|
50
|
+
return ((self._data & (1 << 1)) != 0)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def in_collision(self) -> bool:
|
|
54
|
+
return ((self._data & (1 << 2)) != 0)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def out_deferred(self) -> bool:
|
|
58
|
+
return ((self._data & (1 << 3)) != 0)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def out_excessive(self) -> bool:
|
|
62
|
+
return ((self._data & (1 << 4)) != 0)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def polarity_error(self) -> bool:
|
|
66
|
+
return ((self._data & (1 << 5)) != 0)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def skew_warning(self) -> bool:
|
|
70
|
+
return ((self._data & (1 << 6)) != 0)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def length_warning(self) -> bool:
|
|
74
|
+
return ((self._data & (1 << 7)) != 0)
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
errs = ""
|
|
78
|
+
if self.in_error:
|
|
79
|
+
errs += "[rx errors]"
|
|
80
|
+
if self.in_fcs_error:
|
|
81
|
+
errs += "[rx fcs]"
|
|
82
|
+
if self.in_collision:
|
|
83
|
+
errs += "[rx collision]"
|
|
84
|
+
if self.out_deferred:
|
|
85
|
+
errs += "[tx deferred]"
|
|
86
|
+
if self.out_excessive:
|
|
87
|
+
errs += "[tx excessive]"
|
|
88
|
+
if self.polarity_error:
|
|
89
|
+
errs += "[polarity]"
|
|
90
|
+
if self.skew_warning:
|
|
91
|
+
errs += "[skew]"
|
|
92
|
+
if self.length_warning:
|
|
93
|
+
errs += "[length warning]"
|
|
94
|
+
if errs == "":
|
|
95
|
+
errs = "healthy"
|
|
96
|
+
return errs
|
|
97
|
+
|
|
98
|
+
class NetworkPortStatusImpl(NetworkPortStatus):
|
|
99
|
+
def __init__(self, data:bytes) -> None:
|
|
100
|
+
self.data = data
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def port(self) -> int:
|
|
104
|
+
return int(self.data[0])
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def errors(self) -> UtpLinkErrorStatus:
|
|
108
|
+
return UtpLinkErrorStatusImpl(self.data[1])
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def vct_status(self) -> list[str]:
|
|
112
|
+
rv = []
|
|
113
|
+
for x in range(4):
|
|
114
|
+
if (self.data[2] & (1 << x) != 0):
|
|
115
|
+
rv.append("WARNING")
|
|
116
|
+
else:
|
|
117
|
+
rv.append("healthy")
|
|
118
|
+
return rv
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def link_speed(self) -> UtpLinkSpeed:
|
|
122
|
+
return UtpLinkSpeed(self.data[3] & 0x7)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def link_full_duplex(self) -> bool:
|
|
126
|
+
return ((self.data[3] & (1 << 3)) != 0)
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def name(self) -> str:
|
|
130
|
+
return self.data[112:128].split(b'\0',1)[0].decode('ascii')
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def ip(self) -> str:
|
|
134
|
+
ip = int.from_bytes(self.data[132:136], "big")
|
|
135
|
+
return socket.inet_ntoa(struct.pack('!L', ip))
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def querier(self) -> str:
|
|
139
|
+
ip = int.from_bytes(self.data[136:140], "big")
|
|
140
|
+
return socket.inet_ntoa(struct.pack('!L', ip))
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def cable_status(self) -> list[UtpCableStatus]:
|
|
144
|
+
return [UtpCableStatusImpl(self.data[8:20]), UtpCableStatusImpl(self.data[20:32]), UtpCableStatusImpl(self.data[32:44]), UtpCableStatusImpl(self.data[44:56])]
|
|
145
|
+
|
|
146
|
+
def __str__(self) -> str:
|
|
147
|
+
return f"network status port {self.name} status: {self.errors} ip: {self.ip} vct: {self.vct_status} speed: {self.link_speed} full duplex: {self.link_full_duplex} cable: {str(self.cable_status)}"
|
|
148
|
+
|
|
149
|
+
class FrameNetworkStatus(FrameBase):
|
|
150
|
+
def __init__(self, header:FrameHeader):
|
|
151
|
+
super().__init__(header)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def status(self) -> NetworkPortStatus:
|
|
155
|
+
return NetworkPortStatusImpl(data=self.payload)
|
|
156
|
+
|
|
157
|
+
def process(self) -> None:
|
|
158
|
+
dev = self.remote_device
|
|
159
|
+
if dev is not None:
|
|
160
|
+
dev.update_network_status(self.status)
|
|
161
|
+
|
|
162
|
+
def __str__(self) -> str:
|
|
163
|
+
return str(self.status)
|
|
@@ -0,0 +1,71 @@
|
|
|
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 .PDUState import PDUState
|
|
11
|
+
import struct
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
class FramePDUState(FrameBase):
|
|
15
|
+
''' pdu state frame, sent every minute by devices on the network '''
|
|
16
|
+
def __init__(self, header:FrameHeader):
|
|
17
|
+
super().__init__(header)
|
|
18
|
+
self._state = PDUState(self)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def state(self) -> PDUState:
|
|
22
|
+
return self._state
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def current(self) -> float:
|
|
26
|
+
# current (A)
|
|
27
|
+
return round(struct.unpack('f', self.payload[0:4])[0], 2)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def voltage(self) -> float:
|
|
31
|
+
# voltage (V)
|
|
32
|
+
return round(struct.unpack('f', self.payload[4:8])[0], 2)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def power(self) -> float:
|
|
36
|
+
# power consumption (W)
|
|
37
|
+
return round(struct.unpack('f', self.payload[8:12])[0], 2)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def dissipation(self) -> float:
|
|
41
|
+
# power dissipation (W)
|
|
42
|
+
return round(struct.unpack('f', self.payload[12:16])[0], 2)
|
|
43
|
+
|
|
44
|
+
#@property
|
|
45
|
+
#def power_factor(self) -> float:
|
|
46
|
+
# # power factor (NOT SUPPORTED)
|
|
47
|
+
# return round(struct.unpack('f', self.payload[16:20])[0], 2)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def frequency(self) -> float:
|
|
51
|
+
# AC frequency
|
|
52
|
+
return round(struct.unpack('f', self.payload[20:24])[0], 2)
|
|
53
|
+
|
|
54
|
+
def outlet_state(self, outlet):
|
|
55
|
+
return self.payload[24 + outlet] if outlet < 8 else 0
|
|
56
|
+
|
|
57
|
+
def process(self) -> None:
|
|
58
|
+
# update the local cached state
|
|
59
|
+
dev = self.mxr.get(self.remote_id)
|
|
60
|
+
if dev is not None:
|
|
61
|
+
dev.on_mxr_update_pdu(self.state)
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other:Any) -> bool:
|
|
64
|
+
if not isinstance(other, FramePDUState):
|
|
65
|
+
return False
|
|
66
|
+
return (self.payload[0:16].append(self.payload[24:32])) == \
|
|
67
|
+
(other.payload[0:16].append(other.payload[24:32]))
|
|
68
|
+
|
|
69
|
+
def __ne__(self, other:Any) -> bool:
|
|
70
|
+
return not self.__eq__(other)
|
|
71
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
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 FramePowerChange(FrameBase):
|
|
13
|
+
''' power status changed of a device connected to a bay '''
|
|
14
|
+
def __init__(self, header:FrameHeader):
|
|
15
|
+
super().__init__(header)
|
|
16
|
+
self.dev = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def bay(self) -> BayBase:
|
|
20
|
+
# bay that changed
|
|
21
|
+
portnum = self.payload[0]
|
|
22
|
+
dev = self.remote_device
|
|
23
|
+
if dev is None:
|
|
24
|
+
return
|
|
25
|
+
return dev.get_by_portnum(portnum)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def power(self) -> bool:
|
|
29
|
+
# new power status
|
|
30
|
+
return (self.payload[1] == 1)
|
|
31
|
+
|
|
32
|
+
def process(self) -> None:
|
|
33
|
+
bay = self.bay
|
|
34
|
+
if bay is not None:
|
|
35
|
+
bay.power_status = 'on' if self.power else 'off'
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return f"{self.bay} power status: {self.power}"
|
|
@@ -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 .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 FrameRCAction(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 bay(self) -> BayBase:
|
|
30
|
+
# bay that received the key press
|
|
31
|
+
dev = self.remote_device
|
|
32
|
+
if dev is None:
|
|
33
|
+
return None
|
|
34
|
+
portnum = ((int(self.payload[1]) << 8) | int(self.payload[0])) if (dev.protocol >= 6) else self.payload[0]
|
|
35
|
+
return dev.get_by_portnum(portnum)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def action(self) -> RCAction:
|
|
39
|
+
dev = self.remote_device
|
|
40
|
+
if dev is None:
|
|
41
|
+
return None
|
|
42
|
+
return RCAction(int(self.payload[2]))
|
|
43
|
+
|
|
44
|
+
def process(self) -> None:
|
|
45
|
+
bay = self.bay
|
|
46
|
+
if bay is not None:
|
|
47
|
+
bay.on_action_received(self.action)
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
return f"{self.bay} action receive: {self.action}"
|
|
@@ -0,0 +1,17 @@
|
|
|
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 FrameRCIr(FrameBase):
|
|
12
|
+
''' IR key press '''
|
|
13
|
+
def __init__(self, header:FrameHeader):
|
|
14
|
+
super().__init__(header)
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return f"IR key press"
|