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
@@ -4,6 +4,8 @@ from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
+ from typing_extensions import override
8
+
7
9
  from .complexdop import ComplexDop
8
10
  from .dataobjectproperty import DataObjectProperty
9
11
  from .decodestate import DecodeState
@@ -54,32 +56,29 @@ class BasicStructure(ComplexDop):
54
56
  return 8 * self.byte_size
55
57
 
56
58
  cursor = 0
57
- length = 0
59
+ byte_length = 0
58
60
  for param in self.parameters:
59
61
  param_bit_length = param.get_static_bit_length()
60
62
  if param_bit_length is None:
61
63
  # We were not able to calculate a static bit length
62
64
  return None
63
65
  elif param.byte_position is not None:
64
- bit_pos = param.bit_position or 0
65
- byte_pos = param.byte_position or 0
66
- cursor = byte_pos * 8 + bit_pos
66
+ cursor = param.byte_position
67
67
 
68
- cursor += param_bit_length
69
- length = max(length, cursor)
68
+ cursor += ((param.bit_position or 0) + param_bit_length + 7) // 8
69
+ byte_length = max(byte_length, cursor)
70
70
 
71
71
  # Round up to account for padding bits (all structures are
72
72
  # byte aligned)
73
- return ((length + 7) // 8) * 8
73
+ return byte_length * 8
74
74
 
75
75
  def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
76
- prefix = b''
77
- encode_state = EncodeState(
78
- bytearray(prefix), parameter_values={}, triggering_request=request_prefix)
76
+ encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
77
+
79
78
  for param in self.parameters:
80
- if isinstance(param, (CodedConstParameter, NrcConstParameter, MatchingRequestParameter,
81
- PhysicalConstantParameter)):
82
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
79
+ if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
80
+ isinstance(param, (CodedConstParameter, NrcConstParameter, PhysicalConstantParameter)):
81
+ param.encode_into_pdu(physical_value=None, encode_state=encode_state)
83
82
  else:
84
83
  break
85
84
  return encode_state.coded_message
@@ -116,58 +115,101 @@ class BasicStructure(ComplexDop):
116
115
 
117
116
  print(parameter_info(self.free_parameters), end="")
118
117
 
119
- def convert_physical_to_internal(self,
120
- param_value: ParameterValue,
121
- triggering_coded_request: Optional[bytes],
122
- is_end_of_pdu: bool = True) -> bytes:
118
+ def _validate_coded_message_size(self, coded_byte_len: int) -> None:
119
+
120
+ if self.byte_size is not None:
121
+ # We definitely broke something if we didn't respect the explicit byte_size
122
+ if self.byte_size != coded_byte_len:
123
+ warnings.warn(
124
+ "Verification of coded message failed: Incorrect size.",
125
+ OdxWarning,
126
+ stacklevel=1)
127
+
128
+ return
129
+
130
+ bit_length = self.get_static_bit_length()
131
+
132
+ if bit_length is None:
133
+ # Nothing to check
134
+ return
135
+
136
+ if coded_byte_len * 8 != bit_length:
137
+ # We may have broke something
138
+ # but it could be that bit_length was mis calculated and not the actual bytes are wrong
139
+ # Could happen with overlapping parameters and parameters with gaps
140
+ warnings.warn(
141
+ "Verification of coded message possibly failed: Size may be incorrect.",
142
+ OdxWarning,
143
+ stacklevel=1)
123
144
 
124
- if not isinstance(param_value, dict):
125
- raise EncodeError(
145
+ @override
146
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
147
+ encode_state: EncodeState) -> None:
148
+ if not isinstance(physical_value, dict):
149
+ odxraise(
126
150
  f"Expected a dictionary for the values of structure {self.short_name}, "
127
- f"got {type(param_value)}")
151
+ f"got {type(physical_value).__name__}", EncodeError)
152
+ elif encode_state.cursor_bit_position != 0:
153
+ odxraise(
154
+ f"Structures must be byte aligned, but "
155
+ f"{self.short_name} requested to be at bit position "
156
+ f"{encode_state.cursor_bit_position}", EncodeError)
157
+ encode_state.bit_position = 0
158
+
159
+ orig_cursor = encode_state.cursor_byte_position
160
+ orig_origin = encode_state.origin_byte_position
161
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
162
+
163
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
164
+ encode_state.is_end_of_pdu = False
128
165
 
129
166
  # in strict mode, ensure that no values for unknown parameters are specified.
130
167
  if strict_mode:
131
- param_names = [param.short_name for param in self.parameters]
132
- for param_key in param_value:
133
- if param_key not in param_names:
134
- odxraise(f"Value for unknown parameter '{param_key}' specified")
135
-
136
- encode_state = EncodeState(
137
- bytearray(),
138
- dict(param_value),
139
- triggering_request=triggering_coded_request,
140
- is_end_of_pdu=False,
141
- )
168
+ param_names = {param.short_name for param in self.parameters}
169
+ for param_value_name in physical_value:
170
+ if param_value_name not in param_names:
171
+ odxraise(f"Value for unknown parameter '{param_value_name}' specified "
172
+ f"for structure {self.short_name}")
142
173
 
143
174
  for param in self.parameters:
144
- if param == self.parameters[-1]:
145
- # The last parameter is at the end of the PDU if the
146
- # structure itself is at the end of the PDU. TODO:
147
- # This assumes that the last parameter specified in
148
- # the ODX is located last in the PDU...
149
- encode_state.is_end_of_pdu = is_end_of_pdu
150
-
151
- if isinstance(
152
- param,
153
- (LengthKeyParameter, TableKeyParameter)) and param.short_name in param_value:
154
- # This is a hack to always encode a dummy value for
155
- # length- and table keys. since these can be specified
175
+ if id(param) == id(self.parameters[-1]):
176
+ # The last parameter of the structure is at the end of
177
+ # the PDU if the structure itself is at the end of the
178
+ # PDU. TODO: This assumes that the last parameter
179
+ # specified in the ODX is located last in the PDU...
180
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
181
+
182
+ if isinstance(param, (LengthKeyParameter, TableKeyParameter)):
183
+ # At this point, we encode a placeholder value for length-
184
+ # and table keys, since these can be specified
156
185
  # implicitly (i.e., by means of parameters that use
157
- # these keys), to avoid getting an "overlapping
186
+ # these keys). To avoid getting an "overlapping
158
187
  # parameter" warning, we must encode a value of zero
159
188
  # into the PDU here and add the real value of the
160
- # parameter in a post processing step.
161
- tmp = encode_state.parameter_values.pop(param.short_name)
162
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
163
- encode_state.parameter_values[param.short_name] = tmp
189
+ # parameter in a post-processing step.
190
+ param.encode_placeholder_into_pdu(
191
+ physical_value=physical_value.get(param.short_name), encode_state=encode_state)
192
+
164
193
  continue
165
194
 
166
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
195
+ if param.is_required and param.short_name not in physical_value:
196
+ odxraise(f"No value for required parameter {param.short_name} specified",
197
+ EncodeError)
167
198
 
168
- if self.byte_size is not None and len(encode_state.coded_message) < self.byte_size:
169
- # Padding bytes needed
170
- encode_state.coded_message = encode_state.coded_message.ljust(self.byte_size, b"\0")
199
+ param.encode_into_pdu(
200
+ physical_value=physical_value.get(param.short_name), encode_state=encode_state)
201
+
202
+ encode_state.is_end_of_pdu = False
203
+ if self.byte_size is not None:
204
+ actual_len = encode_state.cursor_byte_position - encode_state.origin_byte_position
205
+ if actual_len < self.byte_size:
206
+ # Padding bytes needed. We add an empty object at the
207
+ # position directly after the structure and let
208
+ # EncodeState add the padding as needed.
209
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_size
210
+ # Padding bytes needed. these count as "used".
211
+ encode_state.coded_message += b"\x00" * (self.byte_size - actual_len)
212
+ encode_state.used_mask += b"\xff" * (self.byte_size - actual_len)
171
213
 
172
214
  # encode the length- and table keys. This cannot be done above
173
215
  # because we allow these to be defined implicitly (i.e. they
@@ -177,56 +219,17 @@ class BasicStructure(ComplexDop):
177
219
  # the current parameter is neither a length- nor a table key
178
220
  continue
179
221
 
180
- # Encode the key parameter into the message
181
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
222
+ # Encode the value of the key parameter into the message
223
+ param.encode_value_into_pdu(encode_state=encode_state)
182
224
 
183
225
  # Assert that length is as expected
