odxtools 6.7.0__py3-none-any.whl → 9.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 (213) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,84 +1,96 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import re
3
3
  import warnings
4
+ from copy import deepcopy
4
5
  from dataclasses import dataclass
5
6
  from functools import cached_property
6
- from itertools import chain
7
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union, cast
7
+ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar,
8
+ Union, cast)
8
9
  from xml.etree import ElementTree
9
10
 
10
- from deprecation import deprecated
11
-
12
- from .additionalaudience import AdditionalAudience
13
- from .admindata import AdminData
14
- from .companydata import CompanyData
15
- from .comparaminstance import ComparamInstance
16
- from .comparamspec import ComparamSpec
17
- from .diagcomm import DiagComm
18
- from .diagdatadictionaryspec import DiagDataDictionarySpec
19
- from .diaglayerraw import DiagLayerRaw
20
- from .diaglayertype import DiagLayerType
21
- from .diagservice import DiagService
22
- from .ecuvariantpattern import EcuVariantPattern
23
- from .exceptions import DecodeError, OdxWarning, odxassert, odxraise
24
- from .functionalclass import FunctionalClass
25
- from .message import Message
26
- from .nameditemlist import NamedItemList, OdxNamed
27
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
28
- from .parentref import ParentRef
29
- from .protstack import ProtStack
30
- from .request import Request
31
- from .response import Response
32
- from .servicebinner import ServiceBinner
33
- from .singleecujob import SingleEcuJob
34
- from .specialdatagroup import SpecialDataGroup
35
- from .statechart import StateChart
36
- from .table import Table
37
- from .unitgroup import UnitGroup
38
- from .unitspec import UnitSpec
11
+ from ..additionalaudience import AdditionalAudience
12
+ from ..admindata import AdminData
13
+ from ..comparaminstance import ComparamInstance
14
+ from ..diagcomm import DiagComm
15
+ from ..diagdatadictionaryspec import DiagDataDictionarySpec
16
+ from ..diagservice import DiagService
17
+ from ..exceptions import OdxWarning, odxassert, odxraise
18
+ from ..functionalclass import FunctionalClass
19
+ from ..nameditemlist import NamedItemList, OdxNamed
20
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
21
+ from ..parentref import ParentRef
22
+ from ..response import Response
23
+ from ..singleecujob import SingleEcuJob
24
+ from ..snrefcontext import SnRefContext
25
+ from ..specialdatagroup import SpecialDataGroup
26
+ from ..statechart import StateChart
27
+ from ..unitgroup import UnitGroup
28
+ from ..unitspec import UnitSpec
29
+ from .diaglayer import DiagLayer
30
+ from .hierarchyelementraw import HierarchyElementRaw
31
+
32
+ if TYPE_CHECKING:
33
+ from .database import Database
34
+ from .protocol import Protocol
39
35
 
40
36
  TNamed = TypeVar("TNamed", bound=OdxNamed)
41
37
 
42
- PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
43
-
44
38
 
45
39
  @dataclass
46
- class DiagLayer:
47
- """This class represents a "logical view" upon a diagnostic layer
48
- according to the ODX standard.
49
-
50
- i.e. it handles the value inheritance, communication parameters,
51
- encoding/decoding of data, etc.
40
+ class HierarchyElement(DiagLayer):
41
+ """This is the base class for diagnostic layers that may be involved in value inheritance
52
42
  """
53
43
 
54
- diag_layer_raw: DiagLayerRaw
44
+ @property
45
+ def hierarchy_element_raw(self) -> HierarchyElementRaw:
46
+ return cast(HierarchyElementRaw, self.diag_layer_raw)
47
+
48
+ @staticmethod
49
+ def from_et(et_element: ElementTree.Element,
50
+ doc_frags: List[OdxDocFragment]) -> "HierarchyElement":
51
+ hierarchy_element_raw = HierarchyElementRaw.from_et(et_element, doc_frags)
52
+
53
+ return HierarchyElement(diag_layer_raw=hierarchy_element_raw)
55
54
 
56
55
  def __post_init__(self) -> None:
57
- self._global_negative_responses: NamedItemList[Response]
56
+ super().__post_init__()
58
57
 
59
- @staticmethod
60
- def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
61
- diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
58
+ self._global_negative_responses: NamedItemList[Response]
62
59
 
63
- # Create DiagLayer
64
- return DiagLayer(diag_layer_raw=diag_layer_raw)
60
+ odxassert(
61
+ isinstance(self.diag_layer_raw, HierarchyElementRaw),
62
+ "The raw diagnostic layer passed to HierarchyElement "
63
+ "must be a HierarchyElementRaw")
65
64
 
66
65
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
67
- """Construct a mapping from IDs to all objects that are contained in this diagnostic layer."""
68
- result = self.diag_layer_raw._build_odxlinks()
69
-
70
- # we want to get the full diag layer, not just the raw layer
71
- # when referencing...
72
- result[self.odx_id] = self
66
+ result = super()._build_odxlinks()
73
67
 
74
68
  return result
75
69
 
76
70
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
77
- """Recursively resolve all references."""
71
+ super()._resolve_odxlinks(odxlinks)
72
+
73
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
74
+ super()._resolve_snrefs(context)
75
+
76
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
77
+ """Create a deep copy of the hierarchy element
78
+
79
+ Note that the copied diagnostic layer is not fully
80
+ initialized, so `_finalize_init()` should to be called on it
81
+ before it can be used normally.
82
+ """
83
+
84
+ new_he = super().__deepcopy__(memo)
85
+
86
+ # note that the self.hierarchy_element_raw object is *not*
87
+ # copied at this place because the attribute points to the
88
+ # same object as self.diag_layer_raw.
89
+ new_he.hierarchy_element_raw = deepcopy(self.hierarchy_element_raw)
78
90
 
79
- self.diag_layer_raw._resolve_odxlinks(odxlinks)
91
+ return new_he
80
92
 
81
- def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
93
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
82
94
  """This method deals with everything inheritance related and
83
95
  -- after the final set of objects covered by the diagnostic
84
96
  layer is determined -- resolves any short name references in
@@ -98,27 +110,7 @@ class DiagLayer:
98
110
  # fill in all applicable objects that use value inheritance
99
111
  #####
100
112
 
101
- # diagnostic communication objects with the ODXLINKs resolved
102
- diag_comms = self._compute_available_diag_comms(odxlinks)
103
- self._diag_comms = NamedItemList(diag_comms)
104
-
105
- # filter the diag comms for services and single-ECU jobs
106
- services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
107
- single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
108
- self._services = NamedItemList(services)
109
- self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
110
-
111
- global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
112
- self._global_negative_responses = NamedItemList(global_negative_responses)
113
-
114
- functional_classes = self._compute_available_functional_classes()
115
- self._functional_classes = NamedItemList(functional_classes)
116
-
117
- additional_audiences = self._compute_available_additional_audiences()
118
- self._additional_audiences = NamedItemList(additional_audiences)
119
-
120
- state_charts = self._compute_available_state_charts()
121
- self._state_charts = NamedItemList(state_charts)
113
+ self._compute_value_inheritance(odxlinks)
122
114
 
123
115
  ############
124
116
  # create a new unit_spec object. This is necessary because
@@ -167,10 +159,18 @@ class DiagLayer:
167
159
  lambda ddd_spec: ddd_spec.dtc_dops,
168
160
  lambda parent_ref: parent_ref.not_inherited_dops,
169
161
  )
162
+ static_fields = self._compute_available_ddd_spec_items(
163
+ lambda ddd_spec: ddd_spec.static_fields,
164
+ lambda parent_ref: parent_ref.not_inherited_dops,
165
+ )
170
166
  end_of_pdu_fields = self._compute_available_ddd_spec_items(
171
167
  lambda ddd_spec: ddd_spec.end_of_pdu_fields,
172
168
  lambda parent_ref: parent_ref.not_inherited_dops,
173
169
  )
170
+ dynamic_endmarker_fields = self._compute_available_ddd_spec_items(
171
+ lambda ddd_spec: ddd_spec.dynamic_endmarker_fields,
172
+ lambda parent_ref: parent_ref.not_inherited_dops,
173
+ )
174
174
  dynamic_length_fields = self._compute_available_ddd_spec_items(
175
175
  lambda ddd_spec: ddd_spec.dynamic_length_fields,
176
176
  lambda parent_ref: parent_ref.not_inherited_dops,
@@ -185,22 +185,24 @@ class DiagLayer:
185
185
  lambda ddd_spec: ddd_spec.muxs, lambda parent_ref: parent_ref.not_inherited_dops)
186
186
  tables = self._compute_available_ddd_spec_items(
187
187
  lambda ddd_spec: ddd_spec.tables, lambda parent_ref: parent_ref.not_inherited_tables)
188
- ddds_sdgs: List[SpecialDataGroup]
188
+
189
+ ddds_admin_data: Optional[AdminData] = None
190
+ ddds_sdgs: List[SpecialDataGroup] = []
189
191
  if self.diag_layer_raw.diag_data_dictionary_spec:
192
+ ddds_admin_data = self.diag_layer_raw.diag_data_dictionary_spec.admin_data
190
193
  ddds_sdgs = self.diag_layer_raw.diag_data_dictionary_spec.sdgs
191
- else:
192
- ddds_sdgs = []
193
194
 
194
195
  # create a DiagDataDictionarySpec which includes all the
195
196
  # inherited objects. To me, this seems rather inelegant, but
196
197
  # hey, it's described like this in the standard.
197
198
  self._diag_data_dictionary_spec = DiagDataDictionarySpec(
198
- admin_data=None,
199
+ admin_data=ddds_admin_data,
199
200
  data_object_props=dops,
200
201
  dtc_dops=dtc_dops,
201
202
  structures=structures,
202
- static_fields=NamedItemList(),
203
+ static_fields=static_fields,
203
204
  end_of_pdu_fields=end_of_pdu_fields,
205
+ dynamic_endmarker_fields=dynamic_endmarker_fields,
204
206
  dynamic_length_fields=dynamic_length_fields,
205
207
  tables=tables,
206
208
  env_data_descs=env_data_descs,
@@ -217,7 +219,7 @@ class DiagLayer:
217
219
  # scheme, cf the docstring of
218
220
  # _compute_available_commmunication_parameters().
219
221
  #####
220
- self._comparams = NamedItemList(self._compute_available_commmunication_parameters())
222
+ self._comparam_refs = NamedItemList(self._compute_available_commmunication_parameters())
221
223
 
222
224
  #####
223
225
  # resolve all SNREFs. TODO: We allow SNREFS to objects that
@@ -225,169 +227,40 @@ class DiagLayer:
225
227
  # by the spec (So far, I haven't found any definitive
226
228
  # statement...)
227
229
  #####
228
- self.diag_layer_raw._resolve_snrefs(self)
229
-
230
- #####
231
- # <convenience functionality>
232
- #####
233
- @cached_property
234
- def service_groups(self) -> ServiceBinner:
235
- return ServiceBinner(self.services)
236
-
237
- #####
238
- # </convenience functionality>
239
- #####
240
-
241
- #####
242
- # <properties forwarded to the "raw" diag layer>
243
- #####
244
- @property
245
- def variant_type(self) -> DiagLayerType:
246
- return self.diag_layer_raw.variant_type
247
-
248
- @property
249
- def odx_id(self) -> OdxLinkId:
250
- return self.diag_layer_raw.odx_id
251
-
252
- @property
253
- def short_name(self) -> str:
254
- return self.diag_layer_raw.short_name
255
-
256
- @property
257
- def long_name(self) -> Optional[str]:
258
- return self.diag_layer_raw.long_name
259
-
260
- @property
261
- def description(self) -> Optional[str]:
262
- return self.diag_layer_raw.description
263
-
264
- @property
265
- def admin_data(self) -> Optional[AdminData]:
266
- return self.diag_layer_raw.admin_data
267
-
268
- @property
269
- def company_datas(self) -> NamedItemList[CompanyData]:
270
- return self.diag_layer_raw.company_datas
271
-
272
- @property
273
- def requests(self) -> NamedItemList[Request]:
274
- return self.diag_layer_raw.requests
275
-
276
- @property
277
- def positive_responses(self) -> NamedItemList[Response]:
278
- return self.diag_layer_raw.positive_responses
279
-
280
- @property
281
- def negative_responses(self) -> NamedItemList[Response]:
282
- return self.diag_layer_raw.negative_responses
283
-
284
- @property
285
- def import_refs(self) -> List[OdxLinkRef]:
286
- return self.diag_layer_raw.import_refs
287
-
288
- @property
289
- def sdgs(self) -> List[SpecialDataGroup]:
290
- return self.diag_layer_raw.sdgs
291
-
292
- @property
293
- def parent_refs(self) -> List[ParentRef]:
294
- return self.diag_layer_raw.parent_refs
295
-
296
- @property
297
- def ecu_variant_patterns(self) -> List[EcuVariantPattern]:
298
- return self.diag_layer_raw.ecu_variant_patterns
299
-
300
- @property
301
- def comparam_spec_ref(self) -> Optional[OdxLinkRef]:
302
- return self.diag_layer_raw.comparam_spec_ref
303
-
304
- @property
305
- def prot_stack_snref(self) -> Optional[str]:
306
- return self.diag_layer_raw.prot_stack_snref
307
-
308
- @property
309
- def comparam_spec(self) -> Optional[ComparamSpec]:
310
- return self.diag_layer_raw.comparam_spec
311
-
312
- @property
313
- def prot_stack(self) -> Optional[ProtStack]:
314
- return self.diag_layer_raw.prot_stack
230
+ context = SnRefContext(database=database)
231
+ context.diag_layer = self
232
+ self._resolve_snrefs(context)
233
+ context.diag_layer = None
315
234
 
316
235
  #####
317
- # </properties forwarded to the "raw" diag layer>
236
+ # <value inheritance mechanism helpers>
318
237
  #####
238
+ def _compute_value_inheritance(self, odxlinks: OdxLinkDatabase) -> None:
239
+ # diagnostic communication objects with the ODXLINKs resolved
240
+ diag_comms = self._compute_available_diag_comms(odxlinks)
241
+ self._diag_comms = NamedItemList[DiagComm](diag_comms)
319
242
 
320
- #######
321
- # <stuff subject to value inheritance>
322
- #######
323
- @property
324
- def diag_comms(self) -> NamedItemList[DiagComm]:
325
- """All diagnostic communication primitives applicable to this DiagLayer
326
-
327
- Diagnostic communication primitives are diagnostic services as
328
- well as single-ECU jobs. This list has all references
329
- resolved.
330
- """
331
- return self._diag_comms
332
-
333
- @property
334
- def services(self) -> NamedItemList[DiagService]:
335
- """All diagnostic services applicable to this DiagLayer
336
-
337
- This is a subset of all diagnostic communication
338
- primitives. All references are resolved in the list returned.
339
- """
340
- return self._services
341
-
342
- @property
343
- def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
344
- """All single-ECU jobs applicable to this DiagLayer
345
-
346
- This is a subset of all diagnostic communication
347
- primitives. All references are resolved in the list returned.
348
- """
349
- return self._single_ecu_jobs
350
-
351
- @property
352
- def global_negative_responses(self) -> NamedItemList[Response]:
353
- """All global negative responses applicable to this DiagLayer"""
354
- return self._global_negative_responses
355
-
356
- @property
357
- @deprecated(details="use diag_data_dictionary_spec.tables") # type: ignore[misc]
358
- def tables(self) -> NamedItemList[Table]:
359
- return self.diag_data_dictionary_spec.tables
360
-
361
- @property
362
- def functional_classes(self) -> NamedItemList[FunctionalClass]:
363
- """All functional classes applicable to this DiagLayer"""
364
- return self._functional_classes
243
+ # filter the diag comms for services and single-ECU jobs
244
+ diag_services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
245
+ single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
246
+ self._diag_services = NamedItemList(diag_services)
247
+ self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
365
248
 
366
- @property
367
- def state_charts(self) -> NamedItemList[StateChart]:
368
- """All state charts applicable to this DiagLayer"""
369
- return self._state_charts
249
+ global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
250
+ self._global_negative_responses = NamedItemList(global_negative_responses)
370
251
 
371
- @property
372
- def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
373
- """All audiences applicable to this DiagLayer"""
374
- return self._additional_audiences
252
+ functional_classes = self._compute_available_functional_classes()
253
+ self._functional_classes = NamedItemList(functional_classes)
375
254
 
376
- @property
377
- def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
378
- """The DiagDataDictionarySpec applicable to this DiagLayer"""
379
- return self._diag_data_dictionary_spec
255
+ additional_audiences = self._compute_available_additional_audiences()
256
+ self._additional_audiences = NamedItemList(additional_audiences)
380
257
 
381
- #######
382
- # </stuff subject to value inheritance>
383
- #######
258
+ state_charts = self._compute_available_state_charts()
259
+ self._state_charts = NamedItemList(state_charts)
384
260
 
385
- #####
386
- # <value inheritance mechanism helpers>
387
- #####
388
261
  def _get_parent_refs_sorted_by_priority(self, reverse: bool = False) -> Iterable[ParentRef]:
389
262
  return sorted(
390
- self.diag_layer_raw.parent_refs,
263
+ getattr(self.diag_layer_raw, "parent_refs", []),
391
264
  key=lambda pr: pr.layer.variant_type.inheritance_priority,
392
265
  reverse=reverse)
393
266
 
@@ -481,41 +354,6 @@ class DiagLayer:
481
354
 
482
355
  return [x[0] for x in result_dict.values()]
483
356
 
484
- def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
485
- """Return the list of locally defined diagnostic communications.
486
-
487
- This is not completely trivial as it requires to resolving the
488
- references specified in the <DIAG-COMMS> XML tag.
489
- """
490
- result_dict: Dict[str, DiagComm] = {}
491
-
492
- # TODO (?): add objects from the import-refs
493
-
494
- for dc_proxy in self.diag_layer_raw.diag_comms:
495
- if isinstance(dc_proxy, OdxLinkRef):
496
- dc = odxlinks.resolve(dc_proxy)
497
- else:
498
- dc = dc_proxy
499
-
500
- odxassert(isinstance(dc, DiagComm))
501
- odxassert(
502
- dc.short_name not in result_dict,
503
- f"Multiple definitions of DIAG-COMM '{dc.short_name}' in "
504
- f"layer '{self.short_name}'")
505
- result_dict[dc.short_name] = dc
506
-
507
- return result_dict.values()
508
-
509
- def _get_local_unit_groups(self) -> Iterable[UnitGroup]:
510
- if self.diag_layer_raw.diag_data_dictionary_spec is None:
511
- return []
512
-
513
- unit_spec = self.diag_layer_raw.diag_data_dictionary_spec.unit_spec
514
- if unit_spec is None:
515
- return []
516
-
517
- return unit_spec.unit_groups
518
-
519
357
  def _compute_available_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
520
358
 
521
359
  def get_local_objects_fn(dl: DiagLayer) -> Iterable[DiagComm]:
@@ -595,6 +433,70 @@ class DiagLayer:
595
433
  # </value inheritance mechanism helpers>
596
434
  #####
597
435
 
436
+ #######
437
+ # <properties subject to value inheritance>
438
+ #######
439
+ @property
440
+ def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
441
+ return self._diag_data_dictionary_spec
442
+
443
+ @property
444
+ def diag_comms(self) -> NamedItemList[DiagComm]:
445
+ """All diagnostic communication primitives applicable to this DiagLayer
446
+
447
+ Diagnostic communication primitives are diagnostic services as
448
+ well as single-ECU jobs. This list has all references
449
+ resolved.
450
+ """
451
+ return self._diag_comms
452
+
453
+ @property
454
+ def services(self) -> NamedItemList[DiagService]:
455
+ """This property is an alias for `.diag_services`"""
456
+ return self._diag_services
457
+
458
+ @property
459
+ def diag_services(self) -> NamedItemList[DiagService]:
460
+ """All diagnostic services applicable to this DiagLayer
461
+
462
+ This is a subset of all diagnostic communication
463
+ primitives. All references are resolved in the list returned.
464
+ """
465
+ return self._diag_services
466
+
467
+ @property
468
+ def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
469
+ """All single-ECU jobs applicable to this DiagLayer
470
+
471
+ This is a subset of all diagnostic communication
472
+ primitives. All references are resolved in the list returned.
473
+ """
474
+ return self._single_ecu_jobs
475
+
476
+ @property
477
+ def global_negative_responses(self) -> NamedItemList[Response]:
478
+ """All global negative responses applicable to this DiagLayer"""
479
+ return self._global_negative_responses
480
+
481
+ @property
482
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
483
+ """All functional classes applicable to this DiagLayer"""
484
+ return self._functional_classes
485
+
486
+ @property
487
+ def state_charts(self) -> NamedItemList[StateChart]:
488
+ """All state charts applicable to this DiagLayer"""
489
+ return self._state_charts
490
+
491
+ @property
492
+ def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
493
+ """All audiences applicable to this DiagLayer"""
494
+ return self._additional_audiences
495
+
496
+ #######
497
+ # </properties subject to value inheritance>
498
+ #######
499
+
598
500
  #####
599
501
  # <communication parameter handling>
600
502
  #####
@@ -628,39 +530,45 @@ class DiagLayer:
628
530
  # parameters. First fetch the communication parameters from
629
531
  # low priority parents, then update with increasing priority.
630
532
  for parent_ref in self._get_parent_refs_sorted_by_priority():
631
- for cp in parent_ref.layer._compute_available_commmunication_parameters():
533
+ parent_layer = parent_ref.layer
534
+ if not isinstance(parent_layer, HierarchyElement):
535
+ continue
536
+ for cp in parent_layer._compute_available_commmunication_parameters():
632
537
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
633
538
 
634
539
  # finally, handle the locally defined communication parameters
635
- for cp in self.diag_layer_raw.comparams:
540
+ for cp in getattr(self.hierarchy_element_raw, "comparam_refs", []):
636
541
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
637
542
 
638
543
  return list(com_params_dict.values())
639
544
 
640
545
  @property
641
- def comparams(self) -> NamedItemList[ComparamInstance]:
546
+ def comparam_refs(self) -> NamedItemList[ComparamInstance]:
642
547
  """All communication parameters applicable to this DiagLayer
643
548
 
644
549
  Note that, although communication parameters use inheritance,
645
550
  it is *not* the "value inheritance" scheme used by e.g. DOPs,
646
551
  tables, state charts, ...
647
552
  """
648
- return self._comparams
553
+ return self._comparam_refs
649
554
 
650
555
  @cached_property
651
- def protocols(self) -> NamedItemList["DiagLayer"]:
556
+ def protocols(self) -> NamedItemList["Protocol"]:
652
557
  """Return the set of all protocols which are applicable to the diagnostic layer
653
558
 
654
- Note that protocols are *not* explicitly inherited objects,
655
- but the parent diagnostic layers of variant type "PROTOCOL".
559
+ Note that protocols are *not* explicitly defined by the XML,
560
+ but they are the parent layers of variant type "PROTOCOL".
561
+
656
562
  """
657
- result_dict: Dict[str, DiagLayer] = {}
563
+ from .protocol import Protocol
658
564
 
659
- for parent_ref in self._get_parent_refs_sorted_by_priority():
660
- for prot in parent_ref.layer.protocols:
565
+ result_dict: Dict[str, Protocol] = {}
566
+
567
+ for parent_ref in getattr(self, "parent_refs", []):
568
+ for prot in getattr(parent_ref.layer, "protocols", []):
661
569
  result_dict[prot.short_name] = prot
662
570
 
663
- if self.diag_layer_raw.variant_type == DiagLayerType.PROTOCOL:
571
+ if isinstance(self, Protocol):
664
572
  result_dict[self.diag_layer_raw.short_name] = self
665
573
 
666
574
  return NamedItemList(result_dict.values())
@@ -669,20 +577,22 @@ class DiagLayer:
669
577
  self,
670
578
  cp_short_name: str,
671
579
  *,
672
- protocol: Optional[Union[str, "DiagLayer"]] = None,
580
+ protocol: Optional[Union[str, "Protocol"]] = None,
673
581
  ) -> Optional[ComparamInstance]:
674
582
  """Find a specific communication parameter according to some criteria.
675
583
 
676
584
  Setting a given parameter to `None` means "don't care"."""
677
585
 
586
+ from .protocol import Protocol
587
+
678
588
  protocol_name: Optional[str]
679
- if isinstance(protocol, DiagLayer):
589
+ if isinstance(protocol, Protocol):
680
590
  protocol_name = protocol.short_name
681
591
  else:
682
592
  protocol_name = protocol
683
593
 
684
594
  # determine the set of applicable communication parameters
685
- cps = [cp for cp in self.comparams if cp.short_name == cp_short_name]
595
+ cps = [cp for cp in self.comparam_refs if cp.short_name == cp_short_name]
686
596
  if protocol_name is not None:
687
597
  cps = [cp for cp in cps if cp.protocol_snref in (None, protocol_name)]
688
598
 
@@ -700,7 +610,7 @@ class DiagLayer:
700
610
 
701
611
  def get_max_can_payload_size(self,
702
612
  protocol: Optional[Union[str,
703
- "DiagLayer"]] = None) -> Optional[int]:
613
+ "Protocol"]] = None) -> Optional[int]:
704
614
  """Return the maximum size of a CAN frame payload that can be
705
615
  transmitted in bytes.
706
616
 
@@ -730,13 +640,13 @@ class DiagLayer:
730
640
  # unexpected format of parameter value
731
641
  return 8
732
642
 
733
- def uses_can(self, protocol: Optional[Union[str, "DiagLayer"]] = None) -> bool:
643
+ def uses_can(self, protocol: Optional[Union[str, "Protocol"]] = None) -> bool:
734
644
  """
735
645
  Check if CAN ought to be used as the link layer protocol.
736
646
  """
737
647
  return self.get_can_receive_id(protocol=protocol) is not None
738
648
 
739
- def uses_can_fd(self, protocol: Optional[Union[str, "DiagLayer"]] = None) -> bool:
649
+ def uses_can_fd(self, protocol: Optional[Union[str, "Protocol"]] = None) -> bool:
740
650
  """Check if CAN-FD ought to be used.
741
651
 
742
652
  If the ECU is not using CAN-FD for the specified protocol, `False`
@@ -755,7 +665,7 @@ class DiagLayer:
755
665
 
756
666
  return "CANFD" in com_param.value
757
667
 
758
- def get_can_baudrate(self, protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
668
+ def get_can_baudrate(self, protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
759
669
  """Baudrate of the CAN bus which is used by the ECU [bits/s]
760
670
 
761
671
  If the ECU is not using CAN for the specified protocol, None
@@ -773,7 +683,7 @@ class DiagLayer:
773
683
  return int(val)
774
684
 
775
685
  def get_can_fd_baudrate(self,
776
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
686
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
777
687
  """Data baudrate of the CAN bus which is used by the ECU [bits/s]
778
688
 
779
689
  If the ECU is not using CAN-FD for the specified protocol,
@@ -793,7 +703,7 @@ class DiagLayer:
793
703
  return int(val)
794
704
 
795
705
  def get_can_receive_id(self,
796
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
706
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
797
707
  """CAN ID to which the ECU listens for diagnostic messages"""
798
708
  com_param = self.get_comparam("CP_UniqueRespIdTable", protocol=protocol)
799
709
  if com_param is None:
@@ -812,11 +722,7 @@ class DiagLayer:
812
722
 
813
723
  return int(result)
814
724
 
815
- @deprecated(details="use get_can_receive_id()") # type: ignore[misc]
816
- def get_receive_id(self) -> Optional[int]:
817
- return self.get_can_receive_id()
818
-
819
- def get_can_send_id(self, protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
725
+ def get_can_send_id(self, protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
820
726
  """CAN ID to which the ECU sends replies to diagnostic messages"""
821
727
 
822
728
  # this hopefully resolves to the 'CP_UniqueRespIdTable'
@@ -841,12 +747,8 @@ class DiagLayer:
841
747
 
842
748
  return int(result)
843
749
 
844
- @deprecated(details="use get_can_send_id()") # type: ignore[misc]
845
- def get_send_id(self) -> Optional[int]:
846
- return self.get_can_send_id()
847
-
848
750
  def get_can_func_req_id(self,
849
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
751
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
850
752
  """CAN Functional Request Id."""
851
753
  com_param = self.get_comparam("CP_CanFuncReqId", protocol=protocol)
852
754
  if com_param is None:
@@ -860,8 +762,8 @@ class DiagLayer:
860
762
  return int(result)
861
763
 
862
764
  def get_doip_logical_ecu_address(self,
863
- protocol: Optional[Union[str, "DiagLayer"]] = None
864
- ) -> Optional[int]:
765
+ protocol: Optional[Union[str,
766
+ "Protocol"]] = None) -> Optional[int]:
865
767
  """Return the address of the ECU when using functional addressing.
866
768
 
867
769
  The parameter protocol is used to distinguish between
@@ -892,7 +794,7 @@ class DiagLayer:
892
794
  return int(ecu_addr)
893
795
 
894
796
  def get_doip_logical_gateway_address(self,
895
- protocol: Optional[Union[str, "DiagLayer"]] = None
797
+ protocol: Optional[Union[str, "Protocol"]] = None
896
798
  ) -> Optional[int]:
897
799
  """The logical gateway address for the diagnosis over IP transport protocol"""
898
800
 
@@ -910,7 +812,7 @@ class DiagLayer:
910
812
  return int(result)
911
813
 
912
814
  def get_doip_logical_tester_address(self,
913
- protocol: Optional[Union[str, "DiagLayer"]] = None
815
+ protocol: Optional[Union[str, "Protocol"]] = None
914
816
  ) -> Optional[int]:
915
817
  """DoIp logical gateway address"""
916
818
 
@@ -928,7 +830,7 @@ class DiagLayer:
928
830
  return int(result)
929
831
 
930
832
  def get_doip_logical_functional_address(self,
931
- protocol: Optional[Union[str, "DiagLayer"]] = None
833
+ protocol: Optional[Union[str, "Protocol"]] = None
932
834
  ) -> Optional[int]:
933
835
  """The logical functional DoIP address of the ECU."""
934
836
 
@@ -949,7 +851,7 @@ class DiagLayer:
949
851
  return int(result)
950
852
 
951
853
  def get_doip_routing_activation_timeout(self,
952
- protocol: Optional[Union[str, "DiagLayer"]] = None
854
+ protocol: Optional[Union[str, "Protocol"]] = None
953
855
  ) -> Optional[float]:
954
856
  """The timout for the DoIP routing activation request in seconds"""
955
857
 
@@ -967,7 +869,7 @@ class DiagLayer:
967
869
  return float(result) / 1e6
968
870
 
969
871
  def get_doip_routing_activation_type(self,
970
- protocol: Optional[Union[str, "DiagLayer"]] = None
872
+ protocol: Optional[Union[str, "Protocol"]] = None
971
873
  ) -> Optional[int]:
972
874
  """The DoIP routing activation type
973
875
 
@@ -994,7 +896,7 @@ class DiagLayer:
994
896
 
995
897
  def get_tester_present_time(self,
996
898
  protocol: Optional[Union[str,
997
- "DiagLayer"]] = None) -> Optional[float]:
899
+ "Protocol"]] = None) -> Optional[float]:
998
900
  """Timeout on inactivity in seconds.
999
901
 
1000
902
  This is defined by the communication parameter "CP_TesterPresentTime".
@@ -1021,144 +923,3 @@ class DiagLayer:
1021
923
  #####
1022
924
  # </communication parameter handling>
1023
925
  #####
1024
-
1025
- #####
1026
- # <PDU decoding>
1027
- #####
1028
-
1029
- @cached_property
1030
- def _prefix_tree(self) -> PrefixTree:
1031
- """Constructs the coded prefix tree of the services.
1032
-
1033
- Each leaf node is a list of `DiagService`s. (This is because
1034
- navigating from a service to the request/ responses is easier
1035
- than finding the service for a given request/response object.)
1036
-
1037
- Example:
1038
- Let there be four services with corresponding requests:
1039
- * Request 1 has the coded constant prefix `12 34`.
1040
- * Request 2 has the coded constant prefix `12 34`.
1041
- * Request 3 has the coded constant prefix `12 56`.
1042
- * Request 4 has the coded constant prefix `12 56 00`.
1043
-
1044
- Then, the constructed prefix tree is the dict
1045
- ```
1046
- {0x12: {0x34: {-1: [<Service 1>, <Service 2>]},
1047
- 0x56: {-1: [<Service 3>],
1048
- 0x0: {-1: [<Service 4>]}
1049
- }}}
1050
- ```
1051
- Note, that the inner `-1` are constant to distinguish them
1052
- from possible service IDs.
1053
-
1054
- Also note, that it is actually allowed that
1055
- (a) SIDs for different services are the same like for service 1 and 2 (thus each leaf node is a list) and
1056
- (b) one SID is the prefix of another SID like for service 3 and 4 (thus the constant `-1` key).
1057
-
1058
- """
1059
- prefix_tree: PrefixTree = {}
1060
- for s in self.services:
1061
- # Compute prefixes for the service's request and all
1062
- # possible responses. We need to consider the global
1063
- # negative responses here, because they might contain
1064
- # MATCHING-REQUEST parameters. If these global responses
1065
- # do not contain such parameters, this will potentially
1066
- # result in an enormous amount of decoded messages for
1067
- # global negative responses. (I.e., one for each
1068
- # service. This can be avoided by specifying the
1069
- # corresponding request for `decode_response()`.)
1070
- request_prefix = b''
1071
- if s.request is not None:
1072
- request_prefix = s.request.coded_const_prefix()
1073
- prefixes = [request_prefix]
1074
- prefixes += [
1075
- x.coded_const_prefix(request_prefix=request_prefix) for x in chain(
1076
- s.positive_responses, s.negative_responses, self.global_negative_responses)
1077
- ]
1078
- for coded_prefix in prefixes:
1079
- self._extend_prefix_tree(prefix_tree, coded_prefix, s)
1080
-
1081
- return prefix_tree
1082
-
1083
- @staticmethod
1084
- def _extend_prefix_tree(prefix_tree: PrefixTree, coded_prefix: bytes,
1085
- service: DiagService) -> None:
1086
-
1087
- # make sure that tree has an entry for the given prefix
1088
- sub_tree = prefix_tree
1089
- for b in coded_prefix:
1090
- if b not in sub_tree:
1091
- sub_tree[b] = {}
1092
- sub_tree = cast(PrefixTree, sub_tree[b])
1093
-
1094
- # Store the object as in the prefix tree. This is done by
1095
- # assigning the list of possible objects to the key -1 of the
1096
- # dictionary (this is quite hacky...)
1097
- if sub_tree.get(-1) is None:
1098
- sub_tree[-1] = [service]
1099
- else:
1100
- cast(List[DiagService], sub_tree[-1]).append(service)
1101
-
1102
- def _find_services_for_uds(self, message: bytes) -> List[DiagService]:
1103
- prefix_tree = self._prefix_tree
1104
-
1105
- # Find matching service(s) in prefix tree
1106
- possible_services: List[DiagService] = []
1107
- for b in message:
1108
- if b in prefix_tree:
1109
- odxassert(isinstance(prefix_tree[b], dict))
1110
- prefix_tree = cast(PrefixTree, prefix_tree[b])
1111
- else:
1112
- break
1113
- if -1 in prefix_tree:
1114
- possible_services += cast(List[DiagService], prefix_tree[-1])
1115
- return possible_services
1116
-
1117
- def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> List[Message]:
1118
- decoded_messages: List[Message] = []
1119
-
1120
- for service in candidate_services:
1121
- try:
1122
- decoded_messages.append(service.decode_message(message))
1123
- except DecodeError:
1124
- # check if the message can be decoded as a global
1125
- # negative response for the service
1126
- for gnr in self.global_negative_responses:
1127
- try:
1128
- decoded_gnr = gnr.decode(message)
1129
- if not isinstance(decoded_gnr, dict):
1130
- raise DecodeError(f"Expected the decoded value of a global "
1131
- f"negative response to be a dictionary, "
1132
- f"got {type(decoded_gnr)} for {self.short_name}")
1133
-
1134
- decoded_messages.append(
1135
- Message(
1136
- coded_message=message,
1137
- service=service,
1138
- coding_object=gnr,
1139
- param_dict=decoded_gnr))
1140
- except DecodeError:
1141
- pass
1142
-
1143
- if len(decoded_messages) == 0:
1144
- raise DecodeError(
1145
- f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
1146
- )
1147
-
1148
- return decoded_messages
1149
-
1150
- def decode(self, message: bytes) -> List[Message]:
1151
- candidate_services = self._find_services_for_uds(message)
1152
-
1153
- return self._decode(message, candidate_services)
1154
-
1155
- def decode_response(self, response: bytes, request: bytes) -> List[Message]:
1156
- candidate_services = self._find_services_for_uds(request)
1157
- if candidate_services is None:
1158
- raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
1159
-
1160
- return self._decode(response, candidate_services)
1161
-
1162
- #####
1163
- # </PDU decoding>
1164
- #####