pyxcp 0.23.0__cp312-cp312-win_arm64.whl

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

Potentially problematic release.


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

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