From e6fd478a8f35672dff580a08df9464b288d652af Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Thu, 7 Mar 2024 09:29:34 +0100 Subject: [PATCH 1/3] make `Parameter` instantiable abstract base classes cannot be instantiated, yet we need to be able to do this in order to implement the `.from_et()` pattern with inheritance. Signed-off-by: Andreas Lauser Signed-off-by: Florian Jost --- odxtools/parameters/parameter.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/odxtools/parameters/parameter.py b/odxtools/parameters/parameter.py index 0264a33b..bdfb487f 100644 --- a/odxtools/parameters/parameter.py +++ b/odxtools/parameters/parameter.py @@ -1,5 +1,4 @@ # SPDX-License-Identifier: MIT -import abc from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional @@ -32,7 +31,7 @@ @dataclass -class Parameter(NamedElement, abc.ABC): +class Parameter(NamedElement): """This class corresponds to POSITIONABLE-PARAM in the ODX specification. @@ -63,9 +62,8 @@ 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 +78,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 +89,13 @@ 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: From a72340d9238537ee819ead33886b32a44c255568 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Thu, 7 Mar 2024 09:30:04 +0100 Subject: [PATCH 2/3] switch creation of all parameters to the `.from_et()` pattern Signed-off-by: Andreas Lauser Signed-off-by: Florian Jost --- examples/somersault.pdx | Bin 25021 -> 25029 bytes odxtools/parameters/codedconstparameter.py | 32 ++- odxtools/parameters/createanyparameter.py | 214 ++---------------- odxtools/parameters/dynamicparameter.py | 18 ++ odxtools/parameters/lengthkeyparameter.py | 25 +- .../parameters/matchingrequestparameter.py | 25 +- odxtools/parameters/nrcconstparameter.py | 32 ++- odxtools/parameters/parameter.py | 35 ++- odxtools/parameters/parameterwithdop.py | 25 +- .../parameters/physicalconstantparameter.py | 25 +- odxtools/parameters/reservedparameter.py | 22 +- odxtools/parameters/systemparameter.py | 20 ++ odxtools/parameters/tableentryparameter.py | 38 +++- odxtools/parameters/tablekeyparameter.py | 41 +++- odxtools/parameters/tablestructparameter.py | 31 ++- odxtools/parameters/valueparameter.py | 26 ++- .../templates/macros/printParam.xml.jinja2 | 18 +- 17 files changed, 397 insertions(+), 230 deletions(-) diff --git a/examples/somersault.pdx b/examples/somersault.pdx index 874e58c8a09d1c26709221a13eaecaaa7e9658fc..d6b0a1ecf028fe2822c925e9b6652162b185d48f 100644 GIT binary patch literal 25029 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 FzX0+WcUu4e literal 25021 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 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..e0840b27 100644 --- a/odxtools/parameters/dynamicparameter.py +++ b/odxtools/parameters/dynamicparameter.py @@ -1,32 +1,50 @@ # 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.") + @override @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 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 bdfb487f..5aa200a4 100644 --- a/odxtools/parameters/parameter.py +++ b/odxtools/parameters/parameter.py @@ -1,15 +1,18 @@ # SPDX-License-Identifier: MIT 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 @@ -45,6 +48,28 @@ class Parameter(NamedElement): 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,7 +88,8 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: @property def parameter_type(self) -> ParameterType: - raise NotImplementedError(".parameter_type is not implemented by the concrete parameter class") + raise NotImplementedError( + ".parameter_type is not implemented by the concrete parameter class") def get_static_bit_length(self) -> Optional[int]: return None @@ -95,7 +121,8 @@ 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`. """ - raise NotImplementedError(".get_coded_value_as_bytes() is not implemented by the concrete parameter class") + 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) }} From 99de68ad20f725805a9c8ba895e7f0de96829e58 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Fri, 8 Mar 2024 08:41:08 +0100 Subject: [PATCH 3/3] remove duplicate `@override` decorator Signed-off-by: Andreas Lauser Signed-off-by: Florian Jost --- odxtools/parameters/dynamicparameter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/odxtools/parameters/dynamicparameter.py b/odxtools/parameters/dynamicparameter.py index e0840b27..1323bc95 100644 --- a/odxtools/parameters/dynamicparameter.py +++ b/odxtools/parameters/dynamicparameter.py @@ -44,7 +44,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.") - @override @override def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue: raise NotImplementedError("Decoding a DynamicParameter is not implemented yet.")