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.
- odxtools/__init__.py +7 -5
- odxtools/additionalaudience.py +3 -5
- odxtools/admindata.py +5 -7
- odxtools/audience.py +10 -13
- odxtools/basecomparam.py +3 -5
- odxtools/basicstructure.py +55 -241
- odxtools/cli/_parser_utils.py +16 -1
- odxtools/cli/_print_utils.py +169 -134
- odxtools/cli/browse.py +127 -103
- odxtools/cli/compare.py +114 -87
- odxtools/cli/decode.py +2 -1
- odxtools/cli/dummy_sub_parser.py +3 -1
- odxtools/cli/find.py +2 -1
- odxtools/cli/list.py +26 -16
- odxtools/cli/main.py +1 -0
- odxtools/cli/snoop.py +32 -6
- odxtools/codec.py +211 -0
- odxtools/commrelation.py +122 -0
- odxtools/companydata.py +5 -7
- odxtools/companydocinfo.py +7 -8
- odxtools/companyrevisioninfo.py +3 -5
- odxtools/companyspecificinfo.py +8 -9
- odxtools/comparam.py +4 -6
- odxtools/comparaminstance.py +14 -14
- odxtools/comparamspec.py +16 -54
- odxtools/comparamsubset.py +22 -62
- odxtools/complexcomparam.py +5 -7
- odxtools/compumethods/compucodecompumethod.py +63 -0
- odxtools/compumethods/compuconst.py +31 -0
- odxtools/compumethods/compudefaultvalue.py +27 -0
- odxtools/compumethods/compuinternaltophys.py +56 -0
- odxtools/compumethods/compuinversevalue.py +7 -0
- odxtools/compumethods/compumethod.py +94 -15
- odxtools/compumethods/compuphystointernal.py +56 -0
- odxtools/compumethods/compurationalcoeffs.py +20 -9
- odxtools/compumethods/compuscale.py +67 -32
- odxtools/compumethods/createanycompumethod.py +31 -172
- odxtools/compumethods/identicalcompumethod.py +31 -6
- odxtools/compumethods/limit.py +70 -36
- odxtools/compumethods/linearcompumethod.py +70 -181
- odxtools/compumethods/linearsegment.py +190 -0
- odxtools/compumethods/ratfunccompumethod.py +106 -0
- odxtools/compumethods/ratfuncsegment.py +87 -0
- odxtools/compumethods/scalelinearcompumethod.py +132 -26
- odxtools/compumethods/scaleratfunccompumethod.py +113 -0
- odxtools/compumethods/tabintpcompumethod.py +123 -92
- odxtools/compumethods/texttablecompumethod.py +117 -57
- odxtools/createanydiagcodedtype.py +10 -67
- odxtools/database.py +167 -87
- odxtools/dataobjectproperty.py +25 -32
- odxtools/decodestate.py +14 -17
- odxtools/description.py +47 -0
- odxtools/determinenumberofitems.py +4 -5
- odxtools/diagcodedtype.py +37 -106
- odxtools/diagcomm.py +24 -12
- odxtools/diagdatadictionaryspec.py +120 -96
- odxtools/diaglayercontainer.py +46 -54
- odxtools/diaglayers/basevariant.py +128 -0
- odxtools/diaglayers/basevariantraw.py +123 -0
- odxtools/diaglayers/diaglayer.py +432 -0
- odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
- odxtools/diaglayers/diaglayertype.py +42 -0
- odxtools/diaglayers/ecushareddata.py +96 -0
- odxtools/diaglayers/ecushareddataraw.py +87 -0
- odxtools/diaglayers/ecuvariant.py +124 -0
- odxtools/diaglayers/ecuvariantraw.py +129 -0
- odxtools/diaglayers/functionalgroup.py +110 -0
- odxtools/diaglayers/functionalgroupraw.py +106 -0
- odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
- odxtools/diaglayers/hierarchyelementraw.py +58 -0
- odxtools/diaglayers/protocol.py +64 -0
- odxtools/diaglayers/protocolraw.py +91 -0
- odxtools/diagnostictroublecode.py +8 -9
- odxtools/diagservice.py +57 -44
- odxtools/diagvariable.py +113 -0
- odxtools/docrevision.py +5 -7
- odxtools/dopbase.py +15 -15
- odxtools/dtcdop.py +170 -50
- odxtools/dynamicendmarkerfield.py +134 -0
- odxtools/dynamiclengthfield.py +47 -42
- odxtools/dyndefinedspec.py +177 -0
- odxtools/dynenddopref.py +38 -0
- odxtools/ecuvariantmatcher.py +6 -7
- odxtools/element.py +13 -15
- odxtools/encodestate.py +199 -22
- odxtools/endofpdufield.py +31 -18
- odxtools/environmentdata.py +8 -1
- odxtools/environmentdatadescription.py +198 -36
- odxtools/exceptions.py +11 -2
- odxtools/field.py +10 -10
- odxtools/functionalclass.py +3 -5
- odxtools/inputparam.py +3 -12
- odxtools/internalconstr.py +14 -5
- odxtools/isotp_state_machine.py +14 -6
- odxtools/leadinglengthinfotype.py +37 -18
- odxtools/library.py +66 -0
- odxtools/loadfile.py +64 -0
- odxtools/matchingparameter.py +3 -3
- odxtools/message.py +0 -7
- odxtools/minmaxlengthtype.py +61 -33
- odxtools/modification.py +3 -5
- odxtools/multiplexer.py +135 -75
- odxtools/multiplexercase.py +39 -18
- odxtools/multiplexerdefaultcase.py +15 -12
- odxtools/multiplexerswitchkey.py +4 -5
- odxtools/nameditemlist.py +33 -8
- odxtools/negoutputparam.py +3 -5
- odxtools/odxcategory.py +83 -0
- odxtools/odxlink.py +62 -53
- odxtools/odxtypes.py +93 -8
- odxtools/outputparam.py +5 -16
- odxtools/parameterinfo.py +219 -61
- odxtools/parameters/codedconstparameter.py +45 -32
- odxtools/parameters/createanyparameter.py +19 -193
- odxtools/parameters/dynamicparameter.py +25 -4
- odxtools/parameters/lengthkeyparameter.py +83 -25
- odxtools/parameters/matchingrequestparameter.py +48 -18
- odxtools/parameters/nrcconstparameter.py +76 -54
- odxtools/parameters/parameter.py +97 -73
- odxtools/parameters/parameterwithdop.py +41 -38
- odxtools/parameters/physicalconstantparameter.py +41 -20
- odxtools/parameters/reservedparameter.py +36 -18
- odxtools/parameters/systemparameter.py +74 -7
- odxtools/parameters/tableentryparameter.py +47 -7
- odxtools/parameters/tablekeyparameter.py +142 -55
- odxtools/parameters/tablestructparameter.py +79 -58
- odxtools/parameters/valueparameter.py +39 -21
- odxtools/paramlengthinfotype.py +56 -33
- odxtools/parentref.py +20 -3
- odxtools/physicaldimension.py +3 -8
- odxtools/progcode.py +26 -11
- odxtools/protstack.py +3 -5
- odxtools/py.typed +0 -0
- odxtools/relateddoc.py +7 -9
- odxtools/request.py +120 -10
- odxtools/response.py +123 -23
- odxtools/scaleconstr.py +14 -8
- odxtools/servicebinner.py +1 -1
- odxtools/singleecujob.py +12 -10
- odxtools/snrefcontext.py +29 -0
- odxtools/specialdata.py +3 -5
- odxtools/specialdatagroup.py +7 -9
- odxtools/specialdatagroupcaption.py +3 -6
- odxtools/standardlengthtype.py +80 -14
- odxtools/state.py +3 -5
- odxtools/statechart.py +13 -19
- odxtools/statetransition.py +8 -18
- odxtools/staticfield.py +107 -0
- odxtools/subcomponent.py +288 -0
- odxtools/swvariable.py +21 -0
- odxtools/table.py +9 -9
- odxtools/tablerow.py +30 -15
- odxtools/teammember.py +3 -5
- odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
- odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
- odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
- odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
- odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
- odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
- odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
- odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
- odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
- odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
- odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
- odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
- odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
- odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
- odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
- odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
- odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
- odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
- odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
- odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
- odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
- odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
- odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
- odxtools/templates/macros/printMux.xml.jinja2 +5 -3
- odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
- odxtools/templates/macros/printParam.xml.jinja2 +18 -19
- odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
- odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
- odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
- odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
- odxtools/templates/macros/printService.xml.jinja2 +3 -2
- odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
- odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
- odxtools/templates/macros/printState.xml.jinja2 +1 -1
- odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
- odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
- odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
- odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
- odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
- odxtools/templates/macros/printTable.xml.jinja2 +4 -5
- odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
- odxtools/uds.py +2 -10
- odxtools/unit.py +4 -8
- odxtools/unitgroup.py +3 -5
- odxtools/unitspec.py +17 -17
- odxtools/utils.py +38 -20
- odxtools/variablegroup.py +32 -0
- odxtools/version.py +2 -2
- odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
- odxtools/xdoc.py +3 -5
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
- odxtools-9.3.0.dist-info/RECORD +228 -0
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
- odxtools/createcompanydatas.py +0 -17
- odxtools/createsdgs.py +0 -19
- odxtools/diaglayertype.py +0 -30
- odxtools/load_file.py +0 -13
- odxtools/load_odx_d_file.py +0 -6
- odxtools/load_pdx_file.py +0 -8
- odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
- odxtools-6.6.1.dist-info/RECORD +0 -180
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
- {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
|
+
#####
|