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.
Files changed (88) hide show
  1. odxtools/__init__.py +1 -1
  2. odxtools/basicstructure.py +76 -91
  3. odxtools/cli/_parser_utils.py +12 -9
  4. odxtools/cli/_print_utils.py +7 -7
  5. odxtools/cli/browse.py +94 -73
  6. odxtools/cli/find.py +42 -59
  7. odxtools/cli/list.py +21 -17
  8. odxtools/cli/snoop.py +19 -18
  9. odxtools/communicationparameterref.py +6 -3
  10. odxtools/companydocinfo.py +2 -2
  11. odxtools/companyrevisioninfo.py +1 -1
  12. odxtools/comparamsubset.py +6 -6
  13. odxtools/complexcomparam.py +1 -1
  14. odxtools/compumethods/compumethod.py +6 -9
  15. odxtools/compumethods/createanycompumethod.py +11 -9
  16. odxtools/compumethods/identicalcompumethod.py +5 -4
  17. odxtools/compumethods/limit.py +9 -9
  18. odxtools/compumethods/linearcompumethod.py +25 -17
  19. odxtools/compumethods/scalelinearcompumethod.py +6 -5
  20. odxtools/compumethods/tabintpcompumethod.py +30 -9
  21. odxtools/compumethods/texttablecompumethod.py +22 -24
  22. odxtools/database.py +5 -5
  23. odxtools/dataobjectproperty.py +10 -23
  24. odxtools/decodestate.py +1 -1
  25. odxtools/determinenumberofitems.py +37 -8
  26. odxtools/diagcodedtype.py +14 -9
  27. odxtools/diagdatadictionaryspec.py +60 -37
  28. odxtools/diaglayer.py +30 -21
  29. odxtools/diaglayercontainer.py +40 -40
  30. odxtools/diaglayerraw.py +92 -63
  31. odxtools/diagnostictroublecode.py +2 -2
  32. odxtools/diagservice.py +53 -35
  33. odxtools/docrevision.py +1 -1
  34. odxtools/dopbase.py +14 -3
  35. odxtools/dtcdop.py +15 -9
  36. odxtools/dynamiclengthfield.py +6 -4
  37. odxtools/endofpdufield.py +22 -23
  38. odxtools/environmentdata.py +2 -5
  39. odxtools/environmentdatadescription.py +6 -4
  40. odxtools/field.py +3 -8
  41. odxtools/isotp_state_machine.py +52 -38
  42. odxtools/leadinglengthinfotype.py +9 -7
  43. odxtools/load_file.py +2 -1
  44. odxtools/load_odx_d_file.py +2 -5
  45. odxtools/load_pdx_file.py +2 -6
  46. odxtools/message.py +11 -3
  47. odxtools/minmaxlengthtype.py +107 -78
  48. odxtools/modification.py +2 -2
  49. odxtools/multiplexer.py +23 -21
  50. odxtools/multiplexerswitchkey.py +37 -8
  51. odxtools/nameditemlist.py +59 -58
  52. odxtools/odxlink.py +4 -2
  53. odxtools/odxtypes.py +4 -3
  54. odxtools/parameterinfo.py +6 -6
  55. odxtools/parameters/codedconstparameter.py +15 -25
  56. odxtools/parameters/createanyparameter.py +1 -1
  57. odxtools/parameters/dynamicparameter.py +6 -5
  58. odxtools/parameters/lengthkeyparameter.py +2 -1
  59. odxtools/parameters/matchingrequestparameter.py +8 -11
  60. odxtools/parameters/nrcconstparameter.py +11 -21
  61. odxtools/parameters/parameter.py +4 -18
  62. odxtools/parameters/parameterwithdop.py +14 -29
  63. odxtools/parameters/physicalconstantparameter.py +7 -9
  64. odxtools/parameters/reservedparameter.py +17 -38
  65. odxtools/parameters/systemparameter.py +6 -5
  66. odxtools/parameters/tableentryparameter.py +6 -5
  67. odxtools/parameters/tablekeyparameter.py +8 -15
  68. odxtools/parameters/tablestructparameter.py +11 -12
  69. odxtools/parameters/valueparameter.py +9 -24
  70. odxtools/paramlengthinfotype.py +11 -9
  71. odxtools/physicaldimension.py +1 -1
  72. odxtools/physicaltype.py +2 -2
  73. odxtools/response.py +7 -3
  74. odxtools/singleecujob.py +48 -22
  75. odxtools/standardlengthtype.py +11 -6
  76. odxtools/uds.py +1 -1
  77. odxtools/unit.py +5 -5
  78. odxtools/unitgroup.py +1 -1
  79. odxtools/unitspec.py +2 -2
  80. odxtools/version.py +13 -3
  81. odxtools/write_pdx_file.py +7 -4
  82. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
  83. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
  84. odxtools/positioneddataobjectproperty.py +0 -74
  85. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
  86. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
  87. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
  88. {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 Dict, List, Union
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 ..odxtypes import DataType
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.is_optional and input == "":
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
- val = _convert_string_to_odx_type(input, parameter.physical_type.base_data_type)
46
- except:
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
- return parameter.dop.is_valid_physical_value(val)
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 (not isinstance(parameter, ParameterWithDOP) or
56
- parameter.get_valid_physical_values() is None or
57
- parameter.get_valid_physical_values() is []):
58
- param_prompt = [{
59
- "type":
60
- "input",
61
- "name":
62
- parameter.short_name,
63
- "message":
64
- f"Value for parameter '{parameter.short_name}' (Type: {parameter.physical_type.base_data_type})"
65
- + (f"[optional]" if parameter.is_optional else ""),
66
- # TODO: improve validation
67
- "validate":
68
- lambda x: _validate_string_value(x, parameter),
69
- # TODO: do type conversion?
70
- "filter":
71
- lambda x: x
72
- # x if x == "" or p.physical_type.base_data_type is None
73
- # 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?)
74
- }]
75
- else:
76
- param_prompt = [{
77
- "type": "list",
78
- "name": parameter.short_name,
79
- "message": f"Value for parameter '{parameter.short_name}'",
80
- "choices": parameter.get_valid_physical_values(),
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.is_optional:
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, ask_user_confirmation=False):
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 k, param_or_dict in param_dict.items():
115
+ for param_or_dict in param_dict.values():
102
116
  if isinstance(param_or_dict, dict):
103
- for k, param in param_or_dict.items():
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: Dict[str, Union[str, Dict[str, str]]] = {},
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 parameter_sn, parameter in param_dict.items():
177
- if isinstance(parameter, dict):
178
- # parameter_value refers to a structure (represented as dict of params)
179
- for simple_param_sn, simple_param in parameter.items():
180
- structured_value = parameter_values.get(parameter_sn)
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"{parameter_sn} :: {simple_param_sn}")
201
+ missing_parameter_names.append(f"{param_sn} :: {simple_param_sn}")
186
202
  else:
187
- if parameter.is_required and parameter_values.get(parameter_sn) is None:
188
- missing_parameter_names.append(parameter_sn)
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, simple_val in parameter_value.items():
201
- try:
202
- parameter = param_dict[parameter_sn][simple_param_sn] # type: ignore
203
- except:
204
- print(f"I don't know the parameter {simple_param_sn}")
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
- simple_val,
209
- parameter.physical_type.base_data_type # type: ignore[union-attr]
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 parameter.parameter_type != "MATCHING-REQUEST-PARAM":
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, _param):
16
- import binascii
17
-
16
+ def get_display_value(v: ParameterValue) -> str:
18
17
  if isinstance(v, bytes):
19
- return binascii.hexlify(v, " ", 1).decode("utf-8")
18
+ return v.hex(" ")
20
19
  elif isinstance(v, int):
21
- return f"{v} ({hex(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=None,
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
- print_diagnostic_service(
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(f"SingleEcuJob: {service.odx_id}")
63
+ print(
64
+ f"Decoded by single ecu job '{service.short_name}' (decoding ECUs: {', '.join(ecu_names)})"
65
+ )
83
66
  else:
84
- print(f"Unknown service: {service}")
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
- print_decoded_message(service, data)
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
- def add_subparser(subparsers):
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=False,
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
- nargs="*",
135
- default=None,
136
- metavar="DECODE",
124
+ default=False,
125
+ action="store_true",
137
126
  required=False,
138
- help="Print a list of diagnostic services associated with the hex request and decode the request.",
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(data):
169
- import binascii
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
- variants = args.variants if args.variants else None
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=(lambda s: s.short_name in args.services
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 odxtools.DecodeError:
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 odxtools.DecodeError:
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, **kwargs):
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(BaseClass, self).on_sequence_error(telegram_idx, expected_idx, rx_idx)
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, odx_database=None):
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
- odx_database = odxtools.load_pdx_file(args.pdx_file)
242
-
243
- run(args, odx_database)
244
+ run(args)