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/__init__.py CHANGED
@@ -71,7 +71,7 @@ from .write_pdx_file import write_pdx_file
71
71
  __author__ = "Katrin Bauer"
72
72
 
73
73
 
74
- def _main():
74
+ def _main() -> None:
75
75
  # Command line tool
76
76
  from .cli import main as _main
77
77
 
@@ -1,73 +1,64 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import math
3
2
  import warnings
4
3
  from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, List, Optional, Tuple, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
6
5
 
7
6
  from .dataobjectproperty import DataObjectProperty
8
7
  from .decodestate import DecodeState
9
8
  from .dopbase import DopBase
10
9
  from .encodestate import EncodeState
11
- from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert
10
+ from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert, odxraise
12
11
  from .nameditemlist import NamedItemList
13
- from .odxlink import OdxLinkDatabase
14
- from .odxtypes import ParameterDict, ParameterValueDict
12
+ from .odxlink import OdxLinkDatabase, OdxLinkId
13
+ from .odxtypes import ParameterDict, ParameterValue, ParameterValueDict
15
14
  from .parameters.codedconstparameter import CodedConstParameter
16
15
  from .parameters.lengthkeyparameter import LengthKeyParameter
17
16
  from .parameters.matchingrequestparameter import MatchingRequestParameter
17
+ from .parameters.nrcconstparameter import NrcConstParameter
18
18
  from .parameters.parameter import Parameter
19
19
  from .parameters.parameterwithdop import ParameterWithDOP
20
+ from .parameters.physicalconstantparameter import PhysicalConstantParameter
20
21
  from .parameters.tablekeyparameter import TableKeyParameter
21
22
  from .parameters.valueparameter import ValueParameter
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from .diaglayer import DiagLayer
25
- from .endofpdufield import EndOfPduField
26
26
 
27
27
 
28
28
  @dataclass
29
29
  class BasicStructure(DopBase):
30
- parameters: NamedItemList[Union[Parameter, "EndOfPduField"]]
30
+ parameters: NamedItemList[Parameter]
31
31
  byte_size: Optional[int]
32
32
 
33
- @property
34
- def bit_length(self) -> Optional[int]:
33
+ def get_static_bit_length(self) -> Optional[int]:
35
34
  # Explicit size was specified
36
35
  if self.byte_size:
37
36
  return 8 * self.byte_size
38
37
 
39
- if all(p.bit_length is not None for p in self.parameters):
40
- offset = 0
41
- length = 0
42
- for param in self.parameters:
43
- if isinstance(param, ValueParameter) and hasattr(param.dop, "min_number_of_items"):
44
- # The param repeats itself, making bit_length calculation invalid
45
- # Temporary workaround
46
- # Can not import EndOfPduField to check on its type due to circular dependency
47
- return None
48
-
49
- if param.byte_position is not None:
50
- bit_position_int = param.bit_position if param.bit_position is not None else 0
51
- byte_position_int = (
52
- param.byte_position if param.byte_position is not None else 0)
53
- offset = byte_position_int * 8 + bit_position_int
54
- offset += param.bit_length
55
-
56
- length = max(length, offset)
57
-
58
- # Round up to account for padding bits
59
- return math.ceil(length / 8) * 8
60
-
61
- # We were not able to calculate a static bit length
62
- return None
63
-
64
- def coded_const_prefix(self, request_prefix: bytes = bytes()) -> bytes:
65
- prefix = bytes()
38
+ cursor = 0
39
+ length = 0
40
+ for param in self.parameters:
41
+ param_bit_length = param.get_static_bit_length()
42
+ if param_bit_length is None:
43
+ # We were not able to calculate a static bit length
44
+ return None
45
+ elif param.byte_position is not None:
46
+ bit_pos = param.bit_position or 0
47
+ byte_pos = param.byte_position or 0
48
+ cursor = byte_pos * 8 + bit_pos
49
+
50
+ cursor += param_bit_length
51
+ length = max(length, cursor)
52
+
53
+ # Round up to account for padding bits
54
+ return ((length + 7) // 8) * 8
55
+
56
+ def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
57
+ prefix = b''
66
58
  encode_state = EncodeState(prefix, parameter_values={}, triggering_request=request_prefix)
67
59
  for p in self.parameters:
68
- if isinstance(p, CodedConstParameter) and p.bit_length % 8 == 0:
69
- encode_state.coded_message = p.encode_into_pdu(encode_state)
70
- elif isinstance(p, MatchingRequestParameter):
60
+ if isinstance(p, (CodedConstParameter, NrcConstParameter, MatchingRequestParameter,
61
+ PhysicalConstantParameter)):
71
62
  encode_state.coded_message = p.encode_into_pdu(encode_state)
72
63
  else:
73
64
  break
@@ -80,7 +71,7 @@ class BasicStructure(DopBase):
80
71
  return [p for p in self.parameters if p.is_required]
81
72
 
82
73
  @property
83
- def free_parameters(self) -> List[Union[Parameter, "EndOfPduField"]]: # type: ignore
74
+ def free_parameters(self) -> List[Parameter]:
84
75
  """Return the list of parameters which can be freely specified by
85
76
  the user when encoding the structure.
86
77
 
@@ -89,17 +80,9 @@ class BasicStructure(DopBase):
89
80
  the corresponding request (in the case of responses).
90
81
 
91
82
  """
92
- from .endofpdufield import EndOfPduField
93
-
94
- result: List[Union[Parameter, EndOfPduField]] = []
83
+ result: List[Parameter] = []
95
84
  for param in self.parameters:
96
- if isinstance(param, EndOfPduField):
97
- result.append(param)
98
- continue
99
- elif not param.is_required:
100
- continue
101
- # The user cannot specify MatchingRequestParameters freely!
102
- elif isinstance(param, MatchingRequestParameter):
85
+ if not param.is_settable:
103
86
  continue
104
87
  result.append(param)
105
88
 
@@ -114,13 +97,18 @@ class BasicStructure(DopBase):
114
97
  print(parameter_info(self.free_parameters), end="")
115
98
 
116
99
  def convert_physical_to_internal(self,
117
- param_values: ParameterValueDict,
100
+ param_value: ParameterValue,
118
101
  triggering_coded_request: Optional[bytes],
119
102
  is_end_of_pdu: bool = True) -> bytes:
120
103
 
104
+ if not isinstance(param_value, dict):
105
+ raise EncodeError(
106
+ f"Expected a dictionary for the values of structure {self.short_name}, "
107
+ f"got {type(param_value)}")
108
+
121
109
  encode_state = EncodeState(
122
- bytes(),
123
- dict(param_values),
110
+ b'',
111
+ dict(param_value),
124
112
  triggering_request=triggering_coded_request,
125
113
  is_end_of_pdu=False,
126
114
  )
@@ -161,11 +149,11 @@ class BasicStructure(DopBase):
161
149
  # We definitely broke something if we didn't respect the explicit byte_size
162
150
  odxassert(
163
151
  len(coded_message) == self.byte_size,
164
- self._get_encode_error_str("was", coded_message, self.byte_size * 8))
152
+ "Verification of coded message {coded_message.hex()} failed: Incorrect size.")
165
153
  # No need to check further
166
154
  return
167
155
 
168
- bit_length = self.bit_length
156
+ bit_length = self.get_static_bit_length()
169
157
 
170
158
  if bit_length is None:
171
159
  # Nothing to check
@@ -176,19 +164,18 @@ class BasicStructure(DopBase):
176
164
  # but it could be that bit_length was mis calculated and not the actual bytes are wrong
177
165
  # Could happen with overlapping parameters and parameters with gaps
178
166
  warnings.warn(
179
- self._get_encode_error_str("may have been", coded_message, bit_length), OdxWarning)
180
-
181
- def _get_encode_error_str(self, verb: str, coded_message: bytes, bit_length: int) -> str:
182
- return str(f"Structure {self.short_name} {verb} encoded incorrectly:" +
183
- f" actual length is {len(coded_message)}," +
184
- f" computed byte length is {bit_length // 8}," +
185
- f" computed_rpc is {coded_message.hex()}\n" +
186
- "\n".join(self.__message_format_lines()))
167
+ "Verification of coded message {coded_message.hex()} possibly failed: Size may be incorrect.",
168
+ OdxWarning,
169
+ stacklevel=1)
187
170
 
188
171
  def convert_physical_to_bytes(self,
189
- param_values: ParameterValueDict,
172
+ param_values: ParameterValue,
190
173
  encode_state: EncodeState,
191
174
  bit_position: int = 0) -> bytes:
175
+ if not isinstance(param_values, dict):
176
+ raise EncodeError(
177
+ f"Expected a dictionary for the values of structure {self.short_name}, "
178
+ f"got {type(param_values)}")
192
179
  if bit_position != 0:
193
180
  raise EncodeError("Structures must be aligned, i.e. bit_position=0, but "
194
181
  f"{self.short_name} was passed the bit position {bit_position}")
@@ -200,27 +187,27 @@ class BasicStructure(DopBase):
200
187
 
201
188
  def convert_bytes_to_physical(self,
202
189
  decode_state: DecodeState,
203
- bit_position: int = 0) -> Tuple[ParameterValueDict, int]:
190
+ bit_position: int = 0) -> Tuple[ParameterValue, int]:
204
191
  if bit_position != 0:
205
192
  raise DecodeError("Structures must be aligned, i.e. bit_position=0, but "
206
193
  f"{self.short_name} was passed the bit position {bit_position}")
207
- byte_code = decode_state.coded_message[decode_state.next_byte_position:]
194
+ byte_code = decode_state.coded_message[decode_state.cursor_position:]
208
195
  inner_decode_state = DecodeState(
209
- coded_message=byte_code, parameter_values={}, next_byte_position=0)
196
+ coded_message=byte_code, parameter_values={}, cursor_position=0)
210
197
 
211
198
  for parameter in self.parameters:
212
- value, next_byte_position = parameter.decode_from_pdu(inner_decode_state)
199
+ value, cursor_position = parameter.decode_from_pdu(inner_decode_state)
213
200
 
214
201
  inner_decode_state.parameter_values[parameter.short_name] = value
215
202
  inner_decode_state = DecodeState(
216
203
  coded_message=byte_code,
217
204
  parameter_values=inner_decode_state.parameter_values,
218
- next_byte_position=max(inner_decode_state.next_byte_position, next_byte_position),
205
+ cursor_position=max(inner_decode_state.cursor_position, cursor_position),
219
206
  )
220
207
 
221
- return inner_decode_state.parameter_values, decode_state.next_byte_position + inner_decode_state.next_byte_position
208
+ return inner_decode_state.parameter_values, decode_state.cursor_position + inner_decode_state.cursor_position
222
209
 
223
- def encode(self, coded_request: Optional[bytes] = None, **params) -> bytes:
210
+ def encode(self, coded_request: Optional[bytes] = None, **params: ParameterValue) -> bytes:
224
211
  """
225
212
  Composes an UDS message as bytes for this service.
226
213
  Parameters:
@@ -237,13 +224,17 @@ class BasicStructure(DopBase):
237
224
 
238
225
  def decode(self, message: bytes) -> ParameterValueDict:
239
226
  # dummy decode state to be passed to convert_bytes_to_physical
240
- decode_state = DecodeState(parameter_values={}, coded_message=message, next_byte_position=0)
241
- param_values, next_byte_position = self.convert_bytes_to_physical(decode_state)
242
- if len(message) != next_byte_position:
227
+ decode_state = DecodeState(parameter_values={}, coded_message=message, cursor_position=0)
228
+ param_values, cursor_position = self.convert_bytes_to_physical(decode_state)
229
+ if not isinstance(param_values, dict):
230
+ odxraise(f"Decoding a structure must result in a dictionary of parameter "
231
+ f"values (is {type(param_values)})")
232
+ if len(message) != cursor_position:
243
233
  warnings.warn(
244
234
  f"The message {message.hex()} is longer than could be parsed."
245
- f" Expected {next_byte_position} but got {len(message)}.",
235
+ f" Expected {cursor_position} but got {len(message)}.",
246
236
  DecodeError,
237
+ stacklevel=1,
247
238
  )
248
239
  return param_values
249
240
 
@@ -270,7 +261,7 @@ class BasicStructure(DopBase):
270
261
  })
271
262
  return param_dict
272
263
 
273
- def _build_odxlinks(self):
264
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
274
265
  result = super()._build_odxlinks()
275
266
 
276
267
  for p in self.parameters:
@@ -292,15 +283,11 @@ class BasicStructure(DopBase):
292
283
  for p in self.parameters:
293
284
  p._resolve_snrefs(diag_layer)
294
285
 
295
- def __message_format_lines(self, allow_unknown_lengths: bool = False) -> List[str]:
286
+ def _message_format_lines(self, allow_unknown_lengths: bool = False) -> List[str]:
296
287
  # sort parameters
297
288
  sorted_params: list = list(self.parameters) # copy list
298
289
 
299
- def param_sort_key(param: Union[Parameter, "EndOfPduField"]) -> Tuple[int, int]:
300
- if not isinstance(param, Parameter):
301
- # -> EndOfPduField should come last!
302
- return (1000 * 1000, 0)
303
-
290
+ def param_sort_key(param: Parameter) -> Tuple[int, int]:
304
291
  byte_position_int = param.byte_position if param.byte_position is not None else 0
305
292
  bit_position_int = param.bit_position if param.bit_position is not None else 0
306
293
  return (byte_position_int, 8 - bit_position_int)
@@ -310,8 +297,8 @@ class BasicStructure(DopBase):
310
297
  # replace structure parameters by their sub parameters
311
298
  params: List[Parameter] = []
312
299
  for p in sorted_params:
313
- if isinstance(p, ValueParameter) and isinstance(p.dop, BasicStructure):
314
- params += p.dop.parameters
300
+ if isinstance(p, ValueParameter) and isinstance(p._dop, BasicStructure):
301
+ params += p._dop.parameters
315
302
  else:
316
303
  params.append(p)
317
304
 
@@ -358,14 +345,12 @@ class BasicStructure(DopBase):
358
345
  # END-OF-PDU fields do not exhibit a fixed bit
359
346
  # length, so they need special treatment here
360
347
  dct = None
361
- if hasattr(params[param_idx], "dop"):
362
- dop = params[param_idx].dop # type: ignore
348
+ if hasattr(params[param_idx], "_dop"):
349
+ dop = params[param_idx]._dop # type: ignore[attr-defined]
363
350
  if hasattr(dop, "diag_coded_type"):
364
351
  dct = dop.diag_coded_type
365
352
 
366
- bit_length = None
367
- if hasattr(params[param_idx], "bit_length"):
368
- bit_length = params[param_idx].bit_length
353
+ bit_length = params[param_idx].get_static_bit_length()
369
354
 
370
355
  if dct is not None and dct.dct_type == "MIN-MAX-LENGTH-TYPE":
371
356
  name = params[param_idx].short_name + " ("
@@ -434,12 +419,12 @@ class BasicStructure(DopBase):
434
419
  else:
435
420
  return []
436
421
 
437
- def print_message_format(self, indent: int = 5, allow_unknown_lengths=False):
422
+ def print_message_format(self, indent: int = 5, allow_unknown_lengths: bool = False) -> None:
438
423
  """
439
424
  Print a description of the message format to `stdout`.
440
425
  """
441
426
 
442
- message_as_lines = self.__message_format_lines(allow_unknown_lengths=allow_unknown_lengths)
427
+ message_as_lines = self._message_format_lines(allow_unknown_lengths=allow_unknown_lengths)
443
428
  if message_as_lines is not None:
444
429
  print(f"{indent * ' '}" + f"\n{indent * ' '}".join(message_as_lines))
445
430
  else:
@@ -1,17 +1,20 @@
1
1
  #! /usr/bin/python3
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ import argparse
5
+
6
+ from ..database import Database
4
7
  from ..load_file import load_file as _load_file
5
8
 
6
9
 
7
- def add_pdx_argument(parser):
8
- parser.add_argument("pdx_file", metavar="PDX_FILE", help="path to the .pdx file")
9
- # parser.add_argument("pdx_files", metavar="PDX_FILES", nargs="+", help="PDX descriptions of all ECUs which shall be analyzed")
10
+ def add_pdx_argument(parser: argparse.ArgumentParser, is_optional: bool = False) -> None:
11
+ parser.add_argument(
12
+ "pdx_file",
13
+ help="Location of the .pdx file",
14
+ nargs="?" if is_optional else 1,
15
+ metavar="PDX_FILE")
10
16
 
11
17
 
12
- def load_file(args):
13
- db_file_name = args.pdx_file
14
- odxdb = None
15
- if db_file_name is not None:
16
- odxdb = _load_file(db_file_name)
17
- return odxdb
18
+ def load_file(args: argparse.Namespace) -> Database:
19
+ pdx_file_name = args.pdx_file if isinstance(args.pdx_file, str) else args.pdx_file[0]
20
+ return _load_file(pdx_file_name)
@@ -6,7 +6,7 @@ import markdownify
6
6
  from ..diagservice import DiagService
7
7
 
8
8
 
9
- def format_desc(desc, ident=0):
9
+ def format_desc(desc: str, ident: int = 0) -> str:
10
10
  # Collapse whitespaces
11
11
  desc = re.sub(r"\s+", " ", desc)
12
12
  # Covert XHTML to Markdown
@@ -21,12 +21,12 @@ def format_desc(desc, ident=0):
21
21
 
22
22
  def print_diagnostic_service(
23
23
  service: DiagService,
24
- print_params=False,
25
- print_pre_condition_states=False,
26
- print_state_transitions=False,
27
- print_audiences=False,
28
- allow_unknown_bit_lengths=False,
29
- ):
24
+ print_params: bool = False,
25
+ print_pre_condition_states: bool = False,
26
+ print_state_transitions: bool = False,
27
+ print_audiences: bool = False,
28
+ allow_unknown_bit_lengths: bool = False,
29
+ ) -> None:
30
30
  print(f" {service.short_name} <ID: {service.odx_id}>")
31
31
 
32
32
  if service.description: