odxtools 7.2.0__py3-none-any.whl → 7.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odxtools/basicstructure.py +10 -7
- odxtools/cli/_print_utils.py +3 -3
- odxtools/cli/browse.py +4 -2
- odxtools/cli/list.py +3 -3
- odxtools/commrelation.py +122 -0
- odxtools/comparaminstance.py +1 -1
- odxtools/comparamspec.py +1 -2
- odxtools/compumethods/linearsegment.py +0 -2
- odxtools/database.py +17 -11
- odxtools/decodestate.py +8 -2
- odxtools/diaglayer.py +23 -17
- odxtools/diaglayerraw.py +116 -23
- odxtools/diagnostictroublecode.py +2 -2
- odxtools/diagservice.py +33 -20
- odxtools/diagvariable.py +104 -0
- odxtools/dtcdop.py +39 -14
- odxtools/dyndefinedspec.py +179 -0
- odxtools/encodestate.py +14 -2
- odxtools/environmentdatadescription.py +137 -16
- odxtools/exceptions.py +10 -1
- odxtools/multiplexer.py +92 -56
- odxtools/multiplexercase.py +6 -5
- odxtools/multiplexerdefaultcase.py +7 -6
- odxtools/odxlink.py +19 -47
- odxtools/odxtypes.py +1 -1
- odxtools/parameterinfo.py +2 -2
- odxtools/parameters/nrcconstparameter.py +28 -37
- odxtools/parameters/systemparameter.py +1 -1
- odxtools/parameters/tablekeyparameter.py +11 -4
- odxtools/servicebinner.py +1 -1
- odxtools/specialdatagroup.py +1 -1
- odxtools/swvariable.py +21 -0
- odxtools/templates/macros/printComparam.xml.jinja2 +4 -2
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -8
- odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
- odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
- odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
- odxtools/templates/macros/printParam.xml.jinja2 +7 -8
- odxtools/templates/macros/printService.xml.jinja2 +3 -2
- odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -2
- odxtools/templates/macros/printVariant.xml.jinja2 +30 -13
- odxtools/variablegroup.py +22 -0
- odxtools/version.py +2 -2
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/METADATA +18 -18
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/RECORD +49 -42
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/WHEEL +1 -1
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/LICENSE +0 -0
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/entry_points.txt +0 -0
- {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .diagcomm import DiagClassType, DiagComm
|
7
|
+
from .exceptions import odxraise, odxrequire
|
8
|
+
from .nameditemlist import NamedItemList
|
9
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
10
|
+
from .snrefcontext import SnRefContext
|
11
|
+
from .table import Table
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class DynIdDefModeInfo:
|
16
|
+
def_mode: str
|
17
|
+
|
18
|
+
clear_dyn_def_message_ref: Optional[OdxLinkRef]
|
19
|
+
clear_dyn_def_message_snref: Optional[str]
|
20
|
+
|
21
|
+
read_dyn_def_message_ref: Optional[OdxLinkRef]
|
22
|
+
read_dyn_def_message_snref: Optional[str]
|
23
|
+
|
24
|
+
dyn_def_message_ref: Optional[OdxLinkRef]
|
25
|
+
dyn_def_message_snref: Optional[str]
|
26
|
+
|
27
|
+
supported_dyn_ids: List[bytes]
|
28
|
+
selection_table_refs: List[Union[OdxLinkRef, str]]
|
29
|
+
|
30
|
+
@property
|
31
|
+
def clear_dyn_def_message(self) -> DiagComm:
|
32
|
+
return self._clear_dyn_def_message
|
33
|
+
|
34
|
+
@property
|
35
|
+
def read_dyn_def_message(self) -> DiagComm:
|
36
|
+
return self._read_dyn_def_message
|
37
|
+
|
38
|
+
@property
|
39
|
+
def dyn_def_message(self) -> DiagComm:
|
40
|
+
return self._dyn_def_message
|
41
|
+
|
42
|
+
@property
|
43
|
+
def selection_tables(self) -> NamedItemList[Table]:
|
44
|
+
return self._selection_tables
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def from_et(et_element: ElementTree.Element,
|
48
|
+
doc_frags: List[OdxDocFragment]) -> "DynIdDefModeInfo":
|
49
|
+
def_mode = odxrequire(et_element.findtext("DEF-MODE"))
|
50
|
+
|
51
|
+
clear_dyn_def_message_ref = OdxLinkRef.from_et(
|
52
|
+
et_element.find("CLEAR-DYN-DEF-MESSAGE-REF"), doc_frags)
|
53
|
+
if (snref_elem := et_element.find("CLEAR-DYN-DEF-MESSAGE-SNREF")) is not None:
|
54
|
+
clear_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
55
|
+
|
56
|
+
read_dyn_def_message_ref = OdxLinkRef.from_et(
|
57
|
+
et_element.find("READ-DYN-DEF-MESSAGE-REF"), doc_frags)
|
58
|
+
if (snref_elem := et_element.find("READ-DYN-DEF-MESSAGE-SNREF")) is not None:
|
59
|
+
read_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
60
|
+
|
61
|
+
dyn_def_message_ref = OdxLinkRef.from_et(et_element.find("DYN-DEF-MESSAGE-REF"), doc_frags)
|
62
|
+
if (snref_elem := et_element.find("DYN-DEF-MESSAGE-SNREF")) is not None:
|
63
|
+
dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
64
|
+
|
65
|
+
supported_dyn_ids = [
|
66
|
+
bytes.fromhex(odxrequire(x.text))
|
67
|
+
for x in et_element.iterfind("SUPPORTED-DYN-IDS/SUPPORTED-DYN-ID")
|
68
|
+
]
|
69
|
+
|
70
|
+
selection_table_refs: List[Union[OdxLinkRef, str]] = []
|
71
|
+
if (st_elems := et_element.find("SELECTION-TABLE-REFS")) is not None:
|
72
|
+
for st_elem in st_elems:
|
73
|
+
if st_elem.tag == "SELECTION-TABLE-REF":
|
74
|
+
selection_table_refs.append(OdxLinkRef.from_et(st_elem, doc_frags))
|
75
|
+
elif st_elem.tag == "SELECTION-TABLE-SNREF":
|
76
|
+
selection_table_refs.append(odxrequire(st_elem.get("SHORT-NAME")))
|
77
|
+
else:
|
78
|
+
odxraise()
|
79
|
+
|
80
|
+
return DynIdDefModeInfo(
|
81
|
+
def_mode=def_mode,
|
82
|
+
clear_dyn_def_message_ref=clear_dyn_def_message_ref,
|
83
|
+
clear_dyn_def_message_snref=clear_dyn_def_message_snref,
|
84
|
+
read_dyn_def_message_ref=read_dyn_def_message_ref,
|
85
|
+
read_dyn_def_message_snref=read_dyn_def_message_snref,
|
86
|
+
dyn_def_message_ref=dyn_def_message_ref,
|
87
|
+
dyn_def_message_snref=dyn_def_message_snref,
|
88
|
+
supported_dyn_ids=supported_dyn_ids,
|
89
|
+
selection_table_refs=selection_table_refs,
|
90
|
+
)
|
91
|
+
|
92
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
93
|
+
result: Dict[OdxLinkId, Any] = {}
|
94
|
+
|
95
|
+
return result
|
96
|
+
|
97
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
98
|
+
self._selection_tables = NamedItemList[Table]()
|
99
|
+
|
100
|
+
if self.clear_dyn_def_message_ref is not None:
|
101
|
+
self._clear_dyn_def_message = odxlinks.resolve(self.clear_dyn_def_message_ref, DiagComm)
|
102
|
+
|
103
|
+
if self.read_dyn_def_message_ref is not None:
|
104
|
+
self._read_dyn_def_message = odxlinks.resolve(self.read_dyn_def_message_ref, DiagComm)
|
105
|
+
|
106
|
+
if self.dyn_def_message_ref is not None:
|
107
|
+
self._dyn_def_message = odxlinks.resolve(self.dyn_def_message_ref, DiagComm)
|
108
|
+
|
109
|
+
# resolve the selection tables referenced using ODXLINK
|
110
|
+
for x in self.selection_table_refs:
|
111
|
+
if isinstance(x, OdxLinkRef):
|
112
|
+
self._selection_tables.append(odxlinks.resolve(x, Table))
|
113
|
+
|
114
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
115
|
+
diag_layer = odxrequire(context.diag_layer)
|
116
|
+
|
117
|
+
if self.clear_dyn_def_message_snref is not None:
|
118
|
+
self._clear_dyn_def_message = resolve_snref(self.clear_dyn_def_message_snref,
|
119
|
+
diag_layer.diag_comms, DiagComm)
|
120
|
+
|
121
|
+
if self.read_dyn_def_message_snref is not None:
|
122
|
+
self._read_dyn_def_message = resolve_snref(self.read_dyn_def_message_snref,
|
123
|
+
diag_layer.diag_comms, DiagComm)
|
124
|
+
|
125
|
+
if self.dyn_def_message_snref is not None:
|
126
|
+
self._dyn_def_message = resolve_snref(self.dyn_def_message_snref, diag_layer.diag_comms,
|
127
|
+
DiagComm)
|
128
|
+
|
129
|
+
if self._clear_dyn_def_message.diagnostic_class != DiagClassType.CLEAR_DYN_DEF_MESSAGE:
|
130
|
+
odxraise(
|
131
|
+
f"Diagnostic communication object of wrong type referenced: "
|
132
|
+
f"({odxrequire(self._clear_dyn_def_message.diagnostic_class).value} instead of "
|
133
|
+
f"CLEAR-DYN-DEF-MESSAGE)")
|
134
|
+
if self._read_dyn_def_message.diagnostic_class != DiagClassType.READ_DYN_DEFINED_MESSAGE:
|
135
|
+
odxraise(f"Diagnostic communication object of wrong type referenced: "
|
136
|
+
f"({odxrequire(self._read_dyn_def_message.diagnostic_class).value} instead of "
|
137
|
+
f"READ-DYN-DEFINED-MESSAGE)")
|
138
|
+
if self._dyn_def_message.diagnostic_class != DiagClassType.DYN_DEF_MESSAGE:
|
139
|
+
odxraise(f"Diagnostic communication object of wrong type referenced: "
|
140
|
+
f"({odxrequire(self._dyn_def_message.diagnostic_class).value} instead of "
|
141
|
+
f"DYN-DEF-MESSAGE)")
|
142
|
+
|
143
|
+
# resolve the remaining selection tables that are referenced via SNREF
|
144
|
+
for i, x in enumerate(self.selection_table_refs):
|
145
|
+
if isinstance(x, str):
|
146
|
+
ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
|
147
|
+
self._selection_tables.insert(i, resolve_snref(x, ddd_spec.tables, Table))
|
148
|
+
|
149
|
+
|
150
|
+
@dataclass
|
151
|
+
class DynDefinedSpec:
|
152
|
+
dyn_id_def_mode_infos: List[DynIdDefModeInfo]
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def from_et(et_element: ElementTree.Element,
|
156
|
+
doc_frags: List[OdxDocFragment]) -> "DynDefinedSpec":
|
157
|
+
dyn_id_def_mode_infos = [
|
158
|
+
DynIdDefModeInfo.from_et(x, doc_frags)
|
159
|
+
for x in et_element.iterfind("DYN-ID-DEF-MODE-INFOS/DYN-ID-DEF-MODE-INFO")
|
160
|
+
]
|
161
|
+
return DynDefinedSpec(dyn_id_def_mode_infos=dyn_id_def_mode_infos)
|
162
|
+
|
163
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
164
|
+
result: Dict[OdxLinkId, Any] = {}
|
165
|
+
|
166
|
+
result.update(self._build_odxlinks())
|
167
|
+
|
168
|
+
for didmi in self.dyn_id_def_mode_infos:
|
169
|
+
result.update(didmi._build_odxlinks())
|
170
|
+
|
171
|
+
return result
|
172
|
+
|
173
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
174
|
+
for didmi in self.dyn_id_def_mode_infos:
|
175
|
+
didmi._resolve_odxlinks(odxlinks)
|
176
|
+
|
177
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
178
|
+
for didmi in self.dyn_id_def_mode_infos:
|
179
|
+
didmi._resolve_snrefs(context)
|
odxtools/encodestate.py
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import warnings
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import Dict, Optional, SupportsBytes
|
4
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
|
5
5
|
|
6
6
|
from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
|
7
|
-
from .odxtypes import AtomicOdxType, DataType
|
7
|
+
from .odxtypes import AtomicOdxType, DataType, ParameterValue
|
8
8
|
|
9
9
|
try:
|
10
10
|
import bitstruct.c as bitstruct
|
11
11
|
except ImportError:
|
12
12
|
import bitstruct
|
13
13
|
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from .parameters.parameter import Parameter
|
16
|
+
|
14
17
|
|
15
18
|
@dataclass
|
16
19
|
class EncodeState:
|
@@ -56,6 +59,15 @@ class EncodeState:
|
|
56
59
|
#: (needed for MinMaxLengthType, EndOfPduField, etc.)
|
57
60
|
is_end_of_pdu: bool = True
|
58
61
|
|
62
|
+
#: list of parameters that have been encoded so far. The journal
|
63
|
+
#: is used by some types of parameters which depend on the values of
|
64
|
+
#: other parameters; e.g., environment data description parameters
|
65
|
+
journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
|
66
|
+
|
67
|
+
#: If this is True, specifying unknown parameters for encoding
|
68
|
+
#: will raise an OdxError exception in strict mode.
|
69
|
+
allow_unknown_parameters = False
|
70
|
+
|
59
71
|
def __post_init__(self) -> None:
|
60
72
|
# if a coded message has been specified, but no used_mask, we
|
61
73
|
# assume that all of the bits of the coded message are
|
@@ -1,17 +1,19 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Any, Dict, List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional, cast
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
8
|
from .complexdop import ComplexDop
|
9
9
|
from .decodestate import DecodeState
|
10
|
+
from .dtcdop import DtcDop
|
10
11
|
from .encodestate import EncodeState
|
11
12
|
from .environmentdata import EnvironmentData
|
12
13
|
from .exceptions import odxraise, odxrequire
|
13
14
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
14
|
-
from .odxtypes import ParameterValue
|
15
|
+
from .odxtypes import ParameterValue, ParameterValueDict
|
16
|
+
from .parameters.parameter import Parameter
|
15
17
|
from .snrefcontext import SnRefContext
|
16
18
|
from .utils import dataclass_fields_asdict
|
17
19
|
|
@@ -27,16 +29,26 @@ class EnvironmentDataDescription(ComplexDop):
|
|
27
29
|
|
28
30
|
"""
|
29
31
|
|
32
|
+
param_snref: Optional[str]
|
33
|
+
param_snpathref: Optional[str]
|
34
|
+
|
30
35
|
# in ODX 2.0.0, ENV-DATAS seems to be a mandatory
|
31
36
|
# sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
|
32
37
|
# present
|
33
38
|
env_datas: List[EnvironmentData]
|
34
39
|
env_data_refs: List[OdxLinkRef]
|
35
|
-
param_snref: Optional[str]
|
36
|
-
param_snpathref: Optional[str]
|
37
40
|
|
38
|
-
|
39
|
-
|
41
|
+
@property
|
42
|
+
def param(self) -> Parameter:
|
43
|
+
# the parameter referenced via SNREF cannot be resolved here
|
44
|
+
# because the relevant list of parameters depends on the
|
45
|
+
# concrete codec object processed, whilst an environment data
|
46
|
+
# description object can be featured in an arbitrary number of
|
47
|
+
# responses. Instead, lookup of the appropriate parameter is
|
48
|
+
# done within the encode and decode methods.
|
49
|
+
odxraise("The parameter of ENV-DATA-DESC objects cannot be resolved "
|
50
|
+
"because it depends on the context")
|
51
|
+
return cast(None, Parameter)
|
40
52
|
|
41
53
|
@staticmethod
|
42
54
|
def from_et(et_element: ElementTree.Element,
|
@@ -71,8 +83,9 @@ class EnvironmentDataDescription(ComplexDop):
|
|
71
83
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
72
84
|
odxlinks = {self.odx_id: self}
|
73
85
|
|
74
|
-
|
75
|
-
|
86
|
+
if not self.env_data_refs:
|
87
|
+
for ed in self.env_datas:
|
88
|
+
odxlinks.update(ed._build_odxlinks())
|
76
89
|
|
77
90
|
return odxlinks
|
78
91
|
|
@@ -96,17 +109,125 @@ class EnvironmentDataDescription(ComplexDop):
|
|
96
109
|
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
97
110
|
encode_state: EncodeState) -> None:
|
98
111
|
"""Convert a physical value into bytes and emplace them into a PDU.
|
99
|
-
|
100
|
-
Since environmental data is supposed to never appear on the
|
101
|
-
wire, this method just raises an EncodeError exception.
|
102
112
|
"""
|
103
|
-
|
113
|
+
|
114
|
+
# retrieve the relevant DTC parameter which must be located in
|
115
|
+
# front of the environment data description.
|
116
|
+
if self.param_snref is None:
|
117
|
+
odxraise("Specifying the DTC parameter for environment data "
|
118
|
+
"descriptions via SNPATHREF is not supported yet")
|
119
|
+
return None
|
120
|
+
|
121
|
+
dtc_param: Optional[Parameter] = None
|
122
|
+
dtc_dop: Optional[DtcDop] = None
|
123
|
+
dtc_param_value: Optional[ParameterValue] = None
|
124
|
+
for prev_param, prev_param_value in reversed(encode_state.journal):
|
125
|
+
if prev_param.short_name == self.param_snref:
|
126
|
+
dtc_param = prev_param
|
127
|
+
prev_dop = getattr(prev_param, "dop", None)
|
128
|
+
if not isinstance(prev_dop, DtcDop):
|
129
|
+
odxraise(f"The DOP of the parameter referenced by environment data "
|
130
|
+
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
|
131
|
+
return
|
132
|
+
dtc_dop = prev_dop
|
133
|
+
dtc_param_value = prev_param_value
|
134
|
+
break
|
135
|
+
|
136
|
+
if dtc_param is None:
|
137
|
+
odxraise("Environment data description parameters are only allowed following "
|
138
|
+
"the referenced value parameter.")
|
139
|
+
return
|
140
|
+
|
141
|
+
if dtc_param_value is None or dtc_dop is None:
|
142
|
+
# this should never happen
|
143
|
+
odxraise()
|
144
|
+
return
|
145
|
+
|
146
|
+
numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
|
147
|
+
|
148
|
+
# deal with the "all value" environment data. This holds
|
149
|
+
# parameters that are common to all DTCs. Be aware that the
|
150
|
+
# specification mandates that there is at most one such
|
151
|
+
# environment data object
|
152
|
+
for env_data in self.env_datas:
|
153
|
+
if env_data.all_value:
|
154
|
+
tmp = encode_state.allow_unknown_parameters
|
155
|
+
encode_state.allow_unknown_parameters = True
|
156
|
+
env_data.encode_into_pdu(physical_value, encode_state)
|
157
|
+
encode_state.allow_unknown_parameters = tmp
|
158
|
+
break
|
159
|
+
|
160
|
+
# find the environment data corresponding to the given trouble
|
161
|
+
# code
|
162
|
+
for env_data in self.env_datas:
|
163
|
+
if numerical_dtc in env_data.dtc_values:
|
164
|
+
tmp = encode_state.allow_unknown_parameters
|
165
|
+
encode_state.allow_unknown_parameters = True
|
166
|
+
env_data.encode_into_pdu(physical_value, encode_state)
|
167
|
+
encode_state.allow_unknown_parameters = tmp
|
168
|
+
break
|
104
169
|
|
105
170
|
@override
|
106
171
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
107
172
|
"""Extract the bytes from a PDU and convert them to a physical value.
|
108
|
-
|
109
|
-
Since environmental data is supposed to never appear on the
|
110
|
-
wire, this method just raises an DecodeError exception.
|
111
173
|
"""
|
112
|
-
|
174
|
+
|
175
|
+
# retrieve the relevant DTC parameter which must be located in
|
176
|
+
# front of the environment data description.
|
177
|
+
if self.param_snref is None:
|
178
|
+
odxraise("Specifying the DTC parameter for environment data "
|
179
|
+
"descriptions via SNPATHREF is not supported yet")
|
180
|
+
return None
|
181
|
+
|
182
|
+
dtc_param: Optional[Parameter] = None
|
183
|
+
dtc_dop: Optional[DtcDop] = None
|
184
|
+
dtc_param_value: Optional[ParameterValue] = None
|
185
|
+
for prev_param, prev_param_value in reversed(decode_state.journal):
|
186
|
+
if prev_param.short_name == self.param_snref:
|
187
|
+
dtc_param = prev_param
|
188
|
+
prev_dop = getattr(prev_param, "dop", None)
|
189
|
+
if not isinstance(prev_dop, DtcDop):
|
190
|
+
odxraise(f"The DOP of the parameter referenced by environment data "
|
191
|
+
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
|
192
|
+
return
|
193
|
+
dtc_dop = prev_dop
|
194
|
+
dtc_param_value = prev_param_value
|
195
|
+
break
|
196
|
+
|
197
|
+
if dtc_param is None:
|
198
|
+
odxraise("Environment data description parameters are only allowed following "
|
199
|
+
"the referenced value parameter.")
|
200
|
+
return
|
201
|
+
|
202
|
+
if dtc_param_value is None or dtc_dop is None:
|
203
|
+
# this should never happen
|
204
|
+
odxraise()
|
205
|
+
return
|
206
|
+
|
207
|
+
numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
|
208
|
+
|
209
|
+
result: ParameterValueDict = {}
|
210
|
+
|
211
|
+
# deal with the "all value" environment data. This holds
|
212
|
+
# parameters that are common to all DTCs. Be aware that the
|
213
|
+
# specification mandates that there is at most one such
|
214
|
+
# environment data object
|
215
|
+
for env_data in self.env_datas:
|
216
|
+
if env_data.all_value:
|
217
|
+
tmp = env_data.decode_from_pdu(decode_state)
|
218
|
+
if not isinstance(tmp, dict):
|
219
|
+
odxraise()
|
220
|
+
result.update(tmp)
|
221
|
+
break
|
222
|
+
|
223
|
+
# find the environment data corresponding to the given trouble
|
224
|
+
# code
|
225
|
+
for env_data in self.env_datas:
|
226
|
+
if numerical_dtc in env_data.dtc_values:
|
227
|
+
tmp = env_data.decode_from_pdu(decode_state)
|
228
|
+
if not isinstance(tmp, dict):
|
229
|
+
odxraise()
|
230
|
+
result.update(tmp)
|
231
|
+
break
|
232
|
+
|
233
|
+
return result
|
odxtools/exceptions.py
CHANGED
@@ -9,13 +9,22 @@ class OdxError(Exception):
|
|
9
9
|
|
10
10
|
|
11
11
|
class EncodeError(Warning, OdxError):
|
12
|
-
"""Encoding of a message to raw data failed
|
12
|
+
"""Encoding of a message to raw data failed"""
|
13
13
|
|
14
14
|
|
15
15
|
class DecodeError(Warning, OdxError):
|
16
16
|
"""Decoding raw data failed."""
|
17
17
|
|
18
18
|
|
19
|
+
class DecodeMismatch(DecodeError):
|
20
|
+
"""Decoding failed because some parameters exhibit an incorrect value
|
21
|
+
|
22
|
+
This is can happen if NRC-CONST or environment data descriptions
|
23
|
+
are present.
|
24
|
+
|
25
|
+
"""
|
26
|
+
|
27
|
+
|
19
28
|
class OdxWarning(Warning):
|
20
29
|
"""Any warning that happens during interacting with diagnostic objects."""
|
21
30
|
|
odxtools/multiplexer.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Any, Dict, List, Optional, Tuple
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from typing_extensions import override
|
@@ -8,7 +8,7 @@ from typing_extensions import override
|
|
8
8
|
from .complexdop import ComplexDop
|
9
9
|
from .decodestate import DecodeState
|
10
10
|
from .encodestate import EncodeState
|
11
|
-
from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
|
11
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
12
12
|
from .multiplexercase import MultiplexerCase
|
13
13
|
from .multiplexerdefaultcase import MultiplexerDefaultCase
|
14
14
|
from .multiplexerswitchkey import MultiplexerSwitchKey
|
@@ -79,87 +79,123 @@ class Multiplexer(ComplexDop):
|
|
79
79
|
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
80
80
|
|
81
81
|
if encode_state.cursor_bit_position != 0:
|
82
|
-
raise EncodeError(f"Multiplexer must be aligned, i.e. bit_position=0, but "
|
82
|
+
raise EncodeError(f"Multiplexer parameters must be aligned, i.e. bit_position=0, but "
|
83
83
|
f"{self.short_name} was passed the bit position "
|
84
84
|
f"{encode_state.cursor_bit_position}")
|
85
85
|
|
86
|
-
if not isinstance(physical_value, dict) or len(physical_value) != 1:
|
87
|
-
raise EncodeError("""Multiplexer should be defined as a dict
|
88
|
-
with only one key equal to the desired case""")
|
89
|
-
|
90
86
|
orig_origin = encode_state.origin_byte_position
|
91
|
-
|
92
87
|
encode_state.origin_byte_position = encode_state.cursor_byte_position
|
93
88
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
89
|
+
if isinstance(physical_value, (list, tuple)) and len(physical_value) == 2:
|
90
|
+
case_spec, case_value = physical_value
|
91
|
+
elif isinstance(physical_value, dict) and len(physical_value) == 1:
|
92
|
+
case_spec, case_value = next(iter(physical_value.items()))
|
93
|
+
else:
|
94
|
+
raise EncodeError(
|
95
|
+
f"Values of multiplexer parameters must be defined as a "
|
96
|
+
f"(case_name, content_value) tuple instead of as '{physical_value!r}'")
|
97
|
+
|
98
|
+
mux_case: Union[MultiplexerCase, MultiplexerDefaultCase]
|
99
|
+
if isinstance(case_spec, str):
|
100
|
+
applicable_cases = [x for x in self.cases if x.short_name == case_spec]
|
101
|
+
if len(applicable_cases) == 0:
|
102
|
+
raise EncodeError(
|
103
|
+
f"Multiplexer {self.short_name} does not know any case called {case_spec}")
|
104
|
+
|
105
|
+
odxassert(len(applicable_cases) == 1)
|
106
|
+
mux_case = applicable_cases[0]
|
107
|
+
key_value, _ = self._get_case_limits(mux_case)
|
108
|
+
elif isinstance(case_spec, int):
|
109
|
+
applicable_cases = []
|
110
|
+
for x in self.cases:
|
111
|
+
lower, upper = cast(Tuple[int, int], self._get_case_limits(x))
|
112
|
+
if lower <= case_spec and case_spec <= upper:
|
113
|
+
applicable_cases.append(x)
|
114
|
+
|
115
|
+
if len(applicable_cases) == 0:
|
116
|
+
if self.default_case is None:
|
117
|
+
raise EncodeError(
|
118
|
+
f"Multiplexer {self.short_name} does not know any case called {case_spec}")
|
119
|
+
mux_case = self.default_case
|
120
|
+
key_value = case_spec
|
121
|
+
else:
|
122
|
+
mux_case = applicable_cases[0]
|
123
|
+
key_value = case_spec
|
124
|
+
elif isinstance(case_spec, MultiplexerCase):
|
125
|
+
mux_case = case_spec
|
126
|
+
key_value, _ = self._get_case_limits(mux_case)
|
127
|
+
elif case_spec is None:
|
128
|
+
if self.default_case is None:
|
129
|
+
raise EncodeError(f"Multiplexer {self.short_name} does not define a default case")
|
130
|
+
key_value = 0
|
131
|
+
else:
|
132
|
+
raise EncodeError(f"Illegal case specification '{case_spec}' for "
|
133
|
+
f"multiplexer {self.short_name}")
|
134
|
+
|
135
|
+
# the byte position of the switch key is relative to
|
136
|
+
# the multiplexer's position
|
137
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
|
138
|
+
encode_state.cursor_bit_position = self.switch_key.bit_position or 0
|
139
|
+
self.switch_key.dop.encode_into_pdu(physical_value=key_value, encode_state=encode_state)
|
140
|
+
encode_state.cursor_bit_position = 0
|
141
|
+
|
142
|
+
if mux_case.structure is not None:
|
143
|
+
# the byte position of the content is specified by the
|
144
|
+
# BYTE-POSITION attribute of the multiplexer
|
145
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
|
146
|
+
mux_case.structure.encode_into_pdu(physical_value=case_value, encode_state=encode_state)
|
147
|
+
|
148
|
+
encode_state.origin_byte_position = orig_origin
|
123
149
|
|
124
150
|
@override
|
125
151
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
126
|
-
|
127
|
-
# multiplexers are structures and thus the origin position
|
128
|
-
# must be moved to the start of the multiplexer
|
129
152
|
orig_origin = decode_state.origin_byte_position
|
130
|
-
if self.byte_position is not None:
|
131
|
-
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
|
132
153
|
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
133
154
|
|
155
|
+
# Decode the switch key. Its BYTE-POSITION is relative to the
|
156
|
+
# that of the multiplexer.
|
157
|
+
if self.switch_key.byte_position is not None:
|
158
|
+
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.switch_key.byte_position
|
159
|
+
decode_state.cursor_bit_position = self.switch_key.bit_position or 0
|
134
160
|
key_value = self.switch_key.dop.decode_from_pdu(decode_state)
|
161
|
+
decode_state.cursor_bit_position = 0
|
135
162
|
|
136
163
|
if not isinstance(key_value, int):
|
137
164
|
odxraise(f"Multiplexer keys must be integers (is '{type(key_value).__name__}'"
|
138
165
|
f" for multiplexer '{self.short_name}')")
|
139
166
|
|
140
|
-
|
141
|
-
|
142
|
-
|
167
|
+
# "If a matching CASE is found, the referenced STRUCTURE is
|
168
|
+
# analyzed at the BYTE-POSITION (child element of MUX)
|
169
|
+
# relatively to the byte position of the MUX."
|
170
|
+
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
|
171
|
+
|
172
|
+
applicable_case: Optional[Union[MultiplexerCase, MultiplexerDefaultCase]] = None
|
173
|
+
for mux_case in self.cases:
|
143
174
|
lower, upper = self._get_case_limits(mux_case)
|
144
175
|
if lower <= key_value and key_value <= upper: # type: ignore[operator]
|
145
|
-
|
146
|
-
case_value = mux_case._structure.decode_from_pdu(decode_state)
|
176
|
+
applicable_case = mux_case
|
147
177
|
break
|
148
178
|
|
149
|
-
if
|
150
|
-
|
151
|
-
|
179
|
+
if applicable_case is None:
|
180
|
+
applicable_case = self.default_case
|
181
|
+
|
182
|
+
if applicable_case is None:
|
183
|
+
odxraise(
|
184
|
+
f"Cannot find an applicable case for value {key_value} in "
|
185
|
+
f"multiplexer {self.short_name}", DecodeError)
|
186
|
+
decode_state.origin_byte_position = orig_origin
|
187
|
+
return (None, None)
|
152
188
|
|
153
|
-
if
|
154
|
-
|
155
|
-
|
189
|
+
if applicable_case.structure is not None:
|
190
|
+
case_value = applicable_case.structure.decode_from_pdu(decode_state)
|
191
|
+
else:
|
192
|
+
case_value = {}
|
156
193
|
|
157
|
-
|
194
|
+
result = (applicable_case.short_name, case_value)
|
158
195
|
|
159
|
-
# go back to the original origin
|
160
196
|
decode_state.origin_byte_position = orig_origin
|
161
197
|
|
162
|
-
return
|
198
|
+
return result
|
163
199
|
|
164
200
|
@override
|
165
201
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|