pyxcp 0.23.0__cp311-cp311-win_arm64.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.

Potentially problematic release.


This version of pyxcp might be problematic. Click here for more details.

Files changed (128) hide show
  1. pyxcp/__init__.py +20 -0
  2. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  3. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  4. pyxcp/aml/XCP_Common.aml +408 -0
  5. pyxcp/aml/XCPonCAN.aml +78 -0
  6. pyxcp/aml/XCPonEth.aml +33 -0
  7. pyxcp/aml/XCPonFlx.aml +113 -0
  8. pyxcp/aml/XCPonSxI.aml +66 -0
  9. pyxcp/aml/XCPonUSB.aml +106 -0
  10. pyxcp/aml/ifdata_CAN.a2l +20 -0
  11. pyxcp/aml/ifdata_Eth.a2l +11 -0
  12. pyxcp/aml/ifdata_Flx.a2l +94 -0
  13. pyxcp/aml/ifdata_SxI.a2l +13 -0
  14. pyxcp/aml/ifdata_USB.a2l +81 -0
  15. pyxcp/asam/__init__.py +0 -0
  16. pyxcp/asam/types.py +131 -0
  17. pyxcp/asamkeydll.c +116 -0
  18. pyxcp/asamkeydll.sh +2 -0
  19. pyxcp/checksum.py +732 -0
  20. pyxcp/cmdline.py +52 -0
  21. pyxcp/config/__init__.py +1113 -0
  22. pyxcp/config/legacy.py +120 -0
  23. pyxcp/constants.py +47 -0
  24. pyxcp/cpp_ext/__init__.py +0 -0
  25. pyxcp/cpp_ext/bin.hpp +104 -0
  26. pyxcp/cpp_ext/blockmem.hpp +58 -0
  27. pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
  28. pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
  29. pyxcp/cpp_ext/daqlist.hpp +206 -0
  30. pyxcp/cpp_ext/event.hpp +67 -0
  31. pyxcp/cpp_ext/extension_wrapper.cpp +100 -0
  32. pyxcp/cpp_ext/helper.hpp +280 -0
  33. pyxcp/cpp_ext/mcobject.hpp +246 -0
  34. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  35. pyxcp/daq_stim/__init__.py +232 -0
  36. pyxcp/daq_stim/optimize/__init__.py +67 -0
  37. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  38. pyxcp/daq_stim/scheduler.cpp +62 -0
  39. pyxcp/daq_stim/scheduler.hpp +75 -0
  40. pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
  41. pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
  42. pyxcp/daq_stim/stim.cpp +13 -0
  43. pyxcp/daq_stim/stim.hpp +604 -0
  44. pyxcp/daq_stim/stim_wrapper.cpp +50 -0
  45. pyxcp/dllif.py +100 -0
  46. pyxcp/errormatrix.py +878 -0
  47. pyxcp/examples/conf_can.toml +19 -0
  48. pyxcp/examples/conf_can_user.toml +16 -0
  49. pyxcp/examples/conf_can_vector.json +11 -0
  50. pyxcp/examples/conf_can_vector.toml +11 -0
  51. pyxcp/examples/conf_eth.toml +9 -0
  52. pyxcp/examples/conf_nixnet.json +20 -0
  53. pyxcp/examples/conf_socket_can.toml +12 -0
  54. pyxcp/examples/conf_sxi.json +9 -0
  55. pyxcp/examples/conf_sxi.toml +7 -0
  56. pyxcp/examples/run_daq.py +163 -0
  57. pyxcp/examples/xcp_policy.py +60 -0
  58. pyxcp/examples/xcp_read_benchmark.py +38 -0
  59. pyxcp/examples/xcp_skel.py +49 -0
  60. pyxcp/examples/xcp_unlock.py +38 -0
  61. pyxcp/examples/xcp_user_supplied_driver.py +44 -0
  62. pyxcp/examples/xcphello.py +78 -0
  63. pyxcp/examples/xcphello_recorder.py +107 -0
  64. pyxcp/master/__init__.py +9 -0
  65. pyxcp/master/errorhandler.py +442 -0
  66. pyxcp/master/master.py +2047 -0
  67. pyxcp/py.typed +0 -0
  68. pyxcp/recorder/__init__.py +101 -0
  69. pyxcp/recorder/build_clang.cmd +1 -0
  70. pyxcp/recorder/build_clang.sh +2 -0
  71. pyxcp/recorder/build_gcc.cmd +1 -0
  72. pyxcp/recorder/build_gcc.sh +2 -0
  73. pyxcp/recorder/build_gcc_arm.sh +2 -0
  74. pyxcp/recorder/converter/__init__.py +450 -0
  75. pyxcp/recorder/lz4.c +2829 -0
  76. pyxcp/recorder/lz4.h +879 -0
  77. pyxcp/recorder/lz4hc.c +2041 -0
  78. pyxcp/recorder/lz4hc.h +413 -0
  79. pyxcp/recorder/mio.hpp +1714 -0
  80. pyxcp/recorder/reader.hpp +139 -0
  81. pyxcp/recorder/reco.py +277 -0
  82. pyxcp/recorder/recorder.rst +0 -0
  83. pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
  84. pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
  85. pyxcp/recorder/rekorder.cpp +59 -0
  86. pyxcp/recorder/rekorder.hpp +274 -0
  87. pyxcp/recorder/setup.py +41 -0
  88. pyxcp/recorder/test_reko.py +34 -0
  89. pyxcp/recorder/unfolder.hpp +1332 -0
  90. pyxcp/recorder/wrap.cpp +189 -0
  91. pyxcp/recorder/writer.hpp +302 -0
  92. pyxcp/scripts/__init__.py +0 -0
  93. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  94. pyxcp/scripts/xcp_examples.py +64 -0
  95. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  96. pyxcp/scripts/xcp_id_scanner.py +19 -0
  97. pyxcp/scripts/xcp_info.py +144 -0
  98. pyxcp/scripts/xcp_profile.py +27 -0
  99. pyxcp/scripts/xmraw_converter.py +31 -0
  100. pyxcp/stim/__init__.py +0 -0
  101. pyxcp/tests/test_asam_types.py +24 -0
  102. pyxcp/tests/test_binpacking.py +186 -0
  103. pyxcp/tests/test_can.py +1324 -0
  104. pyxcp/tests/test_checksum.py +95 -0
  105. pyxcp/tests/test_daq.py +193 -0
  106. pyxcp/tests/test_daq_opt.py +426 -0
  107. pyxcp/tests/test_frame_padding.py +156 -0
  108. pyxcp/tests/test_master.py +2006 -0
  109. pyxcp/tests/test_transport.py +81 -0
  110. pyxcp/tests/test_utils.py +30 -0
  111. pyxcp/timing.py +60 -0
  112. pyxcp/transport/__init__.py +10 -0
  113. pyxcp/transport/base.py +440 -0
  114. pyxcp/transport/base_transport.hpp +0 -0
  115. pyxcp/transport/can.py +556 -0
  116. pyxcp/transport/eth.py +219 -0
  117. pyxcp/transport/sxi.py +135 -0
  118. pyxcp/transport/transport_wrapper.cpp +0 -0
  119. pyxcp/transport/usb_transport.py +213 -0
  120. pyxcp/types.py +1000 -0
  121. pyxcp/utils.py +128 -0
  122. pyxcp/vector/__init__.py +0 -0
  123. pyxcp/vector/map.py +82 -0
  124. pyxcp-0.23.0.dist-info/LICENSE +165 -0
  125. pyxcp-0.23.0.dist-info/METADATA +107 -0
  126. pyxcp-0.23.0.dist-info/RECORD +128 -0
  127. pyxcp-0.23.0.dist-info/WHEEL +4 -0
  128. pyxcp-0.23.0.dist-info/entry_points.txt +9 -0
