recom 0.0.1__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.
recom/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from recom.device import RecomDevice, DeviceException
2
+ from recom.interface import RecomInterface
3
+
4
+ __version__ = "0.0.1" # This will be replaced by setuptools_scm during installation
5
+
6
+ __all__ = ["RecomDevice", "RecomInterface", "DeviceException"]
recom/__main__.py ADDED
@@ -0,0 +1,25 @@
1
+ import sys
2
+ import traceback
3
+
4
+ from recom.cli import cli
5
+
6
+
7
+ def main(argv=None):
8
+ """Main entry point"""
9
+
10
+ if argv==None:
11
+ argv = sys.argv
12
+
13
+ try:
14
+ return cli(argv[1:])
15
+ except KeyboardInterrupt:
16
+ print("Aborted by user")
17
+ except Exception as e:
18
+ print("FATAL: ", repr(e))
19
+ print(traceback.format_exc())
20
+ sys.exit(1)
21
+
22
+
23
+ if __name__ == '__main__':
24
+ sys.exit(main())
25
+
File without changes
recom/backend/uart.py ADDED
@@ -0,0 +1,8 @@
1
+ import serial
2
+ import serial.tools.list_ports
3
+
4
+ def find_serial_device_by_com_port(com_port):
5
+ return None
6
+
7
+ def find_serial_device_by_serial(serial):
8
+ return None
recom/backend/usb.py ADDED
@@ -0,0 +1,121 @@
1
+ import enum
2
+ import usb.core
3
+ import usb.util
4
+
5
+ def find_usb_device_by_vid_pid(vid_pid):
6
+ if isinstance(vid_pid, tuple):
7
+ vid = vid_pid[0]
8
+ pid = vid_pid[1]
9
+ elif isinstance(vid_pid, str):
10
+ if ':' in vid_pid:
11
+ vp_parts = vid_pid.split(':', 1)
12
+ vid = int(vp_parts[0].strip(), 16) if len(vp_parts[0]) else None
13
+ pid = int(vp_parts[1].strip(), 16) if len(vp_parts[1]) else None
14
+ else:
15
+ vid = int(vid_pid.strip(), 16)
16
+ pid = None
17
+ if vid is not None and pid is not None:
18
+ return list(usb.core.find(find_all=True, idVendor=vid, idProduct=pid))
19
+ elif vid is not None:
20
+ return list(usb.core.find(find_all=True, idVendor=vid))
21
+ elif pid is not None:
22
+ return list(usb.core.find(find_all=True, idProduct=pid))
23
+ return None
24
+
25
+ def find_usb_device_by_serial(serial):
26
+ return usb.core.find(serial=serial)
27
+
28
+ class CTRL_REQ(enum.IntEnum):
29
+ DEVICE_VENDOR_OUT = 0x40
30
+ DEVICE_VENDOR_IN = 0xC0
31
+ INTERFACE_VENDOR_OUT = 0x41
32
+ INTERFACE_VENDOR_IN = 0xC1
33
+
34
+
35
+ class USBDevice:
36
+
37
+ def __init__(self, dev_handle):
38
+ self.dev = dev_handle
39
+ self.interfaces = []
40
+
41
+ for cfg in self.dev:
42
+ for itf in cfg:
43
+ self.interfaces.append(USBInterface(self.dev, itf))
44
+ self.dev.set_configuration(1)
45
+
46
+ def __repr__(self):
47
+ return "USB Device 0x%04X:0x%04X" % (self.dev.idVendor, self.dev.idProduct)
48
+
49
+ @property
50
+ def dev_type(self):
51
+ return 'usb'
52
+
53
+ def controlRead(self, request, value=0, index=0, dataLen=512, timeout=1000):
54
+ return self.dev.ctrl_transfer(CTRL_REQ.DEVICE_VENDOR_IN, request, value, index, dataLen, timeout)
55
+
56
+ def controlWrite(self, request, data=b'', value=0, index=0, timeout=1000):
57
+ return self.dev.ctrl_transfer(CTRL_REQ.DEVICE_VENDOR_OUT, request, value, index, data, timeout)
58
+
59
+ def get_interface_list(self):
60
+ itf_list = []
61
+ for itf in self.interfaces:
62
+ itf_list.append([itf.desc.bInterfaceClass, itf.desc.bInterfaceSubClass,
63
+ itf.desc.bInterfaceProtocol, itf.itf_str])
64
+ return itf_list
65
+
66
+ def get_interface(self, itf_identifier):
67
+ if isinstance(itf_identifier, int):
68
+ # Interface index
69
+ return self.interfaces[itf_identifier]
70
+ elif isinstance(itf_identifier, tuple):
71
+ # Interface subclass/protocol tuple
72
+ for itf in self.interfaces:
73
+ if itf.desc.bInterfaceSubClass == itf_identifier[0] and \
74
+ itf.desc.bInterfaceProtocol == itf_identifier[1]:
75
+ return itf
76
+ elif isinstance(itf_identifier, str):
77
+ # Interface description string
78
+ for itf in self.interfaces:
79
+ if itf_identifier in itf.itf_str:
80
+ return itf
81
+ return None
82
+
83
+
84
+ class USBInterface():
85
+
86
+ def __init__(self, dev_handle, itf_desc):
87
+ self.dev = dev_handle
88
+ self.desc = itf_desc
89
+ self.itf_idx = itf_desc.bInterfaceNumber
90
+ interface = usb.core.Interface(self.dev, self.itf_idx)
91
+ self.eps = []
92
+ self.ep_in = usb.util.find_descriptor(interface, custom_match = lambda e: \
93
+ usb.util.endpoint_direction(e.bEndpointAddress) == \
94
+ usb.util.ENDPOINT_IN)
95
+ self.ep_out = usb.util.find_descriptor(interface, custom_match = lambda e: \
96
+ usb.util.endpoint_direction(e.bEndpointAddress) == \
97
+ usb.util.ENDPOINT_OUT)
98
+ self.subclass = self.desc.bInterfaceSubClass
99
+ self.protocol = self.desc.bInterfaceProtocol
100
+ self.itf_str = usb.util.get_string(self.dev, self.desc.iInterface)
101
+
102
+ def __repr__(self):
103
+ return "%s: Subclass=%d, Protocol=%d, EP_OUT=0x%02X, EP_IN=0x%02X" % \
104
+ (self.itf_str, self.subclass, self.protocol, self.ep_out.bEndpointAddress,
105
+ self.ep_in.bEndpointAddress)
106
+
107
+ @property
108
+ def itf_string(self):
109
+ return self.itf_str
110
+
111
+ def controlRead(self, request, value=0, index=0, dataLen=512, timeout=1000):
112
+ return self.dev.ctrl_transfer(CTRL_REQ.INTERFACE_VENDOR_IN, request, value, index, dataLen, timeout)
113
+
114
+ def controlWrite(self, request, data=b'', value=0, index=0, timeout=1000):
115
+ return self.dev.ctrl_transfer(CTRL_REQ.INTERFACE_VENDOR_OUT, request, value, index, data, timeout)
116
+
117
+ def read(self, dataLen=64, timeout=1000):
118
+ return self.ep_in.read(dataLen, timeout)
119
+
120
+ def write(self, data, timeout=1000):
121
+ return self.ep_out.write(data, timeout)
recom/cli.py ADDED
@@ -0,0 +1,47 @@
1
+ import argparse
2
+
3
+ import recom
4
+ from recom.device import RecomDevice, DeviceException
5
+
6
+ def list_devices(device_id, serial):
7
+ dev = None
8
+ print("Finding device")
9
+ if serial is not None:
10
+ print(f'Find by serial - {serial}')
11
+ try:
12
+ dev = RecomDevice(serial=serial)
13
+ except DeviceException as dev_exp:
14
+ print(dev_exp)
15
+ return
16
+
17
+ else:
18
+ print(f'Find by DeviceID - {device_id}')
19
+ try:
20
+ dev = RecomDevice(device_id=device_id)
21
+ except DeviceException as dev_exp:
22
+ print(dev_exp)
23
+ return
24
+
25
+ print("Found device: %s" % dev)
26
+
27
+ print("\tHW ID/Rev: 0x%04X / 0x%04X" % (dev.hw_id, dev.hw_revision))
28
+ print("\tFW Rev: %s" % dev.fw_revision)
29
+ print("\tSerial: %s" % dev.serial)
30
+ print("\n\tInterfaces:")
31
+ interfaces = dev.getAllInterfaces()
32
+ for itf in interfaces:
33
+ itf_tuple = (itf[1], itf[2])
34
+ itf_handle = dev.getInterfaceHandleFromID(itf_tuple)
35
+ print("\t\t%s" % itf_handle)
36
+
37
+
38
+ def cli(argv):
39
+ parser = argparse.ArgumentParser(description="Open a serial port and read/write data.")
40
+ parser.add_argument('--version', action='version', version=recom.__version__,
41
+ help="Print package version")
42
+ parser.add_argument('-d', '--device', help='Device ID to search for ([VID:PID] for USB, port for serial)')
43
+ parser.add_argument('-S', '--serial', help='Serial number to search for')
44
+
45
+ args = parser.parse_args(argv)
46
+
47
+ list_devices(args.device, args.serial)
recom/device.py ADDED
@@ -0,0 +1,137 @@
1
+ #from interface import DeviceInterface
2
+ import enum
3
+ import struct
4
+ from recom.backend.usb import find_usb_device_by_serial, find_usb_device_by_vid_pid
5
+ from recom.backend.uart import find_serial_device_by_serial, find_serial_device_by_com_port
6
+ from recom.backend.usb import USBDevice
7
+ from recom.interface import RecomInterface
8
+
9
+ class BASE_DEV_CMDS(enum.IntEnum):
10
+ CMD_SCRATCH_REG = 0x00,
11
+ CMD_HW_ID = 0x01,
12
+ CMD_HW_REV = 0x02,
13
+ CMD_FW_REV = 0x03,
14
+ CMD_SERIAL = 0x04,
15
+ CMD_RESET = 0x05
16
+ CMD_GET_INTERFACES = 0x06,
17
+
18
+ class DeviceException(Exception):
19
+ pass
20
+
21
+ class BaseDevice:
22
+
23
+ def __init__(self, device):
24
+ self._interfaces = []
25
+ self._comsBackend = None
26
+ self.dev = device
27
+ #print(self.dev)
28
+
29
+ # TODO: Detect and setup COMs backend
30
+ # For now, default to USB
31
+ self._comsBackend = USBDevice(device)
32
+
33
+ def __repr__(self):
34
+ return repr(self._comsBackend)
35
+
36
+ def _detectInterfaces(self):
37
+ #If USB, get the interfaces from the USB library/descriptor
38
+ #If UART or other interface, use the control interface to query the available interfaces
39
+ pass
40
+
41
+ def getAllInterfaces(self):
42
+ """Returns a list of available interfaces"""
43
+ return self._comsBackend.get_interface_list()
44
+
45
+ def getInterfaceHandleFromID(self, itf_id):
46
+ """Finds an interface based on its ID and returns its handle"""
47
+ itf = self._comsBackend.get_interface(itf_id)
48
+ if itf is None:
49
+ raise DeviceException("Interface not found")
50
+ return RecomInterface(self, itf)
51
+
52
+ def getInterfaceHandleFromNumber(self, itf_num):
53
+ """Finds an interface based on its number in the interface list and returns its handle"""
54
+ itf_list = self._comsBackend.get_interface_list()
55
+ if itf_num >= len(itf_list):
56
+ raise DeviceException("Interface number out of range")
57
+ itf = self._comsBackend.get_interface(itf_list[itf_num])
58
+ return RecomInterface(self, itf)
59
+
60
+ def getHwID(self):
61
+ # The HW ID is a 32-bit number
62
+ data = self._comsBackend.controlRead(BASE_DEV_CMDS.CMD_HW_ID)
63
+ return struct.unpack('<I', data)
64
+
65
+ def getHwRev(self):
66
+ # The HW revision is a 32-bit number
67
+ data = self._comsBackend.controlRead(BASE_DEV_CMDS.CMD_HW_REV)
68
+ return struct.unpack('<I', data)
69
+
70
+ def getFwRev(self):
71
+ # The FW revision is a string
72
+ data = self._comsBackend.controlRead(BASE_DEV_CMDS.CMD_FW_REV)
73
+ return ''.join(chr(x) for x in data)
74
+
75
+ def getSerial(self):
76
+ # The device serial number is a string
77
+ data = self._comsBackend.controlRead(BASE_DEV_CMDS.CMD_SERIAL)
78
+ return ''.join(chr(x) for x in data)
79
+
80
+ def getScratchReg(self):
81
+ # The scratch register is a 32-bit number
82
+ data = self._comsBackend.controlRead(BASE_DEV_CMDS.CMD_SCRATCH_REG)
83
+ return struct.unpack('<I', data)
84
+
85
+ def setScratchReg(self, scratch_value:int):
86
+ data = struct.pack('<I', scratch_value)
87
+ return self._comsBackend.controlWrite(BASE_DEV_CMDS.CMD_SCRATCH_REG, data)
88
+
89
+ class RecomDevice(BaseDevice):
90
+
91
+ def __init__(self, serial=None, device_id=None):
92
+ dev = self._find_device(serial, device_id)
93
+ if dev is None:
94
+ raise DeviceException("No device found")
95
+ super().__init__(dev)
96
+
97
+ def _find_device(self, serial, device_id):
98
+ if serial is not None:
99
+ # Check if we have a USB device with the specified serial
100
+ dev = find_usb_device_by_serial(serial)
101
+ if dev is not None:
102
+ return dev
103
+ # Next, check if there is a serial device with the specified serial
104
+ dev = find_serial_device_by_serial(serial)
105
+ return dev
106
+ elif device_id is not None:
107
+ # Check if we can find a USB device with the specified device_id (VID:PID in this case)
108
+ dev = find_usb_device_by_vid_pid(device_id)
109
+ if len(dev) > 1:
110
+ raise DeviceException("More than one device found!")
111
+ elif len(dev) == 1:
112
+ return dev[0]
113
+ # No USB devices found, now try to find serial devices with the specified device_id (port ID)
114
+ dev = find_serial_device_by_com_port(device_id)
115
+ return dev
116
+
117
+
118
+ def reset(self):
119
+ pass
120
+
121
+ @property
122
+ def hw_id(self):
123
+ return self.getHwID()[0]
124
+
125
+ @property
126
+ def hw_revision(self):
127
+ return self.getHwRev()[0]
128
+
129
+ @property
130
+ def fw_revision(self):
131
+ return self.getFwRev()
132
+
133
+ @property
134
+ def serial(self):
135
+ return self.getSerial()
136
+
137
+
recom/interface.py ADDED
@@ -0,0 +1,50 @@
1
+ import enum
2
+
3
+ class RecomInterface:
4
+
5
+ def __init__(self, deviceHandle, interfaceHandle):
6
+ self._parentDevice = deviceHandle
7
+ self._handle = interfaceHandle
8
+
9
+ def __repr__(self):
10
+ return f"Interface: {self.name} (Sub={self._handle.subclass}/Prot={self._handle.protocol})"
11
+
12
+ def read(self, dataLen=-1):
13
+ """
14
+ Reads data from the interface's data endpoint
15
+
16
+ dataLen specifies how many bytes should be read. If not speciied, any
17
+ available bytes will be returned
18
+ """
19
+ if dataLen == -1:
20
+ return self._handle.read()
21
+ else:
22
+ return self._handle.read(dataLen)
23
+
24
+ def write(self, data):
25
+ """
26
+ Writes data to the interface's data endpoint
27
+
28
+ Returns the number of bytes written to the interface
29
+ """
30
+ return self._handle.write(data)
31
+
32
+ def controlRead(self, request, value=0, index=0, dataLen=64, timeout=1000):
33
+ """Reads data from the interface's control endpoint"""
34
+ return self._handle.controlRead(request=request,
35
+ value=value,
36
+ index=index,
37
+ dataLen=dataLen,
38
+ timeout=timeout)
39
+
40
+ def controlWrite(self, request, data=b'', value=0, index=0, timeout=1000):
41
+ """Writes data to the interface's control endpoint"""
42
+ return self._handle.controlWrite(request=request,
43
+ data=data,
44
+ value=value,
45
+ index=index,
46
+ timeout=timeout)
47
+
48
+ @property
49
+ def name(self):
50
+ return self._handle.itf_string
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.1
2
+ Name: recom
3
+ Version: 0.0.1
4
+ Summary: Embedded communication backend
5
+ Home-page: https://github.com/redhill-embedded/recom.git
6
+ Author: Adrian Rothenbuhler
7
+ Author-email: adrian@redhill-embedded.com
8
+ License: MIT
9
+ Keywords: embedded communication backedn usb serial
10
+ Requires-Python: >=3.8
11
+ Requires-Dist: pyusb
12
+ Requires-Dist: pyserial
13
+
@@ -0,0 +1,13 @@
1
+ recom/__init__.py,sha256=qKz9D0bpUY1veQV50yDG5zefBZW34Z0X9JjhLk9-FqM,248
2
+ recom/__main__.py,sha256=6PAkcjlgShgd-tb0CPgBQ_piqLAliD2ut4_b3ZVAszI,424
3
+ recom/cli.py,sha256=kAY52PqNIbbbU91F_PGU4JwFtR3Wb29rAISAj1PqbxE,1558
4
+ recom/device.py,sha256=up-1t7a0NHEKl7od6zGMbzYIZ-viAQ5LdW3vEFQBqVk,4682
5
+ recom/interface.py,sha256=Pgesnm10aL0YXsvbgBvBeZkuWdBPUut42QjpPO0snk8,1785
6
+ recom/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ recom/backend/uart.py,sha256=3hNXg6CRLgjhnJebeh5kDXfvVANcNR1Xj8AcdQrEh30,166
8
+ recom/backend/usb.py,sha256=PQEIjbzt8U9WT-YWeTCoYb_KsO32uF4h0QJ33rcE38Y,4734
9
+ recom-0.0.1.dist-info/METADATA,sha256=UlzKWL4w9WaM5MZITVAEjfEkdTzB1Bo9NypOC_qaVI0,350
10
+ recom-0.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
11
+ recom-0.0.1.dist-info/entry_points.txt,sha256=HilsJbY9JIp1JI9ODFJFZAzVaENiqCNjQAhsAJYqSG8,46
12
+ recom-0.0.1.dist-info/top_level.txt,sha256=ROCpXv7wHDp6Gpq-HyKPmH3U0Iu9_gMJz7F23mF1k7U,6
13
+ recom-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ recom = recom.__main__:main
@@ -0,0 +1 @@
1
+ recom