184
- self._validate_coded_message(encode_state.coded_message)
185
-
186
- return bytearray(encode_state.coded_message)
187
-
188
- def _validate_coded_message(self, coded_message: bytes) -> None:
189
-
190
- if self.byte_size is not None:
191
- # We definitely broke something if we didn't respect the explicit byte_size
192
- odxassert(
193
- len(coded_message) == self.byte_size,
194
- "Verification of coded message {coded_message.hex()} failed: Incorrect size.")
195
- # No need to check further
196
- return
197
-
198
- bit_length = self.get_static_bit_length()
199
-
200
- if bit_length is None:
201
- # Nothing to check
202
- return
226
+ self._validate_coded_message_size(encode_state.cursor_byte_position -
227
+ encode_state.origin_byte_position)
203
228
 
204
- if len(coded_message) * 8 != bit_length:
205
- # We may have broke something
206
- # but it could be that bit_length was mis calculated and not the actual bytes are wrong
207
- # Could happen with overlapping parameters and parameters with gaps
208
- warnings.warn(
209
- f"Verification of coded message '{coded_message.hex()}' possibly failed: Size may be incorrect.",
210
- OdxWarning,
211
- stacklevel=1)
212
-
213
- def convert_physical_to_bytes(self,
214
- physical_value: ParameterValue,
215
- encode_state: EncodeState,
216
- bit_position: int = 0) -> bytes:
217
- if not isinstance(physical_value, dict):
218
- raise EncodeError(
219
- f"Expected a dictionary for the values of structure {self.short_name}, "
220
- f"got {type(physical_value)}")
221
- if bit_position != 0:
222
- raise EncodeError("Structures must be aligned, i.e. bit_position=0, but "
223
- f"{self.short_name} was passed the bit position {bit_position}")
224
- return self.convert_physical_to_internal(
225
- physical_value,
226
- triggering_coded_request=encode_state.triggering_request,
227
- is_end_of_pdu=encode_state.is_end_of_pdu,
228
- )
229
+ encode_state.origin_byte_position = orig_origin
230
+ encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
229
231
 
232
+ @override
230
233
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
231
234
  # move the origin since positions specified by sub-parameters of
232
235
  # structures are relative to the beginning of the structure object.
@@ -244,19 +247,6 @@ class BasicStructure(ComplexDop):
244
247
 
245
248
  return result
246
249
 
247
- def encode(self, coded_request: Optional[bytes] = None, **params: ParameterValue) -> bytes:
248
- """
249
- Composes an UDS message as bytes for this service.
250
- Parameters:
251
- ----------
252
- coded_request: bytes
253
- coded request (only needed when encoding a response)
254
- params: dict
255
- Parameters of the RPC as mapping from SHORT-NAME of the parameter to the value
256
- """
257
- return self.convert_physical_to_internal(
258
- params, triggering_coded_request=coded_request, is_end_of_pdu=True)
259
-
260
250
  def decode(self, message: bytes) -> ParameterValueDict:
261
251
  decode_state = DecodeState(coded_message=message)
262
252
  param_values = self.decode_from_pdu(decode_state)
@@ -316,4 +306,4 @@ class BasicStructure(ComplexDop):
316
306
  super()._resolve_snrefs(diag_layer)
317
307
 
318
308
  for param in self.parameters:
319
- param._resolve_snrefs(diag_layer)
309
+ param._parameter_resolve_snrefs(diag_layer, param_list=self.parameters)
@@ -120,10 +120,7 @@ class DataObjectProperty(DopBase):
120
120
 
121
121
  return self.compu_method.convert_physical_to_internal(physical_value)
122
122
 
123
- def convert_physical_to_bytes(self,
124
- physical_value: Any,
125
- encode_state: EncodeState,
126
- bit_position: int = 0) -> bytes:
123
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
127
124
  """
128
125
  Convert a physical representation of a parameter to a string bytes that can be send over the wire
129
126
  """
@@ -132,9 +129,8 @@ class DataObjectProperty(DopBase):
132
129
  f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
133
130
  f" is not a valid.")
134
131
 
135
- internal_val = self.convert_physical_to_internal(physical_value)
136
- return self.diag_coded_type.convert_internal_to_bytes(
137
- internal_val, encode_state, bit_position=bit_position)
132
+ internal_value = self.convert_physical_to_internal(physical_value)
133
+ self.diag_coded_type.encode_into_pdu(internal_value, encode_state)
138
134
 
