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
mx_remote/Uid.py ADDED
@@ -0,0 +1,137 @@
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
+ import logging
9
+ import json
10
+ _LOGGER = logging.getLogger(__name__)
11
+ class MxrDeviceUid:
12
+ ''' Unique ID of an mx_remote device on the network'''
13
+
14
+ def __init__(self, value: object) -> None:
15
+ if isinstance(value, MxrDeviceUid):
16
+ self._value = value._value
17
+ return
18
+ if isinstance(value, str):
19
+ spl = value.split(".")
20
+ if len(spl) < 4:
21
+ raise Exception(f"invalid uid {value}")
22
+ self._value = []
23
+ for part in spl:
24
+ self._value += int(part, 16).to_bytes(4, 'little')
25
+ self._value = bytes(self._value)
26
+ elif isinstance(value, bytes):
27
+ if len(value) == 0:
28
+ self._value = None
29
+ elif len(value) < 16:
30
+ raise Exception(f"invalid uid length {len(value)}")
31
+ else:
32
+ self._value = value
33
+ else:
34
+ raise Exception(f"invalid uid type {str(type(value))}")
35
+
36
+ def toJson(self):
37
+ return json.dumps(self, default=lambda o: o.__dict__)
38
+
39
+ @property
40
+ def value(self) -> str:
41
+ ''' value as human readble string '''
42
+ return ''.join('%02x'%i for i in reversed(self._value[0:4])) + '.' + \
43
+ ''.join('%02x'%i for i in reversed(self._value[4:8])) + '.' + \
44
+ ''.join('%02x'%i for i in reversed(self._value[8:12])) + '.' + \
45
+ ''.join('%02x'%i for i in reversed(self._value[12:16]))
46
+
47
+ @property
48
+ def empty(self) -> bool:
49
+ ''' True if all 0 '''
50
+ for v in self._value:
51
+ if int(v) != 0:
52
+ return False
53
+ return True
54
+
55
+ @property
56
+ def byte_value(self) -> bytes:
57
+ ''' value as bytes '''
58
+ return self._value
59
+
60
+ def __str__(self) -> str:
61
+ return self.value
62
+
63
+ def __repr__(self) -> str:
64
+ return str(self)
65
+
66
+ def __hash__(self) -> int:
67
+ return hash(self.value)
68
+
69
+ def __eq__(self, value: object) -> bool:
70
+ if isinstance(value, str) or isinstance(value, MxrDeviceUid):
71
+ return str(self) == str(value)
72
+ return False
73
+
74
+ def __hash__(self) -> int:
75
+ return hash(str(self))
76
+
77
+ class MxrBayUidOld:
78
+ def __init__(self, serial:str, port_name:str) -> None:
79
+ self._serial = serial
80
+ self._port_name = port_name
81
+
82
+ @property
83
+ def serial(self) -> str:
84
+ return self._serial
85
+
86
+ @property
87
+ def port_name(self) -> str:
88
+ return self._port_name
89
+
90
+ @property
91
+ def empty(self) -> bool:
92
+ return (len(self.serial) == 0) or (len(self.port_name) == 0)
93
+
94
+ def __str__(self) -> str:
95
+ return f"{self.serial}:{self.port_name}"
96
+
97
+ def __eq__(self, value: object) -> bool:
98
+ if isinstance(value, str):
99
+ return (str(self) == value)
100
+ if isinstance(value, MxrBayUidOld):
101
+ return (self.serial == value.serial) and (self.port_name == value.port_name)
102
+ return False
103
+
104
+ def __hash__(self) -> int:
105
+ return hash(str(self))
106
+
107
+ class MxrBayUid:
108
+ def __init__(self, device:MxrDeviceUid, port_number:int) -> None:
109
+ self._device = device
110
+ self._port = port_number
111
+
112
+ @property
113
+ def device(self) -> MxrDeviceUid:
114
+ return self._device
115
+
116
+ @property
117
+ def port(self) -> int:
118
+ return self._port
119
+
120
+ def __hash__(self) -> int:
121
+ return hash(str(self))
122
+
123
+ def __str__(self) -> str:
124
+ return f"{str(self.device)}:{self.port}"
125
+
126
+ def __repr__(self) -> str:
127
+ return str(self)
128
+
129
+ def __eq__(self, value: object) -> bool:
130
+ if isinstance(value, str):
131
+ return (str(self) == value)
132
+ if not isinstance(value, MxrBayUid):
133
+ return False
134
+ return (self.device == value.device) and (self.port == value.port)
135
+
136
+ def __hash__(self) -> int:
137
+ return hash(str(self))
mx_remote/__init__.py ADDED
@@ -0,0 +1,12 @@
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 .remote.Remote import Remote
9
+ from .Interface import *
10
+ from .const import VERSION
11
+ from .proto.Constants import MXR_PROTOCOL_VERSION
12
+ from .main import mxr_console, mxr_main, proto_parser
@@ -0,0 +1,53 @@
1
+ ##################################################
2
+ ## MX Remote Python Interface ##
3
+ ## ##
4
+ ## author: Lars Op den Kamp (lars@opdenkamp.eu) ##
5
+ ## copyright (c) 2024 Op den Kamp IT Solutions ##
6
+ ##################################################
7
+
8
+ class BayConfig:
9
+ ''' Bay details from the api '''
10
+ def __init__(self, bay):
11
+ self.bay = bay
12
+ self._data = None
13
+
14
+ @property
15
+ def name(self) -> str:
16
+ if (self._data is None) or ('Name' not in self._data.keys()):
17
+ return None
18
+ return self._data['Name']
19
+
20
+ @property
21
+ def bay_type(self) -> str:
22
+ if (self._data is None) or ('Name' not in self._data.keys()):
23
+ return "Unknown"
24
+ return self._data['Type']
25
+
26
+ @property
27
+ def status(self) -> int:
28
+ if (self._data is None) or ('Status' not in self._data.keys()):
29
+ return -1
30
+ return int(self._data['Status'])
31
+
32
+ @property
33
+ def hidden(self) -> bool:
34
+ return (self._data is not None) and ('Hidden' in self._data.keys()) and (self._data['Hidden'])
35
+
36
+ @property
37
+ def is_hdmi(self) -> bool:
38
+ return (self.bay_type[0:4] == 'HDMI')
39
+
40
+ @property
41
+ def is_hdbt(self) -> bool:
42
+ return (self.bay_type[0:4] == 'HDBT')
43
+
44
+ @property
45
+ def is_audio(self) -> bool:
46
+ return (self.bay_type[0:5] == 'AUDIO')
47
+
48
+ @property
49
+ def cec_version(self) -> int:
50
+ if (self._data is None) or ('CEC_version' not in self._data.keys()):
51
+ return None
52
+ return int(self._data['CEC_version'])
53
+
@@ -0,0 +1,8 @@
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 .BayConfig import BayConfig
mx_remote/const.py ADDED
@@ -0,0 +1,20 @@
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
+ """ constant definitions for mx_remote """
9
+ import os
10
+
11
+ VERSION = '2.0.0'
12
+ __version__ = VERSION
13
+
14
+ MX_BCAST_UDP_IP = '10.8.8.255'
15
+ MX_BCAST_UDP_PORT = 8811
16
+
17
+ MX_MCAST_UDP_IP = '224.8.8.8'
18
+ MX_MCAST_UDP_PORT = 8812
19
+
20
+ BASE_PATH = os.path.dirname(__file__)
mx_remote/main.py ADDED
@@ -0,0 +1,117 @@
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
+ import argparse
9
+ import asyncio
10
+ import logging
11
+ import mx_remote
12
+ import os
13
+ from typing import Any
14
+
15
+ _LOGGER = logging.getLogger(__name__)
16
+
17
+ # import pdb_attach
18
+ # pdb_attach.listen(50000)
19
+
20
+ def proto_parser(logger:logging.Logger, file:str, filter:str|None) -> None:
21
+ """
22
+ protocol parser entry point
23
+
24
+ Process a file captured by MatrixOS or Wireshark
25
+
26
+ Args:
27
+ logger (logging.Logger): logger to use to output processed data from the capture file
28
+ file (str): path to the file to process
29
+ filter (str): IP address filter (optional)
30
+ """
31
+ if os.path.isfile(file):
32
+ raise Exception(f"cannot find '{file}'")
33
+
34
+ with open(file, "r") as f:
35
+ data = f.read().split("\n")
36
+ remote = mx_remote.Remote(open_connection=False)
37
+ logger.debug(f"processing data from {file}")
38
+ for line in data:
39
+ spl = line.split(",")
40
+ if len(spl) == 3:
41
+ ts = spl[0]
42
+ source = spl[1]
43
+ if filter is not None and source != filter:
44
+ continue
45
+ frame = bytes.fromhex(spl[2])
46
+ try:
47
+ logger.debug(f"[[frame {ts}]]")
48
+ remote.on_datagram_received(frame, (source, 8811))
49
+ except Exception as e:
50
+ logger.warning(f"source: {source} frame FAILED: {e}")
51
+
52
+ def mxr_main( extra_args_callback:callable=None,
53
+ log_level_callback:callable=None,
54
+ entry_callback:callable=None,
55
+ callback_param:Any=None) -> None:
56
+ """
57
+ mx_remote main entry point for stand alone applications
58
+
59
+ Args:
60
+ extra_args_callback (callable): callback to add extra arguments into the arguments parser
61
+ log_level_callback (callable): callback to set the default logging level
62
+ entry_callback (callable): callback to override the main entry point of the application
63
+ callback_param (Any): parameter passed to callbacks
64
+ """
65
+
66
+ # command line arguments
67
+ argparser = argparse.ArgumentParser(description="MX Remote Manager / Debugger")
68
+ argparser.add_argument("-i", dest='input', help='capture file to process', required=False)
69
+ argparser.add_argument("-f", dest='filter', help='ip address to process in the capture file', required=False)
70
+ argparser.add_argument("-o", dest='output', help='write output to a file', required=False)
71
+ argparser.add_argument("-l", dest='local_ip', help='local ip address of the network interface to use', required=False)
72
+ argparser.add_argument("-b", dest='broadcast', help='use broadcast mode instead of multicast mode', required=False, type=bool)
73
+ if (extra_args_callback is not None):
74
+ extra_args_callback(callback_param, argparser)
75
+ args = argparser.parse_args()
76
+
77
+ if (log_level_callback is not None):
78
+ default_level = log_level_callback(callback_param, args)
79
+ else:
80
+ default_level = logging.DEBUG
81
+
82
+ # log output
83
+ if (args.output is None):
84
+ # console
85
+ logging.basicConfig(level=default_level, format='%(asctime)s [%(levelname)s] %(message)s', force=True)
86
+ else:
87
+ # file
88
+ logging.basicConfig(
89
+ level=default_level,
90
+ format="%(asctime)s - %(message)s",
91
+ datefmt='%d-%b-%y %H:%M:%S',
92
+ force=True,
93
+ handlers=[
94
+ logging.FileHandler(args.output),
95
+ logging.StreamHandler()
96
+ ])
97
+
98
+ if (args.input is not None):
99
+ proto_parser(_LOGGER, args.input, args.filter)
100
+ else:
101
+ if (entry_callback is not None) and entry_callback(callback_param, args):
102
+ # handled externally
103
+ return
104
+
105
+ # run the console app
106
+ loop = asyncio.get_event_loop()
107
+ try:
108
+ mx = mx_remote.Remote(local_ip=args.local_ip, broadcast=(args.broadcast is not None and args.broadcast))
109
+ loop.run_until_complete(mx.start_async())
110
+ loop.run_forever()
111
+ except KeyboardInterrupt:
112
+ pass
113
+ loop.run_until_complete(mx.close())
114
+ loop.close()
115
+
116
+ def mxr_console() -> None:
117
+ mxr_main()
@@ -0,0 +1,104 @@
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 .Constants import BayStatusMask
10
+ import struct
11
+
12
+ class BayConfig:
13
+ ''' Bay configuration for a remote device '''
14
+ def __init__(self, payload:bytes):
15
+ self.payload = payload
16
+
17
+ @property
18
+ def port(self) -> int:
19
+ # port number
20
+ return int(self.payload[0])
21
+
22
+ @property
23
+ def modenum(self) -> int:
24
+ # port mode number
25
+ return int(self.payload[1])
26
+
27
+ @property
28
+ def mode(self) -> str:
29
+ # port mode
30
+ nb = self.modenum
31
+ if nb == 0:
32
+ return 'Input'
33
+ if nb == 1:
34
+ return 'Output'
35
+ return 'Unknown'
36
+
37
+ @property
38
+ def is_input(self) -> bool:
39
+ # input bay
40
+ return self.modenum == 0
41
+
42
+ @property
43
+ def is_output(self) -> bool:
44
+ # output bay
45
+ return self.modenum == 1
46
+
47
+ @property
48
+ def bay(self) -> int:
49
+ # bay number
50
+ return int(self.payload[2])
51
+
52
+ @property
53
+ def video_source(self) -> int:
54
+ # video source bay number
55
+ return int(self.payload[3])
56
+
57
+ @property
58
+ def edid_profile(self) -> int:
59
+ return ((int(self.payload[4]) & 0xF) << 8) | int(self.payload[3])
60
+
61
+ @property
62
+ def rc_type(self) -> int:
63
+ return ((int(self.payload[4]) > 4) & 0xF)
64
+
65
+ @property
66
+ def audio_source(self) -> int:
67
+ # audio source bay number
68
+ return int(self.payload[4])
69
+
70
+ @property
71
+ def bay_name(self) -> str:
72
+ # bay name
73
+ return self.payload[5:21].split(b'\0',1)[0].decode('ascii')
74
+
75
+ @property
76
+ def user_name(self) -> str:
77
+ # user set name
78
+ return self.payload[21:37].split(b'\0',1)[0].decode('ascii')
79
+
80
+ @property
81
+ def signal_type(self) -> str:
82
+ # video signal type
83
+ return self.payload[37:53].split(b'\0',1)[0].decode('ascii')
84
+
85
+ @property
86
+ def status(self) -> BayStatusMask:
87
+ # bay status
88
+ return BayStatusMask(struct.unpack('<L', self.payload[53:57])[0])
89
+
90
+ @property
91
+ def features(self) -> int:
92
+ # features mask
93
+ return struct.unpack('<L', self.payload[57:61])[0]
94
+
95
+ def __str__(self) -> str:
96
+ return f"{self.mode} {self.bay + 1} (port {self.port}): {self.user_name} - {self.signal_type}"
97
+
98
+ def bay_match(self, other: BayConfig) -> bool:
99
+ # check whether other is a configuration for the same bay as this one
100
+ return (self.port == other.port) and \
101
+ (self.modenum == other.modenum) and \
102
+ (self.bay == other.bay) and \
103
+ (self.features == other.features)
104
+