pyxcp 0.21.10__cp312-cp312-win32.whl → 0.22.1__cp312-cp312-win32.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 (168) hide show
  1. CMakeLists.txt +117 -0
  2. pyxcp/__init__.py +12 -20
  3. pyxcp/aml/EtasCANMonitoring.a2l +82 -83
  4. pyxcp/aml/XCP_Common.aml +0 -1
  5. pyxcp/aml/XCPonUSB.aml +1 -1
  6. pyxcp/aml/ifdata_CAN.a2l +0 -1
  7. pyxcp/aml/ifdata_Eth.a2l +0 -1
  8. pyxcp/aml/ifdata_Flx.a2l +0 -1
  9. pyxcp/aml/ifdata_SxI.a2l +0 -1
  10. pyxcp/aml/ifdata_USB.a2l +0 -1
  11. pyxcp/asam/types.py +4 -4
  12. pyxcp/asamkeydll.c +0 -1
  13. pyxcp/checksum.py +0 -1
  14. pyxcp/cmdline.py +32 -50
  15. pyxcp/config/__init__.py +1089 -0
  16. pyxcp/config/legacy.py +120 -0
  17. pyxcp/constants.py +12 -13
  18. pyxcp/cpp_ext/__init__.py +8 -0
  19. pyxcp/cpp_ext/bin.hpp +104 -0
  20. pyxcp/cpp_ext/blockmem.hpp +58 -0
  21. pyxcp/cpp_ext/cpp_ext.cp312-win_amd64.pyd +0 -0
  22. pyxcp/cpp_ext/daqlist.hpp +197 -0
  23. pyxcp/cpp_ext/event.hpp +67 -0
  24. pyxcp/cpp_ext/extension_wrapper.cpp +94 -0
  25. pyxcp/cpp_ext/helper.hpp +264 -0
  26. pyxcp/cpp_ext/mcobject.hpp +241 -0
  27. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  28. pyxcp/daq_stim/__init__.py +226 -0
  29. pyxcp/daq_stim/optimize/__init__.py +67 -0
  30. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  31. pyxcp/daq_stim/scheduler.cpp +28 -0
  32. pyxcp/daq_stim/scheduler.hpp +75 -0
  33. pyxcp/daq_stim/stim.cp312-win_amd64.pyd +0 -0
  34. pyxcp/daq_stim/stim.cpp +13 -0
  35. pyxcp/daq_stim/stim.hpp +604 -0
  36. pyxcp/daq_stim/stim_wrapper.cpp +48 -0
  37. pyxcp/dllif.py +21 -18
  38. pyxcp/errormatrix.py +5 -3
  39. pyxcp/examples/conf_can.toml +4 -2
  40. pyxcp/examples/conf_can_vector.json +9 -9
  41. pyxcp/examples/conf_can_vector.toml +4 -2
  42. pyxcp/examples/conf_eth.toml +5 -2
  43. pyxcp/examples/conf_nixnet.json +18 -18
  44. pyxcp/examples/conf_sxi.json +7 -7
  45. pyxcp/examples/ex_arrow.py +109 -0
  46. pyxcp/examples/ex_mdf.py +124 -0
  47. pyxcp/examples/ex_sqlite.py +128 -0
  48. pyxcp/examples/run_daq.py +146 -0
  49. pyxcp/examples/xcp_policy.py +6 -7
  50. pyxcp/examples/xcp_read_benchmark.py +8 -6
  51. pyxcp/examples/xcp_skel.py +0 -2
  52. pyxcp/examples/xcp_unlock.py +1 -1
  53. pyxcp/examples/xcp_user_supplied_driver.py +1 -2
  54. pyxcp/examples/xcphello.py +6 -3
  55. pyxcp/examples/xcphello_recorder.py +4 -4
  56. pyxcp/master/__init__.py +1 -2
  57. pyxcp/master/errorhandler.py +107 -74
  58. pyxcp/master/master.py +198 -117
  59. pyxcp/py.typed +0 -0
  60. pyxcp/recorder/__init__.py +27 -6
  61. pyxcp/recorder/converter/__init__.py +37 -0
  62. pyxcp/recorder/lz4.c +129 -51
  63. pyxcp/recorder/lz4.h +45 -28
  64. pyxcp/recorder/lz4hc.c +560 -156
  65. pyxcp/recorder/lz4hc.h +1 -1
  66. pyxcp/recorder/mio.hpp +721 -767
  67. pyxcp/recorder/reader.hpp +139 -0
  68. pyxcp/recorder/reco.py +5 -8
  69. pyxcp/recorder/rekorder.cp312-win_amd64.pyd +0 -0
  70. pyxcp/recorder/rekorder.cpp +18 -22
  71. pyxcp/recorder/rekorder.hpp +200 -587
  72. pyxcp/recorder/setup.py +11 -10
  73. pyxcp/recorder/test_reko.py +2 -3
  74. pyxcp/recorder/unfolder.hpp +1249 -0
  75. pyxcp/recorder/wrap.cpp +171 -9
  76. pyxcp/recorder/writer.hpp +302 -0
  77. pyxcp/scripts/pyxcp_probe_can_drivers.py +0 -2
  78. pyxcp/scripts/xcp_fetch_a2l.py +15 -10
  79. pyxcp/scripts/xcp_id_scanner.py +2 -6
  80. pyxcp/scripts/xcp_info.py +98 -63
  81. pyxcp/scripts/xcp_profile.py +27 -0
  82. pyxcp/stim/__init__.py +0 -0
  83. pyxcp/tests/test_asam_types.py +2 -2
  84. pyxcp/tests/test_binpacking.py +184 -0
  85. pyxcp/tests/test_can.py +1132 -38
  86. pyxcp/tests/test_checksum.py +2 -1
  87. pyxcp/tests/test_daq.py +188 -0
  88. pyxcp/tests/test_frame_padding.py +3 -3
  89. pyxcp/tests/test_master.py +42 -31
  90. pyxcp/tests/test_transport.py +12 -12
  91. pyxcp/tests/test_utils.py +2 -5
  92. pyxcp/timing.py +0 -2
  93. pyxcp/transport/__init__.py +9 -9
  94. pyxcp/transport/base.py +142 -124
  95. pyxcp/transport/base_transport.hpp +0 -0
  96. pyxcp/transport/can.py +194 -167
  97. pyxcp/transport/eth.py +80 -82
  98. pyxcp/transport/sxi.py +106 -60
  99. pyxcp/transport/transport_wrapper.cpp +0 -0
  100. pyxcp/transport/usb_transport.py +65 -83
  101. pyxcp/types.py +69 -20
  102. pyxcp/utils.py +47 -16
  103. pyxcp/vector/map.py +1 -3
  104. {pyxcp-0.21.10.dist-info → pyxcp-0.22.1.dist-info}/METADATA +27 -22
  105. pyxcp-0.22.1.dist-info/RECORD +126 -0
  106. {pyxcp-0.21.10.dist-info → pyxcp-0.22.1.dist-info}/WHEEL +1 -1
  107. {pyxcp-0.21.10.dist-info → pyxcp-0.22.1.dist-info}/entry_points.txt +1 -0
  108. pyxcp/config.py +0 -57
  109. pyxcp/cxx/asynchiofactory.hpp +0 -24
  110. pyxcp/cxx/blocking_client.cpp +0 -44
  111. pyxcp/cxx/blocking_socket.cpp +0 -43
  112. pyxcp/cxx/blocking_socket.hpp +0 -558
  113. pyxcp/cxx/concurrent_queue.hpp +0 -60
  114. pyxcp/cxx/eth.hpp +0 -57
  115. pyxcp/cxx/exceptions.hpp +0 -30
  116. pyxcp/cxx/iasyncioservice.hpp +0 -31
  117. pyxcp/cxx/iresource.hpp +0 -17
  118. pyxcp/cxx/isocket.hpp +0 -22
  119. pyxcp/cxx/linux/epoll.cpp +0 -51
  120. pyxcp/cxx/linux/epoll.hpp +0 -87
  121. pyxcp/cxx/linux/lit_tester.cpp +0 -19
  122. pyxcp/cxx/linux/socket.hpp +0 -234
  123. pyxcp/cxx/linux/timeout.hpp +0 -81
  124. pyxcp/cxx/memoryblock.hpp +0 -42
  125. pyxcp/cxx/pool.hpp +0 -81
  126. pyxcp/cxx/poolmgr.cpp +0 -6
  127. pyxcp/cxx/poolmgr.hpp +0 -31
  128. pyxcp/cxx/test_queue.cpp +0 -69
  129. pyxcp/cxx/timestamp.hpp +0 -84
  130. pyxcp/cxx/utils.cpp +0 -38
  131. pyxcp/cxx/utils.hpp +0 -29
  132. pyxcp/cxx/win/iocp.cpp +0 -242
  133. pyxcp/cxx/win/iocp.hpp +0 -42
  134. pyxcp/cxx/win/perhandledata.hpp +0 -24
  135. pyxcp/cxx/win/periodata.hpp +0 -97
  136. pyxcp/cxx/win/socket.hpp +0 -185
  137. pyxcp/cxx/win/timeout.hpp +0 -83
  138. pyxcp/examples/conf_can.json +0 -20
  139. pyxcp/examples/conf_eth.json +0 -8
  140. pyxcp/logger.py +0 -67
  141. pyxcp/tests/test_config.py +0 -62
  142. pyxcp/transport/candriver/__init__.py +0 -2
  143. pyxcp/transport/candriver/pc_canalystii.py +0 -27
  144. pyxcp/transport/candriver/pc_etas.py +0 -25
  145. pyxcp/transport/candriver/pc_gsusb.py +0 -23
  146. pyxcp/transport/candriver/pc_iscan.py +0 -23
  147. pyxcp/transport/candriver/pc_ixxat.py +0 -27
  148. pyxcp/transport/candriver/pc_kvaser.py +0 -39
  149. pyxcp/transport/candriver/pc_neovi.py +0 -31
  150. pyxcp/transport/candriver/pc_nican.py +0 -23
  151. pyxcp/transport/candriver/pc_nixnet.py +0 -23
  152. pyxcp/transport/candriver/pc_pcan.py +0 -25
  153. pyxcp/transport/candriver/pc_seeed.py +0 -28
  154. pyxcp/transport/candriver/pc_serial.py +0 -27
  155. pyxcp/transport/candriver/pc_slcan.py +0 -29
  156. pyxcp/transport/candriver/pc_socketcan.py +0 -23
  157. pyxcp/transport/candriver/pc_systec.py +0 -29
  158. pyxcp/transport/candriver/pc_usb2can.py +0 -30
  159. pyxcp/transport/candriver/pc_vector.py +0 -34
  160. pyxcp/transport/candriver/python_can.py +0 -101
  161. pyxcp/transport/cxx_ext/CMakeLists.txt +0 -51
  162. pyxcp/transport/cxx_ext/setup.py +0 -49
  163. pyxcp/transport/cxx_ext/tests/test_basic_socket.cpp +0 -39
  164. pyxcp/transport/cxx_ext/tests/test_pool.cpp +0 -39
  165. pyxcp/transport/cxx_ext/tests/test_timestamp.cpp +0 -27
  166. pyxcp-0.21.10.dist-info/RECORD +0 -147
  167. rekorder.cp312-win32.pyd +0 -0
  168. {pyxcp-0.21.10.dist-info/licenses → pyxcp-0.22.1.dist-info}/LICENSE +0 -0
pyxcp/master/master.py CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
2
  """Lowlevel API reflecting available XCP services.
4
3
 
5
4
  .. note:: For technical reasons the API is split into two parts;
@@ -8,37 +7,26 @@
8
7
  .. [1] XCP Specification, Part 2 - Protocol Layer Specification
9
8
  """
10
9
  import functools
11
- import logging
12
10
  import struct
13
11
  import traceback
14
12
  import warnings
15
- from time import sleep
16
- from typing import Callable
17
- from typing import Collection
18
- from typing import Dict
19
- from typing import List
20
- from typing import Optional
21
- from typing import Union
22
-
23
- from pyxcp import checksum
24
- from pyxcp import types
25
- from pyxcp.config import Configuration
26
- from pyxcp.constants import makeBytePacker
27
- from pyxcp.constants import makeByteUnpacker
28
- from pyxcp.constants import makeDLongPacker
29
- from pyxcp.constants import makeDLongUnpacker
30
- from pyxcp.constants import makeDWordPacker
31
- from pyxcp.constants import makeDWordUnpacker
32
- from pyxcp.constants import makeWordPacker
33
- from pyxcp.constants import makeWordUnpacker
34
- from pyxcp.constants import PackerType
35
- from pyxcp.constants import UnpackerType
36
- from pyxcp.master.errorhandler import disable_error_handling
37
- from pyxcp.master.errorhandler import wrapped
38
- from pyxcp.transport.base import createTransport
39
- from pyxcp.utils import decode_bytes
40
- from pyxcp.utils import delay
41
- from pyxcp.utils import SHORT_SLEEP
13
+ from typing import Any, Callable, Collection, Dict, List, Optional, Tuple
14
+
15
+ from pyxcp import checksum, types
16
+ from pyxcp.constants import (
17
+ makeBytePacker,
18
+ makeByteUnpacker,
19
+ makeDLongPacker,
20
+ makeDLongUnpacker,
21
+ makeDWordPacker,
22
+ makeDWordUnpacker,
23
+ makeWordPacker,
24
+ makeWordUnpacker,
25
+ )
26
+ from pyxcp.daq_stim.stim import DaqEventInfo, Stim
27
+ from pyxcp.master.errorhandler import SystemExit, disable_error_handling, wrapped
28
+ from pyxcp.transport.base import create_transport
29
+ from pyxcp.utils import decode_bytes, delay, short_sleep
42
30
 
43
31
 
44
32
  def broadcasted(func: Callable):
@@ -50,7 +38,7 @@ class SlaveProperties(dict):
50
38
  """Container class for fixed parameters, like byte-order, maxCTO, ..."""
51
39
 
52
40
  def __init__(self, *args, **kws):
53
- super(SlaveProperties, self).__init__(*args, **kws)
41
+ super().__init__(*args, **kws)
54
42
 
55
43
  def __getattr__(self, name):
56
44
  return self[name]
@@ -70,39 +58,36 @@ class Master:
70
58
 
71
59
  Parameters
72
60
  ----------
73
- transportName : str
61
+ transport_name : str
74
62
  XCP transport layer name ['can', 'eth', 'sxi']
75
63
  config: dict
76
64
  """
77
65
 
78
- PARAMETER_MAP = {
79
- # Type Req'd Default
80
- "LOGLEVEL": (str, False, "WARN"),
81
- "DISABLE_ERROR_HANDLING": (
82
- bool,
83
- False,
84
- False,
85
- ), # Bypass error-handling for performance reasons.
86
- "SEED_N_KEY_DLL": (str, False, ""),
87
- "SEED_N_KEY_DLL_SAME_BIT_WIDTH": (bool, False, False),
88
- "DISCONNECT_RESPONSE_OPTIONAL": (bool, False, False),
89
- }
90
-
91
- def __init__(self, transportName, config=None, policy=None):
66
+ def __init__(self, transport_name: Optional[str], config, policy=None, transport_layer_interface=None):
67
+ if transport_name is None:
68
+ raise ValueError("No transport-layer selected") # Never reached -- to keep type-checkers happy.
92
69
  self.ctr = 0
93
70
  self.succeeded = True
94
- self.config = Configuration(self.PARAMETER_MAP or {}, config or {})
95
- self.logger = logging.getLogger("pyXCP")
96
- self.logger.setLevel(self.config.get("LOGLEVEL"))
97
- disable_error_handling(self.config.get("DISABLE_ERROR_HANDLING"))
71
+ self.config = config.general
72
+ self.logger = config.log
98
73
 
99
- self.transport = createTransport(transportName, config, policy)
100
- self.transport_name = transportName
74
+ disable_error_handling(self.config.disable_error_handling)
75
+ self.transport_name = transport_name.lower()
76
+ transport_config = config.transport
77
+ self.transport = create_transport(transport_name, transport_config, policy, transport_layer_interface)
78
+
79
+ self.stim = Stim(self.config.stim_support)
80
+ self.stim.clear()
81
+ self.stim.set_policy_feeder(self.transport.policy.feed)
82
+ self.stim.set_frame_sender(self.transport.block_request)
101
83
 
102
84
  # In some cases the transport-layer needs to communicate with us.
103
85
  self.transport.parent = self
104
86
  self.service = None
105
87
 
88
+ # Policies may issue XCP commands on there own.
89
+ self.transport.policy.xcp_master = self
90
+
106
91
  # (D)Word (un-)packers are byte-order dependent
107
92
  # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
108
93
  self.BYTE_pack = None
@@ -119,11 +104,13 @@ class Master:
119
104
  self.mta = types.MtaType(None, None)
120
105
  self.currentDaqPtr = None
121
106
  self.currentProtectionStatus = None
122
- self.seedNKeyDLL = self.config.get("SEED_N_KEY_DLL")
123
- self.seedNKeyDLL_same_bit_width = self.config.get("SEED_N_KEY_DLL_SAME_BIT_WIDTH")
124
- self.disconnect_response_optional = self.config.get("DISCONNECT_RESPONSE_OPTIONAL")
107
+ self.seed_n_key_dll = self.config.seed_n_key_dll
108
+ self.seed_n_key_function = self.config.seed_n_key_function
109
+ self.seed_n_key_dll_same_bit_width = self.config.seed_n_key_dll_same_bit_width
110
+ self.disconnect_response_optional = self.config.disconnect_response_optional
125
111
  self.slaveProperties = SlaveProperties()
126
112
  self.slaveProperties.pgmProcessor = SlaveProperties()
113
+ self.slaveProperties.transport_layer = self.transport_name.upper()
127
114
 
128
115
  def __enter__(self):
129
116
  """Context manager entry part."""
@@ -460,7 +447,7 @@ class Master:
460
447
  response += data[1 : rem + 1]
461
448
  rem = byte_count - len(response)
462
449
  else:
463
- sleep(SHORT_SLEEP)
450
+ short_sleep()
464
451
  return response
465
452
 
466
453
  @wrapped
@@ -580,7 +567,7 @@ class Master:
580
567
  address is not included because of services implicitly setting address information like :meth:`getID` .
581
568
  """
