odxtools 6.7.1__py3-none-any.whl → 7.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 +106 -112
- odxtools/dataobjectproperty.py +3 -7
- odxtools/decodestate.py +1 -13
- odxtools/diagcodedtype.py +9 -96
- odxtools/diaglayer.py +45 -0
- odxtools/diagservice.py +12 -16
- odxtools/dopbase.py +5 -8
- odxtools/dtcdop.py +21 -19
- odxtools/dynamiclengthfield.py +30 -26
- odxtools/encodestate.py +188 -23
- odxtools/endofpdufield.py +12 -14
- odxtools/environmentdatadescription.py +13 -13
- odxtools/leadinglengthinfotype.py +25 -12
- odxtools/minmaxlengthtype.py +36 -26
- odxtools/multiplexer.py +42 -23
- odxtools/multiplexercase.py +1 -1
- odxtools/nameditemlist.py +14 -0
- odxtools/odxtypes.py +17 -0
- odxtools/parameterinfo.py +126 -40
- odxtools/parameters/codedconstparameter.py +14 -11
- odxtools/parameters/dynamicparameter.py +5 -4
- odxtools/parameters/lengthkeyparameter.py +62 -14
- odxtools/parameters/matchingrequestparameter.py +23 -11
- odxtools/parameters/nrcconstparameter.py +39 -20
- odxtools/parameters/parameter.py +29 -42
- odxtools/parameters/parameterwithdop.py +5 -8
- odxtools/parameters/physicalconstantparameter.py +12 -13
- odxtools/parameters/reservedparameter.py +5 -2
- odxtools/parameters/systemparameter.py +3 -2
- odxtools/parameters/tableentryparameter.py +3 -2
- odxtools/parameters/tablekeyparameter.py +79 -30
- odxtools/parameters/tablestructparameter.py +62 -42
- odxtools/parameters/valueparameter.py +12 -14
- odxtools/paramlengthinfotype.py +30 -22
- odxtools/request.py +9 -0
- odxtools/response.py +5 -13
- odxtools/standardlengthtype.py +51 -13
- odxtools/staticfield.py +15 -19
- odxtools/version.py +2 -2
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/METADATA +1 -1
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/RECORD +45 -45
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/LICENSE +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/WHEEL +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/top_level.txt +0 -0
odxtools/basicstructure.py
CHANGED
@@ -4,6 +4,8 @@ from dataclasses import dataclass
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
+
from typing_extensions import override
|
8
|
+
|
7
9
|
from .complexdop import ComplexDop
|
8
10
|
from .dataobjectproperty import DataObjectProperty
|
9
11
|
from .decodestate import DecodeState
|
@@ -21,6 +23,7 @@ from .parameters.parameter import Parameter
|
|
21
23
|
from .parameters.parameterwithdop import ParameterWithDOP
|
22
24
|
from .parameters.physicalconstantparameter import PhysicalConstantParameter
|
23
25
|
from .parameters.tablekeyparameter import TableKeyParameter
|
26
|
+
from .parameters.tablestructparameter import TableStructParameter
|
24
27
|
from .utils import dataclass_fields_asdict
|
25
28
|
|
26
29
|
if TYPE_CHECKING:
|
@@ -54,32 +57,29 @@ class BasicStructure(ComplexDop):
|
|
54
57
|
return 8 * self.byte_size
|
55
58
|
|
56
59
|
cursor = 0
|
57
|
-
|
60
|
+
byte_length = 0
|
58
61
|
for param in self.parameters:
|
59
62
|
param_bit_length = param.get_static_bit_length()
|
60
63
|
if param_bit_length is None:
|
61
64
|
# We were not able to calculate a static bit length
|
62
65
|
return None
|
63
66
|
elif param.byte_position is not None:
|
64
|
-
|
65
|
-
byte_pos = param.byte_position or 0
|
66
|
-
cursor = byte_pos * 8 + bit_pos
|
67
|
+
cursor = param.byte_position
|
67
68
|
|
68
|
-
cursor += param_bit_length
|
69
|
-
|
69
|
+
cursor += ((param.bit_position or 0) + param_bit_length + 7) // 8
|
70
|
+
byte_length = max(byte_length, cursor)
|
70
71
|
|
71
72
|
# Round up to account for padding bits (all structures are
|
72
73
|
# byte aligned)
|
73
|
-
return
|
74
|
+
return byte_length * 8
|
74
75
|
|
75
76
|
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
|
76
|
-
|
77
|
-
|
78
|
-
bytearray(prefix), parameter_values={}, triggering_request=request_prefix)
|
77
|
+
encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
|
78
|
+
|
79
79
|
for param in self.parameters:
|
80
|
-
if isinstance(param, (
|
81
|
-
|
82
|
-
|
80
|
+
if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
|
81
|
+
isinstance(param, (CodedConstParameter, NrcConstParameter, PhysicalConstantParameter)):
|
82
|
+
param.encode_into_pdu(physical_value=None, encode_state=encode_state)
|
83
83
|
else:
|
84
84
|
break
|
85
85
|
return encode_state.coded_message
|
@@ -116,58 +116,101 @@ class BasicStructure(ComplexDop):
|
|
116
116
|
|
117
117
|
print(parameter_info(self.free_parameters), end="")
|
118
118
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
def _validate_coded_message_size(self, coded_byte_len: int) -> None:
|
120
|
+
|
121
|
+
if self.byte_size is not None:
|
122
|
+
# We definitely broke something if we didn't respect the explicit byte_size
|
123
|
+
if self.byte_size != coded_byte_len:
|
124
|
+
warnings.warn(
|
125
|
+
"Verification of coded message failed: Incorrect size.",
|
126
|
+
OdxWarning,
|
127
|
+
stacklevel=1)
|
128
|
+
|
129
|
+
return
|
130
|
+
|
131
|
+
bit_length = self.get_static_bit_length()
|
123
132
|
|
124
|
-
if
|
125
|
-
|
133
|
+
if bit_length is None:
|
134
|
+
# Nothing to check
|
135
|
+
return
|
136
|
+
|
137
|
+
if coded_byte_len * 8 != bit_length:
|
138
|
+
# We may have broke something
|
139
|
+
# but it could be that bit_length was mis calculated and not the actual bytes are wrong
|
140
|
+
# Could happen with overlapping parameters and parameters with gaps
|
141
|
+
warnings.warn(
|
142
|
+
"Verification of coded message possibly failed: Size may be incorrect.",
|
143
|
+
OdxWarning,
|
144
|
+
stacklevel=1)
|
145
|
+
|
146
|
+
@override
|
147
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
148
|
+
encode_state: EncodeState) -> None:
|
149
|
+
if not isinstance(physical_value, dict):
|
150
|
+
odxraise(
|
126
151
|
f"Expected a dictionary for the values of structure {self.short_name}, "
|
127
|
-
f"got {type(
|
152
|
+
f"got {type(physical_value).__name__}", EncodeError)
|
153
|
+
elif encode_state.cursor_bit_position != 0:
|
154
|
+
odxraise(
|
155
|
+
f"Structures must be byte aligned, but "
|
156
|
+
f"{self.short_name} requested to be at bit position "
|
157
|
+
f"{encode_state.cursor_bit_position}", EncodeError)
|
158
|
+
encode_state.bit_position = 0
|
159
|
+
|
160
|
+
orig_cursor = encode_state.cursor_byte_position
|
161
|
+
orig_origin = encode_state.origin_byte_position
|
162
|
+
encode_state.origin_byte_position = encode_state.cursor_byte_position
|
163
|
+
|
164
|
+
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
165
|
+
encode_state.is_end_of_pdu = False
|
128
166
|
|
129
167
|
# in strict mode, ensure that no values for unknown parameters are specified.
|
130
168
|
if strict_mode:
|
131
|
-
param_names =
|
132
|
-
for
|
133
|
-
if
|
134
|
-
odxraise(f"Value for unknown parameter '{
|
135
|
-
|
136
|
-
encode_state = EncodeState(
|
137
|
-
bytearray(),
|
138
|
-
dict(param_value),
|
139
|
-
triggering_request=triggering_coded_request,
|
140
|
-
is_end_of_pdu=False,
|
141
|
-
)
|
169
|
+
param_names = {param.short_name for param in self.parameters}
|
170
|
+
for param_value_name in physical_value:
|
171
|
+
if param_value_name not in param_names:
|
172
|
+
odxraise(f"Value for unknown parameter '{param_value_name}' specified "
|
173
|
+
f"for structure {self.short_name}")
|
142
174
|
|
143
175
|
for param in self.parameters:
|
144
|
-
if param == self.parameters[-1]:
|
145
|
-
# The last parameter is at the end of
|
146
|
-
# structure itself is at the end of the
|
147
|
-
# This assumes that the last parameter
|
148
|
-
# the ODX is located last in the PDU...
|
149
|
-
encode_state.is_end_of_pdu =
|
150
|
-
|
151
|
-
if isinstance(
|
152
|
-
|
153
|
-
|
154
|
-
# This is a hack to always encode a dummy value for
|
155
|
-
# length- and table keys. since these can be specified
|
176
|
+
if id(param) == id(self.parameters[-1]):
|
177
|
+
# The last parameter of the structure is at the end of
|
178
|
+
# the PDU if the structure itself is at the end of the
|
179
|
+
# PDU. TODO: This assumes that the last parameter
|
180
|
+
# specified in the ODX is located last in the PDU...
|
181
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
182
|
+
|
183
|
+
if isinstance(param, (LengthKeyParameter, TableKeyParameter)):
|
184
|
+
# At this point, we encode a placeholder value for length-
|
185
|
+
# and table keys, since these can be specified
|
156
186
|
# implicitly (i.e., by means of parameters that use
|
157
|
-
# these keys)
|
187
|
+
# these keys). To avoid getting an "overlapping
|
158
188
|
# parameter" warning, we must encode a value of zero
|
159
189
|
# into the PDU here and add the real value of the
|
160
|
-
# parameter in a post
|
161
|
-
|
162
|
-
|
163
|
-
|
190
|
+
# parameter in a post-processing step.
|
191
|
+
param.encode_placeholder_into_pdu(
|
192
|
+
physical_value=physical_value.get(param.short_name), encode_state=encode_state)
|
193
|
+
|
164
194
|
continue
|
165
195
|
|
166
|
-
|
196
|
+
if param.is_required and param.short_name not in physical_value:
|
197
|
+
odxraise(f"No value for required parameter {param.short_name} specified",
|
198
|
+
EncodeError)
|
167
199
|
|
168
|
-
|
169
|
-
|
170
|
-
|
200
|
+
param.encode_into_pdu(
|
201
|
+
physical_value=physical_value.get(param.short_name), encode_state=encode_state)
|
202
|
+
|
203
|
+
encode_state.is_end_of_pdu = False
|
204
|
+
if self.byte_size is not None:
|
205
|
+
actual_len = encode_state.cursor_byte_position - encode_state.origin_byte_position
|
206
|
+
if actual_len < self.byte_size:
|
207
|
+
# Padding bytes needed. We add an empty object at the
|
208
|
+
# position directly after the structure and let
|
209
|
+
# EncodeState add the padding as needed.
|
210
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_size
|
211
|
+
# Padding bytes needed. these count as "used".
|
212
|
+
encode_state.coded_message += b"\x00" * (self.byte_size - actual_len)
|
213
|
+
encode_state.used_mask += b"\xff" * (self.byte_size - actual_len)
|
171
214
|
|
172
215
|
# encode the length- and table keys. This cannot be done above
|
173
216
|
# because we allow these to be defined implicitly (i.e. they
|
@@ -177,56 +220,17 @@ class BasicStructure(ComplexDop):
|
|
177
220
|
# the current parameter is neither a length- nor a table key
|
178
221
|
continue
|
179
222
|
|
180
|
-
# Encode the key parameter into the message
|
181
|
-
|
223
|
+
# Encode the value of the key parameter into the message
|
224
|
+
param.encode_value_into_pdu(encode_state=encode_state)
|
182
225
|
|
183
226
|
# Assert that length is as expected
|
184
|
-
self.
|
185
|
-
|
186
|
-
return bytearray(encode_state.coded_message)
|
187
|
-
|
188
|
-
def _validate_coded_message(self, coded_message: bytes) -> None:
|
189
|
-
|
190
|
-
if self.byte_size is not None:
|
191
|
-
# We definitely broke something if we didn't respect the explicit byte_size
|
192
|
-
odxassert(
|
193
|
-
len(coded_message) == self.byte_size,
|
194
|
-
"Verification of coded message {coded_message.hex()} failed: Incorrect size.")
|
195
|
-
# No need to check further
|
196
|
-
return
|
197
|
-
|
198
|
-
bit_length = self.get_static_bit_length()
|
199
|
-
|
200
|
-
if bit_length is None:
|
201
|
-
# Nothing to check
|
202
|
-
return
|
227
|
+
self._validate_coded_message_size(encode_state.cursor_byte_position -
|
228
|
+
encode_state.origin_byte_position)
|
203
229
|
|
204
|
-
|
205
|
-
|
206
|
-
# but it could be that bit_length was mis calculated and not the actual bytes are wrong
|
207
|
-
# Could happen with overlapping parameters and parameters with gaps
|
208
|
-
warnings.warn(
|
209
|
-
f"Verification of coded message '{coded_message.hex()}' possibly failed: Size may be incorrect.",
|
210
|
-
OdxWarning,
|
211
|
-
stacklevel=1)
|
212
|
-
|
213
|
-
def convert_physical_to_bytes(self,
|
214
|
-
physical_value: ParameterValue,
|
215
|
-
encode_state: EncodeState,
|
216
|
-
bit_position: int = 0) -> bytes:
|
217
|
-
if not isinstance(physical_value, dict):
|
218
|
-
raise EncodeError(
|
219
|
-
f"Expected a dictionary for the values of structure {self.short_name}, "
|
220
|
-
f"got {type(physical_value)}")
|
221
|
-
if bit_position != 0:
|
222
|
-
raise EncodeError("Structures must be aligned, i.e. bit_position=0, but "
|
223
|
-
f"{self.short_name} was passed the bit position {bit_position}")
|
224
|
-
return self.convert_physical_to_internal(
|
225
|
-
physical_value,
|
226
|
-
triggering_coded_request=encode_state.triggering_request,
|
227
|
-
is_end_of_pdu=encode_state.is_end_of_pdu,
|
228
|
-
)
|
230
|
+
encode_state.origin_byte_position = orig_origin
|
231
|
+
encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
|
229
232
|
|
233
|
+
@override
|
230
234
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
231
235
|
# move the origin since positions specified by sub-parameters of
|
232
236
|
# structures are relative to the beginning of the structure object.
|
@@ -244,19 +248,6 @@ class BasicStructure(ComplexDop):
|
|
244
248
|
|
245
249
|
return result
|
246
250
|
|
247
|
-
def encode(self, coded_request: Optional[bytes] = None, **params: ParameterValue) -> bytes:
|
248
|
-
"""
|
249
|
-
Composes an UDS message as bytes for this service.
|
250
|
-
Parameters:
|
251
|
-
----------
|
252
|
-
coded_request: bytes
|
253
|
-
coded request (only needed when encoding a response)
|
254
|
-
params: dict
|
255
|
-
Parameters of the RPC as mapping from SHORT-NAME of the parameter to the value
|
256
|
-
"""
|
257
|
-
return self.convert_physical_to_internal(
|
258
|
-
params, triggering_coded_request=coded_request, is_end_of_pdu=True)
|
259
|
-
|
260
251
|
def decode(self, message: bytes) -> ParameterValueDict:
|
261
252
|
decode_state = DecodeState(coded_message=message)
|
262
253
|
param_values = self.decode_from_pdu(decode_state)
|
@@ -316,4 +307,7 @@ class BasicStructure(ComplexDop):
|
|
316
307
|
super()._resolve_snrefs(diag_layer)
|
317
308
|
|
318
309
|
for param in self.parameters:
|
319
|
-
param
|
310
|
+
if isinstance(param, TableStructParameter):
|
311
|
+
param._table_struct_resolve_snrefs(diag_layer, param_list=self.parameters)
|
312
|
+
else:
|
313
|
+
param._resolve_snrefs(diag_layer)
|
odxtools/dataobjectproperty.py
CHANGED
@@ -120,10 +120,7 @@ class DataObjectProperty(DopBase):
|
|
120
120
|
|
121
121
|
return self.compu_method.convert_physical_to_internal(physical_value)
|
122
122
|
|
123
|
-
def
|
124
|
-
physical_value: Any,
|
125
|
-
encode_state: EncodeState,
|
126
|
-
bit_position: int = 0) -> bytes:
|
123
|
+
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
127
124
|
"""
|
128
125
|
Convert a physical representation of a parameter to a string bytes that can be send over the wire
|
129
126
|
"""
|
@@ -132,9 +129,8 @@ class DataObjectProperty(DopBase):
|
|
132
129
|
f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
|
133
130
|
f" is not a valid.")
|
134
131
|
|
135
|
-
|
136
|
-
|
137
|
-
internal_val, encode_state, bit_position=bit_position)
|
132
|
+
internal_value = self.convert_physical_to_internal(physical_value)
|
133
|
+
self.diag_coded_type.encode_into_pdu(internal_value, encode_state)
|
138
134
|
|
139
135
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
140
136
|
"""
|
odxtools/decodestate.py
CHANGED
@@ -15,18 +15,6 @@ except ImportError:
|
|
15
15
|
if TYPE_CHECKING:
|
16
16
|
from .tablerow import TableRow
|
17
17
|
|
18
|
-
# format specifiers for the data type using the bitstruct module
|
19
|
-
ODX_TYPE_TO_FORMAT_LETTER = {
|
20
|
-
DataType.A_INT32: "s",
|
21
|
-
DataType.A_UINT32: "u",
|
22
|
-
DataType.A_FLOAT32: "f",
|
23
|
-
DataType.A_FLOAT64: "f",
|
24
|
-
DataType.A_BYTEFIELD: "r",
|
25
|
-
DataType.A_UNICODE2STRING: "r", # UTF-16 strings must be converted explicitly
|
26
|
-
DataType.A_ASCIISTRING: "r",
|
27
|
-
DataType.A_UTF8STRING: "r",
|
28
|
-
}
|
29
|
-
|
30
18
|
|
31
19
|
@dataclass
|
32
20
|
class DecodeState:
|
@@ -94,7 +82,7 @@ class DecodeState:
|
|
94
82
|
|
95
83
|
padding = (8 - (bit_length + self.cursor_bit_position) % 8) % 8
|
96
84
|
internal_value, = bitstruct.unpack_from(
|
97
|
-
f"{
|
85
|
+
f"{base_data_type.bitstruct_format_letter}{bit_length}",
|
98
86
|
extracted_bytes,
|
99
87
|
offset=padding)
|
100
88
|
|
odxtools/diagcodedtype.py
CHANGED
@@ -1,19 +1,14 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import abc
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
|
5
5
|
|
6
|
-
from .decodestate import
|
6
|
+
from .decodestate import DecodeState
|
7
7
|
from .encodestate import EncodeState
|
8
|
-
from .exceptions import
|
8
|
+
from .exceptions import odxassert, odxraise
|
9
9
|
from .odxlink import OdxLinkDatabase, OdxLinkId
|
10
10
|
from .odxtypes import AtomicOdxType, DataType
|
11
11
|
|
12
|
-
try:
|
13
|
-
import bitstruct.c as bitstruct
|
14
|
-
except ImportError:
|
15
|
-
import bitstruct
|
16
|
-
|
17
12
|
if TYPE_CHECKING:
|
18
13
|
from .diaglayer import DiagLayer
|
19
14
|
|
@@ -56,88 +51,6 @@ class DiagCodedType(abc.ABC):
|
|
56
51
|
def is_highlow_byte_order(self) -> bool:
|
57
52
|
return self.is_highlow_byte_order_raw in [None, True]
|
58
53
|
|
59
|
-
@staticmethod
|
60
|
-
def _encode_internal_value(
|
61
|
-
internal_value: AtomicOdxType,
|
62
|
-
bit_position: int,
|
63
|
-
bit_length: int,
|
64
|
-
base_data_type: DataType,
|
65
|
-
is_highlow_byte_order: bool,
|
66
|
-
) -> bytes:
|
67
|
-
"""Convert the internal_value to bytes."""
|
68
|
-
# Check that bytes and strings actually fit into the bit length
|
69
|
-
if base_data_type == DataType.A_BYTEFIELD:
|
70
|
-
if isinstance(internal_value, bytearray):
|
71
|
-
internal_value = bytes(internal_value)
|
72
|
-
if not isinstance(internal_value, bytes):
|
73
|
-
odxraise()
|
74
|
-
if 8 * len(internal_value) > bit_length:
|
75
|
-
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
|
76
|
-
f"({len(internal_value)} bytes)."
|
77
|
-
f" The maximum length is {bit_length//8}.")
|
78
|
-
if base_data_type == DataType.A_ASCIISTRING:
|
79
|
-
if not isinstance(internal_value, str):
|
80
|
-
odxraise()
|
81
|
-
|
82
|
-
# The spec says ASCII, meaning only byte values 0-127.
|
83
|
-
# But in practice, vendors use iso-8859-1, aka latin-1
|
84
|
-
# reason being iso-8859-1 never fails since it has a valid
|
85
|
-
# character mapping for every possible byte sequence.
|
86
|
-
internal_value = internal_value.encode("iso-8859-1")
|
87
|
-
|
88
|
-
if 8 * len(internal_value) > bit_length:
|
89
|
-
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
90
|
-
f" The maximum number of characters is {bit_length//8}.")
|
91
|
-
elif base_data_type == DataType.A_UTF8STRING:
|
92
|
-
if not isinstance(internal_value, str):
|
93
|
-
odxraise()
|
94
|
-
|
95
|
-
internal_value = internal_value.encode("utf-8")
|
96
|
-
|
97
|
-
if 8 * len(internal_value) > bit_length:
|
98
|
-
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
99
|
-
f" The maximum number of bytes is {bit_length//8}.")
|
100
|
-
|
101
|
-
elif base_data_type == DataType.A_UNICODE2STRING:
|
102
|
-
if not isinstance(internal_value, str):
|
103
|
-
odxraise()
|
104
|
-
|
105
|
-
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
106
|
-
internal_value = internal_value.encode(text_encoding)
|
107
|
-
|
108
|
-
if 8 * len(internal_value) > bit_length:
|
109
|
-
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
110
|
-
f" The maximum number of characters is {bit_length//16}.")
|
111
|
-
|
112
|
-
# If the bit length is zero, return empty bytes
|
113
|
-
if bit_length == 0:
|
114
|
-
if (base_data_type.value in [
|
115
|
-
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
116
|
-
] and base_data_type.value != 0):
|
117
|
-
raise EncodeError(
|
118
|
-
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
|
119
|
-
return b''
|
120
|
-
|
121
|
-
char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
|
122
|
-
padding = (8 - ((bit_length + bit_position) % 8)) % 8
|
123
|
-
odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0),
|
124
|
-
f"Incorrect padding {padding}")
|
125
|
-
left_pad = f"p{padding}" if padding > 0 else ""
|
126
|
-
|
127
|
-
# actually encode the value
|
128
|
-
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)
|
129
|
-
|
130
|
-
# apply byte order for numeric objects
|
131
|
-
if not is_highlow_byte_order and base_data_type in [
|
132
|
-
DataType.A_INT32,
|
133
|
-
DataType.A_UINT32,
|
134
|
-
DataType.A_FLOAT32,
|
135
|
-
DataType.A_FLOAT64,
|
136
|
-
]:
|
137
|
-
coded = coded[::-1]
|
138
|
-
|
139
|
-
return cast(bytes, coded)
|
140
|
-
|
141
54
|
def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
|
142
55
|
"""Helper method to get the minimal byte length.
|
143
56
|
(needed for LeadingLength- and MinMaxLengthType)
|
@@ -160,11 +73,10 @@ class DiagCodedType(abc.ABC):
|
|
160
73
|
odxassert(
|
161
74
|
byte_length % 2 == 0, f"The bit length of A_UNICODE2STRING must"
|
162
75
|
f" be a multiple of 16 but is {8*byte_length}")
|
76
|
+
|
163
77
|
return byte_length
|
164
78
|
|
165
|
-
|
166
|
-
def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
|
167
|
-
bit_position: int) -> bytes:
|
79
|
+
def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
|
168
80
|
"""Encode the internal value.
|
169
81
|
|
170
82
|
Parameters
|
@@ -177,9 +89,9 @@ class DiagCodedType(abc.ABC):
|
|
177
89
|
mapping from ID (of the length key) to bit length
|
178
90
|
(only needed for ParamLengthInfoType)
|
179
91
|
"""
|
180
|
-
|
92
|
+
raise NotImplementedError(
|
93
|
+
f".encode_into_pdu() is not implemented by the class {type(self).__name__}")
|
181
94
|
|
182
|
-
@abc.abstractmethod
|
183
95
|
def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
|
184
96
|
"""Decode the parameter value from the coded message.
|
185
97
|
|
@@ -195,4 +107,5 @@ class DiagCodedType(abc.ABC):
|
|
195
107
|
int
|
196
108
|
the next byte position after the extracted parameter
|
197
109
|
"""
|
198
|
-
|
110
|
+
raise NotImplementedError(
|
111
|
+
f".decode_from_pdu() is not implemented by the class {type(self).__name__}")
|
odxtools/diaglayer.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import re
|
3
3
|
import warnings
|
4
|
+
from copy import copy
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from functools import cached_property
|
6
7
|
from itertools import chain
|
@@ -76,6 +77,50 @@ class DiagLayer:
|
|
76
77
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
77
78
|
"""Recursively resolve all references."""
|
78
79
|
|
80
|
+
# deal with the import references: these basically extend the
|
81
|
+
# pool of objects that are referenceable without having to
|
82
|
+
# explicitly specify the DOCREF attribute in the
|
83
|
+
# reference. This mechanism can thus be seen as a kind of
|
84
|
+
# "poor man's inheritance".
|
85
|
+
if self.import_refs:
|
86
|
+
imported_links: Dict[OdxLinkId, Any] = {}
|
87
|
+
for import_ref in self.import_refs:
|
88
|
+
imported_dl = odxlinks.resolve(import_ref, DiagLayer)
|
89
|
+
|
90
|
+
odxassert(
|
91
|
+
imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
|
92
|
+
f"Tried to import references from diagnostic layer "
|
93
|
+
f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
|
94
|
+
f"Only ECU-SHARED-DATA layers may be referenced using the "
|
95
|
+
f"IMPORT-REF mechanism")
|
96
|
+
|
97
|
+
# TODO: ensure that the imported diagnostic layer has
|
98
|
+
# not been referenced in any PARENT-REF of the current
|
99
|
+
# layer or any of its parents.
|
100
|
+
|
101
|
+
# TODO: detect and complain about cyclic IMPORT-REFs
|
102
|
+
|
103
|
+
# TODO (?): detect conflicts with locally-defined
|
104
|
+
# objects
|
105
|
+
|
106
|
+
imported_dl_links = imported_dl._build_odxlinks()
|
107
|
+
for link_id, obj in imported_dl_links.items():
|
108
|
+
# the imported objects shall behave as if they
|
109
|
+
# were defined by the importing layer. IOW, they
|
110
|
+
# must be visible in the same document fragments.
|
111
|
+
link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
|
112
|
+
imported_links[link_id] = obj
|
113
|
+
|
114
|
+
# We need to copy the odxlink database here since this
|
115
|
+
# function must not modify its argument because the
|
116
|
+
# imported references only apply within this specific
|
117
|
+
# diagnostic layer
|
118
|
+
extended_odxlinks = copy(odxlinks)
|
119
|
+
extended_odxlinks.update(imported_links)
|
120
|
+
|
121
|
+
self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
|
122
|
+
return
|
123
|
+
|
79
124
|
self.diag_layer_raw._resolve_odxlinks(odxlinks)
|
80
125
|
|
81
126
|
def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
|
odxtools/diagservice.py
CHANGED
@@ -215,13 +215,9 @@ class DiagService(DiagComm):
|
|
215
215
|
coding_object=coding_object,
|
216
216
|
param_dict=param_dict)
|
217
217
|
|
218
|
-
def encode_request(self, **
|
218
|
+
def encode_request(self, **kwargs: ParameterValue) -> bytes:
|
219
219
|
"""
|
220
|
-
Composes an UDS request
|
221
|
-
Parameters:
|
222
|
-
----------
|
223
|
-
params: dict
|
224
|
-
Parameters of the RPC as mapping from SHORT-NAME of the parameter to the physical value
|
220
|
+
Composes an UDS request an array of bytes for this service.
|
225
221
|
"""
|
226
222
|
# make sure that all parameters which are required for
|
227
223
|
# encoding are specified (parameters which have a default are
|
@@ -230,31 +226,31 @@ class DiagService(DiagComm):
|
|
230
226
|
return b''
|
231
227
|
|
232
228
|
missing_params = {x.short_name
|
233
|
-
for x in self.request.required_parameters}.difference(
|
229
|
+
for x in self.request.required_parameters}.difference(kwargs.keys())
|
234
230
|
odxassert(
|
235
231
|
len(missing_params) == 0, f"The parameters {missing_params} are required but missing!")
|
236
232
|
|
237
233
|
# make sure that no unknown parameters are specified
|
238
234
|
rq_all_param_names = {x.short_name for x in self.request.parameters}
|
239
235
|
odxassert(
|
240
|
-
set(
|
241
|
-
f"Unknown parameters specified for encoding: {
|
236
|
+
set(kwargs.keys()).issubset(rq_all_param_names),
|
237
|
+
f"Unknown parameters specified for encoding: {kwargs.keys()}, "
|
242
238
|
f"known parameters are: {rq_all_param_names}")
|
243
|
-
return self.request.encode(
|
239
|
+
return self.request.encode(**kwargs)
|
244
240
|
|
245
241
|
def encode_positive_response(self,
|
246
242
|
coded_request: bytes,
|
247
243
|
response_index: int = 0,
|
248
|
-
**
|
244
|
+
**kwargs: ParameterValue) -> bytes:
|
249
245
|
# TODO: Should the user decide the positive response or what are the differences?
|
250
|
-
return self.positive_responses[response_index].encode(coded_request, **
|
246
|
+
return self.positive_responses[response_index].encode(coded_request, **kwargs)
|
251
247
|
|
252
248
|
def encode_negative_response(self,
|
253
249
|
coded_request: bytes,
|
254
250
|
response_index: int = 0,
|
255
|
-
**
|
256
|
-
return self.negative_responses[response_index].encode(coded_request, **
|
251
|
+
**kwargs: ParameterValue) -> bytes:
|
252
|
+
return self.negative_responses[response_index].encode(coded_request, **kwargs)
|
257
253
|
|
258
|
-
def __call__(self, **
|
254
|
+
def __call__(self, **kwargs: ParameterValue) -> bytes:
|
259
255
|
"""Encode a request."""
|
260
|
-
return self.encode_request(**
|
256
|
+
return self.encode_request(**kwargs)
|
odxtools/dopbase.py
CHANGED
@@ -21,7 +21,8 @@ if TYPE_CHECKING:
|
|
21
21
|
class DopBase(IdentifiableElement):
|
22
22
|
"""Base class for all DOPs.
|
23
23
|
|
24
|
-
Any class that a parameter can reference via a DOP-REF should
|
24
|
+
Any class that a parameter can reference via a DOP-REF should
|
25
|
+
inherit from this class.
|
25
26
|
"""
|
26
27
|
|
27
28
|
admin_data: Optional[AdminData]
|
@@ -60,15 +61,11 @@ class DopBase(IdentifiableElement):
|
|
60
61
|
return None
|
61
62
|
|
62
63
|
def is_valid_physical_value(self, physical_value: ParameterValue) -> bool:
|
63
|
-
"""Determine if a phyical value can be handled by the DOP
|
64
|
-
"""
|
64
|
+
"""Determine if a phyical value can be handled by the DOP"""
|
65
65
|
raise NotImplementedError
|
66
66
|
|
67
|
-
def
|
68
|
-
|
69
|
-
encode_state: EncodeState,
|
70
|
-
bit_position: int = 0) -> bytes:
|
71
|
-
"""Convert the physical value into bytes."""
|
67
|
+
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
68
|
+
"""Convert the physical value to bytes and emplace them into a PDU."""
|
72
69
|
raise NotImplementedError
|
73
70
|
|
74
71
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|