odxtools 5.3.1__py3-none-any.whl → 6.0.1__py3-none-any.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.
- odxtools/__init__.py +1 -1
- odxtools/basicstructure.py +76 -91
- odxtools/cli/_parser_utils.py +12 -9
- odxtools/cli/_print_utils.py +7 -7
- odxtools/cli/browse.py +94 -73
- odxtools/cli/find.py +42 -59
- odxtools/cli/list.py +21 -17
- odxtools/cli/snoop.py +19 -18
- odxtools/communicationparameterref.py +6 -3
- odxtools/companydocinfo.py +2 -2
- odxtools/companyrevisioninfo.py +1 -1
- odxtools/comparamsubset.py +6 -6
- odxtools/complexcomparam.py +1 -1
- odxtools/compumethods/compumethod.py +6 -9
- odxtools/compumethods/createanycompumethod.py +11 -9
- odxtools/compumethods/identicalcompumethod.py +5 -4
- odxtools/compumethods/limit.py +9 -9
- odxtools/compumethods/linearcompumethod.py +25 -17
- odxtools/compumethods/scalelinearcompumethod.py +6 -5
- odxtools/compumethods/tabintpcompumethod.py +30 -9
- odxtools/compumethods/texttablecompumethod.py +22 -24
- odxtools/database.py +5 -5
- odxtools/dataobjectproperty.py +10 -23
- odxtools/decodestate.py +1 -1
- odxtools/determinenumberofitems.py +37 -8
- odxtools/diagcodedtype.py +14 -9
- odxtools/diagdatadictionaryspec.py +60 -37
- odxtools/diaglayer.py +30 -21
- odxtools/diaglayercontainer.py +40 -40
- odxtools/diaglayerraw.py +92 -63
- odxtools/diagnostictroublecode.py +2 -2
- odxtools/diagservice.py +53 -35
- odxtools/docrevision.py +1 -1
- odxtools/dopbase.py +14 -3
- odxtools/dtcdop.py +15 -9
- odxtools/dynamiclengthfield.py +6 -4
- odxtools/endofpdufield.py +22 -23
- odxtools/environmentdata.py +2 -5
- odxtools/environmentdatadescription.py +6 -4
- odxtools/field.py +3 -8
- odxtools/isotp_state_machine.py +52 -38
- odxtools/leadinglengthinfotype.py +9 -7
- odxtools/load_file.py +2 -1
- odxtools/load_odx_d_file.py +2 -5
- odxtools/load_pdx_file.py +2 -6
- odxtools/message.py +11 -3
- odxtools/minmaxlengthtype.py +107 -78
- odxtools/modification.py +2 -2
- odxtools/multiplexer.py +23 -21
- odxtools/multiplexerswitchkey.py +37 -8
- odxtools/nameditemlist.py +59 -58
- odxtools/odxlink.py +4 -2
- odxtools/odxtypes.py +4 -3
- odxtools/parameterinfo.py +6 -6
- odxtools/parameters/codedconstparameter.py +15 -25
- odxtools/parameters/createanyparameter.py +1 -1
- odxtools/parameters/dynamicparameter.py +6 -5
- odxtools/parameters/lengthkeyparameter.py +2 -1
- odxtools/parameters/matchingrequestparameter.py +8 -11
- odxtools/parameters/nrcconstparameter.py +11 -21
- odxtools/parameters/parameter.py +4 -18
- odxtools/parameters/parameterwithdop.py +14 -29
- odxtools/parameters/physicalconstantparameter.py +7 -9
- odxtools/parameters/reservedparameter.py +17 -38
- odxtools/parameters/systemparameter.py +6 -5
- odxtools/parameters/tableentryparameter.py +6 -5
- odxtools/parameters/tablekeyparameter.py +8 -15
- odxtools/parameters/tablestructparameter.py +11 -12
- odxtools/parameters/valueparameter.py +9 -24
- odxtools/paramlengthinfotype.py +11 -9
- odxtools/physicaldimension.py +1 -1
- odxtools/physicaltype.py +2 -2
- odxtools/response.py +7 -3
- odxtools/singleecujob.py +48 -22
- odxtools/standardlengthtype.py +11 -6
- odxtools/uds.py +1 -1
- odxtools/unit.py +5 -5
- odxtools/unitgroup.py +1 -1
- odxtools/unitspec.py +2 -2
- odxtools/version.py +13 -3
- odxtools/write_pdx_file.py +7 -4
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
- odxtools/positioneddataobjectproperty.py +0 -74
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/top_level.txt +0 -0
odxtools/cli/browse.py
CHANGED
@@ -2,16 +2,20 @@
|
|
2
2
|
import argparse
|
3
3
|
import logging
|
4
4
|
import sys
|
5
|
-
from typing import
|
5
|
+
from typing import List, Optional, Union
|
6
6
|
|
7
7
|
import PyInquirer.prompt as PI_prompt
|
8
8
|
|
9
9
|
from ..database import Database
|
10
|
+
from ..dataobjectproperty import DataObjectProperty
|
10
11
|
from ..diaglayer import DiagLayer
|
11
12
|
from ..diagservice import DiagService
|
12
|
-
from ..
|
13
|
+
from ..exceptions import odxraise, odxrequire
|
14
|
+
from ..odxtypes import AtomicOdxType, DataType, ParameterValueDict
|
15
|
+
from ..parameters.matchingrequestparameter import MatchingRequestParameter
|
13
16
|
from ..parameters.parameter import Parameter
|
14
17
|
from ..parameters.parameterwithdop import ParameterWithDOP
|
18
|
+
from ..parameters.valueparameter import ValueParameter
|
15
19
|
from ..request import Request
|
16
20
|
from ..response import Response
|
17
21
|
from . import _parser_utils
|
@@ -20,7 +24,7 @@ from . import _parser_utils
|
|
20
24
|
_odxtools_tool_name_ = "browse"
|
21
25
|
|
22
26
|
|
23
|
-
def _convert_string_to_odx_type(string_value: str, odx_type: DataType):
|
27
|
+
def _convert_string_to_odx_type(string_value: str, odx_type: DataType) -> AtomicOdxType:
|
24
28
|
"""Similar to odx_type.from_string(string_value) but more relaxed to parse user input"""
|
25
29
|
if odx_type == DataType.A_UINT32:
|
26
30
|
return int(string_value, 0)
|
@@ -30,57 +34,66 @@ def _convert_string_to_odx_type(string_value: str, odx_type: DataType):
|
|
30
34
|
return odx_type.from_string(string_value)
|
31
35
|
|
32
36
|
|
33
|
-
def _convert_string_to_bytes(string_value):
|
37
|
+
def _convert_string_to_bytes(string_value: str) -> bytes:
|
34
38
|
if all(len(x) <= 2 for x in string_value.split(" ")):
|
35
39
|
return bytes(int(x, 16) for x in string_value.split(" ") if len(x) > 0)
|
36
40
|
else:
|
37
41
|
return int(string_value, 16).to_bytes((int(string_value, 16).bit_length() + 7) // 8, "big")
|
38
42
|
|
39
43
|
|
40
|
-
def _validate_string_value(input, parameter):
|
41
|
-
if parameter.
|
44
|
+
def _validate_string_value(input: str, parameter: Parameter) -> bool:
|
45
|
+
if not parameter.is_required and input == "":
|
42
46
|
return True
|
43
47
|
elif isinstance(parameter, ParameterWithDOP):
|
44
48
|
try:
|
45
|
-
|
46
|
-
|
49
|
+
phys_type = odxrequire(parameter.physical_type)
|
50
|
+
val = _convert_string_to_odx_type(input, phys_type.base_data_type)
|
51
|
+
except: # noqa: E722
|
47
52
|
return False
|
48
|
-
|
53
|
+
dop = parameter.dop
|
54
|
+
if isinstance(dop, DataObjectProperty):
|
55
|
+
return dop.is_valid_physical_value(val)
|
56
|
+
else:
|
57
|
+
raise NotImplementedError("Validation of complex DOPs")
|
49
58
|
else:
|
50
59
|
logging.info("This value is not validated precisely: Parameter {parameter}")
|
51
60
|
return input != ""
|
52
61
|
|
53
62
|
|
54
|
-
def prompt_single_parameter_value(parameter):
|
55
|
-
if
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
63
|
+
def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxType]:
|
64
|
+
if not isinstance(parameter, ValueParameter):
|
65
|
+
odxraise("Only the value of ValueParameters can be queried")
|
66
|
+
if parameter.physical_type is None:
|
67
|
+
odxraise("Only ValueParameters which define a physical data type can be queried")
|
68
|
+
|
69
|
+
# TODO: add valid choices for the parameter
|
70
|
+
# "choices": parameter.get_valid_physical_values(),
|
71
|
+
param_prompt = [{
|
72
|
+
"type":
|
73
|
+
"input",
|
74
|
+
"name":
|
75
|
+
parameter.short_name,
|
76
|
+
"message":
|
77
|
+
f"Value for parameter '{parameter.short_name}' (Type: {parameter.physical_type.base_data_type})"
|
78
|
+
+ (f"[optional]" if not parameter.is_required else ""),
|
79
|
+
# TODO: improve validation
|
80
|
+
"validate":
|
81
|
+
lambda x: _validate_string_value(x, parameter),
|
82
|
+
# TODO: do type conversion?
|
83
|
+
"filter":
|
84
|
+
lambda x: x
|
85
|
+
# x if x == "" or p.physical_type.base_data_type is None
|
86
|
+
# else _convert_string_to_odx_type(x, p.physical_type.base_data_type, param=p) # This does not work because the next parameter to be promted is used (for some reason?)
|
87
|
+
}]
|
88
|
+
|
89
|
+
if hasattr(parameter, "dop") and hasattr(parameter.dop, "compu_method") \
|
90
|
+
and hasattr(parameter.dop.compu_method, "get_scales"):
|
91
|
+
scales = parameter.dop.compu_method.get_scales()
|
92
|
+
choices = [scale.compu_const for scale in scales if scale is not None]
|
93
|
+
param_prompt[0]["choices"] = choices
|
94
|
+
|
82
95
|
answer = PI_prompt.prompt(param_prompt)
|
83
|
-
if answer.get(parameter.short_name) == "" and parameter.
|
96
|
+
if answer.get(parameter.short_name) == "" and not parameter.is_required:
|
84
97
|
return None
|
85
98
|
elif parameter.physical_type.base_data_type is not None:
|
86
99
|
return _convert_string_to_odx_type(
|
@@ -92,21 +105,22 @@ def prompt_single_parameter_value(parameter):
|
|
92
105
|
return answer.get(parameter.short_name)
|
93
106
|
|
94
107
|
|
95
|
-
def encode_message_interactively(sub_service,
|
108
|
+
def encode_message_interactively(sub_service: Union[Request, Response],
|
109
|
+
ask_user_confirmation: bool = False) -> None:
|
96
110
|
if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
|
97
111
|
raise SystemError("This command can only be used in an interactive shell!")
|
98
112
|
param_dict = sub_service.parameter_dict()
|
99
113
|
|
100
114
|
exists_definable_param = False
|
101
|
-
for
|
115
|
+
for param_or_dict in param_dict.values():
|
102
116
|
if isinstance(param_or_dict, dict):
|
103
|
-
for
|
104
|
-
if param.is_settable:
|
117
|
+
for param in param_or_dict.values():
|
118
|
+
if not isinstance(param_or_dict, dict) and param.is_settable:
|
105
119
|
exists_definable_param = True
|
106
120
|
elif param_or_dict.is_settable:
|
107
121
|
exists_definable_param = True
|
108
122
|
|
109
|
-
param_values = {}
|
123
|
+
param_values: ParameterValueDict = {}
|
110
124
|
if exists_definable_param > 0:
|
111
125
|
# Ask whether user wants to encode a message
|
112
126
|
if ask_user_confirmation:
|
@@ -142,9 +156,9 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
|
|
142
156
|
print(
|
143
157
|
f"The next {len(param_or_structure)} parameters belong to the structure '{key}'"
|
144
158
|
)
|
145
|
-
structure_param_values = {}
|
159
|
+
structure_param_values: ParameterValueDict = {}
|
146
160
|
for param_sn, param in param_or_structure.items():
|
147
|
-
if param.is_settable:
|
161
|
+
if not isinstance(param, dict) and param.is_settable:
|
148
162
|
val = prompt_single_parameter_value(param)
|
149
163
|
if val is not None:
|
150
164
|
structure_param_values[param_sn] = val
|
@@ -157,7 +171,7 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
|
|
157
171
|
if isinstance(sub_service, Response):
|
158
172
|
payload = sub_service.encode(coded_request=answered_request, **param_values)
|
159
173
|
else:
|
160
|
-
payload = sub_service.encode(**param_values)
|
174
|
+
payload = sub_service.encode(coded_request=b'', **param_values)
|
161
175
|
else:
|
162
176
|
# There are no optional parameters that need to be defined by the user -> Just print message
|
163
177
|
payload = sub_service.encode()
|
@@ -166,26 +180,28 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
|
|
166
180
|
|
167
181
|
def encode_message_from_string_values(
|
168
182
|
sub_service: Union[Request, Response],
|
169
|
-
parameter_values:
|
170
|
-
):
|
183
|
+
parameter_values: Optional[ParameterValueDict] = None,
|
184
|
+
) -> None:
|
185
|
+
if parameter_values is None:
|
186
|
+
parameter_values = {}
|
171
187
|
parameter_values = parameter_values.copy()
|
172
188
|
param_dict = sub_service.parameter_dict()
|
173
189
|
|
174
190
|
# Check if all needed parameters are given
|
175
191
|
missing_parameter_names = []
|
176
|
-
for
|
177
|
-
if isinstance(
|
178
|
-
#
|
179
|
-
for simple_param_sn, simple_param in
|
180
|
-
structured_value = parameter_values.get(
|
192
|
+
for param_sn, param in param_dict.items():
|
193
|
+
if isinstance(param, dict):
|
194
|
+
# param_value refers to a structure (represented as dict of params)
|
195
|
+
for simple_param_sn, simple_param in param.items():
|
196
|
+
structured_value = parameter_values.get(param_sn)
|
181
197
|
if not isinstance(simple_param, Parameter):
|
182
198
|
continue
|
183
199
|
if simple_param.is_required and (not isinstance(structured_value, dict) or
|
184
200
|
structured_value.get(simple_param_sn) is None):
|
185
|
-
missing_parameter_names.append(f"{
|
201
|
+
missing_parameter_names.append(f"{param_sn} :: {simple_param_sn}")
|
186
202
|
else:
|
187
|
-
if
|
188
|
-
missing_parameter_names.append(
|
203
|
+
if param.is_required and parameter_values.get(param_sn) is None:
|
204
|
+
missing_parameter_names.append(param_sn)
|
189
205
|
|
190
206
|
if len(missing_parameter_names) > 0:
|
191
207
|
print("The following parameters are required but missing!")
|
@@ -194,31 +210,36 @@ def encode_message_from_string_values(
|
|
194
210
|
|
195
211
|
# Request values for parameters
|
196
212
|
for parameter_sn, parameter_value in parameter_values.items():
|
213
|
+
parameter = param_dict.get(parameter_sn)
|
214
|
+
if parameter is None:
|
215
|
+
print(f"I don't know the parameter {parameter_sn}")
|
216
|
+
continue
|
217
|
+
|
197
218
|
if isinstance(parameter_value, dict):
|
198
219
|
# parameter_value refers to a structure (represented as dict of params)
|
199
220
|
typed_dict = parameter_value.copy()
|
200
|
-
for simple_param_sn,
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
221
|
+
for simple_param_sn, simple_param_val in parameter_value.items():
|
222
|
+
simple_parameter = param_dict.get(simple_param_sn)
|
223
|
+
if simple_parameter is None:
|
224
|
+
print(f"Unknown sub-parameter {simple_param_sn}")
|
225
|
+
continue
|
226
|
+
if not isinstance(simple_param_val, str):
|
227
|
+
print(f"The value specified for parameter {simple_param_sn} is not a string")
|
205
228
|
continue
|
206
229
|
|
207
230
|
typed_dict[simple_param_sn] = _convert_string_to_odx_type(
|
208
|
-
|
209
|
-
|
231
|
+
simple_param_val,
|
232
|
+
simple_parameter.physical_type.base_data_type # type: ignore[union-attr]
|
210
233
|
)
|
211
234
|
parameter_values[parameter_sn] = typed_dict
|
212
235
|
else:
|
213
|
-
try:
|
214
|
-
parameter = param_dict[parameter_sn]
|
215
|
-
except:
|
216
|
-
print(f"I don't know the parameter {parameter_sn}")
|
217
|
-
continue
|
218
|
-
|
219
236
|
assert isinstance(parameter, Parameter)
|
220
237
|
|
221
|
-
if
|
238
|
+
if not isinstance(parameter_value, str):
|
239
|
+
print(f"Value for parameter {parameter_sn} is not a string")
|
240
|
+
continue
|
241
|
+
|
242
|
+
if not isinstance(parameter, MatchingRequestParameter):
|
222
243
|
parameter_values[parameter_sn] = _convert_string_to_odx_type(
|
223
244
|
parameter_value,
|
224
245
|
parameter.physical_type.base_data_type, # type: ignore[attr-defined]
|
@@ -231,7 +252,7 @@ def encode_message_from_string_values(
|
|
231
252
|
print(f"Message payload: 0x{bytes(payload).hex()}")
|
232
253
|
|
233
254
|
|
234
|
-
def browse(odxdb: Database):
|
255
|
+
def browse(odxdb: Database) -> None:
|
235
256
|
if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
|
236
257
|
raise SystemError("This command can only be used in an interactive shell!")
|
237
258
|
dl_names = [dl.short_name for dl in odxdb.diag_layers]
|
@@ -323,7 +344,7 @@ def browse(odxdb: Database):
|
|
323
344
|
encode_message_interactively(sub_service, ask_user_confirmation=True)
|
324
345
|
|
325
346
|
|
326
|
-
def add_subparser(subparsers):
|
347
|
+
def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
327
348
|
# Browse interactively to avoid spamming the console.
|
328
349
|
parser = subparsers.add_parser(
|
329
350
|
"browse",
|
@@ -335,6 +356,6 @@ def add_subparser(subparsers):
|
|
335
356
|
_parser_utils.add_pdx_argument(parser)
|
336
357
|
|
337
358
|
|
338
|
-
def run(args):
|
359
|
+
def run(args: argparse.Namespace) -> None:
|
339
360
|
odxdb = _parser_utils.load_file(args)
|
340
361
|
browse(odxdb)
|
odxtools/cli/find.py
CHANGED
@@ -1,46 +1,36 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import argparse
|
3
|
-
from typing import Dict, List
|
3
|
+
from typing import Dict, List, Optional
|
4
4
|
|
5
5
|
from ..database import Database
|
6
6
|
from ..diagservice import DiagService
|
7
|
+
from ..exceptions import odxraise
|
8
|
+
from ..odxtypes import ParameterValue
|
7
9
|
from ..singleecujob import SingleEcuJob
|
8
10
|
from . import _parser_utils
|
9
|
-
from ._print_utils import print_diagnostic_service
|
10
11
|
|
11
12
|
# name of the tool
|
12
13
|
_odxtools_tool_name_ = "find"
|
13
14
|
|
14
15
|
|
15
|
-
def get_display_value(v
|
16
|
-
import binascii
|
17
|
-
|
16
|
+
def get_display_value(v: ParameterValue) -> str:
|
18
17
|
if isinstance(v, bytes):
|
19
|
-
return
|
18
|
+
return v.hex(" ")
|
20
19
|
elif isinstance(v, int):
|
21
|
-
return f"{v} ({
|
20
|
+
return f"{v} (0x{v:x})"
|
22
21
|
else:
|
23
|
-
return v
|
24
|
-
|
25
|
-
|
26
|
-
def print_decoded_message(service: DiagService, message: bytes):
|
27
|
-
decoded = service.decode_message(message)
|
28
|
-
print(f"\nDecoded {decoded.structure}:")
|
29
|
-
for k, v in decoded.param_dict.items():
|
30
|
-
param = decoded.structure.parameter_dict()[k]
|
31
|
-
print(f"\t{k}: {get_display_value(v, param)}")
|
32
|
-
pass
|
22
|
+
return str(v)
|
33
23
|
|
34
24
|
|
35
25
|
def print_summary(
|
36
26
|
odxdb: Database,
|
37
|
-
ecu_variants=None,
|
38
|
-
data=
|
39
|
-
service_names=None,
|
40
|
-
decode=False,
|
41
|
-
print_params=False,
|
42
|
-
allow_unknown_bit_lengths=False,
|
43
|
-
):
|
27
|
+
ecu_variants: Optional[List[str]] = None,
|
28
|
+
data: bytes = b'',
|
29
|
+
service_names: Optional[List[str]] = None,
|
30
|
+
decode: bool = False,
|
31
|
+
print_params: bool = False,
|
32
|
+
allow_unknown_bit_lengths: bool = False,
|
33
|
+
) -> None:
|
44
34
|
ecu_names = ecu_variants if ecu_variants else [ecu.short_name for ecu in odxdb.ecus]
|
45
35
|
services: Dict[DiagService, List[str]] = {}
|
46
36
|
for ecu_name in ecu_names:
|
@@ -63,31 +53,31 @@ def print_summary(
|
|
63
53
|
ecu_names.append(ecu_name)
|
64
54
|
services[service] = ecu_names
|
65
55
|
|
56
|
+
print(f"Binary data: {data.hex(' ')}")
|
66
57
|
for service, ecu_names in services.items():
|
67
|
-
display_names = ", ".join(ecu_names)
|
68
|
-
filler = str.ljust("", len(display_names), "=")
|
69
|
-
print(f"\n{filler}")
|
70
|
-
print(f"{', '.join(ecu_names)}")
|
71
|
-
print(f"{filler}\n\n")
|
72
58
|
if isinstance(service, DiagService):
|
73
|
-
|
74
|
-
service,
|
75
|
-
print_params=print_params,
|
76
|
-
allow_unknown_bit_lengths=allow_unknown_bit_lengths,
|
77
|
-
print_pre_condition_states=True,
|
78
|
-
print_state_transitions=True,
|
79
|
-
print_audiences=True,
|
59
|
+
print(
|
60
|
+
f"Decoded by service '{service.short_name}' (decoding ECUs: {', '.join(ecu_names)})"
|
80
61
|
)
|
81
62
|
elif isinstance(service, SingleEcuJob):
|
82
|
-
print(
|
63
|
+
print(
|
64
|
+
f"Decoded by single ecu job '{service.short_name}' (decoding ECUs: {', '.join(ecu_names)})"
|
65
|
+
)
|
83
66
|
else:
|
84
|
-
print(f"
|
67
|
+
print(f"Decoded by unknown diagnostic communication: '{service.short_name}' "
|
68
|
+
f"(decoding ECUs: {', '.join(ecu_names)})")
|
85
69
|
|
86
70
|
if decode:
|
87
|
-
|
71
|
+
if data is None:
|
72
|
+
odxraise("Data is required for decoding")
|
88
73
|
|
74
|
+
decoded = service.decode_message(data)
|
75
|
+
print(f"Decoded data:")
|
76
|
+
for param_name, param_value in decoded.param_dict.items():
|
77
|
+
print(f" {param_name}={get_display_value(param_value)}")
|
89
78
|
|
90
|
-
|
79
|
+
|
80
|
+
def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
91
81
|
parser = subparsers.add_parser(
|
92
82
|
"find",
|
93
83
|
description="\n".join([
|
@@ -111,7 +101,7 @@ def add_subparser(subparsers):
|
|
111
101
|
parser.add_argument(
|
112
102
|
"-v",
|
113
103
|
"--variants",
|
114
|
-
nargs=
|
104
|
+
nargs=1,
|
115
105
|
metavar="VARIANT",
|
116
106
|
required=False,
|
117
107
|
help="Specifies which ecu variants should be included.",
|
@@ -121,21 +111,20 @@ def add_subparser(subparsers):
|
|
121
111
|
parser.add_argument(
|
122
112
|
"-d",
|
123
113
|
"--data",
|
124
|
-
nargs=
|
114
|
+
nargs=1,
|
125
115
|
default=None,
|
126
116
|
metavar="DATA",
|
127
|
-
required=
|
117
|
+
required=True,
|
128
118
|
help="Print a list of diagnostic services associated with the hex request.",
|
129
119
|
)
|
130
120
|
|
131
121
|
parser.add_argument(
|
132
122
|
"-D",
|
133
123
|
"--decode",
|
134
|
-
|
135
|
-
|
136
|
-
metavar="DECODE",
|
124
|
+
default=False,
|
125
|
+
action="store_true",
|
137
126
|
required=False,
|
138
|
-
help="
|
127
|
+
help="Decode the specified hex request.",
|
139
128
|
)
|
140
129
|
|
141
130
|
parser.add_argument(
|
@@ -165,21 +154,15 @@ def add_subparser(subparsers):
|
|
165
154
|
)
|
166
155
|
|
167
156
|
|
168
|
-
def hex_to_binary(
|
169
|
-
|
157
|
+
def hex_to_binary(data_str: str) -> bytes:
|
158
|
+
return bytes.fromhex(data_str)
|
170
159
|
|
171
|
-
return binascii.unhexlify("".join(data).replace(" ", ""))
|
172
160
|
|
173
|
-
|
174
|
-
def run(args):
|
161
|
+
def run(args: argparse.Namespace) -> None:
|
175
162
|
odxdb = _parser_utils.load_file(args)
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
data = (
|
180
|
-
hex_to_binary(args.data)
|
181
|
-
if args.data else hex_to_binary(args.decode) if args.decode else None)
|
182
|
-
decode = True if args.decode else False
|
163
|
+
variants = args.variants
|
164
|
+
data = bytes.fromhex(args.data[0])
|
165
|
+
decode = args.decode
|
183
166
|
|
184
167
|
print_summary(
|
185
168
|
odxdb,
|
odxtools/cli/list.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import argparse
|
3
|
-
from typing import List, Union
|
3
|
+
from typing import Callable, List, Optional, Union
|
4
4
|
|
5
5
|
from ..database import Database
|
6
6
|
from ..diaglayer import DiagLayer
|
@@ -15,18 +15,18 @@ _odxtools_tool_name_ = "list"
|
|
15
15
|
|
16
16
|
def print_summary(
|
17
17
|
odxdb: Database,
|
18
|
-
print_global_negative_responses=False,
|
19
|
-
print_services=False,
|
20
|
-
print_dops=False,
|
21
|
-
print_params=False,
|
22
|
-
print_com_params=False,
|
23
|
-
print_pre_condition_states=False,
|
24
|
-
print_state_transitions=False,
|
25
|
-
print_audiences=False,
|
26
|
-
allow_unknown_bit_lengths=False,
|
27
|
-
variants=None,
|
28
|
-
service_filter=lambda x: True,
|
29
|
-
):
|
18
|
+
print_global_negative_responses: bool = False,
|
19
|
+
print_services: bool = False,
|
20
|
+
print_dops: bool = False,
|
21
|
+
print_params: bool = False,
|
22
|
+
print_com_params: bool = False,
|
23
|
+
print_pre_condition_states: bool = False,
|
24
|
+
print_state_transitions: bool = False,
|
25
|
+
print_audiences: bool = False,
|
26
|
+
allow_unknown_bit_lengths: bool = False,
|
27
|
+
variants: Optional[str] = None,
|
28
|
+
service_filter: Callable[[Union[DiagService, SingleEcuJob]], bool] = lambda x: True,
|
29
|
+
) -> None:
|
30
30
|
|
31
31
|
diag_layer_names = variants if variants else [dl.short_name for dl in odxdb.diag_layers]
|
32
32
|
|
@@ -97,7 +97,7 @@ def print_summary(
|
|
97
97
|
print(f" {com_param.short_name}: {com_param.value}")
|
98
98
|
|
99
99
|
|
100
|
-
def add_subparser(subparsers):
|
100
|
+
def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
101
101
|
parser = subparsers.add_parser(
|
102
102
|
"list",
|
103
103
|
description="\n".join([
|
@@ -179,16 +179,20 @@ def add_subparser(subparsers):
|
|
179
179
|
)
|
180
180
|
|
181
181
|
|
182
|
-
def run(args):
|
182
|
+
def run(args: argparse.Namespace) -> None:
|
183
183
|
odxdb = _parser_utils.load_file(args)
|
184
184
|
|
185
|
+
def service_filter(s: Union[DiagService, SingleEcuJob]) -> bool:
|
186
|
+
if args.services and len(args.services) > 0:
|
187
|
+
return s.short_name in args.services
|
188
|
+
return True
|
189
|
+
|
185
190
|
variants = args.variants if args.variants else None
|
186
191
|
print_summary(
|
187
192
|
odxdb,
|
188
193
|
print_global_negative_responses=args.all or args.global_negative_responses,
|
189
194
|
print_services=args.all or args.params or args.services is not None,
|
190
|
-
service_filter=
|
191
|
-
if args.services and len(args.services) > 0 else lambda s: True),
|
195
|
+
service_filter=service_filter,
|
192
196
|
print_dops=args.all or args.dops,
|
193
197
|
variants=None if variants == "all" else variants,
|
194
198
|
print_params=args.all or args.params,
|
odxtools/cli/snoop.py
CHANGED
@@ -4,12 +4,14 @@
|
|
4
4
|
import argparse
|
5
5
|
import asyncio
|
6
6
|
import sys
|
7
|
+
from typing import Any, Type
|
7
8
|
|
8
9
|
import can
|
9
10
|
|
10
|
-
import odxtools
|
11
11
|
import odxtools.isotp_state_machine as ism
|
12
12
|
import odxtools.uds as uds
|
13
|
+
from odxtools.exceptions import DecodeError
|
14
|
+
from odxtools.isotp_state_machine import IsoTpStateMachine
|
13
15
|
|
14
16
|
from . import _parser_utils
|
15
17
|
|
@@ -23,7 +25,7 @@ ecu_rx_id = None
|
|
23
25
|
ecu_tx_id = None
|
24
26
|
|
25
27
|
|
26
|
-
def handle_telegram(telegram_id, payload):
|
28
|
+
def handle_telegram(telegram_id: int, payload: bytes) -> None:
|
27
29
|
global odx_diag_layer
|
28
30
|
global last_request
|
29
31
|
|
@@ -36,7 +38,7 @@ def handle_telegram(telegram_id, payload):
|
|
36
38
|
if last_request is not None:
|
37
39
|
try:
|
38
40
|
decoded_message = odx_diag_layer.decode_response(payload, last_request)[0]
|
39
|
-
except
|
41
|
+
except DecodeError:
|
40
42
|
pass
|
41
43
|
|
42
44
|
if decoded_message is not None:
|
@@ -52,7 +54,7 @@ def handle_telegram(telegram_id, payload):
|
|
52
54
|
try:
|
53
55
|
decoded_message = odx_diag_layer.decode(payload)[0]
|
54
56
|
last_request = payload
|
55
|
-
except
|
57
|
+
except DecodeError:
|
56
58
|
last_request = None
|
57
59
|
|
58
60
|
if decoded_message:
|
@@ -60,21 +62,22 @@ def handle_telegram(telegram_id, payload):
|
|
60
62
|
else:
|
61
63
|
print(f"Tester: "
|
62
64
|
f"{payload.hex()} "
|
63
|
-
f"({payload}, {len(payload)} bytes)")
|
65
|
+
f"({payload!r}, {len(payload)} bytes)")
|
64
66
|
|
65
67
|
|
66
|
-
def init_verbose_state_machine(BaseClass, *args,
|
68
|
+
def init_verbose_state_machine(BaseClass: Type[IsoTpStateMachine], *args: Any,
|
69
|
+
**kwargs: Any) -> IsoTpStateMachine:
|
67
70
|
|
68
|
-
class InformativeIsoTpDecoder(BaseClass):
|
71
|
+
class InformativeIsoTpDecoder(BaseClass): # type: ignore[valid-type, misc]
|
69
72
|
|
70
|
-
def on_sequence_error(self, telegram_idx, expected_idx, rx_idx):
|
73
|
+
def on_sequence_error(self, telegram_idx: int, expected_idx: int, rx_idx: int) -> None:
|
71
74
|
rx_can_id = self.can_rx_id(telegram_idx)
|
72
75
|
print(f"Sequence error for ID 0x{rx_can_id:x}: "
|
73
76
|
f"Received sequence number {rx_idx} but expected {expected_idx}")
|
74
77
|
|
75
|
-
super(
|
78
|
+
super().on_sequence_error(telegram_idx, expected_idx, rx_idx)
|
76
79
|
|
77
|
-
def on_frame_type_error(self, telegram_idx, frame_type):
|
80
|
+
def on_frame_type_error(self, telegram_idx: int, frame_type: int) -> None:
|
78
81
|
rx_can_id = self.can_rx_id(telegram_idx)
|
79
82
|
|
80
83
|
print(f"Invalid ISO-TP frame for CAN ID 0x{rx_can_id:x}: {frame_type}")
|
@@ -82,7 +85,7 @@ def init_verbose_state_machine(BaseClass, *args, **kwargs):
|
|
82
85
|
return InformativeIsoTpDecoder(*args, **kwargs)
|
83
86
|
|
84
87
|
|
85
|
-
async def active_main(args):
|
88
|
+
async def active_main(args: argparse.Namespace) -> None:
|
86
89
|
global ecu_rx_id, ecu_tx_id
|
87
90
|
|
88
91
|
can_bus = can.Bus(channel=args.channel, bustype="socketcan")
|
@@ -102,7 +105,7 @@ async def active_main(args):
|
|
102
105
|
handle_telegram(telegram_id, payload)
|
103
106
|
|
104
107
|
|
105
|
-
async def passive_main(args):
|
108
|
+
async def passive_main(args: argparse.Namespace) -> None:
|
106
109
|
global ecu_rx_id, ecu_tx_id
|
107
110
|
|
108
111
|
ecu_rx_id = int(args.rx, 0)
|
@@ -124,7 +127,7 @@ async def passive_main(args):
|
|
124
127
|
handle_telegram(telegram_id, payload)
|
125
128
|
|
126
129
|
|
127
|
-
def add_cli_arguments(parser):
|
130
|
+
def add_cli_arguments(parser: argparse.ArgumentParser) -> None:
|
128
131
|
parser.add_argument(
|
129
132
|
"--active",
|
130
133
|
"-a",
|
@@ -172,7 +175,7 @@ def add_cli_arguments(parser):
|
|
172
175
|
_parser_utils.add_pdx_argument(parser)
|
173
176
|
|
174
177
|
|
175
|
-
def add_subparser(subparsers):
|
178
|
+
def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
176
179
|
parser = subparsers.add_parser(
|
177
180
|
"snoop",
|
178
181
|
description="Live decoding of a diagnostic session.",
|
@@ -182,7 +185,7 @@ def add_subparser(subparsers):
|
|
182
185
|
add_cli_arguments(parser)
|
183
186
|
|
184
187
|
|
185
|
-
def run(args
|
188
|
+
def run(args: argparse.Namespace) -> None:
|
186
189
|
global odx_diag_layer
|
187
190
|
odx_database = _parser_utils.load_file(args)
|
188
191
|
|
@@ -238,6 +241,4 @@ if __name__ == "__main__":
|
|
238
241
|
|
239
242
|
args = parser.parse_args() # deals with the help message handling
|
240
243
|
|
241
|
-
|
242
|
-
|
243
|
-
run(args, odx_database)
|
244
|
+
run(args)
|