pyxcp 0.23.0__cp311-cp311-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 (128) hide show
  1. pyxcp/__init__.py +20 -0
  2. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  3. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  4. pyxcp/aml/XCP_Common.aml +408 -0
  5. pyxcp/aml/XCPonCAN.aml +78 -0
  6. pyxcp/aml/XCPonEth.aml +33 -0
  7. pyxcp/aml/XCPonFlx.aml +113 -0
  8. pyxcp/aml/XCPonSxI.aml +66 -0
  9. pyxcp/aml/XCPonUSB.aml +106 -0
  10. pyxcp/aml/ifdata_CAN.a2l +20 -0
  11. pyxcp/aml/ifdata_Eth.a2l +11 -0
  12. pyxcp/aml/ifdata_Flx.a2l +94 -0
  13. pyxcp/aml/ifdata_SxI.a2l +13 -0
  14. pyxcp/aml/ifdata_USB.a2l +81 -0
  15. pyxcp/asam/__init__.py +0 -0
  16. pyxcp/asam/types.py +131 -0
  17. pyxcp/asamkeydll.c +116 -0
  18. pyxcp/asamkeydll.sh +2 -0
  19. pyxcp/checksum.py +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/daqlist.hpp +206 -0
  30. pyxcp/cpp_ext/event.hpp +67 -0
  31. pyxcp/cpp_ext/extension_wrapper.cpp +100 -0
  32. pyxcp/cpp_ext/helper.hpp +280 -0
  33. pyxcp/cpp_ext/mcobject.hpp +246 -0
  34. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  35. pyxcp/daq_stim/__init__.py +232 -0
  36. pyxcp/daq_stim/optimize/__init__.py +67 -0
  37. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  38. pyxcp/daq_stim/scheduler.cpp +62 -0
  39. pyxcp/daq_stim/scheduler.hpp +75 -0
  40. pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
  41. pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
  42. pyxcp/daq_stim/stim.cpp +13 -0
  43. pyxcp/daq_stim/stim.hpp +604 -0
  44. pyxcp/daq_stim/stim_wrapper.cpp +50 -0
  45. pyxcp/dllif.py +100 -0
  46. pyxcp/errormatrix.py +878 -0
  47. pyxcp/examples/conf_can.toml +19 -0
  48. pyxcp/examples/conf_can_user.toml +16 -0
  49. pyxcp/examples/conf_can_vector.json +11 -0
  50. pyxcp/examples/conf_can_vector.toml +11 -0
  51. pyxcp/examples/conf_eth.toml +9 -0
  52. pyxcp/examples/conf_nixnet.json +20 -0
  53. pyxcp/examples/conf_socket_can.toml +12 -0
  54. pyxcp/examples/conf_sxi.json +9 -0
  55. pyxcp/examples/conf_sxi.toml +7 -0
  56. pyxcp/examples/run_daq.py +163 -0
  57. pyxcp/examples/xcp_policy.py +60 -0
  58. pyxcp/examples/xcp_read_benchmark.py +38 -0
  59. pyxcp/examples/xcp_skel.py +49 -0
  60. pyxcp/examples/xcp_unlock.py +38 -0
  61. pyxcp/examples/xcp_user_supplied_driver.py +44 -0
  62. pyxcp/examples/xcphello.py +78 -0
  63. pyxcp/examples/xcphello_recorder.py +107 -0
  64. pyxcp/master/__init__.py +9 -0
  65. pyxcp/master/errorhandler.py +442 -0
  66. pyxcp/master/master.py +2047 -0
  67. pyxcp/py.typed +0 -0
  68. pyxcp/recorder/__init__.py +101 -0
  69. pyxcp/recorder/build_clang.cmd +1 -0
  70. pyxcp/recorder/build_clang.sh +2 -0
  71. pyxcp/recorder/build_gcc.cmd +1 -0
  72. pyxcp/recorder/build_gcc.sh +2 -0
  73. pyxcp/recorder/build_gcc_arm.sh +2 -0
  74. pyxcp/recorder/converter/__init__.py +450 -0
  75. pyxcp/recorder/lz4.c +2829 -0
  76. pyxcp/recorder/lz4.h +879 -0
  77. pyxcp/recorder/lz4hc.c +2041 -0
  78. pyxcp/recorder/lz4hc.h +413 -0
  79. pyxcp/recorder/mio.hpp +1714 -0
  80. pyxcp/recorder/reader.hpp +139 -0
  81. pyxcp/recorder/reco.py +277 -0
  82. pyxcp/recorder/recorder.rst +0 -0
  83. pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
  84. pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
  85. pyxcp/recorder/rekorder.cpp +59 -0
  86. pyxcp/recorder/rekorder.hpp +274 -0
  87. pyxcp/recorder/setup.py +41 -0
  88. pyxcp/recorder/test_reko.py +34 -0
  89. pyxcp/recorder/unfolder.hpp +1332 -0
  90. pyxcp/recorder/wrap.cpp +189 -0
  91. pyxcp/recorder/writer.hpp +302 -0
  92. pyxcp/scripts/__init__.py +0 -0
  93. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  94. pyxcp/scripts/xcp_examples.py +64 -0
  95. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  96. pyxcp/scripts/xcp_id_scanner.py +19 -0
  97. pyxcp/scripts/xcp_info.py +144 -0
  98. pyxcp/scripts/xcp_profile.py +27 -0
  99. pyxcp/scripts/xmraw_converter.py +31 -0
  100. pyxcp/stim/__init__.py +0 -0
  101. pyxcp/tests/test_asam_types.py +24 -0
  102. pyxcp/tests/test_binpacking.py +186 -0
  103. pyxcp/tests/test_can.py +1324 -0
  104. pyxcp/tests/test_checksum.py +95 -0
  105. pyxcp/tests/test_daq.py +193 -0
  106. pyxcp/tests/test_daq_opt.py +426 -0
  107. pyxcp/tests/test_frame_padding.py +156 -0
  108. pyxcp/tests/test_master.py +2006 -0
  109. pyxcp/tests/test_transport.py +81 -0
  110. pyxcp/tests/test_utils.py +30 -0
  111. pyxcp/timing.py +60 -0
  112. pyxcp/transport/__init__.py +10 -0
  113. pyxcp/transport/base.py +440 -0
  114. pyxcp/transport/base_transport.hpp +0 -0
  115. pyxcp/transport/can.py +556 -0
  116. pyxcp/transport/eth.py +219 -0
  117. pyxcp/transport/sxi.py +135 -0
  118. pyxcp/transport/transport_wrapper.cpp +0 -0
  119. pyxcp/transport/usb_transport.py +213 -0
  120. pyxcp/types.py +1000 -0
  121. pyxcp/utils.py +128 -0
  122. pyxcp/vector/__init__.py +0 -0
  123. pyxcp/vector/map.py +82 -0
  124. pyxcp-0.23.0.dist-info/LICENSE +165 -0
  125. pyxcp-0.23.0.dist-info/METADATA +107 -0
  126. pyxcp-0.23.0.dist-info/RECORD +128 -0
  127. pyxcp-0.23.0.dist-info/WHEEL +4 -0
  128. pyxcp-0.23.0.dist-info/entry_points.txt +9 -0
@@ -0,0 +1,1113 @@
1
+ #!/usr/bin/env python
2
+ import io
3
+ import json
4
+ import logging
5
+ import sys
6
+ import typing
7
+ from pathlib import Path
8
+
9
+ import can
10
+ import toml
11
+ from rich.logging import RichHandler
12
+ from rich.prompt import Confirm
13
+ from traitlets import (
14
+ Any,
15
+ Bool,
16
+ Callable,
17
+ Dict,
18
+ Enum,
19
+ Float,
20
+ Integer,
21
+ List,
22
+ TraitError,
23
+ Unicode,
24
+ Union,
25
+ )
26
+ from traitlets.config import Application, Configurable, Instance, default
27
+
28
+ from pyxcp.config import legacy
29
+
30
+
31
+ class CanBase:
32
+ has_fd = False
33
+ has_bitrate = True
34
+ has_data_bitrate = False
35
+ has_poll_interval = False
36
+ has_receive_own_messages = False
37
+ has_timing = False
38
+
39
+ OPTIONAL_BASE_PARAMS = (
40
+ "has_fd",
41
+ "has_bitrate",
42
+ "has_data_bitrate",
43
+ "has_poll_interval",
44
+ "has_receive_own_messages",
45
+ "has_timing",
46
+ )
47
+
48
+ CAN_PARAM_MAP = {
49
+ "sjw_abr": None,
50
+ "tseg1_abr": None,
51
+ "tseg2_abr": None,
52
+ "sjw_dbr": None,
53
+ "tseg1_dbr": None,
54
+ "tseg2_dbr": None,
55
+ }
56
+
57
+
58
+ class CanAlystii(Configurable, CanBase):
59
+ """CANalyst-II is a USB to CAN Analyzer device produced by Chuangxin Technology."""
60
+
61
+ interface_name = "canalystii"
62
+
63
+ has_timing = True
64
+
65
+ device = Integer(default_value=None, allow_none=True, help="""Optional USB device number.""").tag(config=True)
66
+ rx_queue_size = Integer(
67
+ default_value=None,
68
+ allow_none=True,
69
+ help="""If set, software received message queue can only grow to this many
70
+ messages (for all channels) before older messages are dropped """,
71
+ ).tag(config=True)
72
+
73
+
74
+ class CanTact(Configurable, CanBase):
75
+ """Interface for CANtact devices from Linklayer Labs"""
76
+
77
+ interface_name = "cantact"
78
+
79
+ has_poll_interval = True
80
+ has_timing = True
81
+
82
+ monitor = Bool(default_value=False, allow_none=True, help="""If true, operate in listen-only monitoring mode""").tag(
83
+ config=True
84
+ )
85
+
86
+
87
+ class Etas(Configurable, CanBase):
88
+ """ETAS"""
89
+
90
+ interface_name = "etas"
91
+
92
+ has_fd = True
93
+ has_data_bitrate = True
94
+ has_receive_own_messages = True
95
+
96
+
97
+ class Gs_Usb(Configurable, CanBase):
98
+ """Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces."""
99
+
100
+ interface_name = "gs_usb"
101
+
102
+ index = Integer(
103
+ default_value=None,
104
+ allow_none=True,
105
+ help="""device number if using automatic scan, starting from 0.
106
+ If specified, bus/address shall not be provided.""",
107
+ ).tag(config=True)
108
+ bus = Integer(default_value=None, allow_none=True, help="""number of the bus that the device is connected to""").tag(
109
+ config=True
110
+ )
111
+ address = Integer(default_value=None, allow_none=True, help="""address of the device on the bus it is connected to""").tag(
112
+ config=True
113
+ )
114
+
115
+
116
+ class Neovi(Configurable, CanBase):
117
+ """Intrepid Control Systems (ICS) neoVI interfaces."""
118
+
119
+ interface_name = "neovi"
120
+
121
+ has_fd = True
122
+ has_data_bitrate = True
123
+ has_receive_own_messages = True
124
+
125
+ use_system_timestamp = Bool(
126
+ default_value=None, allow_none=True, help="Use system timestamp for can messages instead of the hardware timestamp"
127
+ ).tag(config=True)
128
+ serial = Unicode(
129
+ default_value=None, allow_none=True, help="Serial to connect (optional, will use the first found if not supplied)"
130
+ ).tag(config=True)
131
+ override_library_name = Unicode(
132
+ default_value=None, allow_none=True, help="Absolute path or relative path to the library including filename."
133
+ ).tag(config=True)
134
+
135
+
136
+ class IsCan(Configurable, CanBase):
137
+ """Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH."""
138
+
139
+ interface_name = "iscan"
140
+
141
+ has_poll_interval = True
142
+
143
+
144
+ class Ixxat(Configurable, CanBase):
145
+ """IXXAT Virtual Communication Interface"""
146
+
147
+ interface_name = "ixxat"
148
+
149
+ has_fd = True
150
+ has_data_bitrate = True
151
+ has_receive_own_messages = True
152
+
153
+ unique_hardware_id = Integer(
154
+ default_value=None,
155
+ allow_none=True,
156
+ help="""UniqueHardwareId to connect (optional, will use the first found if not supplied)""",
157
+ ).tag(config=True)
158
+ extended = Bool(default_value=None, allow_none=True, help="""Enables the capability to use extended IDs.""").tag(config=True)
159
+ rx_fifo_size = Integer(default_value=None, allow_none=True, help="""Receive fifo size""").tag(config=True)
160
+ tx_fifo_size = Integer(default_value=None, allow_none=True, help="""Transmit fifo size""").tag(config=True)
161
+ ssp_dbr = Integer(
162
+ default_value=None,
163
+ allow_none=True,
164
+ help="Secondary sample point (data). Only takes effect with fd and bitrate switch enabled.",
165
+ ).tag(config=True)
166
+
167
+ CAN_PARAM_MAP = {
168
+ "sjw_abr": "sjw_abr",
169
+ "tseg1_abr": "tseg1_abr",
170
+ "tseg2_abr": "tseg2_abr",
171
+ "sjw_dbr": "sjw_dbr",
172
+ "tseg1_dbr": "tseg1_dbr",
173
+ "tseg2_dbr": "tseg2_dbr",
174
+ }
175
+
176
+
177
+ class Kvaser(Configurable, CanBase):
178
+ """Kvaser's CANLib"""
179
+
180
+ interface_name = "kvaser"
181
+
182
+ has_fd = True
183
+ has_data_bitrate = True
184
+ has_receive_own_messages = True
185
+
186
+ CAN_PARAM_MAP = {
187
+ "sjw_abr": "sjw",
188
+ "tseg1_abr": "tseg1",
189
+ "tseg2_abr": "tseg2",
190
+ }
191
+
192
+ accept_virtual = Bool(default_value=None, allow_none=True, help="If virtual channels should be accepted.").tag(config=True)
193
+ no_samp = Enum(
194
+ values=[1, 3],
195
+ default_value=None,
196
+ allow_none=True,
197
+ help="""Either 1 or 3. Some CAN controllers can also sample each bit three times.
198
+ In this case, the bit will be sampled three quanta in a row,
199
+ with the last sample being taken in the edge between TSEG1 and TSEG2.
200
+ Three samples should only be used for relatively slow baudrates""",
201
+ ).tag(config=True)
202
+ driver_mode = Bool(default_value=None, allow_none=True, help="Silent or normal.").tag(config=True)
203
+ single_handle = Bool(
204
+ default_value=None,
205
+ allow_none=True,
206
+ help="""Use one Kvaser CANLIB bus handle for both reading and writing.
207
+ This can be set if reading and/or writing is done from one thread. """,
208
+ ).tag(config=True)
209
+
210
+
211
+ class NeouSys(Configurable, CanBase):
212
+ """Neousys CAN Interface"""
213
+
214
+ interface_name = "neousys"
215
+
216
+ device = Integer(default_value=None, allow_none=True, help="Device number").tag(config=True)
217
+
218
+
219
+ class NiCan(Configurable, CanBase):
220
+ """National Instruments NI-CAN"""
221
+
222
+ interface_name = "nican"
223
+
224
+ log_errors = Bool(
225
+ default_value=None,
226
+ allow_none=True,
227
+ help="""If True, communication errors will appear as CAN messages with
228
+ ``is_error_frame`` set to True and ``arbitration_id`` will identify
229
+ the error. """,
230
+ ).tag(config=True)
231
+
232
+
233
+ class NixNet(Configurable, CanBase):
234
+ """National Instruments NI-XNET"""
235
+
236
+ interface_name = "nixnet"
237
+
238
+ has_poll_interval = True
239
+ has_receive_own_messages = True
240
+ has_timing = True
241
+ has_fd = True
242
+
243
+ CAN_PARAM_MAP = {
244
+ "data_bitrate": "fd_bitrate",
245
+ }
246
+
247
+ can_termination = Bool(default_value=None, allow_none=True, help="Enable bus termination.")
248
+
249
+
250
+ class PCan(Configurable, CanBase):
251
+ """PCAN Basic API"""
252
+
253
+ interface_name = "pcan"
254
+
255
+ has_fd = True
256
+ has_timing = True
257
+
258
+ CAN_PARAM_MAP = {
259
+ "sjw_abr": "nom_sjw",
260
+ "tseg1_abr": "nom_tseg1",
261
+ "tseg2_abr": "nom_tseg2",
262
+ "sjw_dbr": "data_sjw",
263
+ "tseg1_dbr": "data_tseg1",
264
+ "tseg2_dbr": "data_tseg2",
265
+ }
266
+
267
+ device_id = Integer(
268
+ default_value=None,
269
+ allow_none=True,
270
+ help="""Select the PCAN interface based on its ID. The device ID is a 8/32bit
271
+ value that can be configured for each PCAN device. If you set the
272
+ device_id parameter, it takes precedence over the channel parameter.
273
+ The constructor searches all connected interfaces and initializes the
274
+ first one that matches the parameter value. If no device is found,
275
+ an exception is raised.""",
276
+ ).tag(config=True)
277
+ state = Instance(klass=can.BusState, default_value=None, allow_none=True, help="BusState of the channel.").tag(config=True)
278
+
279
+ f_clock = Enum(
280
+ values=[20000000, 24000000, 30000000, 40000000, 60000000, 80000000],
281
+ default_value=None,
282
+ allow_none=True,
283
+ help="""Ignored if not using CAN-FD.
284
+ Pass either f_clock or f_clock_mhz.""",
285
+ ).tag(config=True)
286
+ f_clock_mhz = Enum(
287
+ values=[20, 24, 30, 40, 60, 80],
288
+ default_value=None,
289
+ allow_none=True,
290
+ help="""Ignored if not using CAN-FD.
291
+ Pass either f_clock or f_clock_mhz. """,
292
+ ).tag(config=True)
293
+
294
+ nom_brp = Integer(
295
+ min=1,
296
+ max=1024,
297
+ default_value=None,
298
+ allow_none=True,
299
+ help="""Clock prescaler for nominal time quantum.
300
+ Ignored if not using CAN-FD.""",
301
+ ).tag(config=True)
302
+ data_brp = Integer(
303
+ min=1,
304
+ max=1024,
305
+ default_value=None,
306
+ allow_none=True,
307
+ help="""Clock prescaler for fast data time quantum.
308
+ Ignored if not using CAN-FD.""",
309
+ ).tag(config=True)
310
+
311
+ auto_reset = Bool(
312
+ default_value=None,
313
+ allow_none=True,
314
+ help="""Enable automatic recovery in bus off scenario.
315
+ Resetting the driver takes ~500ms during which
316
+ it will not be responsive.""",
317
+ ).tag(config=True)
318
+
319
+
320
+ class Robotell(Configurable, CanBase):
321
+ """Interface for Chinese Robotell compatible interfaces"""
322
+
323
+ interface_name = "robotell"
324
+
325
+ ttyBaudrate = Integer(
326
+ default_value=None,
327
+ allow_none=True,
328
+ help="""baudrate of underlying serial or usb device
329
+ (Ignored if set via the `channel` parameter, e.g. COM7@11500).""",
330
+ ).tag(config=True)
331
+ rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True)
332
+
333
+
334
+ class SeeedStudio(Configurable, CanBase):
335
+ """Seeed USB-Can analyzer interface."""
336
+
337
+ interface_name = "seeedstudio"
338
+
339
+ timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True)
340
+ baudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True)
341
+ frame_type = Enum(
342
+ values=["STD", "EXT"], default_value=None, allow_none=True, help="To select standard or extended messages."
343
+ ).tag(config=True)
344
+ operation_mode = Enum(
345
+ values=["normal", "loopback", "silent", "loopback_and_silent"], default_value=None, allow_none=True, help=""" """
346
+ ).tag(config=True)
347
+
348
+
349
+ class Serial(Configurable, CanBase):
350
+ """A text based interface."""
351
+
352
+ interface_name = "serial"
353
+
354
+ has_bitrate = False
355
+
356
+ rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True)
357
+ timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True)
358
+ baudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True)
359
+
360
+
361
+ class SlCan(Configurable, CanBase):
362
+ """CAN over Serial / SLCAN."""
363
+
364
+ interface_name = "slcan"
365
+
366
+ has_poll_interval = True
367
+
368
+ ttyBaudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True)
369
+ rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True)
370
+ timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True)
371
+ btr = Integer(default_value=None, allow_none=True, help="BTR register value to set custom can speed.").tag(config=True)
372
+ sleep_after_open = Float(
373
+ default_value=None, allow_none=True, help="Time to wait in seconds after opening serial connection."
374
+ ).tag(config=True)
375
+
376
+
377
+ class SocketCan(Configurable, CanBase):
378
+ """Linux SocketCAN."""
379
+
380
+ interface_name = "socketcan"
381
+
382
+ has_fd = True
383
+ has_bitrate = False
384
+ has_receive_own_messages = True
385
+
386
+ local_loopback = Bool(
387
+ default_value=None,
388
+ allow_none=True,
389
+ help="""If local loopback should be enabled on this bus.
390
+ Please note that local loopback does not mean that messages sent
391
+ on a socket will be readable on the same socket, they will only
392
+ be readable on other open sockets on the same machine. More info
393
+ can be read on the socketcan documentation:
394
+ See https://www.kernel.org/doc/html/latest/networking/can.html#socketcan-local-loopback1""",
395
+ ).tag(config=True)
396
+
397
+
398
+ class SocketCanD(Configurable, CanBase):
399
+ """Network-to-CAN bridge as a Linux damon."""
400
+
401
+ interface_name = "socketcand"
402
+
403
+ has_bitrate = False
404
+
405
+ host = Unicode(default_value=None, allow_none=True, help=""" """).tag(config=True)
406
+ port = Integer(default_value=None, allow_none=True, help=""" """).tag(config=True)
407
+
408
+
409
+ class Systec(Configurable, CanBase):
410
+ """SYSTEC interface"""
411
+
412
+ interface_name = "systec"
413
+
414
+ has_receive_own_messages = True
415
+
416
+ state = Instance(klass=can.BusState, default_value=None, allow_none=True, help="BusState of the channel.").tag(config=True)
417
+ device_number = Integer(min=0, max=254, default_value=None, allow_none=True, help="The device number of the USB-CAN.").tag(
418
+ config=True
419
+ )
420
+ rx_buffer_entries = Integer(
421
+ default_value=None, allow_none=True, help="The maximum number of entries in the receive buffer."
422
+ ).tag(config=True)
423
+ tx_buffer_entries = Integer(
424
+ default_value=None, allow_none=True, help="The maximum number of entries in the transmit buffer."
425
+ ).tag(config=True)
426
+
427
+
428
+ class Udp_Multicast(Configurable, CanBase):
429
+ """A virtual interface for CAN communications between multiple processes using UDP over Multicast IP."""
430
+
431
+ interface_name = "udp_multicast"
432
+
433
+ has_fd = True
434
+ has_bitrate = False
435
+ has_receive_own_messages = True
436
+
437
+ port = Integer(default_value=None, allow_none=True, help="The IP port to read from and write to.").tag(config=True)
438
+ hop_limit = Integer(default_value=None, allow_none=True, help="The hop limit in IPv6 or in IPv4 the time to live (TTL).").tag(
439
+ config=True
440
+ )
441
+
442
+
443
+ class Usb2Can(Configurable, CanBase):
444
+ """Interface to a USB2CAN Bus."""
445
+
446
+ interface_name = "usb2can"
447
+
448
+ flags = Integer(
449
+ default_value=None, allow_none=True, help="Flags to directly pass to open function of the usb2can abstraction layer."
450
+ ).tag(config=True)
451
+ dll = Unicode(default_value=None, allow_none=True, help="Path to the DLL with the CANAL API to load.").tag(config=True)
452
+ serial = Unicode(default_value=None, allow_none=True, help="Alias for `channel` that is provided for legacy reasons.").tag(
453
+ config=True
454
+ )
455
+
456
+
457
+ class Vector(Configurable, CanBase):
458
+ """Vector Informatik CAN interfaces."""
459
+
460
+ interface_name = "vector"
461
+
462
+ has_fd = True
463
+ has_data_bitrate = True
464
+ has_poll_interval = True
465
+ has_receive_own_messages = True
466
+ has_timing = True
467
+
468
+ CAN_PARAM_MAP = {
469
+ "sjw_abr": "sjw_abr",
470
+ "tseg1_abr": "tseg1_abr",
471
+ "tseg2_abr": "tseg2_abr",
472
+ "sjw_dbr": "sjw_dbr",
473
+ "tseg1_dbr": "tseg1_dbr",
474
+ "tseg2_dbr": "tseg2_dbr",
475
+ }
476
+
477
+ serial = Integer(
478
+ default_value=None,
479
+ allow_none=True,
480
+ help="""Serial number of the hardware to be used.
481
+ If set, the channel parameter refers to the channels ONLY on the specified hardware.
482
+ If set, the `app_name` does not have to be previously defined in
483
+ *Vector Hardware Config*.""",
484
+ ).tag(config=True)
485
+ rx_queue_size = Integer(
486
+ min=16, max=32768, default_value=None, allow_none=True, help="Number of messages in receive queue (power of 2)."
487
+ ).tag(config=True)
488
+ app_name = Unicode(default_value=None, allow_none=True, help="Name of application in *Vector Hardware Config*.").tag(
489
+ config=True
490
+ )
491
+
492
+
493
+ class CanCustom(Configurable, CanBase):
494
+ """ """
495
+
496
+ interface_name = "custom"
497
+
498
+
499
+ class Virtual(Configurable, CanBase):
500
+ """ """
501
+
502
+ interface_name = "virtual"
503
+
504
+ has_bitrate = False
505
+ has_receive_own_messages = True
506
+
507
+ rx_queue_size = Integer(
508
+ default_value=None,
509
+ allow_none=True,
510
+ help="""The size of the reception queue. The reception
511
+ queue stores messages until they are read. If the queue reaches
512
+ its capacity, it will start dropping the oldest messages to make
513
+ room for new ones. If set to 0, the queue has an infinite capacity.
514
+ Be aware that this can cause memory leaks if messages are read
515
+ with a lower frequency than they arrive on the bus. """,
516
+ ).tag(config=True)
517
+ preserve_timestamps = Bool(
518
+ default_value=None,
519
+ allow_none=True,
520
+ help="""If set to True, messages transmitted via
521
+ will keep the timestamp set in the
522
+ :class:`~can.Message` instance. Otherwise, the timestamp value
523
+ will be replaced with the current system time.""",
524
+ ).tag(config=True)
525
+
526
+
527
+ CAN_INTERFACE_MAP = {
528
+ "canalystii": CanAlystii,
529
+ "cantact": CanTact,
530
+ "etas": Etas,
531
+ "gs_usb": Gs_Usb,
532
+ "iscan": IsCan,
533
+ "ixxat": Ixxat,
534
+ "kvaser": Kvaser,
535
+ "neousys": NeouSys,
536
+ "neovi": Neovi,
537
+ "nican": NiCan,
538
+ "nixnet": NixNet,
539
+ "pcan": PCan,
540
+ "robotell": Robotell,
541
+ "seeedstudio": SeeedStudio,
542
+ "serial": Serial,
543
+ "slcan": SlCan,
544
+ "socketcan": SocketCan,
545
+ "socketcand": SocketCanD,
546
+ "systec": Systec,
547
+ "udp_multicast": Udp_Multicast,
548
+ "usb2can": Usb2Can,
549
+ "vector": Vector,
550
+ "virtual": Virtual,
551
+ "custom": CanCustom,
552
+ }
553
+
554
+
555
+ class Can(Configurable):
556
+ VALID_INTERFACES = set(can.interfaces.VALID_INTERFACES)
557
+ VALID_INTERFACES.add("custom")
558
+
559
+ interface = Enum(
560
+ values=VALID_INTERFACES, default_value=None, allow_none=True, help="CAN interface supported by python-can"
561
+ ).tag(config=True)
562
+ channel = Any(
563
+ default_value=None, allow_none=True, help="Channel identification. Expected type and value is backend dependent."
564
+ ).tag(config=True)
565
+ max_dlc_required = Bool(False, help="Master to slave frames always to have DLC = MAX_DLC = 8").tag(config=True)
566
+ # max_can_fd_dlc = Integer(64, help="").tag(config=True)
567
+ padding_value = Integer(0, help="Fill value, if max_dlc_required == True and DLC < MAX_DLC").tag(config=True)
568
+ use_default_listener = Bool(True, help="").tag(config=True)
569
+ can_id_master = Integer(allow_none=False, help="CAN-ID master -> slave (Bit31= 1: extended identifier)").tag(
570
+ config=True
571
+ ) # CMD and STIM packets
572
+ can_id_slave = Integer(allow_none=True, help="CAN-ID slave -> master (Bit31= 1: extended identifier)").tag(
573
+ config=True
574
+ ) # RES, ERR, EV, SERV and DAQ packets.
575
+ can_id_broadcast = Integer(
576
+ default_value=None, allow_none=True, help="Auto detection CAN-ID (Bit31= 1: extended identifier)"
577
+ ).tag(config=True)
578
+ daq_identifier = List(trait=Integer(), default_value=[], allow_none=True, help="One CAN identifier per DAQ-list.").tag(
579
+ config=True
580
+ )
581
+ bitrate = Integer(250000, help="CAN bitrate in bits/s (arbitration phase, if CAN FD).").tag(config=True)
582
+ receive_own_messages = Bool(False, help="Enable self-reception of sent messages.").tag(config=True)
583
+ poll_interval = Float(default_value=None, allow_none=True, help="Poll interval in seconds when reading messages.").tag(
584
+ config=True
585
+ )
586
+ fd = Bool(False, help="If CAN-FD frames should be supported.").tag(config=True)
587
+ data_bitrate = Integer(default_value=None, allow_none=True, help="Which bitrate to use for data phase in CAN FD.").tag(
588
+ config=True
589
+ )
590
+ sjw_abr = Integer(
591
+ default_value=None, allow_none=True, help="Bus timing value sample jump width (arbitration, SJW if CAN classic)."
592
+ ).tag(config=True)
593
+ tseg1_abr = Integer(
594
+ default_value=None, allow_none=True, help="Bus timing value tseg1 (arbitration, TSEG1 if CAN classic)."
595
+ ).tag(config=True)
596
+ tseg2_abr = Integer(
597
+ default_value=None, allow_none=True, help="Bus timing value tseg2 (arbitration, TSEG2, if CAN classic)"
598
+ ).tag(config=True)
599
+ sjw_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value sample jump width (data).").tag(config=True)
600
+ tseg1_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value tseg1 (data).").tag(config=True)
601
+ tseg2_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value tseg2 (data).").tag(config=True)
602
+ timing = Union(
603
+ trait_types=[Instance(klass=can.BitTiming), Instance(klass=can.BitTimingFd)],
604
+ default_value=None,
605
+ allow_none=True,
606
+ help="""Custom bit timing settings.
607
+ (.s https://github.com/hardbyte/python-can/blob/develop/can/bit_timing.py)
608
+ If this parameter is provided, it takes precedence over all other
609
+ timing-related parameters.
610
+ """,
611
+ ).tag(config=True)
612
+
613
+ classes = List(
614
+ [
615
+ CanAlystii,
616
+ CanCustom,
617
+ CanTact,
618
+ Etas,
619
+ Gs_Usb,
620
+ Neovi,
621
+ IsCan,
622
+ Ixxat,
623
+ Kvaser,
624
+ NeouSys,
625
+ NiCan,
626
+ NixNet,
627
+ PCan,
628
+ Robotell,
629
+ SeeedStudio,
630
+ Serial,
631
+ SlCan,
632
+ SocketCan,
633
+ SocketCanD,
634
+ Systec,
635
+ Udp_Multicast,
636
+ Usb2Can,
637
+ Vector,
638
+ Virtual,
639
+ ]
640
+ )
641
+
642
+ def __init__(self, **kws):
643
+ super().__init__(**kws)
644
+
645
+ if self.parent.layer == "CAN":
646
+ if self.interface is None or self.interface not in self.VALID_INTERFACES:
647
+ raise TraitError(
648
+ f"CAN interface must be one of {sorted(list(self.VALID_INTERFACES))} not the"
649
+ " {type(self.interface).__name__} {self.interface}."
650
+ )
651
+ self.canalystii = CanAlystii(config=self.config, parent=self)
652
+ self.cancustom = CanCustom(config=self.config, parent=self)
653
+ self.cantact = CanTact(config=self.config, parent=self)
654
+ self.etas = Etas(config=self.config, parent=self)
655
+ self.gs_usb = Gs_Usb(config=self.config, parent=self)
656
+ self.neovi = Neovi(config=self.config, parent=self)
657
+ self.iscan = IsCan(config=self.config, parent=self)
658
+ self.ixxat = Ixxat(config=self.config, parent=self)
659
+ self.kvaser = Kvaser(config=self.config, parent=self)
660
+ self.neousys = NeouSys(config=self.config, parent=self)
661
+ self.nican = NiCan(config=self.config, parent=self)
662
+ self.nixnet = NixNet(config=self.config, parent=self)
663
+ self.pcan = PCan(config=self.config, parent=self)
664
+ self.robotell = Robotell(config=self.config, parent=self)
665
+ self.seeedstudio = SeeedStudio(config=self.config, parent=self)
666
+ self.serial = Serial(config=self.config, parent=self)
667
+ self.slcan = SlCan(config=self.config, parent=self)
668
+ self.socketcan = SocketCan(config=self.config, parent=self)
669
+ self.socketcand = SocketCanD(config=self.config, parent=self)
670
+ self.systec = Systec(config=self.config, parent=self)
671
+ self.udp_multicast = Udp_Multicast(config=self.config, parent=self)
672
+ self.usb2can = Usb2Can(config=self.config, parent=self)
673
+ self.vector = Vector(config=self.config, parent=self)
674
+ self.virtual = Virtual(config=self.config, parent=self)
675
+
676
+
677
+ class Eth(Configurable):
678
+ """Ethernet."""
679
+
680
+ host = Unicode("localhost", help="Hostname or IP address of XCP slave.").tag(config=True)
681
+ port = Integer(5555, help="TCP/UDP port to connect.").tag(config=True)
682
+ protocol = Enum(values=["TCP", "UDP"], default_value="UDP", help="").tag(config=True)
683
+ ipv6 = Bool(False, help="Use IPv6 if `True` else IPv4.").tag(config=True)
684
+ tcp_nodelay = Bool(False, help="*** Expert option *** -- Disable Nagle's algorithm if `True`.").tag(config=True)
685
+ bind_to_address = Unicode(default_value=None, allow_none=True, help="Bind to specific local address.").tag(config=True)
686
+ bind_to_port = Integer(default_value=None, allow_none=True, help="Bind to specific local port.").tag(config=True)
687
+
688
+
689
+ class SxI(Configurable):
690
+ """SCI and SPI connections."""
691
+
692
+ port = Unicode("COM1", help="Name of communication interface.").tag(config=True)
693
+ bitrate = Integer(38400, help="Connection bitrate").tag(config=True)
694
+ bytesize = Enum(values=[5, 6, 7, 8], default_value=8, help="Size of byte.").tag(config=True)
695
+ parity = Enum(values=["N", "E", "O", "M", "S"], default_value="N", help="Paritybit calculation.").tag(config=True)
696
+ stopbits = Enum(values=[1, 1.5, 2], default_value=1, help="Number of stopbits.").tag(config=True)
697
+ mode = Enum(
698
+ values=[
699
+ "ASYNCH_FULL_DUPLEX_MODE",
700
+ "SYNCH_FULL_DUPLEX_MODE_BYTE",
701
+ "SYNCH_FULL_DUPLEX_MODE_WORD",
702
+ "SYNCH_FULL_DUPLEX_MODE_DWORD",
703
+ "SYNCH_MASTER_SLAVE_MODE_BYTE",
704
+ "SYNCH_MASTER_SLAVE_MODE_WORD",
705
+ "SYNCH_MASTER_SLAVE_MODE_DWORD",
706
+ ],
707
+ default_value="ASYNCH_FULL_DUPLEX_MODE",
708
+ help="Asynchronous (SCI) or synchronous (SPI) communication mode.",
709
+ ).tag(config=True)
710
+ header_format = Enum(
711
+ values=[
712
+ "HEADER_LEN_BYTE",
713
+ "HEADER_LEN_CTR_BYTE",
714
+ "HEADER_LEN_FILL_BYTE",
715
+ "HEADER_LEN_WORD",
716
+ "HEADER_LEN_CTR_WORD",
717
+ "HEADER_LEN_FILL_WORD",
718
+ ],
719
+ default_value="HEADER_LEN_CTR_WORD",
720
+ help="""XCPonSxI header format.
721
+ Number of bytes:
722
+
723
+ LEN CTR FILL
724
+ ______________________________________________________________
725
+ HEADER_LEN_BYTE | 1 X X
726
+ HEADER_LEN_CTR_BYTE | 1 1 X
727
+ HEADER_LEN_FILL_BYTE | 1 X 1
728
+ HEADER_LEN_WORD | 2 X X
729
+ HEADER_LEN_CTR_WORD | 2 2 X
730
+ HEADER_LEN_FILL_WORD | 2 X 2
731
+ """,
732
+ ).tag(config=True)
733
+ tail_format = Enum(
734
+ values=["NO_CHECKSUM", "CHECKSUM_BYTE", "CHECKSUM_WORD"], default_value="NO_CHECKSUM", help="XCPonSxI tail format."
735
+ ).tag(config=True)
736
+ framing = Bool(False, help="Enable SCI framing mechanism (ESC chars).").tag(config=True)
737
+ esc_sync = Integer(0x01, min=0, max=255, help="SCI framing protocol character SYNC.").tag(config=True)
738
+ esc_esc = Integer(0x00, min=0, max=255, help="SCI framing protocol character ESC.").tag(config=True)
739
+
740
+
741
+ class Usb(Configurable):
742
+ """Universal Serial Bus connections."""
743
+
744
+ serial_number = Unicode("", help="Device serial number.").tag(config=True)
745
+ configuration_number = Integer(1, help="USB configuration number.").tag(config=True)
746
+ interface_number = Integer(2, help="USB interface number.").tag(config=True)
747
+ vendor_id = Integer(0, help="USB vendor ID.").tag(config=True)
748
+ product_id = Integer(0, help="USB product ID.").tag(config=True)
749
+ library = Unicode("", help="Absolute path to USB shared library.").tag(config=True)
750
+ header_format = Enum(
751
+ values=[
752
+ "HEADER_LEN_BYTE",
753
+ "HEADER_LEN_CTR_BYTE",
754
+ "HEADER_LEN_FILL_BYTE",
755
+ "HEADER_LEN_WORD",
756
+ "HEADER_LEN_CTR_WORD",
757
+ "HEADER_LEN_FILL_WORD",
758
+ ],
759
+ default_value="HEADER_LEN_CTR_WORD",
760
+ help="",
761
+ ).tag(config=True)
762
+ in_ep_number = Integer(1, help="Ingoing USB reply endpoint number (IN-EP for RES/ERR, DAQ, and EV/SERV).").tag(config=True)
763
+ in_ep_transfer_type = Enum(
764
+ values=["BULK_TRANSFER", "INTERRUPT_TRANSFER"], default_value="BULK_TRANSFER", help="Ingoing: Supported USB transfer types."
765
+ ).tag(config=True)
766
+ in_ep_max_packet_size = Integer(512, help="Ingoing: Maximum packet size of endpoint in bytes.").tag(config=True)
767
+ in_ep_polling_interval = Integer(0, help="Ingoing: Polling interval of endpoint.").tag(config=True)
768
+ in_ep_message_packing = Enum(
769
+ values=["MESSAGE_PACKING_SINGLE", "MESSAGE_PACKING_MULTIPLE", "MESSAGE_PACKING_STREAMING"],
770
+ default_value="MESSAGE_PACKING_SINGLE",
771
+ help="Ingoing: Packing of XCP Messages.",
772
+ ).tag(config=True)
773
+ in_ep_alignment = Enum(
774
+ values=["ALIGNMENT_8_BIT", "ALIGNMENT_16_BIT", "ALIGNMENT_32_BIT", "ALIGNMENT_64_BIT"],
775
+ default_value="ALIGNMENT_8_BIT",
776
+ help="Ingoing: Alignment border.",
777
+ ).tag(config=True)
778
+ in_ep_recommended_host_bufsize = Integer(0, help="Ingoing: Recommended host buffer size.").tag(config=True)
779
+ out_ep_number = Integer(0, help="Outgoing USB command endpoint number (OUT-EP for CMD and STIM).").tag(config=True)
780
+ out_ep_transfer_type = Enum(
781
+ values=["BULK_TRANSFER", "INTERRUPT_TRANSFER"],
782
+ default_value="BULK_TRANSFER",
783
+ help="Outgoing: Supported USB transfer types.",
784
+ ).tag(config=True)
785
+ out_ep_max_packet_size = Integer(512, help="Outgoing: Maximum packet size of endpoint in bytes.").tag(config=True)
786
+ out_ep_polling_interval = Integer(0, help="Outgoing: Polling interval of endpoint.").tag(config=True)
787
+ out_ep_message_packing = Enum(
788
+ values=["MESSAGE_PACKING_SINGLE", "MESSAGE_PACKING_MULTIPLE", "MESSAGE_PACKING_STREAMING"],
789
+ default_value="MESSAGE_PACKING_SINGLE",
790
+ help="Outgoing: Packing of XCP Messages.",
791
+ ).tag(config=True)
792
+ out_ep_alignment = Enum(
793
+ values=["ALIGNMENT_8_BIT", "ALIGNMENT_16_BIT", "ALIGNMENT_32_BIT", "ALIGNMENT_64_BIT"],
794
+ default_value="ALIGNMENT_8_BIT",
795
+ help="Outgoing: Alignment border.",
796
+ ).tag(config=True)
797
+ out_ep_recommended_host_bufsize = Integer(0, help="Outgoing: Recommended host buffer size.").tag(config=True)
798
+
799
+
800
+ class Transport(Configurable):
801
+ """ """
802
+
803
+ classes = List([Can, Eth, SxI, Usb])
804
+
805
+ layer = Enum(
806
+ values=["CAN", "ETH", "SXI", "USB"],
807
+ default_value=None,
808
+ allow_none=True,
809
+ help="Choose one of the supported XCP transport layers.",
810
+ ).tag(config=True)
811
+ create_daq_timestamps = Bool(True, help="Record time of frame reception or set timestamp to 0.").tag(config=True)
812
+ timeout = Float(
813
+ 2.0,
814
+ help="""raise `XcpTimeoutError` after `timeout` seconds
815
+ if there is no response to a command.""",
816
+ ).tag(config=True)
817
+ alignment = Enum(values=[1, 2, 4, 8], default_value=1).tag(config=True)
818
+
819
+ can = Instance(Can).tag(config=True)
820
+ eth = Instance(Eth).tag(config=True)
821
+ sxi = Instance(SxI).tag(config=True)
822
+ usb = Instance(Usb).tag(config=True)
823
+
824
+ def __init__(self, **kws):
825
+ super().__init__(**kws)
826
+ self.can = Can(config=self.config, parent=self)
827
+ self.eth = Eth(config=self.config, parent=self)
828
+ self.sxi = SxI(config=self.config, parent=self)
829
+ self.usb = Usb(config=self.config, parent=self)
830
+
831
+
832
+ class General(Configurable):
833
+ """ """
834
+
835
+ disable_error_handling = Bool(False, help="Disable XCP error-handler for performance reasons.").tag(config=True)
836
+ disconnect_response_optional = Bool(False, help="Ignore missing response on DISCONNECT request.").tag(config=True)
837
+ connect_retries = Integer(help="Number of CONNECT retries (None for infinite retries).", allow_none=True, default_value=3).tag(
838
+ config=True
839
+ )
840
+ seed_n_key_dll = Unicode("", allow_none=False, help="Dynamic library used for slave resource unlocking.").tag(config=True)
841
+ seed_n_key_dll_same_bit_width = Bool(False, help="").tag(config=True)
842
+ custom_dll_loader = Unicode(allow_none=True, default_value=None, help="Use an custom seed and key DLL loader.").tag(config=True)
843
+ seed_n_key_function = Callable(
844
+ default_value=None,
845
+ allow_none=True,
846
+ help="""Python function used for slave resource unlocking.
847
+ Could be used if seed-and-key algorithm is known instead of `seed_n_key_dll`.""",
848
+ ).tag(config=True)
849
+ stim_support = Bool(False, help="").tag(config=True)
850
+
851
+
852
+ class ProfileCreate(Application):
853
+ description = "\nCreate a new profile"
854
+
855
+ dest_file = Unicode(default_value=None, allow_none=True, help="destination file name").tag(config=True)
856
+ aliases = Dict( # type:ignore[assignment]
857
+ dict(
858
+ d="ProfileCreate.dest_file",
859
+ o="ProfileCreate.dest_file",
860
+ )
861
+ )
862
+
863
+ def start(self):
864
+ pyxcp = self.parent.parent
865
+ if self.dest_file:
866
+ dest = Path(self.dest_file)
867
+ if dest.exists():
868
+ if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"):
869
+ print("Aborting...")
870
+ self.exit(1)
871
+ with dest.open("w", encoding="latin1") as out_file:
872
+ pyxcp.generate_config_file(out_file)
873
+ else:
874
+ pyxcp.generate_config_file(sys.stdout)
875
+
876
+
877
+ class ProfileConvert(Application):
878
+ description = "\nConvert legacy configuration file (.json/.toml) to new Python based format."
879
+
880
+ config_file = Unicode(help="Name of legacy config file (.json/.toml).", default_value=None, allow_none=False).tag(
881
+ config=True
882
+ ) # default_value="pyxcp_conf.py",
883
+
884
+ dest_file = Unicode(default_value=None, allow_none=True, help="destination file name").tag(config=True)
885
+
886
+ aliases = Dict( # type:ignore[assignment]
887
+ dict(
888
+ c="ProfileConvert.config_file",
889
+ d="ProfileConvert.dest_file",
890
+ o="ProfileConvert.dest_file",
891
+ )
892
+ )
893
+
894
+ def start(self):
895
+ pyxcp = self.parent.parent
896
+ pyxcp._read_configuration(self.config_file, emit_warning=False)
897
+ if self.dest_file:
898
+ dest = Path(self.dest_file)
899
+ if dest.exists():
900
+ if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"):
901
+ print("Aborting...")
902
+ self.exit(1)
903
+ with dest.open("w", encoding="latin1") as out_file:
904
+ pyxcp.generate_config_file(out_file)
905
+ else:
906
+ pyxcp.generate_config_file(sys.stdout)
907
+
908
+
909
+ class ProfileApp(Application):
910
+ subcommands = Dict(
911
+ dict(
912
+ create=(ProfileCreate, ProfileCreate.description.splitlines()[0]),
913
+ convert=(ProfileConvert, ProfileConvert.description.splitlines()[0]),
914
+ )
915
+ )
916
+
917
+ def start(self):
918
+ if self.subapp is None:
919
+ print(f"No subcommand specified. Must specify one of: {self.subcommands.keys()}")
920
+ print()
921
+ self.print_description()
922
+ self.print_subcommands()
923
+ self.exit(1)
924
+ else:
925
+ self.subapp.start()
926
+
927
+
928
+ class PyXCP(Application):
929
+ description = "pyXCP application"
930
+ config_file = Unicode(default_value="pyxcp_conf.py", help="base name of config file").tag(config=True)
931
+
932
+ classes = List([General, Transport])
933
+
934
+ subcommands = dict(
935
+ profile=(
936
+ ProfileApp,
937
+ """
938
+ Profile stuff
939
+ """.strip(),
940
+ )
941
+ )
942
+
943
+ def start(self):
944
+ if self.subapp:
945
+ self.subapp.start()
946
+ exit(2)
947
+ else:
948
+ has_handlers = logging.getLogger().hasHandlers()
949
+ if has_handlers:
950
+ self.log = logging.getLogger()
951
+ self._read_configuration(self.config_file)
952
+ else:
953
+ self._read_configuration(self.config_file)
954
+ self._setup_logger()
955
+ self.log.debug(f"pyxcp version: {self.version}")
956
+
957
+ def _setup_logger(self):
958
+ from pyxcp.types import Command
959
+
960
+ # Remove any handlers installed by `traitlets`.
961
+ for hdl in self.log.handlers:
962
+ self.log.removeHandler(hdl)
963
+
964
+ # formatter = logging.Formatter(fmt=self.log_format, datefmt=self.log_datefmt)
965
+
966
+ keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff.
967
+ rich_handler = RichHandler(
968
+ rich_tracebacks=True,
969
+ tracebacks_show_locals=True,
970
+ log_time_format=self.log_datefmt,
971
+ level=self.log_level,
972
+ keywords=keywords,
973
+ )
974
+ # rich_handler.setFormatter(formatter)
975
+ self.log.addHandler(rich_handler)
976
+
977
+ def initialize(self, argv=None):
978
+ from pyxcp import __version__ as pyxcp_version
979
+
980
+ PyXCP.version = pyxcp_version
981
+ PyXCP.name = Path(sys.argv[0]).name
982
+ self.parse_command_line(argv[1:])
983
+
984
+ def _read_configuration(self, file_name: str, emit_warning: bool = True) -> None:
985
+ self.read_configuration_file(file_name, emit_warning)
986
+ self.general = General(config=self.config, parent=self)
987
+ self.transport = Transport(parent=self)
988
+
989
+ def read_configuration_file(self, file_name: str, emit_warning: bool = True):
990
+ self.legacy_config: bool = False
991
+
992
+ pth = Path(file_name)
993
+ if not pth.exists():
994
+ raise FileNotFoundError(f"Configuration file {file_name!r} does not exist.")
995
+ suffix = pth.suffix.lower()
996
+ if suffix == ".py":
997
+ self.load_config_file(pth)
998
+ else:
999
+ self.legacy_config = True
1000
+ if suffix == ".json":
1001
+ reader = json
1002
+ elif suffix == ".toml":
1003
+ reader = toml
1004
+ else:
1005
+ raise ValueError(f"Unknown file type for config: {suffix}")
1006
+ with pth.open("r") as f:
1007
+ if emit_warning:
1008
+ self.log.warning(f"Legacy configuration file format ({suffix}), please use Python based configuration.")
1009
+ cfg = reader.loads(f.read())
1010
+ if cfg:
1011
+ cfg = legacy.convert_config(cfg, self.log)
1012
+ self.config = cfg
1013
+ return cfg
1014
+
1015
+ flags = Dict( # type:ignore[assignment]
1016
+ dict(
1017
+ debug=({"PyXCP": {"log_level": 10}}, "Set loglevel to DEBUG"),
1018
+ )
1019
+ )
1020
+
1021
+ @default("log_level")
1022
+ def _default_value(self):
1023
+ return logging.INFO # traitlets default is logging.WARN
1024
+
1025
+ aliases = Dict( # type:ignore[assignment]
1026
+ dict(
1027
+ c="PyXCP.config_file", # Application
1028
+ log_level="PyXCP.log_level",
1029
+ l="PyXCP.log_level",
1030
+ )
1031
+ )
1032
+
1033
+ def _iterate_config_class(self, klass, class_names: typing.List[str], config, out_file: io.IOBase = sys.stdout) -> None:
1034
+ sub_classes = []
1035
+ class_path = ".".join(class_names)
1036
+ print(
1037
+ f"""\n# ------------------------------------------------------------------------------
1038
+ # {class_path} configuration
1039
+ # ------------------------------------------------------------------------------""",
1040
+ end="\n\n",
1041
+ file=out_file,
1042
+ )
1043
+ if hasattr(klass, "classes"):
1044
+ kkk = klass.classes
1045
+ if hasattr(kkk, "default"):
1046
+ if class_names[-1] not in ("PyXCP"):
1047
+ sub_classes.extend(kkk.default())
1048
+ for name, tr in klass.class_own_traits().items():
1049
+ md = tr.metadata
1050
+ if md.get("config"):
1051
+ help = md.get("help", "").lstrip()
1052
+ commented_lines = "\n".join([f"# {line}" for line in help.split("\n")])
1053
+ print(f"#{commented_lines}", file=out_file)
1054
+ value = tr.default()
1055
+ if isinstance(tr, Instance) and tr.__class__.__name__ not in ("Dict", "List"):
1056
+ continue
1057
+ if isinstance(tr, Enum):
1058
+ print(f"# Choices: {tr.info()}", file=out_file)
1059
+ else:
1060
+ print(f"# Type: {tr.info()}", file=out_file)
1061
+ print(f"# Default: {value!r}", file=out_file)
1062
+ if name in config:
1063
+ cfg_value = config[name]
1064
+ print(f"c.{class_path!s}.{name!s} = {cfg_value!r}", end="\n\n", file=out_file)
1065
+ else:
1066
+ print(f"# c.{class_path!s}.{name!s} = {value!r}", end="\n\n", file=out_file)
1067
+ if class_names is None:
1068
+ class_names = []
1069
+ for sub_klass in sub_classes:
1070
+ self._iterate_config_class(
1071
+ sub_klass, class_names + [sub_klass.__name__], config=config.get(sub_klass.__name__, {}), out_file=out_file
1072
+ )
1073
+
1074
+ def generate_config_file(self, file_like: io.IOBase, config=None) -> None:
1075
+ print("#", file=file_like)
1076
+ print("# Configuration file for pyXCP.", file=file_like)
1077
+ print("#", file=file_like)
1078
+ print("c = get_config() # noqa", end="\n\n", file=file_like)
1079
+
1080
+ for klass in self._classes_with_config_traits():
1081
+ self._iterate_config_class(
1082
+ klass, [klass.__name__], config=self.config.get(klass.__name__, {}) if config is None else {}, out_file=file_like
1083
+ )
1084
+
1085
+
1086
+ application: typing.Optional[PyXCP] = None
1087
+
1088
+
1089
+ def create_application(options: typing.Optional[typing.List[typing.Any]] = None) -> PyXCP:
1090
+ global application
1091
+ if options is None:
1092
+ options = []
1093
+ if application is not None:
1094
+ return application
1095
+ application = PyXCP()
1096
+ application.initialize(sys.argv)
1097
+ application.start()
1098
+ return application
1099
+
1100
+
1101
+ def get_application(options: typing.Optional[typing.List[typing.Any]] = None) -> PyXCP:
1102
+ if options is None:
1103
+ options = []
1104
+ global application
1105
+ if application is None:
1106
+ application = create_application(options)
1107
+ return application
1108
+
1109
+
1110
+ def reset_application() -> None:
1111
+ global application
1112
+ del application
1113
+ application = None