odxtools 6.6.0__py3-none-any.whl → 6.7.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 (81) hide show
  1. odxtools/__init__.py +5 -5
  2. odxtools/basicstructure.py +7 -8
  3. odxtools/cli/_parser_utils.py +15 -0
  4. odxtools/cli/_print_utils.py +4 -3
  5. odxtools/cli/browse.py +19 -14
  6. odxtools/cli/compare.py +24 -16
  7. odxtools/cli/decode.py +2 -1
  8. odxtools/cli/dummy_sub_parser.py +3 -1
  9. odxtools/cli/find.py +2 -1
  10. odxtools/cli/list.py +2 -1
  11. odxtools/cli/main.py +1 -0
  12. odxtools/cli/snoop.py +4 -1
  13. odxtools/comparaminstance.py +7 -5
  14. odxtools/compumethods/compumethod.py +2 -4
  15. odxtools/compumethods/compuscale.py +45 -5
  16. odxtools/compumethods/createanycompumethod.py +27 -35
  17. odxtools/compumethods/limit.py +70 -36
  18. odxtools/compumethods/linearcompumethod.py +68 -59
  19. odxtools/compumethods/tabintpcompumethod.py +19 -8
  20. odxtools/compumethods/texttablecompumethod.py +32 -36
  21. odxtools/dataobjectproperty.py +13 -10
  22. odxtools/decodestate.py +6 -3
  23. odxtools/determinenumberofitems.py +1 -1
  24. odxtools/diagcodedtype.py +5 -4
  25. odxtools/diagdatadictionaryspec.py +108 -83
  26. odxtools/diaglayer.py +75 -35
  27. odxtools/diaglayertype.py +17 -5
  28. odxtools/diagservice.py +1 -1
  29. odxtools/dopbase.py +4 -2
  30. odxtools/dtcdop.py +14 -8
  31. odxtools/dynamiclengthfield.py +6 -5
  32. odxtools/endofpdufield.py +4 -4
  33. odxtools/environmentdatadescription.py +4 -2
  34. odxtools/inputparam.py +1 -1
  35. odxtools/internalconstr.py +14 -5
  36. odxtools/isotp_state_machine.py +14 -6
  37. odxtools/message.py +1 -1
  38. odxtools/multiplexer.py +18 -13
  39. odxtools/multiplexercase.py +27 -5
  40. odxtools/multiplexerswitchkey.py +1 -1
  41. odxtools/nameditemlist.py +7 -6
  42. odxtools/odxlink.py +2 -2
  43. odxtools/odxtypes.py +56 -3
  44. odxtools/outputparam.py +2 -2
  45. odxtools/parameterinfo.py +12 -5
  46. odxtools/parameters/codedconstparameter.py +33 -12
  47. odxtools/parameters/createanyparameter.py +19 -193
  48. odxtools/parameters/dynamicparameter.py +21 -1
  49. odxtools/parameters/lengthkeyparameter.py +28 -4
  50. odxtools/parameters/matchingrequestparameter.py +27 -9
  51. odxtools/parameters/nrcconstparameter.py +34 -11
  52. odxtools/parameters/parameter.py +58 -32
  53. odxtools/parameters/parameterwithdop.py +28 -15
  54. odxtools/parameters/physicalconstantparameter.py +30 -10
  55. odxtools/parameters/reservedparameter.py +32 -18
  56. odxtools/parameters/systemparameter.py +25 -2
  57. odxtools/parameters/tableentryparameter.py +45 -6
  58. odxtools/parameters/tablekeyparameter.py +43 -10
  59. odxtools/parameters/tablestructparameter.py +36 -14
  60. odxtools/parameters/valueparameter.py +27 -3
  61. odxtools/paramlengthinfotype.py +4 -1
  62. odxtools/parentref.py +4 -1
  63. odxtools/scaleconstr.py +11 -5
  64. odxtools/statetransition.py +1 -1
  65. odxtools/staticfield.py +101 -0
  66. odxtools/table.py +2 -1
  67. odxtools/tablerow.py +11 -4
  68. odxtools/templates/macros/printDOP.xml.jinja2 +30 -34
  69. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  70. odxtools/templates/macros/printParam.xml.jinja2 +9 -9
  71. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  72. odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
  73. odxtools/uds.py +2 -2
  74. odxtools/version.py +2 -2
  75. odxtools/write_pdx_file.py +3 -3
  76. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/METADATA +28 -16
  77. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/RECORD +81 -79
  78. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/WHEEL +1 -1
  79. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/LICENSE +0 -0
  80. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/entry_points.txt +0 -0
  81. {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
4
7
 
5
8
  from ..decodestate import DecodeState
6
9
  from ..encodestate import EncodeState
7
10
  from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
8
- from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
11
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
12
  from ..odxtypes import ParameterValue
13
+ from ..utils import dataclass_fields_asdict
10
14
  from .parameter import Parameter, ParameterType
11
15
 
12
16
  if TYPE_CHECKING:
@@ -24,6 +28,33 @@ class TableKeyParameter(Parameter):
24
28
  table_row_snref: Optional[str]
25
29
  table_row_ref: Optional[OdxLinkRef]
26
30
 
31
+ @staticmethod
32
+ @override
33
+ def from_et(et_element: ElementTree.Element,
34
+ doc_frags: List[OdxDocFragment]) -> "TableKeyParameter":
35
+
36
+ kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags))
37
+
38
+ odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags))
39
+
40
+ table_ref = OdxLinkRef.from_et(et_element.find("TABLE-REF"), doc_frags)
41
+ table_snref = None
42
+ if (table_snref_elem := et_element.find("TABLE-SNREF")) is not None:
43
+ table_snref = odxrequire(table_snref_elem.get("SHORT-NAME"))
44
+
45
+ table_row_ref = OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags)
46
+ table_row_snref = None
47
+ if (table_row_snref_elem := et_element.find("TABLE-ROW-SNREF")) is not None:
48
+ table_row_snref = odxrequire(table_row_snref_elem.get("SHORT-NAME"))
49
+
50
+ return TableKeyParameter(
51
+ odx_id=odx_id,
52
+ table_ref=table_ref,
53
+ table_snref=table_snref,
54
+ table_row_ref=table_row_ref,
55
+ table_row_snref=table_row_snref,
56
+ **kwargs)
57
+
27
58
  def __post_init__(self) -> None:
28
59
  self._table: "Table"
29
60
  self._table_row: Optional["TableRow"] = None
@@ -32,9 +63,11 @@ class TableKeyParameter(Parameter):
32
63
  odxraise("Either a table or a table row must be defined.")
33
64
 
34
65
  @property
66
+ @override
35
67
  def parameter_type(self) -> ParameterType:
36
68
  return "TABLE-KEY"
37
69
 
70
+ @override
38
71
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
39
72
  result = super()._build_odxlinks()
40
73
 
@@ -42,6 +75,7 @@ class TableKeyParameter(Parameter):
42
75
 
43
76
  return result
44
77
 
78
+ @override
45
79
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
46
80
  super()._resolve_odxlinks(odxlinks)
47
81
 
@@ -59,6 +93,7 @@ class TableKeyParameter(Parameter):
59
93
  self._table_row = odxlinks.resolve(self.table_row_ref)
60
94
  self._table = self._table_row.table
61
95
 
96
+ @override
62
97
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
63
98
  super()._resolve_snrefs(diag_layer)
64
99
 
@@ -86,15 +121,18 @@ class TableKeyParameter(Parameter):
86
121
  return self._table_row
87
122
 
88
123
  @property
124
+ @override
89
125
  def is_required(self) -> bool:
90
126
  # TABLE-KEY parameters can be implicitly determined from the
91
127
  # corresponding TABLE-STRUCT
92
128
  return False
93
129
 
94
130
  @property
131
+ @override
95
132
  def is_settable(self) -> bool:
96
133
  return True
97
134
 
135
+ @override
98
136
  def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
99
137
  tr_short_name = encode_state.parameter_values.get(self.short_name)
100
138
 
@@ -132,14 +170,12 @@ class TableKeyParameter(Parameter):
132
170
  bit_position = 0 if self.bit_position is None else self.bit_position
133
171
  return key_dop.convert_physical_to_bytes(tr.key, encode_state, bit_position=bit_position)
134
172
 
173
+ @override
135
174
  def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
136
175
  return super().encode_into_pdu(encode_state)
137
176
 
138
- def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
139
- orig_cursor = decode_state.cursor_byte_position
140
- if self.byte_position is not None:
141
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
142
-
177
+ @override
178
+ def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
143
179
  if self.table_row is not None:
144
180
  # the table row to be used is statically specified -> no
145
181
  # need to decode anything!
@@ -147,7 +183,6 @@ class TableKeyParameter(Parameter):
147
183
  else:
148
184
  # Use DOP to decode
149
185
  key_dop = odxrequire(self.table.key_dop)
150
- decode_state.cursor_bit_position = self.bit_position or 0
151
186
  key_dop_val = key_dop.decode_from_pdu(decode_state)
152
187
 
153
188
  table_row_candidates = [x for x in self.table.table_rows if x.key == key_dop_val]
@@ -162,6 +197,4 @@ class TableKeyParameter(Parameter):
162
197
  # update the decode_state's table key
163
198
  decode_state.table_keys[self.short_name] = table_row
164
199
 
165
- decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor)
166
-
167
200
  return phys_val
@@ -1,13 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, Optional, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
5
+ from xml.etree import ElementTree
6
+
7
+ from typing_extensions import override
5
8
 
6
9
  from ..decodestate import DecodeState
7
10
  from ..encodestate import EncodeState
8
- from ..exceptions import EncodeError, OdxWarning, odxraise
9
- from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
11
+ from ..exceptions import DecodeError, EncodeError, OdxWarning, odxraise, odxrequire
12
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
13
  from ..odxtypes import ParameterValue
14
+ from ..utils import dataclass_fields_asdict
11
15
  from .parameter import Parameter, ParameterType
12
16
  from .tablekeyparameter import TableKeyParameter
13
17
 
@@ -21,23 +25,42 @@ class TableStructParameter(Parameter):
21
25
  table_key_ref: Optional[OdxLinkRef]
22
26
  table_key_snref: Optional[str]
23
27
 
28
+ @staticmethod
29
+ @override
30
+ def from_et(et_element: ElementTree.Element,
31
+ doc_frags: List[OdxDocFragment]) -> "TableStructParameter":
32
+
33
+ kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags))
34
+
35
+ table_key_ref = OdxLinkRef.from_et(et_element.find("TABLE-KEY-REF"), doc_frags)
36
+ table_key_snref = None
37
+ if (table_key_snref_elem := et_element.find("TABLE-KEY-SNREF")) is not None:
38
+ table_key_snref = odxrequire(table_key_snref_elem.get("SHORT-NAME"))
39
+
40
+ return TableStructParameter(
41
+ table_key_ref=table_key_ref, table_key_snref=table_key_snref, **kwargs)
42
+
24
43
  def __post_init__(self) -> None:
25
44
  if self.table_key_ref is None and self.table_key_snref is None:
26
45
  odxraise("Either table_key_ref or table_key_snref must be defined.")
27
46
 
28
47
  @property
48
+ @override
29
49
  def parameter_type(self) -> ParameterType:
30
50
  return "TABLE-STRUCT"
31
51
 
52
+ @override
32
53
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
33
54
  return super()._build_odxlinks()
34
55
 
56
+ @override
35
57
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
36
58
  super()._resolve_odxlinks(odxlinks)
37
59
 
38
60
  if self.table_key_ref is not None:
39
- self._table_key = odxlinks.resolve(self.table_key_ref)
61
+ self._table_key = odxlinks.resolve(self.table_key_ref, TableKeyParameter)
40
62
 
63
+ @override
41
64
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
42
65
  super()._resolve_snrefs(diag_layer)
43
66
 
@@ -53,13 +76,16 @@ class TableStructParameter(Parameter):
53
76
  return self._table_key
54
77
 
55
78
  @property
79
+ @override
56
80
  def is_required(self) -> bool:
57
81
  return True
58
82
 
59
83
  @property
84
+ @override
60
85
  def is_settable(self) -> bool:
61
86
  return True
62
87
 
88
+ @override
63
89
  def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
64
90
  physical_value = encode_state.parameter_values.get(self.short_name)
65
91
 
@@ -122,22 +148,21 @@ class TableStructParameter(Parameter):
122
148
  return tr.dop.convert_physical_to_bytes(
123
149
  tr_value, encode_state=encode_state, bit_position=bit_position)
124
150
 
151
+ @override
125
152
  def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
126
153
  return super().encode_into_pdu(encode_state)
127
154
 
128
- def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
129
- orig_cursor = decode_state.cursor_byte_position
130
- if self.byte_position is not None:
131
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
132
-
155
+ @override
156
+ def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
133
157
  # find the selected table row
134
158
  key_name = self.table_key.short_name
135
159
 
136
160
  decode_state.table_keys[key_name]
137
161
  table_row = decode_state.table_keys.get(key_name)
138
162
  if table_row is None:
139
- raise odxraise(f"No table key '{key_name}' found when decoding "
140
- f"table struct parameter '{str(self.short_name)}'")
163
+ odxraise(
164
+ f"No table key '{key_name}' found when decoding "
165
+ f"table struct parameter '{str(self.short_name)}'", DecodeError)
141
166
  dummy_val = cast(str, None), cast(int, None)
142
167
  return dummy_val
143
168
 
@@ -145,14 +170,11 @@ class TableStructParameter(Parameter):
145
170
  if table_row.dop is not None:
146
171
  dop = table_row.dop
147
172
  val = dop.decode_from_pdu(decode_state)
148
- decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor)
149
173
  return (table_row.short_name, val)
150
174
  elif table_row.structure is not None:
151
175
  val = table_row.structure.decode_from_pdu(decode_state)
152
- decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor)
153
176
  return (table_row.short_name, val)
154
177
  else:
155
178
  # the table row associated with the key neither defines a
156
179
  # DOP nor a structure -> ignore it
157
- decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor)
158
180
  return (table_row.short_name, cast(int, None))
@@ -1,12 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
4
7
 
5
8
  from ..dataobjectproperty import DataObjectProperty
6
9
  from ..encodestate import EncodeState
7
10
  from ..exceptions import odxraise, odxrequire
8
- from ..odxlink import OdxLinkDatabase, OdxLinkId
11
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
12
  from ..odxtypes import AtomicOdxType
13
+ from ..utils import dataclass_fields_asdict
10
14
  from .parameter import ParameterType
11
15
  from .parameterwithdop import ParameterWithDOP
12
16
 
@@ -18,20 +22,37 @@ if TYPE_CHECKING:
18
22
  class ValueParameter(ParameterWithDOP):
19
23
  physical_default_value_raw: Optional[str]
20
24
 
25
+ def __post_init__(self) -> None:
26
+ self._physical_default_value: Optional[AtomicOdxType] = None
27
+
28
+ @staticmethod
29
+ @override
30
+ def from_et(et_element: ElementTree.Element,
31
+ doc_frags: List[OdxDocFragment]) -> "ValueParameter":
32
+
33
+ kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags))
34
+
35
+ physical_default_value_raw = et_element.findtext("PHYSICAL-DEFAULT-VALUE")
36
+
37
+ return ValueParameter(physical_default_value_raw=physical_default_value_raw, **kwargs)
38
+
21
39
  @property
40
+ @override
22
41
  def parameter_type(self) -> ParameterType:
23
42
  return "VALUE"
24
43
 
44
+ @override
25
45
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
26
46
  return super()._build_odxlinks()
27
47
 
48
+ @override
28
49
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
29
50
  super()._resolve_odxlinks(odxlinks)
30
51
 
52
+ @override
31
53
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
32
54
  super()._resolve_snrefs(diag_layer)
33
55
 
34
- self._physical_default_value: Optional[AtomicOdxType] = None
35
56
  if self.physical_default_value_raw is not None:
36
57
  dop = odxrequire(self.dop)
37
58
  if not isinstance(dop, DataObjectProperty):
@@ -46,13 +67,16 @@ class ValueParameter(ParameterWithDOP):
46
67
  return self._physical_default_value
