pyxcp 0.23.0__cp313-cp313-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.
- pyxcp/__init__.py +20 -0
- pyxcp/aml/EtasCANMonitoring.a2l +82 -0
- pyxcp/aml/EtasCANMonitoring.aml +67 -0
- pyxcp/aml/XCP_Common.aml +408 -0
- pyxcp/aml/XCPonCAN.aml +78 -0
- pyxcp/aml/XCPonEth.aml +33 -0
- pyxcp/aml/XCPonFlx.aml +113 -0
- pyxcp/aml/XCPonSxI.aml +66 -0
- pyxcp/aml/XCPonUSB.aml +106 -0
- pyxcp/aml/ifdata_CAN.a2l +20 -0
- pyxcp/aml/ifdata_Eth.a2l +11 -0
- pyxcp/aml/ifdata_Flx.a2l +94 -0
- pyxcp/aml/ifdata_SxI.a2l +13 -0
- pyxcp/aml/ifdata_USB.a2l +81 -0
- pyxcp/asam/__init__.py +0 -0
- pyxcp/asam/types.py +131 -0
- pyxcp/asamkeydll.c +116 -0
- pyxcp/asamkeydll.sh +2 -0
- pyxcp/checksum.py +732 -0
- pyxcp/cmdline.py +52 -0
- pyxcp/config/__init__.py +1113 -0
- pyxcp/config/legacy.py +120 -0
- pyxcp/constants.py +47 -0
- pyxcp/cpp_ext/__init__.py +0 -0
- pyxcp/cpp_ext/bin.hpp +104 -0
- pyxcp/cpp_ext/blockmem.hpp +58 -0
- pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp313-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/daqlist.hpp +206 -0
- pyxcp/cpp_ext/event.hpp +67 -0
- pyxcp/cpp_ext/extension_wrapper.cpp +100 -0
- pyxcp/cpp_ext/helper.hpp +280 -0
- pyxcp/cpp_ext/mcobject.hpp +246 -0
- pyxcp/cpp_ext/tsqueue.hpp +46 -0
- pyxcp/daq_stim/__init__.py +232 -0
- pyxcp/daq_stim/optimize/__init__.py +67 -0
- pyxcp/daq_stim/optimize/binpacking.py +41 -0
- pyxcp/daq_stim/scheduler.cpp +62 -0
- pyxcp/daq_stim/scheduler.hpp +75 -0
- pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp312-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp313-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cpp +13 -0
- pyxcp/daq_stim/stim.hpp +604 -0
- pyxcp/daq_stim/stim_wrapper.cpp +50 -0
- pyxcp/dllif.py +100 -0
- pyxcp/errormatrix.py +878 -0
- pyxcp/examples/conf_can.toml +19 -0
- pyxcp/examples/conf_can_user.toml +16 -0
- pyxcp/examples/conf_can_vector.json +11 -0
- pyxcp/examples/conf_can_vector.toml +11 -0
- pyxcp/examples/conf_eth.toml +9 -0
- pyxcp/examples/conf_nixnet.json +20 -0
- pyxcp/examples/conf_socket_can.toml +12 -0
- pyxcp/examples/conf_sxi.json +9 -0
- pyxcp/examples/conf_sxi.toml +7 -0
- pyxcp/examples/run_daq.py +163 -0
- pyxcp/examples/xcp_policy.py +60 -0
- pyxcp/examples/xcp_read_benchmark.py +38 -0
- pyxcp/examples/xcp_skel.py +49 -0
- pyxcp/examples/xcp_unlock.py +38 -0
- pyxcp/examples/xcp_user_supplied_driver.py +44 -0
- pyxcp/examples/xcphello.py +78 -0
- pyxcp/examples/xcphello_recorder.py +107 -0
- pyxcp/master/__init__.py +9 -0
- pyxcp/master/errorhandler.py +442 -0
- pyxcp/master/master.py +2047 -0
- pyxcp/py.typed +0 -0
- pyxcp/recorder/__init__.py +101 -0
- pyxcp/recorder/build_clang.cmd +1 -0
- pyxcp/recorder/build_clang.sh +2 -0
- pyxcp/recorder/build_gcc.cmd +1 -0
- pyxcp/recorder/build_gcc.sh +2 -0
- pyxcp/recorder/build_gcc_arm.sh +2 -0
- pyxcp/recorder/converter/__init__.py +450 -0
- pyxcp/recorder/lz4.c +2829 -0
- pyxcp/recorder/lz4.h +879 -0
- pyxcp/recorder/lz4hc.c +2041 -0
- pyxcp/recorder/lz4hc.h +413 -0
- pyxcp/recorder/mio.hpp +1714 -0
- pyxcp/recorder/reader.hpp +139 -0
- pyxcp/recorder/reco.py +277 -0
- pyxcp/recorder/recorder.rst +0 -0
- pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp312-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp313-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cpp +59 -0
- pyxcp/recorder/rekorder.hpp +274 -0
- pyxcp/recorder/setup.py +41 -0
- pyxcp/recorder/test_reko.py +34 -0
- pyxcp/recorder/unfolder.hpp +1332 -0
- pyxcp/recorder/wrap.cpp +189 -0
- pyxcp/recorder/writer.hpp +302 -0
- pyxcp/scripts/__init__.py +0 -0
- pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
- pyxcp/scripts/xcp_examples.py +64 -0
- pyxcp/scripts/xcp_fetch_a2l.py +40 -0
- pyxcp/scripts/xcp_id_scanner.py +19 -0
- pyxcp/scripts/xcp_info.py +144 -0
- pyxcp/scripts/xcp_profile.py +27 -0
- pyxcp/scripts/xmraw_converter.py +31 -0
- pyxcp/stim/__init__.py +0 -0
- pyxcp/tests/test_asam_types.py +24 -0
- pyxcp/tests/test_binpacking.py +186 -0
- pyxcp/tests/test_can.py +1324 -0
- pyxcp/tests/test_checksum.py +95 -0
- pyxcp/tests/test_daq.py +193 -0
- pyxcp/tests/test_daq_opt.py +426 -0
- pyxcp/tests/test_frame_padding.py +156 -0
- pyxcp/tests/test_master.py +2006 -0
- pyxcp/tests/test_transport.py +81 -0
- pyxcp/tests/test_utils.py +30 -0
- pyxcp/timing.py +60 -0
- pyxcp/transport/__init__.py +10 -0
- pyxcp/transport/base.py +440 -0
- pyxcp/transport/base_transport.hpp +0 -0
- pyxcp/transport/can.py +556 -0
- pyxcp/transport/eth.py +219 -0
- pyxcp/transport/sxi.py +135 -0
- pyxcp/transport/transport_wrapper.cpp +0 -0
- pyxcp/transport/usb_transport.py +213 -0
- pyxcp/types.py +1000 -0
- pyxcp/utils.py +128 -0
- pyxcp/vector/__init__.py +0 -0
- pyxcp/vector/map.py +82 -0
- pyxcp-0.23.0.dist-info/LICENSE +165 -0
- pyxcp-0.23.0.dist-info/METADATA +107 -0
- pyxcp-0.23.0.dist-info/RECORD +134 -0
- pyxcp-0.23.0.dist-info/WHEEL +4 -0
- 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)
|