139
135
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
140
136
  """
odxtools/decodestate.py CHANGED
@@ -15,18 +15,6 @@ except ImportError:
15
15
  if TYPE_CHECKING:
16
16
  from .tablerow import TableRow
17
17
 
18
- # format specifiers for the data type using the bitstruct module
19
- ODX_TYPE_TO_FORMAT_LETTER = {
20
- DataType.A_INT32: "s",
21
- DataType.A_UINT32: "u",
22
- DataType.A_FLOAT32: "f",
23
- DataType.A_FLOAT64: "f",
24
- DataType.A_BYTEFIELD: "r",
25
- DataType.A_UNICODE2STRING: "r", # UTF-16 strings must be converted explicitly
26
- DataType.A_ASCIISTRING: "r",
27
- DataType.A_UTF8STRING: "r",
28
- }
29
-
30
18
 
31
19
  @dataclass
32
20
  class DecodeState:
@@ -94,7 +82,7 @@ class DecodeState:
94
82
 
95
83
  padding = (8 - (bit_length + self.cursor_bit_position) % 8) % 8
96
84
  internal_value, = bitstruct.unpack_from(
97
- f"{ODX_TYPE_TO_FORMAT_LETTER[base_data_type]}{bit_length}",
85
+ f"{base_data_type.bitstruct_format_letter}{bit_length}",
98
86
  extracted_bytes,
99
87
  offset=padding)
100
88
 
odxtools/diagcodedtype.py CHANGED
@@ -1,19 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
5
5
 
6
- from .decodestate import ODX_TYPE_TO_FORMAT_LETTER, DecodeState
6
+ from .decodestate import DecodeState
7
7
  from .encodestate import EncodeState
8
- from .exceptions import EncodeError, odxassert, odxraise
8
+ from .exceptions import odxassert, odxraise
9
9
  from .odxlink import OdxLinkDatabase, OdxLinkId
10
10
  from .odxtypes import AtomicOdxType, DataType
11
11
 
12
- try:
13
- import bitstruct.c as bitstruct
14
- except ImportError:
15
- import bitstruct
16
-
17
12
  if TYPE_CHECKING:
18
13
  from .diaglayer import DiagLayer
19
14
 
@@ -56,88 +51,6 @@ class DiagCodedType(abc.ABC):
56
51
  def is_highlow_byte_order(self) -> bool:
57
52
  return self.is_highlow_byte_order_raw in [None, True]
58
53
 
59
- @staticmethod
60
- def _encode_internal_value(
61
- internal_value: AtomicOdxType,
62
- bit_position: int,
63
- bit_length: int,
64
- base_data_type: DataType,
65
- is_highlow_byte_order: bool,
66
- ) -> bytes:
67
- """Convert the internal_value to bytes."""
68
- # Check that bytes and strings actually fit into the bit length
69
- if base_data_type == DataType.A_BYTEFIELD:
70
- if isinstance(internal_value, bytearray):
71
- internal_value = bytes(internal_value)
72
- if not isinstance(internal_value, bytes):
73
- odxraise()
74
- if 8 * len(internal_value) > bit_length:
75
- raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
76
- f"({len(internal_value)} bytes)."
77
- f" The maximum length is {bit_length//8}.")
78
- if base_data_type == DataType.A_ASCIISTRING:
79
- if not isinstance(internal_value, str):
80
- odxraise()
81
-
82
- # The spec says ASCII, meaning only byte values 0-127.
83
- # But in practice, vendors use iso-8859-1, aka latin-1
84
- # reason being iso-8859-1 never fails since it has a valid
85
- # character mapping for every possible byte sequence.
86
- internal_value = internal_value.encode("iso-8859-1")
87
-
88
- if 8 * len(internal_value) > bit_length:
89
- raise EncodeError(f"The string {repr(internal_value)} is too large."
90
- f" The maximum number of characters is {bit_length//8}.")
91
- elif base_data_type == DataType.A_UTF8STRING:
92
- if not isinstance(internal_value, str):
93
- odxraise()
94
-
95
- internal_value = internal_value.encode("utf-8")
96
-
97
- if 8 * len(internal_value) > bit_length:
98
- raise EncodeError(f"The string {repr(internal_value)} is too large."
99
- f" The maximum number of bytes is {bit_length//8}.")
100
-
101
- elif base_data_type == DataType.A_UNICODE2STRING:
102
- if not isinstance(internal_value, str):
103
- odxraise()
104
-
105
- text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
106
- internal_value = internal_value.encode(text_encoding)
107
-
108
- if 8 * len(internal_value) > bit_length:
109
- raise EncodeError(f"The string {repr(internal_value)} is too large."
110
- f" The maximum number of characters is {bit_length//16}.")
111
-
112
- # If the bit length is zero, return empty bytes
113
- if bit_length == 0:
114
- if (base_data_type.value in [
115
- DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
116
- ] and base_data_type.value != 0):
117
- raise EncodeError(
118
- f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
119
- return b''
120
-
121
- char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
122
- padding = (8 - ((bit_length + bit_position) % 8)) % 8
123
- odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0),
124
- f"Incorrect padding {padding}")
125
- left_pad = f"p{padding}" if padding > 0 else ""
126
-
127
- # actually encode the value
128
- coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)
129
-
130
- # apply byte order for numeric objects
131
- if not is_highlow_byte_order and base_data_type in [
132
- DataType.A_INT32,
133
- DataType.A_UINT32,
134
- DataType.A_FLOAT32,
135
- DataType.A_FLOAT64,
136
- ]:
137
- coded = coded[::-1]
138
-
139
- return cast(bytes, coded)
140
-
141
54
  def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
142
55
  """Helper method to get the minimal byte length.
143
56
  (needed for LeadingLength- and MinMaxLengthType)
@@ -160,11 +73,10 @@ class DiagCodedType(abc.ABC):
160
73
  odxassert(
161
74
  byte_length % 2 == 0, f"The bit length of A_UNICODE2STRING must"
162
75
  f" be a multiple of 16 but is {8*byte_length}")
76
+
163
77
  return byte_length
164
78
 
165
- @abc.abstractmethod
166
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
167
- bit_position: int) -> bytes:
79
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
168
80
  """Encode the internal value.
169
81
 
170
82
  Parameters
@@ -177,9 +89,9 @@ class DiagCodedType(abc.ABC):
177
89
  mapping from ID (of the length key) to bit length
178
90
  (only needed for ParamLengthInfoType)
179
91
  """
180
- pass
92
+ raise NotImplementedError(
93
+ f".encode_into_pdu() is not implemented by the class {type(self).__name__}")
181
94
 
182
- @abc.abstractmethod
183
95
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
184
96
  """Decode the parameter value from the coded message.
185
97
 
@@ -195,4 +107,5 @@ class DiagCodedType(abc.ABC):
195
107
  int
196
108
  the next byte position after the extracted parameter
197
109
  """
198
- pass
110
+ raise NotImplementedError(
111
+ f".decode_from_pdu() is not implemented by the class {type(self).__name__}")
odxtools/diagcomm.py CHANGED
@@ -11,7 +11,7 @@ from .element import IdentifiableElement
11
11
  from .exceptions import odxraise, odxrequire
12
12
  from .functionalclass import FunctionalClass
13
13
  from .nameditemlist import NamedItemList
14
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
14
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
15
15
  from .odxtypes import odxstr_to_bool
16
16
  from .specialdatagroup import SpecialDataGroup
17
17
  from .state import State
@@ -211,5 +211,13 @@ class DiagComm(IdentifiableElement):
211
211
  for sdg in self.sdgs:
212
212
  sdg._resolve_snrefs(diag_layer)
213
213
 
214
- self._protocols = NamedItemList(
215
- [diag_layer.protocols[prot_snref] for prot_snref in self.protocol_snrefs])
214
+ if TYPE_CHECKING:
215
+ self._protocols = NamedItemList([
216
+ resolve_snref(prot_snref, diag_layer.protocols, DiagLayer)
217
+ for prot_snref in self.protocol_snrefs
218
+ ])
219
+ else:
220
+ self._protocols = NamedItemList([
221
+ resolve_snref(prot_snref, diag_layer.protocols)
222
+ for prot_snref in self.protocol_snrefs
223
+ ])
@@ -10,6 +10,7 @@ from .createsdgs import create_sdgs_from_et
10
10
  from .dataobjectproperty import DataObjectProperty
