Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 93 additions & 43 deletions odxtools/environmentdatadescription.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@
from typing_extensions import override

from .complexdop import ComplexDop
from .dataobjectproperty import DataObjectProperty
from .decodestate import DecodeState
from .dtcdop import DtcDop
from .encodestate import EncodeState
from .environmentdata import EnvironmentData
from .exceptions import odxraise, odxrequire
from .nameditemlist import NamedItemList
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
from .odxtypes import ParameterValue, ParameterValueDict
from .odxtypes import DataType, ParameterValue, ParameterValueDict
from .parameters.parameter import Parameter
from .parameters.valueparameter import ValueParameter
from .snrefcontext import SnRefContext
from .utils import dataclass_fields_asdict

Expand Down Expand Up @@ -116,40 +118,64 @@ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
"""Convert a physical value into bytes and emplace them into a PDU.
"""

# retrieve the relevant DTC parameter which must be located in
# front of the environment data description.
# retrieve the DTC as a numerical value from the referenced
# parameter (which must be located somewhere before the
# parameter using the environment data description)
if self.param_snref is None:
odxraise("Specifying the DTC parameter for environment data "
"descriptions via SNPATHREF is not supported yet")
return None

dtc_param: Optional[Parameter] = None
dtc_dop: Optional[DtcDop] = None
dtc_param_value: Optional[ParameterValue] = None
numerical_dtc_value: Optional[ParameterValue] = None
for prev_param, prev_param_value in reversed(encode_state.journal):
if prev_param.short_name == self.param_snref:
dtc_param = prev_param
prev_dop = getattr(prev_param, "dop", None)
if not isinstance(prev_dop, DtcDop):
odxraise(f"The DOP of the parameter referenced by environment data "
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
if not isinstance(prev_param, ValueParameter):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be a constant parameter? no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so: environment data descriptions are a multiplexer mechanism to attach additional information to a DTC depending on the value of the trouble code (basically this data describes the circumstances under which the DTC was encountered). mandating the key of the multiplexer to be a constant defies the purpose? (haven't checked the spec about this, though..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the dtc have only one environment data, then it could be in theory a constant.

the main idea, is that the check you are doing either needs to backed by a specification clause, or to prevent an error for something not supported by the rest of the code.

the check here does not seem to fit any of those conditions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the dtc have only one environment data, then it could be in theory a constant.

yes, but then you don't need the multiplexer because you can simply put the parameters of the environment data into the parameters for the response. Anyway, since the spec probably does not explicitly forbid such atrocities, I fixed it: 672ff47 (plus, I found a better solution for the copy-and-paste issue...)

odxraise(
f"The parameter referenced by environment data descriptions "
f"must use a VALUE-PARAMETER (encountered {type(prev_param).__name__} "
f"for reference '{self.param_snref}' of ENV-DATA-DESC '{self.short_name}')")
return
dtc_dop = prev_dop
dtc_param_value = prev_param_value

prev_dop = prev_param.dop
if isinstance(prev_dop, DtcDop):
if prev_dop.diag_coded_type.base_data_type != DataType.A_UINT32:
odxraise(f"The data type used by the DOP of the parameter referenced "
f"by environment data descriptions must be A_UINT32 "
f"(encountered '{prev_dop.diag_coded_type.base_data_type.value}')")
return

if prev_param_value is None:
odxraise()
return

numerical_dtc_value = prev_dop.convert_to_numerical_trouble_code(
prev_param_value)
elif isinstance(prev_dop, DataObjectProperty):
if prev_dop.diag_coded_type.base_data_type != DataType.A_UINT32:
odxraise(f"The data type used by the DOP of the parameter referenced "
f"by environment data descriptions must be A_UINT32 "
f"(encountered '{prev_dop.diag_coded_type.base_data_type.value}')")
return

if not isinstance(prev_param_value, (int, str)):
odxraise()
return

numerical_dtc_value = prev_dop.compu_method.convert_physical_to_internal(
prev_param_value)
else:
odxraise(f"The DOP of the parameter referenced "
f"by environment data descriptions must use a simple DOP or a DTC-DOP "
f"(encountered '{type(prev_dop).__name__}')")
return

break

if dtc_param is None:
if numerical_dtc_value is None:
odxraise("Environment data description parameters are only allowed following "
"the referenced value parameter.")
return

if dtc_param_value is None or dtc_dop is None:
# this should never happen
odxraise()
return

numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)

# deal with the "all value" environment data. This holds
# parameters that are common to all DTCs. Be aware that the
# specification mandates that there is at most one such
Expand All @@ -165,7 +191,7 @@ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
# find the environment data corresponding to the given trouble
# code
for env_data in self.env_datas:
if numerical_dtc in env_data.dtc_values:
if numerical_dtc_value in env_data.dtc_values:
tmp = encode_state.allow_unknown_parameters
encode_state.allow_unknown_parameters = True
env_data.encode_into_pdu(physical_value, encode_state)
Expand All @@ -177,40 +203,64 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
"""Extract the bytes from a PDU and convert them to a physical value.
"""

# retrieve the relevant DTC parameter which must be located in
# front of the environment data description.
# retrieve the DTC as a numerical value from the referenced
# parameter (which must be located somewhere before the
# parameter using the environment data description)
if self.param_snref is None:
odxraise("Specifying the DTC parameter for environment data "
"descriptions via SNPATHREF is not supported yet")
return None

dtc_param: Optional[Parameter] = None
dtc_dop: Optional[DtcDop] = None
dtc_param_value: Optional[ParameterValue] = None
numerical_dtc_value: Optional[ParameterValue] = None
for prev_param, prev_param_value in reversed(decode_state.journal):
if prev_param.short_name == self.param_snref:
dtc_param = prev_param
prev_dop = getattr(prev_param, "dop", None)
if not isinstance(prev_dop, DtcDop):
odxraise(f"The DOP of the parameter referenced by environment data "
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
if not isinstance(prev_param, ValueParameter):
odxraise(
f"The parameter referenced by environment data descriptions "
f"must use a VALUE-PARAMETER (encountered {type(prev_param).__name__} "
f"for reference '{self.param_snref}' of ENV-DATA-DESC '{self.short_name}')")
return
dtc_dop = prev_dop
dtc_param_value = prev_param_value

prev_dop = prev_param.dop
if isinstance(prev_dop, DtcDop):
if prev_dop.diag_coded_type.base_data_type != DataType.A_UINT32:
odxraise(f"The data type used by the DOP of the parameter referenced "
f"by environment data descriptions must be A_UINT32 "
f"(encountered '{prev_dop.diag_coded_type.base_data_type.value}')")
return

if prev_param_value is None:
odxraise()
return

numerical_dtc_value = prev_dop.convert_to_numerical_trouble_code(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this block feels copy-pasted

Copy link
Member Author

@andlaus andlaus Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this too, and tried to move it one indentation level up. this turned out to be surprisingly difficult because not all DOPs exhibit .diag_coded_type which would have necessitated an to make it conditional on isinstance(prev_dop, (DtcDop, DataObjectProperty)). given that isinstance() is relatively expensive and the resulting code was IMO not being much nicer, I decided to live with the copy-and-paste...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce a function, that takes prev_dop and prev_param_value as input and return numerical_dtc_value

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. Done: a9a791f

prev_param_value)
elif isinstance(prev_dop, DataObjectProperty):
if prev_dop.diag_coded_type.base_data_type != DataType.A_UINT32:
odxraise(f"The data type used by the DOP of the parameter referenced "
f"by environment data descriptions must be A_UINT32 "
f"(encountered '{prev_dop.diag_coded_type.base_data_type.value}')")
return

if not isinstance(prev_param_value, (int, str)):
odxraise()
return

numerical_dtc_value = prev_dop.compu_method.convert_physical_to_internal(
prev_param_value)
else:
odxraise(f"The DOP of the parameter referenced "
f"by environment data descriptions must use a simple DOP or a DTC-DOP "
f"(encountered '{type(prev_dop).__name__}')")
return

break

if dtc_param is None:
if numerical_dtc_value is None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check could be moved inside _get_numerical_dtc_from_parameter, since it's done for each call

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the check here is only triggered if no parameter with the specified short name has been found (_get_numerical_dtc_from_parameter() always returns an integer)

odxraise("Environment data description parameters are only allowed following "
"the referenced value parameter.")
return

if dtc_param_value is None or dtc_dop is None:
# this should never happen
odxraise()
return

numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)

result: ParameterValueDict = {}

# deal with the "all value" environment data. This holds
Expand All @@ -228,7 +278,7 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
# find the environment data corresponding to the given trouble
# code
for env_data in self.env_datas:
if numerical_dtc in env_data.dtc_values:
if numerical_dtc_value in env_data.dtc_values:
tmp = env_data.decode_from_pdu(decode_state)
if not isinstance(tmp, dict):
odxraise()
Expand Down