pyxcp 0.22.21__cp39-cp39-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (128) hide show
  1. pyxcp/__init__.py +20 -0
  2. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  3. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  4. pyxcp/aml/XCP_Common.aml +408 -0
  5. pyxcp/aml/XCPonCAN.aml +78 -0
  6. pyxcp/aml/XCPonEth.aml +33 -0
  7. pyxcp/aml/XCPonFlx.aml +113 -0
  8. pyxcp/aml/XCPonSxI.aml +66 -0
  9. pyxcp/aml/XCPonUSB.aml +106 -0
  10. pyxcp/aml/ifdata_CAN.a2l +20 -0
  11. pyxcp/aml/ifdata_Eth.a2l +11 -0
  12. pyxcp/aml/ifdata_Flx.a2l +94 -0
  13. pyxcp/aml/ifdata_SxI.a2l +13 -0
  14. pyxcp/aml/ifdata_USB.a2l +81 -0
  15. pyxcp/asam/__init__.py +0 -0
  16. pyxcp/asam/types.py +131 -0
  17. pyxcp/asamkeydll.c +116 -0
  18. pyxcp/asamkeydll.sh +2 -0
  19. pyxcp/checksum.py +722 -0
  20. pyxcp/cmdline.py +52 -0
  21. pyxcp/config/__init__.py +1100 -0
  22. pyxcp/config/legacy.py +120 -0
  23. pyxcp/constants.py +47 -0
  24. pyxcp/cpp_ext/__init__.py +0 -0
  25. pyxcp/cpp_ext/bin.hpp +104 -0
  26. pyxcp/cpp_ext/blockmem.hpp +58 -0
  27. pyxcp/cpp_ext/cpp_ext.cpython-39-x86_64-linux-gnu.so +0 -0
  28. pyxcp/cpp_ext/daqlist.hpp +200 -0
  29. pyxcp/cpp_ext/event.hpp +67 -0
  30. pyxcp/cpp_ext/extension_wrapper.cpp +96 -0
  31. pyxcp/cpp_ext/helper.hpp +280 -0
  32. pyxcp/cpp_ext/mcobject.hpp +246 -0
  33. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  34. pyxcp/daq_stim/__init__.py +226 -0
  35. pyxcp/daq_stim/optimize/__init__.py +67 -0
  36. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  37. pyxcp/daq_stim/scheduler.cpp +28 -0
  38. pyxcp/daq_stim/scheduler.hpp +75 -0
  39. pyxcp/daq_stim/stim.cpp +13 -0
  40. pyxcp/daq_stim/stim.cpython-39-x86_64-linux-gnu.so +0 -0
  41. pyxcp/daq_stim/stim.hpp +604 -0
  42. pyxcp/daq_stim/stim_wrapper.cpp +48 -0
  43. pyxcp/dllif.py +95 -0
  44. pyxcp/errormatrix.py +878 -0
  45. pyxcp/examples/conf_can.toml +19 -0
  46. pyxcp/examples/conf_can_user.toml +16 -0
  47. pyxcp/examples/conf_can_vector.json +11 -0
  48. pyxcp/examples/conf_can_vector.toml +11 -0
  49. pyxcp/examples/conf_eth.toml +9 -0
  50. pyxcp/examples/conf_nixnet.json +20 -0
  51. pyxcp/examples/conf_socket_can.toml +12 -0
  52. pyxcp/examples/conf_sxi.json +9 -0
  53. pyxcp/examples/conf_sxi.toml +7 -0
  54. pyxcp/examples/ex_arrow.py +109 -0
  55. pyxcp/examples/ex_csv.py +85 -0
  56. pyxcp/examples/ex_excel.py +95 -0
  57. pyxcp/examples/ex_mdf.py +124 -0
  58. pyxcp/examples/ex_sqlite.py +128 -0
  59. pyxcp/examples/run_daq.py +148 -0
  60. pyxcp/examples/xcp_policy.py +60 -0
  61. pyxcp/examples/xcp_read_benchmark.py +38 -0
  62. pyxcp/examples/xcp_skel.py +49 -0
  63. pyxcp/examples/xcp_unlock.py +38 -0
  64. pyxcp/examples/xcp_user_supplied_driver.py +54 -0
  65. pyxcp/examples/xcphello.py +79 -0
  66. pyxcp/examples/xcphello_recorder.py +107 -0
  67. pyxcp/master/__init__.py +9 -0
  68. pyxcp/master/errorhandler.py +436 -0
  69. pyxcp/master/master.py +2029 -0
  70. pyxcp/py.typed +0 -0
  71. pyxcp/recorder/__init__.py +102 -0
  72. pyxcp/recorder/build_clang.cmd +1 -0
  73. pyxcp/recorder/build_clang.sh +2 -0
  74. pyxcp/recorder/build_gcc.cmd +1 -0
  75. pyxcp/recorder/build_gcc.sh +2 -0
  76. pyxcp/recorder/build_gcc_arm.sh +2 -0
  77. pyxcp/recorder/converter/__init__.py +37 -0
  78. pyxcp/recorder/lz4.c +2829 -0
  79. pyxcp/recorder/lz4.h +879 -0
  80. pyxcp/recorder/lz4hc.c +2041 -0
  81. pyxcp/recorder/lz4hc.h +413 -0
  82. pyxcp/recorder/mio.hpp +1714 -0
  83. pyxcp/recorder/reader.hpp +139 -0
  84. pyxcp/recorder/reco.py +277 -0
  85. pyxcp/recorder/recorder.rst +0 -0
  86. pyxcp/recorder/rekorder.cpp +59 -0
  87. pyxcp/recorder/rekorder.cpython-39-x86_64-linux-gnu.so +0 -0
  88. pyxcp/recorder/rekorder.hpp +274 -0
  89. pyxcp/recorder/setup.py +41 -0
  90. pyxcp/recorder/test_reko.py +34 -0
  91. pyxcp/recorder/unfolder.hpp +1332 -0
  92. pyxcp/recorder/wrap.cpp +189 -0
  93. pyxcp/recorder/writer.hpp +302 -0
  94. pyxcp/scripts/__init__.py +0 -0
  95. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  96. pyxcp/scripts/xcp_examples.py +64 -0
  97. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  98. pyxcp/scripts/xcp_id_scanner.py +19 -0
  99. pyxcp/scripts/xcp_info.py +109 -0
  100. pyxcp/scripts/xcp_profile.py +27 -0
  101. pyxcp/stim/__init__.py +0 -0
  102. pyxcp/tests/test_asam_types.py +24 -0
  103. pyxcp/tests/test_binpacking.py +186 -0
  104. pyxcp/tests/test_can.py +1324 -0
  105. pyxcp/tests/test_checksum.py +95 -0
  106. pyxcp/tests/test_daq.py +193 -0
  107. pyxcp/tests/test_frame_padding.py +156 -0
  108. pyxcp/tests/test_master.py +2006 -0
  109. pyxcp/tests/test_transport.py +64 -0
  110. pyxcp/tests/test_utils.py +30 -0
  111. pyxcp/timing.py +60 -0
  112. pyxcp/transport/__init__.py +10 -0
  113. pyxcp/transport/base.py +440 -0
  114. pyxcp/transport/base_transport.hpp +0 -0
  115. pyxcp/transport/can.py +443 -0
  116. pyxcp/transport/eth.py +219 -0
  117. pyxcp/transport/sxi.py +133 -0
  118. pyxcp/transport/transport_wrapper.cpp +0 -0
  119. pyxcp/transport/usb_transport.py +213 -0
  120. pyxcp/types.py +993 -0
  121. pyxcp/utils.py +102 -0
  122. pyxcp/vector/__init__.py +0 -0
  123. pyxcp/vector/map.py +82 -0
  124. pyxcp-0.22.21.dist-info/LICENSE +165 -0
  125. pyxcp-0.22.21.dist-info/METADATA +107 -0
  126. pyxcp-0.22.21.dist-info/RECORD +128 -0
  127. pyxcp-0.22.21.dist-info/WHEEL +6 -0
  128. pyxcp-0.22.21.dist-info/entry_points.txt +8 -0
