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,39 +1,145 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List
|
3
|
+
from typing import List, Union, cast
|
4
|
+
from xml.etree import ElementTree
|
4
5
|
|
5
|
-
from ..exceptions import odxassert
|
6
|
-
from ..
|
7
|
-
from
|
8
|
-
from
|
6
|
+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
|
7
|
+
from ..odxlink import OdxDocFragment
|
8
|
+
from ..odxtypes import AtomicOdxType, DataType
|
9
|
+
from ..utils import dataclass_fields_asdict
|
10
|
+
from .compumethod import CompuCategory, CompuMethod
|
11
|
+
from .limit import IntervalType
|
12
|
+
from .linearsegment import LinearSegment
|
9
13
|
|
10
14
|
|
11
15
|
@dataclass
|
12
16
|
class ScaleLinearCompuMethod(CompuMethod):
|
13
|
-
|
17
|
+
"""A piecewise linear compu method which may feature discontinuities.
|
18
|
+
|
19
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.4.
|
20
|
+
"""
|
14
21
|
|
15
22
|
@property
|
16
|
-
def
|
17
|
-
return
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
def segments(self) -> List[LinearSegment]:
|
24
|
+
return self._segments
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
28
|
+
internal_type: DataType,
|
29
|
+
physical_type: DataType) -> "ScaleLinearCompuMethod":
|
30
|
+
cm = CompuMethod.compu_method_from_et(
|
31
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
32
|
+
kwargs = dataclass_fields_asdict(cm)
|
33
|
+
|
34
|
+
return ScaleLinearCompuMethod(**kwargs)
|
35
|
+
|
36
|
+
def __post_init__(self) -> None:
|
37
|
+
self._segments: List[LinearSegment] = []
|
38
|
+
|
39
|
+
odxassert(self.category == CompuCategory.SCALE_LINEAR,
|
40
|
+
"ScaleLinearCompuMethod must exibit SCALE-LINEAR category")
|
41
|
+
|
42
|
+
odxassert(self.physical_type in [
|
43
|
+
DataType.A_FLOAT32,
|
44
|
+
DataType.A_FLOAT64,
|
45
|
+
DataType.A_INT32,
|
46
|
+
DataType.A_UINT32,
|
47
|
+
])
|
48
|
+
odxassert(self.internal_type in [
|
49
|
+
DataType.A_FLOAT32,
|
50
|
+
DataType.A_FLOAT64,
|
51
|
+
DataType.A_INT32,
|
52
|
+
DataType.A_UINT32,
|
53
|
+
])
|
54
|
+
|
55
|
+
if self.compu_internal_to_phys is None:
|
56
|
+
odxraise("SCALE-LINEAR compu methods require COMPU-INTERNAL-TO-PHYS")
|
57
|
+
return
|
58
|
+
|
59
|
+
compu_scales = self.compu_internal_to_phys.compu_scales
|
60
|
+
|
61
|
+
for scale in compu_scales:
|
62
|
+
self._segments.append(
|
63
|
+
LinearSegment.from_compu_scale(
|
64
|
+
scale, internal_type=self.internal_type, physical_type=self.physical_type))
|
65
|
+
|
66
|
+
# find out if the transfer function is invertible (i.e. if it
|
67
|
+
# can be encoded by normal means). section 7.3.6.6.4 of the
|
68
|
+
# ODX specification states that the condition for
|
69
|
+
# invertibility is that adjacent COMPU-SCALES exhibit the same
|
70
|
+
# values on their common boundaries and that the slope in all
|
71
|
+
# intervals exhibit the same sign (or are 0). For segments
|
72
|
+
# with a slope of zero, COMPU-INVERSE-VALUE shall be used.
|
73
|
+
self._is_invertible = True
|
74
|
+
ref_factor = self._segments[0].factor
|
75
|
+
for i in range(0, len(self._segments) - 1):
|
76
|
+
s0 = self.segments[i]
|
77
|
+
s1 = self.segments[i + 1]
|
78
|
+
|
79
|
+
if ref_factor * s1.factor < 0:
|
80
|
+
self._is_invertible = False
|
81
|
+
break
|
82
|
+
if s1.factor != 0:
|
83
|
+
ref_factor = s1.factor
|
84
|
+
|
85
|
+
# both interval boundaries must not be infinite
|
86
|
+
if s0.internal_upper_limit is None or \
|
87
|
+
s1.internal_lower_limit is None:
|
88
|
+
self._is_invertible = False
|
89
|
+
break
|
90
|
+
elif s0.internal_upper_limit.value is None or \
|
91
|
+
s1.internal_lower_limit.value is None or \
|
92
|
+
s0.internal_upper_limit.interval_type == IntervalType.INFINITE or \
|
93
|
+
s1.internal_lower_limit.interval_type == IntervalType.INFINITE:
|
94
|
+
self._is_invertible = False
|
95
|
+
break
|
96
|
+
|
97
|
+
# the intervals must use the same reference point
|
98
|
+
if (x := s0.internal_upper_limit.value) != s1.internal_lower_limit.value:
|
99
|
+
self._is_invertible = False
|
100
|
+
break
|
101
|
+
|
102
|
+
if not isinstance(x, (int, float)):
|
103
|
+
odxraise("Linear segments must use int or float for all quantities")
|
104
|
+
|
105
|
+
# the respective function value at the interval's
|
106
|
+
# reference point must be identical
|
107
|
+
y0 = s0.convert_internal_to_physical(x)
|
108
|
+
y1 = s1.convert_internal_to_physical(x)
|
109
|
+
if abs(y0 - y1) < 1e-10:
|
110
|
+
self._is_invertible = False
|
111
|
+
break
|
112
|
+
|
113
|
+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[float, int]:
|
114
|
+
if not self._is_invertible:
|
115
|
+
odxraise(
|
116
|
+
f"Trying to encode value {physical_value!r} using a non-invertible "
|
117
|
+
f"SCALE-LINEAR transfer function", EncodeError)
|
118
|
+
|
119
|
+
applicable_segments = [
|
120
|
+
seg for seg in self._segments if seg.physical_applies(physical_value)
|
121
|
+
]
|
122
|
+
if not applicable_segments:
|
123
|
+
odxraise(r"No applicable segment for value {physical_value} found", EncodeError)
|
124
|
+
return cast(int, None)
|
125
|
+
|
126
|
+
seg = applicable_segments[0]
|
127
|
+
|
128
|
+
return seg.convert_physical_to_internal(physical_value)
|
129
|
+
|
130
|
+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> Union[float, int]:
|
131
|
+
applicable_segments = [
|
132
|
+
seg for seg in self._segments if seg.internal_applies(internal_value)
|
133
|
+
]
|
134
|
+
if not applicable_segments:
|
135
|
+
odxraise(r"No applicable segment for value {internal_value} found", DecodeError)
|
136
|
+
return cast(int, None)
|
137
|
+
|
138
|
+
seg = applicable_segments[0]
|
139
|
+
return seg.convert_internal_to_physical(internal_value)
|
32
140
|
|
33
141
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
34
|
-
return any(
|
35
|
-
True for scale in self.linear_methods if scale.is_valid_physical_value(physical_value))
|
142
|
+
return any(True for seg in self._segments if seg.physical_applies(physical_value))
|
36
143
|
|
37
144
|
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
38
|
-
return any(
|
39
|
-
True for scale in self.linear_methods if scale.is_valid_internal_value(internal_value))
|
145
|
+
return any(True for seg in self._segments if seg.internal_applies(internal_value))
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import List, Optional, cast
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
|
7
|
+
from ..odxlink import OdxDocFragment
|
8
|
+
from ..odxtypes import AtomicOdxType, DataType
|
9
|
+
from ..utils import dataclass_fields_asdict
|
10
|
+
from .compumethod import CompuCategory, CompuMethod
|
11
|
+
from .ratfuncsegment import RatFuncSegment
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class ScaleRatFuncCompuMethod(CompuMethod):
|
16
|
+
"""A compu method using a piecewise rational function
|
17
|
+
|
18
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.5.
|
19
|
+
"""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def int_to_phys_segments(self) -> List[RatFuncSegment]:
|
23
|
+
return self._int_to_phys_segments
|
24
|
+
|
25
|
+
@property
|
26
|
+
def phys_to_int_segments(self) -> Optional[List[RatFuncSegment]]:
|
27
|
+
return self._phys_to_int_segments
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
31
|
+
internal_type: DataType,
|
32
|
+
physical_type: DataType) -> "ScaleRatFuncCompuMethod":
|
33
|
+
cm = CompuMethod.compu_method_from_et(
|
34
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
35
|
+
kwargs = dataclass_fields_asdict(cm)
|
36
|
+
|
37
|
+
return ScaleRatFuncCompuMethod(**kwargs)
|
38
|
+
|
39
|
+
def __post_init__(self) -> None:
|
40
|
+
odxassert(self.category == CompuCategory.SCALE_RAT_FUNC,
|
41
|
+
"ScaleRatFuncCompuMethod must exhibit SCALE-RAT-FUNC category")
|
42
|
+
|
43
|
+
odxassert(self.physical_type in [
|
44
|
+
DataType.A_FLOAT32,
|
45
|
+
DataType.A_FLOAT64,
|
46
|
+
DataType.A_INT32,
|
47
|
+
DataType.A_UINT32,
|
48
|
+
])
|
49
|
+
odxassert(self.internal_type in [
|
50
|
+
DataType.A_FLOAT32,
|
51
|
+
DataType.A_FLOAT64,
|
52
|
+
DataType.A_INT32,
|
53
|
+
DataType.A_UINT32,
|
54
|
+
])
|
55
|
+
|
56
|
+
if self.compu_internal_to_phys is None:
|
57
|
+
odxraise("RAT-FUNC compu methods require COMPU-INTERNAL-TO-PHYS")
|
58
|
+
return
|
59
|
+
|
60
|
+
int_to_phys_scales = self.compu_internal_to_phys.compu_scales
|
61
|
+
if len(int_to_phys_scales) < 1:
|
62
|
+
odxraise("RAT-FUNC compu methods expect at least one compu scale within "
|
63
|
+
"COMPU-INTERNAL-TO-PHYS")
|
64
|
+
return
|
65
|
+
|
66
|
+
self._int_to_phys_segments = [
|
67
|
+
RatFuncSegment.from_compu_scale(scale, value_type=self.physical_type)
|
68
|
+
for scale in int_to_phys_scales
|
69
|
+
]
|
70
|
+
|
71
|
+
self._phys_to_int_segments = None
|
72
|
+
if self.compu_phys_to_internal is not None:
|
73
|
+
phys_to_int_scales = self.compu_phys_to_internal.compu_scales
|
74
|
+
if len(phys_to_int_scales) < 1:
|
75
|
+
odxraise("SCALE-RAT-FUNC compu methods expect at least one compu scale within "
|
76
|
+
"COMPU-PHYS-TO-INTERNAL")
|
77
|
+
return
|
78
|
+
|
79
|
+
self._phys_to_int_segments = [
|
80
|
+
RatFuncSegment.from_compu_scale(scale, value_type=self.internal_type)
|
81
|
+
for scale in phys_to_int_scales
|
82
|
+
]
|
83
|
+
|
84
|
+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
85
|
+
for seg in self._int_to_phys_segments:
|
86
|
+
if seg.applies(internal_value):
|
87
|
+
return seg.convert(internal_value)
|
88
|
+
|
89
|
+
odxraise(f"Internal value {internal_value!r} be decoded using this compumethod",
|
90
|
+
DecodeError)
|
91
|
+
return cast(AtomicOdxType, None)
|
92
|
+
|
93
|
+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
94
|
+
if self._phys_to_int_segments is None:
|
95
|
+
odxraise(f"Physical values cannot be encoded using this compumethod", EncodeError)
|
96
|
+
return cast(AtomicOdxType, None)
|
97
|
+
|
98
|
+
for seg in self._phys_to_int_segments:
|
99
|
+
if seg.applies(physical_value):
|
100
|
+
return seg.convert(physical_value)
|
101
|
+
|
102
|
+
odxraise(f"Physical values {physical_value!r} be decoded using this compumethod",
|
103
|
+
EncodeError)
|
104
|
+
return cast(AtomicOdxType, None)
|
105
|
+
|
106
|
+
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
107
|
+
return any(seg.applies(internal_value) for seg in self._int_to_phys_segments)
|
108
|
+
|
109
|
+
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
110
|
+
if self._phys_to_int_segments is None:
|
111
|
+
return False
|
112
|
+
|
113
|
+
return any(seg.applies(physical_value) for seg in self._phys_to_int_segments)
|
@@ -1,82 +1,54 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List,
|
3
|
+
from typing import List, Union
|
4
|
+
from xml.etree import ElementTree
|
4
5
|
|
5
|
-
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
|
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 .limit import IntervalType, Limit
|
9
12
|
|
10
13
|
|
11
14
|
@dataclass
|
12
15
|
class TabIntpCompuMethod(CompuMethod):
|
13
|
-
"""
|
14
|
-
|
15
|
-
|
16
|
-
A `TabIntpCompuMethod` is defined by a set of points. Each point
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
method = TabIntpCompuMethod(
|
32
|
-
internal_type=DataType.A_UINT32,
|
33
|
-
physical_type=DataType.A_UINT32,
|
34
|
-
internal_points=[0, 10, 30],
|
35
|
-
physical_points=[-1, 1, 2]
|
36
|
-
)
|
37
|
-
|
38
|
-
Note that the points are given as two lists. The equivalent odx definition is::
|
39
|
-
|
40
|
-
<COMPU-METHOD>
|
41
|
-
<CATEGORY>TAB-INTP</CATEGORY>
|
42
|
-
<COMPU-INTERNAL-TO-PHYS>
|
43
|
-
<COMPU-SCALES>
|
44
|
-
<COMPU-SCALE>
|
45
|
-
<LOWER-LIMIT INTERVAL-TYPE = "CLOSED">0</LOWER-LIMIT>
|
46
|
-
<COMPU-CONST>
|
47
|
-
<V>-1</V>
|
48
|
-
</COMPU-CONST>
|
49
|
-
</COMPU-SCALE>
|
50
|
-
<COMPU-SCALE>
|
51
|
-
<LOWER-LIMIT INTERVAL-TYPE = "CLOSED">10</LOWER-LIMIT>
|
52
|
-
<COMPU-CONST>
|
53
|
-
<V>1</V>
|
54
|
-
</COMPU-CONST>
|
55
|
-
</COMPU-SCALE>
|
56
|
-
<COMPU-SCALE>
|
57
|
-
<LOWER-LIMIT INTERVAL-TYPE = "CLOSED">30</LOWER-LIMIT>
|
58
|
-
<COMPU-CONST>
|
59
|
-
<V>2</V>
|
60
|
-
</COMPU-CONST>
|
61
|
-
</COMPU-SCALE>
|
62
|
-
</COMPU-SCALES>
|
63
|
-
</COMPU-INTERNAL-TO-PHYS>
|
64
|
-
</COMPU-METHOD>
|
16
|
+
"""A table-based interpolated compu method provides a continuous
|
17
|
+
transfer function based on piecewise linear interpolation.
|
18
|
+
|
19
|
+
A `TabIntpCompuMethod` is defined by a set of points. Each point
|
20
|
+
is an (internal, physical) value pair. When converting from
|
21
|
+
internal to physical or vice-versa, the result is linearly
|
22
|
+
interpolated.
|
23
|
+
|
24
|
+
The function defined by a `TabIntpCompuMethod` is similar to the
|
25
|
+
one of a `ScaleLinearCompuMethod` with the following differences:
|
26
|
+
|
27
|
+
* `TabIntpCompuMethod`s are always continuous whereas
|
28
|
+
`ScaleLinearCompuMethod` might exhibit gaps
|
29
|
+
* `TabIntpCompuMethod`s are always invertible: Even if the linear
|
30
|
+
interpolation is not monotonic, the first matching interval is
|
31
|
+
used.
|
32
|
+
|
33
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.8.
|
65
34
|
|
66
35
|
"""
|
67
36
|
|
68
|
-
|
69
|
-
|
37
|
+
@property
|
38
|
+
def internal_points(self) -> List[Union[float, int]]:
|
39
|
+
return self._internal_points
|
70
40
|
|
71
|
-
|
72
|
-
|
73
|
-
|
41
|
+
@property
|
42
|
+
def physical_points(self) -> List[Union[float, int]]:
|
43
|
+
return self._physical_points
|
74
44
|
|
75
|
-
|
45
|
+
@property
|
46
|
+
def internal_lower_limit(self) -> Limit:
|
47
|
+
return self._internal_lower_limit
|
76
48
|
|
77
49
|
@property
|
78
|
-
def
|
79
|
-
return
|
50
|
+
def internal_upper_limit(self) -> Limit:
|
51
|
+
return self._internal_upper_limit
|
80
52
|
|
81
53
|
@property
|
82
54
|
def physical_lower_limit(self) -> Limit:
|
@@ -86,7 +58,57 @@ class TabIntpCompuMethod(CompuMethod):
|
|
86
58
|
def physical_upper_limit(self) -> Limit:
|
87
59
|
return self._physical_upper_limit
|
88
60
|
|
89
|
-
|
61
|
+
@staticmethod
|
62
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
63
|
+
internal_type: DataType,
|
64
|
+
physical_type: DataType) -> "TabIntpCompuMethod":
|
65
|
+
cm = CompuMethod.compu_method_from_et(
|
66
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
67
|
+
kwargs = dataclass_fields_asdict(cm)
|
68
|
+
|
69
|
+
return TabIntpCompuMethod(**kwargs)
|
70
|
+
|
71
|
+
def __post_init__(self) -> None:
|
72
|
+
odxassert(self.category == CompuCategory.TAB_INTP,
|
73
|
+
"TabIntpCompuMethod must exibit TAB-INTP category")
|
74
|
+
|
75
|
+
self._internal_points: List[Union[int, float]] = []
|
76
|
+
self._physical_points: List[Union[int, float]] = []
|
77
|
+
for scale in odxrequire(self.compu_internal_to_phys).compu_scales:
|
78
|
+
internal_point = odxrequire(scale.lower_limit).value
|
79
|
+
physical_point = odxrequire(scale.compu_const).value
|
80
|
+
|
81
|
+
if not isinstance(internal_point, (float, int)):
|
82
|
+
odxraise("The type of values of tab-intp compumethods must "
|
83
|
+
"either int or float")
|
84
|
+
if not isinstance(physical_point, (float, int)):
|
85
|
+
odxraise("The type of values of tab-intp compumethods must "
|
86
|
+
"either int or float")
|
87
|
+
|
88
|
+
self._internal_points.append(internal_point)
|
89
|
+
self._physical_points.append(physical_point)
|
90
|
+
|
91
|
+
self._physical_lower_limit = Limit(
|
92
|
+
value_raw=str(min(self._physical_points)),
|
93
|
+
value_type=self.physical_type,
|
94
|
+
interval_type=IntervalType.CLOSED)
|
95
|
+
self._physical_upper_limit = Limit(
|
96
|
+
value_raw=str(max(self._physical_points)),
|
97
|
+
value_type=self.physical_type,
|
98
|
+
interval_type=IntervalType.CLOSED)
|
99
|
+
|
100
|
+
self._internal_lower_limit = Limit(
|
101
|
+
value_raw=str(min(self._internal_points)),
|
102
|
+
value_type=self.internal_type,
|
103
|
+
interval_type=IntervalType.CLOSED)
|
104
|
+
self._internal_upper_limit = Limit(
|
105
|
+
value_raw=str(max(self._internal_points)),
|
106
|
+
value_type=self.internal_type,
|
107
|
+
interval_type=IntervalType.CLOSED)
|
108
|
+
|
109
|
+
self.__assert_validity()
|
110
|
+
|
111
|
+
def __assert_validity(self) -> None:
|
90
112
|
odxassert(len(self.internal_points) == len(self.physical_points))
|
91
113
|
|
92
114
|
odxassert(
|
@@ -95,7 +117,7 @@ class TabIntpCompuMethod(CompuMethod):
|
|
95
117
|
DataType.A_UINT32,
|
96
118
|
DataType.A_FLOAT32,
|
97
119
|
DataType.A_FLOAT64,
|
98
|
-
], "Internal data type of
|
120
|
+
], "Internal data type of TAB-INTP compumethod must be one of"
|
99
121
|
" [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
|
100
122
|
odxassert(
|
101
123
|
self.physical_type in [
|
@@ -103,55 +125,64 @@ class TabIntpCompuMethod(CompuMethod):
|
|
103
125
|
DataType.A_UINT32,
|
104
126
|
DataType.A_FLOAT32,
|
105
127
|
DataType.A_FLOAT64,
|
106
|
-
], "Physical data type of
|
128
|
+
], "Physical data type of TAB-INTP compumethod must be one of"
|
107
129
|
" [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
|
108
130
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
131
|
+
def __piecewise_linear_interpolate(self, x: Union[int, float],
|
132
|
+
range_samples: List[Union[int, float]],
|
133
|
+
domain_samples: List[Union[int,
|
134
|
+
float]]) -> Union[float, None]:
|
135
|
+
for i in range(0, len(range_samples) - 1):
|
136
|
+
if (x0 := range_samples[i]) <= x and x <= (x1 := range_samples[i + 1]):
|
137
|
+
y0 = domain_samples[i]
|
138
|
+
y1 = domain_samples[i + 1]
|
114
139
|
return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
115
140
|
|
116
141
|
return None
|
117
142
|
|
118
143
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
119
144
|
if not isinstance(physical_value, (int, float)):
|
120
|
-
|
121
|
-
|
145
|
+
odxraise("The type of values of tab-intp compumethods must "
|
146
|
+
"either int or float", EncodeError)
|
147
|
+
return None
|
122
148
|
|
123
|
-
reference_points = list(zip(self.physical_points, self.internal_points))
|
124
149
|
odxassert(
|
125
150
|
isinstance(physical_value, (int, float)),
|
126
|
-
"Only integers and floats can be piecewise linearly interpolated")
|
127
|
-
result = self.
|
128
|
-
|
129
|
-
reference_points)
|
151
|
+
"Only integers and floats can be piecewise linearly interpolated", EncodeError)
|
152
|
+
result = self.__piecewise_linear_interpolate(physical_value, self._physical_points,
|
153
|
+
self._internal_points)
|
130
154
|
|
131
155
|
if result is None:
|
132
|
-
|
133
|
-
|
156
|
+
odxraise(
|
157
|
+
f"Internal value {physical_value!r} must be inside the range"
|
158
|
+
f" [{min(self.physical_points)}, {max(self.physical_points)}]", EncodeError)
|
159
|
+
|
134
160
|
res = self.internal_type.make_from(result)
|
135
|
-
|
136
|
-
odxraise()
|
161
|
+
|
137
162
|
return res
|
138
163
|
|
139
164
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
140
165
|
if not isinstance(internal_value, (int, float)):
|
141
|
-
|
142
|
-
|
166
|
+
odxraise(
|
167
|
+
"The internal type of values of tab-intp compumethods must "
|
168
|
+
"either int or float", EncodeError)
|
169
|
+
return None
|
143
170
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
171
|
+
odxassert(
|
172
|
+
isinstance(internal_value, (int, float)),
|
173
|
+
"Only integers and floats can be piecewise linearly interpolated", DecodeError)
|
174
|
+
|
175
|
+
result = self.__piecewise_linear_interpolate(internal_value, self._internal_points,
|
176
|
+
self._physical_points)
|
148
177
|
|
149
178
|
if result is None:
|
150
|
-
|
151
|
-
|
179
|
+
odxraise(
|
180
|
+
f"Internal value {internal_value!r} must be inside the range"
|
181
|
+
f" [{min(self.internal_points)}, {max(self.internal_points)}]", DecodeError)
|
182
|
+
return None
|
183
|
+
|
152
184
|
res = self.physical_type.make_from(result)
|
153
|
-
|
154
|
-
odxraise()
|
185
|
+
|
155
186
|
return res
|
156
187
|
|
157
188
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|