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.
Files changed (60) hide show
  1. odxtools/basicstructure.py +102 -112
  2. odxtools/dataobjectproperty.py +3 -7
  3. odxtools/decodestate.py +1 -13
  4. odxtools/diagcodedtype.py +9 -96
  5. odxtools/diagcomm.py +11 -3
  6. odxtools/diagdatadictionaryspec.py +14 -14
  7. odxtools/diaglayer.py +52 -1
  8. odxtools/diaglayerraw.py +13 -7
  9. odxtools/diagservice.py +12 -16
  10. odxtools/dopbase.py +5 -8
  11. odxtools/dtcdop.py +21 -19
  12. odxtools/dynamicendmarkerfield.py +119 -0
  13. odxtools/dynamiclengthfield.py +39 -29
  14. odxtools/dynenddopref.py +38 -0
  15. odxtools/encodestate.py +188 -23
  16. odxtools/endofpdufield.py +33 -18
  17. odxtools/environmentdata.py +8 -1
  18. odxtools/environmentdatadescription.py +21 -15
  19. odxtools/field.py +4 -3
  20. odxtools/leadinglengthinfotype.py +25 -12
  21. odxtools/matchingparameter.py +2 -2
  22. odxtools/minmaxlengthtype.py +36 -26
  23. odxtools/multiplexer.py +42 -23
  24. odxtools/multiplexercase.py +3 -3
  25. odxtools/multiplexerdefaultcase.py +7 -3
  26. odxtools/nameditemlist.py +14 -0
  27. odxtools/odxlink.py +38 -4
  28. odxtools/odxtypes.py +20 -2
  29. odxtools/parameterinfo.py +126 -40
  30. odxtools/parameters/codedconstparameter.py +17 -13
  31. odxtools/parameters/dynamicparameter.py +5 -4
  32. odxtools/parameters/lengthkeyparameter.py +66 -17
  33. odxtools/parameters/matchingrequestparameter.py +23 -11
  34. odxtools/parameters/nrcconstparameter.py +42 -22
  35. odxtools/parameters/parameter.py +35 -42
  36. odxtools/parameters/parameterwithdop.py +15 -22
  37. odxtools/parameters/physicalconstantparameter.py +16 -16
  38. odxtools/parameters/reservedparameter.py +5 -2
  39. odxtools/parameters/systemparameter.py +3 -2
  40. odxtools/parameters/tableentryparameter.py +3 -2
  41. odxtools/parameters/tablekeyparameter.py +88 -39
  42. odxtools/parameters/tablestructparameter.py +45 -44
  43. odxtools/parameters/valueparameter.py +16 -17
  44. odxtools/paramlengthinfotype.py +30 -22
  45. odxtools/request.py +9 -0
  46. odxtools/response.py +5 -13
  47. odxtools/standardlengthtype.py +51 -13
  48. odxtools/statechart.py +5 -9
  49. odxtools/statetransition.py +3 -8
  50. odxtools/staticfield.py +30 -20
  51. odxtools/tablerow.py +5 -3
  52. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  53. odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
  54. odxtools/version.py +2 -2
  55. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/METADATA +1 -1
  56. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/RECORD +60 -57
  57. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/LICENSE +0 -0
  58. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/WHEEL +0 -0
  59. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/entry_points.txt +0 -0
  60. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/top_level.txt +0 -0
odxtools/multiplexer.py CHANGED
@@ -3,6 +3,8 @@ from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
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
@@ -34,6 +36,7 @@ class Multiplexer(ComplexDop):
34
36
  is_visible_raw: Optional[bool]
35
37
 
36
38
  @staticmethod
39
+ @override
37
40
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
38
41
  """Reads a Multiplexer from Diag Layer."""
39
42
  base_obj = ComplexDop.from_et(et_element, doc_frags)
@@ -67,50 +70,63 @@ class Multiplexer(ComplexDop):
67
70
 
68
71
  def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
69
72
  key_type = self.switch_key.dop.physical_type.base_data_type
