odxtools 6.6.1__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 (222) hide show
  1. odxtools/__init__.py +7 -5
  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 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,82 +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
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
39
-
40
- T = TypeVar("T")
41
- TNamed = TypeVar("TNamed", bound=OdxNamed)
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
42
35
 
43
- PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
36
+ TNamed = TypeVar("TNamed", bound=OdxNamed)
44
37
 
45
38
 
46
39
  @dataclass
47
- class DiagLayer:
48
- """This class represents a "logical view" upon a diagnostic layer
49
- according to the ODX standard.
50
-
51
- i.e. it handles the value inheritance, communication parameters,
52
- 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
53
42
  """
54
43
 
55
- diag_layer_raw: DiagLayerRaw
44
+ @property
45
+ def hierarchy_element_raw(self) -> HierarchyElementRaw:
46
+ return cast(HierarchyElementRaw, self.diag_layer_raw)
56
47
 
57
48
  @staticmethod
58
- def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
59
- diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
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)
60
52
 
61
- # Create DiagLayer
62
- return DiagLayer(diag_layer_raw=diag_layer_raw)
53
+ return HierarchyElement(diag_layer_raw=hierarchy_element_raw)
63
54
 
64
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
65
- """Construct a mapping from IDs to all objects that are contained in this diagnostic layer."""
66
- result = self.diag_layer_raw._build_odxlinks()
55
+ def __post_init__(self) -> None:
56
+ super().__post_init__()
57
+
58
+ self._global_negative_responses: NamedItemList[Response]
67
59
 
68
- # we want to get the full diag layer, not just the raw layer
69
- # when referencing...
70
- result[self.odx_id] = self
60
+ odxassert(
61
+ isinstance(self.diag_layer_raw, HierarchyElementRaw),
62
+ "The raw diagnostic layer passed to HierarchyElement "
63
+ "must be a HierarchyElementRaw")
64
+
65
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
66
+ result = super()._build_odxlinks()
71
67
 
72
68
  return result
73
69
 
74
70
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
75
- """Recursively resolve all references."""
71
+ super()._resolve_odxlinks(odxlinks)
76
72
 
77
- self.diag_layer_raw._resolve_odxlinks(odxlinks)
73
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
74
+ super()._resolve_snrefs(context)
78
75
 
79
- def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
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)
90
+
91
+ return new_he
92
+
93
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
80
94
  """This method deals with everything inheritance related and
81
95
  -- after the final set of objects covered by the diagnostic
82
96
  layer is determined -- resolves any short name references in
@@ -96,27 +110,7 @@ class DiagLayer:
96
110
  # fill in all applicable objects that use value inheritance
97
111
  #####
98
112
 
99
- # diagnostic communication objects with the ODXLINKs resolved
100
- diag_comms = self._compute_available_diag_comms(odxlinks)
101
- self._diag_comms = NamedItemList(diag_comms)
102
-
103
- # filter the diag comms for services and single-ECU jobs
104
- services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
105
- single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
106
- self._services = NamedItemList(services)
107
- self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
108
-
109
- global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
110
- self._global_negative_responses = NamedItemList(global_negative_responses)
111
-
112
- functional_classes = self._compute_available_functional_classes()
113
- self._functional_classes = NamedItemList(functional_classes)
114
-
115
- additional_audiences = self._compute_available_additional_audiences()
116
- self._additional_audiences = NamedItemList(additional_audiences)
117
-
118
- state_charts = self._compute_available_state_charts()
119
- self._state_charts = NamedItemList(state_charts)
113
+ self._compute_value_inheritance(odxlinks)
120
114
 
121
115
  ############
122
116
  # create a new unit_spec object. This is necessary because
@@ -165,10 +159,18 @@ class DiagLayer:
165
159
  lambda ddd_spec: ddd_spec.dtc_dops,
166
160
  lambda parent_ref: parent_ref.not_inherited_dops,
167
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
+ )
168
166
  end_of_pdu_fields = self._compute_available_ddd_spec_items(
169
167
  lambda ddd_spec: ddd_spec.end_of_pdu_fields,
170
168
  lambda parent_ref: parent_ref.not_inherited_dops,
171
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
+ )
172
174
  dynamic_length_fields = self._compute_available_ddd_spec_items(
173
175
  lambda ddd_spec: ddd_spec.dynamic_length_fields,
174
176
  lambda parent_ref: parent_ref.not_inherited_dops,
@@ -183,20 +185,24 @@ class DiagLayer:
183
185
  lambda ddd_spec: ddd_spec.muxs, lambda parent_ref: parent_ref.not_inherited_dops)
184
186
  tables = self._compute_available_ddd_spec_items(
185
187
  lambda ddd_spec: ddd_spec.tables, lambda parent_ref: parent_ref.not_inherited_tables)
186
- ddds_sdgs: List[SpecialDataGroup]
188
+
189
+ ddds_admin_data: Optional[AdminData] = None
190
+ ddds_sdgs: List[SpecialDataGroup] = []
187
191
  if self.diag_layer_raw.diag_data_dictionary_spec:
192
+ ddds_admin_data = self.diag_layer_raw.diag_data_dictionary_spec.admin_data
188
193
  ddds_sdgs = self.diag_layer_raw.diag_data_dictionary_spec.sdgs
189
- else:
190
- ddds_sdgs = []
191
194
 
192
195
  # create a DiagDataDictionarySpec which includes all the
193
196
  # inherited objects. To me, this seems rather inelegant, but
194
197
  # hey, it's described like this in the standard.
195
198
  self._diag_data_dictionary_spec = DiagDataDictionarySpec(
199
+ admin_data=ddds_admin_data,
196
200
  data_object_props=dops,
197
201
  dtc_dops=dtc_dops,
198
202
  structures=structures,
203
+ static_fields=static_fields,
199
204
  end_of_pdu_fields=end_of_pdu_fields,
205
+ dynamic_endmarker_fields=dynamic_endmarker_fields,
200
206
  dynamic_length_fields=dynamic_length_fields,
201
207
  tables=tables,
202
208
  env_data_descs=env_data_descs,
@@ -213,7 +219,7 @@ class DiagLayer:
213
219
  # scheme, cf the docstring of
214
220
  # _compute_available_commmunication_parameters().
215
221
  #####
216
- self._comparams = NamedItemList(self._compute_available_commmunication_parameters())
222
+ self._comparam_refs = NamedItemList(self._compute_available_commmunication_parameters())
217
223
 
218
224
  #####
219
225
  # resolve all SNREFs. TODO: We allow SNREFS to objects that
@@ -221,177 +227,48 @@ class DiagLayer:
221
227
  # by the spec (So far, I haven't found any definitive
222
228
  # statement...)
223
229
  #####
224
- self.diag_layer_raw._resolve_snrefs(self)
225
-
226
- #####
227
- # <convenience functionality>
228
- #####
229
- @cached_property
230
- def service_groups(self) -> ServiceBinner:
231
- return ServiceBinner(self.services)
232
-
233
- #####
234
- # </convenience functionality>
235
- #####
236
-
237
- #####
238
- # <properties forwarded to the "raw" diag layer>
239
- #####
240
- @property
241
- def variant_type(self) -> DiagLayerType:
242
- return self.diag_layer_raw.variant_type
243
-
244
- @property
245
- def odx_id(self) -> OdxLinkId:
246
- return self.diag_layer_raw.odx_id
247
-
248
- @property
249
- def short_name(self) -> str:
250
- return self.diag_layer_raw.short_name
251
-
252
- @property
253
- def long_name(self) -> Optional[str]:
254
- return self.diag_layer_raw.long_name
255
-
256
- @property
257
- def description(self) -> Optional[str]:
258
- return self.diag_layer_raw.description
259
-
260
- @property
261
- def admin_data(self) -> Optional[AdminData]:
262
- return self.diag_layer_raw.admin_data
263
-
264
- @property
265
- def company_datas(self) -> NamedItemList[CompanyData]:
266
- return self.diag_layer_raw.company_datas
267
-
268
- @property
269
- def requests(self) -> NamedItemList[Request]:
270
- return self.diag_layer_raw.requests
271
-
272
- @property
273
- def positive_responses(self) -> NamedItemList[Response]:
274
- return self.diag_layer_raw.positive_responses
275
-
276
- @property
277
- def negative_responses(self) -> NamedItemList[Response]:
278
- return self.diag_layer_raw.negative_responses
279
-
280
- @property
281
- def import_refs(self) -> List[OdxLinkRef]:
282
- return self.diag_layer_raw.import_refs
283
-
284
- @property
285
- def sdgs(self) -> List[SpecialDataGroup]:
286
- return self.diag_layer_raw.sdgs
287
-
288
- @property
289
- def parent_refs(self) -> List[ParentRef]:
290
- return self.diag_layer_raw.parent_refs
291
-
292
- @property
293
- def ecu_variant_patterns(self) -> List[EcuVariantPattern]:
294
- return self.diag_layer_raw.ecu_variant_patterns
295
-
296
- @property
297
- def comparam_spec_ref(self) -> Optional[OdxLinkRef]:
298
- return self.diag_layer_raw.comparam_spec_ref
299
-
300
- @property
301
- def prot_stack_snref(self) -> Optional[str]:
302
- return self.diag_layer_raw.prot_stack_snref
303
-
304
- @property
305
- def comparam_spec(self) -> Optional[ComparamSpec]:
306
- return self.diag_layer_raw.comparam_spec
307
-
308
- @property
309
- def prot_stack(self) -> Optional[ProtStack]:
310
- 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
311
234
 
312
235
  #####
313
- # </properties forwarded to the "raw" diag layer>
236
+ # <value inheritance mechanism helpers>
314
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)
315
242
 
316
- #######
317
- # <stuff subject to value inheritance>
318
- #######
319
- @property
320
- def diag_comms(self) -> NamedItemList[DiagComm]:
321
- """All diagnostic communication primitives applicable to this DiagLayer
322
-
323
- Diagnostic communication primitives are diagnostic services as
324
- well as single-ECU jobs. This list has all references
325
- resolved.
326
- """
327
- return self._diag_comms
328
-
329
- @property
330
- def services(self) -> NamedItemList[DiagService]:
331
- """All diagnostic services applicable to this DiagLayer
332
-
333
- This is a subset of all diagnostic communication
334
- primitives. All references are resolved in the list returned.
335
- """
336
- return self._services
337
-
338
- @property
339
- def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
340
- """All single-ECU jobs applicable to this DiagLayer
341
-
342
- This is a subset of all diagnostic communication
343
- primitives. All references are resolved in the list returned.
344
- """
345
- return self._single_ecu_jobs
346
-
347
- @property
348
- def global_negative_responses(self) -> NamedItemList[Response]:
349
- """All global negative responses applicable to this DiagLayer"""
350
- return self._global_negative_responses
351
-
352
- @property
353
- @deprecated(details="use diag_data_dictionary_spec.tables")
354
- def tables(self) -> NamedItemList[Table]:
355
- return self.diag_data_dictionary_spec.tables
356
-
357
- @property
358
- def functional_classes(self) -> NamedItemList[FunctionalClass]:
359
- """All functional classes applicable to this DiagLayer"""
360
- 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)
361
248
 
362
- @property
363
- def state_charts(self) -> NamedItemList[StateChart]:
364
- """All state charts applicable to this DiagLayer"""
365
- return self._state_charts
249
+ global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
250
+ self._global_negative_responses = NamedItemList(global_negative_responses)
366
251
 
367
- @property
368
- def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
369
- """All audiences applicable to this DiagLayer"""
370
- return self._additional_audiences
252
+ functional_classes = self._compute_available_functional_classes()
253
+ self._functional_classes = NamedItemList(functional_classes)
371
254
 
372
- @property
373
- def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
374
- """The DiagDataDictionarySpec applicable to this DiagLayer"""
375
- return self._diag_data_dictionary_spec
255
+ additional_audiences = self._compute_available_additional_audiences()
256
+ self._additional_audiences = NamedItemList(additional_audiences)
376
257
 
377
- #######
378
- # </stuff subject to value inheritance>
379
- #######
258
+ state_charts = self._compute_available_state_charts()
259
+ self._state_charts = NamedItemList(state_charts)
380
260
 
381
- #####
382
- # <value inheritance mechanism helpers>
383
- #####
384
261
  def _get_parent_refs_sorted_by_priority(self, reverse: bool = False) -> Iterable[ParentRef]:
385
262
  return sorted(
386
- self.diag_layer_raw.parent_refs,
263
+ getattr(self.diag_layer_raw, "parent_refs", []),
387
264
  key=lambda pr: pr.layer.variant_type.inheritance_priority,
388
265
  reverse=reverse)
389
266
 
390
267
  def _compute_available_objects(
391
268
  self,
392
- get_local_objects: Callable[["DiagLayer"], Iterable[T]],
269
+ get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
393
270
  get_not_inherited: Callable[[ParentRef], Iterable[str]],
394
- ) -> Iterable[T]:
271
+ ) -> Iterable[TNamed]:
395
272
  """Helper method to compute the set of all objects applicable
396
273
  to the DiagLayer if these objects are subject to the value
397
274
  inheritance mechanism
@@ -410,66 +287,72 @@ class DiagLayer:
410
287
 
411
288
  """
412
289
 
413
- result_dict: Dict[str, T] = {}
290
+ local_objects = get_local_objects(self)
291
+ local_object_short_names = {x.short_name for x in local_objects}
292
+ result_dict: Dict[str, Tuple[TNamed, DiagLayer]] = {}
414
293
 
415
294
  # populate the result dictionary with the inherited objects
416
- #
417
- # TODO (?): make sure that there are no "illegal" collisions
418
- # i.e., different objects with the same short name stemming
419
- # from parent layers exhibiting the same priority that are not
420
- # overwritten by a locally defined object. (IMO, this is quite
421
- # a corner case.)
422
- for parent_ref in self._get_parent_refs_sorted_by_priority():
295
+ for parent_ref in self._get_parent_refs_sorted_by_priority(reverse=True):
423
296
  parent_dl = parent_ref.layer
424
- for dc in parent_dl._compute_available_objects(get_local_objects, get_not_inherited):
425
- result_dict[dc.short_name] = dc # type: ignore[attr-defined]
426
-
427
- # remove the explictly not inherited objects
428
- for sn in get_not_inherited(parent_ref):
429
- if sn in result_dict:
430
- del result_dict[sn]
431
297
 
432
- # consider the locally defined objects (override the
433
- # inherited entries or add new ones)
434
- for obj in get_local_objects(self):
435
- result_dict[obj.short_name] = obj # type: ignore[attr-defined]
298
+ # retrieve the set of short names of the objects which we
299
+ # are not supposed to inherit
300
+ not_inherited_short_names = set(get_not_inherited(parent_ref))
436
301
 
437
- return result_dict.values()
438
-
439
- def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
440
- """Return the list of locally defined diagnostic communications.
441
-
442
- This is not completely trivial as it requires to resolving the
443
- references specified in the <DIAG-COMMS> XML tag.
444
- """
445
- result_dict: Dict[str, DiagComm] = {}
446
-
447
- # TODO (?): add objects from the import-refs
448
-
449
- for dc_proxy in self.diag_layer_raw.diag_comms:
450
- if isinstance(dc_proxy, OdxLinkRef):
451
- dc = odxlinks.resolve(dc_proxy)
452
- else:
453
- dc = dc_proxy
454
-
455
- odxassert(isinstance(dc, DiagComm))
456
- odxassert(
457
- dc.short_name not in result_dict,
458
- f"Multiple definitions of DIAG-COMM '{dc.short_name}' in "
459
- f"layer '{self.short_name}'")
460
- result_dict[dc.short_name] = dc
461
-
462
- return result_dict.values()
463
-
464
- def _get_local_unit_groups(self) -> Iterable[UnitGroup]:
465
- if self.diag_layer_raw.diag_data_dictionary_spec is None:
466
- return []
467
-
468
- unit_spec = self.diag_layer_raw.diag_data_dictionary_spec.unit_spec
469
- if unit_spec is None:
470
- return []
302
+ # compute the list of objects which we are supposed to
303
+ # inherit from this diagnostic layer
304
+ inherited_objects = [
305
+ x
306
+ for x in parent_dl._compute_available_objects(get_local_objects, get_not_inherited)
307
+ if x.short_name not in not_inherited_short_names
308
+ ]
471
309
 
472
- return unit_spec.unit_groups
310
+ # update the result set with the objects from the current parent_ref
311
+ for obj in inherited_objects:
312
+
313
+ # no object with the given short name currently
314
+ # exits. add it to the result set and continue
315
+ if obj.short_name not in result_dict:
316
+ result_dict[obj.short_name] = (obj, parent_dl)
317
+ continue
318
+
319
+ # if an object with a given name already exists,
320
+ # there's no problem if it was inherited from a parent
321
+ # of different priority than the one currently
322
+ # considered
323
+ orig_prio = result_dict[obj.short_name][1].variant_type.inheritance_priority
324
+ new_prio = parent_dl.variant_type.inheritance_priority
325
+ if new_prio < orig_prio:
326
+ continue
327
+ elif orig_prio < new_prio:
328
+ result_dict[obj.short_name] = (obj, parent_dl)
329
+ continue
330
+
331
+ # if there is a conflict on the same priority level,
332
+ # it does not matter if the object is overridden
333
+ # locally anyway...
334
+ if obj.short_name in local_object_short_names:
335
+ continue
336
+
337
+ # if all of these conditions do not apply, and if the
338
+ # inherited objects are identical, there is no
339
+ # conflict. (note that value comparisons of complete
340
+ # complex objects tend to be expensive, so this test
341
+ # is done last.)
342
+ if obj == result_dict[obj.short_name][0]:
343
+ continue
344
+
345
+ odxraise(f"Diagnostic layer {self.short_name} cannot inherit object "
346
+ f"{obj.short_name} due to an unresolveable inheritance conflict between "
347
+ f"parent layers {result_dict[obj.short_name][1].short_name} "
348
+ f"and {parent_dl.short_name}")
349
+
350
+ # add the locally defined entries, overriding the inherited
351
+ # ones if necessary
352
+ for obj in local_objects:
353
+ result_dict[obj.short_name] = (obj, self)
354
+
355
+ return [x[0] for x in result_dict.values()]
473
356
 
474
357
  def _compute_available_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
475
358
 
@@ -550,6 +433,70 @@ class DiagLayer:
550
433
  # </value inheritance mechanism helpers>
551
434
  #####
552
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
+
553
500
  #####
554
501
  # <communication parameter handling>
555
502
  #####
@@ -583,39 +530,45 @@ class DiagLayer:
583
530
  # parameters. First fetch the communication parameters from
584
531
  # low priority parents, then update with increasing priority.
585
532
  for parent_ref in self._get_parent_refs_sorted_by_priority():
586
- 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():
587
537
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
588
538
 
589
539
  # finally, handle the locally defined communication parameters
590
- for cp in self.diag_layer_raw.comparams:
540
+ for cp in getattr(self.hierarchy_element_raw, "comparam_refs", []):
591
541
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
592
542
 
593
543
  return list(com_params_dict.values())
594
544
 
595
545
  @property
596
- def comparams(self) -> NamedItemList[ComparamInstance]:
546
+ def comparam_refs(self) -> NamedItemList[ComparamInstance]:
597
547
  """All communication parameters applicable to this DiagLayer
598
548
 
599
549
  Note that, although communication parameters use inheritance,
600
550
  it is *not* the "value inheritance" scheme used by e.g. DOPs,
601
551
  tables, state charts, ...
602
552
  """
603
- return self._comparams
553
+ return self._comparam_refs
604
554
 
605
555
  @cached_property
606
- def protocols(self) -> NamedItemList["DiagLayer"]:
556
+ def protocols(self) -> NamedItemList["Protocol"]:
607
557
  """Return the set of all protocols which are applicable to the diagnostic layer
608
558
 
609
- Note that protocols are *not* explicitly inherited objects,
610
- 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
+
611
562
  """
612
- result_dict: Dict[str, DiagLayer] = {}
563
+ from .protocol import Protocol
613
564
 
614
- for parent_ref in self._get_parent_refs_sorted_by_priority():
615
- 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", []):
616
569
  result_dict[prot.short_name] = prot
617
570
 
618
- if self.diag_layer_raw.variant_type == DiagLayerType.PROTOCOL:
571
+ if isinstance(self, Protocol):
619
572
  result_dict[self.diag_layer_raw.short_name] = self
620
573
 
621
574
  return NamedItemList(result_dict.values())
@@ -624,20 +577,22 @@ class DiagLayer:
624
577
  self,
625
578
  cp_short_name: str,
626
579
  *,
627
- protocol: Optional[Union[str, "DiagLayer"]] = None,
580
+ protocol: Optional[Union[str, "Protocol"]] = None,
628
581
  ) -> Optional[ComparamInstance]:
629
582
  """Find a specific communication parameter according to some criteria.
630
583
 
631
584
  Setting a given parameter to `None` means "don't care"."""
632
585
 
586
+ from .protocol import Protocol
587
+
633
588
  protocol_name: Optional[str]
634
- if isinstance(protocol, DiagLayer):
589
+ if isinstance(protocol, Protocol):
635
590
  protocol_name = protocol.short_name
636
591
  else:
637
592
  protocol_name = protocol
638
593
 
639
594
  # determine the set of applicable communication parameters
640
- 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]
641
596
  if protocol_name is not None:
642
597
  cps = [cp for cp in cps if cp.protocol_snref in (None, protocol_name)]
643
598
 
@@ -655,7 +610,7 @@ class DiagLayer:
655
610
 
656
611
  def get_max_can_payload_size(self,
657
612
  protocol: Optional[Union[str,
658
- "DiagLayer"]] = None) -> Optional[int]:
613
+ "Protocol"]] = None) -> Optional[int]:
659
614
  """Return the maximum size of a CAN frame payload that can be
660
615
  transmitted in bytes.
661
616
 
@@ -685,13 +640,13 @@ class DiagLayer:
685
640
  # unexpected format of parameter value
686
641
  return 8
687
642
 
688
- def uses_can(self, protocol: Optional[Union[str, "DiagLayer"]] = None) -> bool:
643
+ def uses_can(self, protocol: Optional[Union[str, "Protocol"]] = None) -> bool:
689
644
  """
690
645
  Check if CAN ought to be used as the link layer protocol.
691
646
  """
692
647
  return self.get_can_receive_id(protocol=protocol) is not None
693
648
 
694
- 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:
695
650
  """Check if CAN-FD ought to be used.
696
651
 
697
652
  If the ECU is not using CAN-FD for the specified protocol, `False`
@@ -710,7 +665,7 @@ class DiagLayer:
710
665
 
711
666
  return "CANFD" in com_param.value
712
667
 
713
- 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]:
714
669
  """Baudrate of the CAN bus which is used by the ECU [bits/s]
715
670
 
716
671
  If the ECU is not using CAN for the specified protocol, None
@@ -728,7 +683,7 @@ class DiagLayer:
728
683
  return int(val)
729
684
 
730
685
  def get_can_fd_baudrate(self,
731
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
686
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
732
687
  """Data baudrate of the CAN bus which is used by the ECU [bits/s]
733
688
 
734
689
  If the ECU is not using CAN-FD for the specified protocol,
@@ -748,7 +703,7 @@ class DiagLayer:
748
703
  return int(val)
749
704
 
750
705
  def get_can_receive_id(self,
751
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
706
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
752
707
  """CAN ID to which the ECU listens for diagnostic messages"""
753
708
  com_param = self.get_comparam("CP_UniqueRespIdTable", protocol=protocol)
754
709
  if com_param is None:
@@ -767,11 +722,7 @@ class DiagLayer:
767
722
 
768
723
  return int(result)
769
724
 
770
- @deprecated(details="use get_can_receive_id()")
771
- def get_receive_id(self) -> Optional[int]:
772
- return self.get_can_receive_id()
773
-
774
- 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]:
775
726
  """CAN ID to which the ECU sends replies to diagnostic messages"""
776
727
 
777
728
  # this hopefully resolves to the 'CP_UniqueRespIdTable'
@@ -796,12 +747,8 @@ class DiagLayer:
796
747
 
797
748
  return int(result)
798
749
 
799
- @deprecated(details="use get_can_send_id()")
800
- def get_send_id(self) -> Optional[int]:
801
- return self.get_can_send_id()
802
-
803
750
  def get_can_func_req_id(self,
804
- protocol: Optional[Union[str, "DiagLayer"]] = None) -> Optional[int]:
751
+ protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
805
752
  """CAN Functional Request Id."""
806
753
  com_param = self.get_comparam("CP_CanFuncReqId", protocol=protocol)
807
754
  if com_param is None:
@@ -815,8 +762,8 @@ class DiagLayer:
815
762
  return int(result)
816
763
 
817
764
  def get_doip_logical_ecu_address(self,
818
- protocol: Optional[Union[str, "DiagLayer"]] = None
819
- ) -> Optional[int]:
765
+ protocol: Optional[Union[str,
766
+ "Protocol"]] = None) -> Optional[int]:
820
767
  """Return the address of the ECU when using functional addressing.
821
768
 
822
769
  The parameter protocol is used to distinguish between
@@ -847,7 +794,7 @@ class DiagLayer:
847
794
  return int(ecu_addr)
848
795
 
849
796
  def get_doip_logical_gateway_address(self,
850
- protocol: Optional[Union[str, "DiagLayer"]] = None
797
+ protocol: Optional[Union[str, "Protocol"]] = None
851
798
  ) -> Optional[int]:
852
799
  """The logical gateway address for the diagnosis over IP transport protocol"""
853
800
 
@@ -865,7 +812,7 @@ class DiagLayer:
865
812
  return int(result)
866
813
 
867
814
  def get_doip_logical_tester_address(self,
868
- protocol: Optional[Union[str, "DiagLayer"]] = None
815
+ protocol: Optional[Union[str, "Protocol"]] = None
869
816
  ) -> Optional[int]:
870
817
  """DoIp logical gateway address"""
871
818
 
@@ -883,7 +830,7 @@ class DiagLayer:
883
830
  return int(result)
884
831
 
885
832
  def get_doip_logical_functional_address(self,
886
- protocol: Optional[Union[str, "DiagLayer"]] = None
833
+ protocol: Optional[Union[str, "Protocol"]] = None
887
834
  ) -> Optional[int]:
888
835
  """The logical functional DoIP address of the ECU."""
889
836
 
@@ -904,7 +851,7 @@ class DiagLayer:
904
851
  return int(result)
905
852
 
906
853
  def get_doip_routing_activation_timeout(self,
907
- protocol: Optional[Union[str, "DiagLayer"]] = None
854
+ protocol: Optional[Union[str, "Protocol"]] = None
908
855
  ) -> Optional[float]:
909
856
  """The timout for the DoIP routing activation request in seconds"""
910
857
 
@@ -922,7 +869,7 @@ class DiagLayer:
922
869
  return float(result) / 1e6
923
870
 
924
871
  def get_doip_routing_activation_type(self,
925
- protocol: Optional[Union[str, "DiagLayer"]] = None
872
+ protocol: Optional[Union[str, "Protocol"]] = None
926
873
  ) -> Optional[int]:
927
874
  """The DoIP routing activation type
928
875
 
@@ -949,7 +896,7 @@ class DiagLayer:
949
896
 
950
897
  def get_tester_present_time(self,
951
898
  protocol: Optional[Union[str,
952
- "DiagLayer"]] = None) -> Optional[float]:
899
+ "Protocol"]] = None) -> Optional[float]:
953
900
  """Timeout on inactivity in seconds.
954
901
 
955
902
  This is defined by the communication parameter "CP_TesterPresentTime".
@@ -976,149 +923,3 @@ class DiagLayer:
976
923
  #####
977
924
  # </communication parameter handling>
978
925
  #####
979
-
980
- #####
981
- # <PDU decoding>
982
- #####
983
-
984
- @cached_property
985
- def _prefix_tree(self) -> PrefixTree:
986
- """Constructs the coded prefix tree of the services.
987
-
988
- Each leaf node is a list of `DiagService`s. (This is because
989
- navigating from a service to the request/ responses is easier
990
- than finding the service for a given request/response object.)
991
-
992
- Example:
993
- Let there be four services with corresponding requests:
994
- * Request 1 has the coded constant prefix `12 34`.
995
- * Request 2 has the coded constant prefix `12 34`.
996
- * Request 3 has the coded constant prefix `12 56`.
997
- * Request 4 has the coded constant prefix `12 56 00`.
998
-
999
- Then, the constructed prefix tree is the dict
1000
- ```
1001
- {0x12: {0x34: {-1: [<Service 1>, <Service 2>]},
1002
- 0x56: {-1: [<Service 3>],
1003
- 0x0: {-1: [<Service 4>]}
1004
- }}}
1005
- ```
1006
- Note, that the inner `-1` are constant to distinguish them
1007
- from possible service IDs.
1008
-
1009
- Also note, that it is actually allowed that
1010
- (a) SIDs for different services are the same like for service 1 and 2 (thus each leaf node is a list) and
1011
- (b) one SID is the prefix of another SID like for service 3 and 4 (thus the constant `-1` key).
1012
-
1013
- """
1014
- prefix_tree: PrefixTree = {}
1015
- for s in self.services:
1016
- # Compute prefixes for the service's request and all
1017
- # possible responses. We need to consider the global
1018
- # negative responses here, because they might contain
1019
- # MATCHING-REQUEST parameters. If these global responses
1020
- # do not contain such parameters, this will potentially
1021
- # result in an enormous amount of decoded messages for
1022
- # global negative responses. (I.e., one for each
1023
- # service. This can be avoided by specifying the
1024
- # corresponding request for `decode_response()`.)
1025
- request_prefix = b''
1026
- if s.request is not None:
1027
- request_prefix = s.request.coded_const_prefix()
1028
- prefixes = [request_prefix]
1029
- prefixes += [
1030
- x.coded_const_prefix(request_prefix=request_prefix) for x in chain(
1031
- s.positive_responses, s.negative_responses, self.global_negative_responses)
1032
- ]
1033
- for coded_prefix in prefixes:
1034
- self._extend_prefix_tree(prefix_tree, coded_prefix, s)
1035
-
1036
- return prefix_tree
1037
-
1038
- @staticmethod
1039
- def _extend_prefix_tree(prefix_tree: PrefixTree, coded_prefix: bytes,
1040
- service: DiagService) -> None:
1041
-
1042
- # make sure that tree has an entry for the given prefix
1043
- sub_tree = prefix_tree
1044
- for b in coded_prefix:
1045
- if b not in sub_tree:
1046
- sub_tree[b] = {}
1047
- sub_tree = cast(PrefixTree, sub_tree[b])
1048
-
1049
- # Store the object as in the prefix tree. This is done by
1050
- # assigning the list of possible objects to the key -1 of the
1051
- # dictionary (this is quite hacky...)
1052
- if sub_tree.get(-1) is None:
1053
- sub_tree[-1] = [service]
1054
- else:
1055
- cast(List[DiagService], sub_tree[-1]).append(service)
1056
-
1057
- def _find_services_for_uds(self, message: bytes) -> List[DiagService]:
1058
- prefix_tree = self._prefix_tree
1059
-
1060
- # Find matching service(s) in prefix tree
1061
- possible_services: List[DiagService] = []
1062
- for b in message:
1063
- if b in prefix_tree:
1064
- odxassert(isinstance(prefix_tree[b], dict))
1065
- prefix_tree = cast(PrefixTree, prefix_tree[b])
1066
- else:
1067
- break
1068
- if -1 in prefix_tree:
1069
- possible_services += cast(List[DiagService], prefix_tree[-1])
1070
- return possible_services
1071
-
1072
- def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> List[Message]:
1073
- decoded_messages: List[Message] = []
1074
-
1075
- for service in candidate_services:
1076
- try:
1077
- decoded_messages.append(service.decode_message(message))
1078
- except DecodeError:
1079
- # check if the message can be decoded as a global
1080
- # negative response for the service
1081
- for gnr in self.global_negative_responses:
1082
- try:
1083
- decoded_gnr = gnr.decode(message)
1084
- if not isinstance(decoded_gnr, dict):
1085
- raise DecodeError(f"Expected the decoded value of a global "
1086
- f"negative response to be a dictionary, "
1087
- f"got {type(decoded_gnr)} for {self.short_name}")
1088
-
1089
- decoded_messages.append(
1090
- Message(
1091
- coded_message=message,
1092
- service=service,
1093
- coding_object=gnr,
1094
- param_dict=decoded_gnr))
1095
- except DecodeError:
1096
- pass
1097
-
1098
- if len(decoded_messages) == 0:
1099
- raise DecodeError(
1100
- f"None of the services {candidate_services} could parse {message.hex()}.")
1101
-
1102
- return decoded_messages
1103
-
1104
- def decode(self, message: bytes) -> List[Message]:
1105
- candidate_services = self._find_services_for_uds(message)
1106
-
1107
- return self._decode(message, candidate_services)
1108
-
1109
- def decode_response(self, response: bytes, request: Union[bytes, Message]) -> List[Message]:
1110
- if isinstance(request, Message):
1111
- candidate_services = [request.service]
1112
- else:
1113
- if not isinstance(request, (bytes, bytearray)):
1114
- raise TypeError(f"Request parameter must have type "
1115
- f"Message, bytes or bytearray but was {type(request)}")
1116
- candidate_services = self._find_services_for_uds(request)
1117
- if candidate_services is None:
1118
- raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
1119
-
1120
- return self._decode(response, candidate_services)
1121
-
1122
- #####
1123
- # </PDU decoding>
1124
- #####