47
68
 
48
69
  @property
70
+ @override
49
71
  def is_required(self) -> bool:
50
72
  return self._physical_default_value is None
51
73
 
52
74
  @property
75
+ @override
53
76
  def is_settable(self) -> bool:
54
77
  return True
55
78
 
79
+ @override
56
80
  def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
57
81
  physical_value = encode_state.parameter_values.get(self.short_name,
58
82
  self.physical_default_value)
@@ -30,7 +30,10 @@ class ParamLengthInfoType(DiagCodedType):
30
30
  """Recursively resolve any odxlinks references"""
31
31
  super()._resolve_odxlinks(odxlinks)
32
32
 
33
- self._length_key = odxlinks.resolve(self.length_key_ref)
33
+ if TYPE_CHECKING:
34
+ self._length_key = odxlinks.resolve(self.length_key_ref, LengthKeyParameter)
35
+ else:
36
+ self._length_key = odxlinks.resolve(self.length_key_ref)
34
37
 
35
38
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
36
39
  """Recursively resolve any short-name references"""
odxtools/parentref.py CHANGED
@@ -71,7 +71,10 @@ class ParentRef:
71
71
  return {}
72
72
 
73
73
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
74
- self._layer = odxlinks.resolve(self.layer_ref)
74
+ if TYPE_CHECKING:
75
+ self._layer = odxlinks.resolve(self.layer_ref, DiagLayer)
76
+ else:
77
+ self._layer = odxlinks.resolve(self.layer_ref)
75
78
 
76
79
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
77
80
  pass
odxtools/scaleconstr.py CHANGED
@@ -1,11 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Optional
4
+ from typing import List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .compumethods.limit import Limit
8
8
  from .exceptions import odxraise, odxrequire
9
+ from .odxlink import OdxDocFragment
9
10
  from .odxtypes import DataType
10
11
  from .utils import create_description_from_et
11
12
 
@@ -27,14 +28,18 @@ class ScaleConstr:
27
28
  lower_limit: Optional[Limit]
28
29
  upper_limit: Optional[Limit]
29
30
  validity: ValidType
31
+ value_type: DataType
30
32
 
31
33
  @staticmethod
32
- def from_et(et_element: ElementTree.Element, internal_type: DataType) -> "ScaleConstr":
34
+ def scale_constr_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
35
+ value_type: DataType) -> "ScaleConstr":
33
36
  short_label = et_element.findtext("SHORT-LABEL")
34
37
  description = create_description_from_et(et_element.find("DESC"))
35
38
 
36
- lower_limit = Limit.from_et(et_element.find("LOWER-LIMIT"), internal_type=internal_type)
37
- upper_limit = Limit.from_et(et_element.find("UPPER-LIMIT"), internal_type=internal_type)
39
+ lower_limit = Limit.limit_from_et(
40
+ et_element.find("LOWER-LIMIT"), doc_frags, value_type=value_type)
41
+ upper_limit = Limit.limit_from_et(
42
+ et_element.find("UPPER-LIMIT"), doc_frags, value_type=value_type)
38
43
 
39
44
  validity_str = odxrequire(et_element.get("VALIDITY"))
40
45
  try:
@@ -47,4 +52,5 @@ class ScaleConstr:
47
52
  description=description,
48
53
  lower_limit=lower_limit,
49
54
  upper_limit=upper_limit,
50
- validity=validity)
55
+ validity=validity,
56
+ value_type=value_type)
@@ -20,7 +20,7 @@ class StateTransition(IdentifiableElement):
20
20
  """
21
21
  source_snref: str
22
22
  target_snref: str
23
- #external_access_method: Optional[ExternalAccessMethod] # TODO
23
+ # external_access_method: Optional[ExternalAccessMethod] # TODO
24
24
 
25
25
  @property
26
26
  def source_state(self) -> State:
@@ -0,0 +1,101 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
7
+
8
+ from .decodestate import DecodeState
9
+ from .encodestate import EncodeState
10
+ from .exceptions import odxassert, odxraise, odxrequire
11
+ from .field import Field
12
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
13
+ from .odxtypes import ParameterValue
14
+ from .utils import dataclass_fields_asdict
15
+
16
+ if TYPE_CHECKING:
17
+ from .diaglayer import DiagLayer
18
+
19
+
20
+ @dataclass
21
+ class StaticField(Field):
22
+ """Array of a fixed number of structure objects"""
23
+ fixed_number_of_items: int
24
+ item_byte_size: int
25
+
26
+ @staticmethod
27
+ @override
28
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "StaticField":
29
+ kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
30
+
31
+ fixed_number_of_items = int(odxrequire(et_element.findtext('FIXED-NUMBER-OF-ITEMS')))
32
+ item_byte_size = int(odxrequire(et_element.findtext('ITEM-BYTE-SIZE')))
33
+
34
+ return StaticField(
35
+ fixed_number_of_items=fixed_number_of_items, item_byte_size=item_byte_size, **kwargs)
36
+
37
+ @override
38
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
39
+ odxlinks = super()._build_odxlinks()
40
+ return odxlinks
41
+
42
+ @override
43
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
44
+ super()._resolve_odxlinks(odxlinks)
45
+
46
+ @override
47
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
48
+ super()._resolve_snrefs(diag_layer)
49
+
50
+ @override
51
+ def convert_physical_to_bytes(
52
+ self,
53
+ physical_value: ParameterValue,
54
+ encode_state: EncodeState,
55
+ bit_position: int = 0,
56
+ ) -> bytes:
57
+ if not isinstance(physical_value,
58
+ (tuple, list)) or len(physical_value) != self.fixed_number_of_items:
59
+ odxraise(f"Value for static field '{self.short_name}' "
60
+ f"must be a list of size {self.fixed_number_of_items}")
61
+
62
+ result = bytearray()
63
+ for val in physical_value:
64
+ if not isinstance(val, dict):
65
+ odxraise(f"The individual parameter values for static field '{self.short_name}' "
66
+ f"must be dictionaries for structure '{self.structure.short_name}'")
67
+
68
+ data = self.structure.convert_physical_to_bytes(val, encode_state)
69
+
70
+ if len(data) > self.item_byte_size:
71
+ odxraise(f"Insufficient item byte size for static field {self.short_name}: "
72
+ f"Is {self.item_byte_size} bytes, but need at least {len(data)} bytes")
73
+ data = data[:self.item_byte_size]
74
+ elif len(data) < self.item_byte_size:
75
+ # add some padding bytes
76
+ data = data.ljust(self.item_byte_size, b'\x00')
77
+
78
+ result += data
79
+
80
+ return result
81
+
82
+ @override
83
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
84
+
85
+ odxassert(decode_state.cursor_bit_position == 0,
86
+ "No bit position can be specified for static length fields!")
87
+
88
+ result: List[ParameterValue] = []
89
+ for _ in range(self.fixed_number_of_items):
90
+ orig_cursor = decode_state.cursor_byte_position
91
+
92
+ if decode_state.cursor_byte_position - orig_cursor > self.item_byte_size:
93
+ odxraise(f"Insufficient item byte size for static field {self.short_name}: "
94
+ f"Is {self.item_byte_size} bytes, but need at least "
95
+ f"{decode_state.cursor_byte_position - orig_cursor} bytes")
96
+
97
+ result.append(self.structure.decode_from_pdu(decode_state))
98
+
99
+ decode_state.cursor_byte_position = orig_cursor + self.item_byte_size
100
+
101
+ return result
odxtools/table.py CHANGED
@@ -45,7 +45,8 @@ class Table(IdentifiableElement):
45
45
  for sub_elem in et_element:
46
46
  if sub_elem.tag == "TABLE-ROW":
47
47
  table_rows_raw.append(
48
- TableRow.from_et(sub_elem, doc_frags, table_ref=OdxLinkRef.from_id(odx_id)))
48
+ TableRow.tablerow_from_et(
49
+ sub_elem, doc_frags, table_ref=OdxLinkRef.from_id(odx_id)))
49
50
  elif sub_elem.tag == "TABLE-ROW-REF":
50
51
  table_rows_raw.append(OdxLinkRef.from_et(sub_elem, doc_frags))
51
52
 
odxtools/tablerow.py CHANGED
@@ -51,9 +51,13 @@ class TableRow(IdentifiableElement):
51
51
  )
52
52
 
53
53
  @staticmethod
54
- def from_et( # type: ignore[override]
55
- et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
56
- table_ref: OdxLinkRef) -> "TableRow":
54
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> Any:
55
+ raise RuntimeError(
56
+ "Calling TableRow.from_et() is not allowed. Use TableRow.tablerow_from_et().")
57
+
58
+ @staticmethod
59
+ def tablerow_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
60
+ table_ref: OdxLinkRef) -> "TableRow":
57
61
  """Reads a TABLE-ROW."""
58
62
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
59
63
  semantic = et_element.get("SEMANTIC")
@@ -95,7 +99,10 @@ class TableRow(IdentifiableElement):
95
99
  if not isinstance(self._dop, (DataObjectProperty, DtcDop)):
96
100
  odxraise("The DOP-REF of TABLE-ROWs must reference a simple DOP!")
97
101
 
98
- self._table = odxlinks.resolve(self.table_ref)
102
+ if TYPE_CHECKING:
103
+ self._table = odxlinks.resolve(self.table_ref, Table)
104
+ else:
105
+ self._table = odxlinks.resolve(self.table_ref)
99
106
 
100
107
  for sdg in self.sdgs:
101
108
  sdg._resolve_odxlinks(odxlinks)
@@ -43,12 +43,24 @@
43
43
  {%- endif %}
44
44
  {%- endmacro -%}
45
45
 
46
- {%- macro printLimitValue(lv) -%}
47
- {%- if hasattr(lv, 'hex') -%}
48
- {#- bytes or bytarray limit #}
49
- {{lv.hex()}}
50
- {%- else -%}
51
- {{lv}}
46
+ {%- macro printLimit(tag_name, limit_obj) -%}
47
+ {%- if limit_obj is not none %}
48
+ <{{tag_name}}
49
+ {%- if limit_obj.interval_type is not none %}
50
+ {{- make_xml_attrib("INTERVAL-TYPE", limit_obj.interval_type.value) }}
51
+ {%- endif %}
52
+ {%- if limit_obj.value_raw is none %}
53
+ {#- #}/>
54
+ {%- else %}
55
+ {#- #}>
56
+ {%- if hasattr(limit_obj._value, 'hex') -%}
57
+ {#- bytes or bytarray limit #}
58
+ {{- limit_obj._value.hex().upper() }}
59
+ {%- else -%}
60
+ {{- limit_obj._value }}
61
+ {%- endif -%}
62
+ </{{tag_name}}>
63
+ {%- endif -%}
52
64
  {%- endif -%}
53
65
  {%- endmacro -%}
54
66
 
@@ -62,8 +74,8 @@
62
74
  {{sc.description}}
63
75
  </DESC>
64
76
  {%- endif %}
65
- <LOWER-LIMIT>{{printLimitValue(sc.lower_limit.value)}}</LOWER-LIMIT>
66
- <UPPER-LIMIT>{{printLimitValue(sc.upper_limit.value)}}</UPPER-LIMIT>
77
+ {{printLimit("LOWER-LIMIT", sc.lower_limit) }}
78
+ {{printLimit("UPPER-LIMIT", sc.upper_limit) }}
67
79
  </SCALE-CONSTR>
68
80
  {%- endmacro -%}
69
81
 
@@ -73,12 +85,8 @@
73
85
  {%- else %}
74
86
  <INTERNAL-CONSTR>
75
87
  {%- endif %}
76
- {%- if ic.lower_limit %}
77
- <LOWER-LIMIT>{{printLimitValue(ic.lower_limit.value)}}</LOWER-LIMIT>
78
- {%- endif %}
79
- {%- if ic.upper_limit %}
80
- <UPPER-LIMIT>{{printLimitValue(ic.upper_limit.value)}}</UPPER-LIMIT>
81
- {%- endif %}
88
+ {{printLimit("LOWER-LIMIT", ic.lower_limit) }}
89
+ {{printLimit("UPPER-LIMIT", ic.upper_limit) }}
82
90
  {%- if ic.scale_constrs %}
83
91
  <SCALE-CONSTRS>
84
92
  {%- for sc in ic.scale_constrs %}
@@ -109,12 +117,8 @@
109
117
  {{cs.description}}
110
118
  </DESC>
111
119
  {%- endif %}
112
- {%- if cs.lower_limit is not none %}
113
- <LOWER-LIMIT>{{printLimitValue(cs.lower_limit.value)}}</LOWER-LIMIT>
114
- {%- endif %}
115
- {%- if cs.upper_limit is not none %}
116
- <UPPER-LIMIT>{{printLimitValue(cs.upper_limit.value)}}</UPPER-LIMIT>
117
- {%- endif %}
120
+ {{printLimit("LOWER-LIMIT", cs.lower_limit) }}
121
+ {{printLimit("UPPER-LIMIT", cs.upper_limit) }}
118
122
  {%- if cs.compu_inverse_value is not none %}
119
123
  <COMPU-INVERSE-VALUE>
120
124
  <V>{{cs.compu_inverse_value}}</V>
@@ -141,12 +145,8 @@
141
145
  <COMPU-INTERNAL-TO-PHYS>
142
146
  <COMPU-SCALES>
143
147
  <COMPU-SCALE>
144
- {%- if cm.internal_lower_limit is not none and cm.internal_lower_limit.interval_type.value != "INFINITE" %}
145
- <LOWER-LIMIT>{{printLimitValue(cm.internal_lower_limit.value)}}</LOWER-LIMIT>
146
- {%- endif %}
147
- {%- if cm.internal_upper_limit is not none and cm.internal_upper_limit.interval_type.value != "INFINITE" %}
148
- <UPPER-LIMIT>{{printLimitValue(cm.internal_upper_limit.value)}}</UPPER-LIMIT>
149
- {%- endif %}
148
+ {{printLimit("LOWER-LIMIT", cm.internal_lower_limit) }}
149
+ {{printLimit("UPPER-LIMIT", cm.internal_upper_limit) }}
150
150
  <COMPU-RATIONAL-COEFFS>
151
151
  <COMPU-NUMERATOR>
152
152
  <V>{{cm.offset}}</V>
@@ -166,12 +166,8 @@
166
166
  <COMPU-SCALES>
167
167
  {%- for lm in cm.linear_methods %}
168
168
  <COMPU-SCALE>
169
- {%- if lm.internal_lower_limit is not none and lm.internal_lower_limit.interval_type.value != "INFINITE" %}
170
- <LOWER-LIMIT>{{printLimitValue(lm.internal_lower_limit.value)}}</LOWER-LIMIT>
171
- {%- endif %}
172
- {%- if lm.internal_upper_limit is not none and lm.internal_upper_limit.interval_type.value != "INFINITE" %}
173
- <UPPER-LIMIT>{{printLimitValue(lm.internal_upper_limit.value)}}</UPPER-LIMIT>
174
- {%- endif %}
169
+ {{printLimit("LOWER-LIMIT", lm.internal_lower_limit) }}
170
+ {{printLimit("UPPER-LIMIT", lm.internal_upper_limit) }}
175
171
  <COMPU-RATIONAL-COEFFS>
176
172
  <COMPU-NUMERATOR>
177
173
  <V>{{lm.offset}}</V>
@@ -191,8 +187,8 @@
191
187
  <COMPU-INTERNAL-TO-PHYS>
192
188
  <COMPU-SCALES>
193
189
  {%- for idx in range( cm.internal_points | length ) %}
194
- <COMPU-SCALE>
195
- <LOWER-LIMIT INTERVAL-TYPE="CLOSED">{{ printLimitValue(cm.internal_points[idx]) }}</LOWER-LIMIT>
190
+ <COMPU-SCALE>
191
+ <LOWER-LIMIT>{{ cm.internal_points[idx] }}</LOWER-LIMIT>
196
192
  <COMPU-CONST>
197
193
  <V>{{ cm.physical_points[idx] }}</V>
198
194
  </COMPU-CONST>