odxtools 6.6.1__py3-none-any.whl → 6.7.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 +5 -5
- odxtools/basicstructure.py +7 -8
- odxtools/cli/_parser_utils.py +15 -0
- odxtools/cli/_print_utils.py +4 -3
- odxtools/cli/browse.py +19 -14
- odxtools/cli/compare.py +24 -16
- odxtools/cli/decode.py +2 -1
- odxtools/cli/dummy_sub_parser.py +3 -1
- odxtools/cli/find.py +2 -1
- odxtools/cli/list.py +2 -1
- odxtools/cli/main.py +1 -0
- odxtools/cli/snoop.py +4 -1
- odxtools/comparaminstance.py +7 -5
- odxtools/compumethods/compumethod.py +2 -4
- odxtools/compumethods/compuscale.py +45 -5
- odxtools/compumethods/createanycompumethod.py +27 -35
- odxtools/compumethods/limit.py +70 -36
- odxtools/compumethods/linearcompumethod.py +68 -59
- odxtools/compumethods/tabintpcompumethod.py +19 -8
- odxtools/compumethods/texttablecompumethod.py +32 -36
- odxtools/dataobjectproperty.py +13 -10
- odxtools/decodestate.py +6 -3
- odxtools/determinenumberofitems.py +1 -1
- odxtools/diagcodedtype.py +5 -4
- odxtools/diagdatadictionaryspec.py +108 -83
- odxtools/diaglayer.py +75 -35
- odxtools/diaglayertype.py +17 -5
- odxtools/diagservice.py +1 -1
- odxtools/dopbase.py +4 -2
- odxtools/dtcdop.py +7 -5
- odxtools/dynamiclengthfield.py +6 -5
- odxtools/endofpdufield.py +4 -4
- odxtools/environmentdatadescription.py +4 -2
- odxtools/inputparam.py +1 -1
- odxtools/internalconstr.py +14 -5
- odxtools/isotp_state_machine.py +14 -6
- odxtools/message.py +1 -1
- odxtools/multiplexer.py +18 -13
- odxtools/multiplexercase.py +27 -5
- odxtools/multiplexerswitchkey.py +1 -1
- odxtools/nameditemlist.py +7 -6
- odxtools/odxlink.py +2 -2
- odxtools/odxtypes.py +56 -3
- odxtools/outputparam.py +2 -2
- odxtools/parameterinfo.py +12 -5
- odxtools/parameters/codedconstparameter.py +33 -12
- odxtools/parameters/createanyparameter.py +19 -193
- odxtools/parameters/dynamicparameter.py +21 -1
- odxtools/parameters/lengthkeyparameter.py +28 -4
- odxtools/parameters/matchingrequestparameter.py +27 -9
- odxtools/parameters/nrcconstparameter.py +34 -11
- odxtools/parameters/parameter.py +58 -32
- odxtools/parameters/parameterwithdop.py +28 -15
- odxtools/parameters/physicalconstantparameter.py +28 -4
- odxtools/parameters/reservedparameter.py +32 -18
- odxtools/parameters/systemparameter.py +25 -2
- odxtools/parameters/tableentryparameter.py +45 -6
- odxtools/parameters/tablekeyparameter.py +43 -10
- odxtools/parameters/tablestructparameter.py +36 -14
- odxtools/parameters/valueparameter.py +24 -2
- odxtools/paramlengthinfotype.py +4 -1
- odxtools/parentref.py +4 -1
- odxtools/scaleconstr.py +11 -5
- odxtools/statetransition.py +1 -1
- odxtools/staticfield.py +101 -0
- odxtools/table.py +2 -1
- odxtools/tablerow.py +11 -4
- odxtools/templates/macros/printDOP.xml.jinja2 +30 -34
- odxtools/templates/macros/printMux.xml.jinja2 +3 -2
- odxtools/templates/macros/printParam.xml.jinja2 +9 -9
- odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
- odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
- odxtools/uds.py +2 -2
- odxtools/version.py +2 -2
- odxtools/write_pdx_file.py +3 -3
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/METADATA +28 -16
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/RECORD +81 -79
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/WHEEL +1 -1
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/LICENSE +0 -0
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,14 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
|
-
import warnings
|
3
2
|
from typing import Any, Dict, List, Optional
|
4
3
|
from xml.etree import ElementTree
|
5
4
|
|
6
|
-
from ..exceptions import
|
7
|
-
from ..globals import logger
|
5
|
+
from ..exceptions import odxassert, odxraise, odxrequire
|
8
6
|
from ..odxlink import OdxDocFragment
|
9
7
|
from ..odxtypes import DataType
|
10
8
|
from .compumethod import CompuMethod
|
11
9
|
from .compuscale import CompuScale
|
12
10
|
from .identicalcompumethod import IdenticalCompuMethod
|
13
|
-
from .limit import
|
11
|
+
from .limit import Limit
|
14
12
|
from .linearcompumethod import LinearCompuMethod
|
15
13
|
from .scalelinearcompumethod import ScaleLinearCompuMethod
|
16
14
|
from .tabintpcompumethod import TabIntpCompuMethod
|
@@ -18,8 +16,9 @@ from .texttablecompumethod import TexttableCompuMethod
|
|
18
16
|
|
19
17
|
|
20
18
|
def _parse_compu_scale_to_linear_compu_method(
|
19
|
+
et_element: ElementTree.Element,
|
20
|
+
doc_frags: List[OdxDocFragment],
|
21
21
|
*,
|
22
|
-
scale_element: ElementTree.Element,
|
23
22
|
internal_type: DataType,
|
24
23
|
physical_type: DataType,
|
25
24
|
is_scale_linear: bool = False,
|
@@ -38,7 +37,7 @@ def _parse_compu_scale_to_linear_compu_method(
|
|
38
37
|
DataType.A_UINT32,
|
39
38
|
])
|
40
39
|
|
41
|
-
if physical_type.
|
40
|
+
if physical_type.python_type == float:
|
42
41
|
computation_python_type = physical_type.from_string
|
43
42
|
else:
|
44
43
|
computation_python_type = internal_type.from_string
|
@@ -47,7 +46,7 @@ def _parse_compu_scale_to_linear_compu_method(
|
|
47
46
|
kwargs["internal_type"] = internal_type
|
48
47
|
kwargs["physical_type"] = physical_type
|
49
48
|
|
50
|
-
coeffs = odxrequire(
|
49
|
+
coeffs = odxrequire(et_element.find("COMPU-RATIONAL-COEFFS"))
|
51
50
|
nums = coeffs.iterfind("COMPU-NUMERATOR/V")
|
52
51
|
|
53
52
|
offset = computation_python_type(odxrequire(next(nums).text))
|
@@ -57,32 +56,24 @@ def _parse_compu_scale_to_linear_compu_method(
|
|
57
56
|
if (string := coeffs.findtext("COMPU-DENOMINATOR/V")) is not None:
|
58
57
|
denominator = float(string)
|
59
58
|
if denominator == 0:
|
60
|
-
|
61
|
-
|
62
|
-
OdxWarning,
|
63
|
-
stacklevel=1)
|
59
|
+
odxraise("CompuMethod: A denominator of zero will lead to divisions by zero.")
|
60
|
+
|
64
61
|
# Read lower limit
|
65
|
-
internal_lower_limit = Limit.
|
66
|
-
|
67
|
-
|
62
|
+
internal_lower_limit = Limit.limit_from_et(
|
63
|
+
et_element.find("LOWER-LIMIT"),
|
64
|
+
doc_frags,
|
65
|
+
value_type=internal_type,
|
68
66
|
)
|
69
|
-
|
70
|
-
internal_lower_limit = Limit(0, IntervalType.INFINITE)
|
67
|
+
|
71
68
|
kwargs["internal_lower_limit"] = internal_lower_limit
|
72
69
|
|
73
70
|
# Read upper limit
|
74
|
-
internal_upper_limit = Limit.
|
75
|
-
|
76
|
-
|
71
|
+
internal_upper_limit = Limit.limit_from_et(
|
72
|
+
et_element.find("UPPER-LIMIT"),
|
73
|
+
doc_frags,
|
74
|
+
value_type=internal_type,
|
77
75
|
)
|
78
|
-
|
79
|
-
if not is_scale_linear:
|
80
|
-
internal_upper_limit = Limit(0, IntervalType.INFINITE)
|
81
|
-
else:
|
82
|
-
odxassert(internal_lower_limit is not None and
|
83
|
-
internal_lower_limit.interval_type == IntervalType.CLOSED)
|
84
|
-
logger.info("Scale linear without UPPER-LIMIT")
|
85
|
-
internal_upper_limit = internal_lower_limit
|
76
|
+
|
86
77
|
kwargs["internal_upper_limit"] = internal_upper_limit
|
87
78
|
kwargs["denominator"] = denominator
|
88
79
|
kwargs["factor"] = factor
|
@@ -92,19 +83,19 @@ def _parse_compu_scale_to_linear_compu_method(
|
|
92
83
|
|
93
84
|
|
94
85
|
def create_compu_default_value(et_element: Optional[ElementTree.Element],
|
95
|
-
doc_frags: List[OdxDocFragment], internal_type: DataType,
|
86
|
+
doc_frags: List[OdxDocFragment], internal_type: DataType, *,
|
96
87
|
physical_type: DataType) -> Optional[CompuScale]:
|
97
88
|
if et_element is None:
|
98
89
|
return None
|
99
90
|
compu_const = physical_type.create_from_et(et_element)
|
100
|
-
scale = CompuScale.
|
91
|
+
scale = CompuScale.compuscale_from_et(
|
101
92
|
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
102
93
|
scale.compu_const = compu_const
|
103
94
|
return scale
|
104
95
|
|
105
96
|
|
106
97
|
def create_any_compu_method_from_et(et_element: ElementTree.Element,
|
107
|
-
doc_frags: List[OdxDocFragment], internal_type: DataType,
|
98
|
+
doc_frags: List[OdxDocFragment], *, internal_type: DataType,
|
108
99
|
physical_type: DataType) -> CompuMethod:
|
109
100
|
compu_category = et_element.findtext("CATEGORY")
|
110
101
|
odxassert(compu_category in [
|
@@ -142,7 +133,7 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
|
|
142
133
|
internal_to_phys: List[CompuScale] = []
|
143
134
|
for scale_elem in compu_internal_to_phys.iterfind("COMPU-SCALES/COMPU-SCALE"):
|
144
135
|
internal_to_phys.append(
|
145
|
-
CompuScale.
|
136
|
+
CompuScale.compuscale_from_et(
|
146
137
|
scale_elem, doc_frags, internal_type=internal_type,
|
147
138
|
physical_type=physical_type))
|
148
139
|
compu_default_value = create_compu_default_value(
|
@@ -158,13 +149,13 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
|
|
158
149
|
# Compu method can be described by the function f(x) = (offset + factor * x) / denominator
|
159
150
|
|
160
151
|
scale_elem = odxrequire(et_element.find("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE"))
|
161
|
-
return _parse_compu_scale_to_linear_compu_method(
|
152
|
+
return _parse_compu_scale_to_linear_compu_method(scale_elem, doc_frags, **kwargs)
|
162
153
|
|
163
154
|
elif compu_category == "SCALE-LINEAR":
|
164
155
|
|
165
156
|
scale_elems = et_element.iterfind("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE")
|
166
157
|
linear_methods = [
|
167
|
-
_parse_compu_scale_to_linear_compu_method(
|
158
|
+
_parse_compu_scale_to_linear_compu_method(scale_elem, doc_frags, **kwargs)
|
168
159
|
for scale_elem in scale_elems
|
169
160
|
]
|
170
161
|
return ScaleLinearCompuMethod(linear_methods=linear_methods, **kwargs)
|
@@ -189,6 +180,7 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
|
|
189
180
|
return TabIntpCompuMethod(
|
190
181
|
internal_points=internal_points, physical_points=physical_points, **kwargs)
|
191
182
|
|
192
|
-
# TODO: Implement
|
193
|
-
|
183
|
+
# TODO: Implement all categories (never instantiate the CompuMethod base class!)
|
184
|
+
odxraise(f"Warning: Computation category {compu_category} is not implemented!")
|
185
|
+
|
194
186
|
return IdenticalCompuMethod(internal_type=DataType.A_UINT32, physical_type=DataType.A_UINT32)
|
odxtools/compumethods/limit.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from enum import Enum
|
4
|
-
from typing import Optional
|
4
|
+
from typing import List, Optional, overload
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
-
from ..exceptions import
|
8
|
-
from ..
|
7
|
+
from ..exceptions import odxraise
|
8
|
+
from ..odxlink import OdxDocFragment
|
9
|
+
from ..odxtypes import AtomicOdxType, DataType, compare_odx_values
|
9
10
|
|
10
11
|
|
11
12
|
class IntervalType(Enum):
|
@@ -16,42 +17,54 @@ class IntervalType(Enum):
|
|
16
17
|
|
17
18
|
@dataclass
|
18
19
|
class Limit:
|
19
|
-
|
20
|
-
|
20
|
+
value_raw: Optional[str]
|
21
|
+
value_type: Optional[DataType]
|
22
|
+
interval_type: Optional[IntervalType]
|
21
23
|
|
22
24
|
def __post_init__(self) -> None:
|
23
|
-
|
24
|
-
|
25
|
+
self._value: Optional[AtomicOdxType] = None
|
26
|
+
|
27
|
+
if self.value_type is not None:
|
28
|
+
self.set_value_type(self.value_type)
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
@overload
|
32
|
+
def limit_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment],
|
33
|
+
value_type: Optional[DataType]) -> "Limit":
|
34
|
+
...
|
35
|
+
|
36
|
+
@staticmethod
|
37
|
+
@overload
|
38
|
+
def limit_from_et(et_element: None, doc_frags: List[OdxDocFragment],
|
39
|
+
value_type: Optional[DataType]) -> None:
|
40
|
+
...
|
25
41
|
|
26
42
|
@staticmethod
|
27
|
-
def
|
28
|
-
|
43
|
+
def limit_from_et(et_element: Optional[ElementTree.Element], doc_frags: List[OdxDocFragment],
|
44
|
+
value_type: Optional[DataType]) -> Optional["Limit"]:
|
29
45
|
|
30
46
|
if et_element is None:
|
31
47
|
return None
|
32
48
|
|
49
|
+
interval_type = None
|
33
50
|
if (interval_type_str := et_element.get("INTERVAL-TYPE")) is not None:
|
34
51
|
try:
|
35
52
|
interval_type = IntervalType(interval_type_str)
|
36
53
|
except ValueError:
|
37
|
-
interval_type = IntervalType.CLOSED
|
38
54
|
odxraise(f"Encountered unknown interval type '{interval_type_str}'")
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return Limit(bytes.fromhex(hex_text), interval_type)
|
53
|
-
else:
|
54
|
-
return Limit(internal_type.from_string(odxrequire(et_element.text)), interval_type)
|
55
|
+
|
56
|
+
value_raw = et_element.text
|
57
|
+
|
58
|
+
return Limit(value_raw=value_raw, interval_type=interval_type, value_type=value_type)
|
59
|
+
|
60
|
+
def set_value_type(self, value_type: DataType) -> None:
|
61
|
+
self.value_type = value_type
|
62
|
+
if self.value_raw is not None:
|
63
|
+
self._value = value_type.from_string(self.value_raw)
|
64
|
+
|
65
|
+
@property
|
66
|
+
def value(self) -> Optional[AtomicOdxType]:
|
67
|
+
return self._value
|
55
68
|
|
56
69
|
def complies_to_upper(self, value: AtomicOdxType) -> bool:
|
57
70
|
"""Checks if the value is in the range w.r.t. the upper limit.
|
@@ -60,13 +73,23 @@ class Limit:
|
|
60
73
|
* If the interval type is open, return `value < limit.value`.
|
61
74
|
* If the interval type is infinite, return `True`.
|
62
75
|
"""
|
63
|
-
if self.
|
64
|
-
|
65
|
-
|
66
|
-
return value < self.value # type: ignore[operator]
|
67
|
-
elif self.interval_type == IntervalType.INFINITE:
|
76
|
+
if self._value is None:
|
77
|
+
# if no value is specified, assume interval type INFINITE
|
78
|
+
# (what are we supposed to compare against?)
|
68
79
|
return True
|
69
80
|
|
81
|
+
if self.interval_type is None or self.interval_type == IntervalType.CLOSED:
|
82
|
+
# assume interval type CLOSED if a value was specified,
|
83
|
+
# but no interval type
|
84
|
+
return compare_odx_values(value, self._value) <= 0
|
85
|
+
elif self.interval_type == IntervalType.OPEN:
|
86
|
+
return compare_odx_values(value, self._value) < 0
|
87
|
+
|
88
|
+
if self.interval_type != IntervalType.INFINITE:
|
89
|
+
odxraise("Unhandled interval type {self.interval_type}")
|
90
|
+
|
91
|
+
return True
|
92
|
+
|
70
93
|
def complies_to_lower(self, value: AtomicOdxType) -> bool:
|
71
94
|
"""Checks if the value is in the range w.r.t. the lower limit.
|
72
95
|
|
@@ -74,9 +97,20 @@ class Limit:
|
|
74
97
|
* If the interval type is open, return `limit.value < value`.
|
75
98
|
* If the interval type is infinite, return `True`.
|
76
99
|
"""
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
elif self.interval_type == IntervalType.INFINITE:
|
100
|
+
|
101
|
+
if self._value is None:
|
102
|
+
# if no value is specified, assume interval type INFINITE
|
103
|
+
# (what are we supposed to compare against?)
|
82
104
|
return True
|
105
|
+
|
106
|
+
if self.interval_type is None or self.interval_type == IntervalType.CLOSED:
|
107
|
+
# assume interval type CLOSED if a value was specified,
|
108
|
+
# but no interval type
|
109
|
+
return compare_odx_values(value, self._value) >= 0
|
110
|
+
elif self.interval_type == IntervalType.OPEN:
|
111
|
+
return compare_odx_values(value, self._value) > 0
|
112
|
+
|
113
|
+
if self.interval_type != IntervalType.INFINITE:
|
114
|
+
odxraise("Unhandled interval type {self.interval_type}")
|
115
|
+
|
116
|
+
return True
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
3
|
+
from typing import Optional
|
4
4
|
|
5
|
-
from ..exceptions import DecodeError, EncodeError, odxassert
|
5
|
+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
|
6
6
|
from ..odxtypes import AtomicOdxType, DataType
|
7
7
|
from .compumethod import CompuMethod, CompuMethodCategory
|
8
|
-
from .limit import
|
8
|
+
from .limit import Limit
|
9
9
|
|
10
10
|
|
11
11
|
@dataclass
|
@@ -60,8 +60,8 @@ class LinearCompuMethod(CompuMethod):
|
|
60
60
|
offset: float
|
61
61
|
factor: float
|
62
62
|
denominator: float
|
63
|
-
internal_lower_limit: Limit
|
64
|
-
internal_upper_limit: Limit
|
63
|
+
internal_lower_limit: Optional[Limit]
|
64
|
+
internal_upper_limit: Optional[Limit]
|
65
65
|
|
66
66
|
def __post_init__(self) -> None:
|
67
67
|
odxassert(self.denominator > 0)
|
@@ -73,11 +73,11 @@ class LinearCompuMethod(CompuMethod):
|
|
73
73
|
return "LINEAR"
|
74
74
|
|
75
75
|
@property
|
76
|
-
def physical_lower_limit(self) -> Limit:
|
76
|
+
def physical_lower_limit(self) -> Optional[Limit]:
|
77
77
|
return self._physical_lower_limit
|
78
78
|
|
79
79
|
@property
|
80
|
-
def physical_upper_limit(self) -> Limit:
|
80
|
+
def physical_upper_limit(self) -> Optional[Limit]:
|
81
81
|
return self._physical_upper_limit
|
82
82
|
|
83
83
|
def __compute_physical_limits(self) -> None:
|
@@ -86,65 +86,61 @@ class LinearCompuMethod(CompuMethod):
|
|
86
86
|
This method is only called during the initialization of a LinearCompuMethod.
|
87
87
|
"""
|
88
88
|
|
89
|
-
def
|
89
|
+
def convert_internal_to_physical_limit(internal_limit: Optional[Limit],
|
90
|
+
is_upper_limit: bool) -> Optional[Limit]:
|
90
91
|
"""Helper method
|
91
92
|
|
92
93
|
Parameters:
|
93
94
|
|
94
|
-
|
95
|
+
internal_limit
|
95
96
|
the internal limit to be converted to a physical limit
|
96
97
|
is_upper_limit
|
97
98
|
True iff limit is the internal upper limit
|
98
99
|
"""
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
100
|
+
if internal_limit is None or internal_limit.value_raw is None:
|
101
|
+
return None
|
102
|
+
|
103
|
+
internal_value = self.internal_type.from_string(internal_limit.value_raw)
|
104
|
+
physical_value = self._convert_internal_to_physical(internal_value)
|
105
|
+
|
106
|
+
result = Limit(
|
107
|
+
value_raw=str(physical_value),
|
108
|
+
value_type=self.physical_type,
|
109
|
+
interval_type=internal_limit.interval_type)
|
110
|
+
|
111
|
+
return result
|
112
|
+
|
113
|
+
self._physical_lower_limit = None
|
114
|
+
self._physical_upper_limit = None
|
114
115
|
|
115
116
|
if self.factor >= 0:
|
116
|
-
self._physical_lower_limit =
|
117
|
-
|
118
|
-
self._physical_upper_limit =
|
119
|
-
|
117
|
+
self._physical_lower_limit = convert_internal_to_physical_limit(
|
118
|
+
self.internal_lower_limit, False)
|
119
|
+
self._physical_upper_limit = convert_internal_to_physical_limit(
|
120
|
+
self.internal_upper_limit, True)
|
120
121
|
else:
|
121
122
|
# If the factor is negative, the lower and upper limit are swapped
|
122
|
-
self._physical_lower_limit =
|
123
|
-
|
124
|
-
self._physical_upper_limit =
|
125
|
-
|
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)
|
123
|
+
self._physical_lower_limit = convert_internal_to_physical_limit(
|
124
|
+
self.internal_upper_limit, True)
|
125
|
+
self._physical_upper_limit = convert_internal_to_physical_limit(
|
126
|
+
self.internal_lower_limit, False)
|
132
127
|
|
133
128
|
def _convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
134
129
|
if not isinstance(internal_value, (int, float)):
|
135
|
-
raise DecodeError("The type of internal values of linear compumethods must "
|
136
|
-
"either int or float")
|
130
|
+
raise DecodeError(f"The type of internal values of linear compumethods must "
|
131
|
+
f"either int or float (is: {type(internal_value).__name__})")
|
137
132
|
|
138
133
|
if self.denominator is None:
|
139
134
|
result = self.offset + self.factor * internal_value
|
140
135
|
else:
|
141
136
|
result = (self.offset + self.factor * internal_value) / self.denominator
|
142
137
|
|
143
|
-
if self.
|
138
|
+
if self.physical_type in [
|
144
139
|
DataType.A_INT32,
|
145
140
|
DataType.A_UINT32,
|
146
141
|
]:
|
147
142
|
result = round(result)
|
143
|
+
|
148
144
|
return self.physical_type.make_from(result)
|
149
145
|
|
150
146
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
@@ -153,8 +149,10 @@ class LinearCompuMethod(CompuMethod):
|
|
153
149
|
|
154
150
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
155
151
|
if not isinstance(physical_value, (int, float)):
|
156
|
-
|
157
|
-
|
152
|
+
odxraise(
|
153
|
+
"The type of physical values of linear compumethods must "
|
154
|
+
"either int or float", EncodeError)
|
155
|
+
return 0
|
158
156
|
|
159
157
|
odxassert(
|
160
158
|
self.is_valid_physical_value(physical_value),
|
@@ -166,7 +164,7 @@ class LinearCompuMethod(CompuMethod):
|
|
166
164
|
else:
|
167
165
|
result = ((physical_value * self.denominator) - self.offset) / self.factor
|
168
166
|
|
169
|
-
if self.
|
167
|
+
if self.internal_type in [
|
170
168
|
DataType.A_INT32,
|
171
169
|
DataType.A_UINT32,
|
172
170
|
]:
|
@@ -175,29 +173,40 @@ class LinearCompuMethod(CompuMethod):
|
|
175
173
|
|
176
174
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
177
175
|
# Do type checks
|
178
|
-
expected_type = self.physical_type.
|
179
|
-
if expected_type
|
180
|
-
|
181
|
-
|
182
|
-
|
176
|
+
expected_type = self.physical_type.python_type
|
177
|
+
if issubclass(expected_type, float):
|
178
|
+
if not isinstance(physical_value, (int, float)):
|
179
|
+
return False
|
180
|
+
else:
|
181
|
+
if not isinstance(physical_value, expected_type):
|
182
|
+
return False
|
183
183
|
|
184
|
-
#
|
185
|
-
if not self.physical_lower_limit.complies_to_lower(
|
184
|
+
# Check the limits
|
185
|
+
if self.physical_lower_limit is not None and not self.physical_lower_limit.complies_to_lower(
|
186
|
+
physical_value):
|
186
187
|
return False
|
187
|
-
if not self.physical_upper_limit.complies_to_upper(
|
188
|
+
if self.physical_upper_limit is not None and not self.physical_upper_limit.complies_to_upper(
|
189
|
+
physical_value):
|
188
190
|
return False
|
191
|
+
|
189
192
|
return True
|
190
193
|
|
191
194
|
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
195
|
+
# Do type checks
|
196
|
+
expected_type = self.internal_type.python_type
|
197
|
+
if issubclass(expected_type, float):
|
198
|
+
if not isinstance(internal_value, (int, float)):
|
199
|
+
return False
|
200
|
+
else:
|
201
|
+
if not isinstance(internal_value, expected_type):
|
202
|
+
return False
|
197
203
|
|
198
|
-
|
204
|
+
# Check the limits
|
205
|
+
if self.internal_lower_limit is not None and not self.internal_lower_limit.complies_to_lower(
|
206
|
+
internal_value):
|
199
207
|
return False
|
200
|
-
if not self.internal_upper_limit.complies_to_upper(
|
208
|
+
if self.internal_upper_limit is not None and not self.internal_upper_limit.complies_to_upper(
|
209
|
+
internal_value):
|
201
210
|
return False
|
202
211
|
|
203
212
|
return True
|
@@ -69,8 +69,23 @@ class TabIntpCompuMethod(CompuMethod):
|
|
69
69
|
physical_points: List[Union[float, int]]
|
70
70
|
|
71
71
|
def __post_init__(self) -> None:
|
72
|
-
self._physical_lower_limit = Limit(
|
73
|
-
|
72
|
+
self._physical_lower_limit = Limit(
|
73
|
+
value_raw=str(min(self.physical_points)),
|
74
|
+
value_type=self.physical_type,
|
75
|
+
interval_type=IntervalType.CLOSED)
|
76
|
+
self._physical_upper_limit = Limit(
|
77
|
+
value_raw=str(max(self.physical_points)),
|
78
|
+
value_type=self.physical_type,
|
79
|
+
interval_type=IntervalType.CLOSED)
|
80
|
+
|
81
|
+
self._internal_lower_limit = Limit(
|
82
|
+
value_raw=str(min(self.internal_points)),
|
83
|
+
value_type=self.internal_type,
|
84
|
+
interval_type=IntervalType.CLOSED)
|
85
|
+
self._internal_upper_limit = Limit(
|
86
|
+
value_raw=str(max(self.internal_points)),
|
87
|
+
value_type=self.internal_type,
|
88
|
+
interval_type=IntervalType.CLOSED)
|
74
89
|
|
75
90
|
self._assert_validity()
|
76
91
|
|
@@ -124,9 +139,7 @@ class TabIntpCompuMethod(CompuMethod):
|
|
124
139
|
odxassert(
|
125
140
|
isinstance(physical_value, (int, float)),
|
126
141
|
"Only integers and floats can be piecewise linearly interpolated")
|
127
|
-
result = self._piecewise_linear_interpolate(
|
128
|
-
physical_value, # type: ignore[arg-type]
|
129
|
-
reference_points)
|
142
|
+
result = self._piecewise_linear_interpolate(physical_value, reference_points)
|
130
143
|
|
131
144
|
if result is None:
|
132
145
|
raise EncodeError(f"Internal value {physical_value!r} must be inside the range"
|
@@ -142,9 +155,7 @@ class TabIntpCompuMethod(CompuMethod):
|
|
142
155
|
"either int or float")
|
143
156
|
|
144
157
|
reference_points = list(zip(self.internal_points, self.physical_points))
|
145
|
-
result = self._piecewise_linear_interpolate(
|
146
|
-
internal_value, # type: ignore[arg-type]
|
147
|
-
reference_points)
|
158
|
+
result = self._piecewise_linear_interpolate(internal_value, reference_points)
|
148
159
|
|
149
160
|
if result is None:
|
150
161
|
raise DecodeError(f"Internal value {internal_value!r} must be inside the range"
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional
|
3
|
+
from typing import List, Optional, cast
|
4
4
|
|
5
|
-
from ..exceptions import DecodeError, EncodeError, odxassert
|
5
|
+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
|
6
6
|
from ..odxtypes import AtomicOdxType, DataType
|
7
7
|
from .compumethod import CompuMethod, CompuMethodCategory
|
8
8
|
from .compuscale import CompuScale
|
@@ -28,53 +28,49 @@ class TexttableCompuMethod(CompuMethod):
|
|
28
28
|
def category(self) -> CompuMethodCategory:
|
29
29
|
return "TEXTTABLE"
|
30
30
|
|
31
|
-
def get_scales(self) -> List[CompuScale]:
|
32
|
-
scales = list(self.internal_to_phys)
|
33
|
-
if self.compu_default_value:
|
34
|
-
# Default is last, since it's a fallback
|
35
|
-
scales.append(self.compu_default_value)
|
36
|
-
return scales
|
37
|
-
|
38
31
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
39
|
-
matching_scales = [x for x in self.
|
32
|
+
matching_scales = [x for x in self.internal_to_phys if x.compu_const == physical_value]
|
40
33
|
for scale in matching_scales:
|
41
34
|
if scale.compu_inverse_value is not None:
|
42
35
|
return scale.compu_inverse_value
|
43
|
-
elif scale.lower_limit is not None:
|
44
|
-
return scale.lower_limit.
|
45
|
-
elif scale.upper_limit is not None:
|
46
|
-
return scale.upper_limit.
|
36
|
+
elif scale.lower_limit is not None and scale.lower_limit._value is not None:
|
37
|
+
return scale.lower_limit._value
|
38
|
+
elif scale.upper_limit is not None and scale.upper_limit._value is not None:
|
39
|
+
return scale.upper_limit._value
|
40
|
+
|
41
|
+
if self.compu_default_value is not None and self.compu_default_value.compu_inverse_value is not None:
|
42
|
+
return self.compu_default_value.compu_inverse_value
|
47
43
|
|
48
44
|
raise EncodeError(f"Texttable compu method could not encode '{physical_value!r}'.")
|
49
45
|
|
50
46
|
def __is_internal_in_scale(self, internal_value: AtomicOdxType, scale: CompuScale) -> bool:
|
51
47
|
if scale == self.compu_default_value:
|
52
48
|
return True
|
53
|
-
|
54
|
-
|
55
|
-
return False
|
56
|
-
# If no UPPER-LIMIT is defined
|
57
|
-
# the COMPU-SCALE will be applied only for the value defined in LOWER-LIMIT
|
58
|
-
upper_limit = scale.upper_limit or scale.lower_limit
|
59
|
-
if upper_limit is not None and not upper_limit.complies_to_upper(internal_value):
|
60
|
-
return False
|
61
|
-
# value complies to the defined limits
|
62
|
-
return True
|
49
|
+
|
50
|
+
return scale.applies(internal_value)
|
63
51
|
|
64
52
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
53
|
+
matching_scales = [x for x in self.internal_to_phys if x.applies(internal_value)]
|
54
|
+
if len(matching_scales) == 0:
|
55
|
+
if self.compu_default_value is None or self.compu_default_value.compu_const is None:
|
56
|
+
odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
|
57
|
+
return cast(None, AtomicOdxType)
|
58
|
+
|
59
|
+
return self.compu_default_value.compu_const
|
60
|
+
|
61
|
+
if len(matching_scales) != 1 or matching_scales[0].compu_const is None:
|
62
|
+
odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
|
63
|
+
|
64
|
+
return matching_scales[0].compu_const
|
74
65
|
|
75
66
|
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
76
|
-
|
67
|
+
if self.compu_default_value is not None:
|
68
|
+
return True
|
69
|
+
|
70
|
+
return any(x.compu_const == physical_value for x in self.internal_to_phys)
|
77
71
|
|
78
72
|
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
79
|
-
|
80
|
-
|
73
|
+
if self.compu_default_value is not None:
|
74
|
+
return True
|
75
|
+
|
76
|
+
return any(scale.applies(internal_value) for scale in self.internal_to_phys)
|