diff --git a/examples/somersault.pdx b/examples/somersault.pdx index 874e58c8..d6b0a1ec 100644 Binary files a/examples/somersault.pdx and b/examples/somersault.pdx differ diff --git a/odxtools/parameters/codedconstparameter.py b/odxtools/parameters/codedconstparameter.py index 2f7f7234..d0e4d878 100644 --- a/odxtools/parameters/codedconstparameter.py +++ b/odxtools/parameters/codedconstparameter.py @@ -1,16 +1,19 @@ # SPDX-License-Identifier: MIT import warnings from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional +from xml.etree import ElementTree from typing_extensions import override +from ..createanydiagcodedtype import create_any_diag_coded_type_from_et from ..decodestate import DecodeState from ..diagcodedtype import DiagCodedType from ..encodestate import EncodeState -from ..exceptions import DecodeError -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..exceptions import DecodeError, odxrequire +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import AtomicOdxType, DataType +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType if TYPE_CHECKING: @@ -23,10 +26,27 @@ class CodedConstParameter(Parameter): diag_coded_type: DiagCodedType coded_value: AtomicOdxType + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "CodedConstParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + dct_elem = odxrequire(et_element.find("DIAG-CODED-TYPE")) + diag_coded_type = create_any_diag_coded_type_from_et(dct_elem, doc_frags) + coded_value = diag_coded_type.base_data_type.from_string( + odxrequire(et_element.findtext("CODED-VALUE"))) + + return CodedConstParameter( + diag_coded_type=diag_coded_type, coded_value=coded_value, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "CODED-CONST" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = super()._build_odxlinks() @@ -34,12 +54,15 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return result + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) + @override def get_static_bit_length(self) -> Optional[int]: return self.diag_coded_type.get_static_bit_length() @@ -48,13 +71,16 @@ def internal_data_type(self) -> DataType: return self.diag_coded_type.base_data_type @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: return False + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: if (self.short_name in encode_state.parameter_values and encode_state.parameter_values[self.short_name] != self.coded_value): diff --git a/odxtools/parameters/createanyparameter.py b/odxtools/parameters/createanyparameter.py index 0688ca2d..6f2c4548 100644 --- a/odxtools/parameters/createanyparameter.py +++ b/odxtools/parameters/createanyparameter.py @@ -2,13 +2,9 @@ from typing import List from xml.etree import ElementTree -from ..createanydiagcodedtype import create_any_diag_coded_type_from_et -from ..createsdgs import create_sdgs_from_et -from ..element import NamedElement -from ..exceptions import odxrequire +from ..exceptions import odxraise from ..globals import xsi -from ..odxlink import OdxDocFragment, OdxLinkId, OdxLinkRef -from ..utils import dataclass_fields_asdict +from ..odxlink import OdxDocFragment from .codedconstparameter import CodedConstParameter from .dynamicparameter import DynamicParameter from .lengthkeyparameter import LengthKeyParameter @@ -27,205 +23,33 @@ def create_any_parameter_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) \ -> Parameter: - kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags)) - semantic = et_element.get("SEMANTIC") - byte_position_str = et_element.findtext("BYTE-POSITION") - byte_position = int(byte_position_str) if byte_position_str is not None else None - bit_position_str = et_element.findtext("BIT-POSITION") - bit_position = None - if bit_position_str is not None: - bit_position = int(bit_position_str) parameter_type = et_element.get(f"{xsi}type") - sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags) - # Which attributes are set depends on the type of the parameter. - dop_ref = None - dop_snref = None - if parameter_type in ["VALUE", "PHYS-CONST", "SYSTEM", "LENGTH-KEY"]: - dop_ref = OdxLinkRef.from_et(et_element.find("DOP-REF"), doc_frags) - dop_snref = None - if (dop_snref_elem := et_element.find("DOP-SNREF")) is not None: - dop_snref = odxrequire(dop_snref_elem.get("SHORT-NAME")) - - if dop_ref is None and dop_snref is None: - raise ValueError( - f"A parameter of type {parameter_type} must reference a DOP! {dop_ref}, {dop_snref}" - ) - if parameter_type == "VALUE": - physical_default_value_raw = ( - et_element.findtext("PHYSICAL-DEFAULT-VALUE") - if et_element.find("PHYSICAL-DEFAULT-VALUE") is not None else None) - - return ValueParameter( - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - dop_ref=dop_ref, - dop_snref=dop_snref, - physical_default_value_raw=physical_default_value_raw, - sdgs=sdgs, - **kwargs) - - elif parameter_type == "PHYS-CONST": - physical_constant_value = odxrequire(et_element.findtext("PHYS-CONSTANT-VALUE")) - - return PhysicalConstantParameter( - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - dop_ref=dop_ref, - dop_snref=dop_snref, - physical_constant_value_raw=physical_constant_value, - sdgs=sdgs, - **kwargs) - + return ValueParameter.from_et(et_element, doc_frags) elif parameter_type == "CODED-CONST": - dct_elem = odxrequire(et_element.find("DIAG-CODED-TYPE")) - diag_coded_type = create_any_diag_coded_type_from_et(dct_elem, doc_frags) - coded_value = diag_coded_type.base_data_type.from_string( - odxrequire(et_element.findtext("CODED-VALUE"))) - - return CodedConstParameter( - semantic=semantic, - diag_coded_type=diag_coded_type, - coded_value=coded_value, - byte_position=byte_position, - bit_position=bit_position, - sdgs=sdgs, - **kwargs) - + return CodedConstParameter.from_et(et_element, doc_frags) + elif parameter_type == "PHYS-CONST": + return PhysicalConstantParameter.from_et(et_element, doc_frags) + elif parameter_type == "SYSTEM": + return SystemParameter.from_et(et_element, doc_frags) + elif parameter_type == "LENGTH-KEY": + return LengthKeyParameter.from_et(et_element, doc_frags) elif parameter_type == "NRC-CONST": - diag_coded_type = create_any_diag_coded_type_from_et( - odxrequire(et_element.find("DIAG-CODED-TYPE")), doc_frags) - coded_values = [ - diag_coded_type.base_data_type.from_string(odxrequire(val.text)) - for val in et_element.iterfind("CODED-VALUES/CODED-VALUE") - ] - - return NrcConstParameter( - semantic=semantic, - diag_coded_type=diag_coded_type, - coded_values=coded_values, - byte_position=byte_position, - bit_position=bit_position, - sdgs=sdgs, - **kwargs) - + return NrcConstParameter.from_et(et_element, doc_frags) elif parameter_type == "RESERVED": - bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH"))) - - return ReservedParameter( - bit_length=bit_length, - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - sdgs=sdgs, - **kwargs) - + return ReservedParameter.from_et(et_element, doc_frags) elif parameter_type == "MATCHING-REQUEST-PARAM": - byte_length = int(odxrequire(et_element.findtext("BYTE-LENGTH"))) - request_byte_pos = int(odxrequire(et_element.findtext("REQUEST-BYTE-POS"))) - - return MatchingRequestParameter( - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - request_byte_position=request_byte_pos, - byte_length=byte_length, - sdgs=sdgs, - **kwargs) - - elif parameter_type == "SYSTEM": - sysparam = odxrequire(et_element.get("SYSPARAM")) - - return SystemParameter( - sysparam=sysparam, - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - dop_ref=dop_ref, - dop_snref=dop_snref, - sdgs=sdgs, - **kwargs) - - elif parameter_type == "LENGTH-KEY": - odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags)) - - return LengthKeyParameter( - odx_id=odx_id, - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - dop_ref=dop_ref, - dop_snref=dop_snref, - sdgs=sdgs, - **kwargs) - + return MatchingRequestParameter.from_et(et_element, doc_frags) elif parameter_type == "DYNAMIC": - - return DynamicParameter( - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - sdgs=sdgs, - **kwargs) - + return DynamicParameter.from_et(et_element, doc_frags) elif parameter_type == "TABLE-STRUCT": - key_ref = OdxLinkRef.from_et(et_element.find("TABLE-KEY-REF"), doc_frags) - if (key_snref_elem := et_element.find("TABLE-KEY-SNREF")) is not None: - key_snref = odxrequire(key_snref_elem.get("SHORT-NAME")) - else: - key_snref = None - - return TableStructParameter( - table_key_ref=key_ref, - table_key_snref=key_snref, - semantic=semantic, - byte_position=byte_position, - bit_position=bit_position, - sdgs=sdgs, - **kwargs) - + return TableStructParameter.from_et(et_element, doc_frags) elif parameter_type == "TABLE-KEY": - - parameter_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags)) - table_ref = OdxLinkRef.from_et(et_element.find("TABLE-REF"), doc_frags) - if (table_snref_elem := et_element.find("TABLE-SNREF")) is not None: - table_snref = odxrequire(table_snref_elem.get("SHORT-NAME")) - else: - table_snref = None - - table_row_ref = OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags) - if (table_row_snref_elem := et_element.find("TABLE-ROW-SNREF")) is not None: - table_row_snref = odxrequire(table_row_snref_elem.get("SHORT-NAME")) - else: - table_row_snref = None - - return TableKeyParameter( - table_ref=table_ref, - table_snref=table_snref, - table_row_snref=table_row_snref, - table_row_ref=table_row_ref, - odx_id=parameter_id, - byte_position=byte_position, - bit_position=bit_position, - semantic=semantic, - sdgs=sdgs, - **kwargs) - + return TableKeyParameter.from_et(et_element, doc_frags) elif parameter_type == "TABLE-ENTRY": - target = odxrequire(et_element.findtext("TARGET")) - table_row_ref = odxrequire(OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags)) - - return TableEntryParameter( - target=target, - table_row_ref=table_row_ref, - byte_position=byte_position, - bit_position=bit_position, - semantic=semantic, - sdgs=sdgs, - **kwargs) + return TableEntryParameter.from_et(et_element, doc_frags) - raise NotImplementedError(f"I don't know about parameters of type {parameter_type}") + odxraise(f"I don't know about parameters of type {parameter_type}", NotImplementedError) + return Parameter.from_et(et_element, doc_frags) diff --git a/odxtools/parameters/dynamicparameter.py b/odxtools/parameters/dynamicparameter.py index 103985a2..1323bc95 100644 --- a/odxtools/parameters/dynamicparameter.py +++ b/odxtools/parameters/dynamicparameter.py @@ -1,29 +1,46 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing import List +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState +from ..odxlink import OdxDocFragment from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType @dataclass class DynamicParameter(Parameter): + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "DynamicParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + return DynamicParameter(**kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "DYNAMIC" @property + @override def is_required(self) -> bool: raise NotImplementedError(".is_required for a DynamicParameter") @property + @override def is_settable(self) -> bool: raise NotImplementedError(".is_settable for a DynamicParameter") + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: raise NotImplementedError("Encoding a DynamicParameter is not implemented yet.") diff --git a/odxtools/parameters/lengthkeyparameter.py b/odxtools/parameters/lengthkeyparameter.py index 4bf9226e..3c458541 100644 --- a/odxtools/parameters/lengthkeyparameter.py +++ b/odxtools/parameters/lengthkeyparameter.py @@ -1,14 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import odxraise, odxrequire -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import ParameterType from .parameterwithdop import ParameterWithDOP @@ -29,10 +31,23 @@ class LengthKeyParameter(ParameterWithDOP): odx_id: OdxLinkId + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "LengthKeyParameter": + + kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags)) + + odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags)) + + return LengthKeyParameter(odx_id=odx_id, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "LENGTH-KEY" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = super()._build_odxlinks() @@ -40,23 +55,28 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return result + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: # length keys can be explicitly set, but they do not need to # be because they can be implicitly determined by the length # of the corresponding field return True + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: physical_value = encode_state.parameter_values.get(self.short_name, 0) @@ -65,6 +85,7 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: f"A DOP is required for length key parameter {self.short_name}") return dop.convert_physical_to_bytes(physical_value, encode_state, bit_position=bit_pos) + @override def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) diff --git a/odxtools/parameters/matchingrequestparameter.py b/odxtools/parameters/matchingrequestparameter.py index b2ef4da5..8711c237 100644 --- a/odxtools/parameters/matchingrequestparameter.py +++ b/odxtools/parameters/matchingrequestparameter.py @@ -1,13 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import Optional +from typing import List, Optional +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState -from ..exceptions import EncodeError +from ..exceptions import EncodeError, odxrequire +from ..odxlink import OdxDocFragment from ..odxtypes import DataType, ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType @@ -16,21 +19,39 @@ class MatchingRequestParameter(Parameter): request_byte_position: int byte_length: int + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "MatchingRequestParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + request_byte_position = int(odxrequire(et_element.findtext("REQUEST-BYTE-POS"))) + byte_length = int(odxrequire(et_element.findtext("BYTE-LENGTH"))) + + return MatchingRequestParameter( + request_byte_position=request_byte_position, byte_length=byte_length, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "MATCHING-REQUEST-PARAM" + @override def get_static_bit_length(self) -> Optional[int]: return 8 * self.byte_length @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: return False + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: if not encode_state.triggering_request: raise EncodeError(f"Parameter '{self.short_name}' is of matching request type," diff --git a/odxtools/parameters/nrcconstparameter.py b/odxtools/parameters/nrcconstparameter.py index e9e23ed9..8cc3ceef 100644 --- a/odxtools/parameters/nrcconstparameter.py +++ b/odxtools/parameters/nrcconstparameter.py @@ -2,15 +2,18 @@ import warnings from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional +from xml.etree import ElementTree from typing_extensions import override +from ..createanydiagcodedtype import create_any_diag_coded_type_from_et from ..decodestate import DecodeState from ..diagcodedtype import DiagCodedType from ..encodestate import EncodeState -from ..exceptions import DecodeError, EncodeError -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..exceptions import DecodeError, EncodeError, odxrequire +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import AtomicOdxType, DataType +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType if TYPE_CHECKING: @@ -31,10 +34,29 @@ class NrcConstParameter(Parameter): diag_coded_type: DiagCodedType coded_values: List[AtomicOdxType] + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "NrcConstParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + dct_elem = odxrequire(et_element.find("DIAG-CODED-TYPE")) + diag_coded_type = create_any_diag_coded_type_from_et(dct_elem, doc_frags) + coded_values = [ + diag_coded_type.base_data_type.from_string(odxrequire(val.text)) + for val in et_element.iterfind("CODED-VALUES/CODED-VALUE") + ] + + return NrcConstParameter( + diag_coded_type=diag_coded_type, coded_values=coded_values, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "NRC-CONST" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = super()._build_odxlinks() @@ -42,12 +64,15 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return result + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) + @override def get_static_bit_length(self) -> Optional[int]: return self.diag_coded_type.get_static_bit_length() @@ -56,13 +81,16 @@ def internal_data_type(self) -> DataType: return self.diag_coded_type.base_data_type @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: return False + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: if self.short_name in encode_state.parameter_values: if encode_state.parameter_values[self.short_name] not in self.coded_values: diff --git a/odxtools/parameters/parameter.py b/odxtools/parameters/parameter.py index 0264a33b..5aa200a4 100644 --- a/odxtools/parameters/parameter.py +++ b/odxtools/parameters/parameter.py @@ -1,16 +1,18 @@ # SPDX-License-Identifier: MIT -import abc from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional +from xml.etree import ElementTree -from typing_extensions import final +from typing_extensions import final, override +from ..createsdgs import create_sdgs_from_et from ..decodestate import DecodeState from ..element import NamedElement from ..encodestate import EncodeState -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import ParameterValue from ..specialdatagroup import SpecialDataGroup +from ..utils import dataclass_fields_asdict if TYPE_CHECKING: from ..diaglayer import DiagLayer @@ -32,7 +34,7 @@ @dataclass -class Parameter(NamedElement, abc.ABC): +class Parameter(NamedElement): """This class corresponds to POSITIONABLE-PARAM in the ODX specification. @@ -46,6 +48,28 @@ class Parameter(NamedElement, abc.ABC): semantic: Optional[str] sdgs: List[SpecialDataGroup] + @staticmethod + @override + def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Parameter": + + kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags)) + + semantic = et_element.get("SEMANTIC") + sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags) + + byte_position_str = et_element.findtext("BYTE-POSITION") + bit_position_str = et_element.findtext("BIT-POSITION") + + byte_position = int(byte_position_str) if byte_position_str is not None else None + bit_position = int(bit_position_str) if bit_position_str is not None else None + + return Parameter( + byte_position=byte_position, + bit_position=bit_position, + semantic=semantic, + sdgs=sdgs, + **kwargs) + def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = {} @@ -63,9 +87,9 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: sdg._resolve_snrefs(diag_layer) @property - @abc.abstractmethod def parameter_type(self) -> ParameterType: - pass + raise NotImplementedError( + ".parameter_type is not implemented by the concrete parameter class") def get_static_bit_length(self) -> Optional[int]: return None @@ -80,7 +104,7 @@ def is_required(self) -> bool: specified. """ - raise NotImplementedError + raise NotImplementedError(".is_required is not implemented by the concrete parameter class") @property def is_settable(self) -> bool: @@ -91,14 +115,14 @@ def is_settable(self) -> bool: have a default value are settable but not required to be specified. """ - raise NotImplementedError + raise NotImplementedError(".is_settable is not implemented by the concrete parameter class") - @abc.abstractmethod def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: """Get the coded value of the parameter given the encode state. Note that this method is called by `encode_into_pdu`. """ - pass + raise NotImplementedError( + ".get_coded_value_as_bytes() is not implemented by the concrete parameter class") @final def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: diff --git a/odxtools/parameters/parameterwithdop.py b/odxtools/parameters/parameterwithdop.py index 8474c62d..a222fc84 100644 --- a/odxtools/parameters/parameterwithdop.py +++ b/odxtools/parameters/parameterwithdop.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional +from xml.etree import ElementTree from typing_extensions import override @@ -10,9 +11,10 @@ from ..dtcdop import DtcDop from ..encodestate import EncodeState from ..exceptions import odxassert, odxrequire -from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import ParameterValue from ..physicaltype import PhysicalType +from ..utils import dataclass_fields_asdict from .parameter import Parameter if TYPE_CHECKING: @@ -24,14 +26,30 @@ class ParameterWithDOP(Parameter): dop_ref: Optional[OdxLinkRef] dop_snref: Optional[str] + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "ParameterWithDOP": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + dop_ref = OdxLinkRef.from_et(et_element.find("DOP-REF"), doc_frags) + dop_snref = None + if (dop_snref_elem := et_element.find("DOP-SNREF")) is not None: + dop_snref = odxrequire(dop_snref_elem.get("SHORT-NAME")) + + return ParameterWithDOP(dop_ref=dop_ref, dop_snref=dop_snref, **kwargs) + def __post_init__(self) -> None: odxassert(self.dop_snref is not None or self.dop_ref is not None, f"Param {self.short_name} without a DOP-(SN)REF should not exist!") self._dop: Optional[DopBase] = None + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return super()._build_odxlinks() + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @@ -42,6 +60,7 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: # (e.g., static and dynamic fields) self._dop = odxlinks.resolve_lenient(self.dop_ref) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @@ -57,6 +76,7 @@ def dop(self) -> DopBase: self._dop, "Specifying a data object property is mandatory but it " "could not be resolved") + @override def get_static_bit_length(self) -> Optional[int]: if self._dop is not None: return self._dop.get_static_bit_length() @@ -70,6 +90,7 @@ def physical_type(self) -> Optional[PhysicalType]: else: return None + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: dop = odxrequire(self.dop, "Reference to DOP is not resolved") physical_value = encode_state.parameter_values[self.short_name] diff --git a/odxtools/parameters/physicalconstantparameter.py b/odxtools/parameters/physicalconstantparameter.py index b7ad29fc..c15191a5 100644 --- a/odxtools/parameters/physicalconstantparameter.py +++ b/odxtools/parameters/physicalconstantparameter.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from xml.etree import ElementTree from typing_extensions import override @@ -8,8 +9,9 @@ from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, odxraise, odxrequire -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import ParameterType from .parameterwithdop import ParameterWithDOP @@ -22,16 +24,32 @@ class PhysicalConstantParameter(ParameterWithDOP): physical_constant_value_raw: str + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "PhysicalConstantParameter": + + kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags)) + + physical_constant_value_raw = odxrequire(et_element.findtext("PHYS-CONSTANT-VALUE")) + + return PhysicalConstantParameter( + physical_constant_value_raw=physical_constant_value_raw, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "PHYS-CONST" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return super()._build_odxlinks() + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @@ -46,13 +64,16 @@ def physical_constant_value(self) -> ParameterValue: return self._physical_constant_value @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: return False + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: dop = odxrequire(self.dop, "Reference to DOP is not resolved") if (self.short_name in encode_state.parameter_values and diff --git a/odxtools/parameters/reservedparameter.py b/odxtools/parameters/reservedparameter.py index d917217a..56af745b 100644 --- a/odxtools/parameters/reservedparameter.py +++ b/odxtools/parameters/reservedparameter.py @@ -1,12 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import Optional +from typing import List, Optional +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState +from ..exceptions import odxrequire +from ..odxlink import OdxDocFragment from ..odxtypes import DataType, ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType @@ -14,21 +18,37 @@ class ReservedParameter(Parameter): bit_length: int + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "ReservedParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH"))) + + return ReservedParameter(bit_length=bit_length, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "RESERVED" @property + @override def is_required(self) -> bool: return False @property + @override def is_settable(self) -> bool: return False + @override def get_static_bit_length(self) -> Optional[int]: return self.bit_length + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return (0).to_bytes(((self.bit_position or 0) + self.bit_length + 7) // 8, "big") diff --git a/odxtools/parameters/systemparameter.py b/odxtools/parameters/systemparameter.py index e8444dd1..34f0615f 100644 --- a/odxtools/parameters/systemparameter.py +++ b/odxtools/parameters/systemparameter.py @@ -1,11 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing import List +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState +from ..exceptions import odxrequire +from ..odxlink import OdxDocFragment from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import ParameterType from .parameterwithdop import ParameterWithDOP @@ -14,18 +19,33 @@ class SystemParameter(ParameterWithDOP): sysparam: str + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "SystemParameter": + + kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags)) + + sysparam = odxrequire(et_element.findtext("SYSPARAM")) + + return SystemParameter(sysparam=sysparam, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "SYSTEM" @property + @override def is_required(self) -> bool: raise NotImplementedError("SystemParameter.is_required is not implemented yet.") @property + @override def is_settable(self) -> bool: raise NotImplementedError("SystemParameter.is_settable is not implemented yet.") + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: raise NotImplementedError("Encoding a SystemParameter is not implemented yet.") diff --git a/odxtools/parameters/tableentryparameter.py b/odxtools/parameters/tableentryparameter.py index e3447a56..358118ef 100644 --- a/odxtools/parameters/tableentryparameter.py +++ b/odxtools/parameters/tableentryparameter.py @@ -1,35 +1,71 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass +from typing import TYPE_CHECKING, List +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState -from ..odxlink import OdxLinkRef +from ..exceptions import odxrequire +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType +if TYPE_CHECKING: + from ..tablerow import TableRow + @dataclass class TableEntryParameter(Parameter): target: str table_row_ref: OdxLinkRef + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "TableEntryParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + target = odxrequire(et_element.findtext("TARGET")) + table_row_ref = odxrequire(OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags)) + + return TableEntryParameter(target=target, table_row_ref=table_row_ref, **kwargs) + + @override + def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: + super()._resolve_odxlinks(odxlinks) + + if TYPE_CHECKING: + self._table_row = odxlinks.resolve(self.table_row_ref, TableRow) + else: + self._table_row = odxlinks.resolve(self.table_row_ref) + @property + @override def parameter_type(self) -> ParameterType: return "TABLE-ENTRY" @property + @override def is_required(self) -> bool: raise NotImplementedError("TableEntryParameter.is_required is not implemented yet.") @property + @override def is_settable(self) -> bool: raise NotImplementedError("TableEntryParameter.is_settable is not implemented yet.") + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: raise NotImplementedError("Encoding a TableEntryParameter is not implemented yet.") + @property + def table_row(self) -> "TableRow": + return self._table_row + @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 85aa442c..e0f2e148 100644 --- a/odxtools/parameters/tablekeyparameter.py +++ b/odxtools/parameters/tablekeyparameter.py @@ -1,14 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire -from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType if TYPE_CHECKING: @@ -26,6 +28,33 @@ class TableKeyParameter(Parameter): table_row_snref: Optional[str] table_row_ref: Optional[OdxLinkRef] + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "TableKeyParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags)) + + table_ref = OdxLinkRef.from_et(et_element.find("TABLE-REF"), doc_frags) + table_snref = None + if (table_snref_elem := et_element.find("TABLE-SNREF")) is not None: + table_snref = odxrequire(table_snref_elem.get("SHORT-NAME")) + + table_row_ref = OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags) + table_row_snref = None + if (table_row_snref_elem := et_element.find("TABLE-ROW-SNREF")) is not None: + table_row_snref = odxrequire(table_row_snref_elem.get("SHORT-NAME")) + + return TableKeyParameter( + odx_id=odx_id, + table_ref=table_ref, + table_snref=table_snref, + table_row_ref=table_row_ref, + table_row_snref=table_row_snref, + **kwargs) + def __post_init__(self) -> None: self._table: "Table" self._table_row: Optional["TableRow"] = None @@ -34,9 +63,11 @@ def __post_init__(self) -> None: odxraise("Either a table or a table row must be defined.") @property + @override def parameter_type(self) -> ParameterType: return "TABLE-KEY" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = super()._build_odxlinks() @@ -44,6 +75,7 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return result + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @@ -61,6 +93,7 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: self._table_row = odxlinks.resolve(self.table_row_ref) self._table = self._table_row.table + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @@ -88,15 +121,18 @@ def table_row(self) -> Optional["TableRow"]: return self._table_row @property + @override def is_required(self) -> bool: # TABLE-KEY parameters can be implicitly determined from the # corresponding TABLE-STRUCT return False @property + @override def is_settable(self) -> bool: return True + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: tr_short_name = encode_state.parameter_values.get(self.short_name) @@ -134,6 +170,7 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: bit_position = 0 if self.bit_position is None else self.bit_position return key_dop.convert_physical_to_bytes(tr.key, encode_state, bit_position=bit_position) + @override def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) diff --git a/odxtools/parameters/tablestructparameter.py b/odxtools/parameters/tablestructparameter.py index 4a5f8481..ecb0cdb8 100644 --- a/odxtools/parameters/tablestructparameter.py +++ b/odxtools/parameters/tablestructparameter.py @@ -1,15 +1,17 @@ # SPDX-License-Identifier: MIT import warnings from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast +from xml.etree import ElementTree from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState -from ..exceptions import DecodeError, EncodeError, OdxWarning, odxraise -from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..exceptions import DecodeError, EncodeError, OdxWarning, odxraise, odxrequire +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import ParameterValue +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType from .tablekeyparameter import TableKeyParameter @@ -23,23 +25,42 @@ class TableStructParameter(Parameter): table_key_ref: Optional[OdxLinkRef] table_key_snref: Optional[str] + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "TableStructParameter": + + kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags)) + + table_key_ref = OdxLinkRef.from_et(et_element.find("TABLE-KEY-REF"), doc_frags) + table_key_snref = None + if (table_key_snref_elem := et_element.find("TABLE-KEY-SNREF")) is not None: + table_key_snref = odxrequire(table_key_snref_elem.get("SHORT-NAME")) + + return TableStructParameter( + table_key_ref=table_key_ref, table_key_snref=table_key_snref, **kwargs) + def __post_init__(self) -> None: if self.table_key_ref is None and self.table_key_snref is None: odxraise("Either table_key_ref or table_key_snref must be defined.") @property + @override def parameter_type(self) -> ParameterType: return "TABLE-STRUCT" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return super()._build_odxlinks() + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) if self.table_key_ref is not None: self._table_key = odxlinks.resolve(self.table_key_ref, TableKeyParameter) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @@ -55,13 +76,16 @@ def table_key(self) -> TableKeyParameter: return self._table_key @property + @override def is_required(self) -> bool: return True @property + @override def is_settable(self) -> bool: return True + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: physical_value = encode_state.parameter_values.get(self.short_name) @@ -124,6 +148,7 @@ def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: return tr.dop.convert_physical_to_bytes( tr_value, encode_state=encode_state, bit_position=bit_position) + @override def encode_into_pdu(self, encode_state: EncodeState) -> bytes: return super().encode_into_pdu(encode_state) diff --git a/odxtools/parameters/valueparameter.py b/odxtools/parameters/valueparameter.py index ecfb7196..3f439e0f 100644 --- a/odxtools/parameters/valueparameter.py +++ b/odxtools/parameters/valueparameter.py @@ -1,12 +1,16 @@ # SPDX-License-Identifier: MIT from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional +from xml.etree import ElementTree + +from typing_extensions import override from ..dataobjectproperty import DataObjectProperty from ..encodestate import EncodeState from ..exceptions import odxraise, odxrequire -from ..odxlink import OdxLinkDatabase, OdxLinkId +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import AtomicOdxType +from ..utils import dataclass_fields_asdict from .parameter import ParameterType from .parameterwithdop import ParameterWithDOP @@ -21,16 +25,31 @@ class ValueParameter(ParameterWithDOP): def __post_init__(self) -> None: self._physical_default_value: Optional[AtomicOdxType] = None + @staticmethod + @override + def from_et(et_element: ElementTree.Element, + doc_frags: List[OdxDocFragment]) -> "ValueParameter": + + kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags)) + + physical_default_value_raw = et_element.findtext("PHYSICAL-DEFAULT-VALUE") + + return ValueParameter(physical_default_value_raw=physical_default_value_raw, **kwargs) + @property + @override def parameter_type(self) -> ParameterType: return "VALUE" + @override def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: return super()._build_odxlinks() + @override def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) + @override def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) @@ -48,13 +67,16 @@ def physical_default_value(self) -> Optional[AtomicOdxType]: return self._physical_default_value @property + @override def is_required(self) -> bool: return self._physical_default_value is None @property + @override def is_settable(self) -> bool: return True + @override def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes: physical_value = encode_state.parameter_values.get(self.short_name, self.physical_default_value) diff --git a/odxtools/templates/macros/printParam.xml.jinja2 b/odxtools/templates/macros/printParam.xml.jinja2 index f8bf0e83..ed68b4a6 100644 --- a/odxtools/templates/macros/printParam.xml.jinja2 +++ b/odxtools/templates/macros/printParam.xml.jinja2 @@ -8,17 +8,17 @@ {%- import('macros/printSpecialData.xml.jinja2') as psd %} {%- macro printParam(param) -%} -{%- if param.semantic is not none %} -{%- set semattrib = " SEMANTIC=\""+param.semantic+"\"" -%} -{%- else %} -{%- set semattrib = "" -%} -{%- endif -%} -{%- if param.parameter_type == "TABLE-KEY" and param.odx_id is not none %} - +{%- set semattrib = make_xml_attrib("SEMANTIC", param.semantic) -%} +{%- if param.parameter_type == "TABLE-KEY" %} + {%- elif param.parameter_type == "SYSTEM" %} - + {%- else %} - + {%- endif%} {{ peid.printElementIdSubtags(param)|indent(1) }} {{- psd.printSpecialDataGroups(param.sdgs)|indent(1, first=True) }}