pyxcp/master/master.py ADDED
@@ -0,0 +1,2029 @@
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
+ import functools
10
+ import struct
11
+ import traceback
12
+ import warnings
13
+ from typing import Any, Callable, Collection, Dict, List, Optional, Tuple
14
+
15
+ from pyxcp import checksum, types
16
+ from pyxcp.constants import (
17
+ makeBytePacker,
18
+ makeByteUnpacker,
19
+ makeDLongPacker,
20
+ makeDLongUnpacker,
21
+ makeDWordPacker,
22
+ makeDWordUnpacker,
23
+ makeWordPacker,
24
+ makeWordUnpacker,
25
+ )
26
+ from pyxcp.daq_stim.stim import DaqEventInfo, Stim
27
+ from pyxcp.master.errorhandler import SystemExit, disable_error_handling, wrapped
28
+ from pyxcp.transport.base import create_transport
29
+ from pyxcp.utils import decode_bytes, delay, short_sleep
30
+
31
+
32
+ def broadcasted(func: Callable):
33
+ """"""
34
+ return func
35
+
36
+
37
+ class SlaveProperties(dict):
38
+ """Container class for fixed parameters, like byte-order, maxCTO, ..."""
39
+
40
+ def __init__(self, *args, **kws):
41
+ super().__init__(*args, **kws)
42
+
43
+ def __getattr__(self, name):
44
+ return self[name]
45
+
46
+ def __setattr__(self, name, value):
47
+ self[name] = value
48
+
49
+ def __getstate__(self):
50
+ return self
51
+
52
+ def __setstate__(self, state):
53
+ self = state # noqa: F841
54
+
55
+
56
+ class Master:
57
+ """Common part of lowlevel XCP API.
58
+
59
+ Parameters
60
+ ----------
61
+ transport_name : str
62
+ XCP transport layer name ['can', 'eth', 'sxi']
63
+ config: dict
64
+ """
65
+
66
+ def __init__(self, transport_name: Optional[str], config, policy=None, transport_layer_interface=None):
67
+ if transport_name is None:
68
+ raise ValueError("No transport-layer selected") # Never reached -- to keep type-checkers happy.
69
+ self.ctr = 0
70
+ self.succeeded = True
71
+ self.config = config.general
72
+ self.logger = config.log
73
+ disable_error_handling(self.config.disable_error_handling)
74
+ self.transport_name = transport_name.lower()
75
+ transport_config = config.transport
76
+ self.transport = create_transport(transport_name, transport_config, policy, transport_layer_interface)
77
+ self.stim = Stim(self.config.stim_support)
78
+ self.stim.clear()
79
+ self.stim.set_policy_feeder(self.transport.policy.feed)
80
+ self.stim.set_frame_sender(self.transport.block_request)
81
+
82
+ # In some cases the transport-layer needs to communicate with us.
83
+ self.transport.parent = self
84
+ self.service = None
85
+
86
+ # Policies may issue XCP commands on there own.
87
+ self.transport.policy.xcp_master = self
88
+
89
+ # (D)Word (un-)packers are byte-order dependent
90
+ # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
91
+ self.BYTE_pack = None
92
+ self.BYTE_unpack = None
93
+ self.WORD_pack = None
94
+ self.WORD_unpack = None
95
+ self.DWORD_pack = None
96
+ self.DWORD_unpack = None
97
+ self.DLONG_pack = None
98
+ self.DLONG_unpack = None
99
+ self.AG_pack = None
100
+ self.AG_unpack = None
101
+ # self.connected = False
102
+ self.mta = types.MtaType(None, None)
103
+ self.currentDaqPtr = None
104
+ self.currentProtectionStatus = None
105
+ self.seed_n_key_dll = self.config.seed_n_key_dll
106
+ self.seed_n_key_function = self.config.seed_n_key_function
107
+ self.seed_n_key_dll_same_bit_width = self.config.seed_n_key_dll_same_bit_width
108
+ self.disconnect_response_optional = self.config.disconnect_response_optional
109
+ self.slaveProperties = SlaveProperties()
110
+ self.slaveProperties.pgmProcessor = SlaveProperties()
111
+ self.slaveProperties.transport_layer = self.transport_name.upper()
112
+
113
+ def __enter__(self):
114
+ """Context manager entry part."""
115
+ return self
116
+
117
+ def __exit__(self, exc_type, exc_val, exc_tb):
118
+ """Context manager exit part."""
119
+ # if self.connected:
120
+ # self.disconnect()
121
+ self.close()
122
+ if exc_type is None:
123
+ return
124
+ else:
125
+ self.succeeded = False
126
+ # print("=" * 79)
127
+ # print("Exception while in Context-Manager:\n")
128
+ self.logger.error("".join(traceback.format_exception(exc_type, exc_val, exc_tb)))
129
+ # print("=" * 79)
130
+ # return True
131
+
132
+ def _setService(self, service):
133
+ """Records the currently processed service.
134
+
135
+ Parameters
136
+ ----------
137
+ service: `pydbc.types.Command`
138
+
139
+ Note
140
+ ----
141
+ Internal Function, only to be used by transport-layer.
142
+ """
143
+ self.service = service
144
+
145
+ def close(self):
146
+ """Closes transport layer connection."""
147
+ self.transport.policy.finalize()
148
+ self.transport.close()
149
+
150
+ # Mandatory Commands.
151
+ @wrapped
152
+ def connect(self, mode=0x00):
153
+ """Build up connection to an XCP slave.
154
+
155
+ Before the actual XCP traffic starts a connection is required.
156
+
157
+ Parameters
158
+ ----------
159
+ mode : int
160
+ connection mode; default is 0x00 (normal mode)
161
+
162
+ Returns
163
+ -------
164
+ :py:obj:`pyxcp.types.ConnectResponse`
165
+ Describes fundamental client properties.
166
+
167
+ Note
168
+ ----
169
+ Every XCP slave supports at most one connection,
170
+ more attempts to connect are silently ignored.
171
+
172
+ """
173
+ self.transport.connect()
174
+
175
+ response = self.transport.request(types.Command.CONNECT, mode & 0xFF)
176
+
177
+ # First get byte-order
178
+ resultPartial = types.ConnectResponsePartial.parse(response)
179
+ byteOrder = resultPartial.commModeBasic.byteOrder
180
+
181
+ result = types.ConnectResponse.parse(response, byteOrder=byteOrder)
182
+ byteOrderPrefix = "<" if byteOrder == types.ByteOrder.INTEL else ">"
183
+ self.slaveProperties.byteOrder = byteOrder
184
+ self.slaveProperties.maxCto = result.maxCto
185
+ self.slaveProperties.maxDto = result.maxDto
186
+ self.slaveProperties.supportsPgm = result.resource.pgm
187
+ self.slaveProperties.supportsStim = result.resource.stim
188
+ self.slaveProperties.supportsDaq = result.resource.daq
189
+ self.slaveProperties.supportsCalpag = result.resource.calpag
190
+ self.slaveProperties.slaveBlockMode = result.commModeBasic.slaveBlockMode
191
+ self.slaveProperties.addressGranularity = result.commModeBasic.addressGranularity
192
+ self.slaveProperties.protocolLayerVersion = result.protocolLayerVersion
193
+ self.slaveProperties.transportLayerVersion = result.transportLayerVersion
194
+ self.slaveProperties.optionalCommMode = result.commModeBasic.optional
195
+ self.slaveProperties.maxWriteDaqMultipleElements = (
196
+ 0 if self.slaveProperties.maxCto < 10 else int((self.slaveProperties.maxCto - 2) // 8)
197
+ )
198
+ self.BYTE_pack = makeBytePacker(byteOrderPrefix)
199
+ self.BYTE_unpack = makeByteUnpacker(byteOrderPrefix)
200
+ self.WORD_pack = makeWordPacker(byteOrderPrefix)
201
+ self.WORD_unpack = makeWordUnpacker(byteOrderPrefix)
202
+ self.DWORD_pack = makeDWordPacker(byteOrderPrefix)
203
+ self.DWORD_unpack = makeDWordUnpacker(byteOrderPrefix)
204
+ self.DLONG_pack = makeDLongPacker(byteOrderPrefix)
205
+ self.DLONG_unpack = makeDLongUnpacker(byteOrderPrefix)
206
+ self.slaveProperties.bytesPerElement = None # Download/Upload commands are using element- not byte-count.
207
+ if self.slaveProperties.addressGranularity == types.AddressGranularity.BYTE:
208
+ self.AG_pack = struct.Struct("<B").pack
209
+ self.AG_unpack = struct.Struct("<B").unpack
210
+ self.slaveProperties.bytesPerElement = 1
211
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
212
+ self.AG_pack = self.WORD_pack
213
+ self.AG_unpack = self.WORD_unpack
214
+ self.slaveProperties.bytesPerElement = 2
215
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
216
+ self.AG_pack = self.DWORD_pack
217
+ self.AG_unpack = self.DWORD_unpack
218
+ self.slaveProperties.bytesPerElement = 4
219
+ # self.connected = True
220
+ return result
221
+
222
+ @wrapped
223
+ def disconnect(self):
224
+ """Releases the connection to the XCP slave.
225
+
226
+ Thereafter, no further communication with the slave is possible
227
+ (besides `connect`).
228
+
229
+
230
+ Note
231
+ -----
232
+ - If DISCONNECT is currently not possible, ERR_CMD_BUSY will be returned.
233
+ - While XCP spec. requires a response, this behavior can be made optional by adding
234
+ - `DISCONNECT_RESPONSE_OPTIONAL = true` (TOML)
235
+ - `"DISCONNECT_RESPONSE_OPTIONAL": true` (JSON)
236
+ to your configuration file.
237
+ """
238
+ if self.disconnect_response_optional:
239
+ response = self.transport.request_optional_response(types.Command.DISCONNECT)
240
+ else:
241
+ response = self.transport.request(types.Command.DISCONNECT)
242
+ # self.connected = False
243
+ return response
244
+
245
+ @wrapped
246
+ def getStatus(self):
247
+ """Get current status information of the slave device.
248
+
249
+ This includes the status of the resource protection, pending store
250
+ requests and the general status of data acquisition and stimulation.
251
+
252
+ Returns
253
+ -------
254
+ :obj:`pyxcp.types.GetStatusResponse`
255
+ """
256
+ response = self.transport.request(types.Command.GET_STATUS)
257
+ result = types.GetStatusResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
258
+ self._setProtectionStatus(result.resourceProtectionStatus)
259
+ return result
260
+
261
+ @wrapped
262
+ def synch(self):
263
+ """Synchronize command execution after timeout conditions."""
264
+ response = self.transport.request(types.Command.SYNCH)
265
+ return response
266
+
267
+ @wrapped
268
+ def getCommModeInfo(self):
269
+ """Get optional information on different Communication Modes supported
270
+ by the slave.
271
+
272
+ Returns
273
+ -------
274
+ :obj:`pyxcp.types.GetCommModeInfoResponse`
275
+ """
276
+ response = self.transport.request(types.Command.GET_COMM_MODE_INFO)
277
+ result = types.GetCommModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
278
+ self.slaveProperties.interleavedMode = result.commModeOptional.interleavedMode
279
+ self.slaveProperties.masterBlockMode = result.commModeOptional.masterBlockMode
280
+ self.slaveProperties.maxBs = result.maxBs
281
+ self.slaveProperties.minSt = result.minSt
282
+ self.slaveProperties.queueSize = result.queueSize
283
+ self.slaveProperties.xcpDriverVersionNumber = result.xcpDriverVersionNumber
284
+ return result
285
+
286
+ @wrapped
287
+ def getId(self, mode: int):
288
+ """This command is used for automatic session configuration and for
289
+ slave device identification.
290
+
291
+ Parameters
292
+ ----------
293
+ mode : int
294
+ The following identification types may be requested:
295
+ - 0 ASCII text
296
+ - 1 ASAM-MC2 filename without path and extension
297
+ - 2 ASAM-MC2 filename with path and extension
298
+ - 3 URL where the ASAM-MC2 file can be found
299
+ - 4 ASAM-MC2 file to upload
300
+ - 128..255 User defined
301
+
302
+ Returns
303
+ -------
304
+ :obj:`pydbc.types.GetIDResponse`
305
+ """
306
+ response = self.transport.request(types.Command.GET_ID, mode)
307
+ result = types.GetIDResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
308
+ result.length = self.DWORD_unpack(response[3:7])[0]
309
+ return result
310
+
311
+ @wrapped
312
+ def setRequest(self, mode: int, sessionConfigurationId: int):
313
+ """Request to save to non-volatile memory.
314
+
315
+ Parameters
316
+ ----------
317
+ mode : int (bitfield)
318
+ - 1 Request to store calibration data
319
+ - 2 Request to store DAQ list, no resume
320
+ - 4 Request to store DAQ list, resume enabled
321
+ - 8 Request to clear DAQ configuration
322
+ sessionConfigurationId : int
323
+
324
+ """
325
+ return self.transport.request(
326
+ types.Command.SET_REQUEST,
327
+ mode,
328
+ sessionConfigurationId >> 8,
329
+ sessionConfigurationId & 0xFF,
330
+ )
331
+
332
+ @wrapped
333
+ def getSeed(self, first: int, resource: int):
334
+ """Get seed from slave for unlocking a protected resource.
335
+
336
+ Parameters
337
+ ----------
338
+ first : int
339
+ - 0 - first part of seed
340
+ - 1 - remaining part
341
+ resource : int
342
+ - Mode = =0 - Resource
343
+ - Mode == 1 - Don't care
344
+
345
+ Returns
346
+ -------
347
+ `pydbc.types.GetSeedResponse`
348
+ """
349
+ if self.transport_name == "can":
350
+ # for CAN it might happen that the seed is longer than the max DLC
351
+ # in this case the first byte will be the current remaining seed size
352
+ # followed by the seeds bytes that can fit in the current frame
353
+ # the master must call getSeed several times until the complete seed is received
354
+ response = self.transport.request(types.Command.GET_SEED, first, resource)
355
+ size, seed = response[0], response[1:]
356
+ if size < len(seed):
357
+ seed = seed[:size]
358
+ reply = types.GetSeedResponse.parse(
359
+ types.GetSeedResponse.build({"length": size, "seed": bytes(size)}),
360
+ byteOrder=self.slaveProperties.byteOrder,
361
+ )
362
+ reply.seed = seed
363
+ return reply
364
+ else:
365
+ response = self.transport.request(types.Command.GET_SEED, first, resource)
366
+ return types.GetSeedResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
367
+
368
+ @wrapped
369
+ def unlock(self, length: int, key: bytes):
370
+ """Send key to slave for unlocking a protected resource.
371
+
372
+ Parameters
373
+ ----------
374
+ length : int
375
+ indicates the (remaining) number of key bytes.
376
+ key : bytes
377
+
378
+ Returns
379
+ -------
380
+ :obj:`pydbc.types.ResourceType`
381
+
382
+ Note
383
+ ----
384
+ The master has to use :meth:`unlock` in a defined sequence together
385
+ with :meth:`getSeed`. The master only can send an :meth:`unlock` sequence
386
+ if previously there was a :meth:`getSeed` sequence. The master has
387
+ to send the first `unlocking` after a :meth:`getSeed` sequence with
388
+ a Length containing the total length of the key.
389
+ """
390
+ response = self.transport.request(types.Command.UNLOCK, length, *key)
391
+ result = types.ResourceType.parse(response, byteOrder=self.slaveProperties.byteOrder)
392
+ self._setProtectionStatus(result)
393
+ return result
394
+
395
+ @wrapped
396
+ def setMta(self, address: int, addressExt: int = 0x00):
397
+ """Set Memory Transfer Address in slave.
398
+
399
+ Parameters
400
+ ----------
401
+ address : int
402
+ addressExt : int
403
+
404
+ Note
405
+ ----
406
+ The MTA is used by :meth:`buildChecksum`, :meth:`upload`, :meth:`download`, :meth:`downloadNext`,
407
+ :meth:`downloadMax`, :meth:`modifyBits`, :meth:`programClear`, :meth:`program`, :meth:`programNext`
408
+ and :meth:`programMax`.
409
+
410
+ """
411
+ self.mta = types.MtaType(address, addressExt) # Keep track of MTA (needed for error-handling).
412
+ addr = self.DWORD_pack(address)
413
+ return self.transport.request(types.Command.SET_MTA, 0, 0, addressExt, *addr)
414
+
415
+ @wrapped
416
+ def upload(self, length: int):
417
+ """Transfer data from slave to master.
418
+
419
+ Parameters
420
+ ----------
421
+ length : int
422
+ Number of elements (address granularity).
423
+
424
+ Note
425
+ ----
426
+ Adress is set via :meth:`setMta` (Some services like :meth:`getID` also set the MTA).
427
+
428
+ Returns
429
+ -------
430
+ bytes
431
+ """
432
+ byte_count = length * self.slaveProperties.bytesPerElement
433
+ response = self.transport.request(types.Command.UPLOAD, length)
434
+ if byte_count > (self.slaveProperties.maxCto - 1):
435
+ block_response = self.transport.block_receive(length_required=(byte_count - len(response)))
436
+ response += block_response
437
+ elif self.transport_name == "can":
438
+ # larger sizes will send in multiple CAN messages
439
+ # each valid message will start with 0xFF followed by the upload bytes
440
+ # the last message might be padded to the required DLC
441
+ rem = byte_count - len(response)
442
+ while rem:
443
+ if len(self.transport.resQueue):
444
+ data = self.transport.resQueue.popleft()
445
+ response += data[1 : rem + 1]
446
+ rem = byte_count - len(response)
447
+ else:
448
+ short_sleep()
449
+ return response
450
+
451
+ @wrapped
452
+ def shortUpload(self, length: int, address: int, addressExt: int = 0x00):
453
+ """Transfer data from slave to master.
454
+ As opposed to :meth:`upload` this service also includes address information.
455
+
456
+ Parameters
457
+ ----------
458
+ length : int
459
+ Number of elements (address granularity).
460
+ address : int
461
+ addressExt : int
462
+
463
+ Returns
464
+ -------
465
+ bytes
466
+ """
467
+ addr = self.DWORD_pack(address)
468
+ byte_count = length * self.slaveProperties.bytesPerElement
469
+ max_byte_count = self.slaveProperties.maxCto - 1
470
+ if byte_count > max_byte_count:
471
+ self.logger.warn(f"SHORT_UPLOAD: {byte_count} bytes exceeds the maximum value of {max_byte_count}.")
472
+ response = self.transport.request(types.Command.SHORT_UPLOAD, length, 0, addressExt, *addr)
473
+ return response[:byte_count]
474
+
475
+ @wrapped
476
+ def buildChecksum(self, blocksize: int):
477
+ """Build checksum over memory range.
478
+
479
+ Parameters
480
+ ----------
481
+ blocksize : int
482
+
483
+ Returns
484
+ -------
485
+ :obj:`~pyxcp.types.BuildChecksumResponse`
486
+
487
+ .. note:: Adress is set via `setMta`
488
+
489
+ See Also
490
+ --------
491
+ :mod:`~pyxcp.checksum`
492
+ """
493
+ bs = self.DWORD_pack(blocksize)
494
+ response = self.transport.request(types.Command.BUILD_CHECKSUM, 0, 0, 0, *bs)
495
+ return types.BuildChecksumResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
496
+
497
+ @wrapped
498
+ def transportLayerCmd(self, subCommand: int, *data: List[bytes]):
499
+ """Execute transfer-layer specific command.
500
+
501
+ Parameters
502
+ ----------
503
+ subCommand : int
504
+ data : bytes
505
+
506
+ Note
507
+ ----
508
+ For details refer to XCP specification.
509
+ """
510
+ return self.transport.request_optional_response(types.Command.TRANSPORT_LAYER_CMD, subCommand, *data)
511
+
512
+ @wrapped
513
+ def userCmd(self, subCommand: int, data: bytes):
514
+ """Execute proprietary command implemented in your XCP client.
515
+
516
+ Parameters
517
+ ----------
518
+ subCommand : int
519
+ data : bytes
520
+
521
+
522
+ .. note:: For details refer to your XCP client vendor.
523
+ """
524
+
525
+ response = self.transport.request(types.Command.USER_CMD, subCommand, *data)
526
+ return response
527
+
528
+ @wrapped
529
+ def getVersion(self):
530
+ """Get version information.
531
+
532
+ This command returns detailed information about the implemented
533
+ protocol layer version of the XCP slave and the transport layer
534
+ currently in use.
535
+
536
+ Returns
537
+ -------
538
+ :obj:`~types.GetVersionResponse`
539
+ """
540
+
541
+ response = self.transport.request(types.Command.GET_VERSION)
542
+ result = types.GetVersionResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
543
+ self.slaveProperties.protocolMajor = result.protocolMajor
544
+ self.slaveProperties.protocolMinor = result.protocolMinor
545
+ self.slaveProperties.transportMajor = result.transportMajor
546
+ self.slaveProperties.transportMinor = result.transportMinor
547
+ return result
548
+
549
+ def fetch(self, length: int, limitPayload: int = None): # TODO: pull
550
+ """Convenience function for data-transfer from slave to master
551
+ (Not part of the XCP Specification).
552
+
553
+ Parameters
554
+ ----------
555
+ length : int
556
+ limitPayload : int
557
+ transfer less bytes then supported by transport-layer
558
+
559
+ Returns
560
+ -------
561
+ bytes
562
+
563
+ Note
564
+ ----
565
+ address is not included because of services implicitly setting address information like :meth:`getID` .
566
+ """
567
+ if limitPayload and limitPayload < 8:
568
+ raise ValueError(f"Payload must be at least 8 bytes - given: {limitPayload}")
569
+
570
+ slaveBlockMode = self.slaveProperties.slaveBlockMode
571
+ if slaveBlockMode:
572
+ maxPayload = 255
573
+ else:
574
+ maxPayload = self.slaveProperties.maxCto - 1
575
+ payload = min(limitPayload, maxPayload) if limitPayload else maxPayload
576
+ chunkSize = payload
577
+ chunks = range(length // chunkSize)
578
+ remaining = length % chunkSize
579
+ result = []
580
+ for _ in chunks:
581
+ data = self.upload(chunkSize)
582
+ result.extend(data)
583
+ if remaining:
584
+ data = self.upload(remaining)
585
+ result.extend(data)
586
+ return bytes(result)
587
+
588
+ pull = fetch # fetch() may be completely replaced by pull() someday.
589
+
590
+ def push(self, address: int, address_ext: int, data: bytes, callback=None):
591
+ """Convenience function for data-transfer from master to slave.
592
+ (Not part of the XCP Specification).
593
+
594
+ Parameters
595
+ ----------
596
+ address: int
597
+
598
+ data : bytes
599
+ Arbitrary number of bytes.
600
+
601
+ Returns
602
+ -------
603
+ """
604
+ self._generalized_downloader(
605
+ address=address,
606
+ address_ext=address_ext,
607
+ data=data,
608
+ maxCto=self.slaveProperties.maxCto,
609
+ maxBs=self.slaveProperties.maxBs,
610
+ minSt=self.slaveProperties.minSt,
611
+ master_block_mode=self.slaveProperties.masterBlockMode,
612
+ dl_func=self.download,
613
+ dl_next_func=self.downloadNext,
614
+ callback=callback,
615
+ )
616
+
617
+ def flash_program(self, address: int, data: bytes, callback=None):
618
+ """Convenience function for flash programing.
619
+ (Not part of the XCP Specification).
620
+
621
+ Parameters
622
+ ----------
623
+ address: int
624
+
625
+ data : bytes
626
+ Arbitrary number of bytes.
627
+
628
+ Returns
629
+ -------
630
+ """
631
+ self._generalized_downloader(
632
+ address=address,
633
+ data=data,
634
+ maxCto=self.slaveProperties.pgmProcessor.maxCtoPgm,
635
+ maxBs=self.slaveProperties.pgmProcessor.maxBsPgm,
636
+ minSt=self.slaveProperties.pgmProcessor.minStPgm,
637
+ master_block_mode=self.slaveProperties.pgmProcessor.masterBlockMode,
638
+ dl_func=self.program,
639
+ dl_next_func=self.programNext,
640
+ callback=callback,
641
+ )
642
+
643
+ def _generalized_downloader(
644
+ self,
645
+ address: int,
646
+ address_ext: int,
647
+ data: bytes,
648
+ maxCto: int,
649
+ maxBs: int,
650
+ minSt: int,
651
+ master_block_mode: bool,
652
+ dl_func,
653
+ dl_next_func,
654
+ callback=None,
655
+ ):
656
+ """ """
657
+ self.setMta(address, address_ext)
658
+ minSt /= 10000.0
659
+ block_downloader = functools.partial(
660
+ self._block_downloader,
661
+ dl_func=dl_func,
662
+ dl_next_func=dl_next_func,
663
+ minSt=minSt,
664
+ )
665
+ total_length = len(data)
666
+ if master_block_mode:
667
+ max_payload = min(maxBs * (maxCto - 2), 255)
668
+ else:
669
+ max_payload = maxCto - 2
670
+ offset = 0
671
+ if master_block_mode:
672
+ remaining = total_length
673
+ blocks = range(total_length // max_payload)
674
+ percent_complete = 1
675
+ remaining_block_size = total_length % max_payload
676
+ for _ in blocks:
677
+ block = data[offset : offset + max_payload]
678
+ block_downloader(block)
679
+ offset += max_payload
680
+ remaining -= max_payload
681
+ if callback and remaining <= total_length - (total_length / 100) * percent_complete:
682
+ callback(percent_complete)
683
+ percent_complete += 1
684
+ if remaining_block_size:
685
+ block = data[offset : offset + remaining_block_size]
686
+ block_downloader(block)
687
+ if callback:
688
+ callback(percent_complete)
689
+ else:
690
+ chunk_size = max_payload
691
+ chunks = range(total_length // chunk_size)
692
+ remaining = total_length % chunk_size
693
+ percent_complete = 1
694
+ callback_remaining = total_length
695
+ for _ in chunks:
696
+ block = data[offset : offset + max_payload]
697
+ dl_func(block, max_payload, last=True)
698
+ offset += max_payload
699
+ callback_remaining -= chunk_size
700
+ if callback and callback_remaining <= total_length - (total_length / 100) * percent_complete:
701
+ callback(percent_complete)
702
+ percent_complete += 1
703
+ if remaining:
704
+ block = data[offset : offset + remaining]
705
+ dl_func(block, remaining, last=True)
706
+ if callback:
707
+ callback(percent_complete)
708
+
709
+ def _block_downloader(self, data: bytes, dl_func=None, dl_next_func=None, minSt=0):
710
+ """Re-usable block downloader.
711
+
712
+ Parameters
713
+ ----------
714
+ data : bytes
715
+ Arbitrary number of bytes.
716
+
717
+ dl_func: method
718
+ usually :meth: `download` or :meth:`program`
719
+
720
+ dl_next_func: method
721
+ usually :meth: `downloadNext` or :meth:`programNext`
722
+
723
+ minSt: int
724
+ Minimum separation time of frames.
725
+ """
726
+ length = len(data)
727
+ max_packet_size = self.slaveProperties.maxCto - 2 # Command ID + Length
728
+ packets = range(length // max_packet_size)
729
+ offset = 0
730
+ remaining = length % max_packet_size
731
+ remaining_block_size = length
732
+ index = 0
733
+ for index in packets:
734
+ packet_data = data[offset : offset + max_packet_size]
735
+ last = (remaining_block_size - max_packet_size) == 0
736
+ if index == 0:
737
+ dl_func(packet_data, length, last) # Transmit the complete length in the first CTO.
738
+ else:
739
+ dl_next_func(packet_data, remaining_block_size, last)
740
+ offset += max_packet_size
741
+ remaining_block_size -= max_packet_size
742
+ delay(minSt)
743
+ if remaining:
744
+ packet_data = data[offset : offset + remaining]
745
+ if index == 0:
746
+ # length of data is smaller than maxCto - 2
747
+ dl_func(packet_data, remaining, last=True)
748
+ else:
749
+ dl_next_func(packet_data, remaining, last=True)
750
+ delay(minSt)
751
+
752
+ @wrapped
753
+ def download(self, data: bytes, blockModeLength=None, last=False):
754
+ """Transfer data from master to slave.
755
+
756
+ Parameters
757
+ ----------
758
+ data : bytes
759
+ Data to send to slave.
760
+ blockModeLength : int or None
761
+ for block mode, the download request must contain the length of the whole block,
762
+ not just the length in the current packet. The whole block length can be given here for block-mode
763
+ transfers. For normal mode, the length indicates the actual packet's payload length.
764
+
765
+ Note
766
+ ----
767
+ Adress is set via :meth:`setMta`
768
+ """
769
+
770
+ if blockModeLength is None or last:
771
+ # standard mode
772
+ length = len(data)
773
+ response = self.transport.request(types.Command.DOWNLOAD, length, *data)
774
+ return response
775
+ else:
776
+ # block mode
777
+ if not isinstance(blockModeLength, int):
778
+ raise TypeError("blockModeLength must be int!")
779
+ self.transport.block_request(types.Command.DOWNLOAD, blockModeLength, *data)
780
+ return None
781
+
782
+ @wrapped
783
+ def downloadNext(self, data: bytes, remainingBlockLength, last=False):
784
+ """Transfer data from master to slave (block mode).
785
+
786
+ Parameters
787
+ ----------
788
+ data : bytes
789
+ remainingBlockLength : int
790
+ This parameter has to be given the remaining length in the block
791
+ last : bool
792
+ The block mode implementation shall indicate the last packet in the block with this parameter, because
793
+ the slave device will send the response after this.
794
+ """
795
+
796
+ if last:
797
+ # last DOWNLOAD_NEXT packet in a block: the slave device has to send the response after this.
798
+ response = self.transport.request(types.Command.DOWNLOAD_NEXT, remainingBlockLength, *data)
799
+ return response
800
+ else:
801
+ # the slave device won't respond to consecutive DOWNLOAD_NEXT packets in block mode,
802
+ # so we must not wait for any response
803
+ self.transport.block_request(types.Command.DOWNLOAD_NEXT, remainingBlockLength, *data)
804
+ return None
805
+
806
+ @wrapped
807
+ def downloadMax(self, data: bytes):
808
+ """Transfer data from master to slave (fixed size).
809
+
810
+ Parameters
811
+ ----------
812
+ data : bytes
813
+ """
814
+ return self.transport.request(types.Command.DOWNLOAD_MAX, *data)
815
+
816
+ @wrapped
817
+ def shortDownload(self, address, addressExt, data):
818
+ length = len(data)
819
+ addr = self.DWORD_pack(address)
820
+ return self.transport.request(types.Command.SHORT_DOWNLOAD, length, 0, addressExt, *addr, *data)
821
+
822
+ @wrapped
823
+ def modifyBits(self, shiftValue, andMask, xorMask):
824
+ # A = ( (A) & ((~((dword)(((word)~MA)<<S))) )^((dword)(MX<<S)) )
825
+ am = self.WORD_pack(andMask)
826
+ xm = self.WORD_pack(xorMask)
827
+ return self.transport.request(types.Command.MODIFY_BITS, shiftValue, *am, *xm)
828
+
829
+ # Page Switching Commands (PAG)
830
+ @wrapped
831
+ def setCalPage(self, mode: int, logicalDataSegment: int, logicalDataPage: int):
832
+ """Set calibration page.
833
+
834
+ Parameters
835
+ ----------
836
+ mode : int (bitfield)
837
+ - 0x01 - The given page will be used by the slave device application.
838
+ - 0x02 - The slave device XCP driver will access the given page.
839
+ - 0x80 - The logical segment number is ignored. The command applies to all segments
840
+ logicalDataSegment : int
841
+ logicalDataPage : int
842
+ """
843
+ return self.transport.request(types.Command.SET_CAL_PAGE, mode, logicalDataSegment, logicalDataPage)
844
+
845
+ @wrapped
846
+ def getCalPage(self, mode: int, logicalDataSegment: int):
847
+ """Get calibration page
848
+
849
+ Parameters
850
+ ----------
851
+ mode : int
852
+ logicalDataSegment : int
853
+ """
854
+ response = self.transport.request(types.Command.GET_CAL_PAGE, mode, logicalDataSegment)
855
+ return response[2]
856
+
857
+ @wrapped
858
+ def getPagProcessorInfo(self):
859
+ """Get general information on PAG processor.
860
+
861
+ Returns
862
+ -------
863
+ `pydbc.types.GetPagProcessorInfoResponse`
864
+ """
865
+ response = self.transport.request(types.Command.GET_PAG_PROCESSOR_INFO)
866
+ return types.GetPagProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
867
+
868
+ @wrapped
869
+ def getSegmentInfo(self, mode, segmentNumber, segmentInfo, mappingIndex):
870
+ """Get specific information for a segment.
871
+
872
+ Parameters
873
+ ----------
874
+ mode : int
875
+ - 0 = get basic address info for this segment
876
+ - 1 = get standard info for this segment
877
+ - 2 = get address mapping info for this segment
878
+
879
+ segmentNumber : int
880
+ segmentInfo : int
881
+ Mode 0:
882
+ - 0 = address
883
+ - 1 = length
884
+
885
+ Mode 1:
886
+ - don't care
887
+
888
+ Mode 2:
889
+ - 0 = source address
890
+ - 1 = destination address
891
+ - 2 = length address
892
+
893
+ mappingIndex : int
894
+ - Mode 0: don't care
895
+ - Mode 1: don't care
896
+ - Mode 2: identifier for address mapping range that mapping_info belongs to.
897
+
898
+ """
899
+ response = self.transport.request(
900
+ types.Command.GET_SEGMENT_INFO,
901
+ mode,
902
+ segmentNumber,
903
+ segmentInfo,
904
+ mappingIndex,
905
+ )
906
+ if mode == 0:
907
+ return types.GetSegmentInfoMode0Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
908
+ elif mode == 1:
909
+ return types.GetSegmentInfoMode1Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
910
+ elif mode == 2:
911
+ return types.GetSegmentInfoMode2Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
912
+
913
+ @wrapped
914
+ def getPageInfo(self, segmentNumber, pageNumber):
915
+ """Get specific information for a page.
916
+
917
+ Parameters
918
+ ----------
919
+ segmentNumber : int
920
+ pageNumber : int
921
+ """
922
+ response = self.transport.request(types.Command.GET_PAGE_INFO, 0, segmentNumber, pageNumber)
923
+ return (
924
+ types.PageProperties.parse(bytes([response[0]]), byteOrder=self.slaveProperties.byteOrder),
925
+ response[1],
926
+ )
927
+
928
+ @wrapped
929
+ def setSegmentMode(self, mode, segmentNumber):
930
+ """Set mode for a segment.
931
+
932
+ Parameters
933
+ ----------
934
+ mode : int (bitfield)
935
+ 1 = enable FREEZE Mode
936
+ segmentNumber : int
937
+ """
938
+ return self.transport.request(types.Command.SET_SEGMENT_MODE, mode, segmentNumber)
939
+
940
+ @wrapped
941
+ def getSegmentMode(self, segmentNumber):
942
+ """Get mode for a segment.
943
+
944
+ Parameters
945
+ ----------
946
+ segmentNumber : int
947
+ """
948
+ response = self.transport.request(types.Command.GET_SEGMENT_MODE, 0, segmentNumber)
949
+ return response[1]
950
+
951
+ @wrapped
952
+ def copyCalPage(self, srcSegment, srcPage, dstSegment, dstPage):
953
+ """Copy page.
954
+
955
+ Parameters
956
+ ----------
957
+ srcSegment : int
958
+ srcPage : int
959
+ dstSegment : int
960
+ dstPage : int
961
+ """
962
+ return self.transport.request(types.Command.COPY_CAL_PAGE, srcSegment, srcPage, dstSegment, dstPage)
963
+
964
+ # DAQ
965
+
966
+ @wrapped
967
+ def setDaqPtr(self, daqListNumber: int, odtNumber: int, odtEntryNumber: int):
968
+ self.currentDaqPtr = types.DaqPtr(daqListNumber, odtNumber, odtEntryNumber) # Needed for errorhandling.
969
+ daqList = self.WORD_pack(daqListNumber)
970
+ response = self.transport.request(types.Command.SET_DAQ_PTR, 0, *daqList, odtNumber, odtEntryNumber)
971
+ self.stim.setDaqPtr(daqListNumber, odtNumber, odtEntryNumber)
972
+ return response
973
+
974
+ @wrapped
975
+ def clearDaqList(self, daqListNumber: int):
976
+ """Clear DAQ list configuration.
977
+
978
+ Parameters
979
+ ----------
980
+ daqListNumber : int
981
+ """
982
+ daqList = self.WORD_pack(daqListNumber)
983
+ result = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList)
984
+ self.stim.clearDaqList(daqListNumber)
985
+ return result
986
+
987
+ @wrapped
988
+ def writeDaq(self, bitOffset: int, entrySize: int, addressExt: int, address: int):
989
+ """Write element in ODT entry.
990
+
991
+ Parameters
992
+ ----------
993
+ bitOffset : int
994
+ Position of bit in 32-bit variable referenced by the address and
995
+ extension below
996
+ entrySize : int
997
+ addressExt : int
998
+ address : int
999
+ """
1000
+ addr = self.DWORD_pack(address)
1001
+ result = self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr)
1002
+ self.stim.writeDaq(bitOffset, entrySize, addressExt, address)
1003
+ return result
1004
+
1005
+ @wrapped
1006
+ def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority):
1007
+ dln = self.WORD_pack(daqListNumber)
1008
+ ecn = self.WORD_pack(eventChannelNumber)
1009
+ self.stim.setDaqListMode(mode, daqListNumber, eventChannelNumber, prescaler, priority)
1010
+ return self.transport.request(types.Command.SET_DAQ_LIST_MODE, mode, *dln, *ecn, prescaler, priority)
1011
+
1012
+ @wrapped
1013
+ def getDaqListMode(self, daqListNumber):
1014
+ """Get mode from DAQ list.
1015
+
1016
+ Parameters
1017
+ ----------
1018
+ daqListNumber : int
1019
+
1020
+ Returns
1021
+ -------
1022
+ `pyxcp.types.GetDaqListModeResponse`
1023
+ """
1024
+ dln = self.WORD_pack(daqListNumber)
1025
+ response = self.transport.request(types.Command.GET_DAQ_LIST_MODE, 0, *dln)
1026
+ return types.GetDaqListModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1027
+
1028
+ @wrapped
1029
+ def startStopDaqList(self, mode: int, daqListNumber: int):
1030
+ """Start /stop/select DAQ list.
1031
+
1032
+ Parameters
1033
+ ----------
1034
+ mode : int
1035
+ 0 = stop
1036
+ 1 = start
1037
+ 2 = select
1038
+ daqListNumber : int
1039
+ """
1040
+ dln = self.WORD_pack(daqListNumber)
1041
+ response = self.transport.request(types.Command.START_STOP_DAQ_LIST, mode, *dln)
1042
+ self.stim.startStopDaqList(mode, daqListNumber)
1043
+ firstPid = types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1044
+ self.stim.set_first_pid(daqListNumber, firstPid.firstPid)
1045
+ return firstPid
1046
+
1047
+ @wrapped
1048
+ def startStopSynch(self, mode):
1049
+ """Start/stop DAQ lists (synchronously).
1050
+
1051
+ Parameters
1052
+ ----------
1053
+ mode : int
1054
+ 0 = stop all
1055
+ 1 = start selected
1056
+ 2 = stop selected
1057
+ """
1058
+ res = self.transport.request(types.Command.START_STOP_SYNCH, mode)
1059
+ self.stim.startStopSynch(mode)
1060
+ return res
1061
+
1062
+ @wrapped
1063
+ def writeDaqMultiple(self, daqElements):
1064
+ """Write multiple elements in ODT.
1065
+
1066
+ Parameters
1067
+ ----------
1068
+ daqElements : list of `dict` containing the following keys: *bitOffset*, *size*, *address*, *addressExt*.
1069
+ """
1070
+ if len(daqElements) > self.slaveProperties.maxWriteDaqMultipleElements:
1071
+ raise ValueError(f"At most {self.slaveProperties.maxWriteDaqMultipleElements} daqElements are permitted.")
1072
+ data = bytearray()
1073
+ data.append(len(daqElements))
1074
+
1075
+ for daqElement in daqElements:
1076
+ data.extend(types.DaqElement.build(daqElement, byteOrder=self.slaveProperties.byteOrder))
1077
+
1078
+ return self.transport.request(types.Command.WRITE_DAQ_MULTIPLE, *data)
1079
+
1080
+ # optional
1081
+ @wrapped
1082
+ def getDaqClock(self):
1083
+ """Get DAQ clock from slave.
1084
+
1085
+ Returns
1086
+ -------
1087
+ int
1088
+ Current timestamp, format specified by `getDaqResolutionInfo`
1089
+ """
1090
+ response = self.transport.request(types.Command.GET_DAQ_CLOCK)
1091
+ result = types.GetDaqClockResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1092
+ return result.timestamp
1093
+
1094
+ @wrapped
1095
+ def readDaq(self):
1096
+ """Read element from ODT entry.
1097
+
1098
+ Returns
1099
+ -------
1100
+ `pyxcp.types.ReadDaqResponse`
1101
+ """
1102
+ response = self.transport.request(types.Command.READ_DAQ)
1103
+ return types.ReadDaqResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1104
+
1105
+ @wrapped
1106
+ def getDaqProcessorInfo(self):
1107
+ """Get general information on DAQ processor.
1108
+
1109
+ Returns
1110
+ -------
1111
+ `pyxcp.types.GetDaqProcessorInfoResponse`
1112
+ """
1113
+ response = self.transport.request(types.Command.GET_DAQ_PROCESSOR_INFO)
1114
+ return types.GetDaqProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1115
+
1116
+ @wrapped
1117
+ def getDaqResolutionInfo(self):
1118
+ """Get general information on DAQ processing resolution.
1119
+
1120
+ Returns
1121
+ -------
1122
+ `pyxcp.types.GetDaqResolutionInfoResponse`
1123
+ """
1124
+ response = self.transport.request(types.Command.GET_DAQ_RESOLUTION_INFO)
1125
+ return types.GetDaqResolutionInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1126
+
1127
+ @wrapped
1128
+ def getDaqListInfo(self, daqListNumber):
1129
+ """Get specific information for a DAQ list.
1130
+
1131
+ Parameters
1132
+ ----------
1133
+ daqListNumber : int
1134
+ """
1135
+ dln = self.WORD_pack(daqListNumber)
1136
+ response = self.transport.request(types.Command.GET_DAQ_LIST_INFO, 0, *dln)
1137
+ return types.GetDaqListInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1138
+
1139
+ @wrapped
1140
+ def getDaqEventInfo(self, eventChannelNumber):
1141
+ """Get specific information for an event channel.
1142
+
1143
+ Parameters
1144
+ ----------
1145
+ eventChannelNumber : int
1146
+
1147
+ Returns
1148
+ -------
1149
+ `pyxcp.types.GetEventChannelInfoResponse`
1150
+ """
1151
+ ecn = self.WORD_pack(eventChannelNumber)
1152
+ response = self.transport.request(types.Command.GET_DAQ_EVENT_INFO, 0, *ecn)
1153
+ return types.GetEventChannelInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1154
+
1155
+ @wrapped
1156
+ def dtoCtrProperties(self, modifier, eventChannel, relatedEventChannel, mode):
1157
+ """DTO CTR properties
1158
+
1159
+ Parameters
1160
+ ----------
1161
+ modifier :
1162
+ eventChannel : int
1163
+ relatedEventChannel : int
1164
+ mode :
1165
+
1166
+ Returns
1167
+ -------
1168
+ `pyxcp.types.DtoCtrPropertiesResponse`
1169
+ """
1170
+ data = bytearray()
1171
+ data.append(modifier)
1172
+ data.extend(self.WORD_pack(eventChannel))
1173
+ data.extend(self.WORD_pack(relatedEventChannel))
1174
+ data.append(mode)
1175
+ response = self.transport.request(types.Command.DTO_CTR_PROPERTIES, *data)
1176
+ return types.DtoCtrPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1177
+
1178
+ @wrapped
1179
+ def setDaqPackedMode(self, daqListNumber, daqPackedMode, dpmTimestampMode=None, dpmSampleCount=None):
1180
+ """Set DAQ List Packed Mode.
1181
+
1182
+ Parameters
1183
+ ----------
1184
+ daqListNumber : int
1185
+ daqPackedMode : int
1186
+ """
1187
+ params = []
1188
+ dln = self.WORD_pack(daqListNumber)
1189
+ params.extend(dln)
1190
+ params.append(daqPackedMode)
1191
+
1192
+ if daqPackedMode == 1 or daqPackedMode == 2:
1193
+ params.append(dpmTimestampMode)
1194
+ dsc = self.WORD_pack(dpmSampleCount)
1195
+ params.extend(dsc)
1196
+
1197
+ return self.transport.request(types.Command.SET_DAQ_PACKED_MODE, *params)
1198
+
1199
+ @wrapped
1200
+ def getDaqPackedMode(self, daqListNumber):
1201
+ """Get DAQ List Packed Mode.
1202
+
1203
+ This command returns information of the currently active packed mode of
1204
+ the addressed DAQ list.
1205
+
1206
+ Parameters
1207
+ ----------
1208
+ daqListNumber : int
1209
+ """
1210
+ dln = self.WORD_pack(daqListNumber)
1211
+ response = self.transport.request(types.Command.GET_DAQ_PACKED_MODE, *dln)
1212
+ return types.GetDaqPackedModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1213
+
1214
+ # dynamic
1215
+ @wrapped
1216
+ def freeDaq(self):
1217
+ """Clear dynamic DAQ configuration."""
1218
+ result = self.transport.request(types.Command.FREE_DAQ)
1219
+ self.stim.freeDaq()
1220
+ return result
1221
+
1222
+ @wrapped
1223
+ def allocDaq(self, daqCount: int):
1224
+ """Allocate DAQ lists.
1225
+
1226
+ Parameters
1227
+ ----------
1228
+ daqCount : int
1229
+ number of DAQ lists to be allocated
1230
+ """
1231
+ dq = self.WORD_pack(daqCount)
1232
+ result = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
1233
+ self.stim.allocDaq(daqCount)
1234
+ return result
1235
+
1236
+ @wrapped
1237
+ def allocOdt(self, daqListNumber: int, odtCount: int):
1238
+ dln = self.WORD_pack(daqListNumber)
1239
+ result = self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount)
1240
+ self.stim.allocOdt(daqListNumber, odtCount)
1241
+ return result
1242
+
1243
+ @wrapped
1244
+ def allocOdtEntry(self, daqListNumber: int, odtNumber: int, odtEntriesCount: int):
1245
+ dln = self.WORD_pack(daqListNumber)
1246
+ result = self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount)
1247
+ self.stim.allocOdtEntry(daqListNumber, odtNumber, odtEntriesCount)
1248
+ return result
1249
+
1250
+ # PGM
1251
+ @wrapped
1252
+ def programStart(self):
1253
+ """Indicate the beginning of a programming sequence.
1254
+
1255
+ Returns
1256
+ -------
1257
+ `pyxcp.types.ProgramStartResponse`
1258
+ """
1259
+ response = self.transport.request(types.Command.PROGRAM_START)
1260
+ result = types.ProgramStartResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1261
+ self.slaveProperties.pgmProcessor.commModePgm = result.commModePgm
1262
+ self.slaveProperties.pgmProcessor.maxCtoPgm = result.maxCtoPgm
1263
+ self.slaveProperties.pgmProcessor.maxBsPgm = result.maxBsPgm
1264
+ self.slaveProperties.pgmProcessor.minStPgm = result.minStPgm
1265
+ self.slaveProperties.pgmProcessor.queueSizePgm = result.queueSizePgm
1266
+ self.slaveProperties.pgmProcessor.slaveBlockMode = result.commModePgm.slaveBlockMode
1267
+ self.slaveProperties.pgmProcessor.interleavedMode = result.commModePgm.interleavedMode
1268
+ self.slaveProperties.pgmProcessor.masterBlockMode = result.commModePgm.masterBlockMode
1269
+ return result
1270
+
1271
+ @wrapped
1272
+ def programClear(self, mode: int, clearRange: int):
1273
+ """Clear a part of non-volatile memory.
1274
+
1275
+ Parameters
1276
+ ----------
1277
+ mode : int
1278
+ 0x00 = the absolute access mode is active (default)
1279
+ 0x01 = the functional access mode is active
1280
+ clearRange : int
1281
+ """
1282
+ cr = self.DWORD_pack(clearRange)
1283
+ response = self.transport.request(types.Command.PROGRAM_CLEAR, mode, 0, 0, *cr)
1284
+ # ERR_ACCESS_LOCKED
1285
+ return response
1286
+
1287
+ @wrapped
1288
+ def program(self, data: bytes, blockLength, last=False):
1289
+ """Parameters
1290
+ ----------
1291
+ data : bytes
1292
+ Data to send to slave.
1293
+ blockModeLength : int
1294
+ the program request must contain the length of the whole block, not just the length
1295
+ in the current packet.
1296
+ last : bool
1297
+ Indicates that this is the only packet in the block, because
1298
+ the slave device will send the response after this.
1299
+
1300
+ Note
1301
+ ----
1302
+ Adress is set via :meth:`setMta`
1303
+ """
1304
+ # d = bytearray()
1305
+ # d.append(len(data))
1306
+ # if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1307
+ # d.extend(b"\x00\x00") # alignment bytes
1308
+ # for e in data:
1309
+ # d.extend(self.AG_pack(e))
1310
+ if last:
1311
+ # last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
1312
+ response = self.transport.request(types.Command.PROGRAM, blockLength, *data)
1313
+ return response
1314
+ else:
1315
+ # the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
1316
+ # so we must not wait for any response
1317
+ self.transport.block_request(types.Command.PROGRAM, blockLength, *data)
1318
+ return None
1319
+
1320
+ @wrapped
1321
+ def programReset(self, wait_for_optional_response=True):
1322
+ """Indicate the end of a programming sequence."""
1323
+ if wait_for_optional_response:
1324
+ return self.transport.request_optional_response(types.Command.PROGRAM_RESET)
1325
+ else:
1326
+ return self.transport.block_request(types.Command.PROGRAM_RESET)
1327
+
1328
+ @wrapped
1329
+ def getPgmProcessorInfo(self):
1330
+ """Get general information on PGM processor."""
1331
+ response = self.transport.request(types.Command.GET_PGM_PROCESSOR_INFO)
1332
+ result = types.GetPgmProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1333
+ self.slaveProperties.pgmProcessor.pgmProperties = result.pgmProperties
1334
+ self.slaveProperties.pgmProcessor.maxSector = result.maxSector
1335
+ return result
1336
+
1337
+ @wrapped
1338
+ def getSectorInfo(self, mode, sectorNumber):
1339
+ """Get specific information for a sector."""
1340
+ response = self.transport.request(types.Command.GET_SECTOR_INFO, mode, sectorNumber)
1341
+ if mode == 0 or mode == 1:
1342
+ return types.GetSectorInfoResponseMode01.parse(response, byteOrder=self.slaveProperties.byteOrder)
1343
+ elif mode == 2:
1344
+ return types.GetSectorInfoResponseMode2.parse(response, byteOrder=self.slaveProperties.byteOrder)
1345
+
1346
+ @wrapped
1347
+ def programPrepare(self, codesize):
1348
+ """Prepare non-volatile memory programming."""
1349
+ cs = self.WORD_pack(codesize)
1350
+ return self.transport.request(types.Command.PROGRAM_PREPARE, 0x00, *cs)
1351
+
1352
+ @wrapped
1353
+ def programFormat(self, compressionMethod, encryptionMethod, programmingMethod, accessMethod):
1354
+ return self.transport.request(
1355
+ types.Command.PROGRAM_FORMAT,
1356
+ compressionMethod,
1357
+ encryptionMethod,
1358
+ programmingMethod,
1359
+ accessMethod,
1360
+ )
1361
+
1362
+ @wrapped
1363
+ def programNext(self, data: bytes, remainingBlockLength: int, last: bool = False):
1364
+ # d = bytearray()
1365
+ # d.append(len(data))
1366
+ # if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1367
+ # d.extend(b"\x00\x00") # alignment bytes
1368
+ # for e in data:
1369
+ # d.extend(self.AG_pack(e))
1370
+ if last:
1371
+ # last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
1372
+ response = self.transport.request(types.Command.PROGRAM_NEXT, remainingBlockLength, *data)
1373
+ return response
1374
+ else:
1375
+ # the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
1376
+ # so we must not wait for any response
1377
+ self.transport.block_request(types.Command.PROGRAM_NEXT, remainingBlockLength, *data)
1378
+ return None
1379
+
1380
+ @wrapped
1381
+ def programMax(self, data):
1382
+ d = bytearray()
1383
+ if self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
1384
+ d.extend(b"\x00") # alignment bytes
1385
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1386
+ d.extend(b"\x00\x00\x00") # alignment bytes
1387
+ for e in data:
1388
+ d.extend(self.AG_pack(e))
1389
+ return self.transport.request(types.Command.PROGRAM_MAX, *d)
1390
+
1391
+ @wrapped
1392
+ def programVerify(self, verMode, verType, verValue):
1393
+ data = bytearray()
1394
+ data.extend(self.WORD_pack(verType))
1395
+ data.extend(self.DWORD_pack(verValue))
1396
+ return self.transport.request(types.Command.PROGRAM_VERIFY, verMode, *data)
1397
+
1398
+ # DBG
1399
+
1400
+ @wrapped
1401
+ def dbgAttach(self):
1402
+ """Returns detailed information about the implemented version of the SW-DBG feature of the XCP slave
1403
+
1404
+ Returns
1405
+ -------
1406
+ `pyxcp.types.DbgAttachResponse`
1407
+ """
1408
+ response = self.transport.request(types.Command.DBG_ATTACH)
1409
+ return types.DbgAttachResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1410
+
1411
+ @wrapped
1412
+ def dbgGetVendorInfo(self):
1413
+ """"""
1414
+ response = self.transport.request(types.Command.DBG_GET_VENDOR_INFO)
1415
+ return types.DbgGetVendorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1416
+
1417
+ @wrapped
1418
+ def dbgGetModeInfo(self):
1419
+ """"""
1420
+ response = self.transport.request(types.Command.DBG_GET_MODE_INFO)
1421
+ return types.DbgGetModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1422
+
1423
+ @wrapped
1424
+ def dbgGetJtagId(self):
1425
+ """"""
1426
+ response = self.transport.request(types.Command.DBG_GET_JTAG_ID)
1427
+ return types.DbgGetJtagIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1428
+
1429
+ @wrapped
1430
+ def dbgHaltAfterReset(self):
1431
+ """"""
1432
+ return self.transport.request(types.Command.DBG_HALT_AFTER_RESET)
1433
+
1434
+ @wrapped
1435
+ def dbgGetHwioInfo(self, index: int):
1436
+ """"""
1437
+ response = self.transport.request(types.Command.DBG_GET_HWIO_INFO, index)
1438
+ return types.DbgGetHwioInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1439
+
1440
+ @wrapped
1441
+ def dbgSetHwioEvent(self, index: int, trigger: int):
1442
+ """"""
1443
+ return self.transport.request(types.Command.DBG_SET_HWIO_EVENT, index, trigger)
1444
+
1445
+ @wrapped
1446
+ def dbgHwioControl(self, pins):
1447
+ """"""
1448
+ d = bytearray()
1449
+ d.extend(self.BYTE_pack(len(pins)))
1450
+ for p in pins:
1451
+ d.extend(self.BYTE_pack(p[0])) # index
1452
+ d.extend(self.BYTE_pack(p[1])) # state
1453
+ d.extend(self.WORD_pack(p[2])) # frequency
1454
+
1455
+ response = self.transport.request(types.Command.DBG_HWIO_CONTROL, *d)
1456
+ return types.DbgHwioControlResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1457
+
1458
+ @wrapped
1459
+ def dbgExclusiveTargetAccess(self, mode: int, context: int):
1460
+ """"""
1461
+ return self.transport.request(types.Command.DBG_EXCLUSIVE_TARGET_ACCESS, mode, context)
1462
+
1463
+ @wrapped
1464
+ def dbgSequenceMultiple(self, mode: int, num: int, *seq):
1465
+ """"""
1466
+ response = self.transport.request(types.Command.DBG_SEQUENCE_MULTIPLE, mode, self.WORD_pack(num), *seq)
1467
+ return types.DbgSequenceMultipleResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1468
+
1469
+ @wrapped
1470
+ def dbgLlt(self, num: int, mode: int, *llts):
1471
+ """"""
1472
+ response = self.transport.request(types.Command.DBG_LLT, num, mode, *llts)
1473
+ return types.DbgLltResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1474
+
1475
+ @wrapped
1476
+ def dbgReadModifyWrite(self, tri: int, width: int, address: int, mask: int, data: int):
1477
+ """"""
1478
+ d = bytearray()
1479
+ d.extend(b"\x00")
1480
+ d.append(tri)
1481
+ d.append(width)
1482
+ d.extend(b"\x00\x00")
1483
+ d.extend(self.DLONG_pack(address))
1484
+ if width == 0x01:
1485
+ d.extend(self.BYTE_pack(mask))
1486
+ d.extend(self.BYTE_pack(data))
1487
+ elif width == 0x02:
1488
+ d.extend(self.WORD_pack(mask))
1489
+ d.extend(self.WORD_pack(data))
1490
+ elif width == 0x04:
1491
+ d.extend(self.DWORD_pack(mask))
1492
+ d.extend(self.DWORD_pack(data))
1493
+ elif width == 0x08:
1494
+ d.extend(self.DLONG_pack(mask))
1495
+ d.extend(self.DLONG_pack(data))
1496
+ response = self.transport.request(types.Command.DBG_READ_MODIFY_WRITE, *d)
1497
+ return types.DbgReadModifyWriteResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
1498
+
1499
+ @wrapped
1500
+ def dbgWrite(self, tri: int, width: int, address: int, data):
1501
+ """"""
1502
+ d = bytearray()
1503
+ d.extend(b"\x00")
1504
+ d.append(tri)
1505
+ self._dbg_width = width
1506
+ d.append(width)
1507
+ d.extend(self.WORD_pack(len(data)))
1508
+ d.extend(self.DLONG_pack(address))
1509
+ for da in data:
1510
+ if width == 0x01:
1511
+ d.extend(self.BYTE_pack(da))
1512
+ elif width == 0x02:
1513
+ d.extend(self.WORD_pack(da))
1514
+ elif width == 0x04:
1515
+ d.extend(self.DWORD_pack(da))
1516
+ elif width == 0x08:
1517
+ d.extend(self.DLONG_pack(da))
1518
+ return self.transport.request(types.Command.DBG_WRITE, *d)
1519
+
1520
+ @wrapped
1521
+ def dbgWriteNext(self, num: int, data: int):
1522
+ """"""
1523
+ d = bytearray()
1524
+ d.extend(b"\x00")
1525
+ d.extend(self.WORD_pack(num))
1526
+ d.extend(b"\x00\x00")
1527
+ for i in range(num):
1528
+ if self._dbg_width == 0x01:
1529
+ d.extend(self.BYTE_pack(data[i]))
1530
+ elif self._dbg_width == 0x02:
1531
+ d.extend(self.WORD_pack(data[i]))
1532
+ elif self._dbg_width == 0x04:
1533
+ d.extend(self.DWORD_pack(data[i]))
1534
+ elif self._dbg_width == 0x08:
1535
+ d.extend(self.DLONG_pack(data[i]))
1536
+ return self.transport.request(types.Command.DBG_WRITE_NEXT, *d)
1537
+
1538
+ @wrapped
1539
+ def dbgWriteCan1(self, tri: int, address: int):
1540
+ """"""
1541
+ d = bytearray()
1542
+ d.extend(self.BYTE_pack(tri))
1543
+ d.extend(self.DWORD_pack(address))
1544
+ return self.transport.request(types.Command.DBG_WRITE_CAN1, *d)
1545
+
1546
+ @wrapped
1547
+ def dbgWriteCan2(self, width: int, num: int):
1548
+ """"""
1549
+ d = bytearray()
1550
+ self._dbg_width = width
1551
+ d.append(width)
1552
+ d.extend(self.BYTE_pack(num))
1553
+ return self.transport.request(types.Command.DBG_WRITE_CAN2, *d)
1554
+
1555
+ @wrapped
1556
+ def dbgWriteCanNext(self, num: int, data: int):
1557
+ """"""
1558
+ d = bytearray()
1559
+ d.extend(self.BYTE_pack(num))
1560
+ for i in range(num):
1561
+ if self._dbg_width == 0x01:
1562
+ d.extend(self.BYTE_pack(data[i]))
1563
+ elif self._dbg_width == 0x02:
1564
+ d.extend(self.WORD_pack(data[i]))
1565
+ elif self._dbg_width == 0x04:
1566
+ d.extend(self.DWORD_pack(data[i]))
1567
+ elif self._dbg_width == 0x08:
1568
+ d.extend(self.DLONG_pack(data[i]))
1569
+ return self.transport.request(types.Command.DBG_WRITE_CAN_NEXT, *d)
1570
+
1571
+ @wrapped
1572
+ def dbgRead(self, tri: int, width: int, num: int, address: int):
1573
+ """"""
1574
+ d = bytearray()
1575
+ d.extend(b"\x00")
1576
+ d.extend(self.BYTE_pack(tri))
1577
+ self._dbg_width = width
1578
+ d.extend(self.BYTE_pack(width))
1579
+ d.extend(self.WORD_pack(num))
1580
+ d.extend(self.DLONG_pack(address))
1581
+ response = self.transport.request(types.Command.DBG_READ, *d)
1582
+ return types.DbgReadResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
1583
+
1584
+ @wrapped
1585
+ def dbgReadCan1(self, tri: int, address: int):
1586
+ """"""
1587
+ d = bytearray()
1588
+ d.extend(self.BYTE_pack(tri))
1589
+ d.extend(self.DWORD_pack(address))
1590
+ return self.transport.request(types.Command.DBG_READ_CAN1, *d)
1591
+
1592
+ @wrapped
1593
+ def dbgReadCan2(self, width: int, num: int):
1594
+ """"""
1595
+ d = bytearray()
1596
+ self._dbg_width = width
1597
+ d.extend(self.BYTE_pack(width))
1598
+ d.extend(self.BYTE_pack(num))
1599
+ return self.transport.request(types.Command.DBG_READ_CAN2, *d)
1600
+
1601
+ @wrapped
1602
+ def dbgGetTriDescTbl(self):
1603
+ """"""
1604
+ response = self.transport.request(types.Command.DBG_GET_TRI_DESC_TBL, b"\x00\x00\x00\x00\x00")
1605
+ return types.DbgGetTriDescTblResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1606
+
1607
+ @wrapped
1608
+ def dbgLlbt(self, data):
1609
+ """"""
1610
+ d = bytearray()
1611
+ d.extend(b"\x00")
1612
+ d.extend(self.WORD_pack(len(data)))
1613
+ for b in data:
1614
+ d.extend(self.BYTE_pack(b))
1615
+ response = self.transport.request(types.Command.DBG_LLBT, d)
1616
+ return types.DbgLlbtResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1617
+
1618
+ @wrapped
1619
+ def timeCorrelationProperties(self, setProperties, getPropertiesRequest, clusterId):
1620
+ response = self.transport.request(
1621
+ types.Command.TIME_CORRELATION_PROPERTIES, setProperties, getPropertiesRequest, 0, *self.WORD_pack(clusterId)
1622
+ )
1623
+ return types.TimeCorrelationPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1624
+
1625
+ # Transport layer commands / CAN.
1626
+
1627
+ @broadcasted
1628
+ @wrapped
1629
+ def getSlaveID(self, mode: int):
1630
+ response = self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, ord("X"), ord("C"), ord("P"), mode)
1631
+ return types.GetSlaveIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1632
+
1633
+ def getDaqId(self, daqListNumber: int):
1634
+ response = self.transportLayerCmd(types.TransportLayerCommands.GET_DAQ_ID, *self.WORD_pack(daqListNumber))
1635
+ # if response:
1636
+ return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1637
+
1638
+ def setDaqId(self, daqListNumber: int, identifier: int):
1639
+ response = self.transportLayerCmd(
1640
+ types.TransportLayerCommands.SET_DAQ_ID, *self.WORD_pack(daqListNumber), *self.DWORD_pack(identifier)
1641
+ )
1642
+ return response
1643
+
1644
+ # Convenience Functions.
1645
+ def verify(self, addr, length):
1646
+ """Convenience function for verification of a data-transfer from slave
1647
+ to master (Not part of the XCP Specification).
1648
+
1649
+ Parameters
1650
+ ----------
1651
+ addr : int
1652
+ length : int
1653
+
1654
+ Returns
1655
+ -------
1656
+ bool
1657
+ """
1658
+ self.setMta(addr)
1659
+ cs = self.buildChecksum(length)
1660
+ self.logger.debug(f"BuildChecksum return'd: 0x{cs.checksum:08X} [{cs.checksumType}]")
1661
+ self.setMta(addr)
1662
+ data = self.fetch(length)
1663
+ cc = checksum.check(data, cs.checksumType)
1664
+ self.logger.debug(f"Our checksum : 0x{cc:08X}")
1665
+ return cs.checksum == cc
1666
+
1667
+ def getDaqInfo(self):
1668
+ """Get DAQ information: processor, resolution, events."""
1669
+ result = {}
1670
+ dpi = self.getDaqProcessorInfo()
1671
+ processorInfo = {
1672
+ "minDaq": dpi["minDaq"],
1673
+ "maxDaq": dpi["maxDaq"],
1674
+ "properties": {
1675
+ "configType": dpi["daqProperties"]["daqConfigType"],
1676
+ "overloadEvent": dpi["daqProperties"]["overloadEvent"],
1677
+ "overloadMsb": dpi["daqProperties"]["overloadMsb"],
1678
+ "prescalerSupported": dpi["daqProperties"]["prescalerSupported"],
1679
+ "pidOffSupported": dpi["daqProperties"]["pidOffSupported"],
1680
+ "timestampSupported": dpi["daqProperties"]["timestampSupported"],
1681
+ "bitStimSupported": dpi["daqProperties"]["bitStimSupported"],
1682
+ "resumeSupported": dpi["daqProperties"]["resumeSupported"],
1683
+ },
1684
+ "keyByte": {
1685
+ "identificationField": dpi["daqKeyByte"]["Identification_Field"],
1686
+ "addressExtension": dpi["daqKeyByte"]["Address_Extension"],
1687
+ "optimisationType": dpi["daqKeyByte"]["Optimisation_Type"],
1688
+ },
1689
+ }
1690
+ result["processor"] = processorInfo
1691
+
1692
+ dri = self.getDaqResolutionInfo()
1693
+ resolutionInfo = {
1694
+ "timestampTicks": dri["timestampTicks"],
1695
+ "maxOdtEntrySizeDaq": dri["maxOdtEntrySizeDaq"],
1696
+ "maxOdtEntrySizeStim": dri["maxOdtEntrySizeStim"],
1697
+ "granularityOdtEntrySizeDaq": dri["granularityOdtEntrySizeDaq"],
1698
+ "granularityOdtEntrySizeStim": dri["granularityOdtEntrySizeStim"],
1699
+ "timestampMode": {
1700
+ "unit": dri["timestampMode"]["unit"],
1701
+ "fixed": dri["timestampMode"]["fixed"],
1702
+ "size": dri["timestampMode"]["size"],
1703
+ },
1704
+ }
1705
+ result["resolution"] = resolutionInfo
1706
+ channels = []
1707
+ daq_events = []
1708
+ for ecn in range(dpi.maxEventChannel):
1709
+ eci = self.getDaqEventInfo(ecn)
1710
+ cycle = eci["eventChannelTimeCycle"]
1711
+ maxDaqList = eci["maxDaqList"]
1712
+ priority = eci["eventChannelPriority"]
1713
+ time_unit = eci["eventChannelTimeUnit"]
1714
+ consistency = eci["daqEventProperties"]["consistency"]
1715
+ daq_supported = eci["daqEventProperties"]["daq"]
1716
+ stim_supported = eci["daqEventProperties"]["stim"]
1717
+ packed_supported = eci["daqEventProperties"]["packed"]
1718
+ name = self.fetch(eci.eventChannelNameLength)
1719
+ if name:
1720
+ name = decode_bytes(name)
1721
+ channel = {
1722
+ "name": name,
1723
+ "priority": eci["eventChannelPriority"],
1724
+ "unit": eci["eventChannelTimeUnit"],
1725
+ "cycle": eci["eventChannelTimeCycle"],
1726
+ "maxDaqList": eci["maxDaqList"],
1727
+ "properties": {
1728
+ "consistency": consistency,
1729
+ "daq": daq_supported,
1730
+ "stim": stim_supported,
1731
+ "packed": packed_supported,
1732
+ },
1733
+ }
1734
+ daq_event_info = DaqEventInfo(
1735
+ name,
1736
+ types.EVENT_CHANNEL_TIME_UNIT_TO_EXP[time_unit],
1737
+ cycle,
1738
+ maxDaqList,
1739
+ priority,
1740
+ consistency,
1741
+ daq_supported,
1742
+ stim_supported,
1743
+ packed_supported,
1744
+ )
1745
+ daq_events.append(daq_event_info)
1746
+ channels.append(channel)
1747
+ result["channels"] = channels
1748
+ self.stim.setDaqEventInfo(daq_events)
1749
+ return result
1750
+
1751
+ def getCurrentProtectionStatus(self):
1752
+ """"""
1753
+ if self.currentProtectionStatus is None:
1754
+ status = self.getStatus()
1755
+ self._setProtectionStatus(status.resourceProtectionStatus)
1756
+ return self.currentProtectionStatus
1757
+
1758
+ def _setProtectionStatus(self, protection):
1759
+ """"""
1760
+ self.currentProtectionStatus = {
1761
+ "dbg": protection.dbg,
1762
+ "pgm": protection.pgm,
1763
+ "stim": protection.stim,
1764
+ "daq": protection.daq,
1765
+ "calpag": protection.calpag,
1766
+ }
1767
+
1768
+ def cond_unlock(self, resources=None):
1769
+ """Conditionally unlock resources, i.e. only unlock locked resources.
1770
+
1771
+ Precondition: Parameter "SEED_N_KEY_DLL" must be present and point to a valid DLL/SO.
1772
+
1773
+ Parameters
1774
+ ----------
1775
+ resources: str
1776
+ Comma or space separated list of resources, e.g. "DAQ, CALPAG".
1777
+ The names are not case-sensitive.
1778
+ Valid identifiers are: "calpag", "daq", "dbg", "pgm", "stim".
1779
+
1780
+ If omitted, try to unlock every available resource.
1781
+
1782
+ Raises
1783
+ ------
1784
+ ValueError
1785
+ Invalid resource name.
1786
+
1787
+ `dllif.SeedNKeyError`
1788
+ In case of DLL related issues.
1789
+ """
1790
+ import re
1791
+
1792
+ from pyxcp.dllif import SeedNKeyError, SeedNKeyResult, getKey
1793
+
1794
+ MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2
1795
+
1796
+ protection_status = self.getCurrentProtectionStatus()
1797
+ if any(protection_status.values()) and (not (self.seed_n_key_dll or self.seed_n_key_function)):
1798
+ raise RuntimeError("Neither seed-and-key DLL nor function specified, cannot proceed.") # TODO: ConfigurationError
1799
+ if resources is None:
1800
+ result = []
1801
+ if self.slaveProperties["supportsCalpag"]:
1802
+ result.append("calpag")
1803
+ if self.slaveProperties["supportsDaq"]:
1804
+ result.append("daq")
1805
+ if self.slaveProperties["supportsStim"]:
1806
+ result.append("stim")
1807
+ if self.slaveProperties["supportsPgm"]:
1808
+ result.append("pgm")
1809
+ resources = ",".join(result)
1810
+ resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r]
1811
+ for name in resource_names:
1812
+ if name not in types.RESOURCE_VALUES:
1813
+ raise ValueError(f"Invalid resource name {name!r}.")
1814
+ if not protection_status[name]:
1815
+ continue
1816
+ resource_value = types.RESOURCE_VALUES[name]
1817
+ result = self.getSeed(types.XcpGetSeedMode.FIRST_PART, resource_value)
1818
+ seed = list(result.seed)
1819
+ length = result.length
1820
+ if length == 0:
1821
+ continue
1822
+ if length > MAX_PAYLOAD:
1823
+ remaining = length - len(seed)
1824
+ while remaining > 0:
1825
+ result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value)
1826
+ seed.extend(list(result.seed))
1827
+ remaining -= result.length
1828
+ self.logger.debug(f"Got seed {seed!r} for resource {resource_value!r}.")
1829
+ if self.seed_n_key_function:
1830
+ key = self.seed_n_key_function(resource_value, bytes(seed))
1831
+ self.logger.debug(f"Using seed and key function {self.seed_n_key_function.__name__!r}().")
1832
+ result = SeedNKeyResult.ACK
1833
+ elif self.seed_n_key_dll:
1834
+ self.logger.debug(f"Using seed and key DLL {self.seed_n_key_dll!r}.")
1835
+ result, key = getKey(
1836
+ self.logger,
1837
+ self.seed_n_key_dll,
1838
+ resource_value,
1839
+ bytes(seed),
1840
+ self.seed_n_key_dll_same_bit_width,
1841
+ )
1842
+ if result == SeedNKeyResult.ACK:
1843
+ key = list(key)
1844
+ self.logger.debug(f"Unlocking resource {resource_value!r} with key {key!r}.")
1845
+ remaining = len(key)
1846
+ while key:
1847
+ data = key[:MAX_PAYLOAD]
1848
+ key_len = len(data)
1849
+ self.unlock(remaining, data)
1850
+ key = key[MAX_PAYLOAD:]
1851
+ remaining -= key_len
1852
+ else:
1853
+ raise SeedNKeyError(f"SeedAndKey DLL returned: {SeedNKeyResult(result).name!r}")
1854
+
1855
+ def identifier(self, id_value: int) -> str:
1856
+ """Return the identifier for the given value.
1857
+ Use this method instead of calling `getId()` directly.
1858
+
1859
+ Parameters
1860
+ ----------
1861
+ id_value: int
1862
+ For standard identifiers, use the constants from `pyxcp.types.XcpGetIdType`.
1863
+
1864
+ Returns
1865
+ -------
1866
+ str
1867
+ """
1868
+ gid = self.getId(id_value)
1869
+ if (gid.mode & 0x01) == 0x01:
1870
+ value = bytes(gid.identification or b"")
1871
+ else:
1872
+ value = self.fetch(gid.length)
1873
+ return decode_bytes(value)
1874
+
1875
+ def id_scanner(self, scan_ranges: Optional[Collection[Collection[int]]] = None) -> Dict[str, str]:
1876
+ """Scan for available standard identification types (GET_ID).
1877
+
1878
+ Parameters
1879
+ ----------
1880
+ scan_ranges: Optional[Collection[Collection[int]]]
1881
+
1882
+ - If parameter is omitted or `None` test every standard identification type (s. GET_ID service)
1883
+ plus extensions by Vector Informatik.
1884
+ - Else `scan_ranges` must be a list-of-list.
1885
+ e.g: [[12, 80], [123], [240, 16, 35]]
1886
+ - The first list is a range (closed interval).
1887
+ - The second is a single value.
1888
+ - The third is a value list.
1889
+
1890
+ Returns
1891
+ -------
1892
+ Dict[str, str]
1893
+
1894
+ """
1895
+ result = {}
1896
+
1897
+ def make_generator(sr):
1898
+ STD_IDS = {int(v): k for k, v in types.XcpGetIdType.__members__.items()}
1899
+ if sr is None:
1900
+ scan_range = STD_IDS.keys()
1901
+ else:
1902
+ scan_range = []
1903
+ if not isinstance(sr, Collection):
1904
+ raise TypeError("scan_ranges must be of type `Collection`")
1905
+ for element in sr:
1906
+ if not isinstance(element, Collection):
1907
+ raise TypeError("scan_ranges elements must be of type `Collection`")
1908
+ if not element:
1909
+ raise ValueError("scan_ranges elements cannot be empty")
1910
+ if len(element) == 1:
1911
+ scan_range.append(element[0]) # Single value
1912
+ elif len(element) == 2:
1913
+ start, stop = element # Value range
1914
+ scan_range.extend(list(range(start, stop + 1)))
1915
+ else:
1916
+ scan_range.extend(element) # Value list.
1917
+ scan_range = sorted(frozenset(scan_range))
1918
+
1919
+ def generate():
1920
+ for idx, id_value in enumerate(scan_range):
1921
+ if id_value in STD_IDS:
1922
+ name = STD_IDS[id_value]
1923
+ else:
1924
+ name = f"USER_{idx}"
1925
+ yield id_value, name,
1926
+
1927
+ return generate()
1928
+
1929
+ gen = make_generator(scan_ranges)
1930
+ for id_value, name in gen:
1931
+ status, response = self.try_command(self.identifier, id_value)
1932
+ if status == types.TryCommandResult.OK and response:
1933
+ result[name] = response
1934
+ elif status == types.TryCommandResult.XCP_ERROR and response.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1935
+ break # Nothing to do here.
1936
+ elif status == types.TryCommandResult.OTHER_ERROR:
1937
+ raise RuntimeError(f"Error while scanning for ID {id_value}: {response!r}")
1938
+ return result
1939
+
1940
+ @property
1941
+ def start_datetime(self) -> int:
1942
+ """"""
1943
+ return self.transport.start_datetime
1944
+
1945
+ def try_command(self, cmd: Callable, *args, **kws) -> Tuple[types.TryCommandResult, Any]:
1946
+ """Call master functions and handle XCP errors more gracefuly.
1947
+
1948
+ Parameter
1949
+ ---------
1950
+ cmd: Callable
1951
+ args: list
1952
+ variable length arguments to `cmd`.
1953
+ kws: dict
1954
+ keyword arguments to `cmd`.
1955
+
1956
+ `extra_msg`: str
1957
+ Additional info to log message (not passed to `cmd`).
1958
+
1959
+ Returns
1960
+ -------
1961
+
1962
+ Note
1963
+ ----
1964
+ Mainly used for plug-and-play applications, e.g. `id_scanner` may confronted with `ERR_OUT_OF_RANGE` errors, which
1965
+ is normal for this kind of applications -- or to test for optional commands.
1966
+ Use carefuly not to hide serious error causes.
1967
+ """
1968
+ try:
1969
+ extra_msg: Optional[str] = kws.get("extra_msg")
1970
+ if extra_msg:
1971
+ kws.pop("extra_msg")
1972
+ res = cmd(*args, **kws)
1973
+ except SystemExit as e:
1974
+ if e.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1975
+ # This is a rather common use-case, so let the user know that there is some functionality missing.
1976
+ if extra_msg:
1977
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented -- {extra_msg!r}")
1978
+ else:
1979
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented.")
1980
+ return (types.TryCommandResult.XCP_ERROR, e)
1981
+ except Exception as e:
1982
+ return (types.TryCommandResult.OTHER_ERROR, e)
1983
+ else:
1984
+ return (types.TryCommandResult.OK, res)
1985
+
1986
+
1987
+ def ticks_to_seconds(ticks, resolution):
1988
+ """Convert DAQ timestamp/tick value to seconds.
1989
+
1990
+ Parameters
1991
+ ----------
1992
+ ticks: int
1993
+
1994
+ unit: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
1995
+ """
1996
+ warnings.warn(
1997
+ "ticks_to_seconds() deprecated, use factory :func:`make_tick_converter` instead.",
1998
+ Warning,
1999
+ stacklevel=1,
2000
+ )
2001
+ return (10 ** types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]) * resolution.timestampTicks * ticks
2002
+
2003
+
2004
+ def make_tick_converter(resolution):
2005
+ """Make a function that converts tick count from XCP slave to seconds.
2006
+
2007
+ Parameters
2008
+ ----------
2009
+ resolution: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
2010
+
2011
+ """
2012
+ exponent = types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]
2013
+ tick_resolution = resolution.timestampTicks
2014
+ base = (10**exponent) * tick_resolution
2015
+
2016
+ def ticks_to_seconds(ticks):
2017
+ """Convert DAQ timestamp/tick value to seconds.
2018
+
2019
+ Parameters
2020
+ ----------
2021
+ ticks: int
2022
+
2023
+ Returns
2024
+ -------
2025
+ float
2026
+ """
2027
+ return base * ticks
2028
+
2029
+ return ticks_to_seconds