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/__init__.py
CHANGED
odxtools/basicstructure.py
CHANGED
@@ -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
|
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[
|
30
|
+
parameters: NamedItemList[Parameter]
|
31
31
|
byte_size: Optional[int]
|
32
32
|
|
33
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
69
|
-
|
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[
|
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
|
-
|
93
|
-
|
94
|
-
result: List[Union[Parameter, EndOfPduField]] = []
|
83
|
+
result: List[Parameter] = []
|
95
84
|
for param in self.parameters:
|
96
|
-
if
|
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
|
-
|
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
|
-
|
123
|
-
dict(
|
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
|
-
|
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.
|
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
|
-
|
180
|
-
|
181
|
-
|
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:
|
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[
|
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.
|
194
|
+
byte_code = decode_state.coded_message[decode_state.cursor_position:]
|
208
195
|
inner_decode_state = DecodeState(
|
209
|
-
coded_message=byte_code, parameter_values={},
|
196
|
+
coded_message=byte_code, parameter_values={}, cursor_position=0)
|
210
197
|
|
211
198
|
for parameter in self.parameters:
|
212
|
-
value,
|
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
|
-
|
205
|
+
cursor_position=max(inner_decode_state.cursor_position, cursor_position),
|
219
206
|
)
|
220
207
|
|
221
|
-
return inner_decode_state.parameter_values, decode_state.
|
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,
|
241
|
-
param_values,
|
242
|
-
if
|
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 {
|
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
|
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:
|
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.
|
314
|
-
params += p.
|
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], "
|
362
|
-
dop = params[param_idx].
|
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 =
|
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.
|
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:
|
odxtools/cli/_parser_utils.py
CHANGED
@@ -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(
|
9
|
-
|
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
|
-
|
14
|
-
|
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)
|
odxtools/cli/_print_utils.py
CHANGED
@@ -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:
|