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
odxtools/diaglayerraw.py CHANGED
@@ -14,6 +14,8 @@ from .diagcomm import DiagComm
14
14
  from .diagdatadictionaryspec import DiagDataDictionarySpec
15
15
  from .diaglayertype import DiagLayerType
16
16
  from .diagservice import DiagService
17
+ from .diagvariable import DiagVariable
18
+ from .dyndefinedspec import DynDefinedSpec
17
19
  from .ecuvariantpattern import EcuVariantPattern
18
20
  from .element import IdentifiableElement
19
21
  from .exceptions import odxassert, odxraise, odxrequire
@@ -29,6 +31,7 @@ from .snrefcontext import SnRefContext
29
31
  from .specialdatagroup import SpecialDataGroup
30
32
  from .statechart import StateChart
31
33
  from .utils import dataclass_fields_asdict
34
+ from .variablegroup import VariableGroup
32
35
 
33
36
 
34
37
  @dataclass
@@ -44,7 +47,7 @@ class DiagLayerRaw(IdentifiableElement):
44
47
  company_datas: NamedItemList[CompanyData]
45
48
  functional_classes: NamedItemList[FunctionalClass]
46
49
  diag_data_dictionary_spec: Optional[DiagDataDictionarySpec]
47
- diag_comms: List[Union[OdxLinkRef, DiagComm]]
50
+ diag_comms_raw: List[Union[OdxLinkRef, DiagComm]]
48
51
  requests: NamedItemList[Request]
49
52
  positive_responses: NamedItemList[Response]
50
53
  negative_responses: NamedItemList[Response]
@@ -59,15 +62,36 @@ class DiagLayerRaw(IdentifiableElement):
59
62
  # these attributes are only defined for some kinds of diag layers!
60
63
  # TODO: make a proper class hierarchy!
61
64
  parent_refs: List[ParentRef]
62
- comparams: List[ComparamInstance]
65
+ comparam_refs: List[ComparamInstance]
63
66
  ecu_variant_patterns: List[EcuVariantPattern]
64
67
  comparam_spec_ref: Optional[OdxLinkRef]
65
68
  prot_stack_snref: Optional[str]
66
- # diag_variables: List[DiagVariable] # TODO
67
- # diag_variable_groups: List[DiagVariableGroup] # TODO
68
- # dyn_defined_spec: Optional[DynDefinedSpec] # TODO
69
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]]
70
+ variable_groups: NamedItemList[VariableGroup]
71
+ dyn_defined_spec: Optional[DynDefinedSpec]
69
72
  # base_variant_patterns: List[EcuVariantPattern] # TODO
70
73
 
74
+ @property
75
+ def diag_comms(self) -> NamedItemList[DiagComm]:
76
+ return self._diag_comms
77
+
78
+ @property
79
+ def diag_services(self) -> NamedItemList[DiagService]:
80
+ return self._diag_services
81
+
82
+ @property
83
+ def services(self) -> NamedItemList[DiagService]:
84
+ """This property is an alias for `.diag_services`"""
85
+ return self._diag_services
86
+
87
+ @property
88
+ def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
89
+ return self._single_ecu_jobs
90
+
91
+ @property
92
+ def diag_variables(self) -> NamedItemList[DiagVariable]:
93
+ return self._diag_variables
94
+
71
95
  @staticmethod
72
96
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayerRaw":
73
97
  try:
@@ -101,7 +125,7 @@ class DiagLayerRaw(IdentifiableElement):
101
125
  if (ddds_elem := et_element.find("DIAG-DATA-DICTIONARY-SPEC")) is not None:
102
126
  diag_data_dictionary_spec = DiagDataDictionarySpec.from_et(ddds_elem, doc_frags)
103
127
 
104
- diag_comms: List[Union[OdxLinkRef, DiagComm]] = []
128
+ diag_comms_raw: List[Union[OdxLinkRef, DiagComm]] = []
105
129
  if (dc_elems := et_element.find("DIAG-COMMS")) is not None:
106
130
  for dc_proxy_elem in dc_elems:
107
131
  dc: Union[OdxLinkRef, DiagComm]
@@ -113,7 +137,7 @@ class DiagLayerRaw(IdentifiableElement):
113
137
  odxassert(dc_proxy_elem.tag == "SINGLE-ECU-JOB")
114
138
  dc = SingleEcuJob.from_et(dc_proxy_elem, doc_frags)
115
139
 
116
- diag_comms.append(dc)
140
+ diag_comms_raw.append(dc)
117
141
 
118
142
  requests = NamedItemList([
119
143
  Request.from_et(rq_elem, doc_frags)
@@ -159,7 +183,7 @@ class DiagLayerRaw(IdentifiableElement):
159
183
  for pr_el in et_element.iterfind("PARENT-REFS/PARENT-REF")
160
184
  ]
161
185
 
162
- comparams = [
186
+ comparam_refs = [
163
187
  ComparamInstance.from_et(el, doc_frags)
164
188
  for el in et_element.iterfind("COMPARAM-REFS/COMPARAM-REF")
165
189
  ]
@@ -178,6 +202,29 @@ class DiagLayerRaw(IdentifiableElement):
178
202
  if (prot_stack_snref_elem := et_element.find("PROT-STACK-SNREF")) is not None:
179
203
  prot_stack_snref = odxrequire(prot_stack_snref_elem.get("SHORT-NAME"))
180
204
 
205
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
206
+ if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
207
+ for dv_proxy_elem in dv_elems:
208
+ dv_proxy: Union[OdxLinkRef, DiagVariable]
209
+ if dv_proxy_elem.tag == "DIAG-VARIABLE-REF":
210
+ dv_proxy = OdxLinkRef.from_et(dv_proxy_elem, doc_frags)
211
+ elif dv_proxy_elem.tag == "DIAG-VARIABLE":
212
+ dv_proxy = DiagVariable.from_et(dv_proxy_elem, doc_frags)
213
+ else:
214
+ odxraise("DIAG-VARIABLES tags may only contain "
215
+ "DIAG-VARIABLE and DIAG-VARIABLE-REF subtags")
216
+
217
+ diag_variables_raw.append(dv_proxy)
218
+
219
+ variable_groups = NamedItemList([
220
+ VariableGroup.from_et(vg_elem, doc_frags)
221
+ for vg_elem in et_element.iterfind("VARIABLE-GROUPS/VARIABLE-GROUP")
222
+ ])
223
+
224
+ dyn_defined_spec = None
225
+ if (dds_elem := et_element.find("DYN-DEFINED-SPEC")) is not None:
226
+ dyn_defined_spec = DynDefinedSpec.from_et(dds_elem, doc_frags)
227
+
181
228
  # Create DiagLayer
182
229
  return DiagLayerRaw(
183
230
  variant_type=variant_type,
@@ -185,7 +232,7 @@ class DiagLayerRaw(IdentifiableElement):
185
232
  company_datas=NamedItemList(company_datas),
186
233
  functional_classes=NamedItemList(functional_classes),
187
234
  diag_data_dictionary_spec=diag_data_dictionary_spec,
188
- diag_comms=diag_comms,
235
+ diag_comms_raw=diag_comms_raw,
189
236
  requests=requests,
190
237
  positive_responses=positive_responses,
191
238
  negative_responses=negative_responses,
@@ -195,10 +242,13 @@ class DiagLayerRaw(IdentifiableElement):
195
242
  additional_audiences=NamedItemList(additional_audiences),
196
243
  sdgs=sdgs,
197
244
  parent_refs=parent_refs,
198
- comparams=comparams,
245
+ comparam_refs=comparam_refs,
199
246
  ecu_variant_patterns=ecu_variant_patterns,
200
247
  comparam_spec_ref=comparam_spec_ref,
201
248
  prot_stack_snref=prot_stack_snref,
249
+ diag_variables_raw=diag_variables_raw,
250
+ variable_groups=variable_groups,
251
+ dyn_defined_spec=dyn_defined_spec,
202
252
  **kwargs)
203
253
 
204
254
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -214,10 +264,10 @@ class DiagLayerRaw(IdentifiableElement):
214
264
  odxlinks.update(company_data._build_odxlinks())
215
265
  for functional_class in self.functional_classes:
216
266
  odxlinks.update(functional_class._build_odxlinks())
217
- for diag_comm in self.diag_comms:
218
- if isinstance(diag_comm, OdxLinkRef):
267
+ for dc_proxy in self.diag_comms_raw:
268
+ if isinstance(dc_proxy, OdxLinkRef):
219
269
  continue
220
- odxlinks.update(diag_comm._build_odxlinks())
270
+ odxlinks.update(dc_proxy._build_odxlinks())
221
271
  for request in self.requests:
222
272
  odxlinks.update(request._build_odxlinks())
223
273
  for positive_response in self.positive_responses:
@@ -234,8 +284,13 @@ class DiagLayerRaw(IdentifiableElement):
234
284
  odxlinks.update(sdg._build_odxlinks())
235
285
  for parent_ref in self.parent_refs:
236
286
  odxlinks.update(parent_ref._build_odxlinks())
237
- for comparam in self.comparams:
287
+ for comparam in self.comparam_refs:
238
288
  odxlinks.update(comparam._build_odxlinks())
289
+ for dv_proxy in self.diag_variables_raw:
290
+ if not isinstance(dv_proxy, OdxLinkRef):
291
+ odxlinks.update(dv_proxy._build_odxlinks())
292
+ if self.dyn_defined_spec is not None:
293
+ odxlinks.update(self.dyn_defined_spec._build_odxlinks())
239
294
 
240
295
  return odxlinks
241
296
 
@@ -258,10 +313,28 @@ class DiagLayerRaw(IdentifiableElement):
258
313
  company_data._resolve_odxlinks(odxlinks)
259
314
  for functional_class in self.functional_classes:
260
315
  functional_class._resolve_odxlinks(odxlinks)
261
- for diag_comm in self.diag_comms:
262
- if isinstance(diag_comm, OdxLinkRef):
263
- continue
264
- diag_comm._resolve_odxlinks(odxlinks)
316
+
317
+ # resolve references to diagnostic communication objects and
318
+ # separate them into services and single-ecu jobs
319
+ self._diag_comms = NamedItemList[DiagComm]()
320
+ self._diag_services = NamedItemList[DiagService]()
321
+ self._single_ecu_jobs = NamedItemList[SingleEcuJob]()
322
+ for dc_proxy in self.diag_comms_raw:
323
+ if isinstance(dc_proxy, OdxLinkRef):
324
+ dc = odxlinks.resolve(dc_proxy, DiagComm)
325
+ else:
326
+ dc = dc_proxy
327
+ dc._resolve_odxlinks(odxlinks)
328
+
329
+ self._diag_comms.append(dc)
330
+
331
+ if isinstance(dc, DiagService):
332
+ self._diag_services.append(dc)
333
+ elif isinstance(dc, SingleEcuJob):
334
+ self._single_ecu_jobs.append(dc)
335
+ else:
336
+ odxraise()
337
+
265
338
  for request in self.requests:
266
339
  request._resolve_odxlinks(odxlinks)
267
340
  for positive_response in self.positive_responses:
@@ -278,9 +351,22 @@ class DiagLayerRaw(IdentifiableElement):
278
351
  sdg._resolve_odxlinks(odxlinks)
279
352
  for parent_ref in self.parent_refs:
280
353
  parent_ref._resolve_odxlinks(odxlinks)
281
- for comparam in self.comparams:
354
+ for comparam in self.comparam_refs:
282
355
  comparam._resolve_odxlinks(odxlinks)
283
356
 
357
+ self._diag_variables: NamedItemList[DiagVariable] = NamedItemList()
358
+ for dv_proxy in self.diag_variables_raw:
359
+ if isinstance(dv_proxy, OdxLinkRef):
360
+ dv = odxlinks.resolve(dv_proxy, DiagVariable)
361
+ else:
362
+ dv_proxy._resolve_odxlinks(odxlinks)
363
+ dv = dv_proxy
364
+
365
+ self._diag_variables.append(dv)
366
+
367
+ if self.dyn_defined_spec is not None:
368
+ self.dyn_defined_spec._resolve_odxlinks(odxlinks)
369
+
284
370
  def _resolve_snrefs(self, context: SnRefContext) -> None:
285
371
  self._prot_stack: Optional[ProtStack] = None
286
372
  if self.prot_stack_snref is not None:
@@ -299,10 +385,10 @@ class DiagLayerRaw(IdentifiableElement):
299
385
  company_data._resolve_snrefs(context)
300
386
  for functional_class in self.functional_classes:
301
387
  functional_class._resolve_snrefs(context)
302
- for diag_comm in self.diag_comms:
303
- if isinstance(diag_comm, OdxLinkRef):
388
+ for dc_proxy in self.diag_comms_raw:
389
+ if isinstance(dc_proxy, OdxLinkRef):
304
390
  continue
305
- diag_comm._resolve_snrefs(context)
391
+ dc_proxy._resolve_snrefs(context)
306
392
  for request in self.requests:
307
393
  request._resolve_snrefs(context)
308
394
  for positive_response in self.positive_responses:
@@ -319,9 +405,16 @@ class DiagLayerRaw(IdentifiableElement):
319
405
  sdg._resolve_snrefs(context)
320
406
  for parent_ref in self.parent_refs:
321
407
  parent_ref._resolve_snrefs(context)
322
- for comparam in self.comparams:
408
+ for comparam in self.comparam_refs:
323
409
  comparam._resolve_snrefs(context)
324
410
 
411
+ for dv_proxy in self.diag_variables_raw:
412
+ if not isinstance(dv_proxy, OdxLinkRef):
413
+ dv_proxy._resolve_snrefs(context)
414
+
415
+ if self.dyn_defined_spec is not None:
416
+ self.dyn_defined_spec._resolve_snrefs(context)
417
+
325
418
  @property
326
419
  def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
327
420
  return self._comparam_spec
@@ -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
@@ -0,0 +1,104 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .admindata import AdminData
7
+ from .commrelation import CommRelation
8
+ from .element import IdentifiableElement
9
+ from .exceptions import odxrequire
10
+ from .nameditemlist import NamedItemList
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
+ from .odxtypes import odxstr_to_bool
13
+ from .snrefcontext import SnRefContext
14
+ from .specialdatagroup import SpecialDataGroup
15
+ from .swvariable import SwVariable
16
+ from .utils import dataclass_fields_asdict
17
+ from .variablegroup import VariableGroup
18
+
19
+
20
+ @dataclass
21
+ class DiagVariable(IdentifiableElement):
22
+ """Representation of a diagnostic variable
23
+ """
24
+
25
+ admin_data: Optional[AdminData]
26
+ variable_group_ref: OdxLinkRef
27
+ sw_variables: List[SwVariable]
28
+ comm_relations: List[CommRelation]
29
+ #snref_to_tablerow: Optional[SnrefToTableRow] # TODO
30
+ sdgs: List[SpecialDataGroup]
31
+ is_read_before_write_raw: Optional[bool]
32
+
33
+ @property
34
+ def variable_group(self) -> VariableGroup:
35
+ return self._variable_group
36
+
37
+ @property
38
+ def is_read_before_write(self) -> bool:
39
+ return self.is_read_before_write_raw is True
40
+
41
+ @staticmethod
42
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagVariable":
43
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
44
+
45
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
46
+ variable_group_ref = odxrequire(
47
+ OdxLinkRef.from_et(et_element.find("VARIABLE-GROUP-REF"), doc_frags))
48
+ sw_variables = NamedItemList([
49
+ SwVariable.from_et(swv_elem, doc_frags)
50
+ for swv_elem in et_element.iterfind("SW-VARIABLES/SW-VARIABLE")
51
+ ])
52
+ comm_relations = [
53
+ CommRelation.from_et(cr_elem, doc_frags)
54
+ for cr_elem in et_element.iterfind("COMM-RELATIONS/COMM-RELATION")
55
+ ]
56
+ sdgs = [
57
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
58
+ ]
59
+ is_read_before_write_raw = odxstr_to_bool(et_element.get("IS-READ-BEFORE-WRITE"))
60
+
61
+ return DiagVariable(
62
+ admin_data=admin_data,
63
+ variable_group_ref=variable_group_ref,
64
+ sw_variables=sw_variables,
65
+ comm_relations=comm_relations,
66
+ sdgs=sdgs,
67
+ is_read_before_write_raw=is_read_before_write_raw,
68
+ **kwargs)
69
+
70
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
71
+ result = {self.odx_id: self}
72
+
73
+ if self.admin_data is not None:
74
+ result.update(self.admin_data._build_odxlinks())
75
+
76
+ for sdg in self.sdgs:
77
+ result.update(sdg._build_odxlinks())
78
+
79
+ for cr in self.comm_relations:
80
+ result.update(cr._build_odxlinks())
81
+
82
+ return result
83
+
84
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
85
+ self._variable_group = odxlinks.resolve(self.variable_group_ref, VariableGroup)
86
+
87
+ if self.admin_data is not None:
88
+ self.admin_data._resolve_odxlinks(odxlinks)
89
+
90
+ for sdg in self.sdgs:
91
+ sdg._resolve_odxlinks(odxlinks)
92
+
93
+ for cr in self.comm_relations:
94
+ cr._resolve_odxlinks(odxlinks)
95
+
96
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
97
+ if self.admin_data is not None:
98
+ self.admin_data._resolve_snrefs(context)
99
+
100
+ for sdg in self.sdgs:
101
+ sdg._resolve_snrefs(context)
102
+
103
+ for cr in self.comm_relations:
104
+ cr._resolve_snrefs(context)
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]: