odxtools 6.7.1__py3-none-any.whl → 7.0.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 (45) hide show
  1. odxtools/basicstructure.py +106 -112
  2. odxtools/dataobjectproperty.py +3 -7
  3. odxtools/decodestate.py +1 -13
  4. odxtools/diagcodedtype.py +9 -96
  5. odxtools/diaglayer.py +45 -0
  6. odxtools/diagservice.py +12 -16
  7. odxtools/dopbase.py +5 -8
  8. odxtools/dtcdop.py +21 -19
  9. odxtools/dynamiclengthfield.py +30 -26
  10. odxtools/encodestate.py +188 -23
  11. odxtools/endofpdufield.py +12 -14
  12. odxtools/environmentdatadescription.py +13 -13
  13. odxtools/leadinglengthinfotype.py +25 -12
  14. odxtools/minmaxlengthtype.py +36 -26
  15. odxtools/multiplexer.py +42 -23
  16. odxtools/multiplexercase.py +1 -1
  17. odxtools/nameditemlist.py +14 -0
  18. odxtools/odxtypes.py +17 -0
  19. odxtools/parameterinfo.py +126 -40
  20. odxtools/parameters/codedconstparameter.py +14 -11
  21. odxtools/parameters/dynamicparameter.py +5 -4
  22. odxtools/parameters/lengthkeyparameter.py +62 -14
  23. odxtools/parameters/matchingrequestparameter.py +23 -11
  24. odxtools/parameters/nrcconstparameter.py +39 -20
  25. odxtools/parameters/parameter.py +29 -42
  26. odxtools/parameters/parameterwithdop.py +5 -8
  27. odxtools/parameters/physicalconstantparameter.py +12 -13
  28. odxtools/parameters/reservedparameter.py +5 -2
  29. odxtools/parameters/systemparameter.py +3 -2
  30. odxtools/parameters/tableentryparameter.py +3 -2
  31. odxtools/parameters/tablekeyparameter.py +79 -30
  32. odxtools/parameters/tablestructparameter.py +62 -42
  33. odxtools/parameters/valueparameter.py +12 -14
  34. odxtools/paramlengthinfotype.py +30 -22
  35. odxtools/request.py +9 -0
  36. odxtools/response.py +5 -13
  37. odxtools/standardlengthtype.py +51 -13
  38. odxtools/staticfield.py +15 -19
  39. odxtools/version.py +2 -2
  40. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/METADATA +1 -1
  41. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/RECORD +45 -45
  42. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/LICENSE +0 -0
  43. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/WHEEL +0 -0
  44. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/entry_points.txt +0 -0
  45. {odxtools-6.7.1.dist-info → odxtools-7.0.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
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, 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
- from ..exceptions import odxraise, odxrequire
10
+ from ..exceptions import EncodeError, odxraise, odxrequire
11
11
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
12
  from ..odxtypes import ParameterValue
13
13
  from ..utils import dataclass_fields_asdict
@@ -77,17 +77,65 @@ class LengthKeyParameter(ParameterWithDOP):
77
77
  return True
78
78
 
79
79
  @override
80
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
81
- physical_value = encode_state.parameter_values.get(self.short_name, 0)
82
-
83
- bit_pos = self.bit_position or 0
84
- dop = odxrequire(super().dop,
85
- f"A DOP is required for length key parameter {self.short_name}")
86
- return dop.convert_physical_to_bytes(physical_value, encode_state, bit_position=bit_pos)
87
-
88
- @override
89
- def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
90
- return super().encode_into_pdu(encode_state)
80
+ @final
81
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
82
+ encode_state: EncodeState) -> None:
83
+ # if you get this exception, you ought to use
84
+ # `.encode_placeholder_into_pdu()` followed by (after the
85
+ # value of the length key has been determined)
86
+ # `.encode_value_into_pdu()`.
87
+ raise RuntimeError("_encode_positioned_into_pdu() cannot be called for length keys.")
88
+
89
+ def encode_placeholder_into_pdu(self, physical_value: Optional[ParameterValue],
90
+ encode_state: EncodeState) -> None:
91
+
92
+ if physical_value is not None:
93
+ if not self.dop.is_valid_physical_value(physical_value):
94
+ odxraise(f"Invalid explicitly specified physical value '{physical_value!r}' "
95
+ f"for length key '{self.short_name}'.")
96
+
97
+ lkv = encode_state.length_keys.get(self.short_name)
98
+ if lkv is not None and lkv != physical_value:
99
+ odxraise(f"Got conflicting values for length key {self.short_name}: "
100
+ f"{lkv} and {physical_value!r}")
101
+
102
+ if not isinstance(physical_value, int):
103
+ odxraise(
104
+ f"Value of length key {self.short_name} is of type {type(physical_value).__name__} "
105
+ f"instead of int")
106
+
107
+ encode_state.length_keys[self.short_name] = physical_value
108
+
109
+ orig_cursor = encode_state.cursor_byte_position
110
+ pos = encode_state.cursor_byte_position
111
+ if self.byte_position is not None:
112
+ pos = encode_state.origin_byte_position + self.byte_position
113
+ encode_state.key_pos[self.short_name] = pos
114
+ encode_state.cursor_byte_position = pos
115
+ encode_state.cursor_bit_position = self.bit_position or 0
116
+
117
+ # emplace a value of zero into the encode state, but pretend the bits not to be used
118
+ n = odxrequire(self.dop.get_static_bit_length()) + encode_state.cursor_bit_position
119
+ tmp_val = b'\x00' * ((n + 7) // 8)
120
+ encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
121
+
122
+ encode_state.cursor_byte_position = max(encode_state.cursor_byte_position, orig_cursor)
123
+ encode_state.cursor_bit_position = 0
124
+
125
+ def encode_value_into_pdu(self, encode_state: EncodeState) -> None:
126
+
127
+ if self.short_name not in encode_state.length_keys:
128
+ odxraise(
129
+ f"Length key {self.short_name} has not been defined before "
130
+ f"it is required.", EncodeError)
131
+ return
132
+ else:
133
+ physical_value = encode_state.length_keys[self.short_name]
134
+
135
+ encode_state.cursor_byte_position = encode_state.key_pos[self.short_name]
136
+ encode_state.cursor_bit_position = self.bit_position or 0
137
+
138
+ self.dop.encode_into_pdu(encode_state=encode_state, physical_value=physical_value)
91
139
 
92
140
  @override
93
141
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -7,7 +7,7 @@ from typing_extensions import override
7
7
 
8
8
  from ..decodestate import DecodeState
9
9
  from ..encodestate import EncodeState
10
- from ..exceptions import EncodeError, odxrequire
10
+ from ..exceptions import EncodeError, odxraise, odxrequire
11
11
  from ..odxlink import OdxDocFragment
12
12
  from ..odxtypes import DataType, ParameterValue
13
13
  from ..utils import dataclass_fields_asdict
@@ -52,19 +52,31 @@ class MatchingRequestParameter(Parameter):
52
52
  return False
53
53
 
54
54
  @override
55
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
56
- if not encode_state.triggering_request:
57
- raise EncodeError(f"Parameter '{self.short_name}' is of matching request type,"
58
- " but no original request has been specified.")
59
- return encode_state.triggering_request[self
60
- .request_byte_position:self.request_byte_position +
61
- self.byte_length]
55
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
56
+ encode_state: EncodeState) -> None:
57
+ if encode_state.triggering_request is None:
58
+ odxraise(
59
+ f"Parameter '{self.short_name}' is of matching request type,"
60
+ f" but no original request has been specified.", EncodeError)
61
+ return
62
+
63
+ rq_pos = self.request_byte_position
64
+ rq_len = self.byte_length
65
+
66
+ if len(encode_state.triggering_request) < rq_pos + rq_len:
67
+ odxraise(
68
+ f"Specified triggering request 0x{encode_state.triggering_request.hex()} "
69
+ f"is not long enough to encode matching request parameter "
70
+ f"'{self.short_name}': Have {len(encode_state.triggering_request)} "
71
+ f"bytes, need at least {rq_pos + rq_len} bytes", EncodeError)
72
+ return
73
+
74
+ encode_state.emplace_bytes(encode_state.triggering_request[rq_pos:rq_pos + rq_len],
75
+ self.short_name)
62
76
 
63
77
  @override
64
78
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
65
- result = decode_state.extract_atomic_value(
79
+ return decode_state.extract_atomic_value(
66
80
  bit_length=self.byte_length * 8,
67
81
  base_data_type=DataType.A_UINT32,
68
82
  is_highlow_byte_order=False)
69
-
70
- return result
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from typing_extensions import override
@@ -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, EncodeError, 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
 
@@ -22,13 +22,19 @@ if TYPE_CHECKING:
22
22
 
23
23
  @dataclass
24
24
  class NrcConstParameter(Parameter):
25
- """A param of type NRC-CONST defines a set of values to be matched.
25
+ """A param of type NRC-CONST defines a set of values to be matched for a negative response to apply.
26
26
 
27
- An NRC-CONST can only be used in a negative response.
28
- Its encoding behaviour is similar to a VALUE parameter with a TEXTTABLE.
29
- However, an NRC-CONST is used for matching a response (similar to a CODED-CONST).
27
+ The behaviour of NRC-CONST parameters is similar to CODED-CONST
28
+ parameters in that they allow to specify which coding objects
29
+ apply to a binary string, but in contrast to CODED-CONST
30
+ parameters they allow to specify multiple values. Thus, the value
31
+ of a CODED-CONST parameter is usually set using an overlapping
32
+ VALUE parameter. Since NRC-CONST parameters can only be specified
33
+ for negative responses, they can thus be regarded as a multiplexer
34
+ mechanism that is specific to negative responses.
30
35
 
31
36
  See ASAM MCD-2 D (ODX), p. 77-79.
37
+
32
38
  """
33
39
 
34
40
  diag_coded_type: DiagCodedType
@@ -91,21 +97,34 @@ class NrcConstParameter(Parameter):
91
97
  return False
92
98
 
93
99
  @override
94
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
95
- if self.short_name in encode_state.parameter_values:
96
- if encode_state.parameter_values[self.short_name] not in self.coded_values:
97
- raise EncodeError(f"The parameter '{self.short_name}' must have"
98
- f" one of the constant values {self.coded_values}")
100
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
101
+ encode_state: EncodeState) -> None:
102
+ coded_value: ParameterValue
103
+ if physical_value is not None:
104
+ if physical_value not in self.coded_values:
105
+ odxraise(
106
+ f"The value of parameter '{self.short_name}' must "
107
+ f" be one of {self.coded_values} (is: {physical_value!r})", EncodeError)
108
+ coded_value = self.coded_values[0]
99
109
  else:
100
- coded_value = encode_state.parameter_values[self.short_name]
110
+ coded_value = physical_value
101
111
  else:
102
- # If the user does not select one, just select any.
103
- # I think it does not matter ...
104
- coded_value = self.coded_values[0]
105
-
106
- bit_position_int = self.bit_position if self.bit_position is not None else 0
107
- return self.diag_coded_type.convert_internal_to_bytes(
108
- coded_value, encode_state, bit_position=bit_position_int)
112
+ # If the user did not select a value, the value of the
113
+ # this parameter is set by another parameter which
114
+ # overlaps with it. We thus just move the cursor.
115
+ bit_pos = encode_state.cursor_bit_position
116
+ bit_len = self.diag_coded_type.get_static_bit_length()
117
+
118
+ if bit_len is None:
119
+ odxraise("The diag coded type of NRC-CONST parameters must "
120
+ "exhibit a static size")
121
+ return
122
+
123
+ encode_state.cursor_byte_position += (bit_pos + bit_len + 7) // 8
124
+ encode_state.cursor_bit_position = 0
125
+ return
126
+
127
+ self.diag_coded_type.encode_into_pdu(cast(AtomicOdxType, coded_value), encode_state)
109
128
 
110
129
  @override
111
130
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
@@ -117,12 +117,35 @@ class Parameter(NamedElement):
117
117
  """
118
118
  raise NotImplementedError(".is_settable is not implemented by the concrete parameter class")
119
119
 
120
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
121
- """Get the coded value of the parameter given the encode state.
122
- Note that this method is called by `encode_into_pdu`.
120
+ @final
121
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
122
+ encode_state: EncodeState) -> None:
123
+ """Convert a physical value into its encoded form and place it into the PDU
124
+
125
+ Also, adapt the `encode_state` so that it points to where the next
126
+ parameter is located (if the parameter does not explicitly specify a
127
+ position)
123
128
  """
129
+
130
+ orig_cursor = encode_state.cursor_byte_position
131
+ if self.byte_position is not None:
132
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
133
+
134
+ encode_state.cursor_bit_position = self.bit_position or 0
135
+
136
+ self._encode_positioned_into_pdu(physical_value, encode_state)
137
+
138
+ encode_state.cursor_byte_position = max(encode_state.cursor_byte_position, orig_cursor)
139
+ encode_state.cursor_bit_position = 0
140
+
141
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
142
+ encode_state: EncodeState) -> None:
143
+ """Method which actually encodes the parameter
144
+
145
+ Its location is managed by `Parameter`."""
124
146
  raise NotImplementedError(
125
- ".get_coded_value_as_bytes() is not implemented by the concrete parameter class")
147
+ f"Required method '_encode_positioned_into_pdu()' not implemented by "
148
+ f"child class {type(self).__name__}")
126
149
 
127
150
  @final
128
151
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -144,41 +167,5 @@ class Parameter(NamedElement):
144
167
 
145
168
  Its location is managed by `Parameter`."""
146
169
  raise NotImplementedError(
147
- "Required method '_decode_positioned_from_pdu()' not implemented by child class")
148
-
149
- def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
150
- """Encode the value of a parameter into a binary blob and return it
151
-
152
- If the byte position of the parameter is not defined,
153
- the byte code is appended to the blob.
154
-
155
- Technical note for subclasses: The default implementation
156
- tries to compute the coded value via
157
- `self.get_coded_value_as_bytes(encoded_state)` and inserts it
158
- into the PDU. Thus it usually suffices to overwrite
159
- `get_coded_value_as_bytes()` instead of `encode_into_pdu()`.
160
-
161
- Parameters:
162
- ----------
163
- encode_state: EncodeState, i.e. a named tuple with attributes
164
- * coded_message: bytes, the message encoded so far
165
- * parameter_values: List[ParameterValuePairs]
166
- * triggering_coded_request: bytes
167
-
168
- Returns:
169
- -------
170
- bytes
171
- the message's blob after adding the encoded parameter into it
172
-
173
- """
174
- msg_blob = encode_state.coded_message
175
- param_blob = self.get_coded_value_as_bytes(encode_state)
176
-
177
- if self.byte_position is not None:
178
- byte_position = self.byte_position
179
- else:
180
- byte_position = len(msg_blob)
181
-
182
- encode_state.emplace_atomic_value(param_blob, self.short_name, byte_position)
183
-
184
- return encode_state.coded_message
170
+ f"Required method '_decode_positioned_from_pdu()' not implemented by "
171
+ f"child class {type(self).__name__}")
@@ -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, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -12,7 +12,7 @@ from ..dtcdop import DtcDop
12
12
  from ..encodestate import EncodeState
13
13
  from ..exceptions import odxassert, odxrequire
14
14
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
15
- from ..odxtypes import ParameterValue
15
+ from ..odxtypes import AtomicOdxType, ParameterValue
16
16
  from ..physicaltype import PhysicalType
17
17
  from ..utils import dataclass_fields_asdict
18
18
  from .parameter import Parameter
@@ -91,12 +91,9 @@ class ParameterWithDOP(Parameter):
91
91
  return None
92
92
 
93
93
  @override
94
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
95
- dop = odxrequire(self.dop, "Reference to DOP is not resolved")
96
- physical_value = encode_state.parameter_values[self.short_name]
97
- bit_position_int = self.bit_position if self.bit_position is not None else 0
98
- return dop.convert_physical_to_bytes(
99
- physical_value, encode_state, bit_position=bit_position_int)
94
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
95
+ encode_state: EncodeState) -> None:
96
+ self.dop.encode_into_pdu(cast(AtomicOdxType, physical_value), encode_state)
100
97
 
101
98
  @override
102
99
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -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, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -8,7 +8,7 @@ from typing_extensions import override
8
8
  from ..dataobjectproperty import DataObjectProperty
9
9
  from ..decodestate import DecodeState
10
10
  from ..encodestate import EncodeState
11
- from ..exceptions import DecodeError, odxraise, odxrequire
11
+ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
12
12
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
13
13
  from ..odxtypes import ParameterValue
14
14
  from ..utils import dataclass_fields_asdict
@@ -74,17 +74,15 @@ class PhysicalConstantParameter(ParameterWithDOP):
74
74
  return False
75
75
 
76
76
  @override
77
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
78
- dop = odxrequire(self.dop, "Reference to DOP is not resolved")
79
- if (self.short_name in encode_state.parameter_values and
80
- encode_state.parameter_values[self.short_name] != self.physical_constant_value):
81
- raise TypeError(
82
- f"The parameter '{self.short_name}' is constant {self.physical_constant_value!r}"
83
- f" and thus can not be changed.")
84
-
85
- bit_position_int = self.bit_position if self.bit_position is not None else 0
86
- return dop.convert_physical_to_bytes(
87
- self.physical_constant_value, encode_state, bit_position=bit_position_int)
77
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
78
+ encode_state: EncodeState) -> None:
79
+ if physical_value is not None and physical_value != self.physical_constant_value:
80
+ odxraise(
81
+ f"Value for constant parameter `{self.short_name}` name can "
82
+ f"only be specified as {self.physical_constant_value!r} (is: {physical_value!r})",
83
+ EncodeError)
84
+
85
+ self.dop.encode_into_pdu(self.physical_constant_value, encode_state)
88
86
 
89
87
  @override
90
88
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -99,4 +97,5 @@ class PhysicalConstantParameter(ParameterWithDOP):
99
97
  f"{self.physical_constant_value!r} but got {phys_val!r} "
100
98
  f"at byte position {decode_state.cursor_byte_position} "
101
99
  f"in coded message {decode_state.coded_message.hex()}.", DecodeError)
100
+
102
101
  return phys_val
@@ -49,8 +49,11 @@ class ReservedParameter(Parameter):
49
49
  return self.bit_length
50
50
 
51
51
  @override
52
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
53
- return (0).to_bytes(((self.bit_position or 0) + self.bit_length + 7) // 8, "big")
52
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
53
+ encode_state: EncodeState) -> None:
54
+ raw_data = (0).to_bytes((encode_state.cursor_bit_position + self.bit_length + 7) // 8,
55
+ "big")
56
+ encode_state.emplace_bytes(raw_data, self.short_name)
54
57
 
55
58
  @override
56
59
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -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
@@ -46,7 +46,8 @@ class SystemParameter(ParameterWithDOP):
46
46
  raise NotImplementedError("SystemParameter.is_settable is not implemented yet.")
47
47
 
48
48
  @override
49
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
49
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
50
+ encode_state: EncodeState) -> None:
50
51
  raise NotImplementedError("Encoding a SystemParameter is not implemented yet.")
51
52
 
52
53
  @override
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, List
3
+ from typing import TYPE_CHECKING, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -59,7 +59,8 @@ class TableEntryParameter(Parameter):
59
59
  raise NotImplementedError("TableEntryParameter.is_settable is not implemented yet.")
60
60
 
61
61
  @override
62
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
62
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
63
+ encode_state: EncodeState) -> None:
63
64
  raise NotImplementedError("Encoding a TableEntryParameter is not implemented yet.")
64
65
 
65
66
  @property
@@ -3,7 +3,7 @@ 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
@@ -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: