odxtools 6.7.0__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 (46) hide show
  1. odxtools/basicstructure.py +106 -112
  2. odxtools/compumethods/createanycompumethod.py +1 -1
  3. odxtools/dataobjectproperty.py +3 -7
  4. odxtools/decodestate.py +1 -13
  5. odxtools/diagcodedtype.py +9 -96
  6. odxtools/diaglayer.py +45 -0
  7. odxtools/diagservice.py +12 -16
  8. odxtools/dopbase.py +5 -8
  9. odxtools/dtcdop.py +21 -19
  10. odxtools/dynamiclengthfield.py +30 -26
  11. odxtools/encodestate.py +188 -23
  12. odxtools/endofpdufield.py +12 -14
  13. odxtools/environmentdatadescription.py +13 -13
  14. odxtools/leadinglengthinfotype.py +25 -12
  15. odxtools/minmaxlengthtype.py +36 -26
  16. odxtools/multiplexer.py +42 -23
  17. odxtools/multiplexercase.py +1 -1
  18. odxtools/nameditemlist.py +14 -0
  19. odxtools/odxtypes.py +17 -0
  20. odxtools/parameterinfo.py +126 -40
  21. odxtools/parameters/codedconstparameter.py +14 -11
  22. odxtools/parameters/dynamicparameter.py +5 -4
  23. odxtools/parameters/lengthkeyparameter.py +62 -14
  24. odxtools/parameters/matchingrequestparameter.py +23 -11
  25. odxtools/parameters/nrcconstparameter.py +39 -20
  26. odxtools/parameters/parameter.py +29 -42
  27. odxtools/parameters/parameterwithdop.py +5 -8
  28. odxtools/parameters/physicalconstantparameter.py +12 -13
  29. odxtools/parameters/reservedparameter.py +5 -2
  30. odxtools/parameters/systemparameter.py +3 -2
  31. odxtools/parameters/tableentryparameter.py +3 -2
  32. odxtools/parameters/tablekeyparameter.py +79 -30
  33. odxtools/parameters/tablestructparameter.py +62 -42
  34. odxtools/parameters/valueparameter.py +12 -14
  35. odxtools/paramlengthinfotype.py +30 -22
  36. odxtools/request.py +9 -0
  37. odxtools/response.py +5 -13
  38. odxtools/standardlengthtype.py +51 -13
  39. odxtools/staticfield.py +15 -19
  40. odxtools/version.py +2 -2
  41. {odxtools-6.7.0.dist-info → odxtools-7.0.0.dist-info}/METADATA +1 -1
  42. {odxtools-6.7.0.dist-info → odxtools-7.0.0.dist-info}/RECORD +46 -46
  43. {odxtools-6.7.0.dist-info → odxtools-7.0.0.dist-info}/LICENSE +0 -0
  44. {odxtools-6.7.0.dist-info → odxtools-7.0.0.dist-info}/WHEEL +0 -0
  45. {odxtools-6.7.0.dist-info → odxtools-7.0.0.dist-info}/entry_points.txt +0 -0
  46. {odxtools-6.7.0.dist-info → odxtools-7.0.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
@@ -21,6 +23,7 @@ from .parameters.parameter import Parameter
21
23
  from .parameters.parameterwithdop import ParameterWithDOP
22
24
  from .parameters.physicalconstantparameter import PhysicalConstantParameter
23
25
  from .parameters.tablekeyparameter import TableKeyParameter
26
+ from .parameters.tablestructparameter import TableStructParameter
24
27
  from .utils import dataclass_fields_asdict
25
28
 
26
29
  if TYPE_CHECKING:
@@ -54,32 +57,29 @@ class BasicStructure(ComplexDop):
54
57
  return 8 * self.byte_size
55
58
 
56
59
  cursor = 0
57
- length = 0
60
+ byte_length = 0
58
61
  for param in self.parameters:
59
62
  param_bit_length = param.get_static_bit_length()
60
63
  if param_bit_length is None:
61
64
  # We were not able to calculate a static bit length
62
65
  return None
63
66
  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
67
+ cursor = param.byte_position
67
68
 
68
- cursor += param_bit_length
69
- length = max(length, cursor)
69
+ cursor += ((param.bit_position or 0) + param_bit_length + 7) // 8
70
+ byte_length = max(byte_length, cursor)
70
71
 
71
72
  # Round up to account for padding bits (all structures are
72
73
  # byte aligned)
73
- return ((length + 7) // 8) * 8
74
+ return byte_length * 8
74
75
 
75
76
  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)
77
+ encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
78
+
79
79
  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))
80
+ if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
81
+ isinstance(param, (CodedConstParameter, NrcConstParameter, PhysicalConstantParameter)):
82
+ param.encode_into_pdu(physical_value=None, encode_state=encode_state)
83
83
  else:
84
84
  break
85
85
  return encode_state.coded_message
@@ -116,58 +116,101 @@ class BasicStructure(ComplexDop):
116
116
 
117
117
  print(parameter_info(self.free_parameters), end="")
118
118
 
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:
119
+ def _validate_coded_message_size(self, coded_byte_len: int) -> None:
120
+
121
+ if self.byte_size is not None:
122
+ # We definitely broke something if we didn't respect the explicit byte_size
123
+ if self.byte_size != coded_byte_len:
124
+ warnings.warn(
125
+ "Verification of coded message failed: Incorrect size.",
126
+ OdxWarning,
127
+ stacklevel=1)
128
+
129
+ return
130
+
131
+ bit_length = self.get_static_bit_length()
123
132
 
124
- if not isinstance(param_value, dict):
125
- raise EncodeError(
133
+ if bit_length is None:
134
+ # Nothing to check
135
+ return
136
+
137
+ if coded_byte_len * 8 != bit_length:
138
+ # We may have broke something
139
+ # but it could be that bit_length was mis calculated and not the actual bytes are wrong
140
+ # Could happen with overlapping parameters and parameters with gaps
141
+ warnings.warn(
142
+ "Verification of coded message possibly failed: Size may be incorrect.",
143
+ OdxWarning,
144
+ stacklevel=1)
145
+
146
+ @override
147
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
148
+ encode_state: EncodeState) -> None:
149
+ if not isinstance(physical_value, dict):
150
+ odxraise(
126
151
  f"Expected a dictionary for the values of structure {self.short_name}, "
127
- f"got {type(param_value)}")
152
+ f"got {type(physical_value).__name__}", EncodeError)
153
+ elif encode_state.cursor_bit_position != 0:
154
+ odxraise(
155
+ f"Structures must be byte aligned, but "
156
+ f"{self.short_name} requested to be at bit position "
157
+ f"{encode_state.cursor_bit_position}", EncodeError)
158
+ encode_state.bit_position = 0
159
+
160
+ orig_cursor = encode_state.cursor_byte_position
161
+ orig_origin = encode_state.origin_byte_position
162
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
163
+
164
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
165
+ encode_state.is_end_of_pdu = False
128
166
 
129
167
  # in strict mode, ensure that no values for unknown parameters are specified.
130
168
  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
- )
169
+ param_names = {param.short_name for param in self.parameters}
170
+ for param_value_name in physical_value:
171
+ if param_value_name not in param_names:
172
+ odxraise(f"Value for unknown parameter '{param_value_name}' specified "
173
+ f"for structure {self.short_name}")
142
174
 
143
175
  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
176
+ if id(param) == id(self.parameters[-1]):
177
+ # The last parameter of the structure is at the end of
178
+ # the PDU if the structure itself is at the end of the
179
+ # PDU. TODO: This assumes that the last parameter
180
+ # specified in the ODX is located last in the PDU...
181
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
182
+
183
+ if isinstance(param, (LengthKeyParameter, TableKeyParameter)):
184
+ # At this point, we encode a placeholder value for length-
185
+ # and table keys, since these can be specified
156
186
  # implicitly (i.e., by means of parameters that use
157
- # these keys), to avoid getting an "overlapping
187
+ # these keys). To avoid getting an "overlapping
158
188
  # parameter" warning, we must encode a value of zero
159
189
  # 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
190
+ # parameter in a post-processing step.
191
+ param.encode_placeholder_into_pdu(
192
+ physical_value=physical_value.get(param.short_name), encode_state=encode_state)
193
+
164
194
  continue
165
195
 
166
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
196
+ if param.is_required and param.short_name not in physical_value:
197
+ odxraise(f"No value for required parameter {param.short_name} specified",
198
+ EncodeError)
167
199
 
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")
200
+ param.encode_into_pdu(
201
+ physical_value=physical_value.get(param.short_name), encode_state=encode_state)
202
+
203
+ encode_state.is_end_of_pdu = False
204
+ if self.byte_size is not None:
205
+ actual_len = encode_state.cursor_byte_position - encode_state.origin_byte_position
206
+ if actual_len < self.byte_size:
207
+ # Padding bytes needed. We add an empty object at the
208
+ # position directly after the structure and let
209
+ # EncodeState add the padding as needed.
210
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_size
211
+ # Padding bytes needed. these count as "used".
212
+ encode_state.coded_message += b"\x00" * (self.byte_size - actual_len)
213
+ encode_state.used_mask += b"\xff" * (self.byte_size - actual_len)
171
214
 
172
215
  # encode the length- and table keys. This cannot be done above
173
216
  # because we allow these to be defined implicitly (i.e. they
@@ -177,56 +220,17 @@ class BasicStructure(ComplexDop):
177
220
  # the current parameter is neither a length- nor a table key
178
221
  continue
179
222
 
180
- # Encode the key parameter into the message
181
- encode_state.coded_message = bytearray(param.encode_into_pdu(encode_state))
223
+ # Encode the value of the key parameter into the message
224
+ param.encode_value_into_pdu(encode_state=encode_state)
182
225
 
183
226
  # 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
227
+ self._validate_coded_message_size(encode_state.cursor_byte_position -
228
+ encode_state.origin_byte_position)
203
229
 
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
- )
230
+ encode_state.origin_byte_position = orig_origin
231
+ encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
229
232
 
233
+ @override
230
234
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
231
235
  # move the origin since positions specified by sub-parameters of
232
236
  # structures are relative to the beginning of the structure object.
@@ -244,19 +248,6 @@ class BasicStructure(ComplexDop):
244
248
 
245
249
  return result
246
250
 
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
251
  def decode(self, message: bytes) -> ParameterValueDict:
261
252
  decode_state = DecodeState(coded_message=message)
262
253
  param_values = self.decode_from_pdu(decode_state)
@@ -316,4 +307,7 @@ class BasicStructure(ComplexDop):
316
307
  super()._resolve_snrefs(diag_layer)
317
308
 
318
309
  for param in self.parameters:
319
- param._resolve_snrefs(diag_layer)
310
+ if isinstance(param, TableStructParameter):
311
+ param._table_struct_resolve_snrefs(diag_layer, param_list=self.parameters)
312
+ else:
313
+ param._resolve_snrefs(diag_layer)
@@ -137,7 +137,7 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
137
137
  scale_elem, doc_frags, internal_type=internal_type,
138
138
  physical_type=physical_type))
139
139
  compu_default_value = create_compu_default_value(
140
- et_element.find("COMPU-DEFAULT-VALUE"), doc_frags, **kwargs)
140
+ et_element.find("COMPU-INTERNAL-TO-PHYS/COMPU-DEFAULT-VALUE"), doc_frags, **kwargs)
141
141
 
142
142
  return TexttableCompuMethod(
143
143
  internal_to_phys=internal_to_phys,
@@ -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/diaglayer.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import re
3
3
  import warnings
4
+ from copy import copy
4
5
  from dataclasses import dataclass
5
6
  from functools import cached_property
6
7
  from itertools import chain
@@ -76,6 +77,50 @@ class DiagLayer:
76
77
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
77
78
  """Recursively resolve all references."""
78
79
 
80
+ # deal with the import references: these basically extend the
81
+ # pool of objects that are referenceable without having to
82
+ # explicitly specify the DOCREF attribute in the
83
+ # reference. This mechanism can thus be seen as a kind of
84
+ # "poor man's inheritance".
85
+ if self.import_refs:
86
+ imported_links: Dict[OdxLinkId, Any] = {}
87
+ for import_ref in self.import_refs:
88
+ imported_dl = odxlinks.resolve(import_ref, DiagLayer)
89
+
90
+ odxassert(
91
+ imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
92
+ f"Tried to import references from diagnostic layer "
93
+ f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
94
+ f"Only ECU-SHARED-DATA layers may be referenced using the "
95
+ f"IMPORT-REF mechanism")
96
+
97
+ # TODO: ensure that the imported diagnostic layer has
98
+ # not been referenced in any PARENT-REF of the current
99
+ # layer or any of its parents.
100
+
101
+ # TODO: detect and complain about cyclic IMPORT-REFs
102
+
103
+ # TODO (?): detect conflicts with locally-defined
104
+ # objects
105
+
106
+ imported_dl_links = imported_dl._build_odxlinks()
107
+ for link_id, obj in imported_dl_links.items():
108
+ # the imported objects shall behave as if they
109
+ # were defined by the importing layer. IOW, they
110
+ # must be visible in the same document fragments.
111
+ link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
112
+ imported_links[link_id] = obj
113
+
114
+ # We need to copy the odxlink database here since this
115
+ # function must not modify its argument because the
116
+ # imported references only apply within this specific
117
+ # diagnostic layer
118
+ extended_odxlinks = copy(odxlinks)
119
+ extended_odxlinks.update(imported_links)
120
+
121
+ self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
122
+ return
123
+
79
124
  self.diag_layer_raw._resolve_odxlinks(odxlinks)
80
125
 
81
126
  def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
odxtools/diagservice.py CHANGED
@@ -215,13 +215,9 @@ class DiagService(DiagComm):
215
215
  coding_object=coding_object,
216
216
  param_dict=param_dict)
217
217
 
218
- def encode_request(self, **params: ParameterValue) -> bytes:
218
+ def encode_request(self, **kwargs: ParameterValue) -> bytes:
219
219
  """
220
- Composes an UDS request as list of bytes for this service.
221
- Parameters:
222
- ----------
223
- params: dict
224
- Parameters of the RPC as mapping from SHORT-NAME of the parameter to the physical value
220
+ Composes an UDS request an array of bytes for this service.
225
221
  """
226
222
  # make sure that all parameters which are required for
227
223
  # encoding are specified (parameters which have a default are
@@ -230,31 +226,31 @@ class DiagService(DiagComm):
230
226
  return b''
231
227
 
232
228
  missing_params = {x.short_name
233
- for x in self.request.required_parameters}.difference(params.keys())
229
+ for x in self.request.required_parameters}.difference(kwargs.keys())
234
230
  odxassert(
235
231
  len(missing_params) == 0, f"The parameters {missing_params} are required but missing!")
236
232
 
237
233
  # make sure that no unknown parameters are specified
238
234
  rq_all_param_names = {x.short_name for x in self.request.parameters}
239
235
  odxassert(
240
- set(params.keys()).issubset(rq_all_param_names),
241
- f"Unknown parameters specified for encoding: {params.keys()}, "
236
+ set(kwargs.keys()).issubset(rq_all_param_names),
237
+ f"Unknown parameters specified for encoding: {kwargs.keys()}, "
242
238
  f"known parameters are: {rq_all_param_names}")
243
- return self.request.encode(coded_request=None, **params)
239
+ return self.request.encode(**kwargs)
244
240
 
245
241
  def encode_positive_response(self,
246
242
  coded_request: bytes,
247
243
  response_index: int = 0,
248
- **params: ParameterValue) -> bytes:
244
+ **kwargs: ParameterValue) -> bytes:
249
245
  # TODO: Should the user decide the positive response or what are the differences?
250
- return self.positive_responses[response_index].encode(coded_request, **params)
246
+ return self.positive_responses[response_index].encode(coded_request, **kwargs)
251
247
 
252
248
  def encode_negative_response(self,
253
249
  coded_request: bytes,
254
250
  response_index: int = 0,
255
- **params: ParameterValue) -> bytes:
256
- return self.negative_responses[response_index].encode(coded_request, **params)
251
+ **kwargs: ParameterValue) -> bytes:
252
+ return self.negative_responses[response_index].encode(coded_request, **kwargs)
257
253
 
258
- def __call__(self, **params: ParameterValue) -> bytes:
254
+ def __call__(self, **kwargs: ParameterValue) -> bytes:
259
255
  """Encode a request."""
260
- return self.encode_request(**params)
256
+ return self.encode_request(**kwargs)
odxtools/dopbase.py CHANGED
@@ -21,7 +21,8 @@ if TYPE_CHECKING:
21
21
  class DopBase(IdentifiableElement):
22
22
  """Base class for all DOPs.
23
23
 
24
- Any class that a parameter can reference via a DOP-REF should inherit from this class.
24
+ Any class that a parameter can reference via a DOP-REF should
25
+ inherit from this class.
25
26
  """
26
27
 
27
28
  admin_data: Optional[AdminData]
@@ -60,15 +61,11 @@ class DopBase(IdentifiableElement):
60
61
  return None
61
62
 
62
63
  def is_valid_physical_value(self, physical_value: ParameterValue) -> bool:
63
- """Determine if a phyical value can be handled by the DOP
64
- """
64
+ """Determine if a phyical value can be handled by the DOP"""
65
65
  raise NotImplementedError
66
66
 
67
- def convert_physical_to_bytes(self,
68
- physical_value: ParameterValue,
69
- encode_state: EncodeState,
70
- bit_position: int = 0) -> bytes:
71
- """Convert the physical value into bytes."""
67
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
68
+ """Convert the physical value to bytes and emplace them into a PDU."""
72
69
  raise NotImplementedError
73
70
 
74
71
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: