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,80 +1,140 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List,
|
3
|
+
from typing import List, cast
|
4
|
+
from xml.etree import ElementTree
|
4
5
|
|
5
|
-
from ..exceptions import DecodeError, EncodeError, odxassert
|
6
|
+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
7
|
+
from ..odxlink import OdxDocFragment
|
6
8
|
from ..odxtypes import AtomicOdxType, DataType
|
7
|
-
from
|
9
|
+
from ..utils import dataclass_fields_asdict
|
10
|
+
from .compumethod import CompuCategory, CompuMethod
|
8
11
|
from .compuscale import CompuScale
|
9
12
|
|
10
13
|
|
11
14
|
@dataclass
|
12
15
|
class TexttableCompuMethod(CompuMethod):
|
16
|
+
"""Text table compute methods translate numbers to human readable
|
17
|
+
textual descriptions.
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.7.
|
20
|
+
|
21
|
+
"""
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
25
|
+
internal_type: DataType,
|
26
|
+
physical_type: DataType) -> "TexttableCompuMethod":
|
27
|
+
cm = CompuMethod.compu_method_from_et(
|
28
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
29
|
+
kwargs = dataclass_fields_asdict(cm)
|
30
|
+
|
31
|
+
return TexttableCompuMethod(**kwargs)
|
18
32
|
|
19
33
|
def __post_init__(self) -> None:
|
20
|
-
odxassert(self.
|
21
|
-
"
|
34
|
+
odxassert(self.category == CompuCategory.TEXTTABLE,
|
35
|
+
"TexttableCompuMethod must exhibit TEXTTABLE category")
|
36
|
+
|
37
|
+
# the spec says that the physical data type shall be
|
38
|
+
# A_UNICODE2STRING, but we are a bit more lenient and allow
|
39
|
+
# any kind of string...
|
40
|
+
odxassert(
|
41
|
+
self.physical_type
|
42
|
+
in [DataType.A_UNICODE2STRING, DataType.A_UTF8STRING, DataType.A_ASCIISTRING],
|
43
|
+
"TEXTTABLE must have string type as its physical datatype.")
|
44
|
+
|
45
|
+
if self.compu_internal_to_phys is None:
|
46
|
+
odxraise("TEXTTABLE compu methods must exhibit a COMPU-INTERNAL-TO-PHYS subtag.")
|
47
|
+
scales = []
|
48
|
+
else:
|
49
|
+
scales = self.compu_internal_to_phys.compu_scales
|
50
|
+
|
22
51
|
odxassert(
|
23
|
-
all(scale.lower_limit is not None or scale.upper_limit is not None
|
24
|
-
|
25
|
-
"Text table compu method doesn't have expected format!")
|
52
|
+
all(scale.lower_limit is not None or scale.upper_limit is not None for scale in scales),
|
53
|
+
"All scales of TEXTTABLE compu methods must provide limits!")
|
26
54
|
|
27
|
-
|
28
|
-
|
29
|
-
|
55
|
+
self._compu_physical_default_value = None
|
56
|
+
citp = odxrequire(self.compu_internal_to_phys)
|
57
|
+
if (cdv := citp.compu_default_value) is not None:
|
58
|
+
self._compu_physical_default_value = self.physical_type.from_string(odxrequire(cdv.vt))
|
30
59
|
|
31
|
-
|
32
|
-
|
33
|
-
if
|
34
|
-
|
35
|
-
scales.append(self.compu_default_value)
|
36
|
-
return scales
|
60
|
+
self._compu_internal_default_value = None
|
61
|
+
cpti = self.compu_phys_to_internal
|
62
|
+
if cpti is not None and (cdv := cpti.compu_default_value) is not None:
|
63
|
+
self._compu_internal_default_value = self.internal_type.from_string(odxrequire(cdv.v))
|
37
64
|
|
38
65
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return
|
61
|
-
|
62
|
-
|
66
|
+
scales = []
|
67
|
+
if (citp := self.compu_internal_to_phys) is not None:
|
68
|
+
scales = citp.compu_scales
|
69
|
+
matching_scales = [
|
70
|
+
x for x in scales if x.compu_const is not None and x.compu_const.value == physical_value
|
71
|
+
]
|
72
|
+
|
73
|
+
if len(matching_scales) == 0:
|
74
|
+
if self._compu_internal_default_value is None:
|
75
|
+
odxraise(f"Texttable could not encode {physical_value!r}.", EncodeError)
|
76
|
+
return cast(None, AtomicOdxType)
|
77
|
+
|
78
|
+
return self._compu_internal_default_value
|
79
|
+
elif len(matching_scales) > 1:
|
80
|
+
odxraise(f"Texttable could not uniquely encode {physical_value!r}.", EncodeError)
|
81
|
+
|
82
|
+
scale = matching_scales[0]
|
83
|
+
if scale.compu_inverse_value is not None and (civ :=
|
84
|
+
scale.compu_inverse_value.value) is not None:
|
85
|
+
return civ
|
86
|
+
elif scale.lower_limit is not None and scale.lower_limit._value is not None:
|
87
|
+
return scale.lower_limit._value
|
88
|
+
elif scale.upper_limit is not None and scale.upper_limit._value is not None:
|
89
|
+
return scale.upper_limit._value
|
90
|
+
|
91
|
+
odxraise(f"Texttable compu method could not encode '{physical_value!r}'.", EncodeError)
|
63
92
|
|
64
93
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
if
|
71
|
-
|
72
|
-
f"Texttable
|
73
|
-
|
94
|
+
scales = []
|
95
|
+
if (citp := self.compu_internal_to_phys) is not None:
|
96
|
+
scales = citp.compu_scales
|
97
|
+
matching_scales: List[CompuScale] = [x for x in scales if x.applies(internal_value)]
|
98
|
+
|
99
|
+
if len(matching_scales) == 0:
|
100
|
+
if self._compu_physical_default_value is None:
|
101
|
+
odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
|
102
|
+
return cast(None, AtomicOdxType)
|
103
|
+
|
104
|
+
return self._compu_physical_default_value
|
105
|
+
|
106
|
+
if len(matching_scales) > 1:
|
107
|
+
odxraise(f"Texttable could not uniquely decode {internal_value!r}.", DecodeError)
|
108
|
+
|
109
|
+
scale = matching_scales[0]
|
110
|
+
|
111
|
+
if scale.compu_const is None:
|
112
|
+
odxraise(f"Encountered a COMPU-SCALE with no COMPU-CONST.")
|
113
|
+
return cast(None, AtomicOdxType)
|
114
|
+
|
115
|
+
if scale.compu_const.value is not None:
|
116
|
+
return scale.compu_const.value
|
117
|
+
|
118
|
+
odxraise(f"Texttable compu method could not decode '{internal_value!r}'.", EncodeError)
|
74
119
|
|
75
120
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
76
|
-
|
121
|
+
if self._compu_physical_default_value is not None:
|
122
|
+
return True
|
123
|
+
|
124
|
+
scales = []
|
125
|
+
if (cpti := self.compu_internal_to_phys) is not None:
|
126
|
+
scales = cpti.compu_scales
|
127
|
+
|
128
|
+
return any(scale.compu_const.value == physical_value
|
129
|
+
for scale in scales
|
130
|
+
if scale.compu_const is not None)
|
77
131
|
|
78
132
|
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
79
|
-
|
80
|
-
|
133
|
+
if self._compu_internal_default_value is not None:
|
134
|
+
return True
|
135
|
+
|
136
|
+
scales = []
|
137
|
+
if (citp := self.compu_internal_to_phys) is not None:
|
138
|
+
scales = citp.compu_scales
|
139
|
+
|
140
|
+
return any(scale.applies(internal_value) for scale in scales)
|
@@ -1,85 +1,28 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
|
-
from typing import List
|
2
|
+
from typing import List
|
3
3
|
from xml.etree import ElementTree
|
4
4
|
|
5
5
|
from .diagcodedtype import DiagCodedType
|
6
|
-
from .exceptions import odxraise
|
6
|
+
from .exceptions import odxraise
|
7
7
|
from .globals import xsi
|
8
8
|
from .leadinglengthinfotype import LeadingLengthInfoType
|
9
9
|
from .minmaxlengthtype import MinMaxLengthType
|
10
|
-
from .odxlink import OdxDocFragment
|
11
|
-
from .odxtypes import DataType, odxstr_to_bool
|
10
|
+
from .odxlink import OdxDocFragment
|
12
11
|
from .paramlengthinfotype import ParamLengthInfoType
|
13
12
|
from .standardlengthtype import StandardLengthType
|
14
13
|
|
15
14
|
|
16
15
|
def create_any_diag_coded_type_from_et(et_element: ElementTree.Element,
|
17
16
|
doc_frags: List[OdxDocFragment]) -> DiagCodedType:
|
18
|
-
base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
|
19
|
-
|
20
|
-
base_data_type_str = odxrequire(et_element.get("BASE-DATA-TYPE"))
|
21
|
-
try:
|
22
|
-
base_data_type = DataType(base_data_type_str)
|
23
|
-
except ValueError:
|
24
|
-
base_data_type = cast(DataType, None)
|
25
|
-
odxraise(f"Unknown base data type {base_data_type_str}")
|
26
|
-
|
27
|
-
is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
|
28
|
-
|
29
17
|
dct_type = et_element.get(f"{xsi}type")
|
30
|
-
bit_length = None
|
31
18
|
if dct_type == "LEADING-LENGTH-INFO-TYPE":
|
32
|
-
|
33
|
-
return LeadingLengthInfoType(
|
34
|
-
base_data_type=base_data_type,
|
35
|
-
bit_length=bit_length,
|
36
|
-
base_type_encoding=base_type_encoding,
|
37
|
-
is_highlow_byte_order_raw=is_highlow_byte_order_raw,
|
38
|
-
)
|
19
|
+
return LeadingLengthInfoType.from_et(et_element, doc_frags)
|
39
20
|
elif dct_type == "MIN-MAX-LENGTH-TYPE":
|
40
|
-
|
41
|
-
max_length = None
|
42
|
-
if et_element.find("MAX-LENGTH") is not None:
|
43
|
-
max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
|
44
|
-
termination = odxrequire(et_element.get("TERMINATION"))
|
45
|
-
|
46
|
-
return MinMaxLengthType(
|
47
|
-
base_data_type=base_data_type,
|
48
|
-
min_length=min_length,
|
49
|
-
max_length=max_length,
|
50
|
-
termination=termination,
|
51
|
-
base_type_encoding=base_type_encoding,
|
52
|
-
is_highlow_byte_order_raw=is_highlow_byte_order_raw,
|
53
|
-
)
|
21
|
+
return MinMaxLengthType.from_et(et_element, doc_frags)
|
54
22
|
elif dct_type == "PARAM-LENGTH-INFO-TYPE":
|
55
|
-
|
56
|
-
OdxLinkRef.from_et(et_element.find("LENGTH-KEY-REF"), doc_frags))
|
57
|
-
|
58
|
-
return ParamLengthInfoType(
|
59
|
-
base_data_type=base_data_type,
|
60
|
-
length_key_ref=length_key_ref,
|
61
|
-
base_type_encoding=base_type_encoding,
|
62
|
-
is_highlow_byte_order_raw=is_highlow_byte_order_raw,
|
63
|
-
)
|
23
|
+
return ParamLengthInfoType.from_et(et_element, doc_frags)
|
64
24
|
elif dct_type == "STANDARD-LENGTH-TYPE":
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# xsd:hexBinary allows for leading/trailing whitespace, empty strings, and it only allows an even
|
70
|
-
# number of hex digits, while some of the examples shown in the ODX specification exhibit an
|
71
|
-
# odd number of hex digits.
|
72
|
-
# This causes a validation paradox, so we try to be flexible
|
73
|
-
bit_mask_str = bit_mask_str.strip()
|
74
|
-
if len(bit_mask_str):
|
75
|
-
bit_mask = int(bit_mask_str, 16)
|
76
|
-
is_condensed_raw = odxstr_to_bool(et_element.get("CONDENSED"))
|
77
|
-
return StandardLengthType(
|
78
|
-
base_data_type=base_data_type,
|
79
|
-
bit_length=bit_length,
|
80
|
-
bit_mask=bit_mask,
|
81
|
-
is_condensed_raw=is_condensed_raw,
|
82
|
-
base_type_encoding=base_type_encoding,
|
83
|
-
is_highlow_byte_order_raw=is_highlow_byte_order_raw,
|
84
|
-
)
|
85
|
-
raise NotImplementedError(f"I do not know the diag-coded-type {dct_type}")
|
25
|
+
return StandardLengthType.from_et(et_element, doc_frags)
|
26
|
+
|
27
|
+
odxraise(f"Unknown DIAG-CODED-TYPE {dct_type}", NotImplementedError)
|
28
|
+
return DiagCodedType.from_et(et_element, doc_frags)
|
odxtools/database.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from itertools import chain
|
3
|
+
from os import PathLike
|
3
4
|
from pathlib import Path
|
4
|
-
from typing import List, Optional
|
5
|
+
from typing import IO, Any, Dict, List, Optional, OrderedDict, Union
|
5
6
|
from xml.etree import ElementTree
|
6
7
|
from zipfile import ZipFile
|
7
8
|
|
@@ -9,11 +10,17 @@ from packaging.version import Version
|
|
9
10
|
|
10
11
|
from .comparamspec import ComparamSpec
|
11
12
|
from .comparamsubset import ComparamSubset
|
12
|
-
from .diaglayer import DiagLayer
|
13
13
|
from .diaglayercontainer import DiagLayerContainer
|
14
|
-
from .
|
14
|
+
from .diaglayers.basevariant import BaseVariant
|
15
|
+
from .diaglayers.diaglayer import DiagLayer
|
16
|
+
from .diaglayers.ecushareddata import EcuSharedData
|
17
|
+
from .diaglayers.ecuvariant import EcuVariant
|
18
|
+
from .diaglayers.functionalgroup import FunctionalGroup
|
19
|
+
from .diaglayers.protocol import Protocol
|
20
|
+
from .exceptions import odxraise, odxrequire
|
15
21
|
from .nameditemlist import NamedItemList
|
16
|
-
from .odxlink import OdxLinkDatabase
|
22
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
23
|
+
from .snrefcontext import SnRefContext
|
17
24
|
|
18
25
|
|
19
26
|
class Database:
|
@@ -22,110 +29,150 @@ class Database:
|
|
22
29
|
into a single PDX file.
|
23
30
|
"""
|
24
31
|
|
25
|
-
def __init__(self
|
26
|
-
|
27
|
-
|
28
|
-
odx_d_file_name: Optional[str] = None) -> None:
|
29
|
-
self.model_version = None
|
30
|
-
|
31
|
-
if pdx_zip is None and odx_d_file_name is None:
|
32
|
-
# create an empty database object
|
33
|
-
self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
|
34
|
-
self._comparam_subsets = NamedItemList[ComparamSubset]()
|
35
|
-
self._comparam_specs = NamedItemList[ComparamSpec]()
|
36
|
-
return
|
37
|
-
|
38
|
-
if pdx_zip is not None and odx_d_file_name is not None:
|
39
|
-
raise TypeError("The 'pdx_zip' and 'odx_d_file_name' parameters are mutually exclusive")
|
40
|
-
|
41
|
-
documents: List[ElementTree.Element] = []
|
42
|
-
if pdx_zip is not None:
|
43
|
-
names = list(pdx_zip.namelist())
|
44
|
-
names.sort()
|
45
|
-
for zip_member in names:
|
46
|
-
# The name of ODX files can end with .odx, .odx-d,
|
47
|
-
# .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
|
48
|
-
# .odx-v . We could test for all that, or just make
|
49
|
-
# sure that the file's suffix starts with .odx
|
50
|
-
if Path(zip_member).suffix.startswith(".odx"):
|
51
|
-
d = pdx_zip.read(zip_member)
|
52
|
-
root = ElementTree.fromstring(d)
|
53
|
-
documents.append(root)
|
54
|
-
|
55
|
-
elif odx_d_file_name is not None:
|
56
|
-
documents.append(ElementTree.parse(odx_d_file_name).getroot())
|
32
|
+
def __init__(self) -> None:
|
33
|
+
self.model_version: Optional[Version] = None
|
34
|
+
self.auxiliary_files: OrderedDict[str, IO[bytes]] = OrderedDict()
|
57
35
|
|
36
|
+
# create an empty database object
|
37
|
+
self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
|
38
|
+
self._comparam_subsets = NamedItemList[ComparamSubset]()
|
39
|
+
self._comparam_specs = NamedItemList[ComparamSpec]()
|
40
|
+
self._short_name = "odx_database"
|
41
|
+
|
42
|
+
def add_pdx_file(self, pdx_file: Union[str, "PathLike[Any]", IO[bytes], ZipFile]) -> None:
|
43
|
+
"""Add PDX file to database.
|
44
|
+
Either pass the path to the file, an IO with the file content or a ZipFile object.
|
45
|
+
"""
|
46
|
+
if isinstance(pdx_file, ZipFile):
|
47
|
+
pdx_zip = pdx_file
|
48
|
+
else:
|
49
|
+
pdx_zip = ZipFile(pdx_file)
|
50
|
+
for zip_member in pdx_zip.namelist():
|
51
|
+
# The name of ODX files can end with .odx, .odx-d,
|
52
|
+
# .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
|
53
|
+
# .odx-v . We could test for all that, or just make
|
54
|
+
# sure that the file's suffix starts with .odx
|
55
|
+
p = Path(zip_member)
|
56
|
+
if p.suffix.lower().startswith(".odx"):
|
57
|
+
root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
|
58
|
+
self._process_xml_tree(root)
|
59
|
+
elif p.name.lower() == "index.xml":
|
60
|
+
root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
|
61
|
+
db_short_name = odxrequire(root.findtext("SHORT-NAME"))
|
62
|
+
self.short_name = db_short_name
|
63
|
+
else:
|
64
|
+
self.add_auxiliary_file(zip_member, pdx_zip.open(zip_member))
|
65
|
+
|
66
|
+
def add_odx_file(self, odx_file_name: Union[str, "PathLike[Any]"]) -> None:
|
67
|
+
self._process_xml_tree(ElementTree.parse(odx_file_name).getroot())
|
68
|
+
|
69
|
+
def add_auxiliary_file(self,
|
70
|
+
aux_file_name: Union[str, "PathLike[Any]"],
|
71
|
+
aux_file_obj: Optional[IO[bytes]] = None) -> None:
|
72
|
+
if aux_file_obj is None:
|
73
|
+
aux_file_obj = open(aux_file_name, "rb")
|
74
|
+
|
75
|
+
self.auxiliary_files[str(aux_file_name)] = aux_file_obj
|
76
|
+
|
77
|
+
def _process_xml_tree(self, root: ElementTree.Element) -> None:
|
58
78
|
dlcs: List[DiagLayerContainer] = []
|
59
79
|
comparam_subsets: List[ComparamSubset] = []
|
60
80
|
comparam_specs: List[ComparamSpec] = []
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
self.
|
90
|
-
|
91
|
-
self.
|
81
|
+
|
82
|
+
# ODX spec version
|
83
|
+
model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
|
84
|
+
if self.model_version is not None and self.model_version != model_version:
|
85
|
+
odxraise(f"Different ODX versions used for the same database (ODX {model_version} "
|
86
|
+
f"and ODX {self.model_version}")
|
87
|
+
|
88
|
+
self.model_version = model_version
|
89
|
+
|
90
|
+
dlc = root.find("DIAG-LAYER-CONTAINER")
|
91
|
+
if dlc is not None:
|
92
|
+
dlcs.append(DiagLayerContainer.from_et(dlc, []))
|
93
|
+
|
94
|
+
# In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
|
95
|
+
# content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
|
96
|
+
# and COMPARAM-SPEC became a container for PROT-STACKS and
|
97
|
+
# a PROT-STACK references a list of COMPARAM-SUBSET
|
98
|
+
cp_subset = root.find("COMPARAM-SUBSET")
|
99
|
+
if cp_subset is not None:
|
100
|
+
comparam_subsets.append(ComparamSubset.from_et(cp_subset, []))
|
101
|
+
|
102
|
+
cp_spec = root.find("COMPARAM-SPEC")
|
103
|
+
if cp_spec is not None:
|
104
|
+
if model_version < Version("2.2"):
|
105
|
+
comparam_subsets.append(ComparamSubset.from_et(cp_spec, []))
|
106
|
+
else: # odx >= 2.2
|
107
|
+
comparam_specs.append(ComparamSpec.from_et(cp_spec, []))
|
108
|
+
|
109
|
+
self._diag_layer_containers.extend(dlcs)
|
110
|
+
self._comparam_subsets.extend(comparam_subsets)
|
111
|
+
self._comparam_specs.extend(comparam_specs)
|
92
112
|
|
93
113
|
def refresh(self) -> None:
|
94
114
|
# Create wrapper objects
|
95
115
|
self._diag_layers = NamedItemList(
|
96
116
|
chain(*[dlc.diag_layers for dlc in self.diag_layer_containers]))
|
97
117
|
|
118
|
+
self._ecu_shared_datas = NamedItemList(
|
119
|
+
chain(*[dlc.ecu_shared_datas for dlc in self.diag_layer_containers]))
|
98
120
|
self._protocols = NamedItemList(
|
99
121
|
chain(*[dlc.protocols for dlc in self.diag_layer_containers]))
|
100
|
-
|
101
|
-
|
122
|
+
self._functional_groups = NamedItemList(
|
123
|
+
chain(*[dlc.functional_groups for dlc in self.diag_layer_containers]))
|
124
|
+
self._base_variants = NamedItemList(
|
125
|
+
chain(*[dlc.base_variants for dlc in self.diag_layer_containers]))
|
126
|
+
self._ecu_variants = NamedItemList(
|
127
|
+
chain(*[dlc.ecu_variants for dlc in self.diag_layer_containers]))
|
102
128
|
|
103
129
|
# Build odxlinks
|
104
130
|
self._odxlinks = OdxLinkDatabase()
|
131
|
+
self._odxlinks.update(self._build_odxlinks())
|
105
132
|
|
133
|
+
# Resolve ODXLINK references
|
106
134
|
for subset in self.comparam_subsets:
|
107
|
-
self._odxlinks
|
135
|
+
subset._resolve_odxlinks(self._odxlinks)
|
108
136
|
|
109
137
|
for spec in self.comparam_specs:
|
110
|
-
self._odxlinks
|
138
|
+
spec._resolve_odxlinks(self._odxlinks)
|
111
139
|
|
112
140
|
for dlc in self.diag_layer_containers:
|
113
|
-
self._odxlinks
|
141
|
+
dlc._resolve_odxlinks(self._odxlinks)
|
114
142
|
|
115
|
-
#
|
116
|
-
|
117
|
-
|
143
|
+
# resolve short name references for containers which do not do
|
144
|
+
# inheritance (we can call directly call _resolve_snrefs())
|
145
|
+
context = SnRefContext()
|
146
|
+
context.database = self
|
118
147
|
|
148
|
+
# let the diaglayers sort out the inherited objects
|
149
|
+
for subset in self.comparam_subsets:
|
150
|
+
subset._finalize_init(self, self._odxlinks)
|
119
151
|
for spec in self.comparam_specs:
|
120
|
-
spec.
|
152
|
+
spec._finalize_init(self, self._odxlinks)
|
153
|
+
for dlc in self.diag_layer_containers:
|
154
|
+
dlc._finalize_init(self, self._odxlinks)
|
121
155
|
|
156
|
+
for subset in self.comparam_subsets:
|
157
|
+
subset._resolve_snrefs(context)
|
158
|
+
for spec in self.comparam_specs:
|
159
|
+
spec._resolve_snrefs(context)
|
122
160
|
for dlc in self.diag_layer_containers:
|
123
|
-
dlc.
|
161
|
+
dlc._resolve_snrefs(context)
|
162
|
+
|
163
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
164
|
+
result: Dict[OdxLinkId, Any] = {}
|
165
|
+
|
166
|
+
for subset in self.comparam_subsets:
|
167
|
+
result.update(subset._build_odxlinks())
|
168
|
+
|
169
|
+
for spec in self.comparam_specs:
|
170
|
+
result.update(spec._build_odxlinks())
|
124
171
|
|
125
|
-
# let the diaglayers sort out the inherited objects and the
|
126
|
-
# short name references
|
127
172
|
for dlc in self.diag_layer_containers:
|
128
|
-
dlc.
|
173
|
+
result.update(dlc._build_odxlinks())
|
174
|
+
|
175
|
+
return result
|
129
176
|
|
130
177
|
@property
|
131
178
|
def odxlinks(self) -> OdxLinkDatabase:
|
@@ -133,19 +180,52 @@ class Database:
|
|
133
180
|
return self._odxlinks
|
134
181
|
|
135
182
|
@property
|
136
|
-
def
|
137
|
-
|
138
|
-
|
183
|
+
def short_name(self) -> str:
|
184
|
+
return self._short_name
|
185
|
+
|
186
|
+
@short_name.setter
|
187
|
+
def short_name(self, value: str) -> None:
|
188
|
+
self._short_name = value
|
189
|
+
|
190
|
+
@property
|
191
|
+
def ecu_shared_datas(self) -> NamedItemList[EcuSharedData]:
|
192
|
+
"""All ECU shared data layers defined by this database
|
193
|
+
|
194
|
+
ECU shared data layers act as a kind of shared library for
|
195
|
+
data that is common to multiple otherwise unrelated ECUs.
|
196
|
+
|
139
197
|
"""
|
198
|
+
return self._ecu_shared_datas
|
199
|
+
|
200
|
+
@property
|
201
|
+
def protocols(self) -> NamedItemList[Protocol]:
|
202
|
+
"""All protocol layers defined by this database"""
|
140
203
|
return self._protocols
|
141
204
|
|
142
205
|
@property
|
143
|
-
def
|
144
|
-
"""
|
145
|
-
return self.
|
206
|
+
def functional_groups(self) -> NamedItemList["FunctionalGroup"]:
|
207
|
+
"""Functional group layers defined in the database"""
|
208
|
+
return self._functional_groups
|
209
|
+
|
210
|
+
@property
|
211
|
+
def base_variants(self) -> NamedItemList["BaseVariant"]:
|
212
|
+
"""Base variants defined in the database"""
|
213
|
+
return self._base_variants
|
214
|
+
|
215
|
+
@property
|
216
|
+
def ecus(self) -> NamedItemList["EcuVariant"]:
|
217
|
+
"""ECU variants defined in the database
|
218
|
+
|
219
|
+
This property is an alias for `.ecu_variants`"""
|
220
|
+
return self._ecu_variants
|
221
|
+
|
222
|
+
@property
|
223
|
+
def ecu_variants(self) -> NamedItemList["EcuVariant"]:
|
224
|
+
"""ECU variants defined in the database"""
|
225
|
+
return self._ecu_variants
|
146
226
|
|
147
227
|
@property
|
148
|
-
def diag_layers(self) -> NamedItemList[DiagLayer]:
|
228
|
+
def diag_layers(self) -> NamedItemList["DiagLayer"]:
|
149
229
|
"""All diagnostic layers defined in the database"""
|
150
230
|
return self._diag_layers
|
151
231
|
|