pyxcp 0.25.5__cp312-cp312-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 +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 +0 -0
- pyxcp/asamkeydll.c +116 -0
- pyxcp/asamkeydll.sh +2 -0
- pyxcp/checksum.py +732 -0
- pyxcp/cmdline.py +71 -0
- pyxcp/config/__init__.py +1257 -0
- pyxcp/config/legacy.py +120 -0
- pyxcp/constants.py +47 -0
- pyxcp/cpp_ext/__init__.py +0 -0
- pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
- pyxcp/cpp_ext/bin.hpp +105 -0
- pyxcp/cpp_ext/blockmem.hpp +58 -0
- 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/daqlist.hpp +374 -0
- pyxcp/cpp_ext/event.hpp +67 -0
- pyxcp/cpp_ext/extension_wrapper.cpp +208 -0
- pyxcp/cpp_ext/framing.hpp +360 -0
- pyxcp/cpp_ext/helper.hpp +280 -0
- pyxcp/cpp_ext/mcobject.hpp +248 -0
- pyxcp/cpp_ext/sxi_framing.hpp +332 -0
- pyxcp/cpp_ext/tsqueue.hpp +46 -0
- pyxcp/daq_stim/__init__.py +291 -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.cpp +13 -0
- pyxcp/daq_stim/stim.cpython-310-darwin.so +0 -0
- pyxcp/daq_stim/stim.cpython-311-darwin.so +0 -0
- pyxcp/daq_stim/stim.cpython-312-darwin.so +0 -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/run_daq.py +165 -0
- pyxcp/examples/xcp_policy.py +60 -0
- pyxcp/examples/xcp_read_benchmark.py +38 -0
- pyxcp/examples/xcp_skel.py +48 -0
- pyxcp/examples/xcp_unlock.py +36 -0
- pyxcp/examples/xcp_user_supplied_driver.py +43 -0
- pyxcp/examples/xcphello.py +65 -0
- pyxcp/examples/xcphello_recorder.py +107 -0
- pyxcp/master/__init__.py +10 -0
- pyxcp/master/errorhandler.py +677 -0
- pyxcp/master/master.py +2641 -0
- pyxcp/py.typed +0 -0
- 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 -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 +444 -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 +138 -0
- pyxcp/recorder/reco.py +278 -0
- pyxcp/recorder/recorder.rst +0 -0
- pyxcp/recorder/rekorder.cpp +59 -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.hpp +274 -0
- pyxcp/recorder/setup.py +41 -0
- pyxcp/recorder/test_reko.py +34 -0
- pyxcp/recorder/unfolder.hpp +1354 -0
- pyxcp/recorder/wrap.cpp +184 -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 +18 -0
- pyxcp/scripts/xcp_info.py +159 -0
- pyxcp/scripts/xcp_profile.py +26 -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_framing.py +262 -0
- pyxcp/tests/test_master.py +2116 -0
- pyxcp/tests/test_transport.py +177 -0
- pyxcp/tests/test_utils.py +30 -0
- pyxcp/timing.py +60 -0
- pyxcp/transport/__init__.py +13 -0
- pyxcp/transport/base.py +484 -0
- pyxcp/transport/base_transport.hpp +0 -0
- pyxcp/transport/can.py +660 -0
- pyxcp/transport/eth.py +254 -0
- pyxcp/transport/hdf5_policy.py +167 -0
- pyxcp/transport/sxi.py +209 -0
- 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.hpp +214 -0
- pyxcp/transport/transport_wrapper.cpp +249 -0
- pyxcp/transport/usb_transport.py +229 -0
- pyxcp/types.py +987 -0
- pyxcp/utils/__init__.py +127 -0
- pyxcp/utils/cli.py +78 -0
- pyxcp/vector/__init__.py +0 -0
- pyxcp/vector/map.py +82 -0
- pyxcp-0.25.5.dist-info/METADATA +341 -0
- pyxcp-0.25.5.dist-info/RECORD +153 -0
- pyxcp-0.25.5.dist-info/WHEEL +6 -0
- pyxcp-0.25.5.dist-info/entry_points.txt +9 -0
- pyxcp-0.25.5.dist-info/licenses/LICENSE +165 -0
pyxcp/master/master.py
ADDED
|
@@ -0,0 +1,2641 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Lowlevel API reflecting available XCP services.
|
|
3
|
+
|
|
4
|
+
.. note:: For technical reasons the API is split into two parts;
|
|
5
|
+
common methods (this file) and a Python version specific part.
|
|
6
|
+
|
|
7
|
+
.. [1] XCP Specification, Part 2 - Protocol Layer Specification
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import logging
|
|
12
|
+
import struct
|
|
13
|
+
import traceback
|
|
14
|
+
import warnings
|
|
15
|
+
from contextlib import suppress
|
|
16
|
+
from typing import Any, Callable, Collection, Dict, Optional, TypeVar
|
|
17
|
+
|
|
18
|
+
from pyxcp.daq_stim.stim import DaqEventInfo, Stim
|
|
19
|
+
|
|
20
|
+
from pyxcp import checksum, types
|
|
21
|
+
from pyxcp.constants import (
|
|
22
|
+
makeBytePacker,
|
|
23
|
+
makeByteUnpacker,
|
|
24
|
+
makeDLongPacker,
|
|
25
|
+
makeDLongUnpacker,
|
|
26
|
+
makeDWordPacker,
|
|
27
|
+
makeDWordUnpacker,
|
|
28
|
+
makeWordPacker,
|
|
29
|
+
makeWordUnpacker,
|
|
30
|
+
)
|
|
31
|
+
from pyxcp.master.errorhandler import (
|
|
32
|
+
SystemExit,
|
|
33
|
+
disable_error_handling,
|
|
34
|
+
is_suppress_xcp_error_log,
|
|
35
|
+
set_suppress_xcp_error_log,
|
|
36
|
+
wrapped,
|
|
37
|
+
)
|
|
38
|
+
from pyxcp.transport.base import create_transport
|
|
39
|
+
from pyxcp.utils import decode_bytes, delay, short_sleep
|
|
40
|
+
|
|
41
|
+
# Type variables for better type hinting
|
|
42
|
+
T = TypeVar("T")
|
|
43
|
+
R = TypeVar("R")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def broadcasted(func: Callable):
|
|
47
|
+
""""""
|
|
48
|
+
return func
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SlaveProperties(dict):
|
|
52
|
+
"""Container class for fixed parameters, like byte-order, maxCTO, ...
|
|
53
|
+
|
|
54
|
+
This class extends dict to provide attribute-style access to dictionary items.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, *args: Any, **kws: Any) -> None:
|
|
58
|
+
"""Initialize a new SlaveProperties instance.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
*args : Any
|
|
63
|
+
Positional arguments passed to dict.__init__
|
|
64
|
+
**kws : Any
|
|
65
|
+
Keyword arguments passed to dict.__init__
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(*args, **kws)
|
|
68
|
+
|
|
69
|
+
def __getattr__(self, name: str) -> Any:
|
|
70
|
+
"""Get an attribute by name.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
name : str
|
|
75
|
+
The name of the attribute to get
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Any
|
|
80
|
+
The value of the attribute
|
|
81
|
+
"""
|
|
82
|
+
return self[name]
|
|
83
|
+
|
|
84
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
85
|
+
"""Set an attribute by name.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
name : str
|
|
90
|
+
The name of the attribute to set
|
|
91
|
+
value : Any
|
|
92
|
+
The value to set
|
|
93
|
+
"""
|
|
94
|
+
self[name] = value
|
|
95
|
+
|
|
96
|
+
def __getstate__(self) -> dict:
|
|
97
|
+
"""Get the state of the object for pickling.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
dict
|
|
102
|
+
The state of the object
|
|
103
|
+
"""
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def __setstate__(self, state: dict) -> None:
|
|
107
|
+
"""Set the state of the object from unpickling.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
state : dict
|
|
112
|
+
The state to set
|
|
113
|
+
"""
|
|
114
|
+
self.update(state) # Use update instead of direct assignment
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Master:
|
|
118
|
+
"""Common part of lowlevel XCP API.
|
|
119
|
+
|
|
120
|
+
This class provides methods for interacting with an XCP slave device.
|
|
121
|
+
It handles the communication protocol and provides a high-level API
|
|
122
|
+
for sending commands and receiving responses.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
transport_name : str | None
|
|
127
|
+
XCP transport layer name ['can', 'eth', 'sxi']
|
|
128
|
+
config : Any
|
|
129
|
+
Configuration object containing transport and general settings
|
|
130
|
+
policy : Any, optional
|
|
131
|
+
Policy object for handling frames, by default None
|
|
132
|
+
transport_layer_interface : Any, optional
|
|
133
|
+
Custom transport layer interface, by default None
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, transport_name: str | None, config: Any, policy: Any = None,
|
|
137
|
+
transport_layer_interface: Any = None) -> None:
|
|
138
|
+
"""Initialize a new Master instance.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
transport_name : str | None
|
|
143
|
+
XCP transport layer name ['can', 'eth', 'sxi']
|
|
144
|
+
config : Any
|
|
145
|
+
Configuration object containing transport and general settings
|
|
146
|
+
policy : Any, optional
|
|
147
|
+
Policy object for handling frames, by default None
|
|
148
|
+
transport_layer_interface : Any, optional
|
|
149
|
+
Custom transport layer interface, by default None
|
|
150
|
+
|
|
151
|
+
Raises
|
|
152
|
+
------
|
|
153
|
+
ValueError
|
|
154
|
+
If transport_name is None
|
|
155
|
+
"""
|
|
156
|
+
if transport_name is None:
|
|
157
|
+
raise ValueError("No transport-layer selected") # Never reached -- to keep type-checkers happy.
|
|
158
|
+
|
|
159
|
+
# Initialize basic properties
|
|
160
|
+
self.ctr: int = 0
|
|
161
|
+
self.succeeded: bool = True
|
|
162
|
+
self.config: Any = config.general
|
|
163
|
+
self.logger: logging.Logger = logging.getLogger("PyXCP")
|
|
164
|
+
|
|
165
|
+
# Configure error handling
|
|
166
|
+
disable_error_handling(self.config.disable_error_handling)
|
|
167
|
+
|
|
168
|
+
# Set up transport layer
|
|
169
|
+
self.transport_name: str = transport_name.lower()
|
|
170
|
+
transport_config: Any = config.transport
|
|
171
|
+
self.transport: BaseTransport = create_transport(transport_name, transport_config, policy,
|
|
172
|
+
transport_layer_interface)
|
|
173
|
+
|
|
174
|
+
# Set up STIM (stimulation) support
|
|
175
|
+
self.stim: Stim = Stim(self.config.stim_support)
|
|
176
|
+
self.stim.clear()
|
|
177
|
+
self.stim.set_policy_feeder(self.transport.policy.feed)
|
|
178
|
+
self.stim.set_frame_sender(self.transport.block_request)
|
|
179
|
+
|
|
180
|
+
# In some cases the transport-layer needs to communicate with us.
|
|
181
|
+
self.transport.parent = self
|
|
182
|
+
self.service: Any = None
|
|
183
|
+
|
|
184
|
+
# Policies may issue XCP commands on their own.
|
|
185
|
+
self.transport.policy.xcp_master = self
|
|
186
|
+
|
|
187
|
+
# (D)Word (un-)packers are byte-order dependent
|
|
188
|
+
# -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
|
|
189
|
+
self.BYTE_pack: Callable[[int], bytes] | None = None
|
|
190
|
+
self.BYTE_unpack: Callable[[bytes], tuple[int]] | None = None
|
|
191
|
+
self.WORD_pack: Callable[[int], bytes] | None = None
|
|
192
|
+
self.WORD_unpack: Callable[[bytes], tuple[int]] | None = None
|
|
193
|
+
self.DWORD_pack: Callable[[int], bytes] | None = None
|
|
194
|
+
self.DWORD_unpack: Callable[[bytes], tuple[int]] | None = None
|
|
195
|
+
self.DLONG_pack: Callable[[int], bytes] | None = None
|
|
196
|
+
self.DLONG_unpack: Callable[[bytes], tuple[int]] | None = None
|
|
197
|
+
self.AG_pack: Callable[[int], bytes] | None = None
|
|
198
|
+
self.AG_unpack: Callable[[bytes], tuple[int]] | None = None
|
|
199
|
+
|
|
200
|
+
# Initialize state variables
|
|
201
|
+
self.mta: types.MtaType = types.MtaType(None, None)
|
|
202
|
+
self.currentDaqPtr: Any = None
|
|
203
|
+
self.currentProtectionStatus: dict[str, bool] | None = None
|
|
204
|
+
|
|
205
|
+
# Configuration for seed and key
|
|
206
|
+
self.seed_n_key_dll: str | None = self.config.seed_n_key_dll
|
|
207
|
+
self.seed_n_key_function: Callable | None = self.config.seed_n_key_function
|
|
208
|
+
self.seed_n_key_dll_same_bit_width: bool = self.config.seed_n_key_dll_same_bit_width
|
|
209
|
+
self.disconnect_response_optional: bool = self.config.disconnect_response_optional
|
|
210
|
+
|
|
211
|
+
# Initialize slave properties
|
|
212
|
+
self.slaveProperties: SlaveProperties = SlaveProperties()
|
|
213
|
+
self.slaveProperties.pgmProcessor = SlaveProperties()
|
|
214
|
+
self.slaveProperties.transport_layer = self.transport_name.upper()
|
|
215
|
+
|
|
216
|
+
def __enter__(self):
|
|
217
|
+
"""Context manager entry part.
|
|
218
|
+
|
|
219
|
+
This method is called when entering a context manager block.
|
|
220
|
+
It connects to the XCP slave and returns the Master instance.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
Master
|
|
225
|
+
The Master instance
|
|
226
|
+
"""
|
|
227
|
+
self.transport.connect()
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
def __exit__(
|
|
231
|
+
self, exc_type: type[BaseException] | None, exc_val: BaseException | None,
|
|
232
|
+
exc_tb
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Context manager exit part.
|
|
235
|
+
|
|
236
|
+
This method is called when exiting a context manager block.
|
|
237
|
+
It closes the connection to the XCP slave and logs any exceptions.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
exc_type : type[BaseException] | None
|
|
242
|
+
The type of the exception that was raised, or None if no exception was raised
|
|
243
|
+
exc_val : BaseException | None
|
|
244
|
+
The exception instance that was raised, or None if no exception was raised
|
|
245
|
+
exc_tb : traceback.TracebackType | None
|
|
246
|
+
The traceback of the exception that was raised, or None if no exception was raised
|
|
247
|
+
"""
|
|
248
|
+
# Close the connection to the XCP slave
|
|
249
|
+
self.close()
|
|
250
|
+
|
|
251
|
+
# Handle any exceptions that were raised
|
|
252
|
+
if exc_type is not None:
|
|
253
|
+
self.succeeded = False
|
|
254
|
+
self.logger.error("".join(traceback.format_exception(exc_type, exc_val, exc_tb)))
|
|
255
|
+
|
|
256
|
+
def _setService(self, service: Any) -> None:
|
|
257
|
+
"""Record the currently processed service.
|
|
258
|
+
|
|
259
|
+
This method is called by the transport layer to record the
|
|
260
|
+
currently processed service.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
service : Any
|
|
265
|
+
The service being processed, typically a `pyxcp.types.Command`
|
|
266
|
+
|
|
267
|
+
Note
|
|
268
|
+
----
|
|
269
|
+
Internal Function, only to be used by transport-layer.
|
|
270
|
+
"""
|
|
271
|
+
self.service = service
|
|
272
|
+
|
|
273
|
+
def close(self) -> None:
|
|
274
|
+
"""Close the transport layer connection.
|
|
275
|
+
|
|
276
|
+
This method finalizes the policy and closes the transport layer connection.
|
|
277
|
+
It should be called when the Master instance is no longer needed.
|
|
278
|
+
"""
|
|
279
|
+
self.transport.policy.finalize()
|
|
280
|
+
self.transport.close()
|
|
281
|
+
|
|
282
|
+
# Mandatory Commands.
|
|
283
|
+
@wrapped
|
|
284
|
+
def connect(self, mode: int = 0x00) -> types.ConnectResponse:
|
|
285
|
+
"""Build up connection to an XCP slave.
|
|
286
|
+
|
|
287
|
+
Before the actual XCP traffic starts a connection is required.
|
|
288
|
+
This method sends a CONNECT command to the slave and processes
|
|
289
|
+
the response to set up various properties of the slave.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
mode : int, optional
|
|
294
|
+
Connection mode, by default 0x00 (normal mode)
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
types.ConnectResponse
|
|
299
|
+
Response object containing fundamental client properties
|
|
300
|
+
|
|
301
|
+
Note
|
|
302
|
+
----
|
|
303
|
+
Every XCP slave supports at most one connection,
|
|
304
|
+
more attempts to connect are silently ignored.
|
|
305
|
+
"""
|
|
306
|
+
# Send CONNECT command to the slave
|
|
307
|
+
response = self.transport.request(types.Command.CONNECT, mode & 0xFF)
|
|
308
|
+
|
|
309
|
+
# First get byte-order from partial response
|
|
310
|
+
result_partial = types.ConnectResponsePartial.parse(response)
|
|
311
|
+
byte_order = result_partial.commModeBasic.byteOrder
|
|
312
|
+
|
|
313
|
+
# Parse the full response with the correct byte order
|
|
314
|
+
result = types.ConnectResponse.parse(response, byteOrder=byte_order)
|
|
315
|
+
|
|
316
|
+
# Set up byte order dependent properties
|
|
317
|
+
self._setup_slave_properties(result, byte_order)
|
|
318
|
+
|
|
319
|
+
# Set up byte order dependent packers and unpackers
|
|
320
|
+
self._setup_packers_and_unpackers(byte_order)
|
|
321
|
+
|
|
322
|
+
# Set up address granularity dependent properties
|
|
323
|
+
self._setup_address_granularity()
|
|
324
|
+
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
def _setup_slave_properties(self, result: types.ConnectResponse, byte_order: types.ByteOrder) -> None:
|
|
328
|
+
"""Set up slave properties based on the connect response.
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
result : types.ConnectResponse
|
|
333
|
+
The parsed connect response
|
|
334
|
+
byte_order : types.ByteOrder
|
|
335
|
+
The byte order reported by the slave
|
|
336
|
+
"""
|
|
337
|
+
# Set basic properties
|
|
338
|
+
self.slaveProperties.byteOrder = byte_order
|
|
339
|
+
self.slaveProperties.maxCto = result.maxCto
|
|
340
|
+
self.slaveProperties.maxDto = result.maxDto
|
|
341
|
+
|
|
342
|
+
# Set resource support flags
|
|
343
|
+
self.slaveProperties.supportsPgm = result.resource.pgm
|
|
344
|
+
self.slaveProperties.supportsStim = result.resource.stim
|
|
345
|
+
self.slaveProperties.supportsDaq = result.resource.daq
|
|
346
|
+
self.slaveProperties.supportsCalpag = result.resource.calpag
|
|
347
|
+
|
|
348
|
+
# Set communication mode properties
|
|
349
|
+
self.slaveProperties.slaveBlockMode = result.commModeBasic.slaveBlockMode
|
|
350
|
+
self.slaveProperties.addressGranularity = result.commModeBasic.addressGranularity
|
|
351
|
+
self.slaveProperties.optionalCommMode = result.commModeBasic.optional
|
|
352
|
+
|
|
353
|
+
# Set version information
|
|
354
|
+
self.slaveProperties.protocolLayerVersion = result.protocolLayerVersion
|
|
355
|
+
self.slaveProperties.transportLayerVersion = result.transportLayerVersion
|
|
356
|
+
|
|
357
|
+
# Calculate derived properties
|
|
358
|
+
self.slaveProperties.maxWriteDaqMultipleElements = (
|
|
359
|
+
0 if self.slaveProperties.maxCto < 10 else int((self.slaveProperties.maxCto - 2) // 8)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Initialize bytesPerElement (will be set in _setup_address_granularity)
|
|
363
|
+
self.slaveProperties.bytesPerElement = None
|
|
364
|
+
|
|
365
|
+
def _setup_packers_and_unpackers(self, byte_order: types.ByteOrder) -> None:
|
|
366
|
+
"""Set up byte order dependent packers and unpackers.
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
byte_order : types.ByteOrder
|
|
371
|
+
The byte order reported by the slave
|
|
372
|
+
"""
|
|
373
|
+
# Determine byte order prefix for struct format strings
|
|
374
|
+
byte_order_prefix = "<" if byte_order == types.ByteOrder.INTEL else ">"
|
|
375
|
+
|
|
376
|
+
# Create packers and unpackers for different data types
|
|
377
|
+
self.BYTE_pack = makeBytePacker(byte_order_prefix)
|
|
378
|
+
self.BYTE_unpack = makeByteUnpacker(byte_order_prefix)
|
|
379
|
+
self.WORD_pack = makeWordPacker(byte_order_prefix)
|
|
380
|
+
self.WORD_unpack = makeWordUnpacker(byte_order_prefix)
|
|
381
|
+
self.DWORD_pack = makeDWordPacker(byte_order_prefix)
|
|
382
|
+
self.DWORD_unpack = makeDWordUnpacker(byte_order_prefix)
|
|
383
|
+
self.DLONG_pack = makeDLongPacker(byte_order_prefix)
|
|
384
|
+
self.DLONG_unpack = makeDLongUnpacker(byte_order_prefix)
|
|
385
|
+
|
|
386
|
+
def _setup_address_granularity(self) -> None:
|
|
387
|
+
"""Set up address granularity dependent properties and packers/unpackers."""
|
|
388
|
+
# Set up address granularity dependent packers and unpackers
|
|
389
|
+
if self.slaveProperties.addressGranularity == types.AddressGranularity.BYTE:
|
|
390
|
+
self.AG_pack = struct.Struct("<B").pack
|
|
391
|
+
self.AG_unpack = struct.Struct("<B").unpack
|
|
392
|
+
self.slaveProperties.bytesPerElement = 1
|
|
393
|
+
elif self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
|
|
394
|
+
self.AG_pack = self.WORD_pack
|
|
395
|
+
self.AG_unpack = self.WORD_unpack
|
|
396
|
+
self.slaveProperties.bytesPerElement = 2
|
|
397
|
+
elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
|
|
398
|
+
self.AG_pack = self.DWORD_pack
|
|
399
|
+
self.AG_unpack = self.DWORD_unpack
|
|
400
|
+
self.slaveProperties.bytesPerElement = 4
|
|
401
|
+
# self.connected = True
|
|
402
|
+
status = self.getStatus()
|
|
403
|
+
if status.sessionStatus.daqRunning:
|
|
404
|
+
# TODO: resume
|
|
405
|
+
self.startStopSynch(0x00)
|
|
406
|
+
|
|
407
|
+
@wrapped
|
|
408
|
+
def disconnect(self) -> bytes:
|
|
409
|
+
"""Release the connection to the XCP slave.
|
|
410
|
+
|
|
411
|
+
This method sends a DISCONNECT command to the slave, which releases
|
|
412
|
+
the connection. Thereafter, no further communication with the slave
|
|
413
|
+
is possible (besides `connect`).
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
bytes
|
|
418
|
+
The raw response from the slave, typically empty
|
|
419
|
+
|
|
420
|
+
Note
|
|
421
|
+
-----
|
|
422
|
+
- If DISCONNECT is currently not possible, ERR_CMD_BUSY will be returned.
|
|
423
|
+
- While XCP spec. requires a response, this behavior can be made optional by adding
|
|
424
|
+
- `DISCONNECT_RESPONSE_OPTIONAL = true` (TOML)
|
|
425
|
+
- `"DISCONNECT_RESPONSE_OPTIONAL": true` (JSON)
|
|
426
|
+
to your configuration file.
|
|
427
|
+
"""
|
|
428
|
+
# Send DISCONNECT command to the slave
|
|
429
|
+
if self.disconnect_response_optional:
|
|
430
|
+
response = self.transport.request_optional_response(types.Command.DISCONNECT)
|
|
431
|
+
else:
|
|
432
|
+
response = self.transport.request(types.Command.DISCONNECT)
|
|
433
|
+
|
|
434
|
+
return response
|
|
435
|
+
|
|
436
|
+
@wrapped
|
|
437
|
+
def getStatus(self) -> types.GetStatusResponse:
|
|
438
|
+
"""Get current status information of the slave device.
|
|
439
|
+
|
|
440
|
+
This method sends a GET_STATUS command to the slave and processes
|
|
441
|
+
the response to get information about the current status of the slave.
|
|
442
|
+
This includes the status of the resource protection, pending store
|
|
443
|
+
requests and the general status of data acquisition and stimulation.
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
types.GetStatusResponse
|
|
448
|
+
Response object containing status information
|
|
449
|
+
"""
|
|
450
|
+
# Send GET_STATUS command to the slave
|
|
451
|
+
response = self.transport.request(types.Command.GET_STATUS)
|
|
452
|
+
|
|
453
|
+
# Parse the response with the correct byte order
|
|
454
|
+
result = types.GetStatusResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
455
|
+
|
|
456
|
+
# Update the current protection status
|
|
457
|
+
self._setProtectionStatus(result.resourceProtectionStatus)
|
|
458
|
+
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
@wrapped
|
|
462
|
+
def synch(self) -> bytes:
|
|
463
|
+
"""Synchronize command execution after timeout conditions.
|
|
464
|
+
|
|
465
|
+
This method sends a SYNCH command to the slave, which synchronizes
|
|
466
|
+
command execution after timeout conditions. This is useful when
|
|
467
|
+
the slave has timed out and needs to be resynchronized.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
bytes
|
|
472
|
+
The raw response from the slave
|
|
473
|
+
"""
|
|
474
|
+
# Send SYNCH command to the slave
|
|
475
|
+
response = self.transport.request(types.Command.SYNCH)
|
|
476
|
+
return response
|
|
477
|
+
|
|
478
|
+
@wrapped
|
|
479
|
+
def getCommModeInfo(self) -> types.GetCommModeInfoResponse:
|
|
480
|
+
"""Get optional information on different Communication Modes supported
|
|
481
|
+
by the slave.
|
|
482
|
+
|
|
483
|
+
This method sends a GET_COMM_MODE_INFO command to the slave and processes
|
|
484
|
+
the response to get information about the communication modes supported
|
|
485
|
+
by the slave.
|
|
486
|
+
|
|
487
|
+
Returns
|
|
488
|
+
-------
|
|
489
|
+
types.GetCommModeInfoResponse
|
|
490
|
+
Response object containing communication mode information
|
|
491
|
+
"""
|
|
492
|
+
# Send GET_COMM_MODE_INFO command to the slave
|
|
493
|
+
response = self.transport.request(types.Command.GET_COMM_MODE_INFO)
|
|
494
|
+
|
|
495
|
+
# Parse the response with the correct byte order
|
|
496
|
+
result = types.GetCommModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
497
|
+
|
|
498
|
+
# Update slave properties with communication mode information
|
|
499
|
+
self._update_comm_mode_properties(result)
|
|
500
|
+
|
|
501
|
+
return result
|
|
502
|
+
|
|
503
|
+
def _update_comm_mode_properties(self, result: types.GetCommModeInfoResponse) -> None:
|
|
504
|
+
"""Update slave properties with communication mode information.
|
|
505
|
+
|
|
506
|
+
Parameters
|
|
507
|
+
----------
|
|
508
|
+
result : types.GetCommModeInfoResponse
|
|
509
|
+
The parsed GET_COMM_MODE_INFO response
|
|
510
|
+
"""
|
|
511
|
+
# Set optional communication mode properties
|
|
512
|
+
self.slaveProperties.interleavedMode = result.commModeOptional.interleavedMode
|
|
513
|
+
self.slaveProperties.masterBlockMode = result.commModeOptional.masterBlockMode
|
|
514
|
+
|
|
515
|
+
# Set basic communication properties
|
|
516
|
+
self.slaveProperties.maxBs = result.maxBs
|
|
517
|
+
self.slaveProperties.minSt = result.minSt
|
|
518
|
+
self.slaveProperties.queueSize = result.queueSize
|
|
519
|
+
self.slaveProperties.xcpDriverVersionNumber = result.xcpDriverVersionNumber
|
|
520
|
+
|
|
521
|
+
@wrapped
|
|
522
|
+
def getId(self, mode: int) -> types.GetIDResponse:
|
|
523
|
+
"""Get identification information from the slave device.
|
|
524
|
+
|
|
525
|
+
This command is used for automatic session configuration and for
|
|
526
|
+
slave device identification. It sends a GET_ID command to the slave
|
|
527
|
+
and processes the response to get identification information.
|
|
528
|
+
|
|
529
|
+
Parameters
|
|
530
|
+
----------
|
|
531
|
+
mode : int
|
|
532
|
+
The following identification types may be requested:
|
|
533
|
+
- 0 ASCII text
|
|
534
|
+
- 1 ASAM-MC2 filename without path and extension
|
|
535
|
+
- 2 ASAM-MC2 filename with path and extension
|
|
536
|
+
- 3 URL where the ASAM-MC2 file can be found
|
|
537
|
+
- 4 ASAM-MC2 file to upload
|
|
538
|
+
- 128..255 User defined
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
types.GetIDResponse
|
|
543
|
+
Response object containing identification information
|
|
544
|
+
"""
|
|
545
|
+
# Send GET_ID command to the slave
|
|
546
|
+
response = self.transport.request(types.Command.GET_ID, mode)
|
|
547
|
+
result = types.GetIDResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
548
|
+
result.length = self.DWORD_unpack(response[3:7])[0]
|
|
549
|
+
|
|
550
|
+
return result
|
|
551
|
+
|
|
552
|
+
@wrapped
|
|
553
|
+
def setRequest(self, mode: int, session_configuration_id: int) -> bytes:
|
|
554
|
+
"""Request to save data to non-volatile memory.
|
|
555
|
+
|
|
556
|
+
This method sends a SET_REQUEST command to the slave, which requests
|
|
557
|
+
the slave to save data to non-volatile memory. The data to be saved
|
|
558
|
+
is specified by the mode parameter.
|
|
559
|
+
|
|
560
|
+
Parameters
|
|
561
|
+
----------
|
|
562
|
+
mode : int (bitfield)
|
|
563
|
+
- 1 Request to store calibration data
|
|
564
|
+
- 2 Request to store DAQ list, no resume
|
|
565
|
+
- 4 Request to store DAQ list, resume enabled
|
|
566
|
+
- 8 Request to clear DAQ configuration
|
|
567
|
+
session_configuration_id : int
|
|
568
|
+
Identifier for the session configuration
|
|
569
|
+
|
|
570
|
+
Returns
|
|
571
|
+
-------
|
|
572
|
+
bytes
|
|
573
|
+
The raw response from the slave
|
|
574
|
+
"""
|
|
575
|
+
# Send SET_REQUEST command to the slave
|
|
576
|
+
# Split the session_configuration_id into high and low bytes
|
|
577
|
+
return self.transport.request(
|
|
578
|
+
types.Command.SET_REQUEST,
|
|
579
|
+
mode,
|
|
580
|
+
session_configuration_id >> 8, # High byte
|
|
581
|
+
session_configuration_id & 0xFF, # Low byte
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
@wrapped
|
|
585
|
+
def getSeed(self, first: int, resource: int) -> types.GetSeedResponse:
|
|
586
|
+
"""Get seed from slave for unlocking a protected resource.
|
|
587
|
+
|
|
588
|
+
This method sends a GET_SEED command to the slave, which returns a seed
|
|
589
|
+
that can be used to generate a key for unlocking a protected resource.
|
|
590
|
+
The seed is used as input to a key generation algorithm, and the resulting
|
|
591
|
+
key is sent back to the slave using the unlock method.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
first : int
|
|
596
|
+
- 0 - first part of seed
|
|
597
|
+
- 1 - remaining part
|
|
598
|
+
resource : int
|
|
599
|
+
- Mode == 0 - Resource to unlock
|
|
600
|
+
- Mode == 1 - Don't care
|
|
601
|
+
|
|
602
|
+
Returns
|
|
603
|
+
-------
|
|
604
|
+
types.GetSeedResponse
|
|
605
|
+
Response object containing the seed
|
|
606
|
+
|
|
607
|
+
Note
|
|
608
|
+
----
|
|
609
|
+
For CAN transport, the seed may be split across multiple frames if it's
|
|
610
|
+
longer than the maximum DLC. In this case, the first byte of the response
|
|
611
|
+
indicates the remaining seed size, and the master must call getSeed
|
|
612
|
+
multiple times until the complete seed is received.
|
|
613
|
+
"""
|
|
614
|
+
# Send GET_SEED command to the slave
|
|
615
|
+
response = self.transport.request(types.Command.GET_SEED, first, resource)
|
|
616
|
+
|
|
617
|
+
# Handle CAN-specific seed format
|
|
618
|
+
if self.transport_name == "can":
|
|
619
|
+
# For CAN it might happen that the seed is longer than the max DLC
|
|
620
|
+
# In this case the first byte will be the current remaining seed size
|
|
621
|
+
# followed by the seed bytes that can fit in the current frame
|
|
622
|
+
size, seed = response[0], response[1:]
|
|
623
|
+
|
|
624
|
+
# Truncate seed if necessary
|
|
625
|
+
if size < len(seed):
|
|
626
|
+
seed = seed[:size]
|
|
627
|
+
|
|
628
|
+
# Create and populate response object
|
|
629
|
+
reply = types.GetSeedResponse.parse(
|
|
630
|
+
types.GetSeedResponse.build({"length": size, "seed": bytes(size)}),
|
|
631
|
+
byteOrder=self.slaveProperties.byteOrder,
|
|
632
|
+
)
|
|
633
|
+
reply.seed = seed
|
|
634
|
+
return reply
|
|
635
|
+
else:
|
|
636
|
+
# For other transports, parse the response directly
|
|
637
|
+
return types.GetSeedResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
638
|
+
|
|
639
|
+
@wrapped
|
|
640
|
+
def unlock(self, length: int, key: bytes) -> types.ResourceType:
|
|
641
|
+
"""Send key to slave for unlocking a protected resource.
|
|
642
|
+
|
|
643
|
+
This method sends an UNLOCK command to the slave, which attempts to
|
|
644
|
+
unlock a protected resource using the provided key. The key is generated
|
|
645
|
+
from the seed obtained using the getSeed method.
|
|
646
|
+
|
|
647
|
+
Parameters
|
|
648
|
+
----------
|
|
649
|
+
length : int
|
|
650
|
+
Indicates the (remaining) number of key bytes
|
|
651
|
+
key : bytes
|
|
652
|
+
The key bytes to send to the slave
|
|
653
|
+
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
656
|
+
types.ResourceType
|
|
657
|
+
Response object containing the resource protection status
|
|
658
|
+
|
|
659
|
+
Note
|
|
660
|
+
----
|
|
661
|
+
The master has to use :meth:`unlock` in a defined sequence together
|
|
662
|
+
with :meth:`getSeed`. The master can only send an :meth:`unlock` sequence
|
|
663
|
+
if previously there was a :meth:`getSeed` sequence. The master has
|
|
664
|
+
to send the first `unlocking` after a :meth:`getSeed` sequence with
|
|
665
|
+
a Length containing the total length of the key.
|
|
666
|
+
"""
|
|
667
|
+
# Send UNLOCK command to the slave
|
|
668
|
+
response = self.transport.request(types.Command.UNLOCK, length, *key)
|
|
669
|
+
|
|
670
|
+
# Parse the response with the correct byte order
|
|
671
|
+
result = types.ResourceType.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
672
|
+
|
|
673
|
+
# Update the current protection status
|
|
674
|
+
self._setProtectionStatus(result)
|
|
675
|
+
|
|
676
|
+
return result
|
|
677
|
+
|
|
678
|
+
@wrapped
|
|
679
|
+
def setMta(self, address: int, address_ext: int = 0x00) -> bytes:
|
|
680
|
+
"""Set Memory Transfer Address in slave.
|
|
681
|
+
|
|
682
|
+
This method sends a SET_MTA command to the slave, which sets the
|
|
683
|
+
Memory Transfer Address (MTA) to the specified address. The MTA is
|
|
684
|
+
used by various commands that transfer data between the master and
|
|
685
|
+
the slave.
|
|
686
|
+
|
|
687
|
+
Parameters
|
|
688
|
+
----------
|
|
689
|
+
address : int
|
|
690
|
+
The memory address to set
|
|
691
|
+
address_ext : int, optional
|
|
692
|
+
The address extension, by default 0x00
|
|
693
|
+
|
|
694
|
+
Returns
|
|
695
|
+
-------
|
|
696
|
+
bytes
|
|
697
|
+
The raw response from the slave
|
|
698
|
+
|
|
699
|
+
Note
|
|
700
|
+
----
|
|
701
|
+
The MTA is used by :meth:`buildChecksum`, :meth:`upload`, :meth:`download`, :meth:`downloadNext`,
|
|
702
|
+
:meth:`downloadMax`, :meth:`modifyBits`, :meth:`programClear`, :meth:`program`, :meth:`programNext`
|
|
703
|
+
and :meth:`programMax`.
|
|
704
|
+
"""
|
|
705
|
+
# Keep track of MTA (needed for error-handling)
|
|
706
|
+
self.mta = types.MtaType(address, address_ext)
|
|
707
|
+
|
|
708
|
+
# Pack the address into bytes
|
|
709
|
+
addr = self.DWORD_pack(address)
|
|
710
|
+
|
|
711
|
+
# Send SET_MTA command to the slave
|
|
712
|
+
return self.transport.request(types.Command.SET_MTA, 0, 0, address_ext, *addr)
|
|
713
|
+
|
|
714
|
+
@wrapped
|
|
715
|
+
def upload(self, length: int) -> bytes:
|
|
716
|
+
"""Transfer data from slave to master.
|
|
717
|
+
|
|
718
|
+
This method sends an UPLOAD command to the slave, which transfers
|
|
719
|
+
data from the slave to the master. The data is read from the memory
|
|
720
|
+
address specified by the MTA, which must be set before calling this
|
|
721
|
+
method.
|
|
722
|
+
|
|
723
|
+
Parameters
|
|
724
|
+
----------
|
|
725
|
+
length : int
|
|
726
|
+
Number of elements (address granularity) to upload
|
|
727
|
+
|
|
728
|
+
Returns
|
|
729
|
+
-------
|
|
730
|
+
bytes
|
|
731
|
+
The uploaded data
|
|
732
|
+
|
|
733
|
+
Note
|
|
734
|
+
----
|
|
735
|
+
Address is set via :meth:`setMta` (Some services like :meth:`getID` also set the MTA).
|
|
736
|
+
"""
|
|
737
|
+
# Calculate the number of bytes to upload
|
|
738
|
+
byte_count = length * self.slaveProperties.bytesPerElement
|
|
739
|
+
|
|
740
|
+
# Send UPLOAD command to the slave
|
|
741
|
+
response = self.transport.request(types.Command.UPLOAD, length)
|
|
742
|
+
|
|
743
|
+
# Handle block mode for large uploads
|
|
744
|
+
if byte_count > (self.slaveProperties.maxCto - 1):
|
|
745
|
+
# Receive the remaining bytes in block mode
|
|
746
|
+
block_response = self.transport.block_receive(length_required=(byte_count - len(response)))
|
|
747
|
+
response += block_response
|
|
748
|
+
# Handle CAN-specific upload format
|
|
749
|
+
elif self.transport_name == "can":
|
|
750
|
+
# Larger sizes will send in multiple CAN messages
|
|
751
|
+
# Each valid message will start with 0xFF followed by the upload bytes
|
|
752
|
+
# The last message might be padded to the required DLC
|
|
753
|
+
remaining_bytes = byte_count - len(response)
|
|
754
|
+
while remaining_bytes:
|
|
755
|
+
if len(self.transport.resQueue):
|
|
756
|
+
data = self.transport.resQueue.popleft()
|
|
757
|
+
response += data[1: remaining_bytes + 1]
|
|
758
|
+
remaining_bytes = byte_count - len(response)
|
|
759
|
+
else:
|
|
760
|
+
short_sleep()
|
|
761
|
+
return response
|
|
762
|
+
|
|
763
|
+
@wrapped
|
|
764
|
+
def shortUpload(self, length: int, address: int, address_ext: int = 0x00) -> bytes:
|
|
765
|
+
"""Transfer data from slave to master with address information.
|
|
766
|
+
|
|
767
|
+
This method sends a SHORT_UPLOAD command to the slave, which transfers
|
|
768
|
+
data from the slave to the master. Unlike the :meth:`upload` method,
|
|
769
|
+
this method includes the address information in the command, so it
|
|
770
|
+
doesn't require setting the MTA first.
|
|
771
|
+
|
|
772
|
+
Parameters
|
|
773
|
+
----------
|
|
774
|
+
length : int
|
|
775
|
+
Number of elements (address granularity) to upload
|
|
776
|
+
address : int
|
|
777
|
+
The memory address to read from
|
|
778
|
+
address_ext : int, optional
|
|
779
|
+
The address extension, by default 0x00
|
|
780
|
+
|
|
781
|
+
Returns
|
|
782
|
+
-------
|
|
783
|
+
bytes
|
|
784
|
+
The uploaded data
|
|
785
|
+
"""
|
|
786
|
+
# Pack the address into bytes
|
|
787
|
+
addr = self.DWORD_pack(address)
|
|
788
|
+
|
|
789
|
+
# Calculate the number of bytes to upload
|
|
790
|
+
byte_count = length * self.slaveProperties.bytesPerElement
|
|
791
|
+
max_byte_count = self.slaveProperties.maxCto - 1
|
|
792
|
+
|
|
793
|
+
# Check if the requested byte count exceeds the maximum
|
|
794
|
+
if byte_count > max_byte_count:
|
|
795
|
+
self.logger.warn(f"SHORT_UPLOAD: {byte_count} bytes exceeds the maximum value of {max_byte_count}.")
|
|
796
|
+
|
|
797
|
+
# Send SHORT_UPLOAD command to the slave
|
|
798
|
+
response = self.transport.request(types.Command.SHORT_UPLOAD, length, 0, address_ext, *addr)
|
|
799
|
+
|
|
800
|
+
# Return only the requested number of bytes
|
|
801
|
+
return response[:byte_count]
|
|
802
|
+
|
|
803
|
+
@wrapped
|
|
804
|
+
def buildChecksum(self, blocksize: int) -> types.BuildChecksumResponse:
|
|
805
|
+
"""Build checksum over memory range.
|
|
806
|
+
|
|
807
|
+
This method sends a BUILD_CHECKSUM command to the slave, which calculates
|
|
808
|
+
a checksum over a memory range. The memory range starts at the address
|
|
809
|
+
specified by the MTA and has a size of `blocksize` elements.
|
|
810
|
+
|
|
811
|
+
Parameters
|
|
812
|
+
----------
|
|
813
|
+
blocksize : int
|
|
814
|
+
The number of elements (address granularity) to include in the checksum
|
|
815
|
+
|
|
816
|
+
Returns
|
|
817
|
+
-------
|
|
818
|
+
types.BuildChecksumResponse
|
|
819
|
+
Response object containing the checksum information
|
|
820
|
+
|
|
821
|
+
Note
|
|
822
|
+
----
|
|
823
|
+
Address is set via :meth:`setMta`
|
|
824
|
+
|
|
825
|
+
See Also
|
|
826
|
+
--------
|
|
827
|
+
:mod:`~pyxcp.checksum`
|
|
828
|
+
"""
|
|
829
|
+
# Pack the blocksize into bytes
|
|
830
|
+
bs = self.DWORD_pack(blocksize)
|
|
831
|
+
|
|
832
|
+
# Send BUILD_CHECKSUM command to the slave
|
|
833
|
+
response = self.transport.request(types.Command.BUILD_CHECKSUM, 0, 0, 0, *bs)
|
|
834
|
+
|
|
835
|
+
# Parse the response with the correct byte order
|
|
836
|
+
return types.BuildChecksumResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
837
|
+
|
|
838
|
+
@wrapped
|
|
839
|
+
def transportLayerCmd(self, sub_command: int, *data: bytes) -> bytes:
|
|
840
|
+
"""Execute transfer-layer specific command.
|
|
841
|
+
|
|
842
|
+
This method sends a TRANSPORT_LAYER_CMD command to the slave, which
|
|
843
|
+
executes a transport-layer specific command. The exact behavior of
|
|
844
|
+
this command depends on the transport layer being used.
|
|
845
|
+
|
|
846
|
+
Parameters
|
|
847
|
+
----------
|
|
848
|
+
sub_command : int
|
|
849
|
+
The sub-command to execute
|
|
850
|
+
*data : bytes
|
|
851
|
+
Variable number of data bytes to send with the command
|
|
852
|
+
|
|
853
|
+
Returns
|
|
854
|
+
-------
|
|
855
|
+
bytes
|
|
856
|
+
The raw response from the slave, or None if no response is expected
|
|
857
|
+
|
|
858
|
+
Note
|
|
859
|
+
----
|
|
860
|
+
For details refer to XCP specification.
|
|
861
|
+
"""
|
|
862
|
+
# Send TRANSPORT_LAYER_CMD command to the slave
|
|
863
|
+
return self.transport.request_optional_response(types.Command.TRANSPORT_LAYER_CMD, sub_command, *data)
|
|
864
|
+
|
|
865
|
+
@wrapped
|
|
866
|
+
def userCmd(self, sub_command: int, data: bytes) -> bytes:
|
|
867
|
+
"""Execute proprietary command implemented in your XCP client.
|
|
868
|
+
|
|
869
|
+
This method sends a USER_CMD command to the slave, which executes
|
|
870
|
+
a proprietary command implemented in the XCP client. The exact behavior
|
|
871
|
+
of this command depends on the XCP client vendor.
|
|
872
|
+
|
|
873
|
+
Parameters
|
|
874
|
+
----------
|
|
875
|
+
sub_command : int
|
|
876
|
+
The sub-command to execute
|
|
877
|
+
data : bytes
|
|
878
|
+
The data bytes to send with the command
|
|
879
|
+
|
|
880
|
+
Returns
|
|
881
|
+
-------
|
|
882
|
+
bytes
|
|
883
|
+
The raw response from the slave
|
|
884
|
+
|
|
885
|
+
Note
|
|
886
|
+
----
|
|
887
|
+
For details refer to your XCP client vendor.
|
|
888
|
+
"""
|
|
889
|
+
# Send USER_CMD command to the slave
|
|
890
|
+
return self.transport.request(types.Command.USER_CMD, sub_command, *data)
|
|
891
|
+
|
|
892
|
+
@wrapped
|
|
893
|
+
def getVersion(self) -> types.GetVersionResponse:
|
|
894
|
+
"""Get version information from the slave.
|
|
895
|
+
|
|
896
|
+
This method sends a GET_VERSION command to the slave, which returns
|
|
897
|
+
detailed information about the implemented protocol layer version
|
|
898
|
+
of the XCP slave and the transport layer currently in use.
|
|
899
|
+
|
|
900
|
+
Returns
|
|
901
|
+
-------
|
|
902
|
+
types.GetVersionResponse
|
|
903
|
+
Response object containing version information
|
|
904
|
+
"""
|
|
905
|
+
# Send GET_VERSION command to the slave
|
|
906
|
+
response = self.transport.request(types.Command.GET_VERSION)
|
|
907
|
+
|
|
908
|
+
# Parse the response with the correct byte order
|
|
909
|
+
result = types.GetVersionResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
910
|
+
|
|
911
|
+
# Update slave properties with version information
|
|
912
|
+
self._update_version_properties(result)
|
|
913
|
+
|
|
914
|
+
return result
|
|
915
|
+
|
|
916
|
+
def _update_version_properties(self, result: types.GetVersionResponse) -> None:
|
|
917
|
+
"""Update slave properties with version information.
|
|
918
|
+
|
|
919
|
+
Parameters
|
|
920
|
+
----------
|
|
921
|
+
result : types.GetVersionResponse
|
|
922
|
+
The parsed GET_VERSION response
|
|
923
|
+
"""
|
|
924
|
+
# Set version information
|
|
925
|
+
self.slaveProperties.protocolMajor = result.protocolMajor
|
|
926
|
+
self.slaveProperties.protocolMinor = result.protocolMinor
|
|
927
|
+
self.slaveProperties.transportMajor = result.transportMajor
|
|
928
|
+
self.slaveProperties.transportMinor = result.transportMinor
|
|
929
|
+
|
|
930
|
+
def fetch(self, length: int, limit_payload: int = None) -> bytes: # TODO: pull
|
|
931
|
+
"""Convenience function for data-transfer from slave to master.
|
|
932
|
+
|
|
933
|
+
This method transfers data from the slave to the master in chunks,
|
|
934
|
+
handling the details of breaking up large transfers into smaller
|
|
935
|
+
pieces. It's not part of the XCP Specification but provides a
|
|
936
|
+
convenient way to fetch data.
|
|
937
|
+
|
|
938
|
+
Parameters
|
|
939
|
+
----------
|
|
940
|
+
length : int
|
|
941
|
+
The number of bytes to fetch
|
|
942
|
+
limit_payload : int, optional
|
|
943
|
+
Transfer less bytes than supported by transport-layer, by default None
|
|
944
|
+
|
|
945
|
+
Returns
|
|
946
|
+
-------
|
|
947
|
+
bytes
|
|
948
|
+
The fetched data
|
|
949
|
+
|
|
950
|
+
Raises
|
|
951
|
+
------
|
|
952
|
+
ValueError
|
|
953
|
+
If limit_payload is less than 8 bytes
|
|
954
|
+
|
|
955
|
+
Note
|
|
956
|
+
----
|
|
957
|
+
Address is not included because of services implicitly setting
|
|
958
|
+
address information like :meth:`getID`.
|
|
959
|
+
"""
|
|
960
|
+
# Validate limit_payload
|
|
961
|
+
if limit_payload is not None and limit_payload < 8:
|
|
962
|
+
raise ValueError(f"Payload must be at least 8 bytes - given: {limit_payload}")
|
|
963
|
+
|
|
964
|
+
# Determine maximum payload size
|
|
965
|
+
slave_block_mode = self.slaveProperties.slaveBlockMode
|
|
966
|
+
max_payload = 255 if slave_block_mode else self.slaveProperties.maxCto - 1
|
|
967
|
+
|
|
968
|
+
# Apply limit_payload if specified
|
|
969
|
+
payload = min(limit_payload, max_payload) if limit_payload else max_payload
|
|
970
|
+
|
|
971
|
+
# Calculate number of chunks and remaining bytes
|
|
972
|
+
chunk_size = payload
|
|
973
|
+
chunks = range(length // chunk_size)
|
|
974
|
+
remaining = length % chunk_size
|
|
975
|
+
|
|
976
|
+
# Fetch data in chunks
|
|
977
|
+
result = []
|
|
978
|
+
for _ in chunks:
|
|
979
|
+
data = self.upload(chunk_size)
|
|
980
|
+
result.extend(data[:chunk_size])
|
|
981
|
+
|
|
982
|
+
# Fetch remaining bytes
|
|
983
|
+
if remaining:
|
|
984
|
+
data = self.upload(remaining)
|
|
985
|
+
result.extend(data[:remaining])
|
|
986
|
+
|
|
987
|
+
return bytes(result)
|
|
988
|
+
|
|
989
|
+
pull = fetch # fetch() may be completely replaced by pull() someday.
|
|
990
|
+
|
|
991
|
+
def push(self, address: int, address_ext: int, data: bytes, callback: Callable[[int], None] | None = None) -> None:
|
|
992
|
+
"""Convenience function for data-transfer from master to slave.
|
|
993
|
+
|
|
994
|
+
This method transfers data from the master to the slave in chunks,
|
|
995
|
+
handling the details of breaking up large transfers into smaller
|
|
996
|
+
pieces. It's not part of the XCP Specification but provides a
|
|
997
|
+
convenient way to push data.
|
|
998
|
+
|
|
999
|
+
Parameters
|
|
1000
|
+
----------
|
|
1001
|
+
address : int
|
|
1002
|
+
The memory address to write to
|
|
1003
|
+
address_ext : int
|
|
1004
|
+
The address extension
|
|
1005
|
+
data : bytes
|
|
1006
|
+
The data bytes to write
|
|
1007
|
+
callback : Callable[[int], None], optional
|
|
1008
|
+
A callback function that is called with the percentage of completion,
|
|
1009
|
+
by default None
|
|
1010
|
+
|
|
1011
|
+
Note
|
|
1012
|
+
----
|
|
1013
|
+
This method uses the download and downloadNext methods internally.
|
|
1014
|
+
"""
|
|
1015
|
+
# Use the generalized downloader to transfer the data
|
|
1016
|
+
self._generalized_downloader(
|
|
1017
|
+
address=address,
|
|
1018
|
+
address_ext=address_ext,
|
|
1019
|
+
data=data,
|
|
1020
|
+
maxCto=self.slaveProperties.maxCto,
|
|
1021
|
+
maxBs=self.slaveProperties.maxBs,
|
|
1022
|
+
minSt=self.slaveProperties.minSt,
|
|
1023
|
+
master_block_mode=self.slaveProperties.masterBlockMode,
|
|
1024
|
+
dl_func=self.download,
|
|
1025
|
+
dl_next_func=self.downloadNext,
|
|
1026
|
+
callback=callback,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
def flash_program(self, address: int, data: bytes, callback: Callable[[int], None] | None = None) -> None:
|
|
1030
|
+
"""Convenience function for flash programming.
|
|
1031
|
+
|
|
1032
|
+
This method programs flash memory on the slave in chunks, handling
|
|
1033
|
+
the details of breaking up large transfers into smaller pieces.
|
|
1034
|
+
It's not part of the XCP Specification but provides a convenient
|
|
1035
|
+
way to program flash memory.
|
|
1036
|
+
|
|
1037
|
+
Parameters
|
|
1038
|
+
----------
|
|
1039
|
+
address : int
|
|
1040
|
+
The memory address to program
|
|
1041
|
+
data : bytes
|
|
1042
|
+
The data bytes to program
|
|
1043
|
+
callback : Callable[[int], None], optional
|
|
1044
|
+
A callback function that is called with the percentage of completion,
|
|
1045
|
+
by default None
|
|
1046
|
+
|
|
1047
|
+
Note
|
|
1048
|
+
----
|
|
1049
|
+
This method uses the program and programNext methods internally.
|
|
1050
|
+
It automatically uses the programming-specific parameters from the
|
|
1051
|
+
slave properties (maxCtoPgm, maxBsPgm, minStPgm, masterBlockMode).
|
|
1052
|
+
"""
|
|
1053
|
+
# Use the generalized downloader to program the flash
|
|
1054
|
+
self._generalized_downloader(
|
|
1055
|
+
address=address,
|
|
1056
|
+
data=data,
|
|
1057
|
+
maxCto=self.slaveProperties.pgmProcessor.maxCtoPgm,
|
|
1058
|
+
maxBs=self.slaveProperties.pgmProcessor.maxBsPgm,
|
|
1059
|
+
minSt=self.slaveProperties.pgmProcessor.minStPgm,
|
|
1060
|
+
master_block_mode=self.slaveProperties.pgmProcessor.masterBlockMode,
|
|
1061
|
+
dl_func=self.program,
|
|
1062
|
+
dl_next_func=self.programNext,
|
|
1063
|
+
callback=callback,
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
def _generalized_downloader(
|
|
1067
|
+
self,
|
|
1068
|
+
address: int,
|
|
1069
|
+
address_ext: int,
|
|
1070
|
+
data: bytes,
|
|
1071
|
+
maxCto: int,
|
|
1072
|
+
maxBs: int,
|
|
1073
|
+
minSt: int,
|
|
1074
|
+
master_block_mode: bool,
|
|
1075
|
+
dl_func: Callable[[bytes, int, bool], Any],
|
|
1076
|
+
dl_next_func: Callable[[bytes, int, bool], Any],
|
|
1077
|
+
callback: Callable[[int], None] | None = None,
|
|
1078
|
+
) -> None:
|
|
1079
|
+
"""Generic implementation for downloading data to the slave.
|
|
1080
|
+
|
|
1081
|
+
This method is a generic implementation for downloading data to the slave.
|
|
1082
|
+
It handles the details of breaking up large transfers into smaller pieces,
|
|
1083
|
+
and supports both master block mode and normal mode.
|
|
1084
|
+
|
|
1085
|
+
Parameters
|
|
1086
|
+
----------
|
|
1087
|
+
address : int
|
|
1088
|
+
The memory address to write to
|
|
1089
|
+
address_ext : int
|
|
1090
|
+
The address extension
|
|
1091
|
+
data : bytes
|
|
1092
|
+
The data bytes to write
|
|
1093
|
+
maxCto : int
|
|
1094
|
+
Maximum Command Transfer Object size
|
|
1095
|
+
maxBs : int
|
|
1096
|
+
Maximum Block Size
|
|
1097
|
+
minSt : int
|
|
1098
|
+
Minimum Separation Time in 100µs units
|
|
1099
|
+
master_block_mode : bool
|
|
1100
|
+
Whether to use master block mode
|
|
1101
|
+
dl_func : Callable[[bytes, int, bool], Any]
|
|
1102
|
+
Function to use for the first download packet
|
|
1103
|
+
dl_next_func : Callable[[bytes, int, bool], Any]
|
|
1104
|
+
Function to use for subsequent download packets
|
|
1105
|
+
callback : Callable[[int], None], optional
|
|
1106
|
+
A callback function that is called with the percentage of completion,
|
|
1107
|
+
by default None
|
|
1108
|
+
"""
|
|
1109
|
+
# Set the Memory Transfer Address
|
|
1110
|
+
self.setMta(address, address_ext)
|
|
1111
|
+
|
|
1112
|
+
# Convert minSt from 100µs units to seconds
|
|
1113
|
+
minSt_seconds = minSt / 10000.0
|
|
1114
|
+
|
|
1115
|
+
# Create a partial function for block downloading
|
|
1116
|
+
block_downloader = functools.partial(
|
|
1117
|
+
self._block_downloader,
|
|
1118
|
+
dl_func=dl_func,
|
|
1119
|
+
dl_next_func=dl_next_func,
|
|
1120
|
+
minSt=minSt_seconds,
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
# Calculate total length and maximum payload size
|
|
1124
|
+
total_length = len(data)
|
|
1125
|
+
if master_block_mode:
|
|
1126
|
+
max_payload = min(maxBs * (maxCto - 2), 255)
|
|
1127
|
+
else:
|
|
1128
|
+
max_payload = maxCto - 2
|
|
1129
|
+
|
|
1130
|
+
# Initialize offset
|
|
1131
|
+
offset = 0
|
|
1132
|
+
|
|
1133
|
+
# Handle master block mode
|
|
1134
|
+
if master_block_mode:
|
|
1135
|
+
self._download_master_block_mode(data, total_length, max_payload, offset, block_downloader, callback)
|
|
1136
|
+
# Handle normal mode
|
|
1137
|
+
else:
|
|
1138
|
+
self._download_normal_mode(data, total_length, max_payload, offset, dl_func, callback)
|
|
1139
|
+
|
|
1140
|
+
def _download_master_block_mode(
|
|
1141
|
+
self,
|
|
1142
|
+
data: bytes,
|
|
1143
|
+
total_length: int,
|
|
1144
|
+
max_payload: int,
|
|
1145
|
+
offset: int,
|
|
1146
|
+
block_downloader: Callable[[bytes], Any],
|
|
1147
|
+
callback: Callable[[int], None] | None = None,
|
|
1148
|
+
) -> None:
|
|
1149
|
+
"""Download data using master block mode.
|
|
1150
|
+
|
|
1151
|
+
Parameters
|
|
1152
|
+
----------
|
|
1153
|
+
data : bytes
|
|
1154
|
+
The data bytes to write
|
|
1155
|
+
total_length : int
|
|
1156
|
+
The total length of the data
|
|
1157
|
+
max_payload : int
|
|
1158
|
+
Maximum payload size
|
|
1159
|
+
offset : int
|
|
1160
|
+
Starting offset in the data
|
|
1161
|
+
block_downloader : Callable[[bytes], Any]
|
|
1162
|
+
Function to use for downloading blocks
|
|
1163
|
+
callback : Callable[[int], None], optional
|
|
1164
|
+
A callback function that is called with the percentage of completion,
|
|
1165
|
+
by default None
|
|
1166
|
+
"""
|
|
1167
|
+
remaining = total_length
|
|
1168
|
+
blocks = range(total_length // max_payload)
|
|
1169
|
+
percent_complete = 1
|
|
1170
|
+
remaining_block_size = total_length % max_payload
|
|
1171
|
+
|
|
1172
|
+
# Process full blocks
|
|
1173
|
+
for _ in blocks:
|
|
1174
|
+
block = data[offset: offset + max_payload]
|
|
1175
|
+
block_downloader(block)
|
|
1176
|
+
offset += max_payload
|
|
1177
|
+
remaining -= max_payload
|
|
1178
|
+
|
|
1179
|
+
# Call callback if provided
|
|
1180
|
+
if callback and remaining <= total_length - (total_length / 100) * percent_complete:
|
|
1181
|
+
callback(percent_complete)
|
|
1182
|
+
percent_complete += 1
|
|
1183
|
+
|
|
1184
|
+
# Process remaining partial block
|
|
1185
|
+
if remaining_block_size:
|
|
1186
|
+
block = data[offset: offset + remaining_block_size]
|
|
1187
|
+
block_downloader(block)
|
|
1188
|
+
if callback:
|
|
1189
|
+
callback(percent_complete)
|
|
1190
|
+
|
|
1191
|
+
def _download_normal_mode(
|
|
1192
|
+
self,
|
|
1193
|
+
data: bytes,
|
|
1194
|
+
total_length: int,
|
|
1195
|
+
max_payload: int,
|
|
1196
|
+
offset: int,
|
|
1197
|
+
dl_func: Callable[[bytes, int, bool], Any],
|
|
1198
|
+
callback: Callable[[int], None] | None = None,
|
|
1199
|
+
) -> None:
|
|
1200
|
+
"""Download data using normal mode.
|
|
1201
|
+
|
|
1202
|
+
Parameters
|
|
1203
|
+
----------
|
|
1204
|
+
data : bytes
|
|
1205
|
+
The data bytes to write
|
|
1206
|
+
total_length : int
|
|
1207
|
+
The total length of the data
|
|
1208
|
+
max_payload : int
|
|
1209
|
+
Maximum payload size
|
|
1210
|
+
offset : int
|
|
1211
|
+
Starting offset in the data
|
|
1212
|
+
dl_func : Callable[[bytes, int, bool], Any]
|
|
1213
|
+
Function to use for downloading
|
|
1214
|
+
callback : Callable[[int], None], optional
|
|
1215
|
+
A callback function that is called with the percentage of completion,
|
|
1216
|
+
by default None
|
|
1217
|
+
"""
|
|
1218
|
+
chunk_size = max_payload
|
|
1219
|
+
chunks = range(total_length // chunk_size)
|
|
1220
|
+
remaining = total_length % chunk_size
|
|
1221
|
+
percent_complete = 1
|
|
1222
|
+
callback_remaining = total_length
|
|
1223
|
+
|
|
1224
|
+
# Process full chunks
|
|
1225
|
+
for _ in chunks:
|
|
1226
|
+
block = data[offset: offset + max_payload]
|
|
1227
|
+
dl_func(block, max_payload, last=False)
|
|
1228
|
+
offset += max_payload
|
|
1229
|
+
callback_remaining -= chunk_size
|
|
1230
|
+
|
|
1231
|
+
# Call callback if provided
|
|
1232
|
+
if callback and callback_remaining <= total_length - (total_length / 100) * percent_complete:
|
|
1233
|
+
callback(percent_complete)
|
|
1234
|
+
percent_complete += 1
|
|
1235
|
+
|
|
1236
|
+
# Process remaining partial chunk
|
|
1237
|
+
if remaining:
|
|
1238
|
+
block = data[offset: offset + remaining]
|
|
1239
|
+
dl_func(block, remaining, last=True)
|
|
1240
|
+
if callback:
|
|
1241
|
+
callback(percent_complete)
|
|
1242
|
+
|
|
1243
|
+
def _block_downloader(
|
|
1244
|
+
self,
|
|
1245
|
+
data: bytes,
|
|
1246
|
+
dl_func: Callable[[bytes, int, bool], Any] | None = None,
|
|
1247
|
+
dl_next_func: Callable[[bytes, int, bool], Any] | None = None,
|
|
1248
|
+
minSt: float = 0.0,
|
|
1249
|
+
) -> None:
|
|
1250
|
+
"""Re-usable block downloader for transferring data in blocks.
|
|
1251
|
+
|
|
1252
|
+
This method breaks up a block of data into packets and sends them
|
|
1253
|
+
using the provided download functions. It handles the details of
|
|
1254
|
+
calculating packet sizes, setting the 'last' flag, and applying
|
|
1255
|
+
the minimum separation time between packets.
|
|
1256
|
+
|
|
1257
|
+
Parameters
|
|
1258
|
+
----------
|
|
1259
|
+
data : bytes
|
|
1260
|
+
The data bytes to download
|
|
1261
|
+
dl_func : Callable[[bytes, int, bool], Any] | None, optional
|
|
1262
|
+
Function to use for the first download packet,
|
|
1263
|
+
usually :meth:`download` or :meth:`program`, by default None
|
|
1264
|
+
dl_next_func : Callable[[bytes, int, bool], Any] | None, optional
|
|
1265
|
+
Function to use for subsequent download packets,
|
|
1266
|
+
usually :meth:`downloadNext` or :meth:`programNext`, by default None
|
|
1267
|
+
minSt : float, optional
|
|
1268
|
+
Minimum separation time between frames in seconds, by default 0.0
|
|
1269
|
+
"""
|
|
1270
|
+
# Calculate sizes and offsets
|
|
1271
|
+
length = len(data)
|
|
1272
|
+
max_packet_size = self.slaveProperties.maxCto - 2 # Command ID + Length
|
|
1273
|
+
packets = range(length // max_packet_size)
|
|
1274
|
+
offset = 0
|
|
1275
|
+
remaining = length % max_packet_size
|
|
1276
|
+
remaining_block_size = length
|
|
1277
|
+
|
|
1278
|
+
# Process full packets
|
|
1279
|
+
index = 0
|
|
1280
|
+
for index in packets:
|
|
1281
|
+
# Extract packet data
|
|
1282
|
+
packet_data = data[offset: offset + max_packet_size]
|
|
1283
|
+
|
|
1284
|
+
# Determine if this is the last packet
|
|
1285
|
+
last = (remaining_block_size - max_packet_size) == 0
|
|
1286
|
+
|
|
1287
|
+
# Send packet using appropriate function
|
|
1288
|
+
if index == 0:
|
|
1289
|
+
# First packet: use dl_func and transmit the complete length
|
|
1290
|
+
dl_func(packet_data, length, last)
|
|
1291
|
+
else:
|
|
1292
|
+
# Subsequent packets: use dl_next_func
|
|
1293
|
+
dl_next_func(packet_data, remaining_block_size, last)
|
|
1294
|
+
|
|
1295
|
+
# Update offsets and remaining size
|
|
1296
|
+
offset += max_packet_size
|
|
1297
|
+
remaining_block_size -= max_packet_size
|
|
1298
|
+
|
|
1299
|
+
# Apply minimum separation time
|
|
1300
|
+
delay(minSt)
|
|
1301
|
+
|
|
1302
|
+
# Process remaining partial packet
|
|
1303
|
+
if remaining:
|
|
1304
|
+
# Extract remaining data
|
|
1305
|
+
packet_data = data[offset: offset + remaining]
|
|
1306
|
+
|
|
1307
|
+
# Send packet using appropriate function
|
|
1308
|
+
if index == 0:
|
|
1309
|
+
# If there were no full packets, use dl_func
|
|
1310
|
+
# (length of data is smaller than maxCto - 2)
|
|
1311
|
+
dl_func(packet_data, remaining, last=True)
|
|
1312
|
+
else:
|
|
1313
|
+
# Otherwise use dl_next_func
|
|
1314
|
+
dl_next_func(packet_data, remaining, last=True)
|
|
1315
|
+
|
|
1316
|
+
# Apply minimum separation time
|
|
1317
|
+
delay(minSt)
|
|
1318
|
+
|
|
1319
|
+
@wrapped
|
|
1320
|
+
def download(self, data: bytes, block_mode_length: int | None = None, last: bool = False):
|
|
1321
|
+
"""Transfer data from master to slave.
|
|
1322
|
+
|
|
1323
|
+
Parameters
|
|
1324
|
+
----------
|
|
1325
|
+
data : bytes
|
|
1326
|
+
Data to send to slave.
|
|
1327
|
+
block_mode_length : int or None
|
|
1328
|
+
for block mode, the download request must contain the length of the whole block,
|
|
1329
|
+
not just the length in the current packet. The whole block length can be given here for block-mode
|
|
1330
|
+
transfers. For normal mode, the length indicates the actual packet's payload length.
|
|
1331
|
+
|
|
1332
|
+
Note
|
|
1333
|
+
----
|
|
1334
|
+
Adress is set via :meth:`setMta`
|
|
1335
|
+
"""
|
|
1336
|
+
|
|
1337
|
+
if block_mode_length is None or last:
|
|
1338
|
+
# standard mode
|
|
1339
|
+
length = len(data)
|
|
1340
|
+
response = self.transport.request(types.Command.DOWNLOAD, length, *data)
|
|
1341
|
+
return response
|
|
1342
|
+
else:
|
|
1343
|
+
# block mode
|
|
1344
|
+
if not isinstance(block_mode_length, int):
|
|
1345
|
+
raise TypeError("block_mode_length must be int!")
|
|
1346
|
+
self.transport.block_request(types.Command.DOWNLOAD, block_mode_length, *data)
|
|
1347
|
+
return None
|
|
1348
|
+
|
|
1349
|
+
@wrapped
|
|
1350
|
+
def downloadNext(self, data: bytes, remaining_block_length: int, last: bool = False):
|
|
1351
|
+
"""Transfer data from master to slave (block mode).
|
|
1352
|
+
|
|
1353
|
+
Parameters
|
|
1354
|
+
----------
|
|
1355
|
+
data : bytes
|
|
1356
|
+
remainingBlockLength : int
|
|
1357
|
+
This parameter has to be given the remaining length in the block
|
|
1358
|
+
last : bool
|
|
1359
|
+
The block mode implementation shall indicate the last packet in the block with this parameter, because
|
|
1360
|
+
the slave device will send the response after this.
|
|
1361
|
+
"""
|
|
1362
|
+
|
|
1363
|
+
if last:
|
|
1364
|
+
# last DOWNLOAD_NEXT packet in a block: the slave device has to send the response after this.
|
|
1365
|
+
response = self.transport.request(types.Command.DOWNLOAD_NEXT, remaining_block_length, *data)
|
|
1366
|
+
return response
|
|
1367
|
+
else:
|
|
1368
|
+
# the slave device won't respond to consecutive DOWNLOAD_NEXT packets in block mode,
|
|
1369
|
+
# so we must not wait for any response
|
|
1370
|
+
self.transport.block_request(types.Command.DOWNLOAD_NEXT, remaining_block_length, *data)
|
|
1371
|
+
return None
|
|
1372
|
+
|
|
1373
|
+
@wrapped
|
|
1374
|
+
def downloadMax(self, data: bytes):
|
|
1375
|
+
"""Transfer data from master to slave (fixed size).
|
|
1376
|
+
|
|
1377
|
+
Parameters
|
|
1378
|
+
----------
|
|
1379
|
+
data : bytes
|
|
1380
|
+
"""
|
|
1381
|
+
return self.transport.request(types.Command.DOWNLOAD_MAX, *data)
|
|
1382
|
+
|
|
1383
|
+
@wrapped
|
|
1384
|
+
def shortDownload(self, address: int, address_ext: int, data: bytes):
|
|
1385
|
+
length = len(data)
|
|
1386
|
+
addr = self.DWORD_pack(address)
|
|
1387
|
+
return self.transport.request(types.Command.SHORT_DOWNLOAD, length, 0, address_ext, *addr, *data)
|
|
1388
|
+
|
|
1389
|
+
@wrapped
|
|
1390
|
+
def modifyBits(self, shift_value: int, and_mask: int, xor_mask: int):
|
|
1391
|
+
# A = ( (A) & ((~((dword)(((word)~MA)<<S))) )^((dword)(MX<<S)) )
|
|
1392
|
+
am = self.WORD_pack(and_mask)
|
|
1393
|
+
xm = self.WORD_pack(xor_mask)
|
|
1394
|
+
return self.transport.request(types.Command.MODIFY_BITS, shift_value, *am, *xm)
|
|
1395
|
+
|
|
1396
|
+
# Page Switching Commands (PAG)
|
|
1397
|
+
@wrapped
|
|
1398
|
+
def setCalPage(self, mode: int, logical_data_segment: int, logical_data_page: int):
|
|
1399
|
+
"""Set calibration page.
|
|
1400
|
+
|
|
1401
|
+
Parameters
|
|
1402
|
+
----------
|
|
1403
|
+
mode : int (bitfield)
|
|
1404
|
+
- 0x01 - The given page will be used by the slave device application.
|
|
1405
|
+
- 0x02 - The slave device XCP driver will access the given page.
|
|
1406
|
+
- 0x80 - The logical segment number is ignored. The command applies to all segments
|
|
1407
|
+
logicalDataSegment : int
|
|
1408
|
+
logicalDataPage : int
|
|
1409
|
+
"""
|
|
1410
|
+
return self.transport.request(types.Command.SET_CAL_PAGE, mode, logical_data_segment, logical_data_page)
|
|
1411
|
+
|
|
1412
|
+
@wrapped
|
|
1413
|
+
def getCalPage(self, mode: int, logical_data_segment: int):
|
|
1414
|
+
"""Get calibration page
|
|
1415
|
+
|
|
1416
|
+
Parameters
|
|
1417
|
+
----------
|
|
1418
|
+
mode : int
|
|
1419
|
+
logicalDataSegment : int
|
|
1420
|
+
"""
|
|
1421
|
+
response = self.transport.request(types.Command.GET_CAL_PAGE, mode, logical_data_segment)
|
|
1422
|
+
return response[2]
|
|
1423
|
+
|
|
1424
|
+
@wrapped
|
|
1425
|
+
def getPagProcessorInfo(self):
|
|
1426
|
+
"""Get general information on PAG processor.
|
|
1427
|
+
|
|
1428
|
+
Returns
|
|
1429
|
+
-------
|
|
1430
|
+
`pydbc.types.GetPagProcessorInfoResponse`
|
|
1431
|
+
"""
|
|
1432
|
+
response = self.transport.request(types.Command.GET_PAG_PROCESSOR_INFO)
|
|
1433
|
+
return types.GetPagProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1434
|
+
|
|
1435
|
+
@wrapped
|
|
1436
|
+
def getSegmentInfo(self, mode: int, segment_number: int, segment_info: int, mapping_index: int):
|
|
1437
|
+
"""Get specific information for a segment.
|
|
1438
|
+
|
|
1439
|
+
Parameters
|
|
1440
|
+
----------
|
|
1441
|
+
mode : int
|
|
1442
|
+
- 0 = get basic address info for this segment
|
|
1443
|
+
- 1 = get standard info for this segment
|
|
1444
|
+
- 2 = get address mapping info for this segment
|
|
1445
|
+
|
|
1446
|
+
segmentNumber : int
|
|
1447
|
+
segmentInfo : int
|
|
1448
|
+
Mode 0:
|
|
1449
|
+
- 0 = address
|
|
1450
|
+
- 1 = length
|
|
1451
|
+
|
|
1452
|
+
Mode 1:
|
|
1453
|
+
- don't care
|
|
1454
|
+
|
|
1455
|
+
Mode 2:
|
|
1456
|
+
- 0 = source address
|
|
1457
|
+
- 1 = destination address
|
|
1458
|
+
- 2 = length address
|
|
1459
|
+
|
|
1460
|
+
mappingIndex : int
|
|
1461
|
+
- Mode 0: don't care
|
|
1462
|
+
- Mode 1: don't care
|
|
1463
|
+
- Mode 2: identifier for address mapping range that mapping_info belongs to.
|
|
1464
|
+
|
|
1465
|
+
"""
|
|
1466
|
+
response = self.transport.request(
|
|
1467
|
+
types.Command.GET_SEGMENT_INFO,
|
|
1468
|
+
mode,
|
|
1469
|
+
segment_number,
|
|
1470
|
+
segment_info,
|
|
1471
|
+
mapping_index,
|
|
1472
|
+
)
|
|
1473
|
+
if mode == 0:
|
|
1474
|
+
return types.GetSegmentInfoMode0Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1475
|
+
elif mode == 1:
|
|
1476
|
+
return types.GetSegmentInfoMode1Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1477
|
+
elif mode == 2:
|
|
1478
|
+
return types.GetSegmentInfoMode2Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1479
|
+
|
|
1480
|
+
@wrapped
|
|
1481
|
+
def getPageInfo(self, segment_number: int, page_number: int):
|
|
1482
|
+
"""Get specific information for a page.
|
|
1483
|
+
|
|
1484
|
+
Parameters
|
|
1485
|
+
----------
|
|
1486
|
+
segmentNumber : int
|
|
1487
|
+
pageNumber : int
|
|
1488
|
+
"""
|
|
1489
|
+
response = self.transport.request(types.Command.GET_PAGE_INFO, 0, segment_number, page_number)
|
|
1490
|
+
return (
|
|
1491
|
+
types.PageProperties.parse(bytes([response[0]]), byteOrder=self.slaveProperties.byteOrder),
|
|
1492
|
+
response[1],
|
|
1493
|
+
)
|
|
1494
|
+
|
|
1495
|
+
@wrapped
|
|
1496
|
+
def setSegmentMode(self, mode: int, segment_number: int):
|
|
1497
|
+
"""Set mode for a segment.
|
|
1498
|
+
|
|
1499
|
+
Parameters
|
|
1500
|
+
----------
|
|
1501
|
+
mode : int (bitfield)
|
|
1502
|
+
1 = enable FREEZE Mode
|
|
1503
|
+
segmentNumber : int
|
|
1504
|
+
"""
|
|
1505
|
+
return self.transport.request(types.Command.SET_SEGMENT_MODE, mode, segment_number)
|
|
1506
|
+
|
|
1507
|
+
@wrapped
|
|
1508
|
+
def getSegmentMode(self, segment_number: int):
|
|
1509
|
+
"""Get mode for a segment.
|
|
1510
|
+
|
|
1511
|
+
Parameters
|
|
1512
|
+
----------
|
|
1513
|
+
segmentNumber : int
|
|
1514
|
+
"""
|
|
1515
|
+
response = self.transport.request(types.Command.GET_SEGMENT_MODE, 0, segment_number)
|
|
1516
|
+
if response:
|
|
1517
|
+
return response[1]
|
|
1518
|
+
|
|
1519
|
+
@wrapped
|
|
1520
|
+
def copyCalPage(self, src_segment: int, src_page: int, dst_segment: int, dst_page: int):
|
|
1521
|
+
"""Copy page.
|
|
1522
|
+
|
|
1523
|
+
Parameters
|
|
1524
|
+
----------
|
|
1525
|
+
srcSegment : int
|
|
1526
|
+
srcPage : int
|
|
1527
|
+
dstSegment : int
|
|
1528
|
+
dstPage : int
|
|
1529
|
+
"""
|
|
1530
|
+
return self.transport.request(types.Command.COPY_CAL_PAGE, src_segment, src_page, dst_segment, dst_page)
|
|
1531
|
+
|
|
1532
|
+
# DAQ
|
|
1533
|
+
|
|
1534
|
+
@wrapped
|
|
1535
|
+
def setDaqPtr(self, daq_list_number: int, odt_number: int, odt_entry_number: int):
|
|
1536
|
+
self.currentDaqPtr = types.DaqPtr(daq_list_number, odt_number, odt_entry_number) # Needed for errorhandling.
|
|
1537
|
+
daq_list = self.WORD_pack(daq_list_number)
|
|
1538
|
+
response = self.transport.request(types.Command.SET_DAQ_PTR, 0, *daq_list, odt_number, odt_entry_number)
|
|
1539
|
+
self.stim.setDaqPtr(daq_list_number, odt_number, odt_entry_number)
|
|
1540
|
+
return response
|
|
1541
|
+
|
|
1542
|
+
@wrapped
|
|
1543
|
+
def clearDaqList(self, daq_list_number: int):
|
|
1544
|
+
"""Clear DAQ list configuration.
|
|
1545
|
+
|
|
1546
|
+
Parameters
|
|
1547
|
+
----------
|
|
1548
|
+
daqListNumber : int
|
|
1549
|
+
"""
|
|
1550
|
+
daq_list = self.WORD_pack(daq_list_number)
|
|
1551
|
+
result = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daq_list)
|
|
1552
|
+
self.stim.clearDaqList(daq_list_number)
|
|
1553
|
+
return result
|
|
1554
|
+
|
|
1555
|
+
@wrapped
|
|
1556
|
+
def writeDaq(self, bit_offset: int, entry_size: int, address_ext: int, address: int):
|
|
1557
|
+
"""Write element in ODT entry.
|
|
1558
|
+
|
|
1559
|
+
Parameters
|
|
1560
|
+
----------
|
|
1561
|
+
bitOffset : int
|
|
1562
|
+
Position of bit in 32-bit variable referenced by the address and
|
|
1563
|
+
extension below
|
|
1564
|
+
entrySize : int
|
|
1565
|
+
addressExt : int
|
|
1566
|
+
address : int
|
|
1567
|
+
"""
|
|
1568
|
+
addr = self.DWORD_pack(address)
|
|
1569
|
+
result = self.transport.request(types.Command.WRITE_DAQ, bit_offset, entry_size, address_ext, *addr)
|
|
1570
|
+
self.stim.writeDaq(bit_offset, entry_size, address_ext, address)
|
|
1571
|
+
return result
|
|
1572
|
+
|
|
1573
|
+
@wrapped
|
|
1574
|
+
def setDaqListMode(self, mode: int, daq_list_number: int, event_channel_number: int, prescaler: int, priority: int):
|
|
1575
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1576
|
+
ecn = self.WORD_pack(event_channel_number)
|
|
1577
|
+
self.stim.setDaqListMode(mode, daq_list_number, event_channel_number, prescaler, priority)
|
|
1578
|
+
return self.transport.request(types.Command.SET_DAQ_LIST_MODE, mode, *dln, *ecn, prescaler, priority)
|
|
1579
|
+
|
|
1580
|
+
@wrapped
|
|
1581
|
+
def getDaqListMode(self, daq_list_number: int):
|
|
1582
|
+
"""Get mode from DAQ list.
|
|
1583
|
+
|
|
1584
|
+
Parameters
|
|
1585
|
+
----------
|
|
1586
|
+
daqListNumber : int
|
|
1587
|
+
|
|
1588
|
+
Returns
|
|
1589
|
+
-------
|
|
1590
|
+
`pyxcp.types.GetDaqListModeResponse`
|
|
1591
|
+
"""
|
|
1592
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1593
|
+
response = self.transport.request(types.Command.GET_DAQ_LIST_MODE, 0, *dln)
|
|
1594
|
+
return types.GetDaqListModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1595
|
+
|
|
1596
|
+
@wrapped
|
|
1597
|
+
def startStopDaqList(self, mode: int, daq_list_number: int):
|
|
1598
|
+
"""Start /stop/select DAQ list.
|
|
1599
|
+
|
|
1600
|
+
Parameters
|
|
1601
|
+
----------
|
|
1602
|
+
mode : int
|
|
1603
|
+
0 = stop
|
|
1604
|
+
1 = start
|
|
1605
|
+
2 = select
|
|
1606
|
+
daqListNumber : int
|
|
1607
|
+
"""
|
|
1608
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1609
|
+
response = self.transport.request(types.Command.START_STOP_DAQ_LIST, mode, *dln)
|
|
1610
|
+
self.stim.startStopDaqList(mode, daq_list_number)
|
|
1611
|
+
first_pid = types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1612
|
+
self.stim.set_first_pid(daq_list_number, first_pid.firstPid)
|
|
1613
|
+
return first_pid
|
|
1614
|
+
|
|
1615
|
+
@wrapped
|
|
1616
|
+
def startStopSynch(self, mode: int):
|
|
1617
|
+
"""Start/stop DAQ lists (synchronously).
|
|
1618
|
+
|
|
1619
|
+
Parameters
|
|
1620
|
+
----------
|
|
1621
|
+
mode : int
|
|
1622
|
+
0 = stop all
|
|
1623
|
+
1 = start selected
|
|
1624
|
+
2 = stop selected
|
|
1625
|
+
"""
|
|
1626
|
+
res = self.transport.request(types.Command.START_STOP_SYNCH, mode)
|
|
1627
|
+
self.stim.startStopSynch(mode)
|
|
1628
|
+
return res
|
|
1629
|
+
|
|
1630
|
+
@wrapped
|
|
1631
|
+
def writeDaqMultiple(self, daq_elements: dict):
|
|
1632
|
+
"""Write multiple elements in ODT.
|
|
1633
|
+
|
|
1634
|
+
Parameters
|
|
1635
|
+
----------
|
|
1636
|
+
daqElements : list of `dict` containing the following keys: *bitOffset*, *size*, *address*, *addressExt*.
|
|
1637
|
+
"""
|
|
1638
|
+
if len(daq_elements) > self.slaveProperties.maxWriteDaqMultipleElements:
|
|
1639
|
+
raise ValueError(f"At most {self.slaveProperties.maxWriteDaqMultipleElements} daqElements are permitted.")
|
|
1640
|
+
data = bytearray()
|
|
1641
|
+
data.append(len(daq_elements))
|
|
1642
|
+
|
|
1643
|
+
for daq_element in daq_elements:
|
|
1644
|
+
data.extend(types.DaqElement.build(daq_element, byteOrder=self.slaveProperties.byteOrder))
|
|
1645
|
+
|
|
1646
|
+
return self.transport.request(types.Command.WRITE_DAQ_MULTIPLE, *data)
|
|
1647
|
+
|
|
1648
|
+
# optional
|
|
1649
|
+
@wrapped
|
|
1650
|
+
def getDaqClock(self):
|
|
1651
|
+
"""Get DAQ clock from slave.
|
|
1652
|
+
|
|
1653
|
+
Returns
|
|
1654
|
+
-------
|
|
1655
|
+
int
|
|
1656
|
+
Current timestamp, format specified by `getDaqResolutionInfo`
|
|
1657
|
+
"""
|
|
1658
|
+
response = self.transport.request(types.Command.GET_DAQ_CLOCK)
|
|
1659
|
+
result = types.GetDaqClockResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1660
|
+
return result.timestamp
|
|
1661
|
+
|
|
1662
|
+
@wrapped
|
|
1663
|
+
def readDaq(self):
|
|
1664
|
+
"""Read element from ODT entry.
|
|
1665
|
+
|
|
1666
|
+
Returns
|
|
1667
|
+
-------
|
|
1668
|
+
`pyxcp.types.ReadDaqResponse`
|
|
1669
|
+
"""
|
|
1670
|
+
response = self.transport.request(types.Command.READ_DAQ)
|
|
1671
|
+
return types.ReadDaqResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1672
|
+
|
|
1673
|
+
@wrapped
|
|
1674
|
+
def getDaqProcessorInfo(self):
|
|
1675
|
+
"""Get general information on DAQ processor.
|
|
1676
|
+
|
|
1677
|
+
Returns
|
|
1678
|
+
-------
|
|
1679
|
+
`pyxcp.types.GetDaqProcessorInfoResponse`
|
|
1680
|
+
"""
|
|
1681
|
+
response = self.transport.request(types.Command.GET_DAQ_PROCESSOR_INFO)
|
|
1682
|
+
return types.GetDaqProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1683
|
+
|
|
1684
|
+
@wrapped
|
|
1685
|
+
def getDaqResolutionInfo(self):
|
|
1686
|
+
"""Get general information on DAQ processing resolution.
|
|
1687
|
+
|
|
1688
|
+
Returns
|
|
1689
|
+
-------
|
|
1690
|
+
`pyxcp.types.GetDaqResolutionInfoResponse`
|
|
1691
|
+
"""
|
|
1692
|
+
response = self.transport.request(types.Command.GET_DAQ_RESOLUTION_INFO)
|
|
1693
|
+
return types.GetDaqResolutionInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1694
|
+
|
|
1695
|
+
@wrapped
|
|
1696
|
+
def getDaqListInfo(self, daq_list_number: int):
|
|
1697
|
+
"""Get specific information for a DAQ list.
|
|
1698
|
+
|
|
1699
|
+
Parameters
|
|
1700
|
+
----------
|
|
1701
|
+
daqListNumber : int
|
|
1702
|
+
"""
|
|
1703
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1704
|
+
response = self.transport.request(types.Command.GET_DAQ_LIST_INFO, 0, *dln)
|
|
1705
|
+
return types.GetDaqListInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1706
|
+
|
|
1707
|
+
@wrapped
|
|
1708
|
+
def getDaqEventInfo(self, event_channel_number: int):
|
|
1709
|
+
"""Get specific information for an event channel.
|
|
1710
|
+
|
|
1711
|
+
Parameters
|
|
1712
|
+
----------
|
|
1713
|
+
eventChannelNumber : int
|
|
1714
|
+
|
|
1715
|
+
Returns
|
|
1716
|
+
-------
|
|
1717
|
+
`pyxcp.types.GetEventChannelInfoResponse`
|
|
1718
|
+
"""
|
|
1719
|
+
ecn = self.WORD_pack(event_channel_number)
|
|
1720
|
+
response = self.transport.request(types.Command.GET_DAQ_EVENT_INFO, 0, *ecn)
|
|
1721
|
+
return types.GetEventChannelInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1722
|
+
|
|
1723
|
+
@wrapped
|
|
1724
|
+
def dtoCtrProperties(self, modifier: int, event_channel: int, related_event_channel: int, mode: int):
|
|
1725
|
+
"""DTO CTR properties
|
|
1726
|
+
|
|
1727
|
+
Parameters
|
|
1728
|
+
----------
|
|
1729
|
+
modifier :
|
|
1730
|
+
eventChannel : int
|
|
1731
|
+
relatedEventChannel : int
|
|
1732
|
+
mode :
|
|
1733
|
+
|
|
1734
|
+
Returns
|
|
1735
|
+
-------
|
|
1736
|
+
`pyxcp.types.DtoCtrPropertiesResponse`
|
|
1737
|
+
"""
|
|
1738
|
+
data = bytearray()
|
|
1739
|
+
data.append(modifier)
|
|
1740
|
+
data.extend(self.WORD_pack(event_channel))
|
|
1741
|
+
data.extend(self.WORD_pack(related_event_channel))
|
|
1742
|
+
data.append(mode)
|
|
1743
|
+
response = self.transport.request(types.Command.DTO_CTR_PROPERTIES, *data)
|
|
1744
|
+
return types.DtoCtrPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1745
|
+
|
|
1746
|
+
@wrapped
|
|
1747
|
+
def setDaqPackedMode(
|
|
1748
|
+
self, daq_list_number: int, daq_packed_mode: int, dpm_timestamp_mode: int = None,
|
|
1749
|
+
dpm_sample_count: int = None
|
|
1750
|
+
):
|
|
1751
|
+
"""Set DAQ List Packed Mode.
|
|
1752
|
+
|
|
1753
|
+
Parameters
|
|
1754
|
+
----------
|
|
1755
|
+
daqListNumber : int
|
|
1756
|
+
daqPackedMode : int
|
|
1757
|
+
"""
|
|
1758
|
+
params = []
|
|
1759
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1760
|
+
params.extend(dln)
|
|
1761
|
+
params.append(daq_packed_mode)
|
|
1762
|
+
|
|
1763
|
+
if daq_packed_mode == 1 or daq_packed_mode == 2:
|
|
1764
|
+
params.append(dpm_timestamp_mode)
|
|
1765
|
+
dsc = self.WORD_pack(dpm_sample_count)
|
|
1766
|
+
params.extend(dsc)
|
|
1767
|
+
|
|
1768
|
+
return self.transport.request(types.Command.SET_DAQ_PACKED_MODE, *params)
|
|
1769
|
+
|
|
1770
|
+
@wrapped
|
|
1771
|
+
def getDaqPackedMode(self, daq_list_number: int):
|
|
1772
|
+
"""Get DAQ List Packed Mode.
|
|
1773
|
+
|
|
1774
|
+
This command returns information of the currently active packed mode of
|
|
1775
|
+
the addressed DAQ list.
|
|
1776
|
+
|
|
1777
|
+
Parameters
|
|
1778
|
+
----------
|
|
1779
|
+
daqListNumber : int
|
|
1780
|
+
"""
|
|
1781
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1782
|
+
response = self.transport.request(types.Command.GET_DAQ_PACKED_MODE, *dln)
|
|
1783
|
+
return types.GetDaqPackedModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1784
|
+
|
|
1785
|
+
# dynamic
|
|
1786
|
+
@wrapped
|
|
1787
|
+
def freeDaq(self):
|
|
1788
|
+
"""Clear dynamic DAQ configuration."""
|
|
1789
|
+
result = self.transport.request(types.Command.FREE_DAQ)
|
|
1790
|
+
self.stim.freeDaq()
|
|
1791
|
+
return result
|
|
1792
|
+
|
|
1793
|
+
@wrapped
|
|
1794
|
+
def allocDaq(self, daq_count: int):
|
|
1795
|
+
"""Allocate DAQ lists.
|
|
1796
|
+
|
|
1797
|
+
Parameters
|
|
1798
|
+
----------
|
|
1799
|
+
daqCount : int
|
|
1800
|
+
number of DAQ lists to be allocated
|
|
1801
|
+
"""
|
|
1802
|
+
dq = self.WORD_pack(daq_count)
|
|
1803
|
+
result = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
|
|
1804
|
+
self.stim.allocDaq(daq_count)
|
|
1805
|
+
return result
|
|
1806
|
+
|
|
1807
|
+
@wrapped
|
|
1808
|
+
def allocOdt(self, daq_list_number: int, odt_count: int):
|
|
1809
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1810
|
+
result = self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odt_count)
|
|
1811
|
+
self.stim.allocOdt(daq_list_number, odt_count)
|
|
1812
|
+
return result
|
|
1813
|
+
|
|
1814
|
+
@wrapped
|
|
1815
|
+
def allocOdtEntry(self, daq_list_number: int, odt_number: int, odt_entries_count: int):
|
|
1816
|
+
dln = self.WORD_pack(daq_list_number)
|
|
1817
|
+
result = self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odt_number, odt_entries_count)
|
|
1818
|
+
self.stim.allocOdtEntry(daq_list_number, odt_number, odt_entries_count)
|
|
1819
|
+
return result
|
|
1820
|
+
|
|
1821
|
+
# PGM
|
|
1822
|
+
@wrapped
|
|
1823
|
+
def programStart(self):
|
|
1824
|
+
"""Indicate the beginning of a programming sequence.
|
|
1825
|
+
|
|
1826
|
+
Returns
|
|
1827
|
+
-------
|
|
1828
|
+
`pyxcp.types.ProgramStartResponse`
|
|
1829
|
+
"""
|
|
1830
|
+
response = self.transport.request(types.Command.PROGRAM_START)
|
|
1831
|
+
result = types.ProgramStartResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1832
|
+
self.slaveProperties.pgmProcessor.commModePgm = result.commModePgm
|
|
1833
|
+
self.slaveProperties.pgmProcessor.maxCtoPgm = result.maxCtoPgm
|
|
1834
|
+
self.slaveProperties.pgmProcessor.maxBsPgm = result.maxBsPgm
|
|
1835
|
+
self.slaveProperties.pgmProcessor.minStPgm = result.minStPgm
|
|
1836
|
+
self.slaveProperties.pgmProcessor.queueSizePgm = result.queueSizePgm
|
|
1837
|
+
self.slaveProperties.pgmProcessor.slaveBlockMode = result.commModePgm.slaveBlockMode
|
|
1838
|
+
self.slaveProperties.pgmProcessor.interleavedMode = result.commModePgm.interleavedMode
|
|
1839
|
+
self.slaveProperties.pgmProcessor.masterBlockMode = result.commModePgm.masterBlockMode
|
|
1840
|
+
return result
|
|
1841
|
+
|
|
1842
|
+
@wrapped
|
|
1843
|
+
def programClear(self, mode: int, clear_range: int):
|
|
1844
|
+
"""Clear a part of non-volatile memory.
|
|
1845
|
+
|
|
1846
|
+
Parameters
|
|
1847
|
+
----------
|
|
1848
|
+
mode : int
|
|
1849
|
+
0x00 = the absolute access mode is active (default)
|
|
1850
|
+
0x01 = the functional access mode is active
|
|
1851
|
+
clearRange : int
|
|
1852
|
+
"""
|
|
1853
|
+
cr = self.DWORD_pack(clear_range)
|
|
1854
|
+
response = self.transport.request(types.Command.PROGRAM_CLEAR, mode, 0, 0, *cr)
|
|
1855
|
+
# ERR_ACCESS_LOCKED
|
|
1856
|
+
return response
|
|
1857
|
+
|
|
1858
|
+
@wrapped
|
|
1859
|
+
def program(self, data: bytes, block_length: int, last: bool = False):
|
|
1860
|
+
"""Parameters
|
|
1861
|
+
----------
|
|
1862
|
+
data : bytes
|
|
1863
|
+
Data to send to slave.
|
|
1864
|
+
block_mode_length : int
|
|
1865
|
+
the program request must contain the length of the whole block, not just the length
|
|
1866
|
+
in the current packet.
|
|
1867
|
+
last : bool
|
|
1868
|
+
Indicates that this is the only packet in the block, because
|
|
1869
|
+
the slave device will send the response after this.
|
|
1870
|
+
|
|
1871
|
+
Note
|
|
1872
|
+
----
|
|
1873
|
+
Adress is set via :meth:`setMta`
|
|
1874
|
+
"""
|
|
1875
|
+
# d = bytearray()
|
|
1876
|
+
# d.append(len(data))
|
|
1877
|
+
# if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
|
|
1878
|
+
# d.extend(b"\x00\x00") # alignment bytes
|
|
1879
|
+
# for e in data:
|
|
1880
|
+
# d.extend(self.AG_pack(e))
|
|
1881
|
+
if last:
|
|
1882
|
+
# last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
|
|
1883
|
+
response = self.transport.request(types.Command.PROGRAM, block_length, *data)
|
|
1884
|
+
return response
|
|
1885
|
+
else:
|
|
1886
|
+
# the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
|
|
1887
|
+
# so we must not wait for any response
|
|
1888
|
+
self.transport.block_request(types.Command.PROGRAM, block_length, *data)
|
|
1889
|
+
return None
|
|
1890
|
+
|
|
1891
|
+
@wrapped
|
|
1892
|
+
def programReset(self, wait_for_optional_response=True):
|
|
1893
|
+
"""Indicate the end of a programming sequence."""
|
|
1894
|
+
if wait_for_optional_response:
|
|
1895
|
+
return self.transport.request_optional_response(types.Command.PROGRAM_RESET)
|
|
1896
|
+
else:
|
|
1897
|
+
return self.transport.request(types.Command.PROGRAM_RESET)
|
|
1898
|
+
|
|
1899
|
+
@wrapped
|
|
1900
|
+
def getPgmProcessorInfo(self):
|
|
1901
|
+
"""Get general information on PGM processor."""
|
|
1902
|
+
response = self.transport.request(types.Command.GET_PGM_PROCESSOR_INFO)
|
|
1903
|
+
result = types.GetPgmProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1904
|
+
self.slaveProperties.pgmProcessor.pgmProperties = result.pgmProperties
|
|
1905
|
+
self.slaveProperties.pgmProcessor.maxSector = result.maxSector
|
|
1906
|
+
return result
|
|
1907
|
+
|
|
1908
|
+
@wrapped
|
|
1909
|
+
def getSectorInfo(self, mode: int, sector_number: int):
|
|
1910
|
+
"""Get specific information for a sector."""
|
|
1911
|
+
response = self.transport.request(types.Command.GET_SECTOR_INFO, mode, sector_number)
|
|
1912
|
+
if mode == 0 or mode == 1:
|
|
1913
|
+
return types.GetSectorInfoResponseMode01.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1914
|
+
elif mode == 2:
|
|
1915
|
+
return types.GetSectorInfoResponseMode2.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1916
|
+
|
|
1917
|
+
@wrapped
|
|
1918
|
+
def programPrepare(self, codesize):
|
|
1919
|
+
"""Prepare non-volatile memory programming."""
|
|
1920
|
+
cs = self.WORD_pack(codesize)
|
|
1921
|
+
return self.transport.request(types.Command.PROGRAM_PREPARE, 0x00, *cs)
|
|
1922
|
+
|
|
1923
|
+
@wrapped
|
|
1924
|
+
def programFormat(self, compression_method: int, encryption_method: int, programming_method: int,
|
|
1925
|
+
access_method: int):
|
|
1926
|
+
return self.transport.request(
|
|
1927
|
+
types.Command.PROGRAM_FORMAT,
|
|
1928
|
+
compression_method,
|
|
1929
|
+
encryption_method,
|
|
1930
|
+
programming_method,
|
|
1931
|
+
access_method,
|
|
1932
|
+
)
|
|
1933
|
+
|
|
1934
|
+
@wrapped
|
|
1935
|
+
def programNext(self, data: bytes, remaining_block_length: int, last: bool = False):
|
|
1936
|
+
# d = bytearray()
|
|
1937
|
+
# d.append(len(data))
|
|
1938
|
+
# if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
|
|
1939
|
+
# d.extend(b"\x00\x00") # alignment bytes
|
|
1940
|
+
# for e in data:
|
|
1941
|
+
# d.extend(self.AG_pack(e))
|
|
1942
|
+
if last:
|
|
1943
|
+
# last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
|
|
1944
|
+
response = self.transport.request(types.Command.PROGRAM_NEXT, remaining_block_length, *data)
|
|
1945
|
+
return response
|
|
1946
|
+
else:
|
|
1947
|
+
# the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
|
|
1948
|
+
# so we must not wait for any response
|
|
1949
|
+
self.transport.block_request(types.Command.PROGRAM_NEXT, remaining_block_length, *data)
|
|
1950
|
+
return None
|
|
1951
|
+
|
|
1952
|
+
@wrapped
|
|
1953
|
+
def programMax(self, data: bytes):
|
|
1954
|
+
d = bytearray()
|
|
1955
|
+
if self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
|
|
1956
|
+
d.extend(b"\x00") # alignment bytes
|
|
1957
|
+
elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
|
|
1958
|
+
d.extend(b"\x00\x00\x00") # alignment bytes
|
|
1959
|
+
for e in data:
|
|
1960
|
+
d.extend(self.AG_pack(e))
|
|
1961
|
+
return self.transport.request(types.Command.PROGRAM_MAX, *d)
|
|
1962
|
+
|
|
1963
|
+
@wrapped
|
|
1964
|
+
def programVerify(self, ver_mode: int, ver_type: int, ver_value: int):
|
|
1965
|
+
data = bytearray()
|
|
1966
|
+
data.extend(self.WORD_pack(ver_type))
|
|
1967
|
+
data.extend(self.DWORD_pack(ver_value))
|
|
1968
|
+
return self.transport.request(types.Command.PROGRAM_VERIFY, ver_mode, *data)
|
|
1969
|
+
|
|
1970
|
+
# DBG
|
|
1971
|
+
|
|
1972
|
+
@wrapped
|
|
1973
|
+
def dbgAttach(self):
|
|
1974
|
+
"""Returns detailed information about the implemented version of the SW-DBG feature of the XCP slave
|
|
1975
|
+
|
|
1976
|
+
Returns
|
|
1977
|
+
-------
|
|
1978
|
+
`pyxcp.types.DbgAttachResponse`
|
|
1979
|
+
"""
|
|
1980
|
+
response = self.transport.request(types.Command.DBG_ATTACH)
|
|
1981
|
+
return types.DbgAttachResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1982
|
+
|
|
1983
|
+
@wrapped
|
|
1984
|
+
def dbgGetVendorInfo(self):
|
|
1985
|
+
""""""
|
|
1986
|
+
response = self.transport.request(types.Command.DBG_GET_VENDOR_INFO)
|
|
1987
|
+
return types.DbgGetVendorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1988
|
+
|
|
1989
|
+
@wrapped
|
|
1990
|
+
def dbgGetModeInfo(self):
|
|
1991
|
+
""""""
|
|
1992
|
+
response = self.transport.request(types.Command.DBG_GET_MODE_INFO)
|
|
1993
|
+
return types.DbgGetModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
1994
|
+
|
|
1995
|
+
@wrapped
|
|
1996
|
+
def dbgGetJtagId(self):
|
|
1997
|
+
""""""
|
|
1998
|
+
response = self.transport.request(types.Command.DBG_GET_JTAG_ID)
|
|
1999
|
+
return types.DbgGetJtagIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2000
|
+
|
|
2001
|
+
@wrapped
|
|
2002
|
+
def dbgHaltAfterReset(self):
|
|
2003
|
+
""""""
|
|
2004
|
+
return self.transport.request(types.Command.DBG_HALT_AFTER_RESET)
|
|
2005
|
+
|
|
2006
|
+
@wrapped
|
|
2007
|
+
def dbgGetHwioInfo(self, index: int):
|
|
2008
|
+
""""""
|
|
2009
|
+
response = self.transport.request(types.Command.DBG_GET_HWIO_INFO, index)
|
|
2010
|
+
return types.DbgGetHwioInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2011
|
+
|
|
2012
|
+
@wrapped
|
|
2013
|
+
def dbgSetHwioEvent(self, index: int, trigger: int):
|
|
2014
|
+
""""""
|
|
2015
|
+
return self.transport.request(types.Command.DBG_SET_HWIO_EVENT, index, trigger)
|
|
2016
|
+
|
|
2017
|
+
@wrapped
|
|
2018
|
+
def dbgHwioControl(self, pins):
|
|
2019
|
+
""""""
|
|
2020
|
+
d = bytearray()
|
|
2021
|
+
d.extend(self.BYTE_pack(len(pins)))
|
|
2022
|
+
for p in pins:
|
|
2023
|
+
d.extend(self.BYTE_pack(p[0])) # index
|
|
2024
|
+
d.extend(self.BYTE_pack(p[1])) # state
|
|
2025
|
+
d.extend(self.WORD_pack(p[2])) # frequency
|
|
2026
|
+
|
|
2027
|
+
response = self.transport.request(types.Command.DBG_HWIO_CONTROL, *d)
|
|
2028
|
+
return types.DbgHwioControlResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2029
|
+
|
|
2030
|
+
@wrapped
|
|
2031
|
+
def dbgExclusiveTargetAccess(self, mode: int, context: int):
|
|
2032
|
+
""""""
|
|
2033
|
+
return self.transport.request(types.Command.DBG_EXCLUSIVE_TARGET_ACCESS, mode, context)
|
|
2034
|
+
|
|
2035
|
+
@wrapped
|
|
2036
|
+
def dbgSequenceMultiple(self, mode: int, num: int, *seq):
|
|
2037
|
+
""""""
|
|
2038
|
+
response = self.transport.request(types.Command.DBG_SEQUENCE_MULTIPLE, mode, self.WORD_pack(num), *seq)
|
|
2039
|
+
return types.DbgSequenceMultipleResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2040
|
+
|
|
2041
|
+
@wrapped
|
|
2042
|
+
def dbgLlt(self, num: int, mode: int, *llts):
|
|
2043
|
+
""""""
|
|
2044
|
+
response = self.transport.request(types.Command.DBG_LLT, num, mode, *llts)
|
|
2045
|
+
return types.DbgLltResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2046
|
+
|
|
2047
|
+
@wrapped
|
|
2048
|
+
def dbgReadModifyWrite(self, tri: int, width: int, address: int, mask: int, data: int):
|
|
2049
|
+
""""""
|
|
2050
|
+
d = bytearray()
|
|
2051
|
+
d.extend(b"\x00")
|
|
2052
|
+
d.append(tri)
|
|
2053
|
+
d.append(width)
|
|
2054
|
+
d.extend(b"\x00\x00")
|
|
2055
|
+
d.extend(self.DLONG_pack(address))
|
|
2056
|
+
if width == 0x01:
|
|
2057
|
+
d.extend(self.BYTE_pack(mask))
|
|
2058
|
+
d.extend(self.BYTE_pack(data))
|
|
2059
|
+
elif width == 0x02:
|
|
2060
|
+
d.extend(self.WORD_pack(mask))
|
|
2061
|
+
d.extend(self.WORD_pack(data))
|
|
2062
|
+
elif width == 0x04:
|
|
2063
|
+
d.extend(self.DWORD_pack(mask))
|
|
2064
|
+
d.extend(self.DWORD_pack(data))
|
|
2065
|
+
elif width == 0x08:
|
|
2066
|
+
d.extend(self.DLONG_pack(mask))
|
|
2067
|
+
d.extend(self.DLONG_pack(data))
|
|
2068
|
+
response = self.transport.request(types.Command.DBG_READ_MODIFY_WRITE, *d)
|
|
2069
|
+
return types.DbgReadModifyWriteResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
|
|
2070
|
+
|
|
2071
|
+
@wrapped
|
|
2072
|
+
def dbgWrite(self, tri: int, width: int, address: int, data):
|
|
2073
|
+
""""""
|
|
2074
|
+
d = bytearray()
|
|
2075
|
+
d.extend(b"\x00")
|
|
2076
|
+
d.append(tri)
|
|
2077
|
+
self._dbg_width = width
|
|
2078
|
+
d.append(width)
|
|
2079
|
+
d.extend(self.WORD_pack(len(data)))
|
|
2080
|
+
d.extend(self.DLONG_pack(address))
|
|
2081
|
+
for da in data:
|
|
2082
|
+
if width == 0x01:
|
|
2083
|
+
d.extend(self.BYTE_pack(da))
|
|
2084
|
+
elif width == 0x02:
|
|
2085
|
+
d.extend(self.WORD_pack(da))
|
|
2086
|
+
elif width == 0x04:
|
|
2087
|
+
d.extend(self.DWORD_pack(da))
|
|
2088
|
+
elif width == 0x08:
|
|
2089
|
+
d.extend(self.DLONG_pack(da))
|
|
2090
|
+
return self.transport.request(types.Command.DBG_WRITE, *d)
|
|
2091
|
+
|
|
2092
|
+
@wrapped
|
|
2093
|
+
def dbgWriteNext(self, num: int, data: int):
|
|
2094
|
+
""""""
|
|
2095
|
+
d = bytearray()
|
|
2096
|
+
d.extend(b"\x00")
|
|
2097
|
+
d.extend(self.WORD_pack(num))
|
|
2098
|
+
d.extend(b"\x00\x00")
|
|
2099
|
+
for i in range(num):
|
|
2100
|
+
if self._dbg_width == 0x01:
|
|
2101
|
+
d.extend(self.BYTE_pack(data[i]))
|
|
2102
|
+
elif self._dbg_width == 0x02:
|
|
2103
|
+
d.extend(self.WORD_pack(data[i]))
|
|
2104
|
+
elif self._dbg_width == 0x04:
|
|
2105
|
+
d.extend(self.DWORD_pack(data[i]))
|
|
2106
|
+
elif self._dbg_width == 0x08:
|
|
2107
|
+
d.extend(self.DLONG_pack(data[i]))
|
|
2108
|
+
return self.transport.request(types.Command.DBG_WRITE_NEXT, *d)
|
|
2109
|
+
|
|
2110
|
+
@wrapped
|
|
2111
|
+
def dbgWriteCan1(self, tri: int, address: int):
|
|
2112
|
+
""""""
|
|
2113
|
+
d = bytearray()
|
|
2114
|
+
d.extend(self.BYTE_pack(tri))
|
|
2115
|
+
d.extend(self.DWORD_pack(address))
|
|
2116
|
+
return self.transport.request(types.Command.DBG_WRITE_CAN1, *d)
|
|
2117
|
+
|
|
2118
|
+
@wrapped
|
|
2119
|
+
def dbgWriteCan2(self, width: int, num: int):
|
|
2120
|
+
""""""
|
|
2121
|
+
d = bytearray()
|
|
2122
|
+
self._dbg_width = width
|
|
2123
|
+
d.append(width)
|
|
2124
|
+
d.extend(self.BYTE_pack(num))
|
|
2125
|
+
return self.transport.request(types.Command.DBG_WRITE_CAN2, *d)
|
|
2126
|
+
|
|
2127
|
+
@wrapped
|
|
2128
|
+
def dbgWriteCanNext(self, num: int, data: int):
|
|
2129
|
+
""""""
|
|
2130
|
+
d = bytearray()
|
|
2131
|
+
d.extend(self.BYTE_pack(num))
|
|
2132
|
+
for i in range(num):
|
|
2133
|
+
if self._dbg_width == 0x01:
|
|
2134
|
+
d.extend(self.BYTE_pack(data[i]))
|
|
2135
|
+
elif self._dbg_width == 0x02:
|
|
2136
|
+
d.extend(self.WORD_pack(data[i]))
|
|
2137
|
+
elif self._dbg_width == 0x04:
|
|
2138
|
+
d.extend(self.DWORD_pack(data[i]))
|
|
2139
|
+
elif self._dbg_width == 0x08:
|
|
2140
|
+
d.extend(self.DLONG_pack(data[i]))
|
|
2141
|
+
return self.transport.request(types.Command.DBG_WRITE_CAN_NEXT, *d)
|
|
2142
|
+
|
|
2143
|
+
@wrapped
|
|
2144
|
+
def dbgRead(self, tri: int, width: int, num: int, address: int):
|
|
2145
|
+
""""""
|
|
2146
|
+
d = bytearray()
|
|
2147
|
+
d.extend(b"\x00")
|
|
2148
|
+
d.extend(self.BYTE_pack(tri))
|
|
2149
|
+
self._dbg_width = width
|
|
2150
|
+
d.extend(self.BYTE_pack(width))
|
|
2151
|
+
d.extend(self.WORD_pack(num))
|
|
2152
|
+
d.extend(self.DLONG_pack(address))
|
|
2153
|
+
response = self.transport.request(types.Command.DBG_READ, *d)
|
|
2154
|
+
return types.DbgReadResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
|
|
2155
|
+
|
|
2156
|
+
@wrapped
|
|
2157
|
+
def dbgReadCan1(self, tri: int, address: int):
|
|
2158
|
+
""""""
|
|
2159
|
+
d = bytearray()
|
|
2160
|
+
d.extend(self.BYTE_pack(tri))
|
|
2161
|
+
d.extend(self.DWORD_pack(address))
|
|
2162
|
+
return self.transport.request(types.Command.DBG_READ_CAN1, *d)
|
|
2163
|
+
|
|
2164
|
+
@wrapped
|
|
2165
|
+
def dbgReadCan2(self, width: int, num: int):
|
|
2166
|
+
""""""
|
|
2167
|
+
d = bytearray()
|
|
2168
|
+
self._dbg_width = width
|
|
2169
|
+
d.extend(self.BYTE_pack(width))
|
|
2170
|
+
d.extend(self.BYTE_pack(num))
|
|
2171
|
+
return self.transport.request(types.Command.DBG_READ_CAN2, *d)
|
|
2172
|
+
|
|
2173
|
+
@wrapped
|
|
2174
|
+
def dbgGetTriDescTbl(self):
|
|
2175
|
+
""""""
|
|
2176
|
+
response = self.transport.request(types.Command.DBG_GET_TRI_DESC_TBL, b"\x00\x00\x00\x00\x00")
|
|
2177
|
+
return types.DbgGetTriDescTblResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2178
|
+
|
|
2179
|
+
@wrapped
|
|
2180
|
+
def dbgLlbt(self, data):
|
|
2181
|
+
""""""
|
|
2182
|
+
d = bytearray()
|
|
2183
|
+
d.extend(b"\x00")
|
|
2184
|
+
d.extend(self.WORD_pack(len(data)))
|
|
2185
|
+
for b in data:
|
|
2186
|
+
d.extend(self.BYTE_pack(b))
|
|
2187
|
+
response = self.transport.request(types.Command.DBG_LLBT, d)
|
|
2188
|
+
return types.DbgLlbtResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2189
|
+
|
|
2190
|
+
@wrapped
|
|
2191
|
+
def timeCorrelationProperties(self, set_properties: int, get_properties_request: int, cluster_id: int):
|
|
2192
|
+
response = self.transport.request(
|
|
2193
|
+
types.Command.TIME_CORRELATION_PROPERTIES, set_properties, get_properties_request, 0,
|
|
2194
|
+
*self.WORD_pack(cluster_id)
|
|
2195
|
+
)
|
|
2196
|
+
return types.TimeCorrelationPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2197
|
+
|
|
2198
|
+
# Transport layer commands / CAN.
|
|
2199
|
+
|
|
2200
|
+
@broadcasted
|
|
2201
|
+
@wrapped
|
|
2202
|
+
def getSlaveID(self, mode: int):
|
|
2203
|
+
response = self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, ord("X"), ord("C"), ord("P"), mode)
|
|
2204
|
+
return types.GetSlaveIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2205
|
+
|
|
2206
|
+
def getDaqId(self, daq_list_number: int):
|
|
2207
|
+
response = self.transportLayerCmd(types.TransportLayerCommands.GET_DAQ_ID, *self.WORD_pack(daq_list_number))
|
|
2208
|
+
# if response:
|
|
2209
|
+
return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
|
|
2210
|
+
|
|
2211
|
+
def setDaqId(self, daq_list_number: int, identifier: int):
|
|
2212
|
+
response = self.transportLayerCmd(
|
|
2213
|
+
types.TransportLayerCommands.SET_DAQ_ID, *self.WORD_pack(daq_list_number), *self.DWORD_pack(identifier)
|
|
2214
|
+
)
|
|
2215
|
+
return response
|
|
2216
|
+
|
|
2217
|
+
# Convenience Functions.
|
|
2218
|
+
def verify(self, addr, length):
|
|
2219
|
+
"""Convenience function for verification of a data-transfer from slave
|
|
2220
|
+
to master (Not part of the XCP Specification).
|
|
2221
|
+
|
|
2222
|
+
Parameters
|
|
2223
|
+
----------
|
|
2224
|
+
addr : int
|
|
2225
|
+
length : int
|
|
2226
|
+
|
|
2227
|
+
Returns
|
|
2228
|
+
-------
|
|
2229
|
+
bool
|
|
2230
|
+
"""
|
|
2231
|
+
self.setMta(addr)
|
|
2232
|
+
cs = self.buildChecksum(length)
|
|
2233
|
+
self.logger.debug(f"BuildChecksum return'd: 0x{cs.checksum:08X} [{cs.checksumType}]")
|
|
2234
|
+
self.setMta(addr)
|
|
2235
|
+
data = self.fetch(length)
|
|
2236
|
+
cc = checksum.check(data, cs.checksumType)
|
|
2237
|
+
self.logger.debug(f"Our checksum : 0x{cc:08X}")
|
|
2238
|
+
return cs.checksum == cc
|
|
2239
|
+
|
|
2240
|
+
def getDaqInfo(self, include_event_lists=True):
|
|
2241
|
+
"""Get DAQ information: processor, resolution, events."""
|
|
2242
|
+
result = {}
|
|
2243
|
+
dpi = self.getDaqProcessorInfo()
|
|
2244
|
+
processorInfo = {
|
|
2245
|
+
"minDaq": dpi["minDaq"],
|
|
2246
|
+
"maxDaq": dpi["maxDaq"],
|
|
2247
|
+
"properties": {
|
|
2248
|
+
"configType": dpi["daqProperties"]["daqConfigType"],
|
|
2249
|
+
"overloadEvent": dpi["daqProperties"]["overloadEvent"],
|
|
2250
|
+
"overloadMsb": dpi["daqProperties"]["overloadMsb"],
|
|
2251
|
+
"prescalerSupported": dpi["daqProperties"]["prescalerSupported"],
|
|
2252
|
+
"pidOffSupported": dpi["daqProperties"]["pidOffSupported"],
|
|
2253
|
+
"timestampSupported": dpi["daqProperties"]["timestampSupported"],
|
|
2254
|
+
"bitStimSupported": dpi["daqProperties"]["bitStimSupported"],
|
|
2255
|
+
"resumeSupported": dpi["daqProperties"]["resumeSupported"],
|
|
2256
|
+
},
|
|
2257
|
+
"keyByte": {
|
|
2258
|
+
"identificationField": dpi["daqKeyByte"]["Identification_Field"],
|
|
2259
|
+
"addressExtension": dpi["daqKeyByte"]["Address_Extension"],
|
|
2260
|
+
"optimisationType": dpi["daqKeyByte"]["Optimisation_Type"],
|
|
2261
|
+
},
|
|
2262
|
+
}
|
|
2263
|
+
result["processor"] = processorInfo
|
|
2264
|
+
|
|
2265
|
+
dri = self.getDaqResolutionInfo()
|
|
2266
|
+
resolutionInfo = {
|
|
2267
|
+
"timestampTicks": dri["timestampTicks"],
|
|
2268
|
+
"maxOdtEntrySizeDaq": dri["maxOdtEntrySizeDaq"],
|
|
2269
|
+
"maxOdtEntrySizeStim": dri["maxOdtEntrySizeStim"],
|
|
2270
|
+
"granularityOdtEntrySizeDaq": dri["granularityOdtEntrySizeDaq"],
|
|
2271
|
+
"granularityOdtEntrySizeStim": dri["granularityOdtEntrySizeStim"],
|
|
2272
|
+
"timestampMode": {
|
|
2273
|
+
"unit": dri["timestampMode"]["unit"],
|
|
2274
|
+
"fixed": dri["timestampMode"]["fixed"],
|
|
2275
|
+
"size": dri["timestampMode"]["size"],
|
|
2276
|
+
},
|
|
2277
|
+
}
|
|
2278
|
+
result["resolution"] = resolutionInfo
|
|
2279
|
+
channels = []
|
|
2280
|
+
daq_events = []
|
|
2281
|
+
if include_event_lists:
|
|
2282
|
+
for ecn in range(dpi.maxEventChannel):
|
|
2283
|
+
eci = self.getDaqEventInfo(ecn)
|
|
2284
|
+
cycle = eci["eventChannelTimeCycle"]
|
|
2285
|
+
maxDaqList = eci["maxDaqList"]
|
|
2286
|
+
priority = eci["eventChannelPriority"]
|
|
2287
|
+
time_unit = eci["eventChannelTimeUnit"]
|
|
2288
|
+
consistency = eci["daqEventProperties"]["consistency"]
|
|
2289
|
+
daq_supported = eci["daqEventProperties"]["daq"]
|
|
2290
|
+
stim_supported = eci["daqEventProperties"]["stim"]
|
|
2291
|
+
packed_supported = eci["daqEventProperties"]["packed"]
|
|
2292
|
+
name = self.fetch(eci.eventChannelNameLength)
|
|
2293
|
+
if name:
|
|
2294
|
+
name = decode_bytes(name)
|
|
2295
|
+
channel = {
|
|
2296
|
+
"name": name,
|
|
2297
|
+
"priority": eci["eventChannelPriority"],
|
|
2298
|
+
"unit": eci["eventChannelTimeUnit"],
|
|
2299
|
+
"cycle": eci["eventChannelTimeCycle"],
|
|
2300
|
+
"maxDaqList": eci["maxDaqList"],
|
|
2301
|
+
"properties": {
|
|
2302
|
+
"consistency": consistency,
|
|
2303
|
+
"daq": daq_supported,
|
|
2304
|
+
"stim": stim_supported,
|
|
2305
|
+
"packed": packed_supported,
|
|
2306
|
+
},
|
|
2307
|
+
}
|
|
2308
|
+
daq_event_info = DaqEventInfo(
|
|
2309
|
+
name,
|
|
2310
|
+
types.EVENT_CHANNEL_TIME_UNIT_TO_EXP[time_unit],
|
|
2311
|
+
cycle,
|
|
2312
|
+
maxDaqList,
|
|
2313
|
+
priority,
|
|
2314
|
+
consistency,
|
|
2315
|
+
daq_supported,
|
|
2316
|
+
stim_supported,
|
|
2317
|
+
packed_supported,
|
|
2318
|
+
)
|
|
2319
|
+
daq_events.append(daq_event_info)
|
|
2320
|
+
channels.append(channel)
|
|
2321
|
+
result["channels"] = channels
|
|
2322
|
+
self.stim.setDaqEventInfo(daq_events)
|
|
2323
|
+
return result
|
|
2324
|
+
|
|
2325
|
+
def getCurrentProtectionStatus(self):
|
|
2326
|
+
""""""
|
|
2327
|
+
if self.currentProtectionStatus is None:
|
|
2328
|
+
try:
|
|
2329
|
+
status = self.getStatus()
|
|
2330
|
+
except Exception as e: # may temporary ERR_OUT_OF_RANGE
|
|
2331
|
+
return {"dbg": None, "pgm": None, "stim": None, "daq": None, "calpag": None}
|
|
2332
|
+
self._setProtectionStatus(status.resourceProtectionStatus)
|
|
2333
|
+
return self.currentProtectionStatus
|
|
2334
|
+
|
|
2335
|
+
def _setProtectionStatus(self, protection):
|
|
2336
|
+
""""""
|
|
2337
|
+
self.currentProtectionStatus = {
|
|
2338
|
+
"dbg": protection.dbg,
|
|
2339
|
+
"pgm": protection.pgm,
|
|
2340
|
+
"stim": protection.stim,
|
|
2341
|
+
"daq": protection.daq,
|
|
2342
|
+
"calpag": protection.calpag,
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
def cond_unlock(self, resources=None):
|
|
2346
|
+
"""Conditionally unlock resources, i.e. only unlock locked resources.
|
|
2347
|
+
|
|
2348
|
+
Precondition: Parameter "SEED_N_KEY_DLL" must be present and point to a valid DLL/SO.
|
|
2349
|
+
|
|
2350
|
+
Parameters
|
|
2351
|
+
----------
|
|
2352
|
+
resources: str
|
|
2353
|
+
Comma or space separated list of resources, e.g. "DAQ, CALPAG".
|
|
2354
|
+
The names are not case-sensitive.
|
|
2355
|
+
Valid identifiers are: "calpag", "daq", "dbg", "pgm", "stim".
|
|
2356
|
+
|
|
2357
|
+
If omitted, try to unlock every available resource.
|
|
2358
|
+
|
|
2359
|
+
Raises
|
|
2360
|
+
------
|
|
2361
|
+
ValueError
|
|
2362
|
+
Invalid resource name.
|
|
2363
|
+
|
|
2364
|
+
`dllif.SeedNKeyError`
|
|
2365
|
+
In case of DLL related issues.
|
|
2366
|
+
"""
|
|
2367
|
+
import re
|
|
2368
|
+
|
|
2369
|
+
from pyxcp.dllif import SeedNKeyError, SeedNKeyResult, getKey
|
|
2370
|
+
|
|
2371
|
+
MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2
|
|
2372
|
+
|
|
2373
|
+
protection_status = self.getCurrentProtectionStatus()
|
|
2374
|
+
if any(protection_status.values()) and (not (self.seed_n_key_dll or self.seed_n_key_function)):
|
|
2375
|
+
raise RuntimeError(
|
|
2376
|
+
"Neither seed-and-key DLL nor function specified, cannot proceed.") # TODO: ConfigurationError
|
|
2377
|
+
if resources is None:
|
|
2378
|
+
result = []
|
|
2379
|
+
if self.slaveProperties["supportsCalpag"]:
|
|
2380
|
+
result.append("calpag")
|
|
2381
|
+
if self.slaveProperties["supportsDaq"]:
|
|
2382
|
+
result.append("daq")
|
|
2383
|
+
if self.slaveProperties["supportsStim"]:
|
|
2384
|
+
result.append("stim")
|
|
2385
|
+
if self.slaveProperties["supportsPgm"]:
|
|
2386
|
+
result.append("pgm")
|
|
2387
|
+
resources = ",".join(result)
|
|
2388
|
+
resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r]
|
|
2389
|
+
for name in resource_names:
|
|
2390
|
+
if name not in types.RESOURCE_VALUES:
|
|
2391
|
+
raise ValueError(f"Invalid resource name {name!r}.")
|
|
2392
|
+
if not protection_status[name]:
|
|
2393
|
+
continue
|
|
2394
|
+
resource_value = types.RESOURCE_VALUES[name]
|
|
2395
|
+
result = self.getSeed(types.XcpGetSeedMode.FIRST_PART, resource_value)
|
|
2396
|
+
seed = list(result.seed)
|
|
2397
|
+
length = result.length
|
|
2398
|
+
if length == 0:
|
|
2399
|
+
continue
|
|
2400
|
+
if length > MAX_PAYLOAD:
|
|
2401
|
+
remaining = length - len(seed)
|
|
2402
|
+
while remaining > 0:
|
|
2403
|
+
result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value)
|
|
2404
|
+
seed.extend(list(result.seed))
|
|
2405
|
+
remaining -= result.length
|
|
2406
|
+
self.logger.debug(f"Got seed {seed!r} for resource {resource_value!r}.")
|
|
2407
|
+
if self.seed_n_key_function:
|
|
2408
|
+
key = self.seed_n_key_function(resource_value, bytes(seed))
|
|
2409
|
+
self.logger.debug(f"Using seed and key function {self.seed_n_key_function.__name__!r}().")
|
|
2410
|
+
result = SeedNKeyResult.ACK
|
|
2411
|
+
elif self.seed_n_key_dll:
|
|
2412
|
+
self.logger.debug(f"Using seed and key DLL {self.seed_n_key_dll!r}.")
|
|
2413
|
+
result, key = getKey(
|
|
2414
|
+
self.logger,
|
|
2415
|
+
self.config.custom_dll_loader,
|
|
2416
|
+
self.seed_n_key_dll,
|
|
2417
|
+
resource_value,
|
|
2418
|
+
bytes(seed),
|
|
2419
|
+
self.seed_n_key_dll_same_bit_width,
|
|
2420
|
+
)
|
|
2421
|
+
if result == SeedNKeyResult.ACK:
|
|
2422
|
+
key = list(key)
|
|
2423
|
+
self.logger.debug(f"Unlocking resource {resource_value!r} with key {key!r}.")
|
|
2424
|
+
remaining = len(key)
|
|
2425
|
+
while key:
|
|
2426
|
+
data = key[:MAX_PAYLOAD]
|
|
2427
|
+
key_len = len(data)
|
|
2428
|
+
self.unlock(remaining, data)
|
|
2429
|
+
key = key[MAX_PAYLOAD:]
|
|
2430
|
+
remaining -= key_len
|
|
2431
|
+
else:
|
|
2432
|
+
raise SeedNKeyError(f"SeedAndKey DLL returned: {SeedNKeyResult(result).name!r}")
|
|
2433
|
+
|
|
2434
|
+
def identifier(self, id_value: int) -> str:
|
|
2435
|
+
"""Return the identifier for the given value.
|
|
2436
|
+
Use this method instead of calling `getId()` directly.
|
|
2437
|
+
|
|
2438
|
+
Parameters
|
|
2439
|
+
----------
|
|
2440
|
+
id_value: int
|
|
2441
|
+
For standard identifiers, use the constants from `pyxcp.types.XcpGetIdType`.
|
|
2442
|
+
|
|
2443
|
+
Returns
|
|
2444
|
+
-------
|
|
2445
|
+
str
|
|
2446
|
+
"""
|
|
2447
|
+
gid = self.getId(id_value)
|
|
2448
|
+
if (gid.mode & 0x01) == 0x01:
|
|
2449
|
+
value = bytes(gid.identification or b"")
|
|
2450
|
+
else:
|
|
2451
|
+
value = self.fetch(gid.length)
|
|
2452
|
+
return decode_bytes(value)
|
|
2453
|
+
|
|
2454
|
+
def id_scanner(self, scan_ranges: Collection[Collection[int]] | None = None) -> dict[str, str]:
|
|
2455
|
+
"""Scan for available standard identification types (GET_ID).
|
|
2456
|
+
|
|
2457
|
+
Parameters
|
|
2458
|
+
----------
|
|
2459
|
+
scan_ranges: Optional[Collection[Collection[int]]]
|
|
2460
|
+
|
|
2461
|
+
- If parameter is omitted or `None` test every standard identification type (s. GET_ID service)
|
|
2462
|
+
plus extensions by Vector Informatik.
|
|
2463
|
+
- Else `scan_ranges` must be a list-of-list.
|
|
2464
|
+
e.g: [[12, 80], [123], [240, 16, 35]]
|
|
2465
|
+
- The first list is a range (closed interval).
|
|
2466
|
+
- The second is a single value.
|
|
2467
|
+
- The third is a value list.
|
|
2468
|
+
|
|
2469
|
+
Returns
|
|
2470
|
+
-------
|
|
2471
|
+
Dict[str, str]
|
|
2472
|
+
|
|
2473
|
+
"""
|
|
2474
|
+
result = {}
|
|
2475
|
+
|
|
2476
|
+
def make_generator(sr):
|
|
2477
|
+
STD_IDS = {int(v): k for k, v in types.XcpGetIdType.__members__.items()}
|
|
2478
|
+
if sr is None:
|
|
2479
|
+
scan_range = STD_IDS.keys()
|
|
2480
|
+
else:
|
|
2481
|
+
scan_range = []
|
|
2482
|
+
if not isinstance(sr, Collection):
|
|
2483
|
+
raise TypeError("scan_ranges must be of type `Collection`")
|
|
2484
|
+
for element in sr:
|
|
2485
|
+
if not isinstance(element, Collection):
|
|
2486
|
+
raise TypeError("scan_ranges elements must be of type `Collection`")
|
|
2487
|
+
if not element:
|
|
2488
|
+
raise ValueError("scan_ranges elements cannot be empty")
|
|
2489
|
+
if len(element) == 1:
|
|
2490
|
+
scan_range.append(element[0]) # Single value
|
|
2491
|
+
elif len(element) == 2:
|
|
2492
|
+
start, stop = element # Value range
|
|
2493
|
+
scan_range.extend(list(range(start, stop + 1)))
|
|
2494
|
+
else:
|
|
2495
|
+
scan_range.extend(element) # Value list.
|
|
2496
|
+
scan_range = sorted(frozenset(scan_range))
|
|
2497
|
+
|
|
2498
|
+
def generate():
|
|
2499
|
+
for idx, id_value in enumerate(scan_range):
|
|
2500
|
+
if id_value in STD_IDS:
|
|
2501
|
+
name = STD_IDS[id_value]
|
|
2502
|
+
else:
|
|
2503
|
+
name = f"USER_{idx}"
|
|
2504
|
+
yield id_value, name,
|
|
2505
|
+
|
|
2506
|
+
return generate()
|
|
2507
|
+
|
|
2508
|
+
gen = make_generator(scan_ranges)
|
|
2509
|
+
for id_value, name in gen:
|
|
2510
|
+
# Avoid noisy warnings while probing
|
|
2511
|
+
status, response = self.try_command(self.identifier, id_value, silent=True)
|
|
2512
|
+
if status == types.TryCommandResult.OK and response:
|
|
2513
|
+
result[name] = response
|
|
2514
|
+
continue
|
|
2515
|
+
if status == types.TryCommandResult.NOT_IMPLEMENTED:
|
|
2516
|
+
# GET_ID not supported by the slave at all → stop scanning
|
|
2517
|
+
break
|
|
2518
|
+
if status == types.TryCommandResult.XCP_ERROR:
|
|
2519
|
+
# Some IDs may not be supported; ignore typical probe errors
|
|
2520
|
+
try:
|
|
2521
|
+
err = response.error_code
|
|
2522
|
+
except Exception:
|
|
2523
|
+
err = None
|
|
2524
|
+
if err in (types.XcpError.ERR_OUT_OF_RANGE, types.XcpError.ERR_CMD_SYNTAX):
|
|
2525
|
+
continue
|
|
2526
|
+
# For any other XCP error, keep scanning (best-effort) instead of aborting
|
|
2527
|
+
continue
|
|
2528
|
+
if status == types.TryCommandResult.OTHER_ERROR:
|
|
2529
|
+
raise RuntimeError(f"Error while scanning for ID {id_value}: {response!r}")
|
|
2530
|
+
return result
|
|
2531
|
+
|
|
2532
|
+
@property
|
|
2533
|
+
def start_datetime(self) -> int:
|
|
2534
|
+
""""""
|
|
2535
|
+
return self.transport.start_datetime
|
|
2536
|
+
|
|
2537
|
+
def try_command(self, cmd: Callable, *args, **kws) -> tuple[types.TryCommandResult, Any]:
|
|
2538
|
+
"""Call master functions and handle XCP errors more gracefuly.
|
|
2539
|
+
|
|
2540
|
+
Parameter
|
|
2541
|
+
---------
|
|
2542
|
+
cmd: Callable
|
|
2543
|
+
args: list
|
|
2544
|
+
variable length arguments to `cmd`.
|
|
2545
|
+
kws: dict
|
|
2546
|
+
keyword arguments to `cmd`.
|
|
2547
|
+
|
|
2548
|
+
`extra_msg`: str
|
|
2549
|
+
Additional info to log message (not passed to `cmd`).
|
|
2550
|
+
|
|
2551
|
+
Returns
|
|
2552
|
+
-------
|
|
2553
|
+
|
|
2554
|
+
Note
|
|
2555
|
+
----
|
|
2556
|
+
Mainly used for plug-and-play applications, e.g. `id_scanner` may confronted with `ERR_OUT_OF_RANGE` errors, which
|
|
2557
|
+
is normal for this kind of applications -- or to test for optional commands.
|
|
2558
|
+
Use carefuly not to hide serious error causes.
|
|
2559
|
+
"""
|
|
2560
|
+
# Suppress logging of expected XCP negative responses during try_command
|
|
2561
|
+
_prev_suppress = is_suppress_xcp_error_log()
|
|
2562
|
+
set_suppress_xcp_error_log(True)
|
|
2563
|
+
try:
|
|
2564
|
+
extra_msg: str | None = kws.get("extra_msg")
|
|
2565
|
+
if extra_msg:
|
|
2566
|
+
kws.pop("extra_msg")
|
|
2567
|
+
else:
|
|
2568
|
+
extra_msg = ""
|
|
2569
|
+
silent: bool | None = kws.get("silent")
|
|
2570
|
+
if silent:
|
|
2571
|
+
kws.pop("silent")
|
|
2572
|
+
else:
|
|
2573
|
+
silent = False
|
|
2574
|
+
res = cmd(*args, **kws)
|
|
2575
|
+
except SystemExit as e:
|
|
2576
|
+
# restore suppression flag before handling
|
|
2577
|
+
set_suppress_xcp_error_log(_prev_suppress)
|
|
2578
|
+
# print(f"\tUnexpected error while executing command {cmd.__name__!r}: {e!r}")
|
|
2579
|
+
if e.error_code == types.XcpError.ERR_CMD_UNKNOWN:
|
|
2580
|
+
# This is a rather common use-case, so let the user know that there is some functionality missing.
|
|
2581
|
+
if not silent:
|
|
2582
|
+
if extra_msg:
|
|
2583
|
+
self.logger.warning(f"Optional command {cmd.__name__!r} not implemented -- {extra_msg!r}")
|
|
2584
|
+
else:
|
|
2585
|
+
self.logger.warning(f"Optional command {cmd.__name__!r} not implemented.")
|
|
2586
|
+
return (types.TryCommandResult.NOT_IMPLEMENTED, e)
|
|
2587
|
+
else:
|
|
2588
|
+
return (types.TryCommandResult.XCP_ERROR, e)
|
|
2589
|
+
except Exception as e:
|
|
2590
|
+
return (types.TryCommandResult.OTHER_ERROR, e)
|
|
2591
|
+
else:
|
|
2592
|
+
return (types.TryCommandResult.OK, res)
|
|
2593
|
+
finally:
|
|
2594
|
+
# Ensure suppression flag is restored even on success/other exceptions
|
|
2595
|
+
with suppress(Exception):
|
|
2596
|
+
set_suppress_xcp_error_log(_prev_suppress)
|
|
2597
|
+
|
|
2598
|
+
|
|
2599
|
+
def ticks_to_seconds(ticks, resolution):
|
|
2600
|
+
"""Convert DAQ timestamp/tick value to seconds.
|
|
2601
|
+
|
|
2602
|
+
Parameters
|
|
2603
|
+
----------
|
|
2604
|
+
ticks: int
|
|
2605
|
+
|
|
2606
|
+
unit: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
|
|
2607
|
+
"""
|
|
2608
|
+
warnings.warn(
|
|
2609
|
+
"ticks_to_seconds() deprecated, use factory :func:`make_tick_converter` instead.",
|
|
2610
|
+
Warning,
|
|
2611
|
+
stacklevel=1,
|
|
2612
|
+
)
|
|
2613
|
+
return (10 ** types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]) * resolution.timestampTicks * ticks
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def make_tick_converter(resolution):
|
|
2617
|
+
"""Make a function that converts tick count from XCP slave to seconds.
|
|
2618
|
+
|
|
2619
|
+
Parameters
|
|
2620
|
+
----------
|
|
2621
|
+
resolution: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
|
|
2622
|
+
|
|
2623
|
+
"""
|
|
2624
|
+
exponent = types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]
|
|
2625
|
+
tick_resolution = resolution.timestampTicks
|
|
2626
|
+
base = (10 ** exponent) * tick_resolution
|
|
2627
|
+
|
|
2628
|
+
def ticks_to_seconds(ticks):
|
|
2629
|
+
"""Convert DAQ timestamp/tick value to seconds.
|
|
2630
|
+
|
|
2631
|
+
Parameters
|
|
2632
|
+
----------
|
|
2633
|
+
ticks: int
|
|
2634
|
+
|
|
2635
|
+
Returns
|
|
2636
|
+
-------
|
|
2637
|
+
float
|
|
2638
|
+
"""
|
|
2639
|
+
return base * ticks
|
|
2640
|
+
|
|
2641
|
+
return ticks_to_seconds
|