pyxcp/transport/eth.py ADDED
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env python
2
+ import selectors
3
+ import socket
4
+ import struct
5
+ import threading
6
+ from collections import deque
7
+ from typing import Optional
8
+
9
+ from pyxcp.transport.base import BaseTransport
10
+ from pyxcp.utils import short_sleep
11
+
12
+
13
+ DEFAULT_XCP_PORT = 5555
14
+ RECV_SIZE = 8196
15
+
16
+
17
+ def socket_to_str(sock: socket.socket) -> str:
18
+ peer = sock.getpeername()
19
+ local = sock.getsockname()
20
+ AF = {
21
+ socket.AF_INET: "AF_INET",
22
+ socket.AF_INET6: "AF_INET6",
23
+ }
24
+ TYPE = {
25
+ socket.SOCK_DGRAM: "SOCK_DGRAM",
26
+ socket.SOCK_STREAM: "SOCK_STREAM",
27
+ }
28
+ family = AF.get(sock.family, "OTHER")
29
+ typ = TYPE.get(sock.type, "UNKNOWN")
30
+ res = f"XCPonEth - Connected to: {peer[0]}:{peer[1]} local address: {local[0]}:{local[1]} [{family}][{typ}]"
31
+ return res
32
+
33
+
34
+ class Eth(BaseTransport):
35
+ """"""
36
+
37
+ MAX_DATAGRAM_SIZE = 512
38
+ HEADER = struct.Struct("<HH")
39
+ HEADER_SIZE = HEADER.size
40
+
41
+ def __init__(self, config=None, policy=None, transport_layer_interface: Optional[socket.socket] = None) -> None:
42
+ super().__init__(config, policy, transport_layer_interface)
43
+ self.load_config(config)
44
+ self.host: str = self.config.host
45
+ self.port: int = self.config.port
46
+ self.protocol: int = self.config.protocol
47
+ self.ipv6: bool = self.config.ipv6
48
+ self.use_tcp_no_delay: bool = self.config.tcp_nodelay
49
+ address_to_bind: str = self.config.bind_to_address
50
+ bind_to_port: int = self.config.bind_to_port
51
+ self._local_address = (address_to_bind, bind_to_port) if address_to_bind else None
52
+ if self.ipv6 and not socket.has_ipv6:
53
+ msg = "XCPonEth - IPv6 not supported by your platform."
54
+ self.logger.critical(msg)
55
+ raise RuntimeError(msg)
56
+ else:
57
+ address_family = socket.AF_INET6 if self.ipv6 else socket.AF_INET
58
+ proto = socket.SOCK_STREAM if self.protocol == "TCP" else socket.SOCK_DGRAM
59
+ if self.host.lower() == "localhost":
60
+ self.host = "::1" if self.ipv6 else "localhost"
61
+
62
+ try:
63
+ addrinfo = socket.getaddrinfo(self.host, self.port, address_family, proto)
64
+ (
65
+ self.address_family,
66
+ self.socktype,
67
+ self.proto,
68
+ self.canonname,
69
+ self.sockaddr,
70
+ ) = addrinfo[0]
71
+ except BaseException as ex: # noqa: B036
72
+ msg = f"XCPonEth - Failed to resolve address {self.host}:{self.port}"
73
+ self.logger.critical(msg)
74
+ raise Exception(msg) from ex
75
+ self.status: int = 0
76
+ self.sock = socket.socket(self.address_family, self.socktype, self.proto)
77
+ self.selector = selectors.DefaultSelector()
78
+ self.selector.register(self.sock, selectors.EVENT_READ)
79
+ self.use_tcp = self.protocol == "TCP"
80
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
81
+ if self.use_tcp and self.use_tcp_no_delay:
82
+ self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
83
+ if hasattr(socket, "SO_REUSEPORT"):
84
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
85
+ self.sock.settimeout(0.5)
86
+ if self._local_address:
87
+ try:
88
+ self.sock.bind(self._local_address)
89
+ except BaseException as ex: # noqa: B036
90
+ msg = f"XCPonEth - Failed to bind socket to given address {self._local_address}"
91
+ self.logger.critical(msg)
92
+ raise Exception(msg) from ex
93
+ self._packet_listener = threading.Thread(
94
+ target=self._packet_listen,
95
+ args=(),
96
+ kwargs={},
97
+ )
98
+ self._packets = deque()
99
+
100
+ def connect(self) -> None:
101
+ if self.status == 0:
102
+ self.sock.connect(self.sockaddr)
103
+ self.logger.info(socket_to_str(self.sock))
104
+ self.start_listener()
105
+ self.status = 1 # connected
106
+
107
+ def start_listener(self) -> None:
108
+ super().start_listener()
109
+ if self._packet_listener.is_alive():
110
+ self._packet_listener.join()
111
+ self._packet_listener = threading.Thread(target=self._packet_listen)
112
+ self._packet_listener.start()
113
+
114
+ def close(self) -> None:
115
+ """Close the transport-layer connection and event-loop."""
116
+ self.finish_listener()
117
+ if self.listener.is_alive():
118
+ self.listener.join()
119
+ if self._packet_listener.is_alive():
120
+ self._packet_listener.join()
121
+ self.close_connection()
122
+
123
+ def _packet_listen(self) -> None:
124
+ use_tcp: bool = self.use_tcp
125
+ EVENT_READ = selectors.EVENT_READ
126
+ close_event_set = self.closeEvent.is_set
127
+ socket_fileno = self.sock.fileno
128
+ select = self.selector.select
129
+ _packets = self._packets
130
+ if use_tcp:
131
+ sock_recv = self.sock.recv
132
+ else:
133
+ sock_recv = self.sock.recvfrom
134
+ while True:
135
+ try:
136
+ if close_event_set() or socket_fileno() == -1:
137
+ return
138
+ sel = select(0.02)
139
+ for _, events in sel:
140
+ if events & EVENT_READ:
141
+ recv_timestamp = self.timestamp.value
142
+ if use_tcp:
143
+ response = sock_recv(RECV_SIZE)
144
+ if not response:
145
+ self.sock.close()
146
+ self.status = 0
147
+ break
148
+ else:
149
+ _packets.append((response, recv_timestamp))
150
+ else:
151
+ response, _ = sock_recv(Eth.MAX_DATAGRAM_SIZE)
152
+ if not response:
153
+ self.sock.close()
154
+ self.status = 0
155
+ break
156
+ else:
157
+ _packets.append((response, recv_timestamp))
158
+ except BaseException: # noqa: B036
159
+ self.status = 0 # disconnected
160
+ break
161
+
162
+ def listen(self) -> None:
163
+ HEADER_UNPACK_FROM = self.HEADER.unpack_from
164
+ HEADER_SIZE = self.HEADER_SIZE
165
+ process_response = self.process_response
166
+ popleft = self._packets.popleft
167
+ close_event_set = self.closeEvent.is_set
168
+ socket_fileno = self.sock.fileno
169
+ _packets = self._packets
170
+ length: Optional[int] = None
171
+ counter: int = 0
172
+ data: bytearray = bytearray(b"")
173
+ while True:
174
+ if close_event_set() or socket_fileno() == -1:
175
+ return
176
+ count: int = len(_packets)
177
+ if not count:
178
+ short_sleep()
179
+ continue
180
+ for _ in range(count):
181
+ bts, timestamp = popleft()
182
+ data += bts
183
+ current_size: int = len(data)
184
+ current_position: int = 0
185
+ while True:
186
+ if length is None:
187
+ if current_size >= HEADER_SIZE:
188
+ length, counter = HEADER_UNPACK_FROM(data, current_position)
189
+ current_position += HEADER_SIZE
190
+ current_size -= HEADER_SIZE
191
+ else:
192
+ data = data[current_position:]
193
+ break
194
+ else:
195
+ if current_size >= length:
196
+ response = data[current_position : current_position + length]
197
+ process_response(response, length, counter, timestamp)
198
+ current_size -= length
199
+ current_position += length
200
+ length = None
201
+ else:
202
+ data = data[current_position:]
203
+ break
204
+
205
+ def send(self, frame) -> None:
206
+ self.pre_send_timestamp = self.timestamp.value
207
+ self.sock.send(frame)
208
+ self.post_send_timestamp = self.timestamp.value
209
+
210
+ def close_connection(self) -> None:
211
+ if not self.invalidSocket:
212
+ # Seems to be problematic /w IPv6
213
+ # if self.status == 1:
214
+ # self.sock.shutdown(socket.SHUT_RDWR)
215
+ self.sock.close()
216
+
217
+ @property
218
+ def invalidSocket(self) -> bool:
219
+ return not hasattr(self, "sock") or self.sock.fileno() == -1
pyxcp/transport/sxi.py ADDED
@@ -0,0 +1,135 @@
1
+ import struct
2
+ from collections import deque
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ import serial
7
+
8
+ import pyxcp.types as types
9
+ from pyxcp.transport.base import BaseTransport
10
+
11
+
12
+ @dataclass
13
+ class HeaderValues:
14
+ length: int = 0
15
+ counter: int = 0
16
+ filler: int = 0
17
+
18
+
19
+ RECV_SIZE = 16384
20
+
21
+
22
+ class SxI(BaseTransport):
23
+ """"""
24
+
25
+ def __init__(self, config=None, policy=None, transport_layer_interface: Optional[serial.Serial] = None) -> None:
26
+ super().__init__(config, policy, transport_layer_interface)
27
+ self.load_config(config)
28
+ self.port_name = self.config.port
29
+ self.baudrate = self.config.bitrate
30
+ self.bytesize = self.config.bytesize
31
+ self.parity = self.config.parity
32
+ self.stopbits = self.config.stopbits
33
+ self.mode = self.config.mode
34
+ self.header_format = self.config.header_format
35
+ self.tail_format = self.config.tail_format
36
+ self.framing = self.config.framing
37
+ self.esc_sync = self.config.esc_sync
38
+ self.esc_esc = self.config.esc_esc
39
+ self.make_header()
40
+ self.comm_port: serial.Serial
41
+
42
+ if self.has_user_supplied_interface and transport_layer_interface:
43
+ self.comm_port = transport_layer_interface
44
+ else:
45
+ self.logger.info(f"XCPonSxI - trying to open serial comm_port {self.port_name!r}.")
46
+ try:
47
+ self.comm_port = serial.Serial(
48
+ port=self.port_name,
49
+ baudrate=self.baudrate,
50
+ bytesize=self.bytesize,
51
+ parity=self.parity,
52
+ stopbits=self.stopbits,
53
+ timeout=self.timeout,
54
+ write_timeout=self.timeout,
55
+ )
56
+ except serial.SerialException as e:
57
+ self.logger.critical(f"XCPonSxI - {e}")
58
+ raise
59
+ self._packets = deque()
60
+
61
+ def __del__(self) -> None:
62
+ self.close_connection()
63
+
64
+ def make_header(self) -> None:
65
+ def unpack_len(args):
66
+ (length,) = args
67
+ return HeaderValues(length=length)
68
+
69
+ def unpack_len_counter(args):
70
+ length, counter = args
71
+ return HeaderValues(length=length, counter=counter)
72
+
73
+ def unpack_len_filler(args):
74
+ length, filler = args
75
+ return HeaderValues(length=length, filler=filler)
76
+
77
+ HEADER_FORMATS = {
78
+ "HEADER_LEN_BYTE": ("B", unpack_len),
79
+ "HEADER_LEN_CTR_BYTE": ("BB", unpack_len_counter),
80
+ "HEADER_LEN_FILL_BYTE": ("BB", unpack_len_filler),
81
+ "HEADER_LEN_WORD": ("H", unpack_len),
82
+ "HEADER_LEN_CTR_WORD": ("HH", unpack_len_counter),
83
+ "HEADER_LEN_FILL_WORD": ("HH", unpack_len_filler),
84
+ }
85
+ fmt, unpacker = HEADER_FORMATS[self.header_format]
86
+ self.HEADER = struct.Struct(f"<{fmt}")
87
+ self.HEADER_SIZE = self.HEADER.size
88
+ self.unpacker = unpacker
89
+
90
+ def connect(self) -> None:
91
+ self.logger.info(
92
+ f"XCPonSxI - serial comm_port {self.comm_port.portstr!r} openend [{self.baudrate}/{self.bytesize}-{self.parity}-{self.stopbits}]"
93
+ )
94
+ self.start_listener()
95
+
96
+ def output(self, enable) -> None:
97
+ if enable:
98
+ self.comm_port.rts = False
99
+ self.comm_port.dtr = False
100
+ else:
101
+ self.comm_port.rts = True
102
+ self.comm_port.dtr = True
103
+
104
+ def flush(self) -> None:
105
+ self.comm_port.flush()
106
+
107
+ def start_listener(self) -> None:
108
+ super().start_listener()
109
+
110
+ def listen(self) -> None:
111
+ while True:
112
+ if self.closeEvent.is_set():
113
+ return
114
+ if not self.comm_port.in_waiting:
115
+ continue
116
+
117
+ recv_timestamp = self.timestamp.value
118
+ header_values = self.unpacker(self.HEADER.unpack(self.comm_port.read(self.HEADER_SIZE)))
119
+ length, counter, _ = header_values.length, header_values.counter, header_values.filler
120
+
121
+ response = self.comm_port.read(length)
122
+ self.timing.stop()
123
+
124
+ if len(response) != length:
125
+ raise types.FrameSizeError("Size mismatch.")
126
+ self.process_response(response, length, counter, recv_timestamp)
127
+
128
+ def send(self, frame) -> None:
129
+ self.pre_send_timestamp = self.timestamp.value
130
+ self.comm_port.write(frame)
131
+ self.post_send_timestamp = self.timestamp.value
132
+
133
+ def close_connection(self) -> None:
134
+ if hasattr(self, "comm_port") and self.comm_port.is_open and not self.has_user_supplied_interface:
135
+ self.comm_port.close()
File without changes
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python
2
+ import struct
3
+ import threading
4
+ from array import array
5
+ from collections import deque
6
+ from typing import Optional
7
+
8
+ import usb.backend.libusb0 as libusb0
9
+ import usb.backend.libusb1 as libusb1
10
+ import usb.backend.openusb as openusb
11
+ import usb.core
12
+ import usb.util
13
+ from usb.core import USBError, USBTimeoutError
14
+
15
+ from pyxcp.transport.base import BaseTransport
16
+ from pyxcp.utils import short_sleep
17
+
18
+
19
+ RECV_SIZE = 16384
20
+ FIVE_MS = 5_000_000 # Five milliseconds in nanoseconds.
21
+
22
+
23
+ class Usb(BaseTransport):
24
+ """"""
25
+
26
+ HEADER = struct.Struct("<2H")
27
+ HEADER_SIZE = HEADER.size
28
+
29
+ def __init__(self, config=None, policy=None, transport_layer_interface: Optional[usb.core.Device] = None):
30
+ super().__init__(config, policy, transport_layer_interface)
31
+ self.load_config(config)
32
+ self.serial_number: str = self.config.serial_number
33
+ self.vendor_id: int = self.config.vendor_id
34
+ self.product_id: int = self.config.product_id
35
+ self.configuration_number: int = self.config.configuration_number
36
+ self.interface_number: int = self.config.interface_number
37
+ self.library: str = self.config.library
38
+ self.header_format: str = self.config.header_format
39
+ self.library = self.config.get("library")
40
+ self.device = None
41
+
42
+ ## IN-EP (RES/ERR, DAQ, and EV/SERV) Parameters.
43
+ self.in_ep_number: int = self.config.in_ep_number
44
+ self.in_ep_transfer_type = self.config.in_ep_transfer_type
45
+ self.in_ep_max_packet_size: int = self.config.in_ep_max_packet_size
46
+ self.in_ep_polling_interval: int = self.config.in_ep_polling_interval
47
+ self.in_ep_message_packing = self.config.in_ep_message_packing
48
+ self.in_ep_alignment = self.config.in_ep_alignment
49
+ self.in_ep_recommended_host_bufsize: int = self.config.in_ep_recommended_host_bufsize
50
+
51
+ ## OUT-EP (CMD and STIM) Parameters.
52
+ self.out_ep_number: int = self.config.out_ep_number
53
+
54
+ self.device: Optional[usb.core.Device] = None
55
+ self.status = 0
56
+
57
+ self._packet_listener = threading.Thread(
58
+ target=self._packet_listen,
59
+ args=(),
60
+ kwargs={},
61
+ )
62
+ self._packets = deque()
63
+
64
+ def connect(self):
65
+ if self.library:
66
+ for backend_provider in (libusb1, libusb0, openusb):
67
+ backend = backend_provider.get_backend(find_library=lambda x: self.library)
68
+ if backend:
69
+ break
70
+ else:
71
+ backend = None
72
+
73
+ if self.vendor_id and self.product_id:
74
+ kwargs = {
75
+ "find_all": True,
76
+ "idVendor": self.vendor_id,
77
+ "idProduct": self.product_id,
78
+ "backend": backend,
79
+ }
80
+ else:
81
+ kwargs = {
82
+ "find_all": True,
83
+ "backend": backend,
84
+ }
85
+
86
+ for device in usb.core.find(**kwargs):
87
+ try:
88
+ if device.serial_number.strip().strip("\0").lower() == self.serial_number.lower():
89
+ self.device = device
90
+ break
91
+ except (USBError, USBTimeoutError):
92
+ continue
93
+ else:
94
+ raise Exception(f"XCPonUSB - device with serial {self.serial_number!r} not found")
95
+
96
+ current_configuration = self.device.get_active_configuration()
97
+ if current_configuration.bConfigurationValue != self.configuration_number:
98
+ self.device.set_configuration(self.configuration_number)
99
+ cfg = self.device.get_active_configuration()
100
+
101
+ interface = cfg[(self.interface_number, 0)]
102
+
103
+ self.out_ep = interface[self.out_ep_number]
104
+ self.in_ep = interface[self.in_ep_number]
105
+
106
+ self.start_listener()
107
+ self.status = 1 # connected
108
+
109
+ def start_listener(self):
110
+ super().start_listener()
111
+ if self._packet_listener.is_alive():
112
+ self._packet_listener.join()
113
+ self._packet_listener = threading.Thread(target=self._packet_listen)
114
+ self._packet_listener.start()
115
+
116
+ def close(self):
117
+ """Close the transport-layer connection and event-loop."""
118
+ self.finish_listener()
119
+ if self.listener.is_alive():
120
+ self.listener.join()
121
+ if self._packet_listener.is_alive():
122
+ self._packet_listener.join()
123
+ self.close_connection()
124
+
125
+ def _packet_listen(self):
126
+ close_event_set = self.closeEvent.is_set
127
+ _packets = self._packets
128
+ read = self.in_ep.read
129
+ buffer = array("B", bytes(RECV_SIZE))
130
+ buffer_view = memoryview(buffer)
131
+ while True:
132
+ try:
133
+ if close_event_set():
134
+ return
135
+ try:
136
+ recv_timestamp = self.timestamp.value
137
+ read_count = read(buffer, 100) # 100ms timeout
138
+ if read_count != RECV_SIZE:
139
+ _packets.append((buffer_view[:read_count].tobytes(), recv_timestamp))
140
+ else:
141
+ _packets.append((buffer.tobytes(), recv_timestamp))
142
+ except (USBError, USBTimeoutError):
143
+ # print(format_exc())
144
+ short_sleep()
145
+ continue
146
+ except BaseException: # noqa: B036
147
+ # Note: catch-all only permitted if the intention is re-raising.
148
+ self.status = 0 # disconnected
149
+ break
150
+
151
+ def listen(self):
152
+ HEADER_UNPACK_FROM = self.HEADER.unpack_from
153
+ HEADER_SIZE = self.HEADER_SIZE
154
+ popleft = self._packets.popleft
155
+ process_response = self.process_response
156
+ close_event_set = self.closeEvent.is_set
157
+ _packets = self._packets
158
+ length: Optional[int] = None
159
+ counter: int = 0
160
+ data: bytearray = bytearray(b"")
161
+ last_sleep: int = self.timestamp.value
162
+
163
+ while True:
164
+ if close_event_set():
165
+ return
166
+ count: int = len(_packets)
167
+ if not count:
168
+ short_sleep()
169
+ last_sleep = self.timestamp.value
170
+ continue
171
+ for _ in range(count):
172
+ bts, timestamp = popleft()
173
+ data += bts
174
+ current_size: int = len(data)
175
+ current_position: int = 0
176
+ while True:
177
+ if self.timestamp.value - last_sleep >= FIVE_MS:
178
+ short_sleep()
179
+ last_sleep = self.timestamp.value
180
+ if length is None:
181
+ if current_size >= HEADER_SIZE:
182
+ length, counter = HEADER_UNPACK_FROM(data, current_position)
183
+ current_position += HEADER_SIZE
184
+ current_size -= HEADER_SIZE
185
+ else:
186
+ data = data[current_position:]
187
+ break
188
+ else:
189
+ if current_size >= length:
190
+ response = data[current_position : current_position + length]
191
+ process_response(response, length, counter, timestamp)
192
+ current_size -= length
193
+ current_position += length
194
+ length = None
195
+ else:
196
+ data = data[current_position:]
197
+ break
198
+
199
+ def send(self, frame):
200
+ self.pre_send_timestamp = self.timestamp.value
201
+ try:
202
+ self.out_ep.write(frame)
203
+ except (USBError, USBTimeoutError):
204
+ # sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised
205
+ # even though the command is send and a reply is received from the device.
206
+ # Ignore this here since a Timeout error will be raised anyway if
207
+ # the device does not respond
208
+ pass
209
+ self.post_send_timestamp = self.timestamp.value
210
+
211
+ def close_connection(self):
212
+ if self.device is not None:
213
+ usb.util.dispose_resources(self.device)