11
11
  from .dopbase import DopBase
12
12
  from .dtcdop import DtcDop
13
+ from .dynamicendmarkerfield import DynamicEndmarkerField
13
14
  from .dynamiclengthfield import DynamicLengthField
14
15
  from .endofpdufield import EndOfPduField
15
16
  from .environmentdata import EnvironmentData
@@ -37,7 +38,7 @@ class DiagDataDictionarySpec:
37
38
  structures: NamedItemList[BasicStructure]
38
39
  static_fields: NamedItemList[StaticField]
39
40
  dynamic_length_fields: NamedItemList[DynamicLengthField]
40
- #dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
+ dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
42
  end_of_pdu_fields: NamedItemList[EndOfPduField]
42
43
  muxs: NamedItemList[Multiplexer]
43
44
  env_datas: NamedItemList[EnvironmentData]
@@ -54,7 +55,7 @@ class DiagDataDictionarySpec:
54
55
  self.structures,
55
56
  self.static_fields,
56
57
  self.dynamic_length_fields,
57
- #self.dynamic_endmarker_fields,
58
+ self.dynamic_endmarker_fields,
58
59
  self.end_of_pdu_fields,
59
60
  self.muxs,
60
61
  self.env_datas,
@@ -99,11 +100,10 @@ class DiagDataDictionarySpec:
99
100
  for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
100
101
  ]
101
102
 
102
- # TODO: dynamic endmarker fields
103
- #dynamic_endmarker_fields = [
104
- # DynamicEndmarkerField.from_et(dl_element, doc_frags)
105
- # for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
- #]
103
+ dynamic_endmarker_fields = [
104
+ DynamicEndmarkerField.from_et(dl_element, doc_frags) for dl_element in
105
+ et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
+ ]
107
107
 
108
108
  end_of_pdu_fields = [
109
109
  EndOfPduField.from_et(eofp_element, doc_frags)
@@ -145,7 +145,7 @@ class DiagDataDictionarySpec:
145
145
  structures=NamedItemList(structures),
146
146
  static_fields=NamedItemList(static_fields),
147
147
  dynamic_length_fields=NamedItemList(dynamic_length_fields),
148
- #dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
148
+ dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
149
149
  end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
150
150
  muxs=NamedItemList(muxs),
151
151
  env_datas=NamedItemList(env_datas),
@@ -172,8 +172,8 @@ class DiagDataDictionarySpec:
172
172
  odxlinks.update(static_field._build_odxlinks())
173
173
  for dynamic_length_field in self.dynamic_length_fields:
174
174
  odxlinks.update(dynamic_length_field._build_odxlinks())
175
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
- # odxlinks.update(dynamic_endmarker_field._build_odxlinks())
175
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
+ odxlinks.update(dynamic_endmarker_field._build_odxlinks())
177
177
  for end_of_pdu_field in self.end_of_pdu_fields:
178
178
  odxlinks.update(end_of_pdu_field._build_odxlinks())
179
179
  for mux in self.muxs:
@@ -204,8 +204,8 @@ class DiagDataDictionarySpec:
204
204
  static_field._resolve_odxlinks(odxlinks)
205
205
  for dynamic_length_field in self.dynamic_length_fields:
206
206
  dynamic_length_field._resolve_odxlinks(odxlinks)
207
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
- # dynamic_endmarker_field._resolve_odxlinks(odxlinks)
207
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
+ dynamic_endmarker_field._resolve_odxlinks(odxlinks)
209
209
  for end_of_pdu_field in self.end_of_pdu_fields:
210
210
  end_of_pdu_field._resolve_odxlinks(odxlinks)
211
211
  for mux in self.muxs:
@@ -234,8 +234,8 @@ class DiagDataDictionarySpec:
234
234
  static_field._resolve_snrefs(diag_layer)
235
235
  for dynamic_length_field in self.dynamic_length_fields:
236
236
  dynamic_length_field._resolve_snrefs(diag_layer)
237
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
- # dynamic_endmarker_field._resolve_snrefs(diag_layer)
237
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
+ dynamic_endmarker_field._resolve_snrefs(diag_layer)
239
239
  for end_of_pdu_field in self.end_of_pdu_fields:
240
240
  end_of_pdu_field._resolve_snrefs(diag_layer)
241
241
  for mux in self.muxs: