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
odxtools/dtcdop.py CHANGED
@@ -4,6 +4,8 @@ from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
+ from typing_extensions import override
8
+
7
9
  from .compumethods.compumethod import CompuMethod
8
10
  from .compumethods.createanycompumethod import create_any_compu_method_from_et
9
11
  from .createanydiagcodedtype import create_any_diag_coded_type_from_et
@@ -12,7 +14,7 @@ from .diagcodedtype import DiagCodedType
12
14
  from .diagnostictroublecode import DiagnosticTroubleCode
13
15
  from .dopbase import DopBase
14
16
  from .encodestate import EncodeState
15
- from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
17
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
16
18
  from .nameditemlist import NamedItemList
17
19
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
20
  from .odxtypes import ParameterValue, odxstr_to_bool
@@ -87,17 +89,20 @@ class DtcDop(DopBase):
87
89
  def linked_dtc_dops(self) -> NamedItemList["DtcDop"]:
88
90
  return self._linked_dtc_dops
89
91
 
92
+ @override
90
93
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
91
94
 
92
95
  int_trouble_code = self.diag_coded_type.decode_from_pdu(decode_state)
93
96
 
94
- if self.compu_method.is_valid_internal_value(int_trouble_code):
95
- trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
96
- else:
97
+ if not self.compu_method.is_valid_internal_value(int_trouble_code):
97
98
  # TODO: How to prevent this?
98
- raise DecodeError(
99
+ odxraise(
99
100
  f"DTC-DOP {self.short_name} could not convert the coded value "
100
- f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}.")
101
+ f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}.",
102
+ DecodeError)
103
+ return
104
+
105
+ trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
101
106
 
102
107
  assert isinstance(trouble_code, int)
103
108
 
@@ -110,10 +115,12 @@ class DtcDop(DopBase):
110
115
  return dtcs[0]
111
116
 
112
117
  # the DTC was not specified. This probably means that the
113
- # diagnostic description file is incomplete. We do not bail
114
- # out but we cannot provide an interpretation for it out of the
115
- # box...
116
- dtc = DiagnosticTroubleCode(
118
+ # diagnostic description file is incomplete.
119
+ odxraise(
120
+ f"Encountered DTC 0x{trouble_code:06x} which has not been defined "
121
+ f"by the database", DecodeError)
122
+
123
+ return DiagnosticTroubleCode(
117
124
  trouble_code=trouble_code,
118
125
  odx_id=cast(OdxLinkId, None),
119
126
  short_name=f'DTC_{trouble_code:06x}',
@@ -126,12 +133,9 @@ class DtcDop(DopBase):
126
133
  sdgs=[],
127
134
  )
128
135
 
129
- return dtc
130
-
131
- def convert_physical_to_bytes(self,
132
- physical_value: ParameterValue,
133
- encode_state: EncodeState,
134
- bit_position: int = 0) -> bytes:
136
+ @override
137
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
138
+ encode_state: EncodeState) -> None:
135
139
  if isinstance(physical_value, DiagnosticTroubleCode):
136
140
  trouble_code = physical_value.trouble_code
137
141
  elif isinstance(physical_value, int):
@@ -147,9 +151,7 @@ class DtcDop(DopBase):
147
151
  f" DiagnosticTroubleCode but got {physical_value!r}.")
148
152
 
149
153
  internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
150
-
151
- return self.diag_coded_type.convert_internal_to_bytes(
152
- internal_trouble_code, encode_state=encode_state, bit_position=bit_position)
154
+ self.diag_coded_type.encode_into_pdu(internal_trouble_code, encode_state)
153
155
 
154
156
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
155
157
  odxlinks = super()._build_odxlinks()
@@ -3,6 +3,8 @@ from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .decodestate import DecodeState
7
9
  from .determinenumberofitems import DetermineNumberOfItems
8
10
  from .encodestate import EncodeState
@@ -47,44 +49,46 @@ class DynamicLengthField(Field):
47
49
  super()._resolve_snrefs(diag_layer)
48
50
  self.determine_number_of_items._resolve_snrefs(diag_layer)
49
51
 
50
- def convert_physical_to_bytes(
51
- self,
52
- physical_value: ParameterValue,
53
- encode_state: EncodeState,
54
- bit_position: int = 0,
55
- ) -> bytes:
52
+ @override
53
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
54
+
55
+ odxassert(encode_state.cursor_bit_position == 0,
56
+ "No bit position can be specified for dynamic length fields!")
56
57
 
57
- odxassert(bit_position == 0, "No bit position can be specified for dynamic length fields!")
58
58
  if not isinstance(physical_value, list):
59
59
  odxraise(
60
60
  f"Expected a list of values for dynamic length field {self.short_name}, "
61
61
  f"got {type(physical_value)}", EncodeError)
62
62
 
63
+ # move the origin to the cursor position
64
+ orig_cursor = encode_state.cursor_byte_position
65
+ orig_origin = encode_state.origin_byte_position
66
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
67
+
63
68
  det_num_items = self.determine_number_of_items
64
- field_len = det_num_items.dop.convert_physical_to_bytes(
65
- len(physical_value), encode_state, det_num_items.bit_position or 0)
66
-
67
- # hack to emplace the length specifier at the correct location
68
- tmp = encode_state.coded_message
69
- encode_state.coded_message = bytearray()
70
- encode_state.emplace_atomic_value(field_len, self.short_name + ".num_items",
71
- det_num_items.byte_position)
72
- result = encode_state.coded_message
73
- encode_state.coded_message = tmp
74
-
75
- # if required, add padding between the length specifier and
76
- # the first item
77
- if len(result) < self.offset:
78
- result.extend([0] * (self.offset - len(result)))
79
- elif len(result) > self.offset:
69
+ encode_state.cursor_bit_position = self.determine_number_of_items.bit_position or 0
70
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + det_num_items.byte_position
71
+ det_num_items.dop.encode_into_pdu(len(physical_value), encode_state)
72
+
73
+ if encode_state.cursor_byte_position - encode_state.origin_byte_position > self.offset:
80
74
  odxraise(f"The length specifier of field {self.short_name} overlaps "
81
- f"with the first item!")
75
+ f"with its first item!")
76
+
77
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.offset
78
+ encode_state.cursor_bit_position = 0
82
79
 
83
80
  for value in physical_value:
84
- result += self.structure.convert_physical_to_bytes(value, encode_state)
81
+ self.structure.encode_into_pdu(value, encode_state)
85
82
 
86
- return result
83
+ # ensure the correct message size if the field is empty
84
+ if len(physical_value) == 0:
85
+ encode_state.emplace_bytes(b"")
86
+
87
+ # move cursor and origin positions
88
+ encode_state.origin_byte_position = orig_origin
89
+ encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
87
90
 
91
+ @override
88
92
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
89
93
 
90
94
  odxassert(decode_state.cursor_bit_position == 0,
odxtools/encodestate.py CHANGED
@@ -1,12 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Dict, Optional
4
+ from typing import Dict, Optional
5
5
 
6
- from .exceptions import OdxWarning
6
+ from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
7
+ from .odxtypes import AtomicOdxType, DataType
7
8
 
8
- if TYPE_CHECKING:
9
- from .tablerow import TableRow
9
+ try:
10
+ import bitstruct.c as bitstruct
11
+ except ImportError:
12
+ import bitstruct
10
13
 
11
14
 
12
15
  @dataclass
@@ -15,10 +18,23 @@ class EncodeState:
15
18
  """
16
19
 
17
20
  #: payload that has been constructed so far
18
- coded_message: bytearray
21
+ coded_message: bytearray = field(default_factory=bytearray)
19
22
 
20
- #: a mapping from short name to value for each parameter
21
- parameter_values: Dict[str, Any]
23
+ #: the bits of the payload that are used
24
+ used_mask: bytearray = field(default_factory=bytearray)
25
+
26
+ #: The absolute position in bytes from the beginning of the PDU to
27
+ #: which relative positions refer to, e.g., the beginning of the
28
+ #: structure.
29
+ origin_byte_position: int = 0
30
+
31
+ #: The absolute position in bytes from the beginning of the PDU
32
+ #: where the next object ought to be placed into the PDU
33
+ cursor_byte_position: int = 0
34
+
35
+ #: The bit position [0-7] where the next object ought to be
36
+ #: placed into the PDU
37
+ cursor_bit_position: int = 0
22
38
 
23
39
  #: If encoding a response: request that triggered the response
24
40
  triggering_request: Optional[bytes] = None
@@ -28,31 +44,180 @@ class EncodeState:
28
44
  length_keys: Dict[str, int] = field(default_factory=dict)
29
45
 
30
46
  #: Mapping from the short name of a table-key parameter to the
31
- #: corresponding row of the table (specified by TableKeyParameter)
32
- table_keys: Dict[str, "TableRow"] = field(default_factory=dict)
47
+ #: short name of the corresponding row of the table (specified by
48
+ #: TableKeyParameter)
49
+ table_keys: Dict[str, str] = field(default_factory=dict)
50
+
51
+ #: The cursor position where a given length- or table key is located
52
+ #: in the PDU
53
+ key_pos: Dict[str, int] = field(default_factory=dict)
33
54
 
34
55
  #: Flag whether we are currently the last parameter of the PDU
35
- #: (needed for MinMaxLengthType)
36
- is_end_of_pdu: bool = False
56
+ #: (needed for MinMaxLengthType, EndOfPduField, etc.)
57
+ is_end_of_pdu: bool = True
58
+
59
+ def __post_init__(self) -> None:
60
+ # if a coded message has been specified, but no used_mask, we
61
+ # assume that all of the bits of the coded message are
62
+ # currently used.
63
+ if len(self.coded_message) > len(self.used_mask):
64
+ self.used_mask += b'\xff' * (len(self.coded_message) - len(self.used_mask))
65
+ if len(self.coded_message) < len(self.used_mask):
66
+ odxraise(f"The specified bit mask 0x{self.used_mask.hex()} for used bits "
67
+ f"is not suitable for representing the coded_message "
68
+ f"0x{self.coded_message.hex()}")
69
+ self.used_mask = self.used_mask[:len(self.coded_message)]
70
+
71
+ def emplace_atomic_value(
72
+ self,
73
+ *,
74
+ internal_value: AtomicOdxType,
75
+ bit_length: int,
76
+ base_data_type: DataType,
77
+ is_highlow_byte_order: bool,
78
+ used_mask: Optional[bytes],
79
+ ) -> None:
80
+ """Convert the internal_value to bytes and emplace this into the PDU"""
81
+
82
+ raw_value: AtomicOdxType
83
+
84
+ # Check that bytes and strings actually fit into the bit length
85
+ if base_data_type == DataType.A_BYTEFIELD:
86
+ if not isinstance(internal_value, bytes):
87
+ odxraise()
88
+ if 8 * len(internal_value) > bit_length:
89
+ raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
90
+ f"({len(internal_value)} bytes)."
91
+ f" The maximum length is {bit_length//8}.")
92
+ raw_value = internal_value
93
+ elif base_data_type == DataType.A_ASCIISTRING:
94
+ if not isinstance(internal_value, str):
95
+ odxraise()
96
+
97
+ # The spec says ASCII, meaning only byte values 0-127.
98
+ # But in practice, vendors use iso-8859-1, aka latin-1
99
+ # reason being iso-8859-1 never fails since it has a valid
100
+ # character mapping for every possible byte sequence.
101
+ raw_value = internal_value.encode("iso-8859-1")
102
+
103
+ if 8 * len(raw_value) > bit_length:
104
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
105
+ f" The maximum number of characters is {bit_length//8}.")
106
+ elif base_data_type == DataType.A_UTF8STRING:
107
+ if not isinstance(internal_value, str):
108
+ odxraise()
109
+
110
+ raw_value = internal_value.encode("utf-8")
111
+
112
+ if 8 * len(raw_value) > bit_length:
113
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
114
+ f" The maximum number of bytes is {bit_length//8}.")
115
+
116
+ elif base_data_type == DataType.A_UNICODE2STRING:
117
+ if not isinstance(internal_value, str):
118
+ odxraise()
37
119
 
38
- def emplace_atomic_value(self,
39
- new_data: bytes,
40
- param_name: str,
41
- pos: Optional[int] = None) -> None:
42
- if pos is None:
43
- pos = len(self.coded_message)
120
+ text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
121
+ raw_value = internal_value.encode(text_encoding)
122
+
123
+ if 8 * len(raw_value) > bit_length:
124
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
125
+ f" The maximum number of characters is {bit_length//16}.")
126
+ else:
127
+ raw_value = internal_value
128
+
129
+ # If the bit length is zero, return empty bytes
130
+ if bit_length == 0:
131
+ if (base_data_type.value in [
132
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
133
+ ] and base_data_type.value != 0):
134
+ odxraise(
135
+ f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
136
+ EncodeError)
137
+ self.emplace_bytes(b'')
138
+ return
139
+
140
+ char = base_data_type.bitstruct_format_letter
141
+ padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
142
+ odxassert((0 <= padding and padding < 8 and
143
+ (padding + bit_length + self.cursor_bit_position) % 8 == 0),
144
+ f"Incorrect padding {padding}")
145
+ left_pad = f"p{padding}" if padding > 0 else ""
146
+
147
+ # actually encode the value
148
+ coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
149
+
150
+ # create the raw mask of used bits for numeric objects
151
+ used_mask_raw = used_mask
152
+ if base_data_type in [DataType.A_INT32, DataType.A_UINT32
153
+ ] and (self.cursor_bit_position != 0 or
154
+ (self.cursor_bit_position + bit_length) % 8 != 0):
155
+ if used_mask is None:
156
+ tmp = (1 << bit_length) - 1
157
+ else:
158
+ tmp = int.from_bytes(used_mask, "big")
159
+ tmp <<= self.cursor_bit_position
160
+
161
+ used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
162
+
163
+ # apply byte order to numeric objects
164
+ if not is_highlow_byte_order and base_data_type in [
165
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
166
+ ]:
167
+ coded = coded[::-1]
168
+
169
+ if used_mask_raw is not None:
170
+ used_mask_raw = used_mask_raw[::-1]
171
+
172
+ self.cursor_bit_position = 0
173
+ self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
174
+
175
+ def emplace_bytes(self,
176
+ new_data: bytes,
177
+ obj_name: Optional[str] = None,
178
+ obj_used_mask: Optional[bytes] = None) -> None:
179
+ if self.cursor_bit_position != 0:
180
+ odxraise("EncodeState.emplace_bytes can only be called "
181
+ "for a bit position of 0!", RuntimeError)
182
+
183
+ pos = self.cursor_byte_position
44
184
 
45
185
  # Make blob longer if necessary
46
186
  min_length = pos + len(new_data)
47
187
  if len(self.coded_message) < min_length:
48
- self.coded_message.extend([0] * (min_length - len(self.coded_message)))
188
+ pad = b'\x00' * (min_length - len(self.coded_message))
189
+ self.coded_message += pad
190
+ self.used_mask += pad
191
+
192
+ if obj_used_mask is None:
193
+ # Happy path for when no obj_used_mask has been
194
+ # specified. In this case we assume that all bits of the
195
+ # new data to be emplaced are used.
196
+ n = len(new_data)
49
197
 
50
- for byte_idx_val, byte_idx_rpc in enumerate(range(pos, pos + len(new_data))):
51
- # insert byte value
52
- if self.coded_message[byte_idx_rpc] & new_data[byte_idx_val] != 0:
198
+ if self.used_mask[pos:pos + n] != b'\x00' * n:
53
199
  warnings.warn(
54
- f"Object '{param_name}' overlaps with another parameter (bytes are already set)",
200
+ f"Overlapping objects detected in between bytes {pos} and "
201
+ f"{pos+n}",
55
202
  OdxWarning,
56
203
  stacklevel=1,
57
204
  )
58
- self.coded_message[byte_idx_rpc] |= new_data[byte_idx_val]
205
+ self.coded_message[pos:pos + n] = new_data
206
+ self.used_mask[pos:pos + n] = b'\xff' * n
207
+ else:
208
+ # insert data the hard way, i.e. we have to look at each
209
+ # individual byte to determine if it has already been used
210
+ # somewhere else (it would be nice if bytearrays supported
211
+ # bitwise operations!)
212
+ for i in range(len(new_data)):
213
+ if self.used_mask[pos + i] & obj_used_mask[i] != 0:
214
+ warnings.warn(
215
+ f"Overlapping objects detected at position {pos + i}",
216
+ OdxWarning,
217
+ stacklevel=1,
218
+ )
219
+ self.coded_message[pos + i] &= ~obj_used_mask[i]
220
+ self.coded_message[pos + i] |= new_data[i] & obj_used_mask[i]
221
+ self.used_mask[pos + i] |= obj_used_mask[i]
222
+
223
+ self.cursor_byte_position += len(new_data)
odxtools/endofpdufield.py CHANGED
@@ -3,6 +3,8 @@ from dataclasses import dataclass
3
3
  from typing import List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .decodestate import DecodeState
7
9
  from .encodestate import EncodeState
8
10
  from .exceptions import EncodeError, odxassert, odxraise
@@ -39,26 +41,22 @@ class EndOfPduField(Field):
39
41
 
40
42
  return eopf
41
43
 
42
- def convert_physical_to_bytes(
43
- self,
44
- physical_value: ParameterValue,
45
- encode_state: EncodeState,
46
- bit_position: int = 0,
47
- ) -> bytes:
44
+ @override
45
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
46
+ encode_state: EncodeState) -> None:
47
+ odxassert(not encode_state.cursor_bit_position,
48
+ "No bit position can be specified for end-of-pdu fields!")
48
49
 
49
- odxassert(
50
- bit_position == 0, "End of PDU field must be byte aligned. "
51
- "Is there an error in reading the .odx?", EncodeError)
52
50
  if not isinstance(physical_value, list):
53
51
  odxraise(
54
- f"Expected a list of values for end-of-pdu field {self.short_name}, "
55
- f"got {type(physical_value)}", EncodeError)
52
+ f"Invalid type {type(physical_value).__name__} of physical "
53
+ f"value for end-of-pdu field, expected a list", EncodeError)
54
+ return
56
55
 
57
- coded_message = b''
58
56
  for value in physical_value:
59
- coded_message += self.structure.convert_physical_to_bytes(value, encode_state)
60
- return coded_message
57
+ self.structure.encode_into_pdu(value, encode_state)
61
58
 
59
+ @override
62
60
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
63
61
  odxassert(not decode_state.cursor_bit_position,
64
62
  "No bit position can be specified for end-of-pdu fields!")
@@ -1,13 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
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
7
+
6
8
  from .complexdop import ComplexDop
7
9
  from .decodestate import DecodeState
8
10
  from .encodestate import EncodeState
9
11
  from .environmentdata import EnvironmentData
10
- from .exceptions import DecodeError, EncodeError, odxrequire
12
+ from .exceptions import odxraise, odxrequire
11
13
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
14
  from .odxtypes import ParameterValue
13
15
  from .utils import dataclass_fields_asdict
@@ -86,23 +88,21 @@ class EnvironmentDataDescription(ComplexDop):
86
88
  for ed in self.env_datas:
87
89
  ed._resolve_snrefs(diag_layer)
88
90
 
89
- def convert_physical_to_bytes(self,
90
- physical_value: ParameterValue,
91
- encode_state: EncodeState,
92
- bit_position: int = 0) -> bytes:
93
- """Convert the physical value into bytes.
91
+ @override
92
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
93
+ encode_state: EncodeState) -> None:
94
+ """Convert a physical value into bytes and emplace them into a PDU.
94
95
 
95
96
  Since environmental data is supposed to never appear on the
96
97
  wire, this method just raises an EncodeError exception.
97
98
  """
98
- raise EncodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
99
+ odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
99
100
 
100
- def convert_bytes_to_physical(self,
101
- decode_state: DecodeState,
102
- bit_position: int = 0) -> Tuple[ParameterValue, int]:
103
- """Extract the bytes from the PDU and convert them to the physical value.
101
+ @override
102
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
103
+ """Extract the bytes from a PDU and convert them to a physical value.
104
104
 
105
105
  Since environmental data is supposed to never appear on the
106
106
  wire, this method just raises an DecodeError exception.
107
107
  """
108
- raise DecodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
108
+ odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
@@ -1,11 +1,13 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Optional
3
+ from typing import 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 EncodeError, odxassert, odxraise
9
11
  from .odxtypes import AtomicOdxType, DataType
10
12
 
11
13
 
@@ -42,29 +44,40 @@ class LeadingLengthInfoType(DiagCodedType):
42
44
  # DCT is dynamic!
43
45
  return None
44
46
 
45
- def convert_internal_to_bytes(self, internal_value: Any, encode_state: EncodeState,
46
- bit_position: int) -> bytes:
47
+ @override
48
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
49
+
50
+ if not isinstance(internal_value, (str, bytes)):
51
+ odxraise(
52
+ f"LEADING-LENGTH-INFO types can only be used for strings and byte fields, "
53
+ f"not {type(internal_value).__name__}", EncodeError)
54
+ return
47
55
 
48
56
  byte_length = self._minimal_byte_length_of(internal_value)
49
57
 
50
- length_bytes = self._encode_internal_value(
51
- byte_length,
52
- bit_position=bit_position,
58
+ used_mask = None
59
+ bit_pos = encode_state.cursor_bit_position
60
+ if encode_state.cursor_bit_position != 0 or (bit_pos + self.bit_length) % 8 != 0:
61
+ used_mask = (1 << self.bit_length) - 1
62
+ used_mask <<= bit_pos
63
+
64
+ encode_state.emplace_atomic_value(
65
+ internal_value=byte_length,
66
+ used_mask=None,
53
67
  bit_length=self.bit_length,
54
68
  base_data_type=DataType.A_UINT32,
55
69
  is_highlow_byte_order=self.is_highlow_byte_order,
56
70
  )
57
71
 
58
- value_bytes = self._encode_internal_value(
59
- internal_value,
60
- bit_position=0,
72
+ encode_state.emplace_atomic_value(
73
+ internal_value=internal_value,
74
+ used_mask=None,
61
75
  bit_length=8 * byte_length,
62
76
  base_data_type=self.base_data_type,
63
77
  is_highlow_byte_order=self.is_highlow_byte_order,
64
78
  )
65
79
 
66
- return length_bytes + value_bytes
67
-
80
+ @override
68
81
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
69
82
 
70
83
  # Extract length of the parameter value