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/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
|
@@ -14,6 +15,7 @@ from .admindata import AdminData
|
|
14
15
|
from .companydata import CompanyData
|
15
16
|
from .comparaminstance import ComparamInstance
|
16
17
|
from .comparamspec import ComparamSpec
|
18
|
+
from .comparamsubset import ComparamSubset
|
17
19
|
from .diagcomm import DiagComm
|
18
20
|
from .diagdatadictionaryspec import DiagDataDictionarySpec
|
19
21
|
from .diaglayerraw import DiagLayerRaw
|
@@ -76,6 +78,50 @@ class DiagLayer:
|
|
76
78
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
77
79
|
"""Recursively resolve all references."""
|
78
80
|
|
81
|
+
# deal with the import references: these basically extend the
|
82
|
+
# pool of objects that are referenceable without having to
|
83
|
+
# explicitly specify the DOCREF attribute in the
|
84
|
+
# reference. This mechanism can thus be seen as a kind of
|
85
|
+
# "poor man's inheritance".
|
86
|
+
if self.import_refs:
|
87
|
+
imported_links: Dict[OdxLinkId, Any] = {}
|
88
|
+
for import_ref in self.import_refs:
|
89
|
+
imported_dl = odxlinks.resolve(import_ref, DiagLayer)
|
90
|
+
|
91
|
+
odxassert(
|
92
|
+
imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
|
93
|
+
f"Tried to import references from diagnostic layer "
|
94
|
+
f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
|
95
|
+
f"Only ECU-SHARED-DATA layers may be referenced using the "
|
96
|
+
f"IMPORT-REF mechanism")
|
97
|
+
|
98
|
+
# TODO: ensure that the imported diagnostic layer has
|
99
|
+
# not been referenced in any PARENT-REF of the current
|
100
|
+
# layer or any of its parents.
|
101
|
+
|
102
|
+
# TODO: detect and complain about cyclic IMPORT-REFs
|
103
|
+
|
104
|
+
# TODO (?): detect conflicts with locally-defined
|
105
|
+
# objects
|
106
|
+
|
107
|
+
imported_dl_links = imported_dl._build_odxlinks()
|
108
|
+
for link_id, obj in imported_dl_links.items():
|
109
|
+
# the imported objects shall behave as if they
|
110
|
+
# were defined by the importing layer. IOW, they
|
111
|
+
# must be visible in the same document fragments.
|
112
|
+
link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
|
113
|
+
imported_links[link_id] = obj
|
114
|
+
|
115
|
+
# We need to copy the odxlink database here since this
|
116
|
+
# function must not modify its argument because the
|
117
|
+
# imported references only apply within this specific
|
118
|
+
# diagnostic layer
|
119
|
+
extended_odxlinks = copy(odxlinks)
|
120
|
+
extended_odxlinks.update(imported_links)
|
121
|
+
|
122
|
+
self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
|
123
|
+
return
|
124
|
+
|
79
125
|
self.diag_layer_raw._resolve_odxlinks(odxlinks)
|
80
126
|
|
81
127
|
def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
|
@@ -171,6 +217,10 @@ class DiagLayer:
|
|
171
217
|
lambda ddd_spec: ddd_spec.end_of_pdu_fields,
|
172
218
|
lambda parent_ref: parent_ref.not_inherited_dops,
|
173
219
|
)
|
220
|
+
dynamic_endmarker_fields = self._compute_available_ddd_spec_items(
|
221
|
+
lambda ddd_spec: ddd_spec.dynamic_endmarker_fields,
|
222
|
+
lambda parent_ref: parent_ref.not_inherited_dops,
|
223
|
+
)
|
174
224
|
dynamic_length_fields = self._compute_available_ddd_spec_items(
|
175
225
|
lambda ddd_spec: ddd_spec.dynamic_length_fields,
|
176
226
|
lambda parent_ref: parent_ref.not_inherited_dops,
|
@@ -201,6 +251,7 @@ class DiagLayer:
|
|
201
251
|
structures=structures,
|
202
252
|
static_fields=NamedItemList(),
|
203
253
|
end_of_pdu_fields=end_of_pdu_fields,
|
254
|
+
dynamic_endmarker_fields=dynamic_endmarker_fields,
|
204
255
|
dynamic_length_fields=dynamic_length_fields,
|
205
256
|
tables=tables,
|
206
257
|
env_data_descs=env_data_descs,
|
@@ -306,7 +357,7 @@ class DiagLayer:
|
|
306
357
|
return self.diag_layer_raw.prot_stack_snref
|
307
358
|
|
308
359
|
@property
|
309
|
-
def comparam_spec(self) -> Optional[ComparamSpec]:
|
360
|
+
def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
|
310
361
|
return self.diag_layer_raw.comparam_spec
|
311
362
|
|
312
363
|
@property
|
odxtools/diaglayerraw.py
CHANGED
@@ -9,6 +9,7 @@ from .admindata import AdminData
|
|
9
9
|
from .companydata import CompanyData
|
10
10
|
from .comparaminstance import ComparamInstance
|
11
11
|
from .comparamspec import ComparamSpec
|
12
|
+
from .comparamsubset import ComparamSubset
|
12
13
|
from .createsdgs import create_sdgs_from_et
|
13
14
|
from .diagcomm import DiagComm
|
14
15
|
from .diagdatadictionaryspec import DiagDataDictionarySpec
|
@@ -19,7 +20,7 @@ from .element import IdentifiableElement
|
|
19
20
|
from .exceptions import odxassert, odxraise, odxrequire
|
20
21
|
from .functionalclass import FunctionalClass
|
21
22
|
from .nameditemlist import NamedItemList
|
22
|
-
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
23
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
23
24
|
from .parentref import ParentRef
|
24
25
|
from .protstack import ProtStack
|
25
26
|
from .request import Request
|
@@ -243,7 +244,10 @@ class DiagLayerRaw(IdentifiableElement):
|
|
243
244
|
"""Recursively resolve all references."""
|
244
245
|
|
245
246
|
if self.comparam_spec_ref is not None:
|
246
|
-
|
247
|
+
spec = odxlinks.resolve(self.comparam_spec_ref)
|
248
|
+
if not isinstance(spec, (ComparamSubset, ComparamSpec)):
|
249
|
+
odxraise(f"Type {type(spec).__name__} is not allowed for comparam specs")
|
250
|
+
self._comparam_spec = spec
|
247
251
|
|
248
252
|
# do ODXLINK reference resolution
|
249
253
|
if self.admin_data is not None:
|
@@ -281,8 +285,10 @@ class DiagLayerRaw(IdentifiableElement):
|
|
281
285
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
282
286
|
self._prot_stack: Optional[ProtStack] = None
|
283
287
|
if self.prot_stack_snref is not None:
|
284
|
-
|
285
|
-
|
288
|
+
cp_spec = self.comparam_spec
|
289
|
+
if isinstance(cp_spec, ComparamSpec):
|
290
|
+
self._prot_stack = resolve_snref(self.prot_stack_snref, cp_spec.prot_stacks,
|
291
|
+
ProtStack)
|
286
292
|
|
287
293
|
# do short-name reference resolution
|
288
294
|
if self.admin_data is not None:
|
@@ -292,8 +298,8 @@ class DiagLayerRaw(IdentifiableElement):
|
|
292
298
|
|
293
299
|
for company_data in self.company_datas:
|
294
300
|
company_data._resolve_snrefs(diag_layer)
|
295
|
-
for
|
296
|
-
|
301
|
+
for functional_class in self.functional_classes:
|
302
|
+
functional_class._resolve_snrefs(diag_layer)
|
297
303
|
for diag_comm in self.diag_comms:
|
298
304
|
if isinstance(diag_comm, OdxLinkRef):
|
299
305
|
continue
|
@@ -318,7 +324,7 @@ class DiagLayerRaw(IdentifiableElement):
|
|
318
324
|
comparam._resolve_snrefs(diag_layer)
|
319
325
|
|
320
326
|
@property
|
321
|
-
def comparam_spec(self) -> Optional[ComparamSpec]:
|
327
|
+
def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
|
322
328
|
return self._comparam_spec
|
323
329
|
|
324
330
|
@property
|
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:
|
odxtools/dtcdop.py
CHANGED
@@ -4,6 +4,8 @@ from dataclasses import dataclass
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
+
from typing_extensions import override
|
8
|
+
|
7
9
|
from .compumethods.compumethod import CompuMethod
|
8
10
|
from .compumethods.createanycompumethod import create_any_compu_method_from_et
|
9
11
|
from .createanydiagcodedtype import create_any_diag_coded_type_from_et
|
@@ -12,7 +14,7 @@ from .diagcodedtype import DiagCodedType
|
|
12
14
|
from .diagnostictroublecode import DiagnosticTroubleCode
|
13
15
|
from .dopbase import DopBase
|
14
16
|
from .encodestate import EncodeState
|
15
|
-
from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
|
17
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
16
18
|
from .nameditemlist import NamedItemList
|
17
19
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
18
20
|
from .odxtypes import ParameterValue, odxstr_to_bool
|
@@ -87,17 +89,20 @@ class DtcDop(DopBase):
|
|
87
89
|
def linked_dtc_dops(self) -> NamedItemList["DtcDop"]:
|
88
90
|
return self._linked_dtc_dops
|
89
91
|
|
92
|
+
@override
|
90
93
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
91
94
|
|
92
95
|
int_trouble_code = self.diag_coded_type.decode_from_pdu(decode_state)
|
93
96
|
|
94
|
-
if self.compu_method.is_valid_internal_value(int_trouble_code):
|
95
|
-
trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
|
96
|
-
else:
|
97
|
+
if not self.compu_method.is_valid_internal_value(int_trouble_code):
|
97
98
|
# TODO: How to prevent this?
|
98
|
-
|
99
|
+
odxraise(
|
99
100
|
f"DTC-DOP {self.short_name} could not convert the coded value "
|
100
|
-
f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}."
|
101
|
+
f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}.",
|
102
|
+
DecodeError)
|
103
|
+
return
|
104
|
+
|
105
|
+
trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
|
101
106
|
|
102
107
|
assert isinstance(trouble_code, int)
|
103
108
|
|
@@ -110,10 +115,12 @@ class DtcDop(DopBase):
|
|
110
115
|
return dtcs[0]
|
111
116
|
|
112
117
|
# the DTC was not specified. This probably means that the
|
113
|
-
# diagnostic description file is incomplete.
|
114
|
-
|
115
|
-
|
116
|
-
|
118
|
+
# diagnostic description file is incomplete.
|
119
|
+
odxraise(
|
120
|
+
f"Encountered DTC 0x{trouble_code:06x} which has not been defined "
|
121
|
+
f"by the database", DecodeError)
|
122
|
+
|
123
|
+
return DiagnosticTroubleCode(
|
117
124
|
trouble_code=trouble_code,
|
118
125
|
odx_id=cast(OdxLinkId, None),
|
119
126
|
short_name=f'DTC_{trouble_code:06x}',
|
@@ -126,12 +133,9 @@ class DtcDop(DopBase):
|
|
126
133
|
sdgs=[],
|
127
134
|
)
|
128
135
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
physical_value: ParameterValue,
|
133
|
-
encode_state: EncodeState,
|
134
|
-
bit_position: int = 0) -> bytes:
|
136
|
+
@override
|
137
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
138
|
+
encode_state: EncodeState) -> None:
|
135
139
|
if isinstance(physical_value, DiagnosticTroubleCode):
|
136
140
|
trouble_code = physical_value.trouble_code
|
137
141
|
elif isinstance(physical_value, int):
|
@@ -147,9 +151,7 @@ class DtcDop(DopBase):
|
|
147
151
|
f" DiagnosticTroubleCode but got {physical_value!r}.")
|
148
152
|
|
149
153
|
internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
|
150
|
-
|
151
|
-
return self.diag_coded_type.convert_internal_to_bytes(
|
152
|
-
internal_trouble_code, encode_state=encode_state, bit_position=bit_position)
|
154
|
+
self.diag_coded_type.encode_into_pdu(internal_trouble_code, encode_state)
|
153
155
|
|
154
156
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
155
157
|
odxlinks = super()._build_odxlinks()
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Sequence
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from typing_extensions import override
|
7
|
+
|
8
|
+
from .dataobjectproperty import DataObjectProperty
|
9
|
+
from .decodestate import DecodeState
|
10
|
+
from .dynenddopref import DynEndDopRef
|
11
|
+
from .encodestate import EncodeState
|
12
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
13
|
+
from .field import Field
|
14
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
15
|
+
from .odxtypes import AtomicOdxType, ParameterValue
|
16
|
+
from .utils import dataclass_fields_asdict
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from .diaglayer import DiagLayer
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class DynamicEndmarkerField(Field):
|
24
|
+
"""Array of a structure with variable length determined by a termination sequence"""
|
25
|
+
|
26
|
+
dyn_end_dop_ref: DynEndDopRef
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def from_et(et_element: ElementTree.Element,
|
30
|
+
doc_frags: List[OdxDocFragment]) -> "DynamicEndmarkerField":
|
31
|
+
kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
|
32
|
+
|
33
|
+
dyn_end_dop_ref = DynEndDopRef.from_et(
|
34
|
+
odxrequire(et_element.find("DYN-END-DOP-REF")), doc_frags)
|
35
|
+
|
36
|
+
return DynamicEndmarkerField(dyn_end_dop_ref=dyn_end_dop_ref, **kwargs)
|
37
|
+
|
38
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
39
|
+
odxlinks = super()._build_odxlinks()
|
40
|
+
return odxlinks
|
41
|
+
|
42
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
43
|
+
super()._resolve_odxlinks(odxlinks)
|
44
|
+
|
45
|
+
self._dyn_end_dop = odxlinks.resolve(self.dyn_end_dop_ref, DataObjectProperty)
|
46
|
+
|
47
|
+
tv_string = self.dyn_end_dop_ref.termination_value_raw
|
48
|
+
tv_physical = self._dyn_end_dop.diag_coded_type.base_data_type.from_string(tv_string)
|
49
|
+
|
50
|
+
self._termination_value = tv_physical
|
51
|
+
|
52
|
+
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
53
|
+
super()._resolve_snrefs(diag_layer)
|
54
|
+
|
55
|
+
@property
|
56
|
+
def dyn_end_dop(self) -> DataObjectProperty:
|
57
|
+
return self._dyn_end_dop
|
58
|
+
|
59
|
+
@property
|
60
|
+
def termination_value(self) -> AtomicOdxType:
|
61
|
+
return self._termination_value
|
62
|
+
|
63
|
+
@override
|
64
|
+
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
65
|
+
|
66
|
+
odxassert(encode_state.cursor_bit_position == 0,
|
67
|
+
"No bit position can be specified for dynamic endmarker fields!")
|
68
|
+
if not isinstance(physical_value, Sequence):
|
69
|
+
odxraise(
|
70
|
+
f"Expected a sequence of values for dynamic endmarker field {self.short_name}, "
|
71
|
+
f"got {type(physical_value).__name__}", EncodeError)
|
72
|
+
return
|
73
|
+
|
74
|
+
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
75
|
+
encode_state.is_end_of_pdu = False
|
76
|
+
for i, item in enumerate(physical_value):
|
77
|
+
if i == len(physical_value) - 1:
|
78
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
79
|
+
|
80
|
+
self.structure.encode_into_pdu(item, encode_state)
|
81
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
82
|
+
|
83
|
+
if not encode_state.is_end_of_pdu:
|
84
|
+
# only add an endmarker if we are not at the end of the PDU
|
85
|
+
self.dyn_end_dop.encode_into_pdu(self.termination_value, encode_state)
|
86
|
+
|
87
|
+
@override
|
88
|
+
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
89
|
+
|
90
|
+
odxassert(decode_state.cursor_bit_position == 0,
|
91
|
+
"No bit position can be specified for dynamic endmarker fields!")
|
92
|
+
|
93
|
+
orig_origin = decode_state.origin_byte_position
|
94
|
+
orig_cursor = decode_state.cursor_byte_position
|
95
|
+
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
96
|
+
|
97
|
+
result: List[ParameterValue] = []
|
98
|
+
while True:
|
99
|
+
# check if we're at the end of the PDU
|
100
|
+
if decode_state.cursor_byte_position == len(decode_state.coded_message):
|
101
|
+
break
|
102
|
+
|
103
|
+
# check if the cursor currently points to a termination
|
104
|
+
# value
|
105
|
+
tmp_cursor = decode_state.cursor_byte_position
|
106
|
+
try:
|
107
|
+
tv_candidate = self.dyn_end_dop.decode_from_pdu(decode_state)
|
108
|
+
if tv_candidate == self.termination_value:
|
109
|
+
break
|
110
|
+
except DecodeError:
|
111
|
+
pass
|
112
|
+
decode_state.cursor_byte_position = tmp_cursor
|
113
|
+
|
114
|
+
result.append(self.structure.decode_from_pdu(decode_state))
|
115
|
+
|
116
|
+
decode_state.origin_byte_position = orig_origin
|
117
|
+
decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
|
118
|
+
|
119
|
+
return result
|
odxtools/dynamiclengthfield.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import TYPE_CHECKING, Any, Dict, List
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, 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 .determinenumberofitems import DetermineNumberOfItems
|
8
10
|
from .encodestate import EncodeState
|
@@ -47,44 +49,52 @@ class DynamicLengthField(Field):
|
|
47
49
|
super()._resolve_snrefs(diag_layer)
|
48
50
|
self.determine_number_of_items._resolve_snrefs(diag_layer)
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
encode_state
|
54
|
-
|
55
|
-
) -> bytes:
|
52
|
+
@override
|
53
|
+
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
54
|
+
|
55
|
+
odxassert(encode_state.cursor_bit_position == 0,
|
56
|
+
"No bit position can be specified for dynamic length fields!")
|
56
57
|
|
57
|
-
|
58
|
-
if not isinstance(physical_value, list):
|
58
|
+
if not isinstance(physical_value, Sequence):
|
59
59
|
odxraise(
|
60
60
|
f"Expected a list of values for dynamic length field {self.short_name}, "
|
61
61
|
f"got {type(physical_value)}", EncodeError)
|
62
62
|
|
63
|
+
# move the origin to the cursor position
|
64
|
+
orig_cursor = encode_state.cursor_byte_position
|
65
|
+
orig_origin = encode_state.origin_byte_position
|
66
|
+
encode_state.origin_byte_position = encode_state.cursor_byte_position
|
67
|
+
|
63
68
|
det_num_items = self.determine_number_of_items
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
encode_state.coded_message = bytearray()
|
70
|
-
encode_state.emplace_atomic_value(field_len, self.short_name + ".num_items",
|
71
|
-
det_num_items.byte_position)
|
72
|
-
result = encode_state.coded_message
|
73
|
-
encode_state.coded_message = tmp
|
74
|
-
|
75
|
-
# if required, add padding between the length specifier and
|
76
|
-
# the first item
|
77
|
-
if len(result) < self.offset:
|
78
|
-
result.extend([0] * (self.offset - len(result)))
|
79
|
-
elif len(result) > self.offset:
|
69
|
+
encode_state.cursor_bit_position = self.determine_number_of_items.bit_position or 0
|
70
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + det_num_items.byte_position
|
71
|
+
det_num_items.dop.encode_into_pdu(len(physical_value), encode_state)
|
72
|
+
|
73
|
+
if encode_state.cursor_byte_position - encode_state.origin_byte_position > self.offset:
|
80
74
|
odxraise(f"The length specifier of field {self.short_name} overlaps "
|
81
|
-
f"with
|
75
|
+
f"with its first item!")
|
82
76
|
|
83
|
-
|
84
|
-
|
77
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.offset
|
78
|
+
encode_state.cursor_bit_position = 0
|
85
79
|
|
86
|
-
|
80
|
+
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
81
|
+
encode_state.is_end_of_pdu = False
|
82
|
+
for i, value in enumerate(physical_value):
|
83
|
+
if i == len(physical_value) - 1:
|
84
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
85
|
+
|
86
|
+
self.structure.encode_into_pdu(value, encode_state)
|
87
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
88
|
+
|
89
|
+
# ensure the correct message size if the field is empty
|
90
|
+
if len(physical_value) == 0:
|
91
|
+
encode_state.emplace_bytes(b"")
|
92
|
+
|
93
|
+
# move cursor and origin positions
|
94
|
+
encode_state.origin_byte_position = orig_origin
|
95
|
+
encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
|
87
96
|
|
97
|
+
@override
|
88
98
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
89
99
|
|
90
100
|
odxassert(decode_state.cursor_bit_position == 0,
|
odxtools/dynenddopref.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import List, Optional, overload
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .exceptions import odxraise, odxrequire
|
7
|
+
from .odxlink import OdxDocFragment, OdxLinkRef
|
8
|
+
from .utils import dataclass_fields_asdict
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class DynEndDopRef(OdxLinkRef):
|
13
|
+
termination_value_raw: str
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
@overload
|
17
|
+
def from_et(et_element: None, source_doc_frags: List[OdxDocFragment]) -> None:
|
18
|
+
...
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
@overload
|
22
|
+
def from_et(et_element: ElementTree.Element,
|
23
|
+
source_doc_frags: List[OdxDocFragment]) -> "DynEndDopRef":
|
24
|
+
...
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def from_et(et_element: Optional[ElementTree.Element],
|
28
|
+
source_doc_frags: List[OdxDocFragment]) -> Optional["DynEndDopRef"]:
|
29
|
+
|
30
|
+
if et_element is None:
|
31
|
+
odxraise("Mandatory DYN-END-DOP-REF tag is missing")
|
32
|
+
return None
|
33
|
+
|
34
|
+
kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, source_doc_frags))
|
35
|
+
|
36
|
+
termination_value_raw = odxrequire(et_element.findtext("TERMINATION-VALUE"))
|
37
|
+
|
38
|
+
return DynEndDopRef(termination_value_raw=termination_value_raw, **kwargs)
|