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,203 +1,92 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import cast
|
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
|
7
|
+
from ..odxlink import OdxDocFragment
|
6
8
|
from ..odxtypes import AtomicOdxType, DataType
|
7
|
-
from
|
8
|
-
from .
|
9
|
+
from ..utils import dataclass_fields_asdict
|
10
|
+
from .compumethod import CompuCategory, CompuMethod
|
11
|
+
from .linearsegment import LinearSegment
|
9
12
|
|
10
13
|
|
11
14
|
@dataclass
|
12
15
|
class LinearCompuMethod(CompuMethod):
|
13
|
-
"""
|
14
|
-
where d(y) is the physical value and y is the internal value.
|
15
|
-
|
16
|
-
Examples
|
17
|
-
--------
|
18
|
-
|
19
|
-
Define the decoding function `d(y) = 4+2*y` (or equivalent encoding `e(x) = floor((x-4)/2)`)
|
20
|
-
on all integers `y` in the range -10..10 (and `x` in -16..25).
|
21
|
-
|
22
|
-
```python
|
23
|
-
method = LinearCompuMethod(
|
24
|
-
offset=4,
|
25
|
-
factor=2,
|
26
|
-
internal_type=DataType.A_INT32,
|
27
|
-
physical_type=DataType.A_INT32,
|
28
|
-
internal_lower_limit = Limit(-10, IntervalType.CLOSED),
|
29
|
-
internal_upper_limit = Limit(11, IntervalType.OPEN)
|
30
|
-
)
|
31
|
-
```
|
32
|
-
|
33
|
-
Decode an internal value:
|
34
|
-
|
35
|
-
```python
|
36
|
-
>>> method.convert_internal_to_physical(6) # == 4+2*6
|
37
|
-
16
|
38
|
-
```
|
39
|
-
|
40
|
-
Encode a physical value:
|
41
|
-
|
42
|
-
```python
|
43
|
-
>>> method.convert_physical_to_internal(6) # == 6/2-2
|
44
|
-
1
|
45
|
-
```
|
46
|
-
|
47
|
-
Get physical limits:
|
48
|
-
|
49
|
-
```python
|
50
|
-
>>> method.physical_lower_limit
|
51
|
-
Limit(value=-16, interval_type=IntervalType.CLOSED)
|
52
|
-
>>> method.physical_upper_limit
|
53
|
-
Limit(value=26, interval_type=IntervalType.OPEN)
|
54
|
-
```
|
55
|
-
|
56
|
-
(Note that there may be additional restrictions to valid physical values by the surrounding data object prop.
|
57
|
-
For example, limits given by the bit length are not considered in the compu method.)
|
58
|
-
"""
|
59
|
-
|
60
|
-
offset: float
|
61
|
-
factor: float
|
62
|
-
denominator: float
|
63
|
-
internal_lower_limit: Limit
|
64
|
-
internal_upper_limit: Limit
|
16
|
+
"""A compu method which does linear interpoation
|
65
17
|
|
66
|
-
|
67
|
-
|
18
|
+
i.e. internal values are converted to physical ones using the
|
19
|
+
function `f(x) = (offset + factor * x)/denominator` where `f(x)`
|
20
|
+
is the physical value and `x` is the internal value. In contrast
|
21
|
+
to `ScaleLinearCompuMethod`, this compu method only exhibits a
|
22
|
+
single segment)
|
68
23
|
|
69
|
-
|
24
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.3.
|
25
|
+
"""
|
70
26
|
|
71
27
|
@property
|
72
|
-
def
|
73
|
-
return
|
28
|
+
def segment(self) -> LinearSegment:
|
29
|
+
return self._segment
|
74
30
|
|
75
|
-
@
|
76
|
-
def
|
77
|
-
|
31
|
+
@staticmethod
|
32
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
33
|
+
internal_type: DataType,
|
34
|
+
physical_type: DataType) -> "LinearCompuMethod":
|
35
|
+
cm = CompuMethod.compu_method_from_et(
|
36
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
37
|
+
kwargs = dataclass_fields_asdict(cm)
|
78
38
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
self.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if self.factor >= 0:
|
116
|
-
self._physical_lower_limit = convert_to_limit_to_physical(self.internal_lower_limit,
|
117
|
-
False)
|
118
|
-
self._physical_upper_limit = convert_to_limit_to_physical(self.internal_upper_limit,
|
119
|
-
True)
|
120
|
-
else:
|
121
|
-
# If the factor is negative, the lower and upper limit are swapped
|
122
|
-
self._physical_lower_limit = convert_to_limit_to_physical(self.internal_upper_limit,
|
123
|
-
True)
|
124
|
-
self._physical_upper_limit = convert_to_limit_to_physical(self.internal_lower_limit,
|
125
|
-
False)
|
126
|
-
|
127
|
-
if self.physical_type == DataType.A_UINT32:
|
128
|
-
# If the data type is unsigned, the physical lower limit should be at least 0.
|
129
|
-
if (self._physical_lower_limit.interval_type == IntervalType.INFINITE or
|
130
|
-
cast(float, self._physical_lower_limit.value) < 0):
|
131
|
-
self._physical_lower_limit = Limit(value=0, interval_type=IntervalType.CLOSED)
|
132
|
-
|
133
|
-
def _convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
134
|
-
if not isinstance(internal_value, (int, float)):
|
135
|
-
raise DecodeError("The type of internal values of linear compumethods must "
|
136
|
-
"either int or float")
|
137
|
-
|
138
|
-
if self.denominator is None:
|
139
|
-
result = self.offset + self.factor * internal_value
|
140
|
-
else:
|
141
|
-
result = (self.offset + self.factor * internal_value) / self.denominator
|
142
|
-
|
143
|
-
if self.internal_type == DataType.A_FLOAT64 and self.physical_type in [
|
144
|
-
DataType.A_INT32,
|
145
|
-
DataType.A_UINT32,
|
146
|
-
]:
|
147
|
-
result = round(result)
|
148
|
-
return self.physical_type.make_from(result)
|
39
|
+
return LinearCompuMethod(**kwargs)
|
40
|
+
|
41
|
+
def __post_init__(self) -> None:
|
42
|
+
odxassert(self.category == CompuCategory.LINEAR,
|
43
|
+
"LinearCompuMethod must exhibit LINEAR category")
|
44
|
+
|
45
|
+
odxassert(self.physical_type in [
|
46
|
+
DataType.A_FLOAT32,
|
47
|
+
DataType.A_FLOAT64,
|
48
|
+
DataType.A_INT32,
|
49
|
+
DataType.A_UINT32,
|
50
|
+
])
|
51
|
+
odxassert(self.internal_type in [
|
52
|
+
DataType.A_FLOAT32,
|
53
|
+
DataType.A_FLOAT64,
|
54
|
+
DataType.A_INT32,
|
55
|
+
DataType.A_UINT32,
|
56
|
+
])
|
57
|
+
|
58
|
+
if self.compu_internal_to_phys is None:
|
59
|
+
odxraise("LINEAR compu methods require COMPU-INTERNAL-TO-PHYS")
|
60
|
+
return
|
61
|
+
|
62
|
+
compu_scales = self.compu_internal_to_phys.compu_scales
|
63
|
+
|
64
|
+
if len(compu_scales) == 0:
|
65
|
+
odxraise("LINEAR compu methods expect at least one compu scale within "
|
66
|
+
"COMPU-INTERNAL-TO-PHYS")
|
67
|
+
return cast(None, LinearCompuMethod)
|
68
|
+
elif len(compu_scales) > 1:
|
69
|
+
odxraise("LINEAR compu methods expect at most one compu scale within "
|
70
|
+
"COMPU-INTERNAL-TO-PHYS")
|
71
|
+
|
72
|
+
scale = compu_scales[0]
|
73
|
+
self._segment = LinearSegment.from_compu_scale(
|
74
|
+
scale, internal_type=self.internal_type, physical_type=self.physical_type)
|
149
75
|
|
150
76
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
151
|
-
|
152
|
-
|
77
|
+
if not self._segment.internal_applies(internal_value):
|
78
|
+
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)
|
79
|
+
|
80
|
+
return self._segment.convert_internal_to_physical(internal_value)
|
153
81
|
|
154
82
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
155
|
-
if not
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
odxassert(
|
160
|
-
self.is_valid_physical_value(physical_value),
|
161
|
-
f"physical value {physical_value} of type {type(physical_value)} "
|
162
|
-
f"is not valid. Expected type {self.physical_type} with internal "
|
163
|
-
f"range {self.internal_lower_limit} to {self.internal_upper_limit}")
|
164
|
-
if self.denominator is None:
|
165
|
-
result = (physical_value - self.offset) / self.factor
|
166
|
-
else:
|
167
|
-
result = ((physical_value * self.denominator) - self.offset) / self.factor
|
168
|
-
|
169
|
-
if self.physical_type == DataType.A_FLOAT64 and self.internal_type in [
|
170
|
-
DataType.A_INT32,
|
171
|
-
DataType.A_UINT32,
|
172
|
-
]:
|
173
|
-
result = round(result)
|
174
|
-
return self.internal_type.make_from(result)
|
83
|
+
if not self._segment.physical_applies(physical_value):
|
84
|
+
odxraise(f"Cannot decode physical value {physical_value!r}", EncodeError)
|
85
|
+
|
86
|
+
return self._segment.convert_physical_to_internal(physical_value)
|
175
87
|
|
176
88
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
177
|
-
|
178
|
-
expected_type = self.physical_type.as_python_type()
|
179
|
-
if expected_type == float and not isinstance(physical_value, (int, float)):
|
180
|
-
return False
|
181
|
-
elif expected_type != float and not isinstance(physical_value, expected_type):
|
182
|
-
return False
|
183
|
-
|
184
|
-
# Compare to the limits
|
185
|
-
if not self.physical_lower_limit.complies_to_lower(physical_value):
|
186
|
-
return False
|
187
|
-
if not self.physical_upper_limit.complies_to_upper(physical_value):
|
188
|
-
return False
|
189
|
-
return True
|
89
|
+
return self._segment.physical_applies(physical_value)
|
190
90
|
|
191
91
|
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
192
|
-
|
193
|
-
if expected_type == float and not isinstance(internal_value, (int, float)):
|
194
|
-
return False
|
195
|
-
elif expected_type != float and not isinstance(internal_value, expected_type):
|
196
|
-
return False
|
197
|
-
|
198
|
-
if not self.internal_lower_limit.complies_to_lower(internal_value):
|
199
|
-
return False
|
200
|
-
if not self.internal_upper_limit.complies_to_upper(internal_value):
|
201
|
-
return False
|
202
|
-
|
203
|
-
return True
|
92
|
+
return self._segment.internal_applies(internal_value)
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Optional, Union
|
4
|
+
|
5
|
+
from ..exceptions import odxraise, odxrequire
|
6
|
+
from ..odxtypes import AtomicOdxType, DataType
|
7
|
+
from .compuscale import CompuScale
|
8
|
+
from .limit import Limit
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class LinearSegment:
|
13
|
+
"""Helper class to represent a segment of a piecewise-linear interpolation.
|
14
|
+
|
15
|
+
Multiple compu methods (LINEAR, SCALE-LINEAR, TAB-INTP) require
|
16
|
+
linear interpolation. This class centralizes the required
|
17
|
+
parameters for a single such segment. (The required parameters are
|
18
|
+
extracted from the respective compu method's
|
19
|
+
COMPU-INTERNAL-TO-PHYS objects. We do it this way because the
|
20
|
+
internal-to-phys objects are rather clunky to work with and
|
21
|
+
feature a lot of irrelevant information.)
|
22
|
+
|
23
|
+
"""
|
24
|
+
offset: float
|
25
|
+
factor: float
|
26
|
+
denominator: float
|
27
|
+
internal_lower_limit: Optional[Limit]
|
28
|
+
internal_upper_limit: Optional[Limit]
|
29
|
+
|
30
|
+
inverse_value: Union[int, float] # value used as inverse if factor is 0
|
31
|
+
|
32
|
+
internal_type: DataType
|
33
|
+
physical_type: DataType
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def from_compu_scale(scale: CompuScale, *, internal_type: DataType,
|
37
|
+
physical_type: DataType) -> "LinearSegment":
|
38
|
+
coeffs = odxrequire(scale.compu_rational_coeffs)
|
39
|
+
|
40
|
+
offset = coeffs.numerators[0]
|
41
|
+
factor = 0 if len(coeffs.numerators) == 1 else coeffs.numerators[1]
|
42
|
+
|
43
|
+
denominator = 1.0
|
44
|
+
if len(coeffs.denominators) > 0:
|
45
|
+
denominator = coeffs.denominators[0]
|
46
|
+
|
47
|
+
inverse_value: Union[int, float] = 0
|
48
|
+
if scale.compu_inverse_value is not None:
|
49
|
+
x = odxrequire(scale.compu_inverse_value).value
|
50
|
+
if not isinstance(x, (int, float)):
|
51
|
+
odxraise(f"Non-numeric COMPU-INVERSE-VALUE specified ({x!r})")
|
52
|
+
inverse_value = x
|
53
|
+
|
54
|
+
internal_lower_limit = scale.lower_limit
|
55
|
+
internal_upper_limit = scale.upper_limit
|
56
|
+
|
57
|
+
return LinearSegment(
|
58
|
+
offset=offset,
|
59
|
+
factor=factor,
|
60
|
+
denominator=denominator,
|
61
|
+
internal_lower_limit=internal_lower_limit,
|
62
|
+
internal_upper_limit=internal_upper_limit,
|
63
|
+
inverse_value=inverse_value,
|
64
|
+
internal_type=internal_type,
|
65
|
+
physical_type=physical_type)
|
66
|
+
|
67
|
+
@property
|
68
|
+
def physical_lower_limit(self) -> Optional[Limit]:
|
69
|
+
return self._physical_lower_limit
|
70
|
+
|
71
|
+
@property
|
72
|
+
def physical_upper_limit(self) -> Optional[Limit]:
|
73
|
+
return self._physical_upper_limit
|
74
|
+
|
75
|
+
def __post_init__(self) -> None:
|
76
|
+
self.__compute_physical_limits()
|
77
|
+
|
78
|
+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> Union[float, int]:
|
79
|
+
if not isinstance(internal_value, (int, float)):
|
80
|
+
odxraise(f"Internal values of linear compumethods must "
|
81
|
+
f"either be int or float (is: {type(internal_value).__name__})")
|
82
|
+
|
83
|
+
result = (self.offset + self.factor * internal_value) / self.denominator
|
84
|
+
|
85
|
+
if self.physical_type in [
|
86
|
+
DataType.A_INT32,
|
87
|
+
DataType.A_UINT32,
|
88
|
+
]:
|
89
|
+
result = round(result)
|
90
|
+
|
91
|
+
return result
|
92
|
+
|
93
|
+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[float, int]:
|
94
|
+
if not isinstance(physical_value, (int, float)):
|
95
|
+
odxraise(f"Physical values of linear compumethods must "
|
96
|
+
f"either be int or float (is: {type(physical_value).__name__})")
|
97
|
+
|
98
|
+
if abs(self.factor) < 1e-10:
|
99
|
+
# "If factor = 0 then COMPU-INVERSE-VALUE shall be specified.
|
100
|
+
return self.inverse_value
|
101
|
+
|
102
|
+
result = (physical_value * self.denominator - self.offset) / self.factor
|
103
|
+
|
104
|
+
if self.internal_type in [
|
105
|
+
DataType.A_INT32,
|
106
|
+
DataType.A_UINT32,
|
107
|
+
]:
|
108
|
+
result = round(result)
|
109
|
+
|
110
|
+
return result
|
111
|
+
|
112
|
+
def __compute_physical_limits(self) -> None:
|
113
|
+
"""Computes the physical limits and stores them in the properties
|
114
|
+
self._physical_lower_limit and self._physical_upper_limit.
|
115
|
+
This method is called by `__post_init__()`.
|
116
|
+
"""
|
117
|
+
|
118
|
+
def convert_internal_to_physical_limit(internal_limit: Optional[Limit]) -> Optional[Limit]:
|
119
|
+
"""Helper method to convert a single internal limit
|
120
|
+
"""
|
121
|
+
if internal_limit is None or internal_limit.value_raw is None:
|
122
|
+
return None
|
123
|
+
|
124
|
+
internal_value = self.internal_type.from_string(internal_limit.value_raw)
|
125
|
+
physical_value = self.convert_internal_to_physical(internal_value)
|
126
|
+
|
127
|
+
result = Limit(
|
128
|
+
value_raw=str(physical_value),
|
129
|
+
value_type=self.physical_type,
|
130
|
+
interval_type=internal_limit.interval_type)
|
131
|
+
|
132
|
+
return result
|
133
|
+
|
134
|
+
self._physical_lower_limit = None
|
135
|
+
self._physical_upper_limit = None
|
136
|
+
|
137
|
+
if self.factor >= 0:
|
138
|
+
self._physical_lower_limit = convert_internal_to_physical_limit(
|
139
|
+
self.internal_lower_limit)
|
140
|
+
self._physical_upper_limit = convert_internal_to_physical_limit(
|
141
|
+
self.internal_upper_limit)
|
142
|
+
else:
|
143
|
+
# If the scaling factor is negative, the lower and upper
|
144
|
+
# limit are swapped
|
145
|
+
self._physical_lower_limit = convert_internal_to_physical_limit(
|
146
|
+
self.internal_upper_limit)
|
147
|
+
self._physical_upper_limit = convert_internal_to_physical_limit(
|
148
|
+
self.internal_lower_limit)
|
149
|
+
|
150
|
+
def physical_applies(self, physical_value: AtomicOdxType) -> bool:
|
151
|
+
"""Returns True iff the segment is applicable to a given physical value"""
|
152
|
+
# Do type checks
|
153
|
+
expected_type = self.physical_type.python_type
|
154
|
+
if issubclass(expected_type, float):
|
155
|
+
if not isinstance(physical_value, (int, float)):
|
156
|
+
return False
|
157
|
+
else:
|
158
|
+
if not isinstance(physical_value, expected_type):
|
159
|
+
return False
|
160
|
+
|
161
|
+
if self._physical_lower_limit is not None and \
|
162
|
+
not self._physical_lower_limit.complies_to_lower(physical_value):
|
163
|
+
return False
|
164
|
+
|
165
|
+
if self._physical_upper_limit is not None and \
|
166
|
+
not self._physical_upper_limit.complies_to_upper(physical_value):
|
167
|
+
return False
|
168
|
+
|
169
|
+
return True
|
170
|
+
|
171
|
+
def internal_applies(self, internal_value: AtomicOdxType) -> bool:
|
172
|
+
"""Returns True iff the segment is applicable to a given internal value"""
|
173
|
+
# Do type checks
|
174
|
+
expected_type = self.internal_type.python_type
|
175
|
+
if issubclass(expected_type, float):
|
176
|
+
if not isinstance(internal_value, (int, float)):
|
177
|
+
return False
|
178
|
+
else:
|
179
|
+
if not isinstance(internal_value, expected_type):
|
180
|
+
return False
|
181
|
+
|
182
|
+
if self.internal_lower_limit is not None and \
|
183
|
+
not self.internal_lower_limit.complies_to_lower(internal_value):
|
184
|
+
return False
|
185
|
+
|
186
|
+
if self.internal_upper_limit is not None and \
|
187
|
+
not self.internal_upper_limit.complies_to_upper(internal_value):
|
188
|
+
return False
|
189
|
+
|
190
|
+
return True
|
@@ -0,0 +1,106 @@
|
|
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 RatFuncCompuMethod(CompuMethod):
|
16
|
+
"""A compu method using a rational function
|
17
|
+
|
18
|
+
i.e. internal values are converted to physical ones using the
|
19
|
+
function `f(x) = (a0 + a1*x + a2*x^2 ...)/(b0 + b0*x^2 ...)` where `f(x)`
|
20
|
+
is the physical value and `x` is the internal value. In contrast
|
21
|
+
to `ScaleRatFuncCompuMethod`, this compu method only exhibits a
|
22
|
+
single segment)
|
23
|
+
|
24
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.5.
|
25
|
+
"""
|
26
|
+
|
27
|
+
@property
|
28
|
+
def int_to_phys_segment(self) -> RatFuncSegment:
|
29
|
+
return self._int_to_phys_segment
|
30
|
+
|
31
|
+
@property
|
32
|
+
def phys_to_int_segment(self) -> Optional[RatFuncSegment]:
|
33
|
+
return self._phys_to_int_segment
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
37
|
+
internal_type: DataType,
|
38
|
+
physical_type: DataType) -> "RatFuncCompuMethod":
|
39
|
+
cm = CompuMethod.compu_method_from_et(
|
40
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
41
|
+
kwargs = dataclass_fields_asdict(cm)
|
42
|
+
|
43
|
+
return RatFuncCompuMethod(**kwargs)
|
44
|
+
|
45
|
+
def __post_init__(self) -> None:
|
46
|
+
odxassert(self.category == CompuCategory.RAT_FUNC,
|
47
|
+
"RatFuncCompuMethod must exhibit RAT-FUNC category")
|
48
|
+
|
49
|
+
odxassert(self.physical_type in [
|
50
|
+
DataType.A_FLOAT32,
|
51
|
+
DataType.A_FLOAT64,
|
52
|
+
DataType.A_INT32,
|
53
|
+
DataType.A_UINT32,
|
54
|
+
])
|
55
|
+
odxassert(self.internal_type in [
|
56
|
+
DataType.A_FLOAT32,
|
57
|
+
DataType.A_FLOAT64,
|
58
|
+
DataType.A_INT32,
|
59
|
+
DataType.A_UINT32,
|
60
|
+
])
|
61
|
+
|
62
|
+
if self.compu_internal_to_phys is None:
|
63
|
+
odxraise("RAT-FUNC compu methods require COMPU-INTERNAL-TO-PHYS")
|
64
|
+
return
|
65
|
+
|
66
|
+
int_to_phys_scales = self.compu_internal_to_phys.compu_scales
|
67
|
+
if len(int_to_phys_scales) != 1:
|
68
|
+
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
|
69
|
+
"COMPU-INTERNAL-TO-PHYS")
|
70
|
+
return cast(None, RatFuncCompuMethod)
|
71
|
+
|
72
|
+
self._int_to_phys_segment = RatFuncSegment.from_compu_scale(
|
73
|
+
int_to_phys_scales[0], value_type=self.physical_type)
|
74
|
+
|
75
|
+
self._phys_to_int_segment = None
|
76
|
+
if self.compu_phys_to_internal is not None:
|
77
|
+
phys_to_int_scales = self.compu_phys_to_internal.compu_scales
|
78
|
+
if len(phys_to_int_scales) != 1:
|
79
|
+
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
|
80
|
+
"COMPU-PHYS-TO-INTERNAL")
|
81
|
+
return cast(None, RatFuncCompuMethod)
|
82
|
+
|
83
|
+
self._phys_to_int_segment = RatFuncSegment.from_compu_scale(
|
84
|
+
phys_to_int_scales[0], value_type=self.internal_type)
|
85
|
+
|
86
|
+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
87
|
+
if not self._int_to_phys_segment.applies(internal_value):
|
88
|
+
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)
|
89
|
+
return cast(AtomicOdxType, None)
|
90
|
+
|
91
|
+
return self._int_to_phys_segment.convert(internal_value)
|
92
|
+
|
93
|
+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
94
|
+
if self._phys_to_int_segment is None or not self._phys_to_int_segment.applies(
|
95
|
+
physical_value):
|
96
|
+
odxraise(f"Cannot encode physical value {physical_value!r}", EncodeError)
|
97
|
+
return cast(AtomicOdxType, None)
|
98
|
+
|
99
|
+
return self._phys_to_int_segment.convert(physical_value)
|
100
|
+
|
101
|
+
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
102
|
+
return self._phys_to_int_segment is not None and self._phys_to_int_segment.applies(
|
103
|
+
physical_value)
|
104
|
+
|
105
|
+
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
106
|
+
return self._int_to_phys_segment.applies(internal_value)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import List, Optional, Union
|
4
|
+
|
5
|
+
from ..exceptions import odxraise, odxrequire
|
6
|
+
from ..odxtypes import AtomicOdxType, DataType
|
7
|
+
from .compuscale import CompuScale
|
8
|
+
from .limit import Limit
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class RatFuncSegment:
|
13
|
+
"""Helper class to represent a segment of a piecewise rational function.
|
14
|
+
"""
|
15
|
+
value_type: DataType
|
16
|
+
|
17
|
+
numerator_coeffs: List[Union[int, float]]
|
18
|
+
denominator_coeffs: List[Union[int, float]]
|
19
|
+
|
20
|
+
lower_limit: Optional[Limit]
|
21
|
+
upper_limit: Optional[Limit]
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def from_compu_scale(scale: CompuScale, value_type: DataType) -> "RatFuncSegment":
|
25
|
+
coeffs = odxrequire(scale.compu_rational_coeffs,
|
26
|
+
"Scales for rational functions must exhibit "
|
27
|
+
"COMPU-RATIONAL-COEFFS")
|
28
|
+
|
29
|
+
numerator_coeffs = coeffs.numerators
|
30
|
+
denominator_coeffs = coeffs.denominators
|
31
|
+
|
32
|
+
lower_limit = scale.lower_limit
|
33
|
+
upper_limit = scale.upper_limit
|
34
|
+
|
35
|
+
return RatFuncSegment(
|
36
|
+
numerator_coeffs=numerator_coeffs,
|
37
|
+
denominator_coeffs=denominator_coeffs,
|
38
|
+
lower_limit=lower_limit,
|
39
|
+
upper_limit=upper_limit,
|
40
|
+
value_type=scale.range_type)
|
41
|
+
|
42
|
+
def convert(self, value: AtomicOdxType) -> Union[float, int]:
|
43
|
+
if not isinstance(value, (int, float)):
|
44
|
+
odxraise(f"Internal values of linear compumethods must "
|
45
|
+
f"either be int or float (is: {type(value).__name__})")
|
46
|
+
|
47
|
+
numerator = 0.0
|
48
|
+
x = float(value)
|
49
|
+
for numerator_coeff in reversed(self.numerator_coeffs):
|
50
|
+
numerator *= x
|
51
|
+
numerator += float(numerator_coeff)
|
52
|
+
|
53
|
+
denominator = 0.0
|
54
|
+
for denominator_coeff in reversed(self.denominator_coeffs):
|
55
|
+
denominator *= x
|
56
|
+
denominator += float(denominator_coeff)
|
57
|
+
|
58
|
+
result = numerator / denominator
|
59
|
+
|
60
|
+
if self.value_type in [
|
61
|
+
DataType.A_INT32,
|
62
|
+
DataType.A_UINT32,
|
63
|
+
]:
|
64
|
+
result = round(result)
|
65
|
+
|
66
|
+
return result
|
67
|
+
|
68
|
+
def applies(self, value: AtomicOdxType) -> bool:
|
69
|
+
"""Returns True iff the segment is applicable to a given internal value"""
|
70
|
+
# Do type checks
|
71
|
+
expected_type = self.value_type.python_type
|
72
|
+
if issubclass(expected_type, float):
|
73
|
+
if not isinstance(value, (int, float)):
|
74
|
+
return False
|
75
|
+
else:
|
76
|
+
if not isinstance(value, expected_type):
|
77
|
+
return False
|
78
|
+
|
79
|
+
if self.lower_limit is not None and \
|
80
|
+
not self.lower_limit.complies_to_lower(value):
|
81
|
+
return False
|
82
|
+
|
83
|
+
if self.upper_limit is not None and \
|
84
|
+
not self.upper_limit.complies_to_upper(value):
|
85
|
+
return False
|
86
|
+
|
87
|
+
return True
|