odxtools 6.7.1__py3-none-any.whl → 7.1.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 +102 -112
- odxtools/dataobjectproperty.py +3 -7
- odxtools/decodestate.py +1 -13
- odxtools/diagcodedtype.py +9 -96
- odxtools/diagcomm.py +11 -3
- odxtools/diagdatadictionaryspec.py +14 -14
- odxtools/diaglayer.py +52 -1
- odxtools/diaglayerraw.py +13 -7
- odxtools/diagservice.py +12 -16
- odxtools/dopbase.py +5 -8
- odxtools/dtcdop.py +21 -19
- odxtools/dynamicendmarkerfield.py +119 -0
- odxtools/dynamiclengthfield.py +39 -29
- odxtools/dynenddopref.py +38 -0
- odxtools/encodestate.py +188 -23
- odxtools/endofpdufield.py +33 -18
- odxtools/environmentdata.py +8 -1
- odxtools/environmentdatadescription.py +21 -15
- odxtools/field.py +4 -3
- odxtools/leadinglengthinfotype.py +25 -12
- odxtools/matchingparameter.py +2 -2
- odxtools/minmaxlengthtype.py +36 -26
- odxtools/multiplexer.py +42 -23
- odxtools/multiplexercase.py +3 -3
- odxtools/multiplexerdefaultcase.py +7 -3
- odxtools/nameditemlist.py +14 -0
- odxtools/odxlink.py +38 -4
- odxtools/odxtypes.py +20 -2
- odxtools/parameterinfo.py +126 -40
- odxtools/parameters/codedconstparameter.py +17 -13
- odxtools/parameters/dynamicparameter.py +5 -4
- odxtools/parameters/lengthkeyparameter.py +66 -17
- odxtools/parameters/matchingrequestparameter.py +23 -11
- odxtools/parameters/nrcconstparameter.py +42 -22
- odxtools/parameters/parameter.py +35 -42
- odxtools/parameters/parameterwithdop.py +15 -22
- odxtools/parameters/physicalconstantparameter.py +16 -16
- odxtools/parameters/reservedparameter.py +5 -2
- odxtools/parameters/systemparameter.py +3 -2
- odxtools/parameters/tableentryparameter.py +3 -2
- odxtools/parameters/tablekeyparameter.py +88 -39
- odxtools/parameters/tablestructparameter.py +45 -44
- odxtools/parameters/valueparameter.py +16 -17
- odxtools/paramlengthinfotype.py +30 -22
- odxtools/request.py +9 -0
- odxtools/response.py +5 -13
- odxtools/standardlengthtype.py +51 -13
- odxtools/statechart.py +5 -9
- odxtools/statetransition.py +3 -8
- odxtools/staticfield.py +30 -20
- odxtools/tablerow.py +5 -3
- odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
- odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
- odxtools/version.py +2 -2
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/METADATA +1 -1
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/RECORD +60 -57
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/LICENSE +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/WHEEL +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/top_level.txt +0 -0
odxtools/encodestate.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import warnings
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import
|
4
|
+
from typing import Dict, Optional, SupportsBytes
|
5
5
|
|
6
|
-
from .exceptions import OdxWarning
|
6
|
+
from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
|
7
|
+
from .odxtypes import AtomicOdxType, DataType
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
try:
|
10
|
+
import bitstruct.c as bitstruct
|
11
|
+
except ImportError:
|
12
|
+
import bitstruct
|
10
13
|
|
11
14
|
|
12
15
|
@dataclass
|
@@ -15,10 +18,23 @@ class EncodeState:
|
|
15
18
|
"""
|
16
19
|
|
17
20
|
#: payload that has been constructed so far
|
18
|
-
coded_message: bytearray
|
21
|
+
coded_message: bytearray = field(default_factory=bytearray)
|
19
22
|
|
20
|
-
#:
|
21
|
-
|
23
|
+
#: the bits of the payload that are used
|
24
|
+
used_mask: bytearray = field(default_factory=bytearray)
|
25
|
+
|
26
|
+
#: The absolute position in bytes from the beginning of the PDU to
|
27
|
+
#: which relative positions refer to, e.g., the beginning of the
|
28
|
+
#: structure.
|
29
|
+
origin_byte_position: int = 0
|
30
|
+
|
31
|
+
#: The absolute position in bytes from the beginning of the PDU
|
32
|
+
#: where the next object ought to be placed into the PDU
|
33
|
+
cursor_byte_position: int = 0
|
34
|
+
|
35
|
+
#: The bit position [0-7] where the next object ought to be
|
36
|
+
#: placed into the PDU
|
37
|
+
cursor_bit_position: int = 0
|
22
38
|
|
23
39
|
#: If encoding a response: request that triggered the response
|
24
40
|
triggering_request: Optional[bytes] = None
|
@@ -28,31 +44,180 @@ class EncodeState:
|
|
28
44
|
length_keys: Dict[str, int] = field(default_factory=dict)
|
29
45
|
|
30
46
|
#: Mapping from the short name of a table-key parameter to the
|
31
|
-
#: corresponding row of the table (specified by
|
32
|
-
|
47
|
+
#: short name of the corresponding row of the table (specified by
|
48
|
+
#: TableKeyParameter)
|
49
|
+
table_keys: Dict[str, str] = field(default_factory=dict)
|
50
|
+
|
51
|
+
#: The cursor position where a given length- or table key is located
|
52
|
+
#: in the PDU
|
53
|
+
key_pos: Dict[str, int] = field(default_factory=dict)
|
33
54
|
|
34
55
|
#: Flag whether we are currently the last parameter of the PDU
|
35
|
-
#: (needed for MinMaxLengthType)
|
36
|
-
is_end_of_pdu: bool =
|
56
|
+
#: (needed for MinMaxLengthType, EndOfPduField, etc.)
|
57
|
+
is_end_of_pdu: bool = True
|
58
|
+
|
59
|
+
def __post_init__(self) -> None:
|
60
|
+
# if a coded message has been specified, but no used_mask, we
|
61
|
+
# assume that all of the bits of the coded message are
|
62
|
+
# currently used.
|
63
|
+
if len(self.coded_message) > len(self.used_mask):
|
64
|
+
self.used_mask += b'\xff' * (len(self.coded_message) - len(self.used_mask))
|
65
|
+
if len(self.coded_message) < len(self.used_mask):
|
66
|
+
odxraise(f"The specified bit mask 0x{self.used_mask.hex()} for used bits "
|
67
|
+
f"is not suitable for representing the coded_message "
|
68
|
+
f"0x{self.coded_message.hex()}")
|
69
|
+
self.used_mask = self.used_mask[:len(self.coded_message)]
|
70
|
+
|
71
|
+
def emplace_atomic_value(
|
72
|
+
self,
|
73
|
+
*,
|
74
|
+
internal_value: AtomicOdxType,
|
75
|
+
bit_length: int,
|
76
|
+
base_data_type: DataType,
|
77
|
+
is_highlow_byte_order: bool,
|
78
|
+
used_mask: Optional[bytes],
|
79
|
+
) -> None:
|
80
|
+
"""Convert the internal_value to bytes and emplace this into the PDU"""
|
81
|
+
|
82
|
+
raw_value: AtomicOdxType
|
83
|
+
|
84
|
+
# Check that bytes and strings actually fit into the bit length
|
85
|
+
if base_data_type == DataType.A_BYTEFIELD:
|
86
|
+
if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
|
87
|
+
odxraise()
|
88
|
+
if 8 * len(internal_value) > bit_length:
|
89
|
+
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
|
90
|
+
f"({len(internal_value)} bytes)."
|
91
|
+
f" The maximum length is {bit_length//8}.")
|
92
|
+
raw_value = bytes(internal_value)
|
93
|
+
elif base_data_type == DataType.A_ASCIISTRING:
|
94
|
+
if not isinstance(internal_value, str):
|
95
|
+
odxraise()
|
96
|
+
|
97
|
+
# The spec says ASCII, meaning only byte values 0-127.
|
98
|
+
# But in practice, vendors use iso-8859-1, aka latin-1
|
99
|
+
# reason being iso-8859-1 never fails since it has a valid
|
100
|
+
# character mapping for every possible byte sequence.
|
101
|
+
raw_value = internal_value.encode("iso-8859-1")
|
102
|
+
|
103
|
+
if 8 * len(raw_value) > bit_length:
|
104
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
105
|
+
f" The maximum number of characters is {bit_length//8}.")
|
106
|
+
elif base_data_type == DataType.A_UTF8STRING:
|
107
|
+
if not isinstance(internal_value, str):
|
108
|
+
odxraise()
|
109
|
+
|
110
|
+
raw_value = internal_value.encode("utf-8")
|
111
|
+
|
112
|
+
if 8 * len(raw_value) > bit_length:
|
113
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
114
|
+
f" The maximum number of bytes is {bit_length//8}.")
|
115
|
+
|
116
|
+
elif base_data_type == DataType.A_UNICODE2STRING:
|
117
|
+
if not isinstance(internal_value, str):
|
118
|
+
odxraise()
|
37
119
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
120
|
+
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
121
|
+
raw_value = internal_value.encode(text_encoding)
|
122
|
+
|
123
|
+
if 8 * len(raw_value) > bit_length:
|
124
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
125
|
+
f" The maximum number of characters is {bit_length//16}.")
|
126
|
+
else:
|
127
|
+
raw_value = internal_value
|
128
|
+
|
129
|
+
# If the bit length is zero, return empty bytes
|
130
|
+
if bit_length == 0:
|
131
|
+
if (base_data_type.value in [
|
132
|
+
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
133
|
+
] and base_data_type.value != 0):
|
134
|
+
odxraise(
|
135
|
+
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
|
136
|
+
EncodeError)
|
137
|
+
self.emplace_bytes(b'')
|
138
|
+
return
|
139
|
+
|
140
|
+
char = base_data_type.bitstruct_format_letter
|
141
|
+
padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
|
142
|
+
odxassert((0 <= padding and padding < 8 and
|
143
|
+
(padding + bit_length + self.cursor_bit_position) % 8 == 0),
|
144
|
+
f"Incorrect padding {padding}")
|
145
|
+
left_pad = f"p{padding}" if padding > 0 else ""
|
146
|
+
|
147
|
+
# actually encode the value
|
148
|
+
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
|
149
|
+
|
150
|
+
# create the raw mask of used bits for numeric objects
|
151
|
+
used_mask_raw = used_mask
|
152
|
+
if base_data_type in [DataType.A_INT32, DataType.A_UINT32
|
153
|
+
] and (self.cursor_bit_position != 0 or
|
154
|
+
(self.cursor_bit_position + bit_length) % 8 != 0):
|
155
|
+
if used_mask is None:
|
156
|
+
tmp = (1 << bit_length) - 1
|
157
|
+
else:
|
158
|
+
tmp = int.from_bytes(used_mask, "big")
|
159
|
+
tmp <<= self.cursor_bit_position
|
160
|
+
|
161
|
+
used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
|
162
|
+
|
163
|
+
# apply byte order to numeric objects
|
164
|
+
if not is_highlow_byte_order and base_data_type in [
|
165
|
+
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
166
|
+
]:
|
167
|
+
coded = coded[::-1]
|
168
|
+
|
169
|
+
if used_mask_raw is not None:
|
170
|
+
used_mask_raw = used_mask_raw[::-1]
|
171
|
+
|
172
|
+
self.cursor_bit_position = 0
|
173
|
+
self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
|
174
|
+
|
175
|
+
def emplace_bytes(self,
|
176
|
+
new_data: bytes,
|
177
|
+
obj_name: Optional[str] = None,
|
178
|
+
obj_used_mask: Optional[bytes] = None) -> None:
|
179
|
+
if self.cursor_bit_position != 0:
|
180
|
+
odxraise("EncodeState.emplace_bytes can only be called "
|
181
|
+
"for a bit position of 0!", RuntimeError)
|
182
|
+
|
183
|
+
pos = self.cursor_byte_position
|
44
184
|
|
45
185
|
# Make blob longer if necessary
|
46
186
|
min_length = pos + len(new_data)
|
47
187
|
if len(self.coded_message) < min_length:
|
48
|
-
|
188
|
+
pad = b'\x00' * (min_length - len(self.coded_message))
|
189
|
+
self.coded_message += pad
|
190
|
+
self.used_mask += pad
|
191
|
+
|
192
|
+
if obj_used_mask is None:
|
193
|
+
# Happy path for when no obj_used_mask has been
|
194
|
+
# specified. In this case we assume that all bits of the
|
195
|
+
# new data to be emplaced are used.
|
196
|
+
n = len(new_data)
|
49
197
|
|
50
|
-
|
51
|
-
# insert byte value
|
52
|
-
if self.coded_message[byte_idx_rpc] & new_data[byte_idx_val] != 0:
|
198
|
+
if self.used_mask[pos:pos + n] != b'\x00' * n:
|
53
199
|
warnings.warn(
|
54
|
-
f"
|
200
|
+
f"Overlapping objects detected in between bytes {pos} and "
|
201
|
+
f"{pos+n}",
|
55
202
|
OdxWarning,
|
56
203
|
stacklevel=1,
|
57
204
|
)
|
58
|
-
self.coded_message[
|
205
|
+
self.coded_message[pos:pos + n] = new_data
|
206
|
+
self.used_mask[pos:pos + n] = b'\xff' * n
|
207
|
+
else:
|
208
|
+
# insert data the hard way, i.e. we have to look at each
|
209
|
+
# individual byte to determine if it has already been used
|
210
|
+
# somewhere else (it would be nice if bytearrays supported
|
211
|
+
# bitwise operations!)
|
212
|
+
for i in range(len(new_data)):
|
213
|
+
if self.used_mask[pos + i] & obj_used_mask[i] != 0:
|
214
|
+
warnings.warn(
|
215
|
+
f"Overlapping objects detected at position {pos + i}",
|
216
|
+
OdxWarning,
|
217
|
+
stacklevel=1,
|
218
|
+
)
|
219
|
+
self.coded_message[pos + i] &= ~obj_used_mask[i]
|
220
|
+
self.coded_message[pos + i] |= new_data[i] & obj_used_mask[i]
|
221
|
+
self.used_mask[pos + i] |= obj_used_mask[i]
|
222
|
+
|
223
|
+
self.cursor_byte_position += len(new_data)
|
odxtools/endofpdufield.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional
|
3
|
+
from typing import List, Optional, Sequence
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from typing_extensions import override
|
7
|
+
|
6
8
|
from .decodestate import DecodeState
|
7
9
|
from .encodestate import EncodeState
|
8
10
|
from .exceptions import EncodeError, odxassert, odxraise
|
@@ -39,30 +41,40 @@ class EndOfPduField(Field):
|
|
39
41
|
|
40
42
|
return eopf
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
encode_state
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
"Is there an error in reading the .odx?", EncodeError)
|
52
|
-
if not isinstance(physical_value, list):
|
44
|
+
@override
|
45
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
46
|
+
encode_state: EncodeState) -> None:
|
47
|
+
odxassert(not encode_state.cursor_bit_position,
|
48
|
+
"No bit position can be specified for end-of-pdu fields!")
|
49
|
+
odxassert(encode_state.is_end_of_pdu,
|
50
|
+
"End-of-pdu fields can only be located at the end of PDUs!")
|
51
|
+
|
52
|
+
if not isinstance(physical_value, Sequence):
|
53
53
|
odxraise(
|
54
|
-
f"
|
55
|
-
f"
|
54
|
+
f"Invalid type {type(physical_value).__name__} of physical "
|
55
|
+
f"value for end-of-pdu field, expected a list", EncodeError)
|
56
|
+
return
|
57
|
+
|
58
|
+
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
59
|
+
encode_state.is_end_of_pdu = False
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return coded_message
|
61
|
+
for i, value in enumerate(physical_value):
|
62
|
+
if i == len(physical_value) - 1:
|
63
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
61
64
|
|
65
|
+
self.structure.encode_into_pdu(value, encode_state)
|
66
|
+
|
67
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
68
|
+
|
69
|
+
@override
|
62
70
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
63
71
|
odxassert(not decode_state.cursor_bit_position,
|
64
72
|
"No bit position can be specified for end-of-pdu fields!")
|
65
73
|
|
74
|
+
orig_origin = decode_state.origin_byte_position
|
75
|
+
orig_cursor = decode_state.cursor_byte_position
|
76
|
+
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
77
|
+
|
66
78
|
result: List[ParameterValue] = []
|
67
79
|
while decode_state.cursor_byte_position < len(decode_state.coded_message):
|
68
80
|
# ATTENTION: the ODX specification is very misleading
|
@@ -71,4 +83,7 @@ class EndOfPduField(Field):
|
|
71
83
|
# repeated are identical, not their values
|
72
84
|
result.append(self.structure.decode_from_pdu(decode_state))
|
73
85
|
|
86
|
+
decode_state.origin_byte_position = orig_origin
|
87
|
+
decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
|
88
|
+
|
74
89
|
return result
|
odxtools/environmentdata.py
CHANGED
@@ -11,7 +11,14 @@ from .utils import dataclass_fields_asdict
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class EnvironmentData(BasicStructure):
|
14
|
-
"""This class represents Environment Data that describes the
|
14
|
+
"""This class represents Environment Data that describes the
|
15
|
+
circumstances in which the error occurred.
|
16
|
+
|
17
|
+
This is one of the many multiplexer mechanisms specified by the
|
18
|
+
ODX standard, because an environment data parameter must only be
|
19
|
+
used if a DTC parameter has a certain set of values. (In this
|
20
|
+
sense, it is quite similar to NRC-CONST parameters.)
|
21
|
+
"""
|
15
22
|
|
16
23
|
all_value: Optional[bool]
|
17
24
|
dtc_values: List[int]
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from typing_extensions import override
|
7
|
+
|
6
8
|
from .complexdop import ComplexDop
|
7
9
|
from .decodestate import DecodeState
|
8
10
|
from .encodestate import EncodeState
|
9
11
|
from .environmentdata import EnvironmentData
|
10
|
-
from .exceptions import
|
12
|
+
from .exceptions import odxraise, odxrequire
|
11
13
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
12
14
|
from .odxtypes import ParameterValue
|
13
15
|
from .utils import dataclass_fields_asdict
|
@@ -18,8 +20,14 @@ if TYPE_CHECKING:
|
|
18
20
|
|
19
21
|
@dataclass
|
20
22
|
class EnvironmentDataDescription(ComplexDop):
|
21
|
-
"""This class represents
|
22
|
-
|
23
|
+
"""This class represents environment data descriptions
|
24
|
+
|
25
|
+
An environment data description provides a list of all environment
|
26
|
+
data objects that are potentially applicable to decode a given
|
27
|
+
response. (If a given environment data object is applicable
|
28
|
+
depends on the value of the DtcDOP that is associated with it.)
|
29
|
+
|
30
|
+
"""
|
23
31
|
|
24
32
|
# in ODX 2.0.0, ENV-DATAS seems to be a mandatory
|
25
33
|
# sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
|
@@ -86,23 +94,21 @@ class EnvironmentDataDescription(ComplexDop):
|
|
86
94
|
for ed in self.env_datas:
|
87
95
|
ed._resolve_snrefs(diag_layer)
|
88
96
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
"""Convert the physical value into bytes.
|
97
|
+
@override
|
98
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
99
|
+
encode_state: EncodeState) -> None:
|
100
|
+
"""Convert a physical value into bytes and emplace them into a PDU.
|
94
101
|
|
95
102
|
Since environmental data is supposed to never appear on the
|
96
103
|
wire, this method just raises an EncodeError exception.
|
97
104
|
"""
|
98
|
-
|
105
|
+
odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
|
99
106
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
"""Extract the bytes from the PDU and convert them to the physical value.
|
107
|
+
@override
|
108
|
+
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
109
|
+
"""Extract the bytes from a PDU and convert them to a physical value.
|
104
110
|
|
105
111
|
Since environmental data is supposed to never appear on the
|
106
112
|
wire, this method just raises an DecodeError exception.
|
107
113
|
"""
|
108
|
-
|
114
|
+
odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
|
odxtools/field.py
CHANGED
@@ -6,7 +6,7 @@ from .basicstructure import BasicStructure
|
|
6
6
|
from .complexdop import ComplexDop
|
7
7
|
from .environmentdatadescription import EnvironmentDataDescription
|
8
8
|
from .exceptions import odxassert, odxrequire
|
9
|
-
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
|
9
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef, resolve_snref
|
10
10
|
from .odxtypes import odxstr_to_bool
|
11
11
|
from .utils import dataclass_fields_asdict
|
12
12
|
|
@@ -84,8 +84,9 @@ class Field(ComplexDop):
|
|
84
84
|
"""Recursively resolve any short-name references"""
|
85
85
|
if self.structure_snref is not None:
|
86
86
|
structures = diag_layer.diag_data_dictionary_spec.structures
|
87
|
-
self._structure =
|
87
|
+
self._structure = resolve_snref(self.structure_snref, structures, BasicStructure)
|
88
88
|
|
89
89
|
if self.env_data_desc_snref is not None:
|
90
90
|
env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
|
91
|
-
self._env_data_desc =
|
91
|
+
self._env_data_desc = resolve_snref(self.env_data_desc_snref, env_data_descs,
|
92
|
+
EnvironmentDataDescription)
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from typing_extensions import override
|
4
6
|
|
5
7
|
from .decodestate import DecodeState
|
6
8
|
from .diagcodedtype import DctType, DiagCodedType
|
7
9
|
from .encodestate import EncodeState
|
8
|
-
from .exceptions import odxassert, odxraise
|
10
|
+
from .exceptions import EncodeError, odxassert, odxraise
|
9
11
|
from .odxtypes import AtomicOdxType, DataType
|
10
12
|
|
11
13
|
|
@@ -42,29 +44,40 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
42
44
|
# DCT is dynamic!
|
43
45
|
return None
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
+
@override
|
48
|
+
def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
|
49
|
+
|
50
|
+
if not isinstance(internal_value, (str, bytes)):
|
51
|
+
odxraise(
|
52
|
+
f"LEADING-LENGTH-INFO types can only be used for strings and byte fields, "
|
53
|
+
f"not {type(internal_value).__name__}", EncodeError)
|
54
|
+
return
|
47
55
|
|
48
56
|
byte_length = self._minimal_byte_length_of(internal_value)
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
used_mask = None
|
59
|
+
bit_pos = encode_state.cursor_bit_position
|
60
|
+
if encode_state.cursor_bit_position != 0 or (bit_pos + self.bit_length) % 8 != 0:
|
61
|
+
used_mask = (1 << self.bit_length) - 1
|
62
|
+
used_mask <<= bit_pos
|
63
|
+
|
64
|
+
encode_state.emplace_atomic_value(
|
65
|
+
internal_value=byte_length,
|
66
|
+
used_mask=None,
|
53
67
|
bit_length=self.bit_length,
|
54
68
|
base_data_type=DataType.A_UINT32,
|
55
69
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
56
70
|
)
|
57
71
|
|
58
|
-
|
59
|
-
internal_value,
|
60
|
-
|
72
|
+
encode_state.emplace_atomic_value(
|
73
|
+
internal_value=internal_value,
|
74
|
+
used_mask=None,
|
61
75
|
bit_length=8 * byte_length,
|
62
76
|
base_data_type=self.base_data_type,
|
63
77
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
64
78
|
)
|
65
79
|
|
66
|
-
|
67
|
-
|
80
|
+
@override
|
68
81
|
def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
|
69
82
|
|
70
83
|
# Extract length of the parameter value
|
odxtools/matchingparameter.py
CHANGED
@@ -32,8 +32,8 @@ class MatchingParameter:
|
|
32
32
|
doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
|
33
33
|
|
34
34
|
expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
|
35
|
-
|
36
|
-
diag_comm_snref = odxrequire(
|
35
|
+
diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
|
36
|
+
diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME"))
|
37
37
|
out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF")
|
38
38
|
out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF")
|
39
39
|
out_param_if = None
|
odxtools/minmaxlengthtype.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from typing import Optional
|
4
4
|
|
5
|
+
from typing_extensions import override
|
6
|
+
|
5
7
|
from .decodestate import DecodeState
|
6
8
|
from .diagcodedtype import DctType, DiagCodedType
|
7
9
|
from .encodestate import EncodeState
|
@@ -51,8 +53,9 @@ class MinMaxLengthType(DiagCodedType):
|
|
51
53
|
termination_sequence = bytes([0xFF, 0xFF])
|
52
54
|
return termination_sequence
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
+
@override
|
57
|
+
def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
|
58
|
+
|
56
59
|
if not isinstance(internal_value, (bytes, str)):
|
57
60
|
odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
|
58
61
|
EncodeError)
|
@@ -62,20 +65,24 @@ class MinMaxLengthType(DiagCodedType):
|
|
62
65
|
else:
|
63
66
|
data_length = len(internal_value)
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
orig_cursor = encode_state.cursor_byte_position
|
69
|
+
encode_state.emplace_atomic_value(
|
70
|
+
internal_value=internal_value,
|
71
|
+
used_mask=None,
|
72
|
+
bit_length=8 * data_length,
|
73
|
+
base_data_type=self.base_data_type,
|
74
|
+
is_highlow_byte_order=self.is_highlow_byte_order,
|
75
|
+
)
|
76
|
+
value_len = encode_state.cursor_byte_position - orig_cursor
|
73
77
|
|
74
78
|
# TODO: ensure that the termination delimiter is not
|
75
79
|
# encountered within the encoded value.
|
76
80
|
|
77
|
-
odxassert(
|
78
|
-
|
81
|
+
odxassert(
|
82
|
+
self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
|
83
|
+
"Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
|
84
|
+
"which is not located at the end of the PDU")
|
85
|
+
if encode_state.is_end_of_pdu or value_len == self.max_length:
|
79
86
|
# All termination types may be ended by the end of the PDU
|
80
87
|
# or once reaching the maximum length. In this case, we
|
81
88
|
# must not add the termination sequence
|
@@ -85,20 +92,23 @@ class MinMaxLengthType(DiagCodedType):
|
|
85
92
|
|
86
93
|
# ensure that we don't try to encode an odd-length
|
87
94
|
# value when using a two-byte terminator
|
88
|
-
odxassert(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
95
|
+
odxassert(value_len % len(termination_sequence) == 0)
|
96
|
+
|
97
|
+
value_len += len(termination_sequence)
|
98
|
+
encode_state.emplace_bytes(termination_sequence)
|
99
|
+
|
100
|
+
if value_len < self.min_length:
|
101
|
+
odxraise(
|
102
|
+
f"Encoded value for MinMaxLengthType "
|
103
|
+
f"must be at least {self.min_length} bytes long. "
|
104
|
+
f"(Is: {value_len} bytes.)", EncodeError)
|
105
|
+
return
|
106
|
+
elif self.max_length is not None and value_len > self.max_length:
|
107
|
+
odxraise(
|
108
|
+
f"Encoded value for MinMaxLengthType "
|
109
|
+
f"must not be longer than {self.max_length} bytes. "
|
110
|
+
f"(Is: {value_len} bytes.)", EncodeError)
|
111
|
+
return
|
102
112
|
|
103
113
|
def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
|
104
114
|
odxassert(decode_state.cursor_bit_position == 0,
|