odxtools 7.2.0__py3-none-any.whl → 7.3.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.
@@ -10,7 +10,7 @@ from .complexdop import ComplexDop
10
10
  from .dataobjectproperty import DataObjectProperty
11
11
  from .decodestate import DecodeState
12
12
  from .encodestate import EncodeState
13
- from .exceptions import EncodeError, OdxWarning, odxassert, odxraise, strict_mode
13
+ from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
14
14
  from .nameditemlist import NamedItemList
15
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
16
  from .odxtypes import ParameterDict, ParameterValue, ParameterValueDict
@@ -18,7 +18,6 @@ from .parameters.codedconstparameter import CodedConstParameter
18
18
  from .parameters.createanyparameter import create_any_parameter_from_et
19
19
  from .parameters.lengthkeyparameter import LengthKeyParameter
20
20
  from .parameters.matchingrequestparameter import MatchingRequestParameter
21
- from .parameters.nrcconstparameter import NrcConstParameter
22
21
  from .parameters.parameter import Parameter
23
22
  from .parameters.parameterwithdop import ParameterWithDOP
24
23
  from .parameters.physicalconstantparameter import PhysicalConstantParameter
@@ -75,10 +74,11 @@ class BasicStructure(ComplexDop):
75
74
 
76
75
  for param in self.parameters:
77
76
  if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
78
- isinstance(param, (CodedConstParameter, NrcConstParameter, PhysicalConstantParameter)):
77
+ isinstance(param, (CodedConstParameter, PhysicalConstantParameter)):
79
78
  param.encode_into_pdu(physical_value=None, encode_state=encode_state)
80
79
  else:
81
80
  break
81
+
82
82
  return encode_state.coded_message
83
83
 
84
84
  @property
@@ -160,8 +160,8 @@ class BasicStructure(ComplexDop):
160
160
  orig_is_end_of_pdu = encode_state.is_end_of_pdu
161
161
  encode_state.is_end_of_pdu = False
162
162
 
163
- # in strict mode, ensure that no values for unknown parameters are specified.
164
- if strict_mode:
163
+ # ensure that no values for unknown parameters are specified.
164
+ if not encode_state.allow_unknown_parameters:
165
165
  param_names = {param.short_name for param in self.parameters}
166
166
  for param_value_name in physical_value:
167
167
  if param_value_name not in param_names:
@@ -193,8 +193,10 @@ class BasicStructure(ComplexDop):
193
193
  odxraise(f"No value for required parameter {param.short_name} specified",
194
194
  EncodeError)
195
195
 
196
- param.encode_into_pdu(
197
- physical_value=physical_value.get(param.short_name), encode_state=encode_state)
196
+ param_phys_value = physical_value.get(param.short_name)
197
+ param.encode_into_pdu(physical_value=param_phys_value, encode_state=encode_state)
198
+
199
+ encode_state.journal.append((param, param_phys_value))
198
200
 
199
201
  encode_state.is_end_of_pdu = False
200
202
  if self.byte_size is not None:
@@ -236,6 +238,7 @@ class BasicStructure(ComplexDop):
236
238
  for param in self.parameters:
237
239
  value = param.decode_from_pdu(decode_state)
238
240
 
241
+ decode_state.journal.append((param, value))
239
242
  result[param.short_name] = value
240
243
 
241
244
  # decoding of the structure finished. go back the original origin.
@@ -46,8 +46,6 @@ class LinearSegment:
46
46
 
47
47
  inverse_value: Union[int, float] = 0
48
48
  if scale.compu_inverse_value is not None:
49
- if abs(factor) < 1e-10:
50
- odxraise(f"COMPU-INVERSE-VALUE for non-zero slope ({factor}) defined")
51
49
  x = odxrequire(scale.compu_inverse_value).value
52
50
  if not isinstance(x, (int, float)):
53
51
  odxraise(f"Non-numeric COMPU-INVERSE-VALUE specified ({x!r})")
odxtools/database.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from itertools import chain
3
3
  from pathlib import Path
4
- from typing import IO, List, Optional, OrderedDict
4
+ from typing import IO, Any, Dict, List, Optional, OrderedDict
5
5
  from xml.etree import ElementTree
6
6
  from zipfile import ZipFile
7
7
 
@@ -13,7 +13,7 @@ from .diaglayer import DiagLayer
13
13
  from .diaglayercontainer import DiagLayerContainer
14
14
  from .exceptions import odxraise
15
15
  from .nameditemlist import NamedItemList
16
- from .odxlink import OdxLinkDatabase
16
+ from .odxlink import OdxLinkDatabase, OdxLinkId
17
17
 
18
18
 
19
19
  class Database:
@@ -108,15 +108,7 @@ class Database:
108
108
 
109
109
  # Build odxlinks
110
110
  self._odxlinks = OdxLinkDatabase()
111
-
112
- for subset in self.comparam_subsets:
113
- self._odxlinks.update(subset._build_odxlinks())
114
-
115
- for spec in self.comparam_specs:
116
- self._odxlinks.update(spec._build_odxlinks())
117
-
118
- for dlc in self.diag_layer_containers:
119
- self._odxlinks.update(dlc._build_odxlinks())
111
+ self._odxlinks.update(self._build_odxlinks())
120
112
 
121
113
  # Resolve ODXLINK references
122
114
  for subset in self.comparam_subsets:
@@ -133,6 +125,20 @@ class Database:
133
125
  for dlc in self.diag_layer_containers:
134
126
  dlc._finalize_init(self, self._odxlinks)
135
127
 
128
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
129
+ result: Dict[OdxLinkId, Any] = {}
130
+
131
+ for subset in self.comparam_subsets:
132
+ result.update(subset._build_odxlinks())
133
+
134
+ for spec in self.comparam_specs:
135
+ result.update(spec._build_odxlinks())
136
+
137
+ for dlc in self.diag_layer_containers:
138
+ result.update(dlc._build_odxlinks())
139
+
140
+ return result
141
+
136
142
  @property
137
143
  def odxlinks(self) -> OdxLinkDatabase:
138
144
  """A map from odx_id to object"""
odxtools/decodestate.py CHANGED
@@ -1,11 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass, field
3
- from typing import TYPE_CHECKING, Dict, cast
3
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, cast
4
4
 
5
5
  import odxtools.exceptions as exceptions
6
6
 
7
7
  from .exceptions import DecodeError
8
- from .odxtypes import AtomicOdxType, DataType
8
+ from .odxtypes import AtomicOdxType, DataType, ParameterValue
9
9
 
10
10
  try:
11
11
  import bitstruct.c as bitstruct
@@ -13,6 +13,7 @@ except ImportError:
13
13
  import bitstruct
14
14
 
15
15
  if TYPE_CHECKING:
16
+ from .parameters.parameter import Parameter
16
17
  from .tablerow import TableRow
17
18
 
18
19
 
@@ -46,6 +47,11 @@ class DecodeState:
46
47
  #: values of the table key parameters decoded so far
47
48
  table_keys: Dict[str, "TableRow"] = field(default_factory=dict)
48
49
 
50
+ #: List of parameters that have been decoded so far. The journal
51
+ #: is used by some types of parameters which depend on the values of
52
+ #: other parameters; i.e., environment data description parameters
53
+ journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
54
+
49
55
  def extract_atomic_value(
50
56
  self,
51
57
  bit_length: int,
odxtools/diaglayer.py CHANGED
@@ -161,11 +161,6 @@ class DiagLayer:
161
161
  excessive memory consumption for large databases...
162
162
  """
163
163
 
164
- # this attribute may be removed later. it is currently
165
- # required to properly deal with auxiliary files within the
166
- # diagnostic layer.
167
- self._database = database
168
-
169
164
  #####
170
165
  # fill in all applicable objects that use value inheritance
171
166
  #####
@@ -1200,16 +1195,19 @@ class DiagLayer:
1200
1195
  for service in candidate_services:
1201
1196
  try:
1202
1197
  decoded_messages.append(service.decode_message(message))
1203
- except DecodeError:
1198
+ except DecodeError as e:
1204
1199
  # check if the message can be decoded as a global
1205
1200
  # negative response for the service
1201
+ gnr_found = False
1206
1202
  for gnr in self.global_negative_responses:
1207
1203
  try:
1208
1204
  decoded_gnr = gnr.decode(message)
1205
+ gnr_found = True
1209
1206
  if not isinstance(decoded_gnr, dict):
1210
- raise DecodeError(f"Expected the decoded value of a global "
1211
- f"negative response to be a dictionary, "
1212
- f"got {type(decoded_gnr)} for {self.short_name}")
1207
+ odxraise(
1208
+ f"Expected the decoded value of a global "
1209
+ f"negative response to be a dictionary, "
1210
+ f"got {type(decoded_gnr)} for {self.short_name}", DecodeError)
1213
1211
 
1214
1212
  decoded_messages.append(
1215
1213
  Message(
@@ -1220,6 +1218,9 @@ class DiagLayer:
1220
1218
  except DecodeError:
1221
1219
  pass
1222
1220
 
1221
+ if not gnr_found:
1222
+ raise e
1223
+
1223
1224
  if len(decoded_messages) == 0:
1224
1225
  raise DecodeError(
1225
1226
  f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
@@ -17,7 +17,7 @@ class DiagnosticTroubleCode(IdentifiableElement):
17
17
  trouble_code: int
18
18
  text: Optional[str]
19
19
  display_trouble_code: Optional[str]
20
- level: Union[int, None]
20
+ level: Optional[int]
21
21
  is_temporary_raw: Optional[bool]
22
22
  sdgs: List[SpecialDataGroup]
23
23
 
odxtools/diagservice.py CHANGED
@@ -6,7 +6,7 @@ from xml.etree import ElementTree
6
6
 
7
7
  from .comparaminstance import ComparamInstance
8
8
  from .diagcomm import DiagComm
9
- from .exceptions import DecodeError, odxassert, odxraise, odxrequire
9
+ from .exceptions import DecodeError, DecodeMismatch, odxassert, odxraise, odxrequire
10
10
  from .message import Message
11
11
  from .nameditemlist import NamedItemList
12
12
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
@@ -42,7 +42,7 @@ class DiagService(DiagComm):
42
42
  pos_response_refs: List[OdxLinkRef]
43
43
  neg_response_refs: List[OdxLinkRef]
44
44
 
45
- # TODO: pos_response_suppressable: Optional[PosResponseSuppressable]
45
+ # TODO: pos_response_suppressable: Optional[PosResponseSuppressable] # (sic!)
46
46
 
47
47
  is_cyclic_raw: Optional[bool]
48
48
  is_multiple_raw: Optional[bool]
@@ -181,8 +181,9 @@ class DiagService(DiagComm):
181
181
  for cpr in self.comparam_refs:
182
182
  cpr._resolve_snrefs(context)
183
183
 
184
- # comparams named list is lazy loaded
185
- # since ComparamInstance short_name is only valid after resolution
184
+ # The named item list of communication parameters is created
185
+ # here because ComparamInstance.short_name is only valid after
186
+ # reference resolution
186
187
  self._comparams = NamedItemList(self.comparam_refs)
187
188
 
188
189
  context.diag_service = None
@@ -202,24 +203,36 @@ class DiagService(DiagComm):
202
203
  if len(raw_message) >= len(prefix) and prefix == raw_message[:len(prefix)]:
203
204
  coding_objects.append(candidate_coding_object)
204
205
 
205
- if len(coding_objects) != 1:
206
- raise DecodeError(
207
- f"The service {self.short_name} cannot decode the message {raw_message.hex()}")
208
- coding_object = coding_objects[0]
209
- param_dict = coding_object.decode(raw_message)
210
- if not isinstance(param_dict, dict):
211
- # if this happens, this is probably due to a bug in
212
- # coding_object.decode()
213
- raise RuntimeError(f"Expected a set of decoded parameters, got {type(param_dict)}")
214
- return Message(
215
- coded_message=raw_message,
216
- service=self,
217
- coding_object=coding_object,
218
- param_dict=param_dict)
206
+ result_list: List[Message] = []
207
+ for coding_object in coding_objects:
208
+ try:
209
+ result_list.append(
210
+ Message(
211
+ coded_message=raw_message,
212
+ service=self,
213
+ coding_object=coding_object,
214
+ param_dict=coding_object.decode(raw_message)))
215
+ except DecodeMismatch:
216
+ # An NRC-CONST or environment data parameter
217
+ # encountered a non-matching value -> coding object
218
+ # does not apply
219
+ pass
220
+
221
+ if len(result_list) < 1:
222
+ odxraise(f"The service {self.short_name} cannot decode the message {raw_message.hex()}",
223
+ DecodeError)
224
+ return Message(
225
+ coded_message=raw_message, service=self, coding_object=None, param_dict={})
226
+ elif len(result_list) > 1:
227
+ odxraise(
228
+ f"The service {self.short_name} cannot uniquely decode the message {raw_message.hex()}",
229
+ DecodeError)
230
+
231
+ return result_list[0]
219
232
 
220
233
  def encode_request(self, **kwargs: ParameterValue) -> bytes:
221
- """
222
- Composes an UDS request an array of bytes for this service.
234
+ """Prepare an array of bytes ready to be send over the wire
235
+ for the request of this service.
223
236
  """
224
237
  # make sure that all parameters which are required for
225
238
  # encoding are specified (parameters which have a default are
odxtools/dtcdop.py CHANGED
@@ -130,24 +130,49 @@ class DtcDop(DopBase):
130
130
  sdgs=[],
131
131
  )
132
132
 
133
- @override
134
- def encode_into_pdu(self, physical_value: Optional[ParameterValue],
135
- encode_state: EncodeState) -> None:
136
- if isinstance(physical_value, DiagnosticTroubleCode):
137
- trouble_code = physical_value.trouble_code
138
- elif isinstance(physical_value, int):
133
+ def convert_to_numerical_trouble_code(self, dtc_value: ParameterValue) -> int:
134
+ if isinstance(dtc_value, DiagnosticTroubleCode):
135
+ return dtc_value.trouble_code
136
+ elif isinstance(dtc_value, int):
139
137
  # assume that physical value is the trouble_code
140
- trouble_code = physical_value
141
- elif isinstance(physical_value, str):
138
+ return dtc_value
139
+ elif isinstance(dtc_value, str):
142
140
  # assume that physical value is the short_name
143
- dtcs = [dtc for dtc in self.dtcs if dtc.short_name == physical_value]
144
- odxassert(len(dtcs) == 1)
145
- trouble_code = dtcs[0].trouble_code
141
+ dtcs = [dtc for dtc in self.dtcs if dtc.short_name == dtc_value]
142
+ if len(dtcs) != 1:
143
+ odxraise(f"No DTC named {dtc_value} found for DTC-DOP "
144
+ f"{self.short_name}.", EncodeError)
145
+ return cast(int, None)
146
+
147
+ return dtcs[0].trouble_code
146
148
  else:
147
- raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
148
- f" DiagnosticTroubleCode but got {physical_value!r}.")
149
+ odxraise(
150
+ f"The DTC-DOP {self.short_name} expected a"
151
+ f" diagnostic trouble code but got {type(dtc_value).__name__}", EncodeError)
152
+ return cast(int, None)
153
+
154
+ @override
155
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
156
+ encode_state: EncodeState) -> None:
157
+ if physical_value is None:
158
+ odxraise(f"No DTC specified", EncodeError)
159
+ return
160
+
161
+ trouble_code = self.convert_to_numerical_trouble_code(physical_value)
162
+
163
+ internal_trouble_code = int(self.compu_method.convert_physical_to_internal(trouble_code))
164
+
165
+ found = False
166
+ for dtc in self.dtcs:
167
+ if internal_trouble_code == dtc.trouble_code:
168
+ found = True
169
+ break
170
+
171
+ if not found:
172
+ odxraise(
173
+ f"Unknown diagnostic trouble code {physical_value!r} "
174
+ f"(0x{internal_trouble_code: 06x}) specified", EncodeError)
149
175
 
150
- internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
151
176
  self.diag_coded_type.encode_into_pdu(internal_trouble_code, encode_state)
152
177
 
153
178
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
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
- def __post_init__(self) -> None:
39
- self.bit_length = None
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,
@@ -96,17 +108,125 @@ class EnvironmentDataDescription(ComplexDop):
96
108
  def encode_into_pdu(self, physical_value: Optional[ParameterValue],
97
109
  encode_state: EncodeState) -> None:
98
110
  """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
111
  """
103
- odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
112
+
113
+ # retrieve the relevant DTC parameter which must be located in
114
+ # front of the environment data description.
115
+ if self.param_snref is None:
116
+ odxraise("Specifying the DTC parameter for environment data "
117
+ "descriptions via SNPATHREF is not supported yet")
118
+ return None
119
+
120
+ dtc_param: Optional[Parameter] = None
121
+ dtc_dop: Optional[DtcDop] = None
122
+ dtc_param_value: Optional[ParameterValue] = None
123
+ for prev_param, prev_param_value in reversed(encode_state.journal):
124
+ if prev_param.short_name == self.param_snref:
125
+ dtc_param = prev_param
126
+ prev_dop = getattr(prev_param, "dop", None)
127
+ if not isinstance(prev_dop, DtcDop):
128
+ odxraise(f"The DOP of the parameter referenced by environment data "
129
+ f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
130
+ return
131
+ dtc_dop = prev_dop
132
+ dtc_param_value = prev_param_value
133
+ break
134
+
135
+ if dtc_param is None:
136
+ odxraise("Environment data description parameters are only allowed following "
137
+ "the referenced value parameter.")
138
+ return
139
+
140
+ if dtc_param_value is None or dtc_dop is None:
141
+ # this should never happen
142
+ odxraise()
143
+ return
144
+
145
+ numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
146
+
147
+ # deal with the "all value" environment data. This holds
148
+ # parameters that are common to all DTCs. Be aware that the
149
+ # specification mandates that there is at most one such
150
+ # environment data object
151
+ for env_data in self.env_datas:
152
+ if env_data.all_value:
153
+ tmp = encode_state.allow_unknown_parameters
154
+ encode_state.allow_unknown_parameters = True
155
+ env_data.encode_into_pdu(physical_value, encode_state)
156
+ encode_state.allow_unknown_parameters = tmp
157
+ break
158
+
159
+ # find the environment data corresponding to the given trouble
160
+ # code
161
+ for env_data in self.env_datas:
162
+ if numerical_dtc in env_data.dtc_values:
163
+ tmp = encode_state.allow_unknown_parameters
164
+ encode_state.allow_unknown_parameters = True
165
+ env_data.encode_into_pdu(physical_value, encode_state)
166
+ encode_state.allow_unknown_parameters = tmp
167
+ break
104
168
 
105
169
  @override
106
170
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
107
171
  """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
172
  """
112
- odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
173
+
174
+ # retrieve the relevant DTC parameter which must be located in
175
+ # front of the environment data description.
176
+ if self.param_snref is None:
177
+ odxraise("Specifying the DTC parameter for environment data "
178
+ "descriptions via SNPATHREF is not supported yet")
179
+ return None
180
+
181
+ dtc_param: Optional[Parameter] = None
182
+ dtc_dop: Optional[DtcDop] = None
183
+ dtc_param_value: Optional[ParameterValue] = None
184
+ for prev_param, prev_param_value in reversed(decode_state.journal):
185
+ if prev_param.short_name == self.param_snref:
186
+ dtc_param = prev_param
187
+ prev_dop = getattr(prev_param, "dop", None)
188
+ if not isinstance(prev_dop, DtcDop):
189
+ odxraise(f"The DOP of the parameter referenced by environment data "
190
+ f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
191
+ return
192
+ dtc_dop = prev_dop
193
+ dtc_param_value = prev_param_value
194
+ break
195
+
196
+ if dtc_param is None:
197
+ odxraise("Environment data description parameters are only allowed following "
198
+ "the referenced value parameter.")
199
+ return
200
+
201
+ if dtc_param_value is None or dtc_dop is None:
202
+ # this should never happen
203
+ odxraise()
204
+ return
205
+
206
+ numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
207
+
208
+ result: ParameterValueDict = {}
209
+
210
+ # deal with the "all value" environment data. This holds
211
+ # parameters that are common to all DTCs. Be aware that the
212
+ # specification mandates that there is at most one such
213
+ # environment data object
214
+ for env_data in self.env_datas:
215
+ if env_data.all_value:
216
+ tmp = env_data.decode_from_pdu(decode_state)
217
+ if not isinstance(tmp, dict):
218
+ odxraise()
219
+ result.update(tmp)
220
+ break
221
+
222
+ # find the environment data corresponding to the given trouble
223
+ # code
224
+ for env_data in self.env_datas:
225
+ if numerical_dtc in env_data.dtc_values:
226
+ tmp = env_data.decode_from_pdu(decode_state)
227
+ if not isinstance(tmp, dict):
228
+ odxraise()
229
+ result.update(tmp)
230
+ break
231
+
232
+ 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