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