pyxcp 0.23.8__cp313-cp313-macosx_11_0_arm64.whl → 0.25.7__cp313-cp313-macosx_11_0_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.
- pyxcp/__init__.py +1 -1
- pyxcp/cmdline.py +14 -29
- pyxcp/config/__init__.py +1257 -1258
- pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
- pyxcp/cpp_ext/bin.hpp +7 -6
- pyxcp/cpp_ext/cpp_ext.cpython-310-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-311-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-312-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-313-darwin.so +0 -0
- pyxcp/cpp_ext/daqlist.hpp +241 -73
- pyxcp/cpp_ext/extension_wrapper.cpp +123 -15
- pyxcp/cpp_ext/framing.hpp +360 -0
- pyxcp/cpp_ext/helper.hpp +280 -280
- pyxcp/cpp_ext/mcobject.hpp +248 -246
- pyxcp/cpp_ext/sxi_framing.hpp +332 -0
- pyxcp/daq_stim/__init__.py +145 -67
- pyxcp/daq_stim/optimize/binpacking.py +2 -2
- pyxcp/daq_stim/scheduler.cpp +8 -8
- pyxcp/errormatrix.py +2 -2
- pyxcp/examples/run_daq.py +5 -4
- pyxcp/examples/xcp_policy.py +6 -6
- pyxcp/examples/xcp_read_benchmark.py +2 -2
- pyxcp/examples/xcp_skel.py +1 -2
- pyxcp/examples/xcp_unlock.py +10 -12
- pyxcp/examples/xcp_user_supplied_driver.py +1 -2
- pyxcp/examples/xcphello.py +2 -15
- pyxcp/examples/xcphello_recorder.py +2 -2
- pyxcp/master/__init__.py +1 -0
- pyxcp/master/errorhandler.py +134 -4
- pyxcp/master/master.py +823 -252
- pyxcp/recorder/.idea/.gitignore +8 -0
- pyxcp/recorder/.idea/misc.xml +4 -0
- pyxcp/recorder/.idea/modules.xml +8 -0
- pyxcp/recorder/.idea/recorder.iml +6 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
- pyxcp/recorder/.idea/vcs.xml +10 -0
- pyxcp/recorder/__init__.py +96 -98
- pyxcp/recorder/converter/__init__.py +4 -10
- pyxcp/recorder/reader.hpp +138 -139
- pyxcp/recorder/reco.py +1 -0
- pyxcp/recorder/rekorder.cpython-310-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-311-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-312-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-313-darwin.so +0 -0
- pyxcp/recorder/rekorder.hpp +274 -274
- pyxcp/recorder/unfolder.hpp +1354 -1319
- pyxcp/recorder/wrap.cpp +184 -183
- pyxcp/recorder/writer.hpp +302 -302
- pyxcp/scripts/xcp_daq_recorder.py +54 -0
- pyxcp/scripts/xcp_fetch_a2l.py +2 -2
- pyxcp/scripts/xcp_id_scanner.py +1 -2
- pyxcp/scripts/xcp_info.py +66 -51
- pyxcp/scripts/xcp_profile.py +1 -2
- pyxcp/tests/test_daq.py +1 -1
- pyxcp/tests/test_framing.py +262 -0
- pyxcp/tests/test_master.py +210 -100
- pyxcp/tests/test_transport.py +138 -42
- pyxcp/timing.py +1 -1
- pyxcp/transport/__init__.py +8 -5
- pyxcp/transport/base.py +70 -180
- pyxcp/transport/can.py +58 -7
- pyxcp/transport/eth.py +32 -15
- pyxcp/transport/hdf5_policy.py +167 -0
- pyxcp/transport/sxi.py +126 -52
- pyxcp/transport/transport_ext.cpython-310-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-311-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-312-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-313-darwin.so +0 -0
- pyxcp/transport/transport_ext.hpp +214 -0
- pyxcp/transport/transport_wrapper.cpp +249 -0
- pyxcp/transport/usb_transport.py +47 -31
- pyxcp/types.py +0 -13
- pyxcp/{utils.py → utils/__init__.py} +1 -2
- pyxcp/utils/cli.py +78 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/METADATA +4 -2
- pyxcp-0.25.7.dist-info/RECORD +158 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/WHEEL +1 -1
- pyxcp/examples/conf_sxi.json +0 -9
- pyxcp/examples/conf_sxi.toml +0 -7
- pyxcp-0.23.8.dist-info/RECORD +0 -135
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/entry_points.txt +0 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info/licenses}/LICENSE +0 -0
pyxcp/transport/can.py
CHANGED
|
@@ -20,9 +20,14 @@ from can.interface import _get_class_for_interface
|
|
|
20
20
|
from rich.console import Console
|
|
21
21
|
|
|
22
22
|
from pyxcp.config import CAN_INTERFACE_MAP
|
|
23
|
-
from pyxcp.transport.base import
|
|
23
|
+
from pyxcp.transport.base import (
|
|
24
|
+
BaseTransport,
|
|
25
|
+
ChecksumType,
|
|
26
|
+
XcpFramingConfig,
|
|
27
|
+
XcpTransportLayerType,
|
|
28
|
+
)
|
|
24
29
|
|
|
25
|
-
from ..utils import seconds_to_nanoseconds
|
|
30
|
+
from ..utils import seconds_to_nanoseconds, short_sleep
|
|
26
31
|
|
|
27
32
|
|
|
28
33
|
console = Console()
|
|
@@ -397,7 +402,15 @@ class Can(BaseTransport):
|
|
|
397
402
|
HEADER_SIZE = 0
|
|
398
403
|
|
|
399
404
|
def __init__(self, config, policy=None, transport_layer_interface: Optional[BusABC] = None):
|
|
400
|
-
|
|
405
|
+
framing_config = XcpFramingConfig(
|
|
406
|
+
transport_layer_type=XcpTransportLayerType.CAN,
|
|
407
|
+
header_len=0,
|
|
408
|
+
header_ctr=0,
|
|
409
|
+
header_fill=0,
|
|
410
|
+
tail_fill=False,
|
|
411
|
+
tail_cs=ChecksumType.NO_CHECKSUM,
|
|
412
|
+
)
|
|
413
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
401
414
|
self.load_config(config)
|
|
402
415
|
self.useDefaultListener = self.config.use_default_listener
|
|
403
416
|
self.can_id_master = Identifier(self.config.can_id_master)
|
|
@@ -485,12 +498,50 @@ class Can(BaseTransport):
|
|
|
485
498
|
)
|
|
486
499
|
|
|
487
500
|
def listen(self):
|
|
501
|
+
"""Process CAN frames received from the interface.
|
|
502
|
+
|
|
503
|
+
This method runs in a separate thread and continuously polls the CAN interface
|
|
504
|
+
for new frames. When a frame is received, it extracts the data and timestamp
|
|
505
|
+
and passes them to the data_received method for further processing.
|
|
506
|
+
|
|
507
|
+
The method includes periodic sleep to prevent CPU hogging and error handling
|
|
508
|
+
to ensure the listener thread doesn't crash on exceptions.
|
|
509
|
+
"""
|
|
510
|
+
# Cache frequently used methods and attributes for better performance
|
|
511
|
+
close_event_set = self.closeEvent.is_set
|
|
512
|
+
can_interface_read = self.can_interface.read
|
|
513
|
+
data_received = self.data_received
|
|
514
|
+
|
|
515
|
+
# State variables for processing
|
|
516
|
+
last_sleep = self.timestamp.value
|
|
517
|
+
FIVE_MS = 5_000_000 # Five milliseconds in nanoseconds
|
|
518
|
+
|
|
488
519
|
while True:
|
|
489
|
-
if
|
|
520
|
+
# Check if we should exit the loop
|
|
521
|
+
if close_event_set():
|
|
490
522
|
return
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
523
|
+
|
|
524
|
+
# Periodically sleep to prevent CPU hogging
|
|
525
|
+
if self.timestamp.value - last_sleep >= FIVE_MS:
|
|
526
|
+
short_sleep()
|
|
527
|
+
last_sleep = self.timestamp.value
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
# Try to read a frame from the CAN interface
|
|
531
|
+
frame = can_interface_read()
|
|
532
|
+
if frame:
|
|
533
|
+
# Process the frame if one was received
|
|
534
|
+
data_received(frame.data, frame.timestamp)
|
|
535
|
+
else:
|
|
536
|
+
# No frame available, sleep briefly to avoid busy waiting
|
|
537
|
+
short_sleep()
|
|
538
|
+
last_sleep = self.timestamp.value
|
|
539
|
+
except Exception as e:
|
|
540
|
+
# Log any exceptions but continue processing
|
|
541
|
+
self.logger.error(f"Error in CAN listen thread: {e}")
|
|
542
|
+
# Sleep briefly to avoid tight error loops
|
|
543
|
+
short_sleep()
|
|
544
|
+
last_sleep = self.timestamp.value
|
|
494
545
|
|
|
495
546
|
def connect(self):
|
|
496
547
|
# Start listener lazily after a successful interface connection to avoid a dangling
|
pyxcp/transport/eth.py
CHANGED
|
@@ -6,7 +6,12 @@ import threading
|
|
|
6
6
|
from collections import deque
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
|
9
|
-
from pyxcp.transport.base import
|
|
9
|
+
from pyxcp.transport.base import (
|
|
10
|
+
BaseTransport,
|
|
11
|
+
ChecksumType,
|
|
12
|
+
XcpFramingConfig,
|
|
13
|
+
XcpTransportLayerType,
|
|
14
|
+
)
|
|
10
15
|
from pyxcp.utils import short_sleep
|
|
11
16
|
|
|
12
17
|
|
|
@@ -36,11 +41,18 @@ class Eth(BaseTransport):
|
|
|
36
41
|
|
|
37
42
|
MAX_DATAGRAM_SIZE = 512
|
|
38
43
|
HEADER = struct.Struct("<HH")
|
|
39
|
-
HEADER_SIZE = HEADER.size
|
|
40
44
|
|
|
41
45
|
def __init__(self, config=None, policy=None, transport_layer_interface: Optional[socket.socket] = None) -> None:
|
|
42
|
-
super().__init__(config, policy, transport_layer_interface)
|
|
43
46
|
self.load_config(config)
|
|
47
|
+
framing_config = XcpFramingConfig(
|
|
48
|
+
transport_layer_type=XcpTransportLayerType.ETH,
|
|
49
|
+
header_len=2,
|
|
50
|
+
header_ctr=2,
|
|
51
|
+
header_fill=0,
|
|
52
|
+
tail_fill=False,
|
|
53
|
+
tail_cs=ChecksumType.NO_CHECKSUM,
|
|
54
|
+
)
|
|
55
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
44
56
|
self.host: str = self.config.host
|
|
45
57
|
self.port: int = self.config.port
|
|
46
58
|
self.protocol: int = self.config.protocol
|
|
@@ -96,6 +108,7 @@ class Eth(BaseTransport):
|
|
|
96
108
|
target=self._packet_listen,
|
|
97
109
|
args=(),
|
|
98
110
|
kwargs={},
|
|
111
|
+
daemon=True,
|
|
99
112
|
)
|
|
100
113
|
self._packets = deque()
|
|
101
114
|
|
|
@@ -109,17 +122,23 @@ class Eth(BaseTransport):
|
|
|
109
122
|
def start_listener(self) -> None:
|
|
110
123
|
super().start_listener()
|
|
111
124
|
if self._packet_listener.is_alive():
|
|
112
|
-
self._packet_listener.join()
|
|
113
|
-
self._packet_listener = threading.Thread(target=self._packet_listen)
|
|
125
|
+
self._packet_listener.join(timeout=2.0)
|
|
126
|
+
self._packet_listener = threading.Thread(target=self._packet_listen, daemon=True)
|
|
114
127
|
self._packet_listener.start()
|
|
115
128
|
|
|
116
129
|
def close(self) -> None:
|
|
117
130
|
"""Close the transport-layer connection and event-loop."""
|
|
118
131
|
self.finish_listener()
|
|
119
|
-
|
|
120
|
-
self.listener.
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
try:
|
|
133
|
+
if self.listener.is_alive():
|
|
134
|
+
self.listener.join(timeout=2.0)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
try:
|
|
138
|
+
if self._packet_listener.is_alive():
|
|
139
|
+
self._packet_listener.join(timeout=2.0)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
123
142
|
self.close_connection()
|
|
124
143
|
|
|
125
144
|
def _packet_listen(self) -> None:
|
|
@@ -162,8 +181,6 @@ class Eth(BaseTransport):
|
|
|
162
181
|
break
|
|
163
182
|
|
|
164
183
|
def listen(self) -> None:
|
|
165
|
-
HEADER_UNPACK_FROM = self.HEADER.unpack_from
|
|
166
|
-
HEADER_SIZE = self.HEADER_SIZE
|
|
167
184
|
process_response = self.process_response
|
|
168
185
|
popleft = self._packets.popleft
|
|
169
186
|
close_event_set = self.closeEvent.is_set
|
|
@@ -186,10 +203,10 @@ class Eth(BaseTransport):
|
|
|
186
203
|
current_position: int = 0
|
|
187
204
|
while True:
|
|
188
205
|
if length is None:
|
|
189
|
-
if current_size >=
|
|
190
|
-
length, counter =
|
|
191
|
-
current_position +=
|
|
192
|
-
current_size -=
|
|
206
|
+
if current_size >= self.framing.header_size:
|
|
207
|
+
length, counter = self.framing.unpack_header(bytes(data), initial_offset=current_position)
|
|
208
|
+
current_position += self.framing.header_size
|
|
209
|
+
current_size -= self.framing.header_size
|
|
193
210
|
else:
|
|
194
211
|
data = data[current_position:]
|
|
195
212
|
break
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Dict
|
|
6
|
+
|
|
7
|
+
import h5py
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyxcp.daq_stim import DaqOnlinePolicy, DaqList
|
|
10
|
+
from pyxcp import __version__ as pyxcp_version
|
|
11
|
+
|
|
12
|
+
BATCH_SIZE = 4096
|
|
13
|
+
|
|
14
|
+
MAP_TO_NP = {
|
|
15
|
+
"U8": np.uint8,
|
|
16
|
+
"I8": np.int8,
|
|
17
|
+
"U16": np.uint16,
|
|
18
|
+
"I16": np.int16,
|
|
19
|
+
"U32": np.uint32,
|
|
20
|
+
"I32": np.int32,
|
|
21
|
+
"U64": np.uint64,
|
|
22
|
+
"I64": np.int64,
|
|
23
|
+
"F32": np.float32,
|
|
24
|
+
"F64": np.float64,
|
|
25
|
+
"F16": np.float16,
|
|
26
|
+
"BF16": np.float16,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
MAP_TO_ASAM_HO = {
|
|
30
|
+
"U8": "A_UINT8",
|
|
31
|
+
"I8": "A_INT8",
|
|
32
|
+
"U16": "A_UINT16",
|
|
33
|
+
"I16": "A_INT16",
|
|
34
|
+
"U32": "A_UINT32",
|
|
35
|
+
"I32": "A_INT32",
|
|
36
|
+
"U64": "A_UINT64",
|
|
37
|
+
"I64": "A_INT64",
|
|
38
|
+
"F32": "A_FLOAT32",
|
|
39
|
+
"F64": "A_FLOAT64",
|
|
40
|
+
"F16": "A_FLOAT16",
|
|
41
|
+
"BF16": "A_FLOAT16",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BufferedDataset:
|
|
46
|
+
def __init__(self, dataset: h5py.Dataset):
|
|
47
|
+
self.dataset = dataset
|
|
48
|
+
self.buffer: List[int | float] = []
|
|
49
|
+
|
|
50
|
+
def add_sample(self, sample: int | float):
|
|
51
|
+
self.buffer.append(sample)
|
|
52
|
+
if len(self.buffer) >= BATCH_SIZE:
|
|
53
|
+
self.flush()
|
|
54
|
+
|
|
55
|
+
def flush(self):
|
|
56
|
+
batch = np.array(self.buffer)
|
|
57
|
+
self.dataset.resize((self.dataset.shape[0] + len(batch),))
|
|
58
|
+
self.dataset[-len(batch) :] = batch
|
|
59
|
+
self.buffer.clear()
|
|
60
|
+
self.dataset.flush()
|
|
61
|
+
|
|
62
|
+
def __len__(self):
|
|
63
|
+
return len(self.buffer)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DatasetGroup:
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
ts0_ds: BufferedDataset,
|
|
70
|
+
ts1_ds: BufferedDataset,
|
|
71
|
+
datasets: List[BufferedDataset],
|
|
72
|
+
):
|
|
73
|
+
self.ts0_ds = ts0_ds
|
|
74
|
+
self.ts1_ds = ts1_ds
|
|
75
|
+
self.datasets = datasets
|
|
76
|
+
|
|
77
|
+
def feed(self, ts0: int, ts1: int, *datasets):
|
|
78
|
+
self.ts0_ds.add_sample(ts0)
|
|
79
|
+
self.ts1_ds.add_sample(ts1)
|
|
80
|
+
for dataset, value in zip(self.datasets, datasets):
|
|
81
|
+
dataset.add_sample(value)
|
|
82
|
+
|
|
83
|
+
def finalize(self):
|
|
84
|
+
for dataset in self.datasets:
|
|
85
|
+
dataset.flush()
|
|
86
|
+
self.ts0_ds.flush()
|
|
87
|
+
self.ts1_ds.flush()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_timestamp_column(hdf_file: h5py.File, group_name: str, num: int) -> h5py.Dataset:
|
|
91
|
+
result = hdf_file.create_dataset(
|
|
92
|
+
f"/{group_name}/timestamp{num}",
|
|
93
|
+
shape=(0,),
|
|
94
|
+
maxshape=(None,),
|
|
95
|
+
dtype=np.uint64,
|
|
96
|
+
chunks=True,
|
|
97
|
+
)
|
|
98
|
+
result.attrs["asam_data_type"] = "A_UINT64"
|
|
99
|
+
result.attrs["resolution"] = ("1 nanosecond",)
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Hdf5OnlinePolicy(DaqOnlinePolicy):
|
|
104
|
+
def __init__(self, file_name: str | Path, daq_lists: List[DaqList], **metadata):
|
|
105
|
+
super().__init__(daq_lists=daq_lists)
|
|
106
|
+
path = Path(file_name)
|
|
107
|
+
if path.suffix != ".h5":
|
|
108
|
+
path = path.with_suffix(".h5")
|
|
109
|
+
self.hdf = h5py.File(path, "w", libver="latest")
|
|
110
|
+
self.metadata = self.set_metadata(**metadata)
|
|
111
|
+
|
|
112
|
+
def set_metadata(self, **metadata):
|
|
113
|
+
basic = {
|
|
114
|
+
"tool_name": "pyXCP",
|
|
115
|
+
"tool_version": f"{pyxcp_version}",
|
|
116
|
+
"created": f"{datetime.datetime.now().astimezone().isoformat()}",
|
|
117
|
+
}
|
|
118
|
+
for k, v in (basic | metadata).items():
|
|
119
|
+
self.hdf.attrs[k] = v
|
|
120
|
+
|
|
121
|
+
def initialize(self):
|
|
122
|
+
self.log.debug("Hdf5OnlinePolicy::Initialize()")
|
|
123
|
+
self.datasets: Dict[int, DatasetGroup] = {}
|
|
124
|
+
for num, daq_list in enumerate(self.daq_lists):
|
|
125
|
+
if daq_list.stim:
|
|
126
|
+
continue
|
|
127
|
+
grp = self.hdf.create_group(daq_list.name)
|
|
128
|
+
grp.attrs["event_num"] = daq_list.event_num
|
|
129
|
+
grp.attrs["enable_timestamps"] = daq_list.enable_timestamps
|
|
130
|
+
grp.attrs["prescaler"] = daq_list.prescaler
|
|
131
|
+
grp.attrs["priority"] = daq_list.priority
|
|
132
|
+
grp.attrs["direction"] = "STIM" if daq_list.stim else "DAQ"
|
|
133
|
+
ts0 = BufferedDataset(create_timestamp_column(self.hdf, daq_list.name, 0))
|
|
134
|
+
ts1 = BufferedDataset(create_timestamp_column(self.hdf, daq_list.name, 1))
|
|
135
|
+
meas_map = {m.name: m for m in self.daq_lists[num].measurements}
|
|
136
|
+
dsets = []
|
|
137
|
+
for name, _ in daq_list.headers:
|
|
138
|
+
meas = meas_map[name]
|
|
139
|
+
dataset = self.hdf.create_dataset(
|
|
140
|
+
f"/{daq_list.name}/{meas.name}/raw",
|
|
141
|
+
shape=(0,),
|
|
142
|
+
maxshape=(None,),
|
|
143
|
+
dtype=MAP_TO_NP[meas.data_type],
|
|
144
|
+
chunks=(1024,),
|
|
145
|
+
)
|
|
146
|
+
sub_group = dataset.parent
|
|
147
|
+
sub_group.attrs["asam_data_type"] = MAP_TO_ASAM_HO.get(meas.data_type, "n/a")
|
|
148
|
+
dataset.attrs["ecu_address"] = meas.address
|
|
149
|
+
dataset.attrs["ecu_address_extension"] = meas.ext
|
|
150
|
+
dsets.append(BufferedDataset(dataset))
|
|
151
|
+
self.datasets[num] = DatasetGroup(ts0_ds=ts0, ts1_ds=ts1, datasets=dsets)
|
|
152
|
+
self.hdf.flush()
|
|
153
|
+
|
|
154
|
+
def finalize(self):
|
|
155
|
+
self.log.debug("Hdf5OnlinePolicy::finalize()")
|
|
156
|
+
if hasattr(self, "datasets"):
|
|
157
|
+
for group in self.datasets.values():
|
|
158
|
+
group.finalize()
|
|
159
|
+
if hasattr(self, "hdf"):
|
|
160
|
+
self.hdf.close()
|
|
161
|
+
|
|
162
|
+
def on_daq_list(self, daq_list: int, timestamp0: int, timestamp1: int, payload: list):
|
|
163
|
+
group = self.datasets.get(daq_list)
|
|
164
|
+
if group is None:
|
|
165
|
+
self.log.warning(f"Received data for unknown DAQ list {daq_list}")
|
|
166
|
+
return
|
|
167
|
+
group.feed(timestamp0, timestamp1, *payload)
|
pyxcp/transport/sxi.py
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import threading
|
|
2
2
|
from collections import deque
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Any, Optional
|
|
5
5
|
|
|
6
6
|
import serial
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
from pyxcp.transport.transport_ext import (
|
|
9
|
+
SxiFrLBCN,
|
|
10
|
+
SxiFrLBC8,
|
|
11
|
+
SxiFrLBC16,
|
|
12
|
+
SxiFrLCBCN,
|
|
13
|
+
SxiFrLCBC8,
|
|
14
|
+
SxiFrLCBC16,
|
|
15
|
+
SxiFrLFBCN,
|
|
16
|
+
SxiFrLFBC8,
|
|
17
|
+
SxiFrLFBC16,
|
|
18
|
+
SxiFrLWCN,
|
|
19
|
+
SxiFrLWC8,
|
|
20
|
+
SxiFrLWC16,
|
|
21
|
+
SxiFrLCWCN,
|
|
22
|
+
SxiFrLCWC8,
|
|
23
|
+
SxiFrLCWC16,
|
|
24
|
+
SxiFrLFWCN,
|
|
25
|
+
SxiFrLFWC8,
|
|
26
|
+
SxiFrLFWC16,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from pyxcp.transport.base import (
|
|
30
|
+
BaseTransport,
|
|
31
|
+
ChecksumType,
|
|
32
|
+
XcpFramingConfig,
|
|
33
|
+
XcpTransportLayerType,
|
|
34
|
+
parse_header_format,
|
|
35
|
+
)
|
|
10
36
|
|
|
11
37
|
|
|
12
38
|
@dataclass
|
|
@@ -19,11 +45,23 @@ class HeaderValues:
|
|
|
19
45
|
RECV_SIZE = 16384
|
|
20
46
|
|
|
21
47
|
|
|
48
|
+
def get_receiver_class(header_format: str, checksum_format: str) -> Any:
|
|
49
|
+
COLUMN = {"NO_CHECKSUM": 0, "CHECKSUM_BYTE": 1, "CHECKSUM_WORD": 2}
|
|
50
|
+
FORMATS = {
|
|
51
|
+
"HEADER_LEN_BYTE": (SxiFrLBCN, SxiFrLBC8, SxiFrLBC16),
|
|
52
|
+
"HEADER_LEN_CTR_BYTE": (SxiFrLCBCN, SxiFrLCBC8, SxiFrLCBC16),
|
|
53
|
+
"HEADER_LEN_FILL_BYTE": (SxiFrLFBCN, SxiFrLFBC8, SxiFrLFBC16),
|
|
54
|
+
"HEADER_LEN_WORD": (SxiFrLWCN, SxiFrLWC8, SxiFrLWC16),
|
|
55
|
+
"HEADER_LEN_CTR_WORD": (SxiFrLCWCN, SxiFrLCWC8, SxiFrLCWC16),
|
|
56
|
+
"HEADER_LEN_FILL_WORD": (SxiFrLFWCN, SxiFrLFWC8, SxiFrLFWC16),
|
|
57
|
+
}
|
|
58
|
+
return FORMATS[header_format][COLUMN[checksum_format]]
|
|
59
|
+
|
|
60
|
+
|
|
22
61
|
class SxI(BaseTransport):
|
|
23
62
|
""""""
|
|
24
63
|
|
|
25
64
|
def __init__(self, config=None, policy=None, transport_layer_interface: Optional[serial.Serial] = None) -> None:
|
|
26
|
-
super().__init__(config, policy, transport_layer_interface)
|
|
27
65
|
self.load_config(config)
|
|
28
66
|
self.port_name = self.config.port
|
|
29
67
|
self.baudrate = self.config.bitrate
|
|
@@ -31,12 +69,28 @@ class SxI(BaseTransport):
|
|
|
31
69
|
self.parity = self.config.parity
|
|
32
70
|
self.stopbits = self.config.stopbits
|
|
33
71
|
self.mode = self.config.mode
|
|
34
|
-
|
|
72
|
+
header_len, header_ctr, header_fill = parse_header_format(self.config.header_format)
|
|
73
|
+
tail_cs_map = {
|
|
74
|
+
"NO_CHECKSUM": ChecksumType.NO_CHECKSUM,
|
|
75
|
+
"CHECKSUM_BYTE": ChecksumType.BYTE_CHECKSUM,
|
|
76
|
+
"CHECKSUM_WORD": ChecksumType.WORD_CHECKSUM,
|
|
77
|
+
}
|
|
78
|
+
# self._listener_running = threading.Event()
|
|
79
|
+
tail_cs = tail_cs_map[self.config.tail_format]
|
|
80
|
+
ReceiverKlass = get_receiver_class(self.config.header_format, self.config.tail_format)
|
|
81
|
+
self.receiver = ReceiverKlass(self.frame_dispatcher)
|
|
82
|
+
framing_config = XcpFramingConfig(
|
|
83
|
+
transport_layer_type=XcpTransportLayerType.SXI,
|
|
84
|
+
header_len=header_len,
|
|
85
|
+
header_ctr=header_ctr,
|
|
86
|
+
header_fill=header_fill,
|
|
87
|
+
tail_fill=False,
|
|
88
|
+
tail_cs=tail_cs,
|
|
89
|
+
)
|
|
90
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
35
91
|
self.tail_format = self.config.tail_format
|
|
36
|
-
self.framing = self.config.framing
|
|
37
92
|
self.esc_sync = self.config.esc_sync
|
|
38
93
|
self.esc_esc = self.config.esc_esc
|
|
39
|
-
self.make_header()
|
|
40
94
|
self.comm_port: serial.Serial
|
|
41
95
|
|
|
42
96
|
if self.has_user_supplied_interface and transport_layer_interface:
|
|
@@ -50,47 +104,30 @@ class SxI(BaseTransport):
|
|
|
50
104
|
bytesize=self.bytesize,
|
|
51
105
|
parity=self.parity,
|
|
52
106
|
stopbits=self.stopbits,
|
|
53
|
-
timeout=self.timeout,
|
|
107
|
+
timeout=0.1, # self.timeout,
|
|
54
108
|
write_timeout=self.timeout,
|
|
55
109
|
)
|
|
56
110
|
except serial.SerialException as e:
|
|
57
111
|
self.logger.critical(f"XCPonSxI - {e}")
|
|
58
112
|
raise
|
|
59
|
-
self.
|
|
113
|
+
self._condition = threading.Condition()
|
|
114
|
+
self._frames = deque()
|
|
115
|
+
# self._frame_listener = threading.Thread(
|
|
116
|
+
# target=self._frame_listen,
|
|
117
|
+
# args=(),
|
|
118
|
+
# kwargs={},
|
|
119
|
+
# )
|
|
60
120
|
|
|
61
121
|
def __del__(self) -> None:
|
|
62
122
|
self.close_connection()
|
|
63
123
|
|
|
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
124
|
def connect(self) -> None:
|
|
91
125
|
self.logger.info(
|
|
92
|
-
f"XCPonSxI - serial comm_port {self.comm_port.portstr!r} openend
|
|
126
|
+
f"XCPonSxI - serial comm_port {self.comm_port.portstr!r} openend "
|
|
127
|
+
f"[{self.baudrate}/{self.bytesize}-{self.parity}-{self.stopbits}] "
|
|
128
|
+
f"mode: {self.config.mode}"
|
|
93
129
|
)
|
|
130
|
+
self.logger.info(f"Framing: {self.config.header_format} {self.config.tail_format}")
|
|
94
131
|
self.start_listener()
|
|
95
132
|
|
|
96
133
|
def output(self, enable) -> None:
|
|
@@ -106,26 +143,63 @@ class SxI(BaseTransport):
|
|
|
106
143
|
|
|
107
144
|
def start_listener(self) -> None:
|
|
108
145
|
super().start_listener()
|
|
146
|
+
if hasattr(self, "_frame_listener") and self._frame_listener.is_alive():
|
|
147
|
+
self._frame_listener.join(timeout=2.0)
|
|
148
|
+
self._frame_listener = threading.Thread(target=self._frame_listen, daemon=True)
|
|
149
|
+
self._frame_listener.start()
|
|
150
|
+
# self._listener_running.wait(2.0)
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
"""Close the transport-layer connection and event-loop."""
|
|
154
|
+
self.finish_listener()
|
|
155
|
+
self.closeEvent.set()
|
|
156
|
+
try:
|
|
157
|
+
if self.listener.is_alive():
|
|
158
|
+
self.listener.join(timeout=2.0)
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
try:
|
|
162
|
+
if hasattr(self, "_frame_listener") and self._frame_listener.is_alive():
|
|
163
|
+
self._frame_listener.join(timeout=2.0)
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
self.close_connection()
|
|
109
167
|
|
|
110
168
|
def listen(self) -> None:
|
|
111
169
|
while True:
|
|
112
170
|
if self.closeEvent.is_set():
|
|
113
171
|
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
172
|
+
frame_to_process = None
|
|
173
|
+
with self._condition:
|
|
174
|
+
while not self._frames:
|
|
175
|
+
res = self._condition.wait(1.0)
|
|
176
|
+
if not res:
|
|
177
|
+
break
|
|
178
|
+
if self._frames:
|
|
179
|
+
frame_to_process = self._frames.popleft()
|
|
180
|
+
|
|
181
|
+
if frame_to_process:
|
|
182
|
+
frame, length, counter, timestamp = frame_to_process
|
|
183
|
+
self.process_response(frame, length, counter, timestamp)
|
|
184
|
+
|
|
185
|
+
def _frame_listen(self) -> None:
|
|
186
|
+
# self._listener_running.set()
|
|
187
|
+
while True:
|
|
188
|
+
if self.closeEvent.is_set():
|
|
189
|
+
return
|
|
190
|
+
data = self.comm_port.read(1)
|
|
191
|
+
if data:
|
|
192
|
+
self.receiver.feed_bytes(data)
|
|
193
|
+
data = self.comm_port.read(self.comm_port.in_waiting)
|
|
194
|
+
if data:
|
|
195
|
+
self.receiver.feed_bytes(data)
|
|
196
|
+
|
|
197
|
+
def frame_dispatcher(self, data: bytes, length: int, counter: int) -> None:
|
|
198
|
+
with self._condition:
|
|
199
|
+
self._frames.append((bytes(data), length, counter, self.timestamp.value))
|
|
200
|
+
self._condition.notify()
|
|
201
|
+
|
|
202
|
+
def send(self, frame: bytes) -> None:
|
|
129
203
|
self.pre_send_timestamp = self.timestamp.value
|
|
130
204
|
self.comm_port.write(frame)
|
|
131
205
|
self.post_send_timestamp = self.timestamp.value
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|