odxtools 8.3.3__py3-none-any.whl → 9.0.0__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/basicstructure.py +36 -207
- odxtools/cli/_print_utils.py +163 -131
- odxtools/cli/browse.py +94 -79
- odxtools/cli/compare.py +88 -69
- odxtools/cli/list.py +2 -3
- odxtools/codec.py +211 -0
- odxtools/diaglayers/diaglayer.py +1 -1
- odxtools/diaglayers/hierarchyelement.py +0 -10
- odxtools/dopbase.py +5 -3
- odxtools/dtcdop.py +101 -14
- odxtools/inputparam.py +0 -7
- odxtools/leadinglengthinfotype.py +1 -8
- odxtools/message.py +0 -7
- odxtools/minmaxlengthtype.py +4 -4
- odxtools/odxlink.py +5 -2
- odxtools/outputparam.py +0 -7
- odxtools/parameterinfo.py +12 -12
- odxtools/parameters/parameter.py +6 -4
- odxtools/paramlengthinfotype.py +8 -9
- odxtools/request.py +109 -16
- odxtools/response.py +115 -15
- odxtools/specialdatagroup.py +1 -1
- odxtools/templates/macros/printDOP.xml.jinja2 +16 -0
- odxtools/uds.py +0 -8
- odxtools/version.py +2 -2
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/METADATA +7 -8
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/RECORD +31 -30
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/WHEEL +1 -1
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/LICENSE +0 -0
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/entry_points.txt +0 -0
- {odxtools-8.3.3.dist-info → odxtools-9.0.0.dist-info}/top_level.txt +0 -0
odxtools/basicstructure.py
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
|
-
import warnings
|
3
2
|
from dataclasses import dataclass
|
4
|
-
from typing import Any, Dict, List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional
|
5
4
|
from xml.etree import ElementTree
|
6
5
|
|
7
6
|
from typing_extensions import override
|
8
7
|
|
8
|
+
from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
|
9
|
+
composite_codec_get_free_parameters, composite_codec_get_required_parameters,
|
10
|
+
composite_codec_get_static_bit_length)
|
9
11
|
from .complexdop import ComplexDop
|
10
|
-
from .dataobjectproperty import DataObjectProperty
|
11
12
|
from .decodestate import DecodeState
|
12
13
|
from .encodestate import EncodeState
|
13
|
-
from .exceptions import
|
14
|
+
from .exceptions import DecodeError, odxraise
|
14
15
|
from .nameditemlist import NamedItemList
|
15
16
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
16
|
-
from .odxtypes import
|
17
|
-
from .parameters.codedconstparameter import CodedConstParameter
|
17
|
+
from .odxtypes import ParameterValue
|
18
18
|
from .parameters.createanyparameter import create_any_parameter_from_et
|
19
|
-
from .parameters.lengthkeyparameter import LengthKeyParameter
|
20
|
-
from .parameters.matchingrequestparameter import MatchingRequestParameter
|
21
19
|
from .parameters.parameter import Parameter
|
22
|
-
from .parameters.parameterwithdop import ParameterWithDOP
|
23
|
-
from .parameters.physicalconstantparameter import PhysicalConstantParameter
|
24
|
-
from .parameters.tablekeyparameter import TableKeyParameter
|
25
20
|
from .snrefcontext import SnRefContext
|
26
21
|
from .utils import dataclass_fields_asdict
|
27
22
|
|
28
23
|
|
29
24
|
@dataclass
|
30
25
|
class BasicStructure(ComplexDop):
|
26
|
+
"""Base class for structure-like objects
|
27
|
+
|
28
|
+
"Structure-like" objects are structures as well as environment
|
29
|
+
data objects. All structure-like objects adhere to the
|
30
|
+
`CompositeCodec` type protocol.
|
31
|
+
"""
|
31
32
|
parameters: NamedItemList[Parameter]
|
32
33
|
byte_size: Optional[int]
|
33
34
|
|
@@ -48,62 +49,20 @@ class BasicStructure(ComplexDop):
|
|
48
49
|
return BasicStructure(parameters=parameters, byte_size=byte_size, **kwargs)
|
49
50
|
|
50
51
|
def get_static_bit_length(self) -> Optional[int]:
|
51
|
-
# Explicit size was specified
|
52
|
-
|
52
|
+
# Explicit size was specified, so we do not need to look at
|
53
|
+
# the list of parameters
|
54
|
+
if self.byte_size is not None:
|
53
55
|
return 8 * self.byte_size
|
54
56
|
|
55
|
-
|
56
|
-
byte_length = 0
|
57
|
-
for param in self.parameters:
|
58
|
-
param_bit_length = param.get_static_bit_length()
|
59
|
-
if param_bit_length is None:
|
60
|
-
# We were not able to calculate a static bit length
|
61
|
-
return None
|
62
|
-
elif param.byte_position is not None:
|
63
|
-
cursor = param.byte_position
|
64
|
-
|
65
|
-
cursor += ((param.bit_position or 0) + param_bit_length + 7) // 8
|
66
|
-
byte_length = max(byte_length, cursor)
|
67
|
-
|
68
|
-
# Round up to account for padding bits (all structures are
|
69
|
-
# byte aligned)
|
70
|
-
return byte_length * 8
|
71
|
-
|
72
|
-
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
|
73
|
-
encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
|
74
|
-
|
75
|
-
for param in self.parameters:
|
76
|
-
if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
|
77
|
-
isinstance(param, (CodedConstParameter, PhysicalConstantParameter)):
|
78
|
-
param.encode_into_pdu(physical_value=None, encode_state=encode_state)
|
79
|
-
else:
|
80
|
-
break
|
81
|
-
|
82
|
-
return encode_state.coded_message
|
57
|
+
return composite_codec_get_static_bit_length(self)
|
83
58
|
|
84
59
|
@property
|
85
60
|
def required_parameters(self) -> List[Parameter]:
|
86
|
-
|
87
|
-
encoding the structure."""
|
88
|
-
return [p for p in self.parameters if p.is_required]
|
61
|
+
return composite_codec_get_required_parameters(self)
|
89
62
|
|
90
63
|
@property
|
91
64
|
def free_parameters(self) -> List[Parameter]:
|
92
|
-
|
93
|
-
the user when encoding the structure.
|
94
|
-
|
95
|
-
This means all required parameters plus the parameters that
|
96
|
-
can be omitted minus those which are implicitly specified by
|
97
|
-
the corresponding request (in the case of responses).
|
98
|
-
|
99
|
-
"""
|
100
|
-
result: List[Parameter] = []
|
101
|
-
for param in self.parameters:
|
102
|
-
if not param.is_settable:
|
103
|
-
continue
|
104
|
-
result.append(param)
|
105
|
-
|
106
|
-
return result
|
65
|
+
return composite_codec_get_free_parameters(self)
|
107
66
|
|
108
67
|
def print_free_parameters_info(self) -> None:
|
109
68
|
"""Return a human readable description of the structure's
|
@@ -113,171 +72,44 @@ class BasicStructure(ComplexDop):
|
|
113
72
|
|
114
73
|
print(parameter_info(self.free_parameters), end="")
|
115
74
|
|
116
|
-
def _validate_coded_message_size(self, coded_byte_len: int) -> None:
|
117
|
-
|
118
|
-
if self.byte_size is not None:
|
119
|
-
# We definitely broke something if we didn't respect the explicit byte_size
|
120
|
-
if self.byte_size != coded_byte_len:
|
121
|
-
warnings.warn(
|
122
|
-
"Verification of coded message failed: Incorrect size.",
|
123
|
-
OdxWarning,
|
124
|
-
stacklevel=1)
|
125
|
-
|
126
|
-
return
|
127
|
-
|
128
|
-
bit_length = self.get_static_bit_length()
|
129
|
-
|
130
|
-
if bit_length is None:
|
131
|
-
# Nothing to check
|
132
|
-
return
|
133
|
-
|
134
|
-
if coded_byte_len * 8 != bit_length:
|
135
|
-
# We may have broke something
|
136
|
-
# but it could be that bit_length was mis calculated and not the actual bytes are wrong
|
137
|
-
# Could happen with overlapping parameters and parameters with gaps
|
138
|
-
warnings.warn(
|
139
|
-
"Verification of coded message possibly failed: Size may be incorrect.",
|
140
|
-
OdxWarning,
|
141
|
-
stacklevel=1)
|
142
|
-
|
143
75
|
@override
|
144
76
|
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
145
77
|
encode_state: EncodeState) -> None:
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
f"got {type(physical_value).__name__}", EncodeError)
|
150
|
-
elif encode_state.cursor_bit_position != 0:
|
151
|
-
odxraise(
|
152
|
-
f"Structures must be byte aligned, but "
|
153
|
-
f"{self.short_name} requested to be at bit position "
|
154
|
-
f"{encode_state.cursor_bit_position}", EncodeError)
|
155
|
-
encode_state.bit_position = 0
|
156
|
-
|
157
|
-
orig_origin = encode_state.origin_byte_position
|
158
|
-
encode_state.origin_byte_position = encode_state.cursor_byte_position
|
159
|
-
|
160
|
-
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
161
|
-
encode_state.is_end_of_pdu = False
|
162
|
-
|
163
|
-
# ensure that no values for unknown parameters are specified.
|
164
|
-
if not encode_state.allow_unknown_parameters:
|
165
|
-
param_names = {param.short_name for param in self.parameters}
|
166
|
-
for param_value_name in physical_value:
|
167
|
-
if param_value_name not in param_names:
|
168
|
-
odxraise(f"Value for unknown parameter '{param_value_name}' specified "
|
169
|
-
f"for structure {self.short_name}")
|
78
|
+
orig_pos = encode_state.cursor_byte_position
|
79
|
+
|
80
|
+
composite_codec_encode_into_pdu(self, physical_value, encode_state)
|
170
81
|
|
171
|
-
for param in self.parameters:
|
172
|
-
if id(param) == id(self.parameters[-1]):
|
173
|
-
# The last parameter of the structure is at the end of
|
174
|
-
# the PDU if the structure itself is at the end of the
|
175
|
-
# PDU. TODO: This assumes that the last parameter
|
176
|
-
# specified in the ODX is located last in the PDU...
|
177
|
-
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
178
|
-
|
179
|
-
if isinstance(param, (LengthKeyParameter, TableKeyParameter)):
|
180
|
-
# At this point, we encode a placeholder value for length-
|
181
|
-
# and table keys, since these can be specified
|
182
|
-
# implicitly (i.e., by means of parameters that use
|
183
|
-
# these keys). To avoid getting an "overlapping
|
184
|
-
# parameter" warning, we must encode a value of zero
|
185
|
-
# into the PDU here and add the real value of the
|
186
|
-
# parameter in a post-processing step.
|
187
|
-
param.encode_placeholder_into_pdu(
|
188
|
-
physical_value=physical_value.get(param.short_name), encode_state=encode_state)
|
189
|
-
|
190
|
-
continue
|
191
|
-
|
192
|
-
if param.is_required and param.short_name not in physical_value:
|
193
|
-
odxraise(f"No value for required parameter {param.short_name} specified",
|
194
|
-
EncodeError)
|
195
|
-
|
196
|
-
param_phys_value = physical_value.get(param.short_name)
|
197
|
-
param.encode_into_pdu(physical_value=param_phys_value, encode_state=encode_state)
|
198
|
-
|
199
|
-
encode_state.journal.append((param, param_phys_value))
|
200
|
-
|
201
|
-
encode_state.is_end_of_pdu = False
|
202
82
|
if self.byte_size is not None:
|
203
|
-
actual_len = encode_state.cursor_byte_position -
|
83
|
+
actual_len = encode_state.cursor_byte_position - orig_pos
|
84
|
+
|
204
85
|
if actual_len < self.byte_size:
|
205
|
-
# Padding bytes needed. We add an empty object at
|
206
|
-
# position directly after the structure and let
|
86
|
+
# Padding bytes are needed. We add an empty object at
|
87
|
+
# the position directly after the structure and let
|
207
88
|
# EncodeState add the padding as needed.
|
208
89
|
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_size
|
209
90
|
# Padding bytes needed. these count as "used".
|
210
91
|
encode_state.coded_message += b"\x00" * (self.byte_size - actual_len)
|
211
92
|
encode_state.used_mask += b"\xff" * (self.byte_size - actual_len)
|
212
93
|
|
213
|
-
# encode the length- and table keys. This cannot be done above
|
214
|
-
# because we allow these to be defined implicitly (i.e. they
|
215
|
-
# are defined by their respective users)
|
216
|
-
for param in self.parameters:
|
217
|
-
if not isinstance(param, (LengthKeyParameter, TableKeyParameter)):
|
218
|
-
# the current parameter is neither a length- nor a table key
|
219
|
-
continue
|
220
|
-
|
221
|
-
# Encode the value of the key parameter into the message
|
222
|
-
param.encode_value_into_pdu(encode_state=encode_state)
|
223
|
-
|
224
|
-
# Assert that length is as expected
|
225
|
-
self._validate_coded_message_size(encode_state.cursor_byte_position -
|
226
|
-
encode_state.origin_byte_position)
|
227
|
-
|
228
|
-
encode_state.origin_byte_position = orig_origin
|
229
|
-
|
230
94
|
@override
|
231
95
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
232
|
-
|
233
|
-
# structures are relative to the beginning of the structure object.
|
234
|
-
orig_origin = decode_state.origin_byte_position
|
235
|
-
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
96
|
+
orig_pos = decode_state.cursor_byte_position
|
236
97
|
|
237
|
-
result =
|
238
|
-
for param in self.parameters:
|
239
|
-
value = param.decode_from_pdu(decode_state)
|
98
|
+
result = composite_codec_decode_from_pdu(self, decode_state)
|
240
99
|
|
241
|
-
|
242
|
-
|
100
|
+
if self.byte_size is not None:
|
101
|
+
n = decode_state.cursor_byte_position - orig_pos
|
102
|
+
if n > self.byte_size:
|
103
|
+
odxraise(
|
104
|
+
f"Attempted to decode too large instance of structure "
|
105
|
+
f"{self.short_name} ({n} instead at most "
|
106
|
+
f"{self.byte_size} bytes)", DecodeError)
|
107
|
+
return result
|
243
108
|
|
244
|
-
|
245
|
-
decode_state.origin_byte_position = orig_origin
|
109
|
+
decode_state.cursor_byte_position = orig_pos + self.byte_size
|
246
110
|
|
247
111
|
return result
|
248
112
|
|
249
|
-
def decode(self, message: bytes) -> ParameterValueDict:
|
250
|
-
decode_state = DecodeState(coded_message=message)
|
251
|
-
param_values = self.decode_from_pdu(decode_state)
|
252
|
-
|
253
|
-
if not isinstance(param_values, dict):
|
254
|
-
odxraise("Decoding structures must result in a dictionary")
|
255
|
-
|
256
|
-
return cast(ParameterValueDict, param_values)
|
257
|
-
|
258
|
-
def parameter_dict(self) -> ParameterDict:
|
259
|
-
"""
|
260
|
-
Returns a dictionary with all parameter short names as keys.
|
261
|
-
|
262
|
-
The values are parameters for simple types or a nested dict for structures.
|
263
|
-
"""
|
264
|
-
from .structure import Structure
|
265
|
-
odxassert(
|
266
|
-
all(not isinstance(p, ParameterWithDOP) or isinstance(p.dop, DataObjectProperty) or
|
267
|
-
isinstance(p.dop, Structure) for p in self.parameters))
|
268
|
-
param_dict: ParameterDict = {
|
269
|
-
p.short_name: p
|
270
|
-
for p in self.parameters
|
271
|
-
if not isinstance(p, ParameterWithDOP) or not isinstance(p.dop, Structure)
|
272
|
-
}
|
273
|
-
param_dict.update({
|
274
|
-
struct_param.short_name: struct_param.dop.parameter_dict()
|
275
|
-
for struct_param in self.parameters
|
276
|
-
if isinstance(struct_param, ParameterWithDOP) and
|
277
|
-
isinstance(struct_param.dop, BasicStructure)
|
278
|
-
})
|
279
|
-
return param_dict
|
280
|
-
|
281
113
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
282
114
|
result = super()._build_odxlinks()
|
283
115
|
|
@@ -287,18 +119,15 @@ class BasicStructure(ComplexDop):
|
|
287
119
|
return result
|
288
120
|
|
289
121
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
290
|
-
"""Recursively resolve any references (odxlinks or sn-refs)"""
|
291
122
|
super()._resolve_odxlinks(odxlinks)
|
292
123
|
|
293
124
|
for param in self.parameters:
|
294
125
|
param._resolve_odxlinks(odxlinks)
|
295
126
|
|
296
127
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
297
|
-
"""Recursively resolve any references (odxlinks or sn-refs)"""
|
298
128
|
context.parameters = self.parameters
|
299
129
|
|
300
130
|
super()._resolve_snrefs(context)
|
301
|
-
|
302
131
|
for param in self.parameters:
|
303
132
|
param._resolve_snrefs(context)
|
304
133
|
|