diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index e4645473..e3a30dc6 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -68,7 +68,8 @@ def get_static_bit_length(self) -> Optional[int]: cursor += param_bit_length length = max(length, cursor) - # Round up to account for padding bits + # Round up to account for padding bits (all structures are + # byte aligned) return ((length + 7) // 8) * 8 def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes: diff --git a/odxtools/parameters/codedconstparameter.py b/odxtools/parameters/codedconstparameter.py index a0d3fc1e..2f7f7234 100644 --- a/odxtools/parameters/codedconstparameter.py +++ b/odxtools/parameters/codedconstparameter.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, Optional +from typing_extensions import override + from ..decodestate import DecodeState from ..diagcodedtype import DiagCodedType from ..encodestate import EncodeState @@ -62,13 +64,8 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return self.diag_coded_type.convert_internal_to_bytes( self.coded_value, encode_state=encode_state, bit_position=bit_position_int) - def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: - # Extract coded values - orig_cursor_pos = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - - decode_state.cursor_bit_position = self.bit_position or 0 + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: coded_val = self.diag_coded_type.decode_from_pdu(decode_state) # Check if the coded value in the message is correct. @@ -83,8 +80,6 @@ def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: stacklevel=1, ) - decode_state.cursor_byte_position = max(orig_cursor_pos, decode_state.cursor_byte_position) - return coded_val @property diff --git a/odxtools/parameters/dynamicparameter.py b/odxtools/parameters/dynamicparameter.py index fc5a6fa3..103985a2 100644 --- a/odxtools/parameters/dynamicparameter.py +++ b/odxtools/parameters/dynamicparameter.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..odxtypes import ParameterValue @@ -25,5 +27,6 @@ def is_settable(self) -> bool: def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: raise NotImplementedError("Encoding a DynamicParameter is not implemented yet.") - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: raise NotImplementedError("Decoding a DynamicParameter is not implemented yet.") diff --git a/odxtools/parameters/lengthkeyparameter.py b/odxtools/parameters/lengthkeyparameter.py index d38ce49d..4bf9226e 100644 --- a/odxtools/parameters/lengthkeyparameter.py +++ b/odxtools/parameters/lengthkeyparameter.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import odxraise, odxrequire @@ -66,8 +68,9 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - phys_val = super().decode_from_pdu(decode_state) + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + phys_val = super()._decode_positioned_from_pdu(decode_state) if not isinstance(phys_val, int): odxraise(f"The pysical type of length keys must be an integer, " diff --git a/odxtools/parameters/matchingrequestparameter.py b/odxtools/parameters/matchingrequestparameter.py index fb6b6c46..b2ef4da5 100644 --- a/odxtools/parameters/matchingrequestparameter.py +++ b/odxtools/parameters/matchingrequestparameter.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import Optional +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import EncodeError @@ -37,16 +39,11 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: .request_byte_position:self.request_byte_position + self.byte_length] - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: result = decode_state.extract_atomic_value( bit_length=self.byte_length * 8, base_data_type=DataType.A_UINT32, is_highlow_byte_order=False) - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) - return result diff --git a/odxtools/parameters/nrcconstparameter.py b/odxtools/parameters/nrcconstparameter.py index 060bb5e7..e9e23ed9 100644 --- a/odxtools/parameters/nrcconstparameter.py +++ b/odxtools/parameters/nrcconstparameter.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing_extensions import override + from ..decodestate import DecodeState from ..diagcodedtype import DiagCodedType from ..encodestate import EncodeState @@ -77,14 +79,9 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return self.diag_coded_type.convert_internal_to_bytes( coded_value, encode_state, bit_position=bit_position_int) - def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - # Update cursor position - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: # Extract coded values - decode_state.cursor_bit_position = self.bit_position or 0 coded_value = self.diag_coded_type.decode_from_pdu(decode_state) # Check if the coded value in the message is correct. @@ -99,8 +96,6 @@ def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: stacklevel=1, ) - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) - return coded_value def get_description_of_valid_values(self) -> str: diff --git a/odxtools/parameters/parameter.py b/odxtools/parameters/parameter.py index 932c08f3..0264a33b 100644 --- a/odxtools/parameters/parameter.py +++ b/odxtools/parameters/parameter.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional +from typing_extensions import final + from ..decodestate import DecodeState from ..element import NamedElement from ..encodestate import EncodeState @@ -31,6 +33,14 @@ @dataclass class Parameter(NamedElement, abc.ABC): + """This class corresponds to POSITIONABLE-PARAM in the ODX + specification. + + Be aware that, even though the ODX specification seems to make the + distinction of "positionable" and "normal" parameters, it does not + define any non-positionable parameter types. + + """ byte_position: Optional[int] bit_position: Optional[int] semantic: Optional[str] @@ -90,35 +100,27 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: """ pass - @abc.abstractmethod + @final def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - """Decode the parameter value from the coded message. + orig_cursor = decode_state.cursor_byte_position + if self.byte_position is not None: + decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - If the parameter does have a byte position property, the coded bytes the parameter covers are extracted - at this byte position and the function parameter `default_byte_position` is ignored. + decode_state.cursor_bit_position = self.bit_position or 0 - If the parameter does not have a byte position and a byte position is passed, - the bytes are extracted at the byte position given by the argument `default_byte_position`. + result = self._decode_positioned_from_pdu(decode_state) - If the parameter does not have a byte position and the argument `default_byte_position` is None, - this function throws a `DecodeError`. + decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) + decode_state.cursor_bit_position = 0 - Parameters - ---------- - decode_state : DecodeState - The decoding state containing - * the byte message to be decoded - * the parameter values that are already decoded - * the next byte position that is used iff the parameter does not specify a byte position + return result - Returns - ------- - ParameterValuePair | List[ParameterValuePair] - the decoded parameter value (the type is defined by the DOP) - int - the next byte position after the extracted parameter - """ - pass + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + """Method which actually decodes the parameter + + Its location is managed by `Parameter`.""" + raise NotImplementedError( + "Required method '_decode_positioned_from_pdu()' not implemented by child class") def encode_into_pdu(self, encode_state: EncodeState) -> bytes: """Encode the value of a parameter into a binary blob and return it diff --git a/odxtools/parameters/parameterwithdop.py b/odxtools/parameters/parameterwithdop.py index c15f17cc..8474c62d 100644 --- a/odxtools/parameters/parameterwithdop.py +++ b/odxtools/parameters/parameterwithdop.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, Optional +from typing_extensions import override + from ..dataobjectproperty import DataObjectProperty from ..decodestate import DecodeState from ..dopbase import DopBase @@ -75,16 +77,6 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return dop.convert_physical_to_bytes( physical_value, encode_state, bit_position=bit_position_int) - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - - decode_state.cursor_bit_position = self.bit_position or 0 - - # Use DOP to decode - phys_val = self.dop.decode_from_pdu(decode_state) - - decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position) - - return phys_val + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + return self.dop.decode_from_pdu(decode_state) diff --git a/odxtools/parameters/physicalconstantparameter.py b/odxtools/parameters/physicalconstantparameter.py index 6a5387b3..b7ad29fc 100644 --- a/odxtools/parameters/physicalconstantparameter.py +++ b/odxtools/parameters/physicalconstantparameter.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict +from typing_extensions import override + from ..dataobjectproperty import DataObjectProperty from ..decodestate import DecodeState from ..encodestate import EncodeState @@ -63,9 +65,10 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return dop.convert_physical_to_bytes( self.physical_constant_value, encode_state, bit_position=bit_position_int) - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: # Decode value - phys_val = super().decode_from_pdu(decode_state) + phys_val = super()._decode_positioned_from_pdu(decode_state) # Check if decoded value matches expected value if phys_val != self.physical_constant_value: diff --git a/odxtools/parameters/reservedparameter.py b/odxtools/parameters/reservedparameter.py index 70b6338c..d917217a 100644 --- a/odxtools/parameters/reservedparameter.py +++ b/odxtools/parameters/reservedparameter.py @@ -1,10 +1,12 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import Optional, cast +from typing import Optional + +from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState -from ..odxtypes import ParameterValue +from ..odxtypes import DataType, ParameterValue from .parameter import Parameter, ParameterType @@ -28,19 +30,13 @@ def get_static_bit_length(self) -> Optional[int]: return self.bit_length def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: - bit_position_int = self.bit_position if self.bit_position is not None else 0 - return (0).to_bytes((self.bit_length + bit_position_int + 7) // 8, "big") - - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - # move the cursor - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position + return (0).to_bytes(((self.bit_position or 0) + self.bit_length + 7) // 8, "big") + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: decode_state.cursor_byte_position += ((self.bit_position or 0) + self.bit_length + 7) // 8 - decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position) - decode_state.cursor_bit_position = 0 - - # ignore the value of the parameter data - return cast(int, None) + return decode_state.extract_atomic_value( + bit_length=self.bit_length, + base_data_type=DataType.A_UINT32, + is_highlow_byte_order=False) diff --git a/odxtools/parameters/systemparameter.py b/odxtools/parameters/systemparameter.py index 7c9b8197..e8444dd1 100644 --- a/odxtools/parameters/systemparameter.py +++ b/odxtools/parameters/systemparameter.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..odxtypes import ParameterValue @@ -27,5 +29,6 @@ def is_settable(self) -> bool: def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: raise NotImplementedError("Encoding a SystemParameter is not implemented yet.") - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - raise NotImplementedError("Decoding a SystemParameter is not implemented yet.") + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + raise NotImplementedError("Decoding SystemParameter is not implemented yet.") diff --git a/odxtools/parameters/tableentryparameter.py b/odxtools/parameters/tableentryparameter.py index 2cdea1f4..e3447a56 100644 --- a/odxtools/parameters/tableentryparameter.py +++ b/odxtools/parameters/tableentryparameter.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..odxlink import OdxLinkRef @@ -19,14 +21,15 @@ def parameter_type(self) -> ParameterType: @property def is_required(self) -> bool: - raise NotImplementedError("TableKeyParameter.is_required is not implemented yet.") + raise NotImplementedError("TableEntryParameter.is_required is not implemented yet.") @property def is_settable(self) -> bool: - raise NotImplementedError("TableKeyParameter.is_settable is not implemented yet.") + raise NotImplementedError("TableEntryParameter.is_settable is not implemented yet.") def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: - raise NotImplementedError("Encoding a TableKeyParameter is not implemented yet.") + raise NotImplementedError("Encoding a TableEntryParameter is not implemented yet.") - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - raise NotImplementedError("Decoding a TableKeyParameter is not implemented yet.") + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: + raise NotImplementedError("Decoding a TableEntryParameter is not implemented yet.") diff --git a/odxtools/parameters/tablekeyparameter.py b/odxtools/parameters/tablekeyparameter.py index 2e82984c..85aa442c 100644 --- a/odxtools/parameters/tablekeyparameter.py +++ b/odxtools/parameters/tablekeyparameter.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, Optional +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire @@ -135,11 +137,8 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: if self.table_row is not None: # the table row to be used is statically specified -> no # need to decode anything! @@ -147,7 +146,6 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: else: # Use DOP to decode key_dop = odxrequire(self.table.key_dop) - decode_state.cursor_bit_position = self.bit_position or 0 key_dop_val = key_dop.decode_from_pdu(decode_state) table_row_candidates = [x for x in self.table.table_rows if x.key == key_dop_val] @@ -162,6 +160,4 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: # update the decode_state's table key decode_state.table_keys[self.short_name] = table_row - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) - return phys_val diff --git a/odxtools/parameters/tablestructparameter.py b/odxtools/parameters/tablestructparameter.py index d3a1e58a..4a5f8481 100644 --- a/odxtools/parameters/tablestructparameter.py +++ b/odxtools/parameters/tablestructparameter.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, Optional, cast +from typing_extensions import override + from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, EncodeError, OdxWarning, odxraise @@ -125,11 +127,8 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) - def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: - orig_cursor = decode_state.cursor_byte_position - if self.byte_position is not None: - decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position - + @override + def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: # find the selected table row key_name = self.table_key.short_name @@ -146,14 +145,11 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: if table_row.dop is not None: dop = table_row.dop val = dop.decode_from_pdu(decode_state) - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) return (table_row.short_name, val) elif table_row.structure is not None: val = table_row.structure.decode_from_pdu(decode_state) - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) return (table_row.short_name, val) else: # the table row associated with the key neither defines a # DOP nor a structure -> ignore it - decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor) return (table_row.short_name, cast(int, None)) diff --git a/pyproject.toml b/pyproject.toml index b2a7e1c3..1fafeda5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,9 @@ dependencies = [ "markdownify >= 0.11", "deprecation >= 2.1", "packaging", - "tabulate >= 0.9.0", - "rich >= 13.7.0", + "tabulate >= 0.9", + "rich >= 13.7", + "typing_extensions >= 4.9", ] dynamic = ["version"]