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
@@ -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
|
7
|
-
|
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
|
11
|
-
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
25
|
-
from
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from .
|
29
|
-
from .
|
30
|
-
|
31
|
-
|
32
|
-
from .
|
33
|
-
from .
|
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
|
-
|
36
|
+
TNamed = TypeVar("TNamed", bound=OdxNamed)
|
44
37
|
|
45
38
|
|
46
39
|
@dataclass
|
47
|
-
class DiagLayer:
|
48
|
-
"""This class
|
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
|
-
|
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,
|
59
|
-
|
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
|
-
|
62
|
-
return DiagLayer(diag_layer_raw=diag_layer_raw)
|
53
|
+
return HierarchyElement(diag_layer_raw=hierarchy_element_raw)
|
63
54
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
55
|
+
def __post_init__(self) -> None:
|
56
|
+
super().__post_init__()
|
57
|
+
|
58
|
+
self._global_negative_responses: NamedItemList[Response]
|
67
59
|
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
71
|
+
super()._resolve_odxlinks(odxlinks)
|
76
72
|
|
77
|
-
|
73
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
74
|
+
super()._resolve_snrefs(context)
|
78
75
|
|
79
|
-
def
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
#
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
-
|
363
|
-
|
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
|
-
|
368
|
-
|
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
|
-
|
373
|
-
|
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
|
-
|
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
|
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[
|
269
|
+
get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
|
393
270
|
get_not_inherited: Callable[[ParentRef], Iterable[str]],
|
394
|
-
) -> Iterable[
|
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
|
-
|
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
|
-
|
433
|
-
|
434
|
-
|
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
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
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.
|
553
|
+
return self._comparam_refs
|
604
554
|
|
605
555
|
@cached_property
|
606
|
-
def protocols(self) -> NamedItemList["
|
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
|
610
|
-
but the parent
|
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
|
-
|
563
|
+
from .protocol import Protocol
|
613
564
|
|
614
|
-
|
615
|
-
|
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
|
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, "
|
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,
|
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.
|
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
|
-
"
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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
|
-
|
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, "
|
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,
|
819
|
-
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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
|
-
"
|
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
|
-
#####
|