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
@@ -0,0 +1,123 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional, Union
4
+ from xml.etree import ElementTree
5
+
6
+ from ..diagvariable import DiagVariable
7
+ from ..dyndefinedspec import DynDefinedSpec
8
+ from ..exceptions import odxraise
9
+ from ..nameditemlist import NamedItemList
10
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
11
+ from ..parentref import ParentRef
12
+ from ..snrefcontext import SnRefContext
13
+ from ..utils import dataclass_fields_asdict
14
+ from ..variablegroup import VariableGroup
15
+ from .hierarchyelementraw import HierarchyElementRaw
16
+
17
+
18
+ @dataclass
19
+ class BaseVariantRaw(HierarchyElementRaw):
20
+ """This is a diagnostic layer for common functionality of an ECU
21
+ """
22
+
23
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]]
24
+ variable_groups: NamedItemList[VariableGroup]
25
+ dyn_defined_spec: Optional[DynDefinedSpec]
26
+ # TODO: base_variant_pattern: Optional[BaseVariantPattern]
27
+ parent_refs: List[ParentRef]
28
+
29
+ @property
30
+ def diag_variables(self) -> NamedItemList[DiagVariable]:
31
+ return self._diag_variables
32
+
33
+ @staticmethod
34
+ def from_et(et_element: ElementTree.Element,
35
+ doc_frags: List[OdxDocFragment]) -> "BaseVariantRaw":
36
+ # objects contained by diagnostic layers exibit an additional
37
+ # document fragment for the diag layer, so we use the document
38
+ # fragments of the odx id of the diag layer for IDs of
39
+ # contained objects.
40
+ her = HierarchyElementRaw.from_et(et_element, doc_frags)
41
+ kwargs = dataclass_fields_asdict(her)
42
+ doc_frags = her.odx_id.doc_fragments
43
+
44
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
45
+ if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
46
+ for dv_proxy_elem in dv_elems:
47
+ dv_proxy: Union[OdxLinkRef, DiagVariable]
48
+ if dv_proxy_elem.tag == "DIAG-VARIABLE-REF":
49
+ dv_proxy = OdxLinkRef.from_et(dv_proxy_elem, doc_frags)
50
+ elif dv_proxy_elem.tag == "DIAG-VARIABLE":
51
+ dv_proxy = DiagVariable.from_et(dv_proxy_elem, doc_frags)
52
+ else:
53
+ odxraise()
54
+
55
+ diag_variables_raw.append(dv_proxy)
56
+
57
+ variable_groups = NamedItemList([
58
+ VariableGroup.from_et(vg_elem, doc_frags)
59
+ for vg_elem in et_element.iterfind("VARIABLE-GROUPS/VARIABLE-GROUP")
60
+ ])
61
+
62
+ dyn_defined_spec = None
63
+ if (dds_elem := et_element.find("DYN-DEFINED-SPEC")) is not None:
64
+ dyn_defined_spec = DynDefinedSpec.from_et(dds_elem, doc_frags)
65
+
66
+ parent_refs = [
67
+ ParentRef.from_et(pr_elem, doc_frags)
68
+ for pr_elem in et_element.iterfind("PARENT-REFS/PARENT-REF")
69
+ ]
70
+
71
+ return BaseVariantRaw(
72
+ diag_variables_raw=diag_variables_raw,
73
+ variable_groups=variable_groups,
74
+ dyn_defined_spec=dyn_defined_spec,
75
+ parent_refs=parent_refs,
76
+ **kwargs)
77
+
78
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
79
+ result = super()._build_odxlinks()
80
+
81
+ for dv_proxy in self.diag_variables_raw:
82
+ if not isinstance(dv_proxy, OdxLinkRef):
83
+ result.update(dv_proxy._build_odxlinks())
84
+
85
+ if self.dyn_defined_spec is not None:
86
+ result.update(self.dyn_defined_spec._build_odxlinks())
87
+
88
+ for parent_ref in self.parent_refs:
89
+ result.update(parent_ref._build_odxlinks())
90
+
91
+ return result
92
+
93
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
94
+ super()._resolve_odxlinks(odxlinks)
95
+
96
+ self._diag_variables: NamedItemList[DiagVariable] = NamedItemList()
97
+ for dv_proxy in self.diag_variables_raw:
98
+ if isinstance(dv_proxy, OdxLinkRef):
99
+ dv = odxlinks.resolve(dv_proxy, DiagVariable)
100
+ else:
101
+ dv_proxy._resolve_odxlinks(odxlinks)
102
+ dv = dv_proxy
103
+
104
+ self._diag_variables.append(dv)
105
+
106
+ if self.dyn_defined_spec is not None:
107
+ self.dyn_defined_spec._resolve_odxlinks(odxlinks)
108
+
109
+ for parent_ref in self.parent_refs:
110
+ parent_ref._resolve_odxlinks(odxlinks)
111
+
112
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
113
+ super()._resolve_snrefs(context)
114
+
115
+ for dv_proxy in self.diag_variables_raw:
116
+ if not isinstance(dv_proxy, OdxLinkRef):
117
+ dv_proxy._resolve_snrefs(context)
118
+
119
+ if self.dyn_defined_spec is not None:
120
+ self.dyn_defined_spec._resolve_snrefs(context)
121
+
122
+ for parent_ref in self.parent_refs:
123
+ parent_ref._resolve_snrefs(context)
@@ -0,0 +1,432 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from copy import copy, deepcopy
3
+ from dataclasses import dataclass
4
+ from functools import cached_property
5
+ from itertools import chain
6
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
7
+ from xml.etree import ElementTree
8
+
9
+ from ..admindata import AdminData
10
+ from ..companydata import CompanyData
11
+ from ..description import Description
12
+ from ..diagcomm import DiagComm
13
+ from ..diagdatadictionaryspec import DiagDataDictionarySpec
14
+ from ..diagservice import DiagService
15
+ from ..exceptions import DecodeError, odxassert, odxraise
16
+ from ..library import Library
17
+ from ..message import Message
18
+ from ..nameditemlist import NamedItemList, TNamed
19
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
20
+ from ..parentref import ParentRef
21
+ from ..request import Request
22
+ from ..response import Response
23
+ from ..servicebinner import ServiceBinner
24
+ from ..singleecujob import SingleEcuJob
25
+ from ..snrefcontext import SnRefContext
26
+ from ..specialdatagroup import SpecialDataGroup
27
+ from ..subcomponent import SubComponent
28
+ from ..unitgroup import UnitGroup
29
+ from .diaglayerraw import DiagLayerRaw
30
+ from .diaglayertype import DiagLayerType
31
+
32
+ PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
33
+
34
+
35
+ @dataclass
36
+ class DiagLayer:
37
+ """This class represents a "logical view" upon a diagnostic layer
38
+ according to the ODX standard.
39
+
40
+ i.e. it handles the value inheritance, communication parameters,
41
+ encoding/decoding of data, etc.
42
+ """
43
+
44
+ diag_layer_raw: DiagLayerRaw
45
+
46
+ @staticmethod
47
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
48
+ diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
49
+
50
+ # Create DiagLayer
51
+ return DiagLayer(diag_layer_raw=diag_layer_raw)
52
+
53
+ def __post_init__(self) -> None:
54
+ if self.diag_layer_raw.diag_data_dictionary_spec is None:
55
+ # create an empry DiagDataDictionarySpec object if the raw
56
+ # layer does not define a DDDS...
57
+ self._diag_data_dictionary_spec = DiagDataDictionarySpec(
58
+ admin_data=None,
59
+ data_object_props=NamedItemList(),
60
+ dtc_dops=NamedItemList(),
61
+ structures=NamedItemList(),
62
+ static_fields=NamedItemList(),
63
+ end_of_pdu_fields=NamedItemList(),
64
+ dynamic_endmarker_fields=NamedItemList(),
65
+ dynamic_length_fields=NamedItemList(),
66
+ tables=NamedItemList(),
67
+ env_data_descs=NamedItemList(),
68
+ env_datas=NamedItemList(),
69
+ muxs=NamedItemList(),
70
+ unit_spec=None,
71
+ sdgs=[])
72
+ else:
73
+ self._diag_data_dictionary_spec = self.diag_layer_raw.diag_data_dictionary_spec
74
+
75
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
76
+ """Construct a mapping from IDs to all objects that are contained in this diagnostic layer."""
77
+ result = self.diag_layer_raw._build_odxlinks()
78
+
79
+ # we want to get the full diag layer, not just the raw layer
80
+ # when referencing...
81
+ result[self.odx_id] = self
82
+
83
+ return result
84
+
85
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
86
+ """Recursively resolve all ODXLINK references."""
87
+
88
+ # deal with the import references: these basically extend the
89
+ # pool of objects that are referenceable without having to
90
+ # explicitly specify the DOCREF attribute in the
91
+ # reference. This mechanism can thus be seen as a kind of
92
+ # "poor man's inheritance".
93
+ if self.import_refs:
94
+ imported_links: Dict[OdxLinkId, Any] = {}
95
+ for import_ref in self.import_refs:
96
+ imported_dl = odxlinks.resolve(import_ref, DiagLayer)
97
+
98
+ odxassert(
99
+ imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
100
+ f"Tried to import references from diagnostic layer "
101
+ f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
102
+ f"Only ECU-SHARED-DATA layers may be referenced using the "
103
+ f"IMPORT-REF mechanism")
104
+
105
+ # TODO: ensure that the imported diagnostic layer has
106
+ # not been referenced in any PARENT-REF of the current
107
+ # layer or any of its parents.
108
+
109
+ # TODO: detect and complain about cyclic IMPORT-REFs
110
+
111
+ # TODO (?): detect conflicts with locally-defined
112
+ # objects
113
+
114
+ imported_dl_links = imported_dl._build_odxlinks()
115
+ for link_id, obj in imported_dl_links.items():
116
+ # the imported objects shall behave as if they
117
+ # were defined by the importing layer. IOW, they
118
+ # must be visible in the same document fragments.
119
+ link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
120
+ imported_links[link_id] = obj
121
+
122
+ # We need to copy the odxlink database here since this
123
+ # function must not modify its argument because the
124
+ # imported references only apply within this specific
125
+ # diagnostic layer
126
+ extended_odxlinks = copy(odxlinks)
127
+ extended_odxlinks.update(imported_links, overwrite=False)
128
+
129
+ self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
130
+ return
131
+
132
+ self.diag_layer_raw._resolve_odxlinks(odxlinks)
133
+
134
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
135
+ self.diag_layer_raw._resolve_snrefs(context)
136
+
137
+ def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
138
+ """Return the list of locally defined diagnostic communications.
139
+
140
+ This is not completely trivial as it requires to resolving the
141
+ references specified in the <DIAG-COMMS> XML tag.
142
+ """
143
+ return self.diag_layer_raw.diag_comms
144
+
145
+ def _get_local_unit_groups(self) -> Iterable[UnitGroup]:
146
+ if self.diag_layer_raw.diag_data_dictionary_spec is None:
147
+ return []
148
+
149
+ unit_spec = self.diag_layer_raw.diag_data_dictionary_spec.unit_spec
150
+ if unit_spec is None:
151
+ return []
152
+
153
+ return unit_spec.unit_groups
154
+
155
+ def _compute_available_objects(
156
+ self,
157
+ get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
158
+ get_not_inherited: Callable[[ParentRef], Iterable[str]],
159
+ ) -> Iterable[TNamed]:
160
+ """Helper method to compute the set of all objects applicable
161
+ to the DiagLayer if these objects are subject to the value
162
+ inheritance mechanism
163
+
164
+ This is the simplified version for diag layers which do not
165
+ have parents and thus do not deal with value inheritance
166
+ (i.e., ECU-SHARED-DATA).
167
+
168
+ """
169
+ return get_local_objects(self)
170
+
171
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
172
+ """Create a deep copy of the diagnostic layer
173
+
174
+ Note that the copied diagnostic layer is not fully
175
+ initialized, so `_finalize_init()` should to be called on it
176
+ before it can be used normally.
177
+ """
178
+ cls = self.__class__
179
+ result = cls.__new__(cls)
180
+ memo[id(self)] = result
181
+
182
+ result.diag_layer_raw = deepcopy(self.diag_layer_raw, memo)
183
+
184
+ return result
185
+
186
+ #####
187
+ # <convenience functionality>
188
+ #####
189
+ @cached_property
190
+ def service_groups(self) -> ServiceBinner:
191
+ return ServiceBinner(self.services)
192
+
193
+ #####
194
+ # </convenience functionality>
195
+ #####
196
+
197
+ #####
198
+ # <properties forwarded to the "raw" diag layer>
199
+ #####
200
+ @property
201
+ def variant_type(self) -> DiagLayerType:
202
+ return self.diag_layer_raw.variant_type
203
+
204
+ @property
205
+ def odx_id(self) -> OdxLinkId:
206
+ return self.diag_layer_raw.odx_id
207
+
208
+ @property
209
+ def short_name(self) -> str:
210
+ return self.diag_layer_raw.short_name
211
+
212
+ @property
213
+ def long_name(self) -> Optional[str]:
214
+ return self.diag_layer_raw.long_name
215
+
216
+ @property
217
+ def description(self) -> Optional[Description]:
218
+ return self.diag_layer_raw.description
219
+
220
+ @property
221
+ def admin_data(self) -> Optional[AdminData]:
222
+ return self.diag_layer_raw.admin_data
223
+
224
+ @property
225
+ def diag_comms(self) -> NamedItemList[DiagComm]:
226
+ return self.diag_layer_raw.diag_comms
227
+
228
+ @property
229
+ def services(self) -> NamedItemList[DiagService]:
230
+ return self.diag_layer_raw.services
231
+
232
+ @property
233
+ def diag_services(self) -> NamedItemList[DiagService]:
234
+ return self.diag_layer_raw.diag_services
235
+
236
+ @property
237
+ def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
238
+ return self.diag_layer_raw.single_ecu_jobs
239
+
240
+ @property
241
+ def company_datas(self) -> NamedItemList[CompanyData]:
242
+ return self.diag_layer_raw.company_datas
243
+
244
+ @property
245
+ def requests(self) -> NamedItemList[Request]:
246
+ return self.diag_layer_raw.requests
247
+
248
+ @property
249
+ def positive_responses(self) -> NamedItemList[Response]:
250
+ return self.diag_layer_raw.positive_responses
251
+
252
+ @property
253
+ def negative_responses(self) -> NamedItemList[Response]:
254
+ return self.diag_layer_raw.negative_responses
255
+
256
+ @property
257
+ def global_negative_responses(self) -> NamedItemList[Response]:
258
+ return self.diag_layer_raw.global_negative_responses
259
+
260
+ @property
261
+ def import_refs(self) -> List[OdxLinkRef]:
262
+ return self.diag_layer_raw.import_refs
263
+
264
+ @property
265
+ def libraries(self) -> NamedItemList[Library]:
266
+ return self.diag_layer_raw.libraries
267
+
268
+ @property
269
+ def sub_components(self) -> NamedItemList[SubComponent]:
270
+ return self.diag_layer_raw.sub_components
271
+
272
+ @property
273
+ def sdgs(self) -> List[SpecialDataGroup]:
274
+ return self.diag_layer_raw.sdgs
275
+
276
+ @property
277
+ def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
278
+ """The DiagDataDictionarySpec applicable to this DiagLayer"""
279
+ return self._diag_data_dictionary_spec
280
+
281
+ #####
282
+ # </properties forwarded to the "raw" diag layer>
283
+ #####
284
+
285
+ #####
286
+ # <PDU decoding>
287
+ #####
288
+ @cached_property
289
+ def _prefix_tree(self) -> PrefixTree:
290
+ """Constructs the coded prefix tree of the services.
291
+
292
+ Each leaf node is a list of `DiagService`s. (This is because
293
+ navigating from a service to the request/ responses is easier
294
+ than finding the service for a given request/response object.)
295
+
296
+ Example:
297
+ Let there be four services with corresponding requests:
298
+ * Request 1 has the coded constant prefix `12 34`.
299
+ * Request 2 has the coded constant prefix `12 34`.
300
+ * Request 3 has the coded constant prefix `12 56`.
301
+ * Request 4 has the coded constant prefix `12 56 00`.
302
+
303
+ Then, the constructed prefix tree is the dict
304
+ ```
305
+ {0x12: {0x34: {-1: [<Service 1>, <Service 2>]},
306
+ 0x56: {-1: [<Service 3>],
307
+ 0x0: {-1: [<Service 4>]}
308
+ }}}
309
+ ```
310
+ Note, that the inner `-1` are constant to distinguish them
311
+ from possible service IDs.
312
+
313
+ Also note, that it is actually allowed that
314
+ (a) SIDs for different services are the same like for service
315
+ 1 and 2 (thus each leaf node is a list) and
316
+ (b) one SID is the prefix of another SID like for service 3
317
+ and 4 (thus the constant `-1` key).
318
+
319
+ """
320
+ prefix_tree: PrefixTree = {}
321
+ for s in self.services:
322
+ # Compute prefixes for the service's request and all
323
+ # possible responses. We need to consider the global
324
+ # negative responses here, because they might contain
325
+ # MATCHING-REQUEST parameters. If these global responses
326
+ # do not contain such parameters, this will potentially
327
+ # result in an enormous amount of decoded messages for
328
+ # global negative responses. (I.e., one for each
329
+ # service. This can be avoided by specifying the
330
+ # corresponding request for `decode_response()`.)
331
+ request_prefix = b''
332
+ if s.request is not None:
333
+ request_prefix = s.request.coded_const_prefix()
334
+ prefixes = [request_prefix]
335
+ gnrs = getattr(self, "global_negative_responses", [])
336
+ prefixes += [
337
+ x.coded_const_prefix(request_prefix=request_prefix)
338
+ for x in chain(s.positive_responses, s.negative_responses, gnrs)
339
+ ]
340
+ for coded_prefix in prefixes:
341
+ self._extend_prefix_tree(prefix_tree, coded_prefix, s)
342
+
343
+ return prefix_tree
344
+
345
+ @staticmethod
346
+ def _extend_prefix_tree(prefix_tree: PrefixTree, coded_prefix: bytes,
347
+ service: DiagService) -> None:
348
+
349
+ # make sure that tree has an entry for the given prefix
350
+ sub_tree = prefix_tree
351
+ for b in coded_prefix:
352
+ if b not in sub_tree:
353
+ sub_tree[b] = {}
354
+ sub_tree = cast(PrefixTree, sub_tree[b])
355
+
356
+ # Store the object as in the prefix tree. This is done by
357
+ # assigning the list of possible objects to the key -1 of the
358
+ # dictionary (this is quite hacky...)
359
+ if sub_tree.get(-1) is None:
360
+ sub_tree[-1] = [service]
361
+ else:
362
+ cast(List[DiagService], sub_tree[-1]).append(service)
363
+
364
+ def _find_services_for_uds(self, message: bytes) -> List[DiagService]:
365
+ prefix_tree = self._prefix_tree
366
+
367
+ # Find matching service(s) in prefix tree
368
+ possible_services: List[DiagService] = []
369
+ for b in message:
370
+ if b in prefix_tree:
371
+ odxassert(isinstance(prefix_tree[b], dict))
372
+ prefix_tree = cast(PrefixTree, prefix_tree[b])
373
+ else:
374
+ break
375
+ if -1 in prefix_tree:
376
+ possible_services += cast(List[DiagService], prefix_tree[-1])
377
+ return possible_services
378
+
379
+ def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> List[Message]:
380
+ decoded_messages: List[Message] = []
381
+
382
+ for service in candidate_services:
383
+ try:
384
+ decoded_messages.append(service.decode_message(message))
385
+ except DecodeError as e:
386
+ # check if the message can be decoded as a global
387
+ # negative response for the service
388
+ gnr_found = False
389
+ for gnr in self.global_negative_responses:
390
+ try:
391
+ decoded_gnr = gnr.decode(message)
392
+ gnr_found = True
393
+ if not isinstance(decoded_gnr, dict):
394
+ odxraise(
395
+ f"Expected the decoded value of a global "
396
+ f"negative response to be a dictionary, "
397
+ f"got {type(decoded_gnr)} for {self.short_name}", DecodeError)
398
+
399
+ decoded_messages.append(
400
+ Message(
401
+ coded_message=message,
402
+ service=service,
403
+ coding_object=gnr,
404
+ param_dict=decoded_gnr))
405
+ except DecodeError:
406
+ pass
407
+
408
+ if not gnr_found:
409
+ raise e
410
+
411
+ if len(decoded_messages) == 0:
412
+ raise DecodeError(
413
+ f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
414
+ )
415
+
416
+ return decoded_messages
417
+
418
+ def decode(self, message: bytes) -> List[Message]:
419
+ candidate_services = self._find_services_for_uds(message)
420
+
421
+ return self._decode(message, candidate_services)
422
+
423
+ def decode_response(self, response: bytes, request: bytes) -> List[Message]:
424
+ candidate_services = self._find_services_for_uds(request)
425
+ if candidate_services is None:
426
+ raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
427
+
428
+ return self._decode(response, candidate_services)
429
+
430
+ #####
431
+ # </PDU decoding>
432
+ #####