582
569
  if limitPayload and limitPayload < 8:
583
- raise ValueError("Payload must be at least 8 bytes - given: {}".format(limitPayload))
570
+ raise ValueError(f"Payload must be at least 8 bytes - given: {limitPayload}")
584
571
 
585
572
  slaveBlockMode = self.slaveProperties.slaveBlockMode
586
573
  if slaveBlockMode:
@@ -977,14 +964,15 @@ class Master:
977
964
  # DAQ
978
965
 
979
966
  @wrapped
980
- def setDaqPtr(self, daqListNumber, odtNumber, odtEntryNumber):
967
+ def setDaqPtr(self, daqListNumber: int, odtNumber: int, odtEntryNumber: int):
981
968
  self.currentDaqPtr = types.DaqPtr(daqListNumber, odtNumber, odtEntryNumber) # Needed for errorhandling.
982
969
  daqList = self.WORD_pack(daqListNumber)
983
970
  response = self.transport.request(types.Command.SET_DAQ_PTR, 0, *daqList, odtNumber, odtEntryNumber)
971
+ self.stim.setDaqPtr(daqListNumber, odtNumber, odtEntryNumber)
984
972
  return response
985
973
 
986
974
  @wrapped
987
- def clearDaqList(self, daqListNumber):
975
+ def clearDaqList(self, daqListNumber: int):
988
976
  """Clear DAQ list configuration.
989
977
 
990
978
  Parameters
@@ -992,10 +980,12 @@ class Master:
992
980
  daqListNumber : int
993
981
  """
994
982
  daqList = self.WORD_pack(daqListNumber)
995
- return self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList)
983
+ result = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList)
984
+ self.stim.clearDaqList(daqListNumber)
985
+ return result
996
986
 
997
987
  @wrapped
998
- def writeDaq(self, bitOffset, entrySize, addressExt, address):
988
+ def writeDaq(self, bitOffset: int, entrySize: int, addressExt: int, address: int):
999
989
  """Write element in ODT entry.
1000
990
 
1001
991
  Parameters
@@ -1008,12 +998,15 @@ class Master:
1008
998
  address : int
1009
999
  """
1010
1000
  addr = self.DWORD_pack(address)
1011
- return self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr)
1001
+ result = self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr)
1002
+ self.stim.writeDaq(bitOffset, entrySize, addressExt, address)
1003
+ return result
1012
1004
 
1013
1005
  @wrapped
1014
1006
  def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority):
1015
1007
  dln = self.WORD_pack(daqListNumber)
1016
1008
  ecn = self.WORD_pack(eventChannelNumber)
1009
+ self.stim.setDaqListMode(mode, daqListNumber, eventChannelNumber, prescaler, priority)
1017
1010
  return self.transport.request(types.Command.SET_DAQ_LIST_MODE, mode, *dln, *ecn, prescaler, priority)
1018
1011
 
1019
1012
  @wrapped
@@ -1033,7 +1026,7 @@ class Master:
1033
1026
  return types.GetDaqListModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1034
1027
 
1035
1028
  @wrapped
1036
- def startStopDaqList(self, mode, daqListNumber):
1029
+ def startStopDaqList(self, mode: int, daqListNumber: int):
1037
1030
  """Start /stop/select DAQ list.
1038
1031
 
1039
1032
  Parameters
@@ -1046,7 +1039,10 @@ class Master:
1046
1039
  """
1047
1040
  dln = self.WORD_pack(daqListNumber)
1048
1041
  response = self.transport.request(types.Command.START_STOP_DAQ_LIST, mode, *dln)
1049
- return types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1042
+ self.stim.startStopDaqList(mode, daqListNumber)
1043
+ firstPid = types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1044
+ self.stim.set_first_pid(daqListNumber, firstPid.firstPid)
1045
+ return firstPid
1050
1046
 
1051
1047
  @wrapped
1052
1048
  def startStopSynch(self, mode):
@@ -1059,7 +1055,9 @@ class Master:
1059
1055
  1 = start selected
1060
1056
  2 = stop selected
1061
1057
  """
1062
- return self.transport.request(types.Command.START_STOP_SYNCH, mode)
1058
+ res = self.transport.request(types.Command.START_STOP_SYNCH, mode)
1059
+ self.stim.startStopSynch(mode)
1060
+ return res
1063
1061
 
1064
1062
  @wrapped
1065
1063
  def writeDaqMultiple(self, daqElements):
@@ -1070,7 +1068,7 @@ class Master:
1070
1068
  daqElements : list of `dict` containing the following keys: *bitOffset*, *size*, *address*, *addressExt*.
1071
1069
  """
1072
1070
  if len(daqElements) > self.slaveProperties.maxWriteDaqMultipleElements:
1073
- raise ValueError("At most {} daqElements are permitted.".format(self.slaveProperties.maxWriteDaqMultipleElements))
1071
+ raise ValueError(f"At most {self.slaveProperties.maxWriteDaqMultipleElements} daqElements are permitted.")
1074
1072
  data = bytearray()
1075
1073
  data.append(len(daqElements))
1076
1074
 
@@ -1217,10 +1215,12 @@ class Master:
1217
1215
  @wrapped
1218
1216
  def freeDaq(self):
1219
1217
  """Clear dynamic DAQ configuration."""
1220
- return self.transport.request(types.Command.FREE_DAQ)
1218
+ result = self.transport.request(types.Command.FREE_DAQ)
1219
+ self.stim.freeDaq()
1220
+ return result
1221
1221
 
1222
1222
  @wrapped
1223
- def allocDaq(self, daqCount):
1223
+ def allocDaq(self, daqCount: int):
1224
1224
  """Allocate DAQ lists.
1225
1225
 
1226
1226
  Parameters
@@ -1229,17 +1229,23 @@ class Master:
1229
1229
  number of DAQ lists to be allocated
1230
1230
  """
1231
1231
  dq = self.WORD_pack(daqCount)
1232
- return self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
1232
+ result = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
1233
+ self.stim.allocDaq(daqCount)
1234
+ return result
1233
1235
 
1234
1236
  @wrapped
1235
- def allocOdt(self, daqListNumber, odtCount):
1237
+ def allocOdt(self, daqListNumber: int, odtCount: int):
1236
1238
  dln = self.WORD_pack(daqListNumber)
1237
- return self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount)
1239
+ result = self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount)
1240
+ self.stim.allocOdt(daqListNumber, odtCount)
1241
+ return result
1238
1242
 
1239
1243
  @wrapped
1240
- def allocOdtEntry(self, daqListNumber, odtNumber, odtEntriesCount):
1244
+ def allocOdtEntry(self, daqListNumber: int, odtNumber: int, odtEntriesCount: int):
1241
1245
  dln = self.WORD_pack(daqListNumber)
1242
- return self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount)
1246
+ result = self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount)
1247
+ self.stim.allocOdtEntry(daqListNumber, odtNumber, odtEntriesCount)
1248
+ return result
1243
1249
 
1244
1250
  # PGM
1245
1251
  @wrapped
@@ -1621,12 +1627,13 @@ class Master:
1621
1627
  @broadcasted
1622
1628
  @wrapped
1623
1629
  def getSlaveID(self, mode: int):
1624
- self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, "X", "C", "P", mode)
1630
+ response = self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, ord("X"), ord("C"), ord("P"), mode)
1631
+ return types.GetSlaveIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1625
1632
 
1626
1633
  def getDaqId(self, daqListNumber: int):
1627
1634
  response = self.transportLayerCmd(types.TransportLayerCommands.GET_DAQ_ID, *self.WORD_pack(daqListNumber))
1628
- if response:
1629
- return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1635
+ # if response:
1636
+ return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder)
1630
1637
 
1631
1638
  def setDaqId(self, daqListNumber: int, identifier: int):
1632
1639
  response = self.transportLayerCmd(
@@ -1650,11 +1657,11 @@ class Master:
1650
1657
  """
1651
1658
  self.setMta(addr)
1652
1659
  cs = self.buildChecksum(length)
1653
- self.logger.debug("BuildChecksum return'd: 0x{:08X} [{}]".format(cs.checksum, cs.checksumType))
1660
+ self.logger.debug(f"BuildChecksum return'd: 0x{cs.checksum:08X} [{cs.checksumType}]")
1654
1661
  self.setMta(addr)
1655
1662
  data = self.fetch(length)
1656
1663
  cc = checksum.check(data, cs.checksumType)
1657
- self.logger.debug("Our checksum : 0x{:08X}".format(cc))
1664
+ self.logger.debug(f"Our checksum : 0x{cc:08X}")
1658
1665
  return cs.checksum == cc
1659
1666
 
1660
1667
  def getDaqInfo(self):
@@ -1696,10 +1703,18 @@ class Master:
1696
1703
  },
1697
1704
  }
1698
1705
  result["resolution"] = resolutionInfo
1699
-
1700
1706
  channels = []
1707
+ daq_events = []
1701
1708
  for ecn in range(dpi.maxEventChannel):
1702
1709
  eci = self.getDaqEventInfo(ecn)
1710
+ cycle = eci["eventChannelTimeCycle"]
1711
+ maxDaqList = eci["maxDaqList"]
1712
+ priority = eci["eventChannelPriority"]
1713
+ time_unit = eci["eventChannelTimeUnit"]
1714
+ consistency = eci["daqEventProperties"]["consistency"]
1715
+ daq_supported = eci["daqEventProperties"]["daq"]
1716
+ stim_supported = eci["daqEventProperties"]["stim"]
1717
+ packed_supported = eci["daqEventProperties"]["packed"]
1703
1718
  name = self.fetch(eci.eventChannelNameLength)
1704
1719
  if name:
1705
1720
  name = decode_bytes(name)
@@ -1710,14 +1725,27 @@ class Master:
1710
1725
  "cycle": eci["eventChannelTimeCycle"],
1711
1726
  "maxDaqList": eci["maxDaqList"],
1712
1727
  "properties": {
1713
- "consistency": eci["daqEventProperties"]["consistency"],
1714
- "daq": eci["daqEventProperties"]["daq"],
1715
- "stim": eci["daqEventProperties"]["stim"],
1716
- "packed": eci["daqEventProperties"]["packed"],
1728
+ "consistency": consistency,
1729
+ "daq": daq_supported,
1730
+ "stim": stim_supported,
1731
+ "packed": packed_supported,
1717
1732
  },
1718
1733
  }
1734
+ daq_event_info = DaqEventInfo(
1735
+ name,
1736
+ types.EVENT_CHANNEL_TIME_UNIT_TO_EXP[time_unit],
1737
+ cycle,
1738
+ maxDaqList,
1739
+ priority,
1740
+ consistency,
1741
+ daq_supported,
1742
+ stim_supported,
1743
+ packed_supported,
1744
+ )
1745
+ daq_events.append(daq_event_info)
1719
1746
  channels.append(channel)
1720
1747
  result["channels"] = channels
1748
+ self.stim.setDaqEventInfo(daq_events)
1721
1749
  return result
1722
1750
 
1723
1751
  def getCurrentProtectionStatus(self):
@@ -1760,12 +1788,14 @@ class Master:
1760
1788
  In case of DLL related issues.
1761
1789
  """
1762
1790
  import re
1763
- from pyxcp.dllif import getKey, SeedNKeyResult, SeedNKeyError
1791
+
1792
+ from pyxcp.dllif import SeedNKeyError, SeedNKeyResult, getKey
1764
1793
 
1765
1794
  MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2
1766
1795
 
1767
- if not self.seedNKeyDLL:
1768
- raise RuntimeError("No seed and key DLL specified, cannot proceed.")
1796
+ protection_status = self.getCurrentProtectionStatus()
1797
+ if any(protection_status.values()) and (not (self.seed_n_key_dll or self.seed_n_key_function)):
1798
+ raise RuntimeError("Neither seed-and-key DLL nor function specified, cannot proceed.") # TODO: ConfigurationError
1769
1799
  if resources is None:
1770
1800
  result = []
1771
1801
  if self.slaveProperties["supportsCalpag"]:
@@ -1777,11 +1807,10 @@ class Master:
1777
1807
  if self.slaveProperties["supportsPgm"]:
1778
1808
  result.append("pgm")
1779
1809
  resources = ",".join(result)
1780
- protection_status = self.getCurrentProtectionStatus()
1781
1810
  resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r]
1782
1811
  for name in resource_names:
1783
1812
  if name not in types.RESOURCE_VALUES:
1784
- raise ValueError("Invalid resource name '{}'.".format(name))
1813
+ raise ValueError(f"Invalid resource name {name!r}.")
1785
1814
  if not protection_status[name]:
1786
1815
  continue
1787
1816
  resource_value = types.RESOURCE_VALUES[name]
@@ -1790,30 +1819,38 @@ class Master:
1790
1819
  length = result.length
1791
1820
  if length == 0:
1792
1821
  continue
1793
-
1794
- while length - len(seed) > 0:
1795
- result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value)
1796
- seed.extend(list(result.seed))
1797
-
1798
- seed = seed[: length] # maybe there are some padding bytes
1799
-
1800
- result, key = getKey(
1801
- self.logger,
1802
- self.seedNKeyDLL,
1803
- resource_value,
1804
- bytes(seed),
1805
- self.seedNKeyDLL_same_bit_width,
1806
- )
1822
+ if length > MAX_PAYLOAD:
1823
+ remaining = length - len(seed)
1824
+ while remaining > 0:
1825
+ result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value)
1826
+ seed.extend(list(result.seed))
1827
+ remaining -= result.length
1828
+ self.logger.debug(f"Got seed {seed!r} for resource {resource_value!r}.")
1829
+ if self.seed_n_key_function:
1830
+ key = self.seed_n_key_function(resource_value, bytes(seed))
1831
+ self.logger.debug(f"Using seed and key function {self.seed_n_key_function.__name__!r}().")
1832
+ result = SeedNKeyResult.ACK
1833
+ elif self.seed_n_key_dll:
1834
+ self.logger.debug(f"Using seed and key DLL {self.seed_n_key_dll!r}.")
1835
+ result, key = getKey(
1836
+ self.logger,
1837
+ self.seed_n_key_dll,
1838
+ resource_value,
1839
+ bytes(seed),
1840
+ self.seed_n_key_dll_same_bit_width,
1841
+ )
1807
1842
  if result == SeedNKeyResult.ACK:
1808
1843
  key = list(key)
1809
- total_length = len(key)
1810
- offset = 0
1811
- while offset < total_length:
1812
- data = key[offset : offset + MAX_PAYLOAD]
1813
- self.unlock(total_length-offset, data)
1814
- offset += len(data)
1844
+ self.logger.debug(f"Unlocking resource {resource_value!r} with key {key!r}.")
1845
+ remaining = len(key)
1846
+ while key:
1847
+ data = key[:MAX_PAYLOAD]
1848
+ key_len = len(data)
1849
+ self.unlock(remaining, data)
1850
+ key = key[MAX_PAYLOAD:]
1851
+ remaining -= key_len
1815
1852
  else:
1816
- raise SeedNKeyError("SeedAndKey DLL returned: {}".format(SeedNKeyResult(result).name))
1853
+ raise SeedNKeyError(f"SeedAndKey DLL returned: {SeedNKeyResult(result).name!r}")
1817
1854
 
1818
1855
  def identifier(self, id_value: int) -> str:
1819
1856
  """Return the identifier for the given value.
@@ -1891,18 +1928,61 @@ class Master:
1891
1928
 
1892
1929
  gen = make_generator(scan_ranges)
1893
1930
  for id_value, name in gen:
1894
- response = b""
1895
- try:
1896
- response = self.identifier(id_value)
1897
- except types.XcpResponseError:
1898
- # don't depend on confirming implementation, i.e.: ID not implemented ==> empty response.
1899
- pass
1900
- except Exception:
1901
- raise
1902
- if response:
1931
+ status, response = self.try_command(self.identifier, id_value)
1932
+ if status == types.TryCommandResult.OK and response:
1903
1933
  result[name] = response
1934
+ elif status == types.TryCommandResult.XCP_ERROR and response.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1935
+ break # Nothing to do here.
1936
+ elif status == types.TryCommandResult.OTHER_ERROR:
1937
+ raise RuntimeError(f"Error while scanning for ID {id_value}: {response!r}")
1904
1938
  return result
1905
1939
 
1940
+ @property
1941
+ def start_datetime(self) -> int:
1942
+ """"""
1943
+ return self.transport.start_datetime
1944
+
1945
+ def try_command(self, cmd: Callable, *args, **kws) -> Tuple[types.TryCommandResult, Any]:
1946
+ """Call master functions and handle XCP errors more gracefuly.
1947
+
1948
+ Parameter
1949
+ ---------
1950
+ cmd: Callable
1951
+ args: list
1952
+ variable length arguments to `cmd`.
1953
+ kws: dict
1954
+ keyword arguments to `cmd`.
1955
+
1956
+ `extra_msg`: str
1957
+ Additional info to log message (not passed to `cmd`).
1958
+
1959
+ Returns
1960
+ -------
1961
+
1962
+ Note
1963
+ ----
1964
+ Mainly used for plug-and-play applications, e.g. `id_scanner` may confronted with `ERR_OUT_OF_RANGE` errors, which
1965
+ is normal for this kind of applications -- or to test for optional commands.
1966
+ Use carefuly not to hide serious error causes.
1967
+ """
1968
+ try:
1969
+ extra_msg: Optional[str] = kws.get("extra_msg")
1970
+ if extra_msg:
1971
+ kws.pop("extra_msg")
1972
+ res = cmd(*args, **kws)
1973
+ except SystemExit as e:
1974
+ if e.error_code == types.XcpError.ERR_CMD_UNKNOWN:
1975
+ # This is a rather common use-case, so let the user know that there is some functionality missing.
1976
+ if extra_msg:
1977
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented -- {extra_msg!r}")
1978
+ else:
1979
+ self.logger.warning(f"Optional command {cmd.__name__!r} not implemented.")
1980
+ return (types.TryCommandResult.XCP_ERROR, e)
1981
+ except Exception as e:
1982
+ return (types.TryCommandResult.OTHER_ERROR, e)
1983
+ else:
1984
+ return (types.TryCommandResult.OK, res)
1985
+
1906
1986
 
1907
1987
  def ticks_to_seconds(ticks, resolution):
1908
1988
  """Convert DAQ timestamp/tick value to seconds.
