odxtools 7.0.0__py3-none-any.whl → 7.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. odxtools/basicstructure.py +1 -5
  2. odxtools/diagcomm.py +11 -3
  3. odxtools/diagdatadictionaryspec.py +14 -14
  4. odxtools/diaglayer.py +7 -1
  5. odxtools/diaglayerraw.py +13 -7
  6. odxtools/dynamicendmarkerfield.py +119 -0
  7. odxtools/dynamiclengthfield.py +9 -3
  8. odxtools/dynenddopref.py +38 -0
  9. odxtools/encodestate.py +3 -3
  10. odxtools/endofpdufield.py +20 -3
  11. odxtools/environmentdata.py +8 -1
  12. odxtools/environmentdatadescription.py +8 -2
  13. odxtools/field.py +4 -3
  14. odxtools/matchingparameter.py +2 -2
  15. odxtools/multiplexercase.py +2 -2
  16. odxtools/multiplexerdefaultcase.py +7 -3
  17. odxtools/odxlink.py +38 -4
  18. odxtools/odxtypes.py +3 -2
  19. odxtools/parameters/codedconstparameter.py +3 -2
  20. odxtools/parameters/lengthkeyparameter.py +4 -3
  21. odxtools/parameters/nrcconstparameter.py +3 -2
  22. odxtools/parameters/parameter.py +6 -0
  23. odxtools/parameters/parameterwithdop.py +10 -14
  24. odxtools/parameters/physicalconstantparameter.py +4 -3
  25. odxtools/parameters/tablekeyparameter.py +9 -9
  26. odxtools/parameters/tablestructparameter.py +6 -25
  27. odxtools/parameters/valueparameter.py +4 -3
  28. odxtools/py.typed +0 -0
  29. odxtools/statechart.py +5 -9
  30. odxtools/statetransition.py +3 -8
  31. odxtools/staticfield.py +17 -3
  32. odxtools/tablerow.py +5 -3
  33. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  34. odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
  35. odxtools/version.py +2 -2
  36. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/METADATA +1 -1
  37. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/RECORD +41 -37
  38. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/LICENSE +0 -0
  39. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/WHEEL +0 -0
  40. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/entry_points.txt +0 -0
  41. {odxtools-7.0.0.dist-info → odxtools-7.1.1.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,6 @@ from .parameters.parameter import Parameter
23
23
  from .parameters.parameterwithdop import ParameterWithDOP
24
24
  from .parameters.physicalconstantparameter import PhysicalConstantParameter
25
25
  from .parameters.tablekeyparameter import TableKeyParameter
26
- from .parameters.tablestructparameter import TableStructParameter
27
26
  from .utils import dataclass_fields_asdict
28
27
 
29
28
  if TYPE_CHECKING:
@@ -307,7 +306,4 @@ class BasicStructure(ComplexDop):
307
306
  super()._resolve_snrefs(diag_layer)
308
307
 
309
308
  for param in self.parameters:
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)
309
+ param._parameter_resolve_snrefs(diag_layer, param_list=self.parameters)
odxtools/diagcomm.py CHANGED
@@ -11,7 +11,7 @@ from .element import IdentifiableElement
11
11
  from .exceptions import odxraise, odxrequire
12
12
  from .functionalclass import FunctionalClass
13
13
  from .nameditemlist import NamedItemList
14
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
14
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
15
15
  from .odxtypes import odxstr_to_bool
16
16
  from .specialdatagroup import SpecialDataGroup
17
17
  from .state import State
@@ -211,5 +211,13 @@ class DiagComm(IdentifiableElement):
211
211
  for sdg in self.sdgs:
212
212
  sdg._resolve_snrefs(diag_layer)
213
213
 
214
- self._protocols = NamedItemList(
215
- [diag_layer.protocols[prot_snref] for prot_snref in self.protocol_snrefs])
214
+ if TYPE_CHECKING:
215
+ self._protocols = NamedItemList([
216
+ resolve_snref(prot_snref, diag_layer.protocols, DiagLayer)
217
+ for prot_snref in self.protocol_snrefs
218
+ ])
219
+ else:
220
+ self._protocols = NamedItemList([
221
+ resolve_snref(prot_snref, diag_layer.protocols)
222
+ for prot_snref in self.protocol_snrefs
223
+ ])
@@ -10,6 +10,7 @@ from .createsdgs import create_sdgs_from_et
10
10
  from .dataobjectproperty import DataObjectProperty
11
11
  from .dopbase import DopBase
12
12
  from .dtcdop import DtcDop
13
+ from .dynamicendmarkerfield import DynamicEndmarkerField
13
14
  from .dynamiclengthfield import DynamicLengthField
14
15
  from .endofpdufield import EndOfPduField
15
16
  from .environmentdata import EnvironmentData
@@ -37,7 +38,7 @@ class DiagDataDictionarySpec:
37
38
  structures: NamedItemList[BasicStructure]
38
39
  static_fields: NamedItemList[StaticField]
39
40
  dynamic_length_fields: NamedItemList[DynamicLengthField]
40
- #dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
+ dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
42
  end_of_pdu_fields: NamedItemList[EndOfPduField]
42
43
  muxs: NamedItemList[Multiplexer]
43
44
  env_datas: NamedItemList[EnvironmentData]
@@ -54,7 +55,7 @@ class DiagDataDictionarySpec:
54
55
  self.structures,
55
56
  self.static_fields,
56
57
  self.dynamic_length_fields,
57
- #self.dynamic_endmarker_fields,
58
+ self.dynamic_endmarker_fields,
58
59
  self.end_of_pdu_fields,
59
60
  self.muxs,
60
61
  self.env_datas,
@@ -99,11 +100,10 @@ class DiagDataDictionarySpec:
99
100
  for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
100
101
  ]
101
102
 
102
- # TODO: dynamic endmarker fields
103
- #dynamic_endmarker_fields = [
104
- # DynamicEndmarkerField.from_et(dl_element, doc_frags)
105
- # for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
- #]
103
+ dynamic_endmarker_fields = [
104
+ DynamicEndmarkerField.from_et(dl_element, doc_frags) for dl_element in
105
+ et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
+ ]
107
107
 