70
- lower_limit = key_type.make_from(case.lower_limit)
71
- upper_limit = key_type.make_from(case.upper_limit)
73
+ lower_limit = key_type.make_from(case.lower_limit.value)
74
+ upper_limit = key_type.make_from(case.upper_limit.value)
72
75
  if not isinstance(lower_limit, type(upper_limit)) and not isinstance(
73
76
  upper_limit, type(lower_limit)):
74
77
  odxraise("Upper and lower bounds of limits must compareable")
75
78
  return lower_limit, upper_limit
76
79
 
77
- def convert_physical_to_bytes(self,
78
- physical_value: ParameterValue,
79
- encode_state: EncodeState,
80
- bit_position: int = 0) -> bytes:
80
+ @override
81
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
81
82
 
82
- if bit_position != 0:
83
- raise EncodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
84
- f"{self.short_name} was passed the bit position {bit_position}")
83
+ if encode_state.cursor_bit_position != 0:
84
+ raise EncodeError(f"Multiplexer must be aligned, i.e. bit_position=0, but "
85
+ f"{self.short_name} was passed the bit position "
86
+ f"{encode_state.cursor_bit_position}")
85
87
 
86
88
  if not isinstance(physical_value, dict) or len(physical_value) != 1:
87
89
  raise EncodeError("""Multiplexer should be defined as a dict
88
90
  with only one key equal to the desired case""")
89
91
 
92
+ orig_origin = encode_state.origin_byte_position
93
+ orig_cursor = encode_state.cursor_byte_position
94
+
95
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
96
+
90
97
  case_name, case_value = next(iter(physical_value.items()))
91
- case_pos = self.byte_position
92
98
 
93
99
  for mux_case in self.cases or []:
94
100
  if mux_case.short_name == case_name:
95
- if mux_case._structure:
96
- case_bytes = mux_case._structure.convert_physical_to_bytes(
97
- case_value, encode_state, 0)
98
- else:
99
- case_bytes = b''
100
-
101
101
  key_value, _ = self._get_case_limits(mux_case)
102
- key_bytes = self.switch_key.dop.convert_physical_to_bytes(
103
- key_value, encode_state, bit_position=self.switch_key.bit_position or 0)
104
102
 
105
- mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
106
- mux_bytes = bytearray(mux_len)
107
- mux_bytes[:len(key_bytes)] = key_bytes
108
- mux_bytes[case_pos:case_pos + len(case_bytes)] = case_bytes
103
+ if self.switch_key.byte_position is not None:
104
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
105
+ encode_state.cursor_bit_position = self.switch_key.bit_position or 0
106
+
107
+ self.switch_key.dop.encode_into_pdu(
108
+ physical_value=key_value, encode_state=encode_state)
109
+
110
+ if self.byte_position is not None:
111
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
112
+ encode_state.cursor_bit_position = 0
113
+
114
+ if mux_case._structure is None:
115
+ odxraise(f"Multiplexer case '{mux_case.short_name}' does not "
116
+ f"reference a structure.")
117
+ return
118
+
119
+ mux_case.structure.encode_into_pdu(
120
+ physical_value=key_value, encode_state=encode_state)
109
121
 
110
- return bytes(mux_bytes)
122
+ encode_state.origin_byte_position = orig_origin
123
+ encode_state.cursor_byte_position = max(orig_cursor,
124
+ encode_state.cursor_byte_position)
125
+ return
111
126
 
112
127
  raise EncodeError(f"The case {case_name} is not found in Multiplexer {self.short_name}")
113
128
 
129
+ @override
114
130
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
115
131
 
116
132
  # multiplexers are structures and thus the origin position
@@ -152,6 +168,7 @@ class Multiplexer(ComplexDop):
152
168
 
153
169
  return mux_value
154
170
 
171
+ @override
155
172
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
156
173
  odxlinks = super()._build_odxlinks()
157
174
 
@@ -161,6 +178,7 @@ class Multiplexer(ComplexDop):
161
178
 
162
179
  return odxlinks
163
180
 
181
+ @override
164
182
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
165
183
  super()._resolve_odxlinks(odxlinks)
166
184
 
@@ -172,6 +190,7 @@ class Multiplexer(ComplexDop):
172
190
  mux_case._mux_case_resolve_odxlinks(
173
191
  odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
174
192
 
193
+ @override
175
194
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
176
195
  super()._resolve_snrefs(diag_layer)
177
196
 
@@ -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
 
@@ -60,7 +60,7 @@ class MultiplexerCase(NamedElement):
60
60
 
61
61
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
62
62
  raise RuntimeError("Calling MultiplexerCase._resolve_odxlinks() is not allowed. "
63
- "Use ._mux_case_resolve_odxlinks()().")
63
+ "Use ._mux_case_resolve_odxlinks().")
64
64
 
65
65
  def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *,
66
66
  key_physical_type: DataType) -> None:
@@ -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
odxtools/nameditemlist.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
+ from copy import deepcopy
3
4
  from keyword import iskeyword
4
5
  from typing import (Any, Collection, Dict, Iterable, List, Optional, Protocol, SupportsIndex, Tuple,
5
6
  TypeVar, Union, cast, overload, runtime_checkable)
@@ -175,6 +176,19 @@ class ItemAttributeList(List[T]):
175
176
  def __repr__(self) -> str:
176
177
  return f"{type(self).__name__}([{', '.join([repr(x) for x in self])}])"
177
178
 
179
+ def __copy__(self) -> Any:
180
+ return self.__class__(list(self))
181
+
182
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
183
+ cls = self.__class__
184
+ result = cls.__new__(cls)
185
+ memo[id(self)] = result
186
+ result._item_dict = {}
187
+ for x in self:
188
+ result.append(deepcopy(x, memo))
189
+
190
+ return result
191
+
178
192
 
179
193
  class NamedItemList(ItemAttributeList[T]):
180
194
 
odxtools/odxlink.py CHANGED
@@ -1,10 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, Type, TypeVar, overload
4
+ from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, overload
5
5
  from xml.etree import ElementTree
6
6
 
7
- from .exceptions import OdxWarning, odxassert
7
+ from .exceptions import OdxWarning, odxassert, odxraise
8
+ from .nameditemlist import OdxNamed, TNamed
8
9
 
9
10
 
10
11
  @dataclass(frozen=True)
@@ -210,8 +211,10 @@ class OdxLinkDatabase:
210
211
 
211
212
  return obj
212
213
 
213
- raise KeyError(f"ODXLINK reference {ref} could not be resolved for any "
214
- f"of the document fragments {ref.ref_docs}")
214
+ odxraise(
215
+ f"ODXLINK reference {ref} could not be resolved for any "
216
+ f"of the document fragments {ref.ref_docs}", KeyError)
217
+ return None
215
218
 
216
219
  @overload
217
220
  def resolve_lenient(self, ref: OdxLinkRef, expected_type: None = None) -> Any:
@@ -270,3 +273,34 @@ class OdxLinkDatabase:
270
273
  self._db[doc_frag] = {}
271
274
 
272
275
  self._db[doc_frag][odx_id] = obj
276
+
277
+
278
+ @overload
279
+ def resolve_snref(target_short_name: str,
280
+ items: Iterable[OdxNamed],
281
+ expected_type: None = None) -> Any:
282
+ """Resolve a short name reference given a sequence of candidate objects"""
283
+ ...
284
+
285
+
286
+ @overload
287
+ def resolve_snref(target_short_name: str, items: Iterable[OdxNamed],
288
+ expected_type: Type[TNamed]) -> TNamed:
289
+ ...
290
+
291
+
292
+ def resolve_snref(target_short_name: str,
293
+ items: Iterable[OdxNamed],
294
+ expected_type: Any = None) -> Any:
295
+ candidates = [x for x in items if x.short_name == target_short_name]
296
+
297
+ if not candidates:
298
+ odxraise(f"Cannot resolve short name reference to '{target_short_name}'")
299
+ return None
300
+ elif len(candidates) > 1:
301
+ odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'")
302
+ elif expected_type is not None and not isinstance(candidates[0], expected_type):
303
+ odxraise(f"Reference '{target_short_name}' points to a {type(candidates[0]).__name__}"
304
+ f"object while expecting {expected_type.__name__}")
305
+
306
+ return candidates[0]
odxtools/odxtypes.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from enum import Enum
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union, overload
3
+ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Optional, Tuple, Type, Union,
4
+ overload)
4
5
  from xml.etree import ElementTree
5
6
 
6
7
  from .exceptions import odxassert, odxraise, odxrequire
@@ -28,7 +29,7 @@ ParameterDict = Dict[str, Union["Parameter", "ParameterDict"]]
28
29
  # multiple items, so this can be a list of objects.
29
30
  TableStructParameterValue = Tuple[str, "ParameterValue"]
30
31
  ParameterValue = Union[AtomicOdxType, "ParameterValueDict", TableStructParameterValue,
31
- List["ParameterValue"], "DiagnosticTroubleCode"]
32
+ Iterable["ParameterValue"], "DiagnosticTroubleCode"]
32
33
  ParameterValueDict = Dict[str, ParameterValue]
33
34
 
34
35
 
@@ -154,6 +155,19 @@ def compare_odx_values(a: AtomicOdxType, b: AtomicOdxType) -> int:
154
155
  f"and {type(b).__name__}")
155
156
 
156
157
 
158
+ # format specifiers for the data type using the bitstruct module
159
+ _BITSTRUCT_FORMAT_LETTER_MAP__ = {
160
+ "A_INT32": "s",
161
+ "A_UINT32": "u",
162
+ "A_FLOAT32": "f",
163
+ "A_FLOAT64": "f",
164
+ "A_BYTEFIELD": "r",
165
+ "A_UNICODE2STRING": "r", # UTF-16 strings must be converted explicitly
166
+ "A_ASCIISTRING": "r",
167
+ "A_UTF8STRING": "r",
168
+ }
169
+
170
+
157
171
  class DataType(Enum):
158
172
  """Types for the physical and internal value.
159
173
 
@@ -181,6 +195,10 @@ class DataType(Enum):
181
195
  def python_type(self) -> Type[AtomicOdxType]:
182
196
  return _ODX_TYPE_TO_PYTHON_TYPE[self.value]
183
197
 
198
+ @property
199
+ def bitstruct_format_letter(self) -> str:
200
+ return _BITSTRUCT_FORMAT_LETTER_MAP__[self.value]
201
+
184
202
  def from_string(self, value: str) -> AtomicOdxType:
185
203
  return _PARSE_ODX_TYPE[self.value](value)
186
204
 
odxtools/parameterinfo.py CHANGED
@@ -1,105 +1,191 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import re
3
- from typing import Iterable, Union
2
+ import textwrap
3
+ from io import StringIO
4
+ from typing import Iterable
4
5
 
5
6
  from .compumethods.identicalcompumethod import IdenticalCompuMethod
6
7
  from .compumethods.limit import IntervalType
7
8
  from .compumethods.linearcompumethod import LinearCompuMethod
8
9
  from .compumethods.texttablecompumethod import TexttableCompuMethod
9
10
  from .dataobjectproperty import DataObjectProperty
11
+ from .dtcdop import DtcDop
12
+ from .dynamiclengthfield import DynamicLengthField
10
13
  from .endofpdufield import EndOfPduField
14
+ from .exceptions import odxrequire
15
+ from .multiplexer import Multiplexer
11
16
  from .odxtypes import DataType
12
17
  from .parameters.codedconstparameter import CodedConstParameter
13
18
  from .parameters.matchingrequestparameter import MatchingRequestParameter
19
+ from .parameters.nrcconstparameter import NrcConstParameter
14
20
  from .parameters.parameter import Parameter
15
21
  from .parameters.parameterwithdop import ParameterWithDOP
16
22
  from .parameters.reservedparameter import ReservedParameter
23
+ from .parameters.tablekeyparameter import TableKeyParameter
24
+ from .parameters.tablestructparameter import TableStructParameter
25
+ from .paramlengthinfotype import ParamLengthInfoType
26
+ from .staticfield import StaticField
17
27
 
18
28
 
19
- def parameter_info(param_list: Iterable[Union[Parameter, EndOfPduField]]) -> str:
20
- result = ""
29
+ def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False) -> str:
30
+ q = "'" if quoted_names else ""
31
+ of = StringIO()
21
32
  for param in param_list:
22
33
  if isinstance(param, CodedConstParameter):
23
- result += f"{param.short_name} : const = {param._coded_value_str}\n"
34
+ of.write(f"{q}{param.short_name}{q}: const = {param._coded_value_str}\n")
24
35
  continue
25
36
  elif isinstance(param, MatchingRequestParameter):
26
- result += f"{param.short_name} : <matches request>\n"
37
+ of.write(f"{q}{param.short_name}{q}: <matches request>\n")
38
+ continue
39
+ elif isinstance(param, NrcConstParameter):
40
+ of.write(f"{q}{param.short_name}{q}: const; choices = {param.coded_values}\n")
27
41
  continue
28
42
  elif isinstance(param, ReservedParameter):
29
- result += f"{param.short_name} : <reserved>\n"
43
+ of.write(f"{q}{param.short_name}{q}: <reserved>\n")
44
+ continue
45
+ elif isinstance(param, TableKeyParameter):
46
+ of.write(
47
+ f"{q}{param.short_name}{q}: <optional> table key; table = '{param.table.short_name}'; choices:\n"
48
+ )
49
+ for tr in param.table.table_rows:
50
+ of.write(f" '{tr.short_name}',\n")
51
+
52
+ continue
53
+ elif isinstance(param, TableStructParameter):
54
+ of.write(
55
+ f"{q}{param.short_name}{q}: table struct; key = '{param.table_key.short_name}'; choices:\n"
56
+ )
57
+ for tr in param.table_key.table.table_rows:
58
+ of.write(f" ('{tr.short_name}',\n")
59
+ of.write(f" {{\n")
60
+ of.write(
61
+ textwrap.indent(
62
+ parameter_info(odxrequire(tr.structure).parameters, True), " "))
63
+ of.write(f" }}),\n")
64
+
30
65
  continue
31
66
  elif not isinstance(param, ParameterWithDOP):
32
- result += f"{param.short_name} : <unhandled parameter type>\n"
67
+ of.write(
68
+ f"{q}{param.short_name}{q}: <unhandled parameter type '{type(param).__name__}'>\n")
33
69
  continue
34
70
 
35
71
  dop = param.dop
36
-
37
72
  if isinstance(dop, EndOfPduField):
38
- result += f"{param.short_name} : <optional> list({{\n"
39
- tmp = parameter_info(dop.structure.parameters).strip()
40
- tmp = re.sub("^", " ", tmp)
41
- result += tmp + "\n"
42
- result += f"}})\n"
73
+ of.write(f"{q}{param.short_name}{q}: list({{\n")
74
+ of.write(textwrap.indent(parameter_info(dop.structure.parameters, True), " "))
75
+ of.write(f"}})\n")
76
+ continue
77
+ elif isinstance(dop, StaticField):
78
+ of.write(f"{q}{param.short_name}{q}: length={dop.fixed_number_of_items}; list({{\n")
79
+ of.write(textwrap.indent(parameter_info(dop.structure.parameters, True), " "))
80
+ of.write(f"}})\n")
81
+ continue
82
+ elif isinstance(dop, DynamicLengthField):
83
+ of.write(f"{q}{param.short_name}{q}: list({{\n")
84
+ of.write(textwrap.indent(parameter_info(dop.structure.parameters, True), " "))
85
+ of.write(f"}})\n")
86
+ continue
87
+ elif isinstance(dop, ParamLengthInfoType):
88
+ of.write(f"{q}{param.short_name}{q}: ")
89
+ of.write("<optional> ")
90
+ of.write(f"int; length_key='{dop.length_key.short_name}'\n")
91
+ continue
92
+ elif isinstance(dop, DtcDop):
93
+ of.write(f"{q}{param.short_name}{q}: ")
94
+ of.write(f"DTC; choices:\n")
95
+ for dtc in dop.dtcs:
96
+ if dtc.display_trouble_code is not None:
97
+ dtc_desc = dtc.text and f"; \"{dtc.text}\""
98
+ of.write(
99
+ f" '{dtc.display_trouble_code}' (0x{dtc.trouble_code:06x}{dtc_desc})\n")
100
+ else:
101
+ dtc_desc = dtc.text and f" (\"{dtc.text}\")"
102
+ of.write(f" 0x{dtc.trouble_code:06x}{dtc_desc}\n")
103
+ continue
104
+ elif isinstance(dop, Multiplexer):
105
+ of.write(f"{q}{param.short_name}{q}: ")
106
+ if dop.default_case is not None:
107
+ of.write(f"<optional>")
108
+ of.write(f"multiplexer; choices:\n")
109
+ for mux_case in dop.cases:
110
+ of.write(f" ({repr(mux_case.short_name)}, {{\n")
111
+ of.write(
112
+ textwrap.indent(parameter_info(mux_case.structure.parameters, True), " "))
113
+ of.write(f" }})\n")
43
114
  continue
44
115
 
45
- result += f"{param.short_name}"
116
+ of.write(f"{q}{param.short_name}{q}")
46
117
 
47
118
  if dop is None:
48
- result += ": <no DOP>\n"
119
+ of.write(": <no DOP>\n")
49
120
  continue
50
121
  elif not isinstance(dop, DataObjectProperty):
51
- result += ": <unhandled DOP>\n"
122
+ of.write(f": <unhandled DOP '{type(dop).__name__}'>\n")
52
123
  continue
53
124
 
54
125
  if (cm := dop.compu_method) is None:
55
- result += ": <no compu method>\n"
126
+ of.write(": <no compu method>\n")
56
127
  continue
57
128
 
58
129
  if isinstance(cm, TexttableCompuMethod):
59
- result += f": enum; choices:\n"
130
+ of.write(f": enum; choices:\n")
60
131
  for scale in cm.internal_to_phys:
61
- result += f" '{str(scale.compu_const)}'\n"
132
+ val_str = ""
133
+ if scale.lower_limit is not None:
134
+ val_str = f"({repr(scale.lower_limit.value)})"
135
+ of.write(f" {repr(scale.compu_const)}{val_str}\n")
62
136
 
63
137
  elif isinstance(cm, IdenticalCompuMethod):
64
138
  bdt = dop.physical_type.base_data_type
65
139
  if bdt in (DataType.A_UTF8STRING, DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING):
66
- result += f": str"
67
- elif bdt in (DataType.A_BYTEFIELD,):
68
- result += f": bytes"
140
+ of.write(f": str")
141
+ elif bdt == DataType.A_BYTEFIELD:
142
+ of.write(f": bytes")
69
143
  elif bdt.name.startswith("A_FLOAT"):
70
- result += f": float"
144
+ of.write(f": float")
71
145
  elif bdt.name.startswith("A_UINT"):
72
- result += f": uint"
146
+ of.write(f": uint")
73
147
  elif bdt.name.startswith("A_INT"):
74
- result += f": int"
148
+ of.write(f": int")
75
149
  else:
76
- result += f": <unknown type>"
77
-
78
- if (bl := dop.get_static_bit_length()) is not None:
79
- result += f"{bl}"
150
+ of.write(f": <unknown type {{ bdt.name }}>")
80
151
 
81
- result += "\n"
152
+ of.write("\n")
82
153
 
83
154
  elif isinstance(cm, LinearCompuMethod):
84
- result += f": float\n"
155
+ bdt = dop.physical_type.base_data_type
156
+ if bdt in (DataType.A_UTF8STRING, DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING):
157
+ of.write(f": str")
158
+ elif bdt in (DataType.A_BYTEFIELD,):
159
+ of.write(f": bytes")
160
+ elif bdt.name.startswith("A_FLOAT"):
161
+ of.write(f": float")
162
+ elif bdt.name.startswith("A_UINT"):
163
+ of.write(f": uint")
164
+ elif bdt.name.startswith("A_INT"):
165
+ of.write(f": int")
166
+ else:
167
+ of.write(f": <unknown type>")
168
+
85
169
  ll = cm.physical_lower_limit
86
170
  ul = cm.physical_upper_limit
87
- if ll is None:
88
- ll_str = "(inf"
171
+ if ll is None or ll.interval_type == IntervalType.INFINITE:
172
+ ll_str = "(-inf"
89
173
  else:
90
- ll_delim = '[' if ll.interval_type == IntervalType.CLOSED else '('
174
+ ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
91
175
  ll_str = f"{ll_delim}{ll._value!r}"
92
176
 
93
- if ul is None:
177
+ if ul is None or ul.interval_type == IntervalType.INFINITE:
94
178
  ul_str = "inf)"
95
179
  else:
96
- ul_delim = ']' if ul.interval_type == IntervalType.CLOSED else ')'
180
+ ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
97
181
  ul_str = f"{ul._value!r}{ul_delim}"
98
- result += f" range: {ll_str}, {ul_str}\n"
182
+ of.write(f"; range: {ll_str}, {ul_str}")
99
183
 
100
184
  unit = dop.unit
101
185
  unit_str = unit.display_name if unit is not None else None
102
186
  if unit_str is not None:
103
- result += f" unit: {unit_str}\n"
187
+ of.write(f"; unit: {unit_str}")
188
+
189
+ of.write("\n")
104
190
 
105
- return result
191
+ return of.getvalue()
@@ -10,9 +10,9 @@ from ..createanydiagcodedtype import create_any_diag_coded_type_from_et
10
10
  from ..decodestate import DecodeState
11
11
  from ..diagcodedtype import DiagCodedType
12
12
  from ..encodestate import EncodeState
13
- from ..exceptions import DecodeError, odxrequire
13
+ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
14
14
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
- from ..odxtypes import AtomicOdxType, DataType
15
+ from ..odxtypes import AtomicOdxType, DataType, ParameterValue
16
16
  from ..utils import dataclass_fields_asdict
17
17
  from .parameter import Parameter, ParameterType
18
18
 
@@ -59,8 +59,9 @@ class CodedConstParameter(Parameter):
59
59
  super()._resolve_odxlinks(odxlinks)
60
60
 
61
61
  @override
62
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
63
- super()._resolve_snrefs(diag_layer)
62
+ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *,
63
+ param_list: List[Parameter]) -> None:
64
+ super()._parameter_resolve_snrefs(diag_layer, param_list=param_list)
64
65
 
65
66
  @override
66
67
  def get_static_bit_length(self) -> Optional[int]:
@@ -81,20 +82,23 @@ class CodedConstParameter(Parameter):
81
82
  return False
82
83
 
83
84
  @override
84
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
85
- if (self.short_name in encode_state.parameter_values and
86
- encode_state.parameter_values[self.short_name] != self.coded_value):
87
- raise TypeError(f"The parameter '{self.short_name}' is constant {self._coded_value_str}"
88
- " and thus can not be changed.")
89
- bit_position_int = self.bit_position if self.bit_position is not None else 0
90
- return self.diag_coded_type.convert_internal_to_bytes(
91
- self.coded_value, encode_state=encode_state, bit_position=bit_position_int)
85
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
86
+ encode_state: EncodeState) -> None:
87
+ if physical_value is not None and physical_value != self.coded_value:
88
+ odxraise(
89
+ f"Value for constant parameter `{self.short_name}` name can "
90
+ f"only be specified as {self.coded_value!r} (is: {physical_value!r})", EncodeError)
91
+
92
+ internal_value = self.coded_value
93
+
94
+ self.diag_coded_type.encode_into_pdu(
95
+ internal_value=internal_value, encode_state=encode_state)
92
96
 
93
97
  @override
94
98
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
95
99
  coded_val = self.diag_coded_type.decode_from_pdu(decode_state)
96
100
 
97
- # Check if the coded value in the message is correct.
101
+ # Check if the coded value contained by the message is correct.
98
102
  if self.coded_value != coded_val:
99
103
  warnings.warn(
100
104
  f"Coded constant parameter does not match! "
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -41,9 +41,10 @@ class DynamicParameter(Parameter):
41
41
  raise NotImplementedError(".is_settable for a DynamicParameter")
42
42
 
43
43
  @override
44
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
45
- raise NotImplementedError("Encoding a DynamicParameter is not implemented yet.")
44
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
45
+ encode_state: EncodeState) -> None:
46
+ raise NotImplementedError("Encoding DynamicParameter is not implemented yet.")
46
47
 
47
48
  @override
48
49
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
49
- raise NotImplementedError("Decoding a DynamicParameter is not implemented yet.")
50
+ raise NotImplementedError("Decoding DynamicParameter is not implemented yet.")