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.
Files changed (49) hide show
  1. odxtools/basicstructure.py +10 -7
  2. odxtools/cli/_print_utils.py +3 -3
  3. odxtools/cli/browse.py +4 -2
  4. odxtools/cli/list.py +3 -3
  5. odxtools/commrelation.py +122 -0
  6. odxtools/comparaminstance.py +1 -1
  7. odxtools/comparamspec.py +1 -2
  8. odxtools/compumethods/linearsegment.py +0 -2
  9. odxtools/database.py +17 -11
  10. odxtools/decodestate.py +8 -2
  11. odxtools/diaglayer.py +23 -17
  12. odxtools/diaglayerraw.py +116 -23
  13. odxtools/diagnostictroublecode.py +2 -2
  14. odxtools/diagservice.py +33 -20
  15. odxtools/diagvariable.py +104 -0
  16. odxtools/dtcdop.py +39 -14
  17. odxtools/dyndefinedspec.py +179 -0
  18. odxtools/encodestate.py +14 -2
  19. odxtools/environmentdatadescription.py +137 -16
  20. odxtools/exceptions.py +10 -1
  21. odxtools/multiplexer.py +92 -56
  22. odxtools/multiplexercase.py +6 -5
  23. odxtools/multiplexerdefaultcase.py +7 -6
  24. odxtools/odxlink.py +19 -47
  25. odxtools/odxtypes.py +1 -1
  26. odxtools/parameterinfo.py +2 -2
  27. odxtools/parameters/nrcconstparameter.py +28 -37
  28. odxtools/parameters/systemparameter.py +1 -1
  29. odxtools/parameters/tablekeyparameter.py +11 -4
  30. odxtools/servicebinner.py +1 -1
  31. odxtools/specialdatagroup.py +1 -1
  32. odxtools/swvariable.py +21 -0
  33. odxtools/templates/macros/printComparam.xml.jinja2 +4 -2
  34. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -8
  35. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  36. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  37. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  38. odxtools/templates/macros/printParam.xml.jinja2 +7 -8
  39. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  40. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -2
  41. odxtools/templates/macros/printVariant.xml.jinja2 +30 -13
  42. odxtools/variablegroup.py +22 -0
  43. odxtools/version.py +2 -2
  44. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/METADATA +18 -18
  45. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/RECORD +49 -42
  46. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/WHEEL +1 -1
  47. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/LICENSE +0 -0
  48. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/entry_points.txt +0 -0
  49. {odxtools-7.2.0.dist-info → odxtools-7.4.0.dist-info}/top_level.txt +0 -0
@@ -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.
@@ -235,7 +235,7 @@ def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = p
235
235
  type = []
236
236
  num_services = []
237
237
  num_dops = []
238
- num_comparams = []
238
+ num_comparam_refs = []
239
239
  for variant in variants:
240
240
  assert isinstance(variant, DiagLayer)
241
241
  all_services: List[Union[DiagService, SingleEcuJob]] = sorted(
@@ -244,13 +244,13 @@ def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = p
244
244
  type.append(variant.variant_type.value)
245
245
  num_services.append(len(all_services))
246
246
  num_dops.append(len(variant.diag_data_dictionary_spec.data_object_props))
247
- num_comparams.append(len(variant.comparams))
247
+ num_comparam_refs.append(len(variant.comparam_refs))
248
248
 
249
249
  table = {
250
250
  'Name': name,
251
251
  'Variant Type': type,
252
252
  'Number of Services': num_services,
253
253
  'Number of DOPs': num_dops,
254
- 'Number of communication parameters': num_comparams
254
+ 'Number of communication parameters': num_comparam_refs
255
255
  }
256
256
  print_fn(tabulate(table, headers='keys', tablefmt='presto'))
odxtools/cli/browse.py CHANGED
@@ -112,7 +112,8 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
112
112
 
113
113
  def encode_message_interactively(sub_service: Union[Request, Response],
114
114
  ask_user_confirmation: bool = False) -> None:
115
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
115
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
116
+ ) or not sys.__stdout__.isatty():
116
117
  raise SystemError("This command can only be used in an interactive shell!")
117
118
  param_dict = sub_service.parameter_dict()
118
119
 
@@ -260,7 +261,8 @@ def encode_message_from_string_values(
260
261
 
261
262
 
262
263
  def browse(odxdb: Database) -> None:
263
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
264
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
265
+ ) or not sys.__stdout__.isatty():
264
266
  raise SystemError("This command can only be used in an interactive shell!")
265
267
  dl_names = [dl.short_name for dl in odxdb.diag_layers]
266
268
  while True:
odxtools/cli/list.py CHANGED
@@ -57,7 +57,7 @@ def print_summary(odxdb: Database,
57
57
  all_services: List[DiagComm] = sorted(dl.services, key=lambda x: x.short_name)
58
58
 
59
59
  data_object_properties = dl.diag_data_dictionary_spec.data_object_props
60
- comparams = dl.comparams
60
+ comparam_refs = dl.comparam_refs
61
61
 
62
62
  for proto in dl.protocols:
63
63
  if (can_rx_id := dl.get_can_receive_id(proto.short_name)) is not None:
@@ -103,12 +103,12 @@ def print_summary(odxdb: Database,
103
103
  data_object_properties, key=lambda x: (type(x).__name__, x.short_name)):
104
104
  rich.print(" " + str(dop.short_name).replace("\n", "\n "))
105
105
 
106
- if print_comparams and len(comparams) > 0:
106
+ if print_comparams and len(comparam_refs) > 0:
107
107
  rich.print("\n")
108
108
  rich.print(
109
109
  f"The communication parameters of the {dl.variant_type.value} '{dl.short_name}' are: "
110
110
  )
111
- for com_param in comparams:
111
+ for com_param in comparam_refs:
112
112
  rich.print(f" {com_param.short_name}: {com_param.value}")
113
113
 
114
114
 
@@ -0,0 +1,122 @@
1
+ # SPDX-License-Identifier: MIT
2
+ import warnings
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional
6
+ from xml.etree import ElementTree
7
+
8
+ from .description import Description
9
+ from .diagcomm import DiagComm
10
+ from .diagservice import DiagService
11
+ from .exceptions import OdxWarning, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
13
+ from .parameters.parameter import Parameter
14
+ from .snrefcontext import SnRefContext
15
+
16
+
17
+ class CommRelationValueType(Enum):
18
+ CURRENT = "CURRENT"
19
+ STORED = "STORED"
20
+ STATIC = "STATIC"
21
+ SUBSTITUTED = "SUBSTITUTED"
22
+
23
+
24
+ @dataclass
25
+ class CommRelation:
26
+ description: Optional[Description]
27
+ relation_type: str
28
+ diag_comm_ref: Optional[OdxLinkRef]
29
+ diag_comm_snref: Optional[str]
30
+ in_param_if_snref: Optional[str]
31
+ #in_param_if_snpathref: Optional[str] # TODO
32
+ out_param_if_snref: Optional[str]
33
+ #out_param_if_snpathref: Optional[str] # TODO
34
+ value_type_raw: Optional[CommRelationValueType]
35
+
36
+ @property
37
+ def diag_comm(self) -> DiagComm:
38
+ return self._diag_comm
39
+
40
+ @property
41
+ def in_param_if(self) -> Optional[Parameter]:
42
+ return self._in_param_if
43
+
44
+ @property
45
+ def out_param_if(self) -> Optional[Parameter]:
46
+ return self._out_param_if
47
+
48
+ @property
49
+ def value_type(self) -> CommRelationValueType:
50
+ if self.value_type_raw is None:
51
+ return CommRelationValueType.CURRENT
52
+
53
+ return self.value_type_raw
54
+
55
+ @staticmethod
56
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "CommRelation":
57
+ description = Description.from_et(et_element.find("DESC"), doc_frags)
58
+ relation_type = odxrequire(et_element.findtext("RELATION-TYPE"))
59
+
60
+ diag_comm_ref = OdxLinkRef.from_et(et_element.find("DIAG-COMM-REF"), doc_frags)
61
+ diag_comm_snref = None
62
+ if (diag_comm_snref_elem := et_element.find("DIAG-COMM-SNREF")) is not None:
63
+ diag_comm_snref = odxrequire(diag_comm_snref_elem.get("SHORT-NAME"))
64
+
65
+ in_param_if_snref = None
66
+ if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
67
+ in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
68
+
69
+ if et_element.find("IN-PARAM-IF-SNPATHREF") is not None:
70
+ warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
71
+
72
+ out_param_if_snref = None
73
+ if (out_param_if_snref_elem := et_element.find("OUT-PARAM-IF-SNREF")) is not None:
74
+ out_param_if_snref = odxrequire(out_param_if_snref_elem.get("SHORT-NAME"))
75
+
76
+ if et_element.find("OUT-PARAM-IF-SNPATHREF") is not None:
77
+ warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
78
+
79
+ value_type_raw = None
80
+ if (value_type_str := et_element.get("VALUE-TYPE")) is not None:
81
+ try:
82
+ value_type_raw = CommRelationValueType(value_type_str)
83
+ except ValueError:
84
+ odxraise(f"Encountered unknown comm relation value type '{value_type_str}'")
85
+
86
+ return CommRelation(
87
+ description=description,
88
+ relation_type=relation_type,
89
+ diag_comm_ref=diag_comm_ref,
90
+ diag_comm_snref=diag_comm_snref,
91
+ in_param_if_snref=in_param_if_snref,
92
+ out_param_if_snref=out_param_if_snref,
93
+ value_type_raw=value_type_raw)
94
+
95
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
96
+ return {}
97
+
98
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
99
+ if self.diag_comm_ref is not None:
100
+ self._diag_comm = odxlinks.resolve(self.diag_comm_ref, DiagComm)
101
+
102
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
103
+ diag_layer = odxrequire(context.diag_layer)
104
+
105
+ if self.diag_comm_snref is not None:
106
+ self._diag_comm = resolve_snref(self.diag_comm_snref, diag_layer.diag_comms, DiagComm)
107
+
108
+ service = self.diag_comm
109
+ if not isinstance(service, DiagService):
110
+ odxraise(f"DIAG-VARIABLE references non-service {type(service).__name__} "
111
+ f"diagnostic communication")
112
+
113
+ self._in_param_if = None
114
+ if self.in_param_if_snref is not None:
115
+ self._in_param_if = resolve_snref(self.in_param_if_snref,
116
+ odxrequire(service.request).parameters, Parameter)
117
+
118
+ self._out_param_if = None
119
+ if self.out_param_if_snref is not None:
120
+ self._out_param_if = resolve_snref(self.out_param_if_snref,
121
+ odxrequire(service.positive_responses[0]).parameters,
122
+ Parameter)
@@ -32,7 +32,7 @@ class ComparamInstance:
32
32
  spec_ref = odxrequire(OdxLinkRef.from_et(et_element, doc_frags))
33
33
 
34
34
  # ODX standard v2.0.0 defined only VALUE. ODX v2.0.1 decided
35
- # to break things and change it to choice between SIMPLE-VALUE
35
+ # to break things and change it to a choice between SIMPLE-VALUE
36
36
  # and COMPLEX-VALUE
37
37
  value: Union[str, List[Union[str, ComplexValue]]]
38
38
  if et_element.find("VALUE") is not None:
odxtools/comparamspec.py CHANGED
@@ -51,8 +51,7 @@ class ComparamSpec(IdentifiableElement):
51
51
 
52
52
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
53
53
  odxlinks: Dict[OdxLinkId, Any] = {}
54
- if self.odx_id is not None:
55
- odxlinks[self.odx_id] = self
54
+ odxlinks[self.odx_id] = self
56
55
 
57
56
  if self.admin_data is not None:
58
57
  odxlinks.update(self.admin_data._build_odxlinks())
@@ -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
  #####
@@ -175,9 +170,9 @@ class DiagLayer:
175
170
  self._diag_comms = NamedItemList(diag_comms)
176
171
 
177
172
  # filter the diag comms for services and single-ECU jobs
178
- services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
173
+ diag_services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
179
174
  single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
180
- self._services = NamedItemList(services)
175
+ self._diag_services = NamedItemList(diag_services)
181
176
  self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
182
177
 
183
178
  global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
@@ -294,7 +289,7 @@ class DiagLayer:
294
289
  # scheme, cf the docstring of
295
290
  # _compute_available_commmunication_parameters().
296
291
  #####
297
- self._comparams = NamedItemList(self._compute_available_commmunication_parameters())
292
+ self._comparam_refs = NamedItemList(self._compute_available_commmunication_parameters())
298
293
 
299
294
  #####
300
295
  # resolve all SNREFs. TODO: We allow SNREFS to objects that
@@ -412,12 +407,17 @@ class DiagLayer:
412
407
 
413
408
  @property
414
409
  def services(self) -> NamedItemList[DiagService]:
410
+ """This property is an alias for `.diag_services`"""
411
+ return self._diag_services
412
+
413
+ @property
414
+ def diag_services(self) -> NamedItemList[DiagService]:
415
415
  """All diagnostic services applicable to this DiagLayer
416
416
 
417
417
  This is a subset of all diagnostic communication
418
418
  primitives. All references are resolved in the list returned.
419
419
  """
420
- return self._services
420
+ return self._diag_services
421
421
 
422
422
  @property
423
423
  def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
@@ -712,20 +712,20 @@ class DiagLayer:
712
712
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
713
713
 
714
714
  # finally, handle the locally defined communication parameters
715
- for cp in self.diag_layer_raw.comparams:
715
+ for cp in self.diag_layer_raw.comparam_refs:
716
716
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
717
717
 
718
718
  return list(com_params_dict.values())
719
719
 
720
720
  @property
721
- def comparams(self) -> NamedItemList[ComparamInstance]:
721
+ def comparam_refs(self) -> NamedItemList[ComparamInstance]:
722
722
  """All communication parameters applicable to this DiagLayer
723
723
 
724
724
  Note that, although communication parameters use inheritance,
725
725
  it is *not* the "value inheritance" scheme used by e.g. DOPs,
726
726
  tables, state charts, ...
727
727
  """
728
- return self._comparams
728
+ return self._comparam_refs
729
729
 
730
730
  @cached_property
731
731
  def protocols(self) -> NamedItemList["DiagLayer"]:
@@ -762,7 +762,7 @@ class DiagLayer:
762
762
  protocol_name = protocol
763
763
 
764
764
  # determine the set of applicable communication parameters
765
- cps = [cp for cp in self.comparams if cp.short_name == cp_short_name]
765
+ cps = [cp for cp in self.comparam_refs if cp.short_name == cp_short_name]
766
766
  if protocol_name is not None:
767
767
  cps = [cp for cp in cps if cp.protocol_snref in (None, protocol_name)]
768
768
 
@@ -1200,16 +1200,19 @@ class DiagLayer:
1200
1200
  for service in candidate_services:
1201
1201
  try:
1202
1202
  decoded_messages.append(service.decode_message(message))
1203
- except DecodeError:
1203
+ except DecodeError as e:
1204
1204
  # check if the message can be decoded as a global
1205
1205
  # negative response for the service
1206
+ gnr_found = False
1206
1207
  for gnr in self.global_negative_responses:
1207
1208
  try:
1208
1209
  decoded_gnr = gnr.decode(message)
1210
+ gnr_found = True
1209
1211
  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}")
1212
+ odxraise(
1213
+ f"Expected the decoded value of a global "
1214
+ f"negative response to be a dictionary, "
1215
+ f"got {type(decoded_gnr)} for {self.short_name}", DecodeError)
1213
1216
 
1214
1217
  decoded_messages.append(
1215
1218
  Message(
@@ -1220,6 +1223,9 @@ class DiagLayer:
1220
1223
  except DecodeError:
1221
1224
  pass
1222
1225
 
1226
+ if not gnr_found:
1227
+ raise e
1228
+
1223
1229
  if len(decoded_messages) == 0:
1224
1230
  raise DecodeError(
1225
1231
  f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."