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