108
108
  end_of_pdu_fields = [
109
109
  EndOfPduField.from_et(eofp_element, doc_frags)
@@ -145,7 +145,7 @@ class DiagDataDictionarySpec:
145
145
  structures=NamedItemList(structures),
146
146
  static_fields=NamedItemList(static_fields),
147
147
  dynamic_length_fields=NamedItemList(dynamic_length_fields),
148
- #dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
148
+ dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
149
149
  end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
150
150
  muxs=NamedItemList(muxs),
151
151
  env_datas=NamedItemList(env_datas),
@@ -172,8 +172,8 @@ class DiagDataDictionarySpec:
172
172
  odxlinks.update(static_field._build_odxlinks())
173
173
  for dynamic_length_field in self.dynamic_length_fields:
174
174
  odxlinks.update(dynamic_length_field._build_odxlinks())
175
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
- # odxlinks.update(dynamic_endmarker_field._build_odxlinks())
175
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
+ odxlinks.update(dynamic_endmarker_field._build_odxlinks())
177
177
  for end_of_pdu_field in self.end_of_pdu_fields:
178
178
  odxlinks.update(end_of_pdu_field._build_odxlinks())
179
179
  for mux in self.muxs:
@@ -204,8 +204,8 @@ class DiagDataDictionarySpec:
204
204
  static_field._resolve_odxlinks(odxlinks)
205
205
  for dynamic_length_field in self.dynamic_length_fields:
206
206
  dynamic_length_field._resolve_odxlinks(odxlinks)
207
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
- # dynamic_endmarker_field._resolve_odxlinks(odxlinks)
207
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
+ dynamic_endmarker_field._resolve_odxlinks(odxlinks)
209
209
  for end_of_pdu_field in self.end_of_pdu_fields:
210
210
  end_of_pdu_field._resolve_odxlinks(odxlinks)
211
211
  for mux in self.muxs:
@@ -234,8 +234,8 @@ class DiagDataDictionarySpec:
234
234
  static_field._resolve_snrefs(diag_layer)
235
235
  for dynamic_length_field in self.dynamic_length_fields:
236
236
  dynamic_length_field._resolve_snrefs(diag_layer)
237
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
- # dynamic_endmarker_field._resolve_snrefs(diag_layer)
237
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
+ dynamic_endmarker_field._resolve_snrefs(diag_layer)
239
239
  for end_of_pdu_field in self.end_of_pdu_fields:
240
240
  end_of_pdu_field._resolve_snrefs(diag_layer)
241
241
  for mux in self.muxs:
odxtools/diaglayer.py CHANGED
@@ -15,6 +15,7 @@ from .admindata import AdminData
15
15
  from .companydata import CompanyData
16
16
  from .comparaminstance import ComparamInstance
17
17
  from .comparamspec import ComparamSpec
18
+ from .comparamsubset import ComparamSubset
18
19
  from .diagcomm import DiagComm
19
20
  from .diagdatadictionaryspec import DiagDataDictionarySpec
20
21
  from .diaglayerraw import DiagLayerRaw
@@ -216,6 +217,10 @@ class DiagLayer:
216
217
  lambda ddd_spec: ddd_spec.end_of_pdu_fields,
217
218
  lambda parent_ref: parent_ref.not_inherited_dops,
218
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
+ )
219
224
  dynamic_length_fields = self._compute_available_ddd_spec_items(
220
225
  lambda ddd_spec: ddd_spec.dynamic_length_fields,
221
226
  lambda parent_ref: parent_ref.not_inherited_dops,
@@ -246,6 +251,7 @@ class DiagLayer:
246
251
  structures=structures,
247
252
  static_fields=NamedItemList(),
248
253
  end_of_pdu_fields=end_of_pdu_fields,
254
+ dynamic_endmarker_fields=dynamic_endmarker_fields,
249
255
  dynamic_length_fields=dynamic_length_fields,
250
256
  tables=tables,
251
257
  env_data_descs=env_data_descs,
@@ -351,7 +357,7 @@ class DiagLayer:
351
357
  return self.diag_layer_raw.prot_stack_snref
352
358
 
353
359
  @property
354
- def comparam_spec(self) -> Optional[ComparamSpec]:
360
+ def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
355
361
  return self.diag_layer_raw.comparam_spec
356
362
 
357
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
- self._comparam_spec = odxlinks.resolve(self.comparam_spec_ref, ComparamSpec)
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
- self._prot_stack = odxrequire(
285
- odxrequire(self.comparam_spec).prot_stacks.get(self.prot_stack_snref))
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 functional_classe in self.functional_classes:
296
- functional_classe._resolve_snrefs(diag_layer)
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
@@ -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
@@ -1,6 +1,6 @@
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
6
  from typing_extensions import override
@@ -55,7 +55,7 @@ class DynamicLengthField(Field):
55
55
  odxassert(encode_state.cursor_bit_position == 0,
56
56
  "No bit position can be specified for dynamic length fields!")
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)
@@ -77,8 +77,14 @@ class DynamicLengthField(Field):
77
77
  encode_state.cursor_byte_position = encode_state.origin_byte_position + self.offset
78
78
  encode_state.cursor_bit_position = 0
79
79
 
80
- for value in physical_value:
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
+
81
86
  self.structure.encode_into_pdu(value, encode_state)
87
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
82
88
 
83
89
  # ensure the correct message size if the field is empty
84
90
  if len(physical_value) == 0:
@@ -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)
odxtools/encodestate.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import Dict, Optional
4
+ from typing import Dict, Optional, SupportsBytes
5
5
 
6
6
  from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
7
7
  from .odxtypes import AtomicOdxType, DataType
@@ -83,13 +83,13 @@ class EncodeState:
83
83
 
84
84
  # Check that bytes and strings actually fit into the bit length
85
85
  if base_data_type == DataType.A_BYTEFIELD:
86
- if not isinstance(internal_value, bytes):
86
+ if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
87
87
  odxraise()
88
88
  if 8 * len(internal_value) > bit_length:
89
89
  raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
90
90
  f"({len(internal_value)} bytes)."
91
91
  f" The maximum length is {bit_length//8}.")
92
- raw_value = internal_value
92
+ raw_value = bytes(internal_value)
93
93
  elif base_data_type == DataType.A_ASCIISTRING:
94
94
  if not isinstance(internal_value, str):
95
95
  odxraise()
odxtools/endofpdufield.py CHANGED
@@ -1,6 +1,6 @@
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
6
  from typing_extensions import override
@@ -46,21 +46,35 @@ class EndOfPduField(Field):
46
46
  encode_state: EncodeState) -> None:
47
47
  odxassert(not encode_state.cursor_bit_position,
48
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!")
49
51
 
50
- if not isinstance(physical_value, list):
52
+ if not isinstance(physical_value, Sequence):
51
53
  odxraise(
52
54
  f"Invalid type {type(physical_value).__name__} of physical "
53
55
  f"value for end-of-pdu field, expected a list", EncodeError)
54
56
  return
55
57
 
56
- for value in physical_value:
58
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
59
+ encode_state.is_end_of_pdu = False
60
+
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
64
+
57
65
  self.structure.encode_into_pdu(value, encode_state)
58
66
 
67
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
68
+
59
69
  @override
60
70
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
61
71
  odxassert(not decode_state.cursor_bit_position,
62
72
  "No bit position can be specified for end-of-pdu fields!")
63
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
+
64
78
  result: List[ParameterValue] = []
65
79
  while decode_state.cursor_byte_position < len(decode_state.coded_message):
66
80
  # ATTENTION: the ODX specification is very misleading
@@ -69,4 +83,7 @@ class EndOfPduField(Field):
69
83
  # repeated are identical, not their values
70
84
  result.append(self.structure.decode_from_pdu(decode_state))
71
85
 
86
+ decode_state.origin_byte_position = orig_origin
87
+ decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
88
+
72
89
  return result
@@ -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 circumstances in which the error occurred."""
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]
@@ -20,8 +20,14 @@ if TYPE_CHECKING:
20
20
 
21
21
  @dataclass
22
22
  class EnvironmentDataDescription(ComplexDop):
23
- """This class represents Environment Data Description, which is a complex DOP
24
- that is used to define the interpretation of environment data."""
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
+ """
25
31
 
26
32
  # in ODX 2.0.0, ENV-DATAS seems to be a mandatory
27
33
  # sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
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 = odxrequire(structures.get(self.structure_snref))
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 = odxrequire(env_data_descs.get(self.env_data_desc_snref))
91
+ self._env_data_desc = resolve_snref(self.env_data_desc_snref, env_data_descs,
92
+ EnvironmentDataDescription)
@@ -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
- diag_com_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
- diag_comm_snref = odxrequire(diag_com_snref_el.get("SHORT-NAME"))
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
@@ -7,7 +7,7 @@ from .basicstructure import BasicStructure
7
7
  from .compumethods.limit import Limit
8
8
  from .element import NamedElement
9
9
  from .exceptions import odxrequire
10
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
11
11
  from .odxtypes import AtomicOdxType, DataType
12
12
  from .utils import dataclass_fields_asdict
13
13
 
@@ -73,7 +73,7 @@ class MultiplexerCase(NamedElement):
73
73
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
74
74
  if self.structure_snref:
75
75
  ddds = diag_layer.diag_data_dictionary_spec
76
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
76
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure)
77
77
 
78
78
  def applies(self, value: AtomicOdxType) -> bool:
79
79
  return self.lower_limit.complies_to_lower(value) \
@@ -6,7 +6,7 @@ from xml.etree import ElementTree
6
6
  from .basicstructure import BasicStructure
7
7
  from .element import NamedElement
8
8
  from .exceptions import odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
10
  from .utils import dataclass_fields_asdict
11
11
 
12
12
  if TYPE_CHECKING:
@@ -20,7 +20,7 @@ class MultiplexerDefaultCase(NamedElement):
20
20
  structure_snref: Optional[str]
21
21
 
22
22
  def __post_init__(self) -> None:
23
- self._structure: Optional[BasicStructure] = None
23
+ self._structure: BasicStructure
24
24
 
25
25
  @staticmethod
26
26
  def from_et(et_element: ElementTree.Element,
@@ -46,4 +46,8 @@ class MultiplexerDefaultCase(NamedElement):
46
46
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
47
47
  if self.structure_snref:
48
48
  ddds = diag_layer.diag_data_dictionary_spec
49
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
49
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure)
50
+
51
+ @property
52
+ def structure(self) -> BasicStructure:
53
+ return self._structure