pyxcp 0.22.1__cp38-cp38-macosx_13_0_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 (126) hide show
  1. CMakeLists.txt +117 -0
  2. pyxcp/__init__.py +20 -0
  3. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  4. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  5. pyxcp/aml/XCP_Common.aml +408 -0
  6. pyxcp/aml/XCPonCAN.aml +78 -0
  7. pyxcp/aml/XCPonEth.aml +33 -0
  8. pyxcp/aml/XCPonFlx.aml +113 -0
  9. pyxcp/aml/XCPonSxI.aml +66 -0
  10. pyxcp/aml/XCPonUSB.aml +106 -0
  11. pyxcp/aml/ifdata_CAN.a2l +20 -0
  12. pyxcp/aml/ifdata_Eth.a2l +11 -0
  13. pyxcp/aml/ifdata_Flx.a2l +94 -0
  14. pyxcp/aml/ifdata_SxI.a2l +13 -0
  15. pyxcp/aml/ifdata_USB.a2l +81 -0
  16. pyxcp/asam/__init__.py +0 -0
  17. pyxcp/asam/types.py +131 -0
  18. pyxcp/asamkeydll.c +116 -0
  19. pyxcp/asamkeydll.sh +2 -0
  20. pyxcp/checksum.py +722 -0
  21. pyxcp/cmdline.py +52 -0
  22. pyxcp/config/__init__.py +1089 -0
  23. pyxcp/config/legacy.py +120 -0
  24. pyxcp/constants.py +47 -0
  25. pyxcp/cpp_ext/__init__.py +8 -0
  26. pyxcp/cpp_ext/bin.hpp +104 -0
  27. pyxcp/cpp_ext/blockmem.hpp +58 -0
  28. pyxcp/cpp_ext/cpp_ext.cpython-312-darwin.so +0 -0
  29. pyxcp/cpp_ext/daqlist.hpp +197 -0
  30. pyxcp/cpp_ext/event.hpp +67 -0
  31. pyxcp/cpp_ext/extension_wrapper.cpp +94 -0
  32. pyxcp/cpp_ext/helper.hpp +264 -0
  33. pyxcp/cpp_ext/mcobject.hpp +241 -0
  34. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  35. pyxcp/daq_stim/__init__.py +226 -0
  36. pyxcp/daq_stim/optimize/__init__.py +67 -0
  37. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  38. pyxcp/daq_stim/scheduler.cpp +28 -0
  39. pyxcp/daq_stim/scheduler.hpp +75 -0
  40. pyxcp/daq_stim/stim.cpp +13 -0
  41. pyxcp/daq_stim/stim.cpython-312-darwin.so +0 -0
  42. pyxcp/daq_stim/stim.hpp +604 -0
  43. pyxcp/daq_stim/stim_wrapper.cpp +48 -0
  44. pyxcp/dllif.py +95 -0
  45. pyxcp/errormatrix.py +878 -0
  46. pyxcp/examples/conf_can.toml +19 -0
  47. pyxcp/examples/conf_can_user.toml +16 -0
  48. pyxcp/examples/conf_can_vector.json +11 -0
  49. pyxcp/examples/conf_can_vector.toml +11 -0
  50. pyxcp/examples/conf_eth.toml +9 -0
  51. pyxcp/examples/conf_nixnet.json +20 -0
  52. pyxcp/examples/conf_socket_can.toml +12 -0
  53. pyxcp/examples/conf_sxi.json +9 -0
  54. pyxcp/examples/conf_sxi.toml +7 -0
  55. pyxcp/examples/ex_arrow.py +109 -0
  56. pyxcp/examples/ex_mdf.py +124 -0
  57. pyxcp/examples/ex_sqlite.py +128 -0
  58. pyxcp/examples/run_daq.py +146 -0
  59. pyxcp/examples/xcp_policy.py +60 -0
  60. pyxcp/examples/xcp_read_benchmark.py +38 -0
  61. pyxcp/examples/xcp_skel.py +49 -0
  62. pyxcp/examples/xcp_unlock.py +38 -0
  63. pyxcp/examples/xcp_user_supplied_driver.py +54 -0
  64. pyxcp/examples/xcphello.py +79 -0
  65. pyxcp/examples/xcphello_recorder.py +107 -0
  66. pyxcp/master/__init__.py +9 -0
  67. pyxcp/master/errorhandler.py +436 -0
  68. pyxcp/master/master.py +2029 -0
  69. pyxcp/py.typed +0 -0
  70. pyxcp/recorder/__init__.py +102 -0
  71. pyxcp/recorder/build_clang.cmd +1 -0
  72. pyxcp/recorder/build_clang.sh +2 -0
  73. pyxcp/recorder/build_gcc.cmd +1 -0
  74. pyxcp/recorder/build_gcc.sh +2 -0
  75. pyxcp/recorder/build_gcc_arm.sh +2 -0
  76. pyxcp/recorder/converter/__init__.py +37 -0
  77. pyxcp/recorder/lz4.c +2829 -0
  78. pyxcp/recorder/lz4.h +879 -0
  79. pyxcp/recorder/lz4hc.c +2041 -0
  80. pyxcp/recorder/lz4hc.h +413 -0
  81. pyxcp/recorder/mio.hpp +1714 -0
  82. pyxcp/recorder/reader.hpp +139 -0
  83. pyxcp/recorder/reco.py +277 -0
  84. pyxcp/recorder/recorder.rst +0 -0
  85. pyxcp/recorder/rekorder.cpp +59 -0
  86. pyxcp/recorder/rekorder.cpython-312-darwin.so +0 -0
  87. pyxcp/recorder/rekorder.hpp +274 -0
  88. pyxcp/recorder/setup.py +41 -0
  89. pyxcp/recorder/test_reko.py +34 -0
  90. pyxcp/recorder/unfolder.hpp +1249 -0
  91. pyxcp/recorder/wrap.cpp +189 -0
  92. pyxcp/recorder/writer.hpp +302 -0
  93. pyxcp/scripts/__init__.py +0 -0
  94. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  95. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  96. pyxcp/scripts/xcp_id_scanner.py +19 -0
  97. pyxcp/scripts/xcp_info.py +109 -0
  98. pyxcp/scripts/xcp_profile.py +27 -0
  99. pyxcp/stim/__init__.py +0 -0
  100. pyxcp/tests/test_asam_types.py +24 -0
  101. pyxcp/tests/test_binpacking.py +184 -0
  102. pyxcp/tests/test_can.py +1324 -0
  103. pyxcp/tests/test_checksum.py +95 -0
  104. pyxcp/tests/test_daq.py +188 -0
  105. pyxcp/tests/test_frame_padding.py +153 -0
  106. pyxcp/tests/test_master.py +2006 -0
  107. pyxcp/tests/test_transport.py +64 -0
  108. pyxcp/tests/test_utils.py +30 -0
  109. pyxcp/timing.py +60 -0
  110. pyxcp/transport/__init__.py +10 -0
  111. pyxcp/transport/base.py +436 -0
  112. pyxcp/transport/base_transport.hpp +0 -0
  113. pyxcp/transport/can.py +443 -0
  114. pyxcp/transport/eth.py +219 -0
  115. pyxcp/transport/sxi.py +133 -0
  116. pyxcp/transport/transport_wrapper.cpp +0 -0
  117. pyxcp/transport/usb_transport.py +213 -0
  118. pyxcp/types.py +993 -0
  119. pyxcp/utils.py +102 -0
  120. pyxcp/vector/__init__.py +0 -0
  121. pyxcp/vector/map.py +82 -0
  122. pyxcp-0.22.1.dist-info/LICENSE +165 -0
  123. pyxcp-0.22.1.dist-info/METADATA +107 -0
  124. pyxcp-0.22.1.dist-info/RECORD +126 -0
  125. pyxcp-0.22.1.dist-info/WHEEL +4 -0
  126. pyxcp-0.22.1.dist-info/entry_points.txt +7 -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
+
74
+ disable_error_handling(self.config.disable_error_handling)
75
+ self.transport_name = transport_name.lower()
76
+ transport_config = config.transport
77
+ self.transport = create_transport(transport_name, transport_config, policy, transport_layer_interface)
78
+
79
+ self.stim = Stim(self.config.stim_support)
80
+ self.stim.clear()
81
+ self.stim.set_policy_feeder(self.transport.policy.feed)
82
+ self.stim.set_frame_sender(self.transport.block_request)
83
+
84
+ # In some cases the transport-layer needs to communicate with us.
85
+ self.transport.parent = self
86
+ self.service = None
87
+
88
+ # Policies may issue XCP commands on there own.
89
+ self.transport.policy.xcp_master = self
90
+
91
+ # (D)Word (un-)packers are byte-order dependent
92
+ # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
93
+ self.BYTE_pack = None
94
+ self.BYTE_unpack = None
95
+ self.WORD_pack = None
96
+ self.WORD_unpack = None
97
+ self.DWORD_pack = None
98
+ self.DWORD_unpack = None
99
+ self.DLONG_pack = None
100
+ self.DLONG_unpack = None
101
+ self.AG_pack = None
102
+ self.AG_unpack = None
103
+ # self.connected = False
104
+ self.mta = types.MtaType(None, None)
105
+ self.currentDaqPtr = None
106
+ self.currentProtectionStatus = None
107
+ self.seed_n_key_dll = self.config.seed_n_key_dll
108
+ self.seed_n_key_function = self.config.seed_n_key_function
109
+ self.seed_n_key_dll_same_bit_width = self.config.seed_n_key_dll_same_bit_width
110
+ self.disconnect_response_optional = self.config.disconnect_response_optional
111
+ self.slaveProperties = SlaveProperties()
112
+ self.slaveProperties.pgmProcessor = SlaveProperties()
113
+ self.slaveProperties.transport_layer = self.transport_name.upper()
114
+
115
+ def __enter__(self):
116
+ """Context manager entry part."""
117
+ return self
118
+
119
+ def __exit__(self, exc_type, exc_val, exc_tb):
120
+ """Context manager exit part."""
121
+ # if self.connected:
122
+ # self.disconnect()
123
+ self.close()
124
+ if exc_type is None:
125
+ return
126
+ else:
127
+ self.succeeded = False
128
+ # print("=" * 79)
129
+ # print("Exception while in Context-Manager:\n")
130
+ self.logger.error("".join(traceback.format_exception(exc_type, exc_val, exc_tb)))
131
+ # print("=" * 79)
132
+ # return True
133
+
134
+ def _setService(self, service):
135
+ """Records the currently processed service.
136
+
137
+ Parameters
138
+ ----------
139
+ service: `pydbc.types.Command`
140
+
141
+ Note
142
+ ----
143
+ Internal Function, only to be used by transport-layer.
144
+ """
145
+ self.service = service
146
+
147
+ def close(self):
148
+ """Closes transport layer connection."""
149
+ self.transport.policy.finalize()
150
+ self.transport.close()
151
+
152
+ # Mandatory Commands.
153
+ @wrapped
154
+ def connect(self, mode=0x00):
155
+ """Build up connection to an XCP slave.
156
+
157
+ Before the actual XCP traffic starts a connection is required.
158
+
159
+ Parameters
160
+ ----------
161
+ mode : int
162
+ connection mode; default is 0x00 (normal mode)
163
+
164
+ Returns
165
+ -------
166
+ :py:obj:`pyxcp.types.ConnectResponse`
167
+ Describes fundamental client properties.
168
+
169
+ Note
170
+ ----
171
+ Every XCP slave supports at most one connection,
172
+ more attempts to connect are silently ignored.
173
+
174
+ """
175
+ self.transport.connect()
176
+
177
+ response = self.transport.request(types.Command.CONNECT, mode & 0xFF)
178
+
179
+ # First get byte-order
180
+ resultPartial = types.ConnectResponsePartial.parse(response)
181
+ byteOrder = resultPartial.commModeBasic.byteOrder
182
+
183
+ result = types.ConnectResponse.parse(response, byteOrder=byteOrder)
184
+ byteOrderPrefix = "<" if byteOrder == types.ByteOrder.INTEL else ">"
185
+ self.slaveProperties.byteOrder = byteOrder
186
+ self.slaveProperties.maxCto = result.maxCto
187
+ self.slaveProperties.maxDto = result.maxDto
188
+ self.slaveProperties.supportsPgm = result.resource.pgm
189
+ self.slaveProperties.supportsStim = result.resource.stim
190
+ self.slaveProperties.supportsDaq = result.resource.daq
191
+ self.slaveProperties.supportsCalpag = result.resource.calpag
192
+ self.slaveProperties.slaveBlockMode = result.commModeBasic.slaveBlockMode
193
+ self.slaveProperties.addressGranularity = result.commModeBasic.addressGranularity
194
+ self.slaveProperties.protocolLayerVersion = result.protocolLayerVersion
195
+ self.slaveProperties.transportLayerVersion = result.transportLayerVersion
196
+ self.slaveProperties.optionalCommMode = result.commModeBasic.optional
197
+ self.slaveProperties.maxWriteDaqMultipleElements = (
198
+ 0 if self.slaveProperties.maxCto < 10 else int((self.slaveProperties.maxCto - 2) // 8)
199
+ )
200
+ self.BYTE_pack = makeBytePacker(byteOrderPrefix)
201
+ self.BYTE_unpack = makeByteUnpacker(byteOrderPrefix)
202
+ self.WORD_pack = makeWordPacker(byteOrderPrefix)
203
+ self.WORD_unpack = makeWordUnpacker(byteOrderPrefix)
204
+ self.DWORD_pack = makeDWordPacker(byteOrderPrefix)
205
+ self.DWORD_unpack = makeDWordUnpacker(byteOrderPrefix)
206
+ self.DLONG_pack = makeDLongPacker(byteOrderPrefix)
207
+ self.DLONG_unpack = makeDLongUnpacker(byteOrderPrefix)
208
+ self.slaveProperties.bytesPerElement = None # Download/Upload commands are using element- not byte-count.
209
+ if self.slaveProperties.addressGranularity == types.AddressGranularity.BYTE:
210
+ self.AG_pack = struct.Struct("<B").pack
211
+ self.AG_unpack = struct.Struct("<B").unpack
212
+ self.slaveProperties.bytesPerElement = 1
213
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
214
+ self.AG_pack = self.WORD_pack
215
+ self.AG_unpack = self.WORD_unpack
216
+ self.slaveProperties.bytesPerElement = 2
217
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
218
+ self.AG_pack = self.DWORD_pack
219
+ self.AG_unpack = self.DWORD_unpack
220
+ self.slaveProperties.bytesPerElement = 4
221
+ # self.connected = True
222
+ return result
223
+
224
+ @wrapped
225
+ def disconnect(self):
226
+ """Releases the connection to the XCP slave.
227
+
228
+ Thereafter, no further communication with the slave is possible
229
+ (besides `connect`).
230
+
231
+
232
+ Note
233
+ -----
234
+ - If DISCONNECT is currently not possible, ERR_CMD_BUSY will be returned.
235
+ - While XCP spec. requires a response, this behavior can be made optional by adding
236
+ - `DISCONNECT_RESPONSE_OPTIONAL = true` (TOML)
237
+ - `"DISCONNECT_RESPONSE_OPTIONAL": true` (JSON)
238
+ to your configuration file.
239
+ """
240
+ if self.disconnect_response_optional:
241
+ response = self.transport.request_optional_response(types.Command.DISCONNECT)
242
+ else:
243
+ response = self.transport.request(types.Command.DISCONNECT)
244
+ # self.connected = False
245
+ return response
246
+
247
+ @wrapped
248
+ def getStatus(self):
249
+ """Get current status information of the slave device.
250
+
251
+ This includes the status of the resource protection, pending store
252
+ requests and the general status of data acquisition and stimulation.
253
+
254
+ Returns
255
+ -------
256
+ :obj:`pyxcp.types.GetStatusResponse`
257
+ """
258
+ response = self.transport.request(types.Command.GET_STATUS)
259
+ result = types.GetStatusResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
260
+ self._setProtectionStatus(result.resourceProtectionStatus)
261
+ return result
262
+
263
+ @wrapped
264
+ def synch(self):
265
+ """Synchronize command execution after timeout conditions."""
266
+ response = self.transport.request(types.Command.SYNCH)
267
+ return response
268
+
269
+ @wrapped
270
+ def getCommModeInfo(self):
271
+ """Get optional information on different Communication Modes supported
272
+ by the slave.
273
+
274
+ Returns
275
+ -------
276
+ :obj:`pyxcp.types.GetCommModeInfoResponse`
277
+ """
278
+ response = self.transport.request(types.Command.GET_COMM_MODE_INFO)
279
+ result = types.GetCommModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
280
+ self.slaveProperties.interleavedMode = result.commModeOptional.interleavedMode
281
+ self.slaveProperties.masterBlockMode = result.commModeOptional.masterBlockMode
282
+ self.slaveProperties.maxBs = result.maxBs
283
+ self.slaveProperties.minSt = result.minSt
284
+ self.slaveProperties.queueSize = result.queueSize
285
+ self.slaveProperties.xcpDriverVersionNumber = result.xcpDriverVersionNumber
286
+ return result
287
+
288
+ @wrapped
289
+ def getId(self, mode: int):
290
+ """This command is used for automatic session configuration and for
291
+ slave device identification.
292
+
293
+ Parameters
294
+ ----------
295
+ mode : int
296
+ The following identification types may be requested:
297
+ - 0 ASCII text
298
+ - 1 ASAM-MC2 filename without path and extension
299
+ - 2 ASAM-MC2 filename with path and extension
300
+ - 3 URL where the ASAM-MC2 file can be found
301
+ - 4 ASAM-MC2 file to upload
302
+ - 128..255 User defined
303
+
304
+ Returns
305
+ -------
306
+ :obj:`pydbc.types.GetIDResponse`
307
+ """
308
+ response = self.transport.request(types.Command.GET_ID, mode)
309
+ result = types.GetIDResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
310
+ result.length = self.DWORD_unpack(response[3:7])[0]
311
+ return result
312
+
313
+ @wrapped
314
+ def setRequest(self, mode: int, sessionConfigurationId: int):
315
+ """Request to save to non-volatile memory.
316
+
317
+ Parameters
318
+ ----------
319
+ mode : int (bitfield)
320
+ - 1 Request to store calibration data
321
+ - 2 Request to store DAQ list, no resume
322
+ - 4 Request to store DAQ list, resume enabled
323
+ - 8 Request to clear DAQ configuration
324
+ sessionConfigurationId : int
325
+
326
+ """
327
+ return self.transport.request(
328
+ types.Command.SET_REQUEST,
329
+ mode,
330
+ sessionConfigurationId >> 8,
331
+ sessionConfigurationId & 0xFF,
332
+ )
333
+
334
+ @wrapped
335
+ def getSeed(self, first: int, resource: int):
336
+ """Get seed from slave for unlocking a protected resource.
337
+
338
+ Parameters
339
+ ----------
340
+ first : int
341
+ - 0 - first part of seed
342
+ - 1 - remaining part
343
+ resource : int
344
+ - Mode = =0 - Resource
345
+ - Mode == 1 - Don't care
346
+
347
+ Returns
348
+ -------
349
+ `pydbc.types.GetSeedResponse`
350
+ """
351
+ if self.transport_name == "can":
352
+ # for CAN it might happen that the seed is longer than the max DLC
353
+ # in this case the first byte will be the current remaining seed size
354
+ # followed by the seeds bytes that can fit in the current frame
355
+ # the master must call getSeed several times until the complete seed is received
356
+ response = self.transport.request(types.Command.GET_SEED, first, resource)
357
+ size, seed = response[0], response[1:]
358
+ if size < len(seed):
359
+ seed = seed[:size]
360
+ reply = types.GetSeedResponse.parse(
361
+ types.GetSeedResponse.build({"length": size, "seed": bytes(size)}),
362
+ byteOrder=self.slaveProperties.byteOrder,
363
+ )
364
+ reply.seed = seed
365
+ return reply
366
+ else:
367
+ response = self.transport.request(types.Command.GET_SEED, first, resource)
368
+ return types.GetSeedResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
369
+
370
+ @wrapped
371
+ def unlock(self, length: int, key: bytes):
372
+ """Send key to slave for unlocking a protected resource.
373
+
374
+ Parameters
375
+ ----------
376
+ length : int
377
+ indicates the (remaining) number of key bytes.
378
+ key : bytes
379
+
380
+ Returns
381
+ -------
382
+ :obj:`pydbc.types.ResourceType`
383
+
384
+ Note
385
+ ----
386
+ The master has to use :meth:`unlock` in a defined sequence together
387
+ with :meth:`getSeed`. The master only can send an :meth:`unlock` sequence
388
+ if previously there was a :meth:`getSeed` sequence. The master has
389
+ to send the first `unlocking` after a :meth:`getSeed` sequence with
390
+ a Length containing the total length of the key.
391
+ """
392
+ response = self.transport.request(types.Command.UNLOCK, length, *key)
393
+ result = types.ResourceType.parse(response, byteOrder=self.slaveProperties.byteOrder)
394
+ self._setProtectionStatus(result)
395
+ return result
396
+
397
+ @wrapped
398
+ def setMta(self, address: int, addressExt: int = 0x00):
399
+ """Set Memory Transfer Address in slave.
400
+
401
+ Parameters
402
+ ----------
403
+ address : int
404
+ addressExt : int
405
+
406
+ Note
407
+ ----
408
+ The MTA is used by :meth:`buildChecksum`, :meth:`upload`, :meth:`download`, :meth:`downloadNext`,
409
+ :meth:`downloadMax`, :meth:`modifyBits`, :meth:`programClear`, :meth:`program`, :meth:`programNext`
410
+ and :meth:`programMax`.
411
+
412
+ """
413
+ self.mta = types.MtaType(address, addressExt) # Keep track of MTA (needed for error-handling).
414
+ addr = self.DWORD_pack(address)
415
+ return self.transport.request(types.Command.SET_MTA, 0, 0, addressExt, *addr)
416
+
417
+ @wrapped
418
+ def upload(self, length: int):
419
+ """Transfer data from slave to master.
420
+
421
+ Parameters
422
+ ----------
423
+ length : int
424
+ Number of elements (address granularity).
425
+
426
+ Note
427
+ ----
428
+ Adress is set via :meth:`setMta` (Some services like :meth:`getID` also set the MTA).
429
+
430
+ Returns
431
+ -------
432
+ bytes
433
+ """
434
+ byte_count = length * self.slaveProperties.bytesPerElement
435
+ response = self.transport.request(types.Command.UPLOAD, length)
436
+ if byte_count > (self.slaveProperties.maxCto - 1):
437
+ block_response = self.transport.block_receive(length_required=(byte_count - len(response)))
438
+ response += block_response
439
+ elif self.transport_name == "can":
440
+ # larger sizes will send in multiple CAN messages
441
+ # each valid message will start with 0xFF followed by the upload bytes
442
+ # the last message might be padded to the required DLC
443
+ rem = byte_count - len(response)
444
+ while rem:
445
+ if len(self.transport.resQueue):
446
+ data = self.transport.resQueue.popleft()
447
+ response += data[1 : rem + 1]
448
+ rem = byte_count - len(response)
449
+ else:
450
+ short_sleep()
451
+ return response
452
+
453
+ @wrapped
454
+ def shortUpload(self, length: int, address: int, addressExt: int = 0x00):
455
+ """Transfer data from slave to master.
456
+ As opposed to :meth:`upload` this service also includes address information.
457
+
458
+ Parameters
459
+ ----------
460
+ length : int
461
+ Number of elements (address granularity).
462
+ address : int
463
+ addressExt : int
464
+
465
+ Returns
466
+ -------
467
+ bytes
468
+ """
469
+ addr = self.DWORD_pack(address)
470
+ byte_count = length * self.slaveProperties.bytesPerElement
471
+ max_byte_count = self.slaveProperties.maxCto - 1
472
+ if byte_count > max_byte_count:
473
+ self.logger.warn(f"SHORT_UPLOAD: {byte_count} bytes exceeds the maximum value of {max_byte_count}.")
474
+ response = self.transport.request(types.Command.SHORT_UPLOAD, length, 0, addressExt, *addr)
475
+ return response[:byte_count]
476
+
477
+ @wrapped
478
+ def buildChecksum(self, blocksize: int):
479
+ """Build checksum over memory range.
480
+
481
+ Parameters
482
+ ----------
483
+ blocksize : int
484
+
485
+ Returns
486
+ -------
487
+ :obj:`~pyxcp.types.BuildChecksumResponse`
488
+
489
+ .. note:: Adress is set via `setMta`
490
+
491
+ See Also
492
+ --------
493
+ :mod:`~pyxcp.checksum`
494
+ """
495
+ bs = self.DWORD_pack(blocksize)
496
+ response = self.transport.request(types.Command.BUILD_CHECKSUM, 0, 0, 0, *bs)
497
+ return types.BuildChecksumResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
498
+
499
+ @wrapped
500
+ def transportLayerCmd(self, subCommand: int, *data: List[bytes]):
501
+ """Execute transfer-layer specific command.
502
+
503
+ Parameters
504
+ ----------
505
+ subCommand : int
506
+ data : bytes
507
+
508
+ Note
509
+ ----
510
+ For details refer to XCP specification.
511
+ """
512
+ return self.transport.request_optional_response(types.Command.TRANSPORT_LAYER_CMD, subCommand, *data)
513
+
514
+ @wrapped
515
+ def userCmd(self, subCommand: int, data: bytes):
516
+ """Execute proprietary command implemented in your XCP client.
517
+
518
+ Parameters
519
+ ----------
520
+ subCommand : int
521
+ data : bytes
522
+
523
+
524
+ .. note:: For details refer to your XCP client vendor.
525
+ """
526
+
527
+ response = self.transport.request(types.Command.USER_CMD, subCommand, *data)
528
+ return response
529
+
530
+ @wrapped
531
+ def getVersion(self):
532
+ """Get version information.
533
+
534
+ This command returns detailed information about the implemented
535
+ protocol layer version of the XCP slave and the transport layer
536
+ currently in use.
537
+
538
+ Returns
539
+ -------
540
+ :obj:`~types.GetVersionResponse`
541
+ """
542
+
543
+ response = self.transport.request(types.Command.GET_VERSION)
544
+ result = types.GetVersionResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
545
+ self.slaveProperties.protocolMajor = result.protocolMajor
546
+ self.slaveProperties.protocolMinor = result.protocolMinor
547
+ self.slaveProperties.transportMajor = result.transportMajor
548
+ self.slaveProperties.transportMinor = result.transportMinor
549
+ return result
550
+
551
+ def fetch(self, length: int, limitPayload: int = None): # TODO: pull
552
+ """Convenience function for data-transfer from slave to master
553
+ (Not part of the XCP Specification).
554
+
555
+ Parameters
556
+ ----------
557
+ length : int
558
+ limitPayload : int
559
+ transfer less bytes then supported by transport-layer
560
+
561
+ Returns
562
+ -------
563
+ bytes
564
+
565
+ Note
566
+ ----
567
+ address is not included because of services implicitly setting address information like :meth:`getID` .
568
+ """
569
+ if limitPayload and limitPayload < 8:
570
+ raise ValueError(f"Payload must be at least 8 bytes - given: {limitPayload}")
571
+
572
+ slaveBlockMode = self.slaveProperties.slaveBlockMode
573
+ if slaveBlockMode:
574
+ maxPayload = 255
575
+ else:
576
+ maxPayload = self.slaveProperties.maxCto - 1
577
+ payload = min(limitPayload, maxPayload) if limitPayload else maxPayload
578
+ chunkSize = payload
579
+ chunks = range(length // chunkSize)
580
+ remaining = length % chunkSize
581
+ result = []
582
+ for _ in chunks:
583
+ data = self.upload(chunkSize)
584
+ result.extend(data)
585
+ if remaining:
586
+ data = self.upload(remaining)
587
+ result.extend(data)
588
+ return bytes(result)
589
+
590
+ pull = fetch # fetch() may be completely replaced by pull() someday.
591
+
592
+ def push(self, address: int, data: bytes, callback=None):
593
+ """Convenience function for data-transfer from master to slave.
594
+ (Not part of the XCP Specification).
595
+
596
+ Parameters
597
+ ----------
598
+ address: int
599
+
600
+ data : bytes
601
+ Arbitrary number of bytes.
602
+
603
+ Returns
604
+ -------
605
+ """
606
+ self._generalized_downloader(
607
+ address=address,
608
+ data=data,
609
+ maxCto=self.slaveProperties.maxCto,
610
+ maxBs=self.slaveProperties.maxBs,
611
+ minSt=self.slaveProperties.minSt,
612
+ master_block_mode=self.slaveProperties.masterBlockMode,
613
+ dl_func=self.download,
614
+ dl_next_func=self.downloadNext,
615
+ callback=callback,
616
+ )
617
+
618
+ def flash_program(self, address: int, data: bytes, callback=None):
619
+ """Convenience function for flash programing.
620
+ (Not part of the XCP Specification).
621
+
622
+ Parameters
623
+ ----------
624
+ address: int
625
+
626
+ data : bytes
627
+ Arbitrary number of bytes.
628
+
629
+ Returns
630
+ -------
631
+ """
632
+ self._generalized_downloader(
633
+ address=address,
634
+ data=data,
635
+ maxCto=self.slaveProperties.pgmProcessor.maxCtoPgm,
636
+ maxBs=self.slaveProperties.pgmProcessor.maxBsPgm,
637
+ minSt=self.slaveProperties.pgmProcessor.minStPgm,
638
+ master_block_mode=self.slaveProperties.pgmProcessor.masterBlockMode,
639
+ dl_func=self.program,
640
+ dl_next_func=self.programNext,
641
+ callback=callback,
642
+ )
643
+
644
+ def _generalized_downloader(
645
+ self,
646
+ address: 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)
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