odxtools 7.1.1__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.
Files changed (135) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +3 -5
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +19 -23
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +3 -2
  9. odxtools/cli/compare.py +1 -1
  10. odxtools/companydata.py +5 -7
  11. odxtools/companydocinfo.py +7 -8
  12. odxtools/companyrevisioninfo.py +3 -5
  13. odxtools/companyspecificinfo.py +8 -9
  14. odxtools/comparam.py +4 -6
  15. odxtools/comparaminstance.py +6 -8
  16. odxtools/comparamspec.py +14 -13
  17. odxtools/comparamsubset.py +17 -16
  18. odxtools/complexcomparam.py +5 -7
  19. odxtools/compumethods/compuconst.py +31 -0
  20. odxtools/compumethods/compudefaultvalue.py +27 -0
  21. odxtools/compumethods/compuinternaltophys.py +39 -0
  22. odxtools/compumethods/compuinversevalue.py +7 -0
  23. odxtools/compumethods/compumethod.py +67 -12
  24. odxtools/compumethods/compuphystointernal.py +39 -0
  25. odxtools/compumethods/compuscale.py +15 -26
  26. odxtools/compumethods/createanycompumethod.py +14 -160
  27. odxtools/compumethods/identicalcompumethod.py +31 -6
  28. odxtools/compumethods/linearcompumethod.py +69 -189
  29. odxtools/compumethods/linearsegment.py +191 -0
  30. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  31. odxtools/compumethods/tabintpcompumethod.py +119 -99
  32. odxtools/compumethods/texttablecompumethod.py +107 -43
  33. odxtools/createanydiagcodedtype.py +10 -67
  34. odxtools/database.py +84 -72
  35. odxtools/dataobjectproperty.py +10 -19
  36. odxtools/decodestate.py +8 -2
  37. odxtools/description.py +47 -0
  38. odxtools/determinenumberofitems.py +4 -5
  39. odxtools/diagcodedtype.py +29 -12
  40. odxtools/diagcomm.py +10 -6
  41. odxtools/diagdatadictionaryspec.py +20 -21
  42. odxtools/diaglayer.py +39 -9
  43. odxtools/diaglayercontainer.py +17 -11
  44. odxtools/diaglayerraw.py +20 -21
  45. odxtools/diagnostictroublecode.py +8 -9
  46. odxtools/diagservice.py +42 -27
  47. odxtools/docrevision.py +5 -7
  48. odxtools/dopbase.py +7 -8
  49. odxtools/dtcdop.py +44 -22
  50. odxtools/dynamicendmarkerfield.py +22 -9
  51. odxtools/dynamiclengthfield.py +5 -11
  52. odxtools/element.py +4 -3
  53. odxtools/encodestate.py +14 -2
  54. odxtools/endofpdufield.py +0 -2
  55. odxtools/environmentdatadescription.py +137 -19
  56. odxtools/exceptions.py +11 -2
  57. odxtools/field.py +9 -9
  58. odxtools/functionalclass.py +3 -5
  59. odxtools/inputparam.py +3 -5
  60. odxtools/leadinglengthinfotype.py +15 -2
  61. odxtools/loadfile.py +64 -0
  62. odxtools/minmaxlengthtype.py +20 -2
  63. odxtools/modification.py +3 -5
  64. odxtools/multiplexer.py +98 -69
  65. odxtools/multiplexercase.py +10 -11
  66. odxtools/multiplexerdefaultcase.py +11 -12
  67. odxtools/multiplexerswitchkey.py +4 -5
  68. odxtools/negoutputparam.py +3 -5
  69. odxtools/odxlink.py +12 -26
  70. odxtools/odxtypes.py +1 -1
  71. odxtools/outputparam.py +3 -5
  72. odxtools/parameterinfo.py +5 -5
  73. odxtools/parameters/codedconstparameter.py +2 -14
  74. odxtools/parameters/lengthkeyparameter.py +3 -17
  75. odxtools/parameters/nrcconstparameter.py +29 -50
  76. odxtools/parameters/parameter.py +22 -22
  77. odxtools/parameters/parameterwithdop.py +6 -8
  78. odxtools/parameters/physicalconstantparameter.py +5 -8
  79. odxtools/parameters/reservedparameter.py +4 -3
  80. odxtools/parameters/systemparameter.py +1 -1
  81. odxtools/parameters/tablekeyparameter.py +6 -9
  82. odxtools/parameters/tablestructparameter.py +6 -8
  83. odxtools/parameters/valueparameter.py +5 -8
  84. odxtools/paramlengthinfotype.py +19 -6
  85. odxtools/parentref.py +15 -1
  86. odxtools/physicaldimension.py +3 -5
  87. odxtools/progcode.py +18 -7
  88. odxtools/protstack.py +3 -5
  89. odxtools/relateddoc.py +7 -9
  90. odxtools/request.py +8 -0
  91. odxtools/response.py +8 -0
  92. odxtools/scaleconstr.py +3 -3
  93. odxtools/singleecujob.py +12 -10
  94. odxtools/snrefcontext.py +29 -0
  95. odxtools/specialdata.py +3 -5
  96. odxtools/specialdatagroup.py +5 -7
  97. odxtools/specialdatagroupcaption.py +3 -6
  98. odxtools/standardlengthtype.py +27 -2
  99. odxtools/state.py +3 -5
  100. odxtools/statechart.py +9 -11
  101. odxtools/statetransition.py +4 -9
  102. odxtools/staticfield.py +4 -8
  103. odxtools/table.py +7 -8
  104. odxtools/tablerow.py +7 -6
  105. odxtools/teammember.py +3 -5
  106. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +2 -5
  107. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +2 -5
  108. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -5
  109. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  110. odxtools/templates/macros/printCompuMethod.xml.jinja2 +153 -0
  111. odxtools/templates/macros/printDOP.xml.jinja2 +10 -132
  112. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  113. odxtools/templates/macros/printElementId.xml.jinja2 +3 -3
  114. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  115. odxtools/templates/macros/printTable.xml.jinja2 +2 -3
  116. odxtools/unit.py +3 -5
  117. odxtools/unitgroup.py +3 -5
  118. odxtools/unitspec.py +9 -10
  119. odxtools/utils.py +1 -26
  120. odxtools/version.py +2 -2
  121. odxtools/{write_pdx_file.py → writepdxfile.py} +19 -10
  122. odxtools/xdoc.py +3 -5
  123. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/METADATA +1 -1
  124. odxtools-7.3.0.dist-info/RECORD +192 -0
  125. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/WHEEL +1 -1
  126. odxtools/createcompanydatas.py +0 -17
  127. odxtools/createsdgs.py +0 -19
  128. odxtools/load_file.py +0 -13
  129. odxtools/load_odx_d_file.py +0 -6
  130. odxtools/load_pdx_file.py +0 -8
  131. odxtools-7.1.1.dist-info/RECORD +0 -186
  132. /odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +0 -0
  133. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/LICENSE +0 -0
  134. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/entry_points.txt +0 -0
  135. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/top_level.txt +0 -0
odxtools/diaglayer.py CHANGED
@@ -1,11 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import re
3
3
  import warnings
4
- from copy import copy
4
+ from copy import copy, deepcopy
5
5
  from dataclasses import dataclass
6
6
  from functools import cached_property
7
7
  from itertools import chain
8
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union, cast
8
+ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar,
9
+ Union, cast)
9
10
  from xml.etree import ElementTree
10
11
 
11
12
  from deprecation import deprecated
@@ -16,6 +17,7 @@ from .companydata import CompanyData
16
17
  from .comparaminstance import ComparamInstance
17
18
  from .comparamspec import ComparamSpec
18
19
  from .comparamsubset import ComparamSubset
20
+ from .description import Description
19
21
  from .diagcomm import DiagComm
20
22
  from .diagdatadictionaryspec import DiagDataDictionarySpec
21
23
  from .diaglayerraw import DiagLayerRaw
@@ -33,12 +35,16 @@ from .request import Request
33
35
  from .response import Response
34
36
  from .servicebinner import ServiceBinner
35
37
  from .singleecujob import SingleEcuJob
38
+ from .snrefcontext import SnRefContext
36
39
  from .specialdatagroup import SpecialDataGroup
37
40
  from .statechart import StateChart
38
41
  from .table import Table
39
42
  from .unitgroup import UnitGroup
40
43
  from .unitspec import UnitSpec
41
44
 
45
+ if TYPE_CHECKING:
46
+ from .database import Database
47
+
42
48
  TNamed = TypeVar("TNamed", bound=OdxNamed)
43
49
 
44
50
  PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
@@ -124,7 +130,22 @@ class DiagLayer:
124
130
 
125
131
  self.diag_layer_raw._resolve_odxlinks(odxlinks)
126
132
 
127
- def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
133
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
134
+ """Create a deep copy of the diagnostic layer
135
+
136
+ Note that the copied diagnostic layer is not fully
137
+ initialized, so `_finalize_init()` should to be called on it
138
+ before it can be used normally.
139
+ """
140
+ cls = self.__class__
141
+ result = cls.__new__(cls)
142
+ memo[id(self)] = result
143
+
144
+ result.diag_layer_raw = deepcopy(self.diag_layer_raw, memo)
145
+
146
+ return result
147
+
148
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
128
149
  """This method deals with everything inheritance related and
129
150
  -- after the final set of objects covered by the diagnostic
130
151
  layer is determined -- resolves any short name references in
@@ -276,7 +297,10 @@ class DiagLayer:
276
297
  # by the spec (So far, I haven't found any definitive
277
298
  # statement...)
278
299
  #####
279
- self.diag_layer_raw._resolve_snrefs(self)
300
+ context = SnRefContext(database=database)
301
+ context.diag_layer = self
302
+ self.diag_layer_raw._resolve_snrefs(context)
303
+ context.diag_layer = None
280
304
 
281
305
  #####
282
306
  # <convenience functionality>
@@ -309,7 +333,7 @@ class DiagLayer:
309
333
  return self.diag_layer_raw.long_name
310
334
 
311
335
  @property
312
- def description(self) -> Optional[str]:
336
+ def description(self) -> Optional[Description]:
313
337
  return self.diag_layer_raw.description
314
338
 
315
339
  @property
@@ -1171,16 +1195,19 @@ class DiagLayer:
1171
1195
  for service in candidate_services:
1172
1196
  try:
1173
1197
  decoded_messages.append(service.decode_message(message))
1174
- except DecodeError:
1198
+ except DecodeError as e:
1175
1199
  # check if the message can be decoded as a global
1176
1200
  # negative response for the service
1201
+ gnr_found = False
1177
1202
  for gnr in self.global_negative_responses:
1178
1203
  try:
1179
1204
  decoded_gnr = gnr.decode(message)
1205
+ gnr_found = True
1180
1206
  if not isinstance(decoded_gnr, dict):
1181
- raise DecodeError(f"Expected the decoded value of a global "
1182
- f"negative response to be a dictionary, "
1183
- 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)
1184
1211
 
1185
1212
  decoded_messages.append(
1186
1213
  Message(
@@ -1191,6 +1218,9 @@ class DiagLayer:
1191
1218
  except DecodeError:
1192
1219
  pass
1193
1220
 
1221
+ if not gnr_found:
1222
+ raise e
1223
+
1194
1224
  if len(decoded_messages) == 0:
1195
1225
  raise DecodeError(
1196
1226
  f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
@@ -1,13 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from itertools import chain
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
8
8
  from .companydata import CompanyData
9
- from .createcompanydatas import create_company_datas_from_et
10
- from .createsdgs import create_sdgs_from_et
11
9
  from .diaglayer import DiagLayer
12
10
  from .element import IdentifiableElement
13
11
  from .exceptions import odxrequire
@@ -16,6 +14,9 @@ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
14
  from .specialdatagroup import SpecialDataGroup
17
15
  from .utils import dataclass_fields_asdict
18
16
 
17
+ if TYPE_CHECKING:
18
+ from .database import Database
19
+
19
20
 
20
21
  @dataclass
21
22
  class DiagLayerContainer(IdentifiableElement):
@@ -48,7 +49,10 @@ class DiagLayerContainer(IdentifiableElement):
48
49
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
49
50
 
50
51
  admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
51
- company_datas = create_company_datas_from_et(et_element.find("COMPANY-DATAS"), doc_frags)
52
+ company_datas = NamedItemList([
53
+ CompanyData.from_et(cde, doc_frags)
54
+ for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
55
+ ])
52
56
  ecu_shared_datas = NamedItemList([
53
57
  DiagLayer.from_et(dl_element, doc_frags)
54
58
  for dl_element in et_element.iterfind("ECU-SHARED-DATAS/ECU-SHARED-DATA")
@@ -69,7 +73,9 @@ class DiagLayerContainer(IdentifiableElement):
69
73
  DiagLayer.from_et(dl_element, doc_frags)
70
74
  for dl_element in et_element.iterfind("ECU-VARIANTS/ECU-VARIANT")
71
75
  ])
72
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
76
+ sdgs = [
77
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
78
+ ]
73
79
 
74
80
  return DiagLayerContainer(
75
81
  admin_data=admin_data,
@@ -124,17 +130,17 @@ class DiagLayerContainer(IdentifiableElement):
124
130
  for ecu_variant in self.ecu_variants:
125
131
  ecu_variant._resolve_odxlinks(odxlinks)
126
132
 
127
- def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
133
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
128
134
  for ecu_shared_data in self.ecu_shared_datas:
129
- ecu_shared_data._finalize_init(odxlinks)
135
+ ecu_shared_data._finalize_init(database, odxlinks)
130
136
  for protocol in self.protocols:
131
- protocol._finalize_init(odxlinks)
137
+ protocol._finalize_init(database, odxlinks)
132
138
  for functional_group in self.functional_groups:
133
- functional_group._finalize_init(odxlinks)
139
+ functional_group._finalize_init(database, odxlinks)
134
140
  for base_variant in self.base_variants:
135
- base_variant._finalize_init(odxlinks)
141
+ base_variant._finalize_init(database, odxlinks)
136
142
  for ecu_variant in self.ecu_variants:
137
- ecu_variant._finalize_init(odxlinks)
143
+ ecu_variant._finalize_init(database, odxlinks)
138
144
 
139
145
  @property
140
146
  def diag_layers(self) -> NamedItemList[DiagLayer]:
odxtools/diaglayerraw.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from copy import copy
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
4
+ from typing import Any, Dict, List, Optional, Union, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .additionalaudience import AdditionalAudience
@@ -10,7 +10,6 @@ from .companydata import CompanyData
10
10
  from .comparaminstance import ComparamInstance
11
11
  from .comparamspec import ComparamSpec
12
12
  from .comparamsubset import ComparamSubset
13
- from .createsdgs import create_sdgs_from_et
14
13
  from .diagcomm import DiagComm
15
14
  from .diagdatadictionaryspec import DiagDataDictionarySpec
16
15
  from .diaglayertype import DiagLayerType
@@ -26,13 +25,11 @@ from .protstack import ProtStack
26
25
  from .request import Request
27
26
  from .response import Response
28
27
  from .singleecujob import SingleEcuJob
28
+ from .snrefcontext import SnRefContext
29
29
  from .specialdatagroup import SpecialDataGroup
30
30
  from .statechart import StateChart
31
31
  from .utils import dataclass_fields_asdict
32
32
 
33
- if TYPE_CHECKING:
34
- from .diaglayer import DiagLayer
35
-
36
33
 
37
34
  @dataclass
38
35
  class DiagLayerRaw(IdentifiableElement):
@@ -153,7 +150,9 @@ class DiagLayerRaw(IdentifiableElement):
153
150
  for el in et_element.iterfind("ADDITIONAL-AUDIENCES/ADDITIONAL-AUDIENCE")
154
151
  ]
155
152
 
156
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
153
+ sdgs = [
154
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
155
+ ]
157
156
 
158
157
  parent_refs = [
159
158
  ParentRef.from_et(pr_el, doc_frags)
@@ -282,7 +281,7 @@ class DiagLayerRaw(IdentifiableElement):
282
281
  for comparam in self.comparams:
283
282
  comparam._resolve_odxlinks(odxlinks)
284
283
 
285
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
284
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
286
285
  self._prot_stack: Optional[ProtStack] = None
287
286
  if self.prot_stack_snref is not None:
288
287
  cp_spec = self.comparam_spec
@@ -292,36 +291,36 @@ class DiagLayerRaw(IdentifiableElement):
292
291
 
293
292
  # do short-name reference resolution
294
293
  if self.admin_data is not None:
295
- self.admin_data._resolve_snrefs(diag_layer)
294
+ self.admin_data._resolve_snrefs(context)
296
295
  if self.diag_data_dictionary_spec is not None:
297
- self.diag_data_dictionary_spec._resolve_snrefs(diag_layer)
296
+ self.diag_data_dictionary_spec._resolve_snrefs(context)
298
297
 
299
298
  for company_data in self.company_datas:
300
- company_data._resolve_snrefs(diag_layer)
299
+ company_data._resolve_snrefs(context)
301
300
  for functional_class in self.functional_classes:
302
- functional_class._resolve_snrefs(diag_layer)
301
+ functional_class._resolve_snrefs(context)
303
302
  for diag_comm in self.diag_comms:
304
303
  if isinstance(diag_comm, OdxLinkRef):
305
304
  continue
306
- diag_comm._resolve_snrefs(diag_layer)
305
+ diag_comm._resolve_snrefs(context)
307
306
  for request in self.requests:
308
- request._resolve_snrefs(diag_layer)
307
+ request._resolve_snrefs(context)
309
308
  for positive_response in self.positive_responses:
310
- positive_response._resolve_snrefs(diag_layer)
309
+ positive_response._resolve_snrefs(context)
311
310
  for negative_response in self.negative_responses:
312
- negative_response._resolve_snrefs(diag_layer)
311
+ negative_response._resolve_snrefs(context)
313
312
  for global_negative_response in self.global_negative_responses:
314
- global_negative_response._resolve_snrefs(diag_layer)
313
+ global_negative_response._resolve_snrefs(context)
315
314
  for state_chart in self.state_charts:
316
- state_chart._resolve_snrefs(diag_layer)
315
+ state_chart._resolve_snrefs(context)
317
316
  for additional_audience in self.additional_audiences:
318
- additional_audience._resolve_snrefs(diag_layer)
317
+ additional_audience._resolve_snrefs(context)
319
318
  for sdg in self.sdgs:
320
- sdg._resolve_snrefs(diag_layer)
319
+ sdg._resolve_snrefs(context)
321
320
  for parent_ref in self.parent_refs:
322
- parent_ref._resolve_snrefs(diag_layer)
321
+ parent_ref._resolve_snrefs(context)
323
322
  for comparam in self.comparams:
324
- comparam._resolve_snrefs(diag_layer)
323
+ comparam._resolve_snrefs(context)
325
324
 
326
325
  @property
327
326
  def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
@@ -1,26 +1,23 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .createsdgs import create_sdgs_from_et
7
6
  from .element import IdentifiableElement
8
7
  from .exceptions import odxrequire
9
8
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
10
9
  from .odxtypes import odxstr_to_bool
10
+ from .snrefcontext import SnRefContext
11
11
  from .specialdatagroup import SpecialDataGroup
12
12
  from .utils import dataclass_fields_asdict
13
13
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
14
 
18
15
  @dataclass
19
16
  class DiagnosticTroubleCode(IdentifiableElement):
20
17
  trouble_code: int
21
18
  text: Optional[str]
22
19
  display_trouble_code: Optional[str]
23
- level: Union[int, None]
20
+ level: Optional[int]
24
21
  is_temporary_raw: Optional[bool]
25
22
  sdgs: List[SpecialDataGroup]
26
23
 
@@ -43,7 +40,9 @@ class DiagnosticTroubleCode(IdentifiableElement):
43
40
  level = None
44
41
 
45
42
  is_temporary_raw = odxstr_to_bool(et_element.get("IS-TEMPORARY"))
46
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
43
+ sdgs = [
44
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
45
+ ]
47
46
 
48
47
  return DiagnosticTroubleCode(
49
48
  trouble_code=int(odxrequire(et_element.findtext("TROUBLE-CODE"))),
@@ -69,6 +68,6 @@ class DiagnosticTroubleCode(IdentifiableElement):
69
68
  for sdg in self.sdgs:
70
69
  sdg._resolve_odxlinks(odxlinks)
71
70
 
72
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
71
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
73
72
  for sdg in self.sdgs:
74
- sdg._resolve_snrefs(diag_layer)
73
+ sdg._resolve_snrefs(context)
odxtools/diagservice.py CHANGED
@@ -1,12 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
4
+ from typing import Any, Dict, List, Optional, Union, cast
5
5
  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
@@ -14,11 +14,9 @@ from .odxtypes import ParameterValue, odxstr_to_bool
14
14
  from .parameters.parameter import Parameter
15
15
  from .request import Request
16
16
  from .response import Response
17
+ from .snrefcontext import SnRefContext
17
18
  from .utils import dataclass_fields_asdict
18
19
 
19
- if TYPE_CHECKING:
20
- from .diaglayer import DiagLayer
21
-
22
20
 
23
21
  class Addressing(Enum):
24
22
  FUNCTIONAL = "FUNCTIONAL"
@@ -44,7 +42,7 @@ class DiagService(DiagComm):
44
42
  pos_response_refs: List[OdxLinkRef]
45
43
  neg_response_refs: List[OdxLinkRef]
46
44
 
47
- # TODO: pos_response_suppressable: Optional[PosResponseSuppressable]
45
+ # TODO: pos_response_suppressable: Optional[PosResponseSuppressable] # (sic!)
48
46
 
49
47
  is_cyclic_raw: Optional[bool]
50
48
  is_multiple_raw: Optional[bool]
@@ -175,16 +173,21 @@ class DiagService(DiagComm):
175
173
  self._negative_responses = NamedItemList[Response](
176
174
  [odxlinks.resolve(x, Response) for x in self.neg_response_refs])
177
175
 
178
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
179
- super()._resolve_snrefs(diag_layer)
176
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
177
+ context.diag_service = self
178
+
179
+ super()._resolve_snrefs(context)
180
180
 
181
181
  for cpr in self.comparam_refs:
182
- cpr._resolve_snrefs(diag_layer)
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
 
189
+ context.diag_service = None
190
+
188
191
  def decode_message(self, raw_message: bytes) -> Message:
189
192
  request_prefix = b''
190
193
  candidate_coding_objects: List[Union[Request, Response]] = [
@@ -200,24 +203,36 @@ class DiagService(DiagComm):
200
203
  if len(raw_message) >= len(prefix) and prefix == raw_message[:len(prefix)]:
201
204
  coding_objects.append(candidate_coding_object)
202
205
 
203
- if len(coding_objects) != 1:
204
- raise DecodeError(
205
- f"The service {self.short_name} cannot decode the message {raw_message.hex()}")
206
- coding_object = coding_objects[0]
207
- param_dict = coding_object.decode(raw_message)
208
- if not isinstance(param_dict, dict):
209
- # if this happens, this is probably due to a bug in
210
- # coding_object.decode()
211
- raise RuntimeError(f"Expected a set of decoded parameters, got {type(param_dict)}")
212
- return Message(
213
- coded_message=raw_message,
214
- service=self,
215
- coding_object=coding_object,
216
- 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]
217
232
 
218
233
  def encode_request(self, **kwargs: ParameterValue) -> bytes:
219
- """
220
- 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.
221
236
  """
222
237
  # make sure that all parameters which are required for
223
238
  # encoding are specified (parameters which have a default are
odxtools/docrevision.py CHANGED
@@ -1,17 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .companyrevisioninfo import CompanyRevisionInfo
7
7
  from .exceptions import odxrequire
8
8
  from .modification import Modification
9
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from .snrefcontext import SnRefContext
10
11
  from .teammember import TeamMember
11
12
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
13
 
16
14
  @dataclass
17
15
  class DocRevision:
@@ -75,9 +73,9 @@ class DocRevision:
75
73
  for mod in self.modifications:
76
74
  mod._resolve_odxlinks(odxlinks)
77
75
 
78
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
76
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
79
77
  for cri in self.company_revision_infos:
80
- cri._resolve_snrefs(diag_layer)
78
+ cri._resolve_snrefs(context)
81
79
 
82
80
  for mod in self.modifications:
83
- mod._resolve_snrefs(diag_layer)
81
+ mod._resolve_snrefs(context)
odxtools/dopbase.py CHANGED
@@ -1,21 +1,18 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .admindata import AdminData
7
- from .createsdgs import create_sdgs_from_et
8
7
  from .decodestate import DecodeState
9
8
  from .element import IdentifiableElement
10
9
  from .encodestate import EncodeState
11
10
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
11
  from .odxtypes import ParameterValue
12
+ from .snrefcontext import SnRefContext
13
13
  from .specialdatagroup import SpecialDataGroup
14
14
  from .utils import dataclass_fields_asdict
15
15
 
16
- if TYPE_CHECKING:
17
- from .diaglayer import DiagLayer
18
-
19
16
 
20
17
  @dataclass
21
18
  class DopBase(IdentifiableElement):
@@ -37,7 +34,9 @@ class DopBase(IdentifiableElement):
37
34
  if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
38
35
  admin_data = AdminData.from_et(admin_data_elem, doc_frags)
39
36
 
40
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
37
+ sdgs = [
38
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
39
+ ]
41
40
 
42
41
  return DopBase(admin_data=admin_data, sdgs=sdgs, **kwargs)
43
42
 
@@ -53,9 +52,9 @@ class DopBase(IdentifiableElement):
53
52
  for sdg in self.sdgs:
54
53
  sdg._resolve_odxlinks(odxlinks)
55
54
 
56
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
55
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
57
56
  for sdg in self.sdgs:
58
- sdg._resolve_snrefs(diag_layer)
57
+ sdg._resolve_snrefs(context)
59
58
 
60
59
  def get_static_bit_length(self) -> Optional[int]:
61
60
  return None
odxtools/dtcdop.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
- # from dataclasses import dataclass, field
3
2
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
3
+ from typing import Any, Dict, List, Optional, Union, cast
5
4
  from xml.etree import ElementTree
6
5
 
7
6
  from typing_extensions import override
@@ -19,11 +18,9 @@ from .nameditemlist import NamedItemList
19
18
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
20
19
  from .odxtypes import ParameterValue, odxstr_to_bool
21
20
  from .physicaltype import PhysicalType
21
+ from .snrefcontext import SnRefContext
22
22
  from .utils import dataclass_fields_asdict
23
23
 
24
- if TYPE_CHECKING:
25
- from .diaglayer import DiagLayer
26
-
27
24
 
28
25
  @dataclass
29
26
  class DtcDop(DopBase):
@@ -133,24 +130,49 @@ class DtcDop(DopBase):
133
130
  sdgs=[],
134
131
  )
135
132
 
136
- @override
137
- def encode_into_pdu(self, physical_value: Optional[ParameterValue],
138
- encode_state: EncodeState) -> None:
139
- if isinstance(physical_value, DiagnosticTroubleCode):
140
- trouble_code = physical_value.trouble_code
141
- 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):
142
137
  # assume that physical value is the trouble_code
143
- trouble_code = physical_value
144
- elif isinstance(physical_value, str):
138
+ return dtc_value
139
+ elif isinstance(dtc_value, str):
145
140
  # assume that physical value is the short_name
146
- dtcs = [dtc for dtc in self.dtcs if dtc.short_name == physical_value]
147
- odxassert(len(dtcs) == 1)
148
- 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
149
148
  else:
150
- raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
151
- 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)
152
175
 
153
- internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
154
176
  self.diag_coded_type.encode_into_pdu(internal_trouble_code, encode_state)
155
177
 
156
178
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -177,9 +199,9 @@ class DtcDop(DopBase):
177
199
  linked_dtc_dops = [odxlinks.resolve(x, DtcDop) for x in self.linked_dtc_dop_refs]
178
200
  self._linked_dtc_dops = NamedItemList(linked_dtc_dops)
179
201
 
180
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
181
- super()._resolve_snrefs(diag_layer)
202
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
203
+ super()._resolve_snrefs(context)
182
204
 
183
205
  for dtc_proxy in self.dtcs_raw:
184
206
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
185
- dtc_proxy._resolve_snrefs(diag_layer)
207
+ dtc_proxy._resolve_snrefs(context)