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
@@ -3,12 +3,12 @@ from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from typing_extensions import override
6
+ from typing_extensions import final, override
7
7
 
8
8
  from ..decodestate import DecodeState
9
9
  from ..encodestate import EncodeState
10
10
  from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
11
- from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
11
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
12
12
  from ..odxtypes import ParameterValue
13
13
  from ..utils import dataclass_fields_asdict
14
14
  from .parameter import Parameter, ParameterType
@@ -94,19 +94,19 @@ class TableKeyParameter(Parameter):
94
94
  self._table = self._table_row.table
95
95
 
96
96
  @override
97
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
98
- super()._resolve_snrefs(diag_layer)
97
+ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *,
98
+ param_list: List[Parameter]) -> None:
99
+ super()._parameter_resolve_snrefs(diag_layer, param_list=param_list)
99
100
 
100
101
  if self.table_snref is not None:
101
- ddd_spec = diag_layer.diag_data_dictionary_spec
102
- self._table = ddd_spec.tables[self.table_snref]
102
+ tables = diag_layer.diag_data_dictionary_spec.tables
103
+ self._table = resolve_snref(self.table_snref, tables, Table)
103
104
  if self.table_row_snref is not None:
104
105
  # make sure that we know the table to which the table row
105
106
  # SNREF is relative to.
106
- table = odxrequire(
107
- self._table, "If a table-row short name reference is defined, a "
108
- "table must also be specified.")
109
- self._table_row = table.table_rows[self.table_row_snref]
107
+ table = odxrequire(self._table,
108
+ "If a table-row is referenced, a table must also be referenced.")
109
+ self._table_row = resolve_snref(self.table_row_snref, table.table_rows, TableRow)
110
110
 
111
111
  @property
112
112
  def table(self) -> "Table":
@@ -133,46 +133,95 @@ class TableKeyParameter(Parameter):
133
133
  return True
134
134
 
135
135
  @override
136
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
137
- tr_short_name = encode_state.parameter_values.get(self.short_name)
138
-
139
- if tr_short_name is None:
140
- # the table key has not been defined explicitly yet, but
141
- # it is most likely implicitly defined by the associated
142
- # TABLE-STRUCT parameters. Use all-zeros as a standin for
143
- # the real data...
136
+ @final
137
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
138
+ encode_state: EncodeState) -> None:
139
+ # if you get this exception, you ought to use
140
+ # `.encode_placeholder_into_pdu()` followed by (after the
141
+ # value of the table key has been determined)
142
+ # `.encode_value_into_pdu()`.
143
+ raise RuntimeError("_encode_positioned_into_pdu() cannot be called for table keys.")
144
+
145
+ def encode_placeholder_into_pdu(self, physical_value: Optional[ParameterValue],
146
+ encode_state: EncodeState) -> None:
147
+
148
+ if physical_value is not None:
144
149
  key_dop = self.table.key_dop
145
150
  if key_dop is None:
146
- raise EncodeError(f"Table '{self.table.short_name}' does not define "
147
- f"a KEY-DOP, but is used in TABLE-KEY parameter "
148
- f"'{self.short_name}'")
151
+ odxraise(
152
+ f"Table '{self.table.short_name}' does not define "
153
+ f"a KEY-DOP, but is used by TABLE-KEY parameter "
154
+ f"'{self.short_name}'", EncodeError)
155
+ return
156
+
157
+ if not isinstance(physical_value, str):
158
+ odxraise(f"Invalid type for for table key '{self.short_name}' specified. "
159
+ f"(expect name of table row.)")
160
+
161
+ tkv = encode_state.table_keys.get(self.short_name)
162
+ if tkv is not None and tkv != physical_value:
163
+ odxraise(f"Got conflicting values for table key {self.short_name}: "
164
+ f"{tkv} and {physical_value!r}")
165
+
166
+ encode_state.table_keys[self.short_name] = physical_value
167
+
168
+ orig_pos = encode_state.cursor_byte_position
169
+ pos = encode_state.cursor_byte_position
170
+ if self.byte_position is not None:
171
+ pos = encode_state.origin_byte_position + self.byte_position
172
+ encode_state.key_pos[self.short_name] = pos
173
+ encode_state.cursor_byte_position = pos
174
+ encode_state.cursor_bit_position = self.bit_position or 0
149
175
 
150
- byte_len = (odxrequire(key_dop.get_static_bit_length()) + 7) // 8
151
- if self.bit_position is not None and self.bit_position > 0:
152
- byte_len += 1
176
+ key_dop = self.table.key_dop
177
+ if key_dop is None:
178
+ odxraise(f"No KEY-DOP specified for table {self.table.short_name}")
179
+ return
180
+
181
+ sz = key_dop.get_static_bit_length()
182
+ if sz is None:
183
+ odxraise("The DOP of table key {self.short_name} must exhibit a fixed size.",
184
+ EncodeError)
185
+ return
186
+
187
+ # emplace a value of zero into the encode state, but pretend the bits not to be used
188
+ n = sz + encode_state.cursor_bit_position
189
+ tmp_val = b'\x00' * ((n + 7) // 8)
190
+ encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
191
+
192
+ encode_state.cursor_byte_position = max(orig_pos, encode_state.cursor_byte_position)
193
+ encode_state.cursor_bit_position = 0
194
+
195
+ def encode_value_into_pdu(self, encode_state: EncodeState) -> None:
153
196
 
154
- return bytes([0] * byte_len)
197
+ key_dop = self.table.key_dop
198
+ if key_dop is None:
199
+ odxraise(
200
+ f"Table '{self.table.short_name}' does not define "
201
+ f"a KEY-DOP, but is used by TABLE-KEY parameter "
202
+ f"'{self.short_name}'", EncodeError)
203
+ return
204
+
205
+ if self.short_name not in encode_state.table_keys:
206
+ odxraise(f"Table key {self.short_name} has not been defined before "
207
+ f"it is required.", EncodeError)
208
+ return
209
+ else:
210
+ tr_short_name = encode_state.table_keys[self.short_name]
155
211
 
156
- # the table key is known. We need to encode the associated DOP
157
- # into the PDU.
212
+ # We need to encode the table key using the associated DOP into the PDU.
158
213
  tr_candidates = [x for x in self.table.table_rows if x.short_name == tr_short_name]
159
214
  if len(tr_candidates) == 0:
160
- raise EncodeError(f"No table row with short name '{tr_short_name}' found")
215
+ odxraise(f"No table row with short name '{tr_short_name}' found", EncodeError)
216
+ return
161
217
  elif len(tr_candidates) > 1:
162
- raise EncodeError(f"Multiple rows exhibiting short name '{tr_short_name}'")
218
+ odxraise(f"Multiple rows exhibiting short name '{tr_short_name}'", EncodeError)
163
219
  tr = tr_candidates[0]
164
220
 
165
- key_dop = self.table.key_dop
166
- if key_dop is None:
167
- raise EncodeError(f"Table '{self.table.short_name}' does not define "
168
- f"a KEY-DOP, but is used in TABLE-KEY parameter "
169
- f"'{self.short_name}'")
170
- bit_position = 0 if self.bit_position is None else self.bit_position
171
- return key_dop.convert_physical_to_bytes(tr.key, encode_state, bit_position=bit_position)
221
+ encode_state.cursor_byte_position = encode_state.key_pos[self.short_name]
222
+ encode_state.cursor_bit_position = self.bit_position or 0
172
223
 
173
- @override
174
- def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
175
- return super().encode_into_pdu(encode_state)
224
+ key_dop.encode_into_pdu(encode_state=encode_state, physical_value=odxrequire(tr.key))
176
225
 
177
226
  @override
178
227
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -1,5 +1,4 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import warnings
3
2
  from dataclasses import dataclass
4
3
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
5
4
  from xml.etree import ElementTree
@@ -8,8 +7,8 @@ from typing_extensions import override
8
7
 
9
8
  from ..decodestate import DecodeState
10
9
  from ..encodestate import EncodeState
11
- from ..exceptions import DecodeError, EncodeError, OdxWarning, odxraise, odxrequire
12
- from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
11
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
13
12
  from ..odxtypes import ParameterValue
14
13
  from ..utils import dataclass_fields_asdict
15
14
  from .parameter import Parameter, ParameterType
@@ -61,15 +60,12 @@ class TableStructParameter(Parameter):
61
60
  self._table_key = odxlinks.resolve(self.table_key_ref, TableKeyParameter)
62
61
 
63
62
  @override
64
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
65
- super()._resolve_snrefs(diag_layer)
63
+ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *,
64
+ param_list: List[Parameter]) -> None:
65
+ super()._parameter_resolve_snrefs(diag_layer, param_list=param_list)
66
66
 
67
67
  if self.table_key_snref is not None:
68
- warnings.warn(
69
- "Table keys cannot yet be defined using SNREFs"
70
- " in TableStructParameters.",
71
- OdxWarning,
72
- stacklevel=1)
68
+ self._table_key = resolve_snref(self.table_key_snref, param_list, TableKeyParameter)
73
69
 
74
70
  @property
75
71
  def table_key(self) -> TableKeyParameter:
@@ -86,71 +82,76 @@ class TableStructParameter(Parameter):
86
82
  return True
87
83
 
88
84
  @override
89
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
90
- physical_value = encode_state.parameter_values.get(self.short_name)
85
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
86
+ encode_state: EncodeState) -> None:
91
87
 
92
- if not isinstance(physical_value, tuple) or \
88
+ if not isinstance(physical_value, (tuple, list)) or \
93
89
  len(physical_value) != 2 or \
94
90
  not isinstance(physical_value[0], str):
95
- raise EncodeError(f"The physical value of TableStructParameter 'self.short_name' "
96
- f"must be a tuple with the short name of the selected table "
97
- f"row as the first element and the physical value for the "
98
- f"row's structure or DOP as the second.")
91
+ odxraise(
92
+ f"The physical value of TableStructParameter 'self.short_name' "
93
+ f"must be a tuple containing the short name of the selected table "
94
+ f"row as the first element and the physical value for the "
95
+ f"row's structure or DOP as the second.", EncodeError)
99
96
 
100
97
  tr_short_name = physical_value[0]
101
98
 
102
99
  # make sure that the same table row is selected for all
103
100
  # TABLE-STRUCT parameters that are using the same key
104
101
  tk_short_name = self.table_key.short_name
105
- tk_value = encode_state.parameter_values.get(tk_short_name)
102
+ tk_value = encode_state.table_keys.get(tk_short_name)
106
103
  if tk_value is None:
107
104
  # no value for the key has been set yet. Set it to the
108
105
  # value which we are using right now
109
- encode_state.parameter_values[tk_short_name] = tr_short_name
106
+ encode_state.table_keys[tk_short_name] = tr_short_name
110
107
  elif tk_value != tr_short_name:
111
- raise EncodeError(f"Cannot determine a unique value for table key '{tk_short_name}': "
112
- f"Requested are '{tk_value}' and '{tr_short_name}'")
108
+ odxraise(
109
+ f"Cannot determine a unique value for table key '{tk_short_name}': "
110
+ f"Requested are '{tk_value}' and '{tr_short_name}'", EncodeError)
111
+ return
113
112
 
114
113
  # deal with the static case (i.e., the table row is selected
115
114
  # by the table key object itself)
116
- if self.table_key.table_row is not None and \
117
- self.table_key.table_row.short_name != tr_short_name:
118
- raise EncodeError(f"The selected table row for the {self.short_name} "
119
- f"parameter must be '{self.table_key.table_row.short_name}' "
120
- f"(is: '{tr_short_name}')")
115
+ if self.table_key.table_row is not None:
116
+ if tr_short_name is not None and self.table_key.table_row.short_name != tr_short_name:
117
+ odxraise(
118
+ f"The selected table row for the {self.short_name} "
119
+ f"parameter must be '{self.table_key.table_row.short_name}' "
120
+ f"instead of '{tr_short_name}'", EncodeError)
121
+ return
122
+
123
+ tr_short_name = self.table_key.table_row.short_name
121
124
 
122
125
  # encode the user specified value using the structure (or DOP)
123
126
  # of the selected table row
124
127
  table = self.table_key.table
125
128
  candidate_trs = [tr for tr in table.table_rows if tr.short_name == tr_short_name]
126
- if len(candidate_trs) != 1:
127
- raise EncodeError(f"Could not uniquely resolve a table row named "
128
- f"'{tr_short_name}' in table '{table.short_name}' ")
129
+ if len(candidate_trs) == 0:
130
+ odxraise(
131
+ f"Could not find a table row named "
132
+ f"'{tr_short_name}' in table '{table.short_name}'", EncodeError)
133
+ return
134
+ elif len(candidate_trs) > 1:
135
+ odxraise(
136
+ f"Found multiple table rows named "
137
+ f"'{tr_short_name}' in table '{table.short_name}'", EncodeError)
138
+
129
139
  tr = candidate_trs[0]
130
140
  tr_value = physical_value[1]
131
141
 
132
- bit_position = self.bit_position or 0
133
142
  if tr.structure is not None:
134
143
  # the selected table row references a structure
135
- inner_encode_state = EncodeState(
136
- coded_message=bytearray(b''),
137
- parameter_values=tr_value,
138
- triggering_request=encode_state.triggering_request)
139
-
140
- return tr.structure.convert_physical_to_bytes(
141
- tr_value, inner_encode_state, bit_position=bit_position)
144
+ tr.structure.encode_into_pdu(tr_value, encode_state)
145
+ return
142
146
 
143
147
  # if the table row does not reference a structure, it must
144
148
  # point to a DOP!
145
149
  if tr.dop is None:
146
- odxraise()
150
+ odxraise(f"Neither a structure nor a DOP has been defined for table row"
151
+ f"'{tr.short_name}'")
152
+ return
147
153
 
148
- return tr.dop.convert_physical_to_bytes(
149
- tr_value, encode_state=encode_state, bit_position=bit_position)
150
-
151
- @override
152
- def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
153
- return super().encode_into_pdu(encode_state)
154
+ tr.dop.encode_into_pdu(tr_value, encode_state)
154
155
 
155
156
  @override
156
157
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -7,11 +7,11 @@ from typing_extensions import override
7
7
 
8
8
  from ..dataobjectproperty import DataObjectProperty
9
9
  from ..encodestate import EncodeState
10
- from ..exceptions import odxraise, odxrequire
10
+ from ..exceptions import EncodeError, odxraise, odxrequire
11
11
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
- from ..odxtypes import AtomicOdxType
12
+ from ..odxtypes import AtomicOdxType, ParameterValue
13
13
  from ..utils import dataclass_fields_asdict
14
- from .parameter import ParameterType
14
+ from .parameter import Parameter, ParameterType
15
15
  from .parameterwithdop import ParameterWithDOP
16
16
 
17
17
  if TYPE_CHECKING:
@@ -50,8 +50,9 @@ class ValueParameter(ParameterWithDOP):
50
50
  super()._resolve_odxlinks(odxlinks)
51
51
 
52
52
  @override
53
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
54
- super()._resolve_snrefs(diag_layer)
53
+ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *,
54
+ param_list: List[Parameter]) -> None:
55
+ super()._parameter_resolve_snrefs(diag_layer, param_list=param_list)
55
56
 
56
57
  if self.physical_default_value_raw is not None:
57
58
  dop = odxrequire(self.dop)
@@ -77,16 +78,14 @@ class ValueParameter(ParameterWithDOP):
77
78
  return True
78
79
 
79
80
  @override
80
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
81
- physical_value = encode_state.parameter_values.get(self.short_name,
82
- self.physical_default_value)
81
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
82
+ encode_state: EncodeState) -> None:
83
+
84
+ if physical_value is None:
85
+ physical_value = self._physical_default_value
83
86
  if physical_value is None:
84
- raise TypeError(f"A value for parameter '{self.short_name}' must be specified"
85
- f" as the parameter does not exhibit a default.")
86
- dop = odxrequire(
87
- self.dop,
88
- f"Param {self.short_name} does not have a DOP. Maybe resolving references failed?")
89
-
90
- bit_position_int = self.bit_position if self.bit_position is not None else 0
91
- return dop.convert_physical_to_bytes(
92
- physical_value, encode_state=encode_state, bit_position=bit_position_int)
87
+ odxraise(
88
+ f"A value for parameter '{self.short_name}' must be specified"
89
+ f" because the parameter does not exhibit a default.", EncodeError)
90
+
91
+ self.dop.encode_into_pdu(physical_value, encode_state=encode_state)
@@ -2,10 +2,12 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Any, Dict, cast
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from .decodestate import DecodeState
6
8
  from .diagcodedtype import DctType, DiagCodedType
7
9
  from .encodestate import EncodeState
8
- from .exceptions import odxraise
10
+ from .exceptions import EncodeError, odxraise
9
11
  from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
12
  from .odxtypes import AtomicOdxType, DataType
11
13
 
@@ -43,35 +45,43 @@ class ParamLengthInfoType(DiagCodedType):
43
45
  def length_key(self) -> "LengthKeyParameter":
44
46
  return self._length_key
45
47
 
46
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
47
- bit_position: int) -> bytes:
48
- bit_length = encode_state.parameter_values.get(self.length_key.short_name, None)
48
+ @override
49
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
50
+ bit_length = encode_state.length_keys.get(self.length_key.short_name)
49
51
 
50
52
  if bit_length is None:
53
+ # the length key is implicit, i.e., we need to set the
54
+ # value for the length key in the encode_state based on
55
+ # the value passed here.
51
56
  if self.base_data_type in [
52
57
  DataType.A_BYTEFIELD,
53
58
  DataType.A_ASCIISTRING,
54
59
  DataType.A_UTF8STRING,
55
60
  ]:
56
- bit_length = 8 * len(internal_value) # type: ignore[arg-type]
57
- if self.base_data_type in [DataType.A_UNICODE2STRING]:
58
- bit_length = 16 * len(internal_value) # type: ignore[arg-type]
59
-
60
- if self.base_data_type in [DataType.A_INT32, DataType.A_UINT32]:
61
+ bit_length = 8 * len(cast(str, internal_value))
62
+ elif self.base_data_type in [DataType.A_UNICODE2STRING]:
63
+ bit_length = 16 * len(cast(str, internal_value))
64
+ elif self.base_data_type in [DataType.A_INT32, DataType.A_UINT32]:
61
65
  bit_length = int(internal_value).bit_length()
62
66
  if self.base_data_type == DataType.A_INT32:
63
67
  bit_length += 1
64
68
  # Round up
65
69
  bit_length = ((bit_length + 7) // 8) * 8
66
-
67
- encode_state.parameter_values[self.length_key.short_name] = bit_length
68
-
69
- if bit_length is None:
70
- odxraise()
71
-
72
- return self._encode_internal_value(
73
- internal_value,
74
- bit_position=bit_position,
70
+ elif self.base_data_type == DataType.A_FLOAT32:
71
+ bit_length = 32
72
+ elif self.base_data_type == DataType.A_FLOAT64:
73
+ bit_length = 64
74
+ else:
75
+ odxraise(
76
+ f"Cannot determine size of an object of type "
77
+ f"{self.base_data_type.value}", EncodeError)
78
+ return
79
+
80
+ encode_state.length_keys[self.length_key.short_name] = bit_length
81
+
82
+ encode_state.emplace_atomic_value(
83
+ internal_value=internal_value,
84
+ used_mask=None,
75
85
  bit_length=bit_length,
76
86
  base_data_type=self.base_data_type,
77
87
  is_highlow_byte_order=self.is_highlow_byte_order,
@@ -82,7 +92,7 @@ class ParamLengthInfoType(DiagCodedType):
82
92
  if self.length_key.short_name not in decode_state.length_keys:
83
93
  odxraise(f"Unspecified mandatory length key parameter "
84
94
  f"{self.length_key.short_name}")
85
- decode_state.cursor_bit_position = None
95
+ decode_state.cursor_bit_position = 0
86
96
  return cast(None, AtomicOdxType)
87
97
 
88
98
  bit_length = decode_state.length_keys[self.length_key.short_name]
@@ -91,10 +101,8 @@ class ParamLengthInfoType(DiagCodedType):
91
101
  bit_length = 0
92
102
 
93
103
  # Extract the internal value and return.
94
- value = decode_state.extract_atomic_value(
104
+ return decode_state.extract_atomic_value(
95
105
  bit_length,
96
106
  self.base_data_type,
97
107
  self.is_highlow_byte_order,
98
108
  )
99
-
100
- return value
odxtools/request.py CHANGED
@@ -4,7 +4,9 @@ from typing import List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .basicstructure import BasicStructure
7
+ from .encodestate import EncodeState
7
8
  from .odxlink import OdxDocFragment
9
+ from .odxtypes import ParameterValue
8
10
  from .utils import dataclass_fields_asdict
9
11
 
10
12
 
@@ -20,3 +22,10 @@ class Request(BasicStructure):
20
22
  kwargs = dataclass_fields_asdict(BasicStructure.from_et(et_element, doc_frags))
21
23
 
22
24
  return Request(**kwargs)
25
+
26
+ def encode(self, **kwargs: ParameterValue) -> bytes:
27
+ encode_state = EncodeState(is_end_of_pdu=True)
28
+
29
+ self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
30
+
31
+ return encode_state.coded_message
odxtools/response.py CHANGED
@@ -5,10 +5,10 @@ from typing import List, Optional, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .basicstructure import BasicStructure
8
+ from .encodestate import EncodeState
8
9
  from .exceptions import odxraise
9
10
  from .odxlink import OdxDocFragment
10
11
  from .odxtypes import ParameterValue
11
- from .parameters.matchingrequestparameter import MatchingRequestParameter
12
12
  from .utils import dataclass_fields_asdict
13
13
 
14
14
 
@@ -38,17 +38,9 @@ class Response(BasicStructure):
38
38
 
39
39
  return Response(response_type=response_type, **kwargs)
40
40
 
41
- def encode(self, coded_request: Optional[bytes] = None, **params: ParameterValue) -> bytes:
42
- if coded_request is not None:
43
- # Extract MATCHING-REQUEST-PARAMs from the coded
44
- # request. TODO: this should be done by
45
- # MatchingRequestParam itself!
46
- for param in self.parameters:
47
- if isinstance(param, MatchingRequestParameter):
48
- byte_pos = param.request_byte_position
49
- byte_length = param.byte_length
41
+ def encode(self, coded_request: Optional[bytes] = None, **kwargs: ParameterValue) -> bytes:
42
+ encode_state = EncodeState(triggering_request=coded_request, is_end_of_pdu=True)
50
43
 
51
- val = coded_request[byte_pos:byte_pos + byte_length]
52
- params[param.short_name] = val
44
+ self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
53
45
 
54
- return super().encode(coded_request=coded_request, **params)
46
+ return encode_state.coded_message
@@ -1,11 +1,13 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Optional
3
+ from typing import Literal, Optional
4
+
5
+ from typing_extensions import override
4
6
 
5
7
  from .decodestate import DecodeState
6
8
  from .diagcodedtype import DctType, DiagCodedType
7
9
  from .encodestate import EncodeState
8
- from .exceptions import odxassert, odxraise
10
+ from .exceptions import odxassert, odxraise, odxrequire
9
11
  from .odxtypes import AtomicOdxType, DataType
10
12
 
11
13
 
@@ -20,6 +22,10 @@ class StandardLengthType(DiagCodedType):
20
22
  def dct_type(self) -> DctType:
21
23
  return "STANDARD-LENGTH-TYPE"
22
24
 
25
+ @property
26
+ def is_condensed(self) -> bool:
27
+ return self.is_condensed_raw is True
28
+
23
29
  def __post_init__(self) -> None:
24
30
  if self.bit_mask is not None:
25
31
  maskable_types = (DataType.A_UINT32, DataType.A_INT32, DataType.A_BYTEFIELD)
@@ -28,11 +34,43 @@ class StandardLengthType(DiagCodedType):
28
34
  'Can not apply a bit_mask on a value of type {self.base_data_type}',
29
35
  )
30
36
 
37
+ def __get_raw_mask(self, internal_value: AtomicOdxType) -> Optional[bytes]:
38
+ """Returns a byte field where all bits that are used by the
39
+ DiagCoded type are set and all unused ones are not set.
40
+
41
+ If `None` is returned, all bits are used.
42
+ """
43
+ if self.bit_mask is None:
44
+ return None
45
+
46
+ if self.is_condensed:
47
+ odxraise("Condensed bit masks are not yet supported", NotImplementedError)
48
+ return
49
+
50
+ endianness: Literal["little", "big"] = "big"
51
+ if not self.is_highlow_byte_order and self.base_data_type in [
52
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
53
+ ]:
54
+ # TODO (?): Technically, little endian A_UNICODE2STRING
55
+ # objects require a byte swap for each 16 bit letter, and
56
+ # thus also for the mask. I somehow doubt that this has
57
+ # been anticipated by the standard, though...
58
+ endianness = "little"
59
+
60
+ sz: int
61
+ if isinstance(internal_value, (bytes, bytearray)):
62
+ sz = len(internal_value)
63
+ else:
64
+ sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
65
+
66
+ return self.bit_mask.to_bytes(sz, endianness)
67
+
31
68
  def __apply_mask(self, internal_value: AtomicOdxType) -> AtomicOdxType:
32
69
  if self.bit_mask is None:
33
70
  return internal_value
34
- if self.is_condensed_raw is True:
35
- raise NotImplementedError("Serialization of condensed bit mask is not supported")
71
+ if self.is_condensed:
72
+ odxraise("Serialization of condensed bit mask is not supported", NotImplementedError)
73
+ return
36
74
  if isinstance(internal_value, int):
37
75
  return internal_value & self.bit_mask
38
76
  if isinstance(internal_value, bytes):
@@ -46,16 +84,16 @@ class StandardLengthType(DiagCodedType):
46
84
  def get_static_bit_length(self) -> Optional[int]:
47
85
  return self.bit_length
48
86
 
49
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
50
- bit_position: int) -> bytes:
51
- return self._encode_internal_value(
52
- self.__apply_mask(internal_value),
53
- bit_position,
54
- self.bit_length,
55
- self.base_data_type,
56
- is_highlow_byte_order=self.is_highlow_byte_order,
57
- )
87
+ @override
88
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
89
+ encode_state.emplace_atomic_value(
90
+ internal_value=self.__apply_mask(internal_value),
91
+ used_mask=self.__get_raw_mask(internal_value),
92
+ bit_length=self.bit_length,
93
+ base_data_type=self.base_data_type,
94
+ is_highlow_byte_order=self.is_highlow_byte_order)
58
95
 
96
+ @override
59
97
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
60
98
  internal_value = decode_state.extract_atomic_value(
61
99
  self.bit_length,
odxtools/statechart.py CHANGED
@@ -6,7 +6,7 @@ from xml.etree import ElementTree
6
6
  from .element import IdentifiableElement
7
7
  from .exceptions import odxrequire
8
8
  from .nameditemlist import NamedItemList
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
10
10
  from .state import State
11
11
  from .statetransition import StateTransition
12
12
  from .utils import dataclass_fields_asdict
@@ -68,19 +68,15 @@ class StateChart(IdentifiableElement):
68
68
  for st in self.states:
69
69
  st._resolve_odxlinks(odxlinks)
70
70
 
71
- # For now, we assume that the start state short name ref
72
- # points to a state local to the state chart. TODO: The XML
71
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
72
+ # For now, we assume that the start state short name reference
73
+ # points to a local state of the state chart. TODO: The XSD
73
74
  # allows to define state charts without any states, yet the
74
75
  # start state SNREF is mandatory. Is this a gap in the spec or
75
76
  # does it allow "foreign" start states? If the latter, what
76
77
  # does that mean?
77
- self._start_state: State
78
- for st in self.states:
79
- if st.short_name == self.start_state_snref:
80
- self._start_state = st
81
- break
78
+ self._start_state = resolve_snref(self.start_state_snref, self.states, State)
82
79
 
83
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
84
80
  for st in self.states:
85
81
  st._resolve_snrefs(diag_layer)
86
82