@@ -1916,6 +1996,7 @@ def ticks_to_seconds(ticks, resolution):
1916
1996
  warnings.warn(
1917
1997
  "ticks_to_seconds() deprecated, use factory :func:`make_tick_converter` instead.",
1918
1998
  Warning,
1999
+ stacklevel=1,
1919
2000
  )
1920
2001
  return (10 ** types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]) * resolution.timestampTicks * ticks
1921
2002
 
pyxcp/py.typed ADDED
File without changes
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
2
  """XCP Frame Recording Facility.
4
3
  """
5
4
  from dataclasses import dataclass
6
- from enum import IntEnum
7
5
  from typing import Union
8
6
 
9
7
  from pyxcp.types import FrameCategory
10
8
 
9
+
11
10
  try:
12
11
  import pandas as pd
13
12
  except ImportError:
@@ -15,13 +14,28 @@ except ImportError:
15
14
  else:
16
15
  HAS_PANDAS = True
17
16
 
18
- import rekorder as rec
17
+ from pyxcp.recorder.rekorder import DaqOnlinePolicy # noqa: F401
18
+ from pyxcp.recorder.rekorder import (
19
+ DaqRecorderPolicy,
20
+ Deserializer,
21
+ MeasurementParameters,
22
+ ValueHolder,
23
+ XcpLogFileDecoder,
24
+ _PyXcpLogFileReader,
25
+ _PyXcpLogFileWriter,
26
+ data_types,
27
+ )
28
+
29
+
30
+ DATA_TYPES = data_types()
19
31
 
20
32
 
21
33
  @dataclass
22
34
  class XcpLogFileHeader:
23
35
  """ """
24
36
 
37
+ version: int
38
+ options: int
25
39
  num_containers: int
26
40
  record_count: int
27
41
  size_uncompressed: int
@@ -36,11 +50,18 @@ class XcpLogFileReader:
36
50
  """ """
37
51
 
38
52
  def __init__(self, file_name):
39
- self._reader = rec._PyXcpLogFileReader(file_name)
53
+ self._reader = _PyXcpLogFileReader(file_name)
54
+
55
+ @property
56
+ def header(self):
57
+ return self._reader.get_header()
40
58
 
41
59
  def get_header(self):
42
60
  return XcpLogFileHeader(*self._reader.get_header_as_tuple())
43
61
 
62
+ def get_metadata(self):
63
+ return self._reader.get_metadata()
64
+
44
65
  def __iter__(self):
45
66
  while True:
46
67
  frames = self._reader.next_block()
@@ -66,8 +87,8 @@ class XcpLogFileReader:
66
87
  class XcpLogFileWriter:
67
88
  """ """
68
89
 
69
- def __init__(self, file_name: str, prealloc=10, chunk_size=1):
70
- self._writer = rec._PyXcpLogFileWriter(file_name, prealloc, chunk_size)
90
+ def __init__(self, file_name: str, prealloc=500, chunk_size=1):
91
+ self._writer = _PyXcpLogFileWriter(file_name, prealloc, chunk_size)
71
92
  self._finalized = False
72
93
 
73
94
  def __del__(self):
@@ -0,0 +1,37 @@
1
+ import logging
2
+ from array import array
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, List
5
+
6
+
7
+ MAP_TO_ARRAY = {
8
+ "U8": "B",
9
+ "I8": "b",
10
+ "U16": "H",
11
+ "I16": "h",
12
+ "U32": "L",
13
+ "I32": "l",
14
+ "U64": "Q",
15
+ "I64": "q",
16
+ "F32": "f",
17
+ "F64": "d",
18
+ "F16": "f",
19
+ "BF16": "f",
20
+ }
21
+
22
+ logger = logging.getLogger("PyXCP")
23
+
24
+
25
+ @dataclass
26
+ class Storage:
27
+ name: str
28
+ arrow_type: Any
29
+ arr: array
30
+
31
+
32
+ @dataclass
33
+ class StorageContainer:
34
+ name: str
35
+ arr: List[Storage] = field(default_factory=[])
36
+ ts0: List[int] = field(default_factory=lambda: array("Q"))
37
+ ts1: List[int] = field(default_factory=lambda: array("Q"))