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