pyxcp 0.22.16__cp310-cp310-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 +1101 -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-310-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-310-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 +2031 -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-310-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 +439 -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.16.dist-info/LICENSE +165 -0
  125. pyxcp-0.22.16.dist-info/METADATA +107 -0
  126. pyxcp-0.22.16.dist-info/RECORD +128 -0
  127. pyxcp-0.22.16.dist-info/WHEEL +6 -0
  128. pyxcp-0.22.16.dist-info/entry_points.txt +8 -0
pyxcp/master/master.py ADDED
@@ -0,0 +1,2031 @@
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, address_ext: 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
+ address_ext=address_ext,
609
+ data=data,
610
+ maxCto=self.slaveProperties.maxCto,
611
+ maxBs=self.slaveProperties.maxBs,
612
+ minSt=self.slaveProperties.minSt,
613
+ master_block_mode=self.slaveProperties.masterBlockMode,
614
+ dl_func=self.download,
615
+ dl_next_func=self.downloadNext,
616
+ callback=callback,
617
+ )
618
+
619
+ def flash_program(self, address: int, data: bytes, callback=None):
620
+ """Convenience function for flash programing.
621
+ (Not part of the XCP Specification).
622
+
623
+ Parameters
624
+ ----------
625
+ address: int
626
+
627
+ data : bytes
628
+ Arbitrary number of bytes.
629
+
630
+ Returns
631
+ -------
632
+ """
633
+ self._generalized_downloader(
634
+ address=address,
635
+ data=data,
636
+ maxCto=self.slaveProperties.pgmProcessor.maxCtoPgm,
637
+ maxBs=self.slaveProperties.pgmProcessor.maxBsPgm,
638
+ minSt=self.slaveProperties.pgmProcessor.minStPgm,
639
+ master_block_mode=self.slaveProperties.pgmProcessor.masterBlockMode,
640
+ dl_func=self.program,
641
+ dl_next_func=self.programNext,
642
+ callback=callback,
643
+ )
644
+
645
+ def _generalized_downloader(
646
+ self,
647
+ address: int,
648
+ address_ext: int,
649
+ data: bytes,
650
+ maxCto: int,
651
+ maxBs: int,
652
+ minSt: int,
653
+ master_block_mode: bool,
654
+ dl_func,
655
+ dl_next_func,
656
+ callback=None,
657
+ ):
658
+ """ """
659
+ self.setMta(address, address_ext)
660
+ minSt /= 10000.0
661
+ block_downloader = functools.partial(
662
+ self._block_downloader,
663
+ dl_func=dl_func,
664
+ dl_next_func=dl_next_func,
665
+ minSt=minSt,
666
+ )
667
+ total_length = len(data)
668
+ if master_block_mode:
669
+ max_payload = min(maxBs * (maxCto - 2), 255)
670
+ else:
671
+ max_payload = maxCto - 2
672
+ offset = 0
673
+ if master_block_mode:
674
+ remaining = total_length
675
+ blocks = range(total_length // max_payload)
676
+ percent_complete = 1
677
+ remaining_block_size = total_length % max_payload
678
+ for _ in blocks:
679
+ block = data[offset : offset + max_payload]
680
+ block_downloader(block)
681
+ offset += max_payload
682
+ remaining -= max_payload
683
+ if callback and remaining <= total_length - (total_length / 100) * percent_complete:
684
+ callback(percent_complete)
685
+ percent_complete += 1
686
+ if remaining_block_size:
687
+ block = data[offset : offset + remaining_block_size]
688
+ block_downloader(block)
689
+ if callback:
690
+ callback(percent_complete)
691
+ else:
692
+ chunk_size = max_payload
693
+ chunks = range(total_length // chunk_size)
694
+ remaining = total_length % chunk_size
695
+ percent_complete = 1
696
+ callback_remaining = total_length
697
+ for _ in chunks:
698
+ block = data[offset : offset + max_payload]
699
+ dl_func(block, max_payload, last=True)
700
+ offset += max_payload
701
+ callback_remaining -= chunk_size
702
+ if callback and callback_remaining <= total_length - (total_length / 100) * percent_complete:
703
+ callback(percent_complete)
704
+ percent_complete += 1
705
+ if remaining:
706
+ block = data[offset : offset + remaining]
707
+ dl_func(block, remaining, last=True)
708
+ if callback:
709
+ callback(percent_complete)
710
+
711
+ def _block_downloader(self, data: bytes, dl_func=None, dl_next_func=None, minSt=0):
712
+ """Re-usable block downloader.
713
+
714
+ Parameters
715
+ ----------
716
+ data : bytes
717
+ Arbitrary number of bytes.
718
+
719
+ dl_func: method
720
+ usually :meth: `download` or :meth:`program`
721
+
722
+ dl_next_func: method
723
+ usually :meth: `downloadNext` or :meth:`programNext`
724
+
725
+ minSt: int
726
+ Minimum separation time of frames.
727
+ """
728
+ length = len(data)
729
+ max_packet_size = self.slaveProperties.maxCto - 2 # Command ID + Length
730
+ packets = range(length // max_packet_size)
731
+ offset = 0
732
+ remaining = length % max_packet_size
733
+ remaining_block_size = length
734
+ index = 0
735
+ for index in packets:
736
+ packet_data = data[offset : offset + max_packet_size]
737
+ last = (remaining_block_size - max_packet_size) == 0
738
+ if index == 0:
739
+ dl_func(packet_data, length, last) # Transmit the complete length in the first CTO.
740
+ else:
741
+ dl_next_func(packet_data, remaining_block_size, last)
742
+ offset += max_packet_size
743
+ remaining_block_size -= max_packet_size
744
+ delay(minSt)
745
+ if remaining:
746
+ packet_data = data[offset : offset + remaining]
747
+ if index == 0:
748
+ # length of data is smaller than maxCto - 2
749
+ dl_func(packet_data, remaining, last=True)
750
+ else:
751
+ dl_next_func(packet_data, remaining, last=True)
752
+ delay(minSt)
753
+
754
+ @wrapped
755
+ def download(self, data: bytes, blockModeLength=None, last=False):
756
+ """Transfer data from master to slave.
757
+
758
+ Parameters
759
+ ----------
760
+ data : bytes
761
+ Data to send to slave.
762
+ blockModeLength : int or None
763
+ for block mode, the download request must contain the length of the whole block,
764
+ not just the length in the current packet. The whole block length can be given here for block-mode
765
+ transfers. For normal mode, the length indicates the actual packet's payload length.
766
+
767
+ Note
768
+ ----
769
+ Adress is set via :meth:`setMta`
770
+ """
771
+
772
+ if blockModeLength is None or last:
773
+ # standard mode
774
+ length = len(data)
775
+ response = self.transport.request(types.Command.DOWNLOAD, length, *data)
776
+ return response
777
+ else:
778
+ # block mode
779
+ if not isinstance(blockModeLength, int):
780
+ raise TypeError("blockModeLength must be int!")
781
+ self.transport.block_request(types.Command.DOWNLOAD, blockModeLength, *data)
782
+ return None
783
+
784
+ @wrapped
785
+ def downloadNext(self, data: bytes, remainingBlockLength, last=False):
786
+ """Transfer data from master to slave (block mode).
787
+
788
+ Parameters
789
+ ----------
790
+ data : bytes
791
+ remainingBlockLength : int
792
+ This parameter has to be given the remaining length in the block
793
+ last : bool
794
+ The block mode implementation shall indicate the last packet in the block with this parameter, because
795
+ the slave device will send the response after this.
796
+ """
797
+
798
+ if last:
799
+ # last DOWNLOAD_NEXT packet in a block: the slave device has to send the response after this.
800
+ response = self.transport.request(types.Command.DOWNLOAD_NEXT, remainingBlockLength, *data)
801
+ return response
802
+ else:
803
+ # the slave device won't respond to consecutive DOWNLOAD_NEXT packets in block mode,
804
+ # so we must not wait for any response
805
+ self.transport.block_request(types.Command.DOWNLOAD_NEXT, remainingBlockLength, *data)
806
+ return None
807
+
808
+ @wrapped
809
+ def downloadMax(self, data: bytes):
810
+ """Transfer data from master to slave (fixed size).
811
+
812
+ Parameters
813
+ ----------
814
+ data : bytes
815
+ """
816
+ return self.transport.request(types.Command.DOWNLOAD_MAX, *data)
817
+
818
+ @wrapped
819
+ def shortDownload(self, address, addressExt, data):
820
+ length = len(data)
821
+ addr = self.DWORD_pack(address)
822
+ return self.transport.request(types.Command.SHORT_DOWNLOAD, length, 0, addressExt, *addr, *data)
823
+
824
+ @wrapped
825
+ def modifyBits(self, shiftValue, andMask, xorMask):
826
+ # A = ( (A) & ((~((dword)(((word)~MA)<<S))) )^((dword)(MX<<S)) )
827
+ am = self.WORD_pack(andMask)
828
+ xm = self.WORD_pack(xorMask)
829
+ return self.transport.request(types.Command.MODIFY_BITS, shiftValue, *am, *xm)
830
+
831
+ # Page Switching Commands (PAG)
832
+ @wrapped
833
+ def setCalPage(self, mode: int, logicalDataSegment: int, logicalDataPage: int):
834
+ """Set calibration page.
835
+
836
+ Parameters
837
+ ----------
838
+ mode : int (bitfield)
839
+ - 0x01 - The given page will be used by the slave device application.
840
+ - 0x02 - The slave device XCP driver will access the given page.
841
+ - 0x80 - The logical segment number is ignored. The command applies to all segments
842
+ logicalDataSegment : int
843
+ logicalDataPage : int
844
+ """
845
+ return self.transport.request(types.Command.SET_CAL_PAGE, mode, logicalDataSegment, logicalDataPage)
846
+
847
+ @wrapped
848
+ def getCalPage(self, mode: int, logicalDataSegment: int):
849
+ """Get calibration page
850
+
851
+ Parameters
852
+ ----------
853
+ mode : int
854
+ logicalDataSegment : int
855
+ """
856
+ response = self.transport.request(types.Command.GET_CAL_PAGE, mode, logicalDataSegment)
857
+ return response[2]
858
+
859
+ @wrapped
860
+ def getPagProcessorInfo(self):
861
+ """Get general information on PAG processor.
862
+
863
+ Returns
864
+ -------
865
+ `pydbc.types.GetPagProcessorInfoResponse`
866
+ """
867
+ response = self.transport.request(types.Command.GET_PAG_PROCESSOR_INFO)
868
+ return types.GetPagProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
869
+
870
+ @wrapped
871
+ def getSegmentInfo(self, mode, segmentNumber, segmentInfo, mappingIndex):
872
+ """Get specific information for a segment.
873
+
874
+ Parameters
875
+ ----------
876
+ mode : int
877
+ - 0 = get basic address info for this segment
878
+ - 1 = get standard info for this segment
879
+ - 2 = get address mapping info for this segment
880
+
881
+ segmentNumber : int
882
+ segmentInfo : int
883
+ Mode 0:
884
+ - 0 = address
885
+ - 1 = length
886
+
887
+ Mode 1:
888
+ - don't care
889
+
890
+ Mode 2:
891
+ - 0 = source address
892
+ - 1 = destination address
893
+ - 2 = length address
894
+
895
+ mappingIndex : int
896
+ - Mode 0: don't care
897
+ - Mode 1: don't care
898
+ - Mode 2: identifier for address mapping range that mapping_info belongs to.
899
+
900
+ """
901
+ response = self.transport.request(
902
+ types.Command.GET_SEGMENT_INFO,
903
+ mode,
904
+ segmentNumber,
905
+ segmentInfo,
906
+ mappingIndex,
907
+ )
908
+ if mode == 0:
909
+ return types.GetSegmentInfoMode0Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
910
+ elif mode == 1:
911
+ return types.GetSegmentInfoMode1Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
912
+ elif mode == 2:
913
+ return types.GetSegmentInfoMode2Response.parse(response, byteOrder=self.slaveProperties.byteOrder)
914
+
915
+ @wrapped
916
+ def getPageInfo(self, segmentNumber, pageNumber):
917
+ """Get specific information for a page.
918
+
919
+ Parameters
920
+ ----------
921
+ segmentNumber : int
922
+ pageNumber : int
923
+ """
924
+ response = self.transport.request(types.Command.GET_PAGE_INFO, 0, segmentNumber, pageNumber)
925
+ return (
926
+ types.PageProperties.parse(bytes([response[0]]), byteOrder=self.slaveProperties.byteOrder),
927
+ response[1],
928
+ )
929
+
930
+ @wrapped
931
+ def setSegmentMode(self, mode, segmentNumber):
932
+ """Set mode for a segment.
933
+
934
+ Parameters
935
+ ----------
936
+ mode : int (bitfield)
937
+ 1 = enable FREEZE Mode
938
+ segmentNumber : int
939
+ """
940
+ return self.transport.request(types.Command.SET_SEGMENT_MODE, mode, segmentNumber)
941
+
942
+ @wrapped
943
+ def getSegmentMode(self, segmentNumber):
944
+ """Get mode for a segment.
945
+
946
+ Parameters
947
+ ----------
948
+ segmentNumber : int
949
+ """
950
+ response = self.transport.request(types.Command.GET_SEGMENT_MODE, 0, segmentNumber)
951
+ return response[1]
952
+
953
+ @wrapped
954
+ def copyCalPage(self, srcSegment, srcPage, dstSegment, dstPage):
955
+ """Copy page.
956
+
957
+ Parameters
958
+ ----------
959
+ srcSegment : int
960
+ srcPage : int
961
+ dstSegment : int
962
+ dstPage : int
963
+ """
964
+ return self.transport.request(types.Command.COPY_CAL_PAGE, srcSegment, srcPage, dstSegment, dstPage)
965
+
966
+ # DAQ
967
+
968
+ @wrapped
969
+ def setDaqPtr(self, daqListNumber: int, odtNumber: int, odtEntryNumber: int):
970
+ self.currentDaqPtr = types.DaqPtr(daqListNumber, odtNumber, odtEntryNumber) # Needed for errorhandling.
971
+ daqList = self.WORD_pack(daqListNumber)
972
+ response = self.transport.request(types.Command.SET_DAQ_PTR, 0, *daqList, odtNumber, odtEntryNumber)
973
+ self.stim.setDaqPtr(daqListNumber, odtNumber, odtEntryNumber)
974
+ return response
975
+
976
+ @wrapped
977
+ def clearDaqList(self, daqListNumber: int):
978
+ """Clear DAQ list configuration.
979
+
980
+ Parameters
981
+ ----------
982
+ daqListNumber : int
983
+ """
984
+ daqList = self.WORD_pack(daqListNumber)
985
+ result = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList)
986
+ self.stim.clearDaqList(daqListNumber)
987
+ return result
988
+
989
+ @wrapped
990
+ def writeDaq(self, bitOffset: int, entrySize: int, addressExt: int, address: int):
991
+ """Write element in ODT entry.
992
+
993
+ Parameters
994
+ ----------
995
+ bitOffset : int
996
+ Position of bit in 32-bit variable referenced by the address and
997
+ extension below
998
+ entrySize : int
999
+ addressExt : int
1000
+ address : int
1001
+ """
1002
+ addr = self.DWORD_pack(address)
1003
+ result = self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr)
1004
+ self.stim.writeDaq(bitOffset, entrySize, addressExt, address)
1005
+ return result
1006
+
1007
+ @wrapped
1008
+ def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority):
1009
+ dln = self.WORD_pack(daqListNumber)
1010
+ ecn = self.WORD_pack(eventChannelNumber)
1011
+ self.stim.setDaqListMode(mode, daqListNumber, eventChannelNumber, prescaler, priority)
1012
+ return self.transport.request(types.Command.SET_DAQ_LIST_MODE, mode, *dln, *ecn, prescaler, priority)
1013
+
1014
+ @wrapped
1015
+ def getDaqListMode(self, daqListNumber):
1016
+ """Get mode from DAQ list.
1017
+
1018
+ Parameters
1019
+ ----------
1020
+ daqListNumber : int
1021
+
1022
+ Returns
1023
+ -------
1024
+ `pyxcp.types.GetDaqListModeResponse`
1025
+ """
1026
+ dln = self.WORD_pack(daqListNumber)
1027
+ response = self.transport.request(types.Command.GET_DAQ_LIST_MODE, 0, *dln)
1028
+ return types.GetDaqListModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1029
+
1030
+ @wrapped
1031
+ def startStopDaqList(self, mode: int, daqListNumber: int):
1032
+ """Start /stop/select DAQ list.
1033
+
1034
+ Parameters
1035
+ ----------
1036
+ mode : int
1037
+ 0 = stop
1038
+ 1 = start
1039
+ 2 = select
1040
+ daqListNumber : int
1041
+ """
1042
+ dln = self.WORD_pack(daqListNumber)
1043
+ response = self.transport.request(types.Command.START_STOP_DAQ_LIST, mode, *dln)
1044
+ self.stim.startStopDaqList(mode, daqListNumber)
1045
+ firstPid = types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1046
+ self.stim.set_first_pid(daqListNumber, firstPid.firstPid)
1047
+ return firstPid
1048
+
1049
+ @wrapped
1050
+ def startStopSynch(self, mode):
1051
+ """Start/stop DAQ lists (synchronously).
1052
+
1053
+ Parameters
1054
+ ----------
1055
+ mode : int
1056
+ 0 = stop all
1057
+ 1 = start selected
1058
+ 2 = stop selected
1059
+ """
1060
+ res = self.transport.request(types.Command.START_STOP_SYNCH, mode)
1061
+ self.stim.startStopSynch(mode)
1062
+ return res
1063
+
1064
+ @wrapped
1065
+ def writeDaqMultiple(self, daqElements):
1066
+ """Write multiple elements in ODT.
1067
+
1068
+ Parameters
1069
+ ----------
1070
+ daqElements : list of `dict` containing the following keys: *bitOffset*, *size*, *address*, *addressExt*.
1071
+ """
1072
+ if len(daqElements) > self.slaveProperties.maxWriteDaqMultipleElements:
1073
+ raise ValueError(f"At most {self.slaveProperties.maxWriteDaqMultipleElements} daqElements are permitted.")
1074
+ data = bytearray()
1075
+ data.append(len(daqElements))
1076
+
1077
+ for daqElement in daqElements:
1078
+ data.extend(types.DaqElement.build(daqElement, byteOrder=self.slaveProperties.byteOrder))
1079
+
1080
+ return self.transport.request(types.Command.WRITE_DAQ_MULTIPLE, *data)
1081
+
1082
+ # optional
1083
+ @wrapped
1084
+ def getDaqClock(self):
1085
+ """Get DAQ clock from slave.
1086
+
1087
+ Returns
1088
+ -------
1089
+ int
1090
+ Current timestamp, format specified by `getDaqResolutionInfo`
1091
+ """
1092
+ response = self.transport.request(types.Command.GET_DAQ_CLOCK)
1093
+ result = types.GetDaqClockResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1094
+ return result.timestamp
1095
+
1096
+ @wrapped
1097
+ def readDaq(self):
1098
+ """Read element from ODT entry.
1099
+
1100
+ Returns
1101
+ -------
1102
+ `pyxcp.types.ReadDaqResponse`
1103
+ """
1104
+ response = self.transport.request(types.Command.READ_DAQ)
1105
+ return types.ReadDaqResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1106
+
1107
+ @wrapped
1108
+ def getDaqProcessorInfo(self):
1109
+ """Get general information on DAQ processor.
1110
+
1111
+ Returns
1112
+ -------
1113
+ `pyxcp.types.GetDaqProcessorInfoResponse`
1114
+ """
1115
+ response = self.transport.request(types.Command.GET_DAQ_PROCESSOR_INFO)
1116
+ return types.GetDaqProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1117
+
1118
+ @wrapped
1119
+ def getDaqResolutionInfo(self):
1120
+ """Get general information on DAQ processing resolution.
1121
+
1122
+ Returns
1123
+ -------
1124
+ `pyxcp.types.GetDaqResolutionInfoResponse`
1125
+ """
1126
+ response = self.transport.request(types.Command.GET_DAQ_RESOLUTION_INFO)
1127
+ return types.GetDaqResolutionInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1128
+
1129
+ @wrapped
1130
+ def getDaqListInfo(self, daqListNumber):
1131
+ """Get specific information for a DAQ list.
1132
+
1133
+ Parameters
1134
+ ----------
1135
+ daqListNumber : int
1136
+ """
1137
+ dln = self.WORD_pack(daqListNumber)
1138
+ response = self.transport.request(types.Command.GET_DAQ_LIST_INFO, 0, *dln)
1139
+ return types.GetDaqListInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1140
+
1141
+ @wrapped
1142
+ def getDaqEventInfo(self, eventChannelNumber):
1143
+ """Get specific information for an event channel.
1144
+
1145
+ Parameters
1146
+ ----------
1147
+ eventChannelNumber : int
1148
+
1149
+ Returns
1150
+ -------
1151
+ `pyxcp.types.GetEventChannelInfoResponse`
1152
+ """
1153
+ ecn = self.WORD_pack(eventChannelNumber)
1154
+ response = self.transport.request(types.Command.GET_DAQ_EVENT_INFO, 0, *ecn)
1155
+ return types.GetEventChannelInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1156
+
1157
+ @wrapped
1158
+ def dtoCtrProperties(self, modifier, eventChannel, relatedEventChannel, mode):
1159
+ """DTO CTR properties
1160
+
1161
+ Parameters
1162
+ ----------
1163
+ modifier :
1164
+ eventChannel : int
1165
+ relatedEventChannel : int
1166
+ mode :
1167
+
1168
+ Returns
1169
+ -------
1170
+ `pyxcp.types.DtoCtrPropertiesResponse`
1171
+ """
1172
+ data = bytearray()
1173
+ data.append(modifier)
1174
+ data.extend(self.WORD_pack(eventChannel))
1175
+ data.extend(self.WORD_pack(relatedEventChannel))
1176
+ data.append(mode)
1177
+ response = self.transport.request(types.Command.DTO_CTR_PROPERTIES, *data)
1178
+ return types.DtoCtrPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1179
+
1180
+ @wrapped
1181
+ def setDaqPackedMode(self, daqListNumber, daqPackedMode, dpmTimestampMode=None, dpmSampleCount=None):
1182
+ """Set DAQ List Packed Mode.
1183
+
1184
+ Parameters
1185
+ ----------
1186
+ daqListNumber : int
1187
+ daqPackedMode : int
1188
+ """
1189
+ params = []
1190
+ dln = self.WORD_pack(daqListNumber)
1191
+ params.extend(dln)
1192
+ params.append(daqPackedMode)
1193
+
1194
+ if daqPackedMode == 1 or daqPackedMode == 2:
1195
+ params.append(dpmTimestampMode)
1196
+ dsc = self.WORD_pack(dpmSampleCount)
1197
+ params.extend(dsc)
1198
+
1199
+ return self.transport.request(types.Command.SET_DAQ_PACKED_MODE, *params)
1200
+
1201
+ @wrapped
1202
+ def getDaqPackedMode(self, daqListNumber):
1203
+ """Get DAQ List Packed Mode.
1204
+
1205
+ This command returns information of the currently active packed mode of
1206
+ the addressed DAQ list.
1207
+
1208
+ Parameters
1209
+ ----------
1210
+ daqListNumber : int
1211
+ """
1212
+ dln = self.WORD_pack(daqListNumber)
1213
+ response = self.transport.request(types.Command.GET_DAQ_PACKED_MODE, *dln)
1214
+ return types.GetDaqPackedModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1215
+
1216
+ # dynamic
1217
+ @wrapped
1218
+ def freeDaq(self):
1219
+ """Clear dynamic DAQ configuration."""
1220
+ result = self.transport.request(types.Command.FREE_DAQ)
1221
+ self.stim.freeDaq()
1222
+ return result
1223
+
1224
+ @wrapped
1225
+ def allocDaq(self, daqCount: int):
1226
+ """Allocate DAQ lists.
1227
+
1228
+ Parameters
1229
+ ----------
1230
+ daqCount : int
1231
+ number of DAQ lists to be allocated
1232
+ """
1233
+ dq = self.WORD_pack(daqCount)
1234
+ result = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
1235
+ self.stim.allocDaq(daqCount)
1236
+ return result
1237
+
1238
+ @wrapped
1239
+ def allocOdt(self, daqListNumber: int, odtCount: int):
1240
+ dln = self.WORD_pack(daqListNumber)
1241
+ result = self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount)
1242
+ self.stim.allocOdt(daqListNumber, odtCount)
1243
+ return result
1244
+
1245
+ @wrapped
1246
+ def allocOdtEntry(self, daqListNumber: int, odtNumber: int, odtEntriesCount: int):
1247
+ dln = self.WORD_pack(daqListNumber)
1248
+ result = self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount)
1249
+ self.stim.allocOdtEntry(daqListNumber, odtNumber, odtEntriesCount)
1250
+ return result
1251
+
1252
+ # PGM
1253
+ @wrapped
1254
+ def programStart(self):
1255
+ """Indicate the beginning of a programming sequence.
1256
+
1257
+ Returns
1258
+ -------
1259
+ `pyxcp.types.ProgramStartResponse`
1260
+ """
1261
+ response = self.transport.request(types.Command.PROGRAM_START)
1262
+ result = types.ProgramStartResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1263
+ self.slaveProperties.pgmProcessor.commModePgm = result.commModePgm
1264
+ self.slaveProperties.pgmProcessor.maxCtoPgm = result.maxCtoPgm
1265
+ self.slaveProperties.pgmProcessor.maxBsPgm = result.maxBsPgm
1266
+ self.slaveProperties.pgmProcessor.minStPgm = result.minStPgm
1267
+ self.slaveProperties.pgmProcessor.queueSizePgm = result.queueSizePgm
1268
+ self.slaveProperties.pgmProcessor.slaveBlockMode = result.commModePgm.slaveBlockMode
1269
+ self.slaveProperties.pgmProcessor.interleavedMode = result.commModePgm.interleavedMode
1270
+ self.slaveProperties.pgmProcessor.masterBlockMode = result.commModePgm.masterBlockMode
1271
+ return result
1272
+
1273
+ @wrapped
1274
+ def programClear(self, mode: int, clearRange: int):
1275
+ """Clear a part of non-volatile memory.
1276
+
1277
+ Parameters
1278
+ ----------
1279
+ mode : int
1280
+ 0x00 = the absolute access mode is active (default)
1281
+ 0x01 = the functional access mode is active
1282
+ clearRange : int
1283
+ """
1284
+ cr = self.DWORD_pack(clearRange)
1285
+ response = self.transport.request(types.Command.PROGRAM_CLEAR, mode, 0, 0, *cr)
1286
+ # ERR_ACCESS_LOCKED
1287
+ return response
1288
+
1289
+ @wrapped
1290
+ def program(self, data: bytes, blockLength, last=False):
1291
+ """Parameters
1292
+ ----------
1293
+ data : bytes
1294
+ Data to send to slave.
1295
+ blockModeLength : int
1296
+ the program request must contain the length of the whole block, not just the length
1297
+ in the current packet.
1298
+ last : bool
1299
+ Indicates that this is the only packet in the block, because
1300
+ the slave device will send the response after this.
1301
+
1302
+ Note
1303
+ ----
1304
+ Adress is set via :meth:`setMta`
1305
+ """
1306
+ # d = bytearray()
1307
+ # d.append(len(data))
1308
+ # if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1309
+ # d.extend(b"\x00\x00") # alignment bytes
1310
+ # for e in data:
1311
+ # d.extend(self.AG_pack(e))
1312
+ if last:
1313
+ # last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
1314
+ response = self.transport.request(types.Command.PROGRAM, blockLength, *data)
1315
+ return response
1316
+ else:
1317
+ # the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
1318
+ # so we must not wait for any response
1319
+ self.transport.block_request(types.Command.PROGRAM, blockLength, *data)
1320
+ return None
1321
+
1322
+ @wrapped
1323
+ def programReset(self, wait_for_optional_response=True):
1324
+ """Indicate the end of a programming sequence."""
1325
+ if wait_for_optional_response:
1326
+ return self.transport.request_optional_response(types.Command.PROGRAM_RESET)
1327
+ else:
1328
+ return self.transport.block_request(types.Command.PROGRAM_RESET)
1329
+
1330
+ @wrapped
1331
+ def getPgmProcessorInfo(self):
1332
+ """Get general information on PGM processor."""
1333
+ response = self.transport.request(types.Command.GET_PGM_PROCESSOR_INFO)
1334
+ result = types.GetPgmProcessorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1335
+ self.slaveProperties.pgmProcessor.pgmProperties = result.pgmProperties
1336
+ self.slaveProperties.pgmProcessor.maxSector = result.maxSector
1337
+ return result
1338
+
1339
+ @wrapped
1340
+ def getSectorInfo(self, mode, sectorNumber):
1341
+ """Get specific information for a sector."""
1342
+ response = self.transport.request(types.Command.GET_SECTOR_INFO, mode, sectorNumber)
1343
+ if mode == 0 or mode == 1:
1344
+ return types.GetSectorInfoResponseMode01.parse(response, byteOrder=self.slaveProperties.byteOrder)
1345
+ elif mode == 2:
1346
+ return types.GetSectorInfoResponseMode2.parse(response, byteOrder=self.slaveProperties.byteOrder)
1347
+
1348
+ @wrapped
1349
+ def programPrepare(self, codesize):
1350
+ """Prepare non-volatile memory programming."""
1351
+ cs = self.WORD_pack(codesize)
1352
+ return self.transport.request(types.Command.PROGRAM_PREPARE, 0x00, *cs)
1353
+
1354
+ @wrapped
1355
+ def programFormat(self, compressionMethod, encryptionMethod, programmingMethod, accessMethod):
1356
+ return self.transport.request(
1357
+ types.Command.PROGRAM_FORMAT,
1358
+ compressionMethod,
1359
+ encryptionMethod,
1360
+ programmingMethod,
1361
+ accessMethod,
1362
+ )
1363
+
1364
+ @wrapped
1365
+ def programNext(self, data: bytes, remainingBlockLength: int, last: bool = False):
1366
+ # d = bytearray()
1367
+ # d.append(len(data))
1368
+ # if self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1369
+ # d.extend(b"\x00\x00") # alignment bytes
1370
+ # for e in data:
1371
+ # d.extend(self.AG_pack(e))
1372
+ if last:
1373
+ # last PROGRAM_NEXT packet in a block: the slave device has to send the response after this.
1374
+ response = self.transport.request(types.Command.PROGRAM_NEXT, remainingBlockLength, *data)
1375
+ return response
1376
+ else:
1377
+ # the slave device won't respond to consecutive PROGRAM_NEXT packets in block mode,
1378
+ # so we must not wait for any response
1379
+ self.transport.block_request(types.Command.PROGRAM_NEXT, remainingBlockLength, *data)
1380
+ return None
1381
+
1382
+ @wrapped
1383
+ def programMax(self, data):
1384
+ d = bytearray()
1385
+ if self.slaveProperties.addressGranularity == types.AddressGranularity.WORD:
1386
+ d.extend(b"\x00") # alignment bytes
1387
+ elif self.slaveProperties.addressGranularity == types.AddressGranularity.DWORD:
1388
+ d.extend(b"\x00\x00\x00") # alignment bytes
1389
+ for e in data:
1390
+ d.extend(self.AG_pack(e))
1391
+ return self.transport.request(types.Command.PROGRAM_MAX, *d)
1392
+
1393
+ @wrapped
1394
+ def programVerify(self, verMode, verType, verValue):
1395
+ data = bytearray()
1396
+ data.extend(self.WORD_pack(verType))
1397
+ data.extend(self.DWORD_pack(verValue))
1398
+ return self.transport.request(types.Command.PROGRAM_VERIFY, verMode, *data)
1399
+
1400
+ # DBG
1401
+
1402
+ @wrapped
1403
+ def dbgAttach(self):
1404
+ """Returns detailed information about the implemented version of the SW-DBG feature of the XCP slave
1405
+
1406
+ Returns
1407
+ -------
1408
+ `pyxcp.types.DbgAttachResponse`
1409
+ """
1410
+ response = self.transport.request(types.Command.DBG_ATTACH)
1411
+ return types.DbgAttachResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1412
+
1413
+ @wrapped
1414
+ def dbgGetVendorInfo(self):
1415
+ """"""
1416
+ response = self.transport.request(types.Command.DBG_GET_VENDOR_INFO)
1417
+ return types.DbgGetVendorInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1418
+
1419
+ @wrapped
1420
+ def dbgGetModeInfo(self):
1421
+ """"""
1422
+ response = self.transport.request(types.Command.DBG_GET_MODE_INFO)
1423
+ return types.DbgGetModeInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1424
+
1425
+ @wrapped
1426
+ def dbgGetJtagId(self):
1427
+ """"""
1428
+ response = self.transport.request(types.Command.DBG_GET_JTAG_ID)
1429
+ return types.DbgGetJtagIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1430
+
1431
+ @wrapped
1432
+ def dbgHaltAfterReset(self):
1433
+ """"""
1434
+ return self.transport.request(types.Command.DBG_HALT_AFTER_RESET)
1435
+
1436
+ @wrapped
1437
+ def dbgGetHwioInfo(self, index: int):
1438
+ """"""
1439
+ response = self.transport.request(types.Command.DBG_GET_HWIO_INFO, index)
1440
+ return types.DbgGetHwioInfoResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1441
+
1442
+ @wrapped
1443
+ def dbgSetHwioEvent(self, index: int, trigger: int):
1444
+ """"""
1445
+ return self.transport.request(types.Command.DBG_SET_HWIO_EVENT, index, trigger)
1446
+
1447
+ @wrapped
1448
+ def dbgHwioControl(self, pins):
1449
+ """"""
1450
+ d = bytearray()
1451
+ d.extend(self.BYTE_pack(len(pins)))
1452
+ for p in pins:
1453
+ d.extend(self.BYTE_pack(p[0])) # index
1454
+ d.extend(self.BYTE_pack(p[1])) # state
1455
+ d.extend(self.WORD_pack(p[2])) # frequency
1456
+
1457
+ response = self.transport.request(types.Command.DBG_HWIO_CONTROL, *d)
1458
+ return types.DbgHwioControlResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1459
+
1460
+ @wrapped
1461
+ def dbgExclusiveTargetAccess(self, mode: int, context: int):
1462
+ """"""
1463
+ return self.transport.request(types.Command.DBG_EXCLUSIVE_TARGET_ACCESS, mode, context)
1464
+
1465
+ @wrapped
1466
+ def dbgSequenceMultiple(self, mode: int, num: int, *seq):
1467
+ """"""
1468
+ response = self.transport.request(types.Command.DBG_SEQUENCE_MULTIPLE, mode, self.WORD_pack(num), *seq)
1469
+ return types.DbgSequenceMultipleResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1470
+
1471
+ @wrapped
1472
+ def dbgLlt(self, num: int, mode: int, *llts):
1473
+ """"""
1474
+ response = self.transport.request(types.Command.DBG_LLT, num, mode, *llts)
1475
+ return types.DbgLltResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1476
+
1477
+ @wrapped
1478
+ def dbgReadModifyWrite(self, tri: int, width: int, address: int, mask: int, data: int):
1479
+ """"""
1480
+ d = bytearray()
1481
+ d.extend(b"\x00")
1482
+ d.append(tri)
1483
+ d.append(width)
1484
+ d.extend(b"\x00\x00")
1485
+ d.extend(self.DLONG_pack(address))
1486
+ if width == 0x01:
1487
+ d.extend(self.BYTE_pack(mask))
1488
+ d.extend(self.BYTE_pack(data))
1489
+ elif width == 0x02:
1490
+ d.extend(self.WORD_pack(mask))
1491
+ d.extend(self.WORD_pack(data))
1492
+ elif width == 0x04:
1493
+ d.extend(self.DWORD_pack(mask))
1494
+ d.extend(self.DWORD_pack(data))
1495
+ elif width == 0x08:
1496
+ d.extend(self.DLONG_pack(mask))
1497
+ d.extend(self.DLONG_pack(data))
1498
+ response = self.transport.request(types.Command.DBG_READ_MODIFY_WRITE, *d)
1499
+ return types.DbgReadModifyWriteResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
1500
+
1501
+ @wrapped
1502
+ def dbgWrite(self, tri: int, width: int, address: int, data):
1503
+ """"""
1504
+ d = bytearray()
1505
+ d.extend(b"\x00")
1506
+ d.append(tri)
1507
+ self._dbg_width = width
1508
+ d.append(width)
1509
+ d.extend(self.WORD_pack(len(data)))
1510
+ d.extend(self.DLONG_pack(address))
1511
+ for da in data:
1512
+ if width == 0x01:
1513
+ d.extend(self.BYTE_pack(da))
1514
+ elif width == 0x02:
1515
+ d.extend(self.WORD_pack(da))
1516
+ elif width == 0x04:
1517
+ d.extend(self.DWORD_pack(da))
1518
+ elif width == 0x08:
1519
+ d.extend(self.DLONG_pack(da))
1520
+ return self.transport.request(types.Command.DBG_WRITE, *d)
1521
+
1522
+ @wrapped
1523
+ def dbgWriteNext(self, num: int, data: int):
1524
+ """"""
1525
+ d = bytearray()
1526
+ d.extend(b"\x00")
1527
+ d.extend(self.WORD_pack(num))
1528
+ d.extend(b"\x00\x00")
1529
+ for i in range(num):
1530
+ if self._dbg_width == 0x01:
1531
+ d.extend(self.BYTE_pack(data[i]))
1532
+ elif self._dbg_width == 0x02:
1533
+ d.extend(self.WORD_pack(data[i]))
1534
+ elif self._dbg_width == 0x04:
1535
+ d.extend(self.DWORD_pack(data[i]))
1536
+ elif self._dbg_width == 0x08:
1537
+ d.extend(self.DLONG_pack(data[i]))
1538
+ return self.transport.request(types.Command.DBG_WRITE_NEXT, *d)
1539
+
1540
+ @wrapped
1541
+ def dbgWriteCan1(self, tri: int, address: int):
1542
+ """"""
1543
+ d = bytearray()
1544
+ d.extend(self.BYTE_pack(tri))
1545
+ d.extend(self.DWORD_pack(address))
1546
+ return self.transport.request(types.Command.DBG_WRITE_CAN1, *d)
1547
+
1548
+ @wrapped
1549
+ def dbgWriteCan2(self, width: int, num: int):
1550
+ """"""
1551
+ d = bytearray()
1552
+ self._dbg_width = width
1553
+ d.append(width)
1554
+ d.extend(self.BYTE_pack(num))
1555
+ return self.transport.request(types.Command.DBG_WRITE_CAN2, *d)
1556
+
1557
+ @wrapped
1558
+ def dbgWriteCanNext(self, num: int, data: int):
1559
+ """"""
1560
+ d = bytearray()
1561
+ d.extend(self.BYTE_pack(num))
1562
+ for i in range(num):
1563
+ if self._dbg_width == 0x01:
1564
+ d.extend(self.BYTE_pack(data[i]))
1565
+ elif self._dbg_width == 0x02:
1566
+ d.extend(self.WORD_pack(data[i]))
1567
+ elif self._dbg_width == 0x04:
1568
+ d.extend(self.DWORD_pack(data[i]))
1569
+ elif self._dbg_width == 0x08:
1570
+ d.extend(self.DLONG_pack(data[i]))
1571
+ return self.transport.request(types.Command.DBG_WRITE_CAN_NEXT, *d)
1572
+
1573
+ @wrapped
1574
+ def dbgRead(self, tri: int, width: int, num: int, address: int):
1575
+ """"""
1576
+ d = bytearray()
1577
+ d.extend(b"\x00")
1578
+ d.extend(self.BYTE_pack(tri))
1579
+ self._dbg_width = width
1580
+ d.extend(self.BYTE_pack(width))
1581
+ d.extend(self.WORD_pack(num))
1582
+ d.extend(self.DLONG_pack(address))
1583
+ response = self.transport.request(types.Command.DBG_READ, *d)
1584
+ return types.DbgReadResponse.parse(response, byteOrder=self.slaveProperties.byteOrder, width=width)
1585
+
1586
+ @wrapped
1587
+ def dbgReadCan1(self, tri: int, address: int):
1588
+ """"""
1589
+ d = bytearray()
1590
+ d.extend(self.BYTE_pack(tri))
1591
+ d.extend(self.DWORD_pack(address))
1592
+ return self.transport.request(types.Command.DBG_READ_CAN1, *d)
1593
+
1594
+ @wrapped
1595
+ def dbgReadCan2(self, width: int, num: int):
1596
+ """"""
1597
+ d = bytearray()
1598
+ self._dbg_width = width
1599
+ d.extend(self.BYTE_pack(width))
1600
+ d.extend(self.BYTE_pack(num))
1601
+ return self.transport.request(types.Command.DBG_READ_CAN2, *d)
1602
+
1603
+ @wrapped
1604
+ def dbgGetTriDescTbl(self):
1605
+ """"""
1606
+ response = self.transport.request(types.Command.DBG_GET_TRI_DESC_TBL, b"\x00\x00\x00\x00\x00")
1607
+ return types.DbgGetTriDescTblResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1608
+
1609
+ @wrapped
1610
+ def dbgLlbt(self, data):
1611
+ """"""
1612
+ d = bytearray()
1613
+ d.extend(b"\x00")
1614
+ d.extend(self.WORD_pack(len(data)))
1615
+ for b in data:
1616
+ d.extend(self.BYTE_pack(b))
1617
+ response = self.transport.request(types.Command.DBG_LLBT, d)
1618
+ return types.DbgLlbtResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1619
+
1620
+ @wrapped
1621
+ def timeCorrelationProperties(self, setProperties, getPropertiesRequest, clusterId):
1622
+ response = self.transport.request(
1623
+ types.Command.TIME_CORRELATION_PROPERTIES, setProperties, getPropertiesRequest, 0, *self.WORD_pack(clusterId)
1624
+ )
1625
+ return types.TimeCorrelationPropertiesResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1626
+
1627
+ # Transport layer commands / CAN.
1628
+
1629
+ @broadcasted
1630
+ @wrapped
1631
+ def getSlaveID(self, mode: int):
1632
+ response = self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, ord("X"), ord("C"), ord("P"), mode)
1633
+ return types.GetSlaveIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1634
+
1635
+ def getDaqId(self, daqListNumber: int):
1636
+ response = self.transportLayerCmd(types.TransportLayerCommands.GET_DAQ_ID, *self.WORD_pack(daqListNumber))
1637
+ # if response:
1638
+ return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1639
+
1640
+ def setDaqId(self, daqListNumber: int, identifier: int):
1641
+ response = self.transportLayerCmd(
1642
+ types.TransportLayerCommands.SET_DAQ_ID, *self.WORD_pack(daqListNumber), *self.DWORD_pack(identifier)
1643
+ )
1644
+ return response
1645
+
1646
+ # Convenience Functions.
1647
+ def verify(self, addr, length):
1648
+ """Convenience function for verification of a data-transfer from slave
1649
+ to master (Not part of the XCP Specification).
1650
+
1651
+ Parameters
1652
+ ----------
1653
+ addr : int
1654
+ length : int
1655
+
1656
+ Returns
1657
+ -------
1658
+ bool
1659
+ """
1660
+ self.setMta(addr)
1661
+ cs = self.buildChecksum(length)
1662
+ self.logger.debug(f"BuildChecksum return'd: 0x{cs.checksum:08X} [{cs.checksumType}]")
1663
+ self.setMta(addr)
1664
+ data = self.fetch(length)
1665
+ cc = checksum.check(data, cs.checksumType)
1666
+ self.logger.debug(f"Our checksum : 0x{cc:08X}")
1667
+ return cs.checksum == cc
1668
+
1669
+ def getDaqInfo(self):
1670
+ """Get DAQ information: processor, resolution, events."""
1671
+ result = {}
1672
+ dpi = self.getDaqProcessorInfo()
1673
+ processorInfo = {
1674
+ "minDaq": dpi["minDaq"],
1675
+ "maxDaq": dpi["maxDaq"],
1676
+ "properties": {
1677
+ "configType": dpi["daqProperties"]["daqConfigType"],
1678
+ "overloadEvent": dpi["daqProperties"]["overloadEvent"],
1679
+ "overloadMsb": dpi["daqProperties"]["overloadMsb"],
1680
+ "prescalerSupported": dpi["daqProperties"]["prescalerSupported"],
1681
+ "pidOffSupported": dpi["daqProperties"]["pidOffSupported"],
1682
+ "timestampSupported": dpi["daqProperties"]["timestampSupported"],
1683
+ "bitStimSupported": dpi["daqProperties"]["bitStimSupported"],
1684
+ "resumeSupported": dpi["daqProperties"]["resumeSupported"],
1685
+ },
1686
+ "keyByte": {
1687
+ "identificationField": dpi["daqKeyByte"]["Identification_Field"],
1688
+ "addressExtension": dpi["daqKeyByte"]["Address_Extension"],
1689
+ "optimisationType": dpi["daqKeyByte"]["Optimisation_Type"],
1690
+ },
1691
+ }
1692
+ result["processor"] = processorInfo
1693
+
1694
+ dri = self.getDaqResolutionInfo()
1695
+ resolutionInfo = {
1696
+ "timestampTicks": dri["timestampTicks"],
1697
+ "maxOdtEntrySizeDaq": dri["maxOdtEntrySizeDaq"],
1698
+ "maxOdtEntrySizeStim": dri["maxOdtEntrySizeStim"],
1699
+ "granularityOdtEntrySizeDaq": dri["granularityOdtEntrySizeDaq"],
1700
+ "granularityOdtEntrySizeStim": dri["granularityOdtEntrySizeStim"],
1701
+ "timestampMode": {
1702
+ "unit": dri["timestampMode"]["unit"],
1703
+ "fixed": dri["timestampMode"]["fixed"],
1704
+ "size": dri["timestampMode"]["size"],
1705
+ },
1706
+ }
1707
+ result["resolution"] = resolutionInfo
1708
+ channels = []
1709
+ daq_events = []
1710
+ for ecn in range(dpi.maxEventChannel):
1711
+ eci = self.getDaqEventInfo(ecn)
1712
+ cycle = eci["eventChannelTimeCycle"]
1713
+ maxDaqList = eci["maxDaqList"]
1714
+ priority = eci["eventChannelPriority"]
1715
+ time_unit = eci["eventChannelTimeUnit"]
1716
+ consistency = eci["daqEventProperties"]["consistency"]
1717
+ daq_supported = eci["daqEventProperties"]["daq"]
1718
+ stim_supported = eci["daqEventProperties"]["stim"]
1719
+ packed_supported = eci["daqEventProperties"]["packed"]
1720
+ name = self.fetch(eci.eventChannelNameLength)
1721
+ if name:
1722
+ name = decode_bytes(name)
1723
+ channel = {
1724
+ "name": name,
1725
+ "priority": eci["eventChannelPriority"],
1726
+ "unit": eci["eventChannelTimeUnit"],
1727
+ "cycle": eci["eventChannelTimeCycle"],
1728
+ "maxDaqList": eci["maxDaqList"],
1729
+ "properties": {
1730
+ "consistency": consistency,
1731
+ "daq": daq_supported,
1732
+ "stim": stim_supported,
1733
+ "packed": packed_supported,
1734
+ },
1735
+ }
1736
+ daq_event_info = DaqEventInfo(
1737
+ name,
1738
+ types.EVENT_CHANNEL_TIME_UNIT_TO_EXP[time_unit],
1739
+ cycle,
1740
+ maxDaqList,
1741
+ priority,
1742
+ consistency,
1743
+ daq_supported,
1744
+ stim_supported,
1745
+ packed_supported,
1746
+ )
1747
+ daq_events.append(daq_event_info)
1748
+ channels.append(channel)
1749
+ result["channels"] = channels
1750
+ self.stim.setDaqEventInfo(daq_events)
1751
+ return result
1752
+
1753
+ def getCurrentProtectionStatus(self):
1754
+ """"""
1755
+ if self.currentProtectionStatus is None:
1756
+ status = self.getStatus()
1757
+ self._setProtectionStatus(status.resourceProtectionStatus)
1758
+ return self.currentProtectionStatus
1759
+
1760
+ def _setProtectionStatus(self, protection):
1761
+ """"""
1762
+ self.currentProtectionStatus = {
1763
+ "dbg": protection.dbg,
1764
+ "pgm": protection.pgm,
1765
+ "stim": protection.stim,
1766
+ "daq": protection.daq,
1767
+ "calpag": protection.calpag,
1768
+ }
1769
+
1770
+ def cond_unlock(self, resources=None):
1771
+ """Conditionally unlock resources, i.e. only unlock locked resources.
1772
+
1773
+ Precondition: Parameter "SEED_N_KEY_DLL" must be present and point to a valid DLL/SO.
1774
+
1775
+ Parameters
1776
+ ----------
1777
+ resources: str
1778
+ Comma or space separated list of resources, e.g. "DAQ, CALPAG".
1779
+ The names are not case-sensitive.
1780
+ Valid identifiers are: "calpag", "daq", "dbg", "pgm", "stim".
1781
+
1782
+ If omitted, try to unlock every available resource.
1783
+
1784
+ Raises
1785
+ ------
1786
+ ValueError
1787
+ Invalid resource name.
1788
+
1789
+ `dllif.SeedNKeyError`
1790
+ In case of DLL related issues.
1791
+ """
1792
+ import re
1793
+
1794
+ from pyxcp.dllif import SeedNKeyError, SeedNKeyResult, getKey
1795
+
1796
+ MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2
1797
+
1798
+ protection_status = self.getCurrentProtectionStatus()
1799
+ if any(protection_status.values()) and (not (self.seed_n_key_dll or self.seed_n_key_function)):
1800
+ raise RuntimeError("Neither seed-and-key DLL nor function specified, cannot proceed.") # TODO: ConfigurationError
1801
+ if resources is None:
1802
+ result = []
1803
+ if self.slaveProperties["supportsCalpag"]:
1804
+ result.append("calpag")
1805
+ if self.slaveProperties["supportsDaq"]:
1806
+ result.append("daq")
1807
+ if self.slaveProperties["supportsStim"]:
1808
+ result.append("stim")
1809
+ if self.slaveProperties["supportsPgm"]:
1810
+ result.append("pgm")
1811
+ resources = ",".join(result)
1812
+ resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r]
1813
+ for name in resource_names:
1814
+ if name not in types.RESOURCE_VALUES:
1815
+ raise ValueError(f"Invalid resource name {name!r}.")
1816
+ if not protection_status[name]:
1817
+ continue
1818
+ resource_value = types.RESOURCE_VALUES[name]
1819
+ result = self.getSeed(types.XcpGetSeedMode.FIRST_PART, resource_value)
1820
+ seed = list(result.seed)
1821
+ length = result.length
1822
+ if length == 0:
1823
+ continue
1824
+ if length > MAX_PAYLOAD:
1825
+ remaining = length - len(seed)
1826
+ while remaining > 0:
1827
+ result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value)
1828
+ seed.extend(list(result.seed))
1829
+ remaining -= result.length
1830
+ self.logger.debug(f"Got seed {seed!r} for resource {resource_value!r}.")
1831
+ if self.seed_n_key_function:
1832
+ key = self.seed_n_key_function(resource_value, bytes(seed))
1833
+ self.logger.debug(f"Using seed and key function {self.seed_n_key_function.__name__!r}().")
1834
+ result = SeedNKeyResult.ACK
1835
+ elif self.seed_n_key_dll:
1836
+ self.logger.debug(f"Using seed and key DLL {self.seed_n_key_dll!r}.")
1837
+ result, key = getKey(
1838
+ self.logger,
1839
+ self.seed_n_key_dll,
1840
+ resource_value,
1841
+ bytes(seed),
1842
+ self.seed_n_key_dll_same_bit_width,
1843
+ )
1844
+ if result == SeedNKeyResult.ACK:
1845
+ key = list(key)
1846
+ self.logger.debug(f"Unlocking resource {resource_value!r} with key {key!r}.")
1847
+ remaining = len(key)
1848
+ while key:
1849
+ data = key[:MAX_PAYLOAD]
1850
+ key_len = len(data)
1851
+ self.unlock(remaining, data)
1852
+ key = key[MAX_PAYLOAD:]
1853
+ remaining -= key_len
1854
+ else:
1855
+ raise SeedNKeyError(f"SeedAndKey DLL returned: {SeedNKeyResult(result).name!r}")
1856
+
1857
+ def identifier(self, id_value: int) -> str:
1858
+ """Return the identifier for the given value.
1859
+ Use this method instead of calling `getId()` directly.
1860
+
1861
+ Parameters
1862
+ ----------
1863
+ id_value: int
1864
+ For standard identifiers, use the constants from `pyxcp.types.XcpGetIdType`.
1865
+
1866
+ Returns
1867
+ -------
1868
+ str
1869
+ """
1870
+ gid = self.getId(id_value)
1871
+ if (gid.mode & 0x01) == 0x01:
1872
+ value = bytes(gid.identification or b"")
1873
+ else:
1874
+ value = self.fetch(gid.length)
1875
+ return decode_bytes(value)
1876
+
1877
+ def id_scanner(self, scan_ranges: Optional[Collection[Collection[int]]] = None) -> Dict[str, str]:
1878
+ """Scan for available standard identification types (GET_ID).
1879
+
1880
+ Parameters
1881
+ ----------
1882
+ scan_ranges: Optional[Collection[Collection[int]]]
1883
+
1884
+ - If parameter is omitted or `None` test every standard identification type (s. GET_ID service)
1885
+ plus extensions by Vector Informatik.
1886
+ - Else `scan_ranges` must be a list-of-list.
1887
+ e.g: [[12, 80], [123], [240, 16, 35]]
1888
+ - The first list is a range (closed interval).
1889
+ - The second is a single value.
1890
+ - The third is a value list.
1891
+
1892
+ Returns
1893
+ -------
1894
+ Dict[str, str]
1895
+
1896
+ """
1897
+ result = {}
1898
+
1899
+ def make_generator(sr):
1900
+ STD_IDS = {int(v): k for k, v in types.XcpGetIdType.__members__.items()}
1901
+ if sr is None:
1902
+ scan_range = STD_IDS.keys()
1903
+ else:
1904
+ scan_range = []
1905
+ if not isinstance(sr, Collection):
1906
+ raise TypeError("scan_ranges must be of type `Collection`")
1907
+ for element in sr:
1908
+ if not isinstance(element, Collection):
1909
+ raise TypeError("scan_ranges elements must be of type `Collection`")
1910
+ if not element:
1911
+ raise ValueError("scan_ranges elements cannot be empty")
1912
+ if len(element) == 1:
1913
+ scan_range.append(element[0]) # Single value
1914
+ elif len(element) == 2:
1915
+ start, stop = element # Value range
1916
+ scan_range.extend(list(range(start, stop + 1)))
1917
+ else:
1918
+ scan_range.extend(element) # Value list.
1919
+ scan_range = sorted(frozenset(scan_range))
1920
+
1921
+ def generate():
1922
+ for idx, id_value in enumerate(scan_range):
1923
+ if id_value in STD_IDS:
1924
+ name = STD_IDS[id_value]
1925
+ else:
1926
+ name = f"USER_{idx}"
1927
+ yield id_value, name,
1928
+
1929
+ return generate()
1930
+
1931
+ gen = make_generator(scan_ranges)
1932
+ for id_value, name in gen:
1933
+ status, response = self.try_command(self.identifier, id_value)
1934
+ if status == types.TryCommandResult.OK and response:
1935
+ result[name] = response
1936
+ elif status == types.TryCommandResult.XCP_ERROR and response.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1937
+ break # Nothing to do here.
1938
+ elif status == types.TryCommandResult.OTHER_ERROR:
1939
+ raise RuntimeError(f"Error while scanning for ID {id_value}: {response!r}")
1940
+ return result
1941
+
1942
+ @property
1943
+ def start_datetime(self) -> int:
1944
+ """"""
1945
+ return self.transport.start_datetime
1946
+
1947
+ def try_command(self, cmd: Callable, *args, **kws) -> Tuple[types.TryCommandResult, Any]:
1948
+ """Call master functions and handle XCP errors more gracefuly.
1949
+
1950
+ Parameter
1951
+ ---------
1952
+ cmd: Callable
1953
+ args: list
1954
+ variable length arguments to `cmd`.
1955
+ kws: dict
1956
+ keyword arguments to `cmd`.
1957
+
1958
+ `extra_msg`: str
1959
+ Additional info to log message (not passed to `cmd`).
1960
+
1961
+ Returns
1962
+ -------
1963
+
1964
+ Note
1965
+ ----
1966
+ Mainly used for plug-and-play applications, e.g. `id_scanner` may confronted with `ERR_OUT_OF_RANGE` errors, which
1967
+ is normal for this kind of applications -- or to test for optional commands.
1968
+ Use carefuly not to hide serious error causes.
1969
+ """
1970
+ try:
1971
+ extra_msg: Optional[str] = kws.get("extra_msg")
1972
+ if extra_msg:
1973
+ kws.pop("extra_msg")
1974
+ res = cmd(*args, **kws)
1975
+ except SystemExit as e:
1976
+ if e.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1977
+ # This is a rather common use-case, so let the user know that there is some functionality missing.
1978
+ if extra_msg:
1979
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented -- {extra_msg!r}")
1980
+ else:
1981
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented.")
1982
+ return (types.TryCommandResult.XCP_ERROR, e)
1983
+ except Exception as e:
1984
+ return (types.TryCommandResult.OTHER_ERROR, e)
1985
+ else:
1986
+ return (types.TryCommandResult.OK, res)
1987
+
1988
+
1989
+ def ticks_to_seconds(ticks, resolution):
1990
+ """Convert DAQ timestamp/tick value to seconds.
1991
+
1992
+ Parameters
1993
+ ----------
1994
+ ticks: int
1995
+
1996
+ unit: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
1997
+ """
1998
+ warnings.warn(
1999
+ "ticks_to_seconds() deprecated, use factory :func:`make_tick_converter` instead.",
2000
+ Warning,
2001
+ stacklevel=1,
2002
+ )
2003
+ return (10 ** types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]) * resolution.timestampTicks * ticks
2004
+
2005
+
2006
+ def make_tick_converter(resolution):
2007
+ """Make a function that converts tick count from XCP slave to seconds.
2008
+
2009
+ Parameters
2010
+ ----------
2011
+ resolution: `GetDaqResolutionInfoResponse` as returned by :meth:`getDaqResolutionInfo`
2012
+
2013
+ """
2014
+ exponent = types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]
2015
+ tick_resolution = resolution.timestampTicks
2016
+ base = (10**exponent) * tick_resolution
2017
+
2018
+ def ticks_to_seconds(ticks):
2019
+ """Convert DAQ timestamp/tick value to seconds.
2020
+
2021
+ Parameters
2022
+ ----------
2023
+ ticks: int
2024
+
2025
+ Returns
2026
+ -------
2027
+ float
2028
+ """
2029
+ return base * ticks
2030
+
2031
+ return ticks_to_seconds