odxtools 8.1.0__py3-none-any.whl → 8.2.1__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/audience.py +7 -8
- odxtools/compumethods/compucodecompumethod.py +63 -0
- odxtools/compumethods/compuinternaltophys.py +19 -2
- odxtools/compumethods/compumethod.py +28 -2
- odxtools/compumethods/compuphystointernal.py +19 -2
- odxtools/compumethods/createanycompumethod.py +12 -0
- odxtools/compumethods/linearcompumethod.py +2 -2
- odxtools/compumethods/ratfunccompumethod.py +106 -0
- odxtools/compumethods/ratfuncsegment.py +87 -0
- odxtools/compumethods/scaleratfunccompumethod.py +113 -0
- odxtools/dataobjectproperty.py +3 -0
- odxtools/diaglayers/basevariantraw.py +7 -1
- odxtools/diaglayers/diaglayer.py +5 -0
- odxtools/diaglayers/diaglayerraw.py +13 -1
- odxtools/diaglayers/ecushareddataraw.py +7 -1
- odxtools/diaglayers/ecuvariantraw.py +7 -1
- odxtools/diaglayers/functionalgroupraw.py +7 -1
- odxtools/diaglayers/hierarchyelementraw.py +7 -1
- odxtools/diaglayers/protocolraw.py +7 -1
- odxtools/diagservice.py +3 -1
- odxtools/dtcdop.py +6 -0
- odxtools/environmentdatadescription.py +11 -6
- odxtools/library.py +66 -0
- odxtools/minmaxlengthtype.py +1 -1
- odxtools/multiplexer.py +4 -4
- odxtools/multiplexercase.py +1 -1
- odxtools/odxtypes.py +14 -0
- odxtools/parameterinfo.py +132 -76
- odxtools/parameters/systemparameter.py +51 -8
- odxtools/progcode.py +8 -4
- odxtools/standardlengthtype.py +4 -1
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +3 -2
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +8 -0
- odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
- odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -23
- odxtools/unitspec.py +10 -9
- odxtools/version.py +2 -2
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/METADATA +1 -1
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/RECORD +43 -37
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/WHEEL +1 -1
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/LICENSE +0 -0
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/entry_points.txt +0 -0
- {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/top_level.txt +0 -0
odxtools/audience.py
CHANGED
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional
|
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from .additionalaudience import AdditionalAudience
|
7
|
+
from .nameditemlist import NamedItemList
|
7
8
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
8
9
|
from .odxtypes import odxstr_to_bool
|
9
10
|
from .snrefcontext import SnRefContext
|
@@ -45,11 +46,11 @@ class Audience:
|
|
45
46
|
return self.is_aftermarket_raw in [None, True]
|
46
47
|
|
47
48
|
@property
|
48
|
-
def enabled_audiences(self) ->
|
49
|
+
def enabled_audiences(self) -> NamedItemList[AdditionalAudience]:
|
49
50
|
return self._enabled_audiences
|
50
51
|
|
51
52
|
@property
|
52
|
-
def disabled_audiences(self) ->
|
53
|
+
def disabled_audiences(self) -> NamedItemList[AdditionalAudience]:
|
53
54
|
return self._disabled_audiences
|
54
55
|
|
55
56
|
@staticmethod
|
@@ -85,12 +86,10 @@ class Audience:
|
|
85
86
|
return {}
|
86
87
|
|
87
88
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
88
|
-
self._enabled_audiences =
|
89
|
-
odxlinks.resolve(ref, AdditionalAudience) for ref in self.enabled_audience_refs
|
90
|
-
|
91
|
-
|
92
|
-
odxlinks.resolve(ref, AdditionalAudience) for ref in self.disabled_audience_refs
|
93
|
-
]
|
89
|
+
self._enabled_audiences = NamedItemList(
|
90
|
+
[odxlinks.resolve(ref, AdditionalAudience) for ref in self.enabled_audience_refs])
|
91
|
+
self._disabled_audiences = NamedItemList(
|
92
|
+
[odxlinks.resolve(ref, AdditionalAudience) for ref in self.disabled_audience_refs])
|
94
93
|
|
95
94
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
96
95
|
pass
|
@@ -0,0 +1,63 @@
|
|
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 ..progcode import ProgCode
|
10
|
+
from ..utils import dataclass_fields_asdict
|
11
|
+
from .compumethod import CompuCategory, CompuMethod
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class CompuCodeCompuMethod(CompuMethod):
|
16
|
+
"""A compu method specifies the tranfer functions using Java bytecode
|
17
|
+
|
18
|
+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.9.
|
19
|
+
"""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def internal_to_phys_code(self) -> Optional[ProgCode]:
|
23
|
+
if self.compu_internal_to_phys is None:
|
24
|
+
return None
|
25
|
+
|
26
|
+
return self.compu_internal_to_phys.prog_code
|
27
|
+
|
28
|
+
@property
|
29
|
+
def phys_to_internal_code(self) -> Optional[ProgCode]:
|
30
|
+
if self.compu_phys_to_internal is None:
|
31
|
+
return None
|
32
|
+
|
33
|
+
return self.compu_phys_to_internal.prog_code
|
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) -> "CompuCodeCompuMethod":
|
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 CompuCodeCompuMethod(**kwargs)
|
44
|
+
|
45
|
+
def __post_init__(self) -> None:
|
46
|
+
odxassert(self.category == CompuCategory.COMPUCODE,
|
47
|
+
"CompuCodeCompuMethod must exhibit COMPUCODE category")
|
48
|
+
|
49
|
+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
50
|
+
odxraise(r"CompuCodeCompuMethod cannot be executed by odxtools", DecodeError)
|
51
|
+
return cast(AtomicOdxType, None)
|
52
|
+
|
53
|
+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
54
|
+
odxraise(r"CompuCodeCompuMethod cannot be executed by odxtools", EncodeError)
|
55
|
+
return cast(AtomicOdxType, None)
|
56
|
+
|
57
|
+
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
|
58
|
+
# CompuCodeCompuMethod cannot be executed by odxtools
|
59
|
+
return False
|
60
|
+
|
61
|
+
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
|
62
|
+
# CompuCodeCompuMethod cannot be executed by odxtools
|
63
|
+
return False
|
@@ -1,11 +1,12 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
-
from ..odxlink import OdxDocFragment
|
6
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
7
7
|
from ..odxtypes import DataType
|
8
8
|
from ..progcode import ProgCode
|
9
|
+
from ..snrefcontext import SnRefContext
|
9
10
|
from .compudefaultvalue import CompuDefaultValue
|
10
11
|
from .compuscale import CompuScale
|
11
12
|
|
@@ -37,3 +38,19 @@ class CompuInternalToPhys:
|
|
37
38
|
|
38
39
|
return CompuInternalToPhys(
|
39
40
|
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
|
41
|
+
|
42
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
43
|
+
result = {}
|
44
|
+
|
45
|
+
if self.prog_code is not None:
|
46
|
+
result.update(self.prog_code._build_odxlinks())
|
47
|
+
|
48
|
+
return result
|
49
|
+
|
50
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
51
|
+
if self.prog_code is not None:
|
52
|
+
self.prog_code._resolve_odxlinks(odxlinks)
|
53
|
+
|
54
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
55
|
+
if self.prog_code is not None:
|
56
|
+
self.prog_code._resolve_snrefs(context)
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from enum import Enum
|
4
|
-
from typing import List, Optional
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
7
|
from ..exceptions import odxraise
|
8
|
-
from ..odxlink import OdxDocFragment
|
8
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
9
9
|
from ..odxtypes import AtomicOdxType, DataType
|
10
|
+
from ..snrefcontext import SnRefContext
|
10
11
|
from .compuinternaltophys import CompuInternalToPhys
|
11
12
|
from .compuphystointernal import CompuPhysToInternal
|
12
13
|
|
@@ -77,6 +78,31 @@ class CompuMethod:
|
|
77
78
|
physical_type=physical_type,
|
78
79
|
internal_type=internal_type)
|
79
80
|
|
81
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
82
|
+
result = {}
|
83
|
+
|
84
|
+
if self.compu_internal_to_phys is not None:
|
85
|
+
result.update(self.compu_internal_to_phys._build_odxlinks())
|
86
|
+
|
87
|
+
if self.compu_phys_to_internal is not None:
|
88
|
+
result.update(self.compu_phys_to_internal._build_odxlinks())
|
89
|
+
|
90
|
+
return result
|
91
|
+
|
92
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
93
|
+
if self.compu_internal_to_phys is not None:
|
94
|
+
self.compu_internal_to_phys._resolve_odxlinks(odxlinks)
|
95
|
+
|
96
|
+
if self.compu_phys_to_internal is not None:
|
97
|
+
self.compu_phys_to_internal._resolve_odxlinks(odxlinks)
|
98
|
+
|
99
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
100
|
+
if self.compu_internal_to_phys is not None:
|
101
|
+
self.compu_internal_to_phys._resolve_snrefs(context)
|
102
|
+
|
103
|
+
if self.compu_phys_to_internal is not None:
|
104
|
+
self.compu_phys_to_internal._resolve_snrefs(context)
|
105
|
+
|
80
106
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
81
107
|
raise NotImplementedError()
|
82
108
|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
-
from ..odxlink import OdxDocFragment
|
6
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
7
7
|
from ..odxtypes import DataType
|
8
8
|
from ..progcode import ProgCode
|
9
|
+
from ..snrefcontext import SnRefContext
|
9
10
|
from .compudefaultvalue import CompuDefaultValue
|
10
11
|
from .compuscale import CompuScale
|
11
12
|
|
@@ -37,3 +38,19 @@ class CompuPhysToInternal:
|
|
37
38
|
|
38
39
|
return CompuPhysToInternal(
|
39
40
|
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
|
41
|
+
|
42
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
43
|
+
result = {}
|
44
|
+
|
45
|
+
if self.prog_code is not None:
|
46
|
+
result.update(self.prog_code._build_odxlinks())
|
47
|
+
|
48
|
+
return result
|
49
|
+
|
50
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
51
|
+
if self.prog_code is not None:
|
52
|
+
self.prog_code._resolve_odxlinks(odxlinks)
|
53
|
+
|
54
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
55
|
+
if self.prog_code is not None:
|
56
|
+
self.prog_code._resolve_snrefs(context)
|
@@ -5,10 +5,13 @@ from xml.etree import ElementTree
|
|
5
5
|
from ..exceptions import odxraise, odxrequire
|
6
6
|
from ..odxlink import OdxDocFragment
|
7
7
|
from ..odxtypes import DataType
|
8
|
+
from .compucodecompumethod import CompuCodeCompuMethod
|
8
9
|
from .compumethod import CompuMethod
|
9
10
|
from .identicalcompumethod import IdenticalCompuMethod
|
10
11
|
from .linearcompumethod import LinearCompuMethod
|
12
|
+
from .ratfunccompumethod import RatFuncCompuMethod
|
11
13
|
from .scalelinearcompumethod import ScaleLinearCompuMethod
|
14
|
+
from .scaleratfunccompumethod import ScaleRatFuncCompuMethod
|
12
15
|
from .tabintpcompumethod import TabIntpCompuMethod
|
13
16
|
from .texttablecompumethod import TexttableCompuMethod
|
14
17
|
|
@@ -27,9 +30,18 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
|
|
27
30
|
elif compu_category == "SCALE-LINEAR":
|
28
31
|
return ScaleLinearCompuMethod.compu_method_from_et(
|
29
32
|
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
33
|
+
elif compu_category == "RAT-FUNC":
|
34
|
+
return RatFuncCompuMethod.compu_method_from_et(
|
35
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
36
|
+
elif compu_category == "SCALE-RAT-FUNC":
|
37
|
+
return ScaleRatFuncCompuMethod.compu_method_from_et(
|
38
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
30
39
|
elif compu_category == "TEXTTABLE":
|
31
40
|
return TexttableCompuMethod.compu_method_from_et(
|
32
41
|
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
42
|
+
elif compu_category == "COMPUCODE":
|
43
|
+
return CompuCodeCompuMethod.compu_method_from_et(
|
44
|
+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
33
45
|
elif compu_category == "TAB-INTP":
|
34
46
|
return TabIntpCompuMethod.compu_method_from_et(
|
35
47
|
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
|
@@ -75,13 +75,13 @@ class LinearCompuMethod(CompuMethod):
|
|
75
75
|
|
76
76
|
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
|
77
77
|
if not self._segment.internal_applies(internal_value):
|
78
|
-
odxraise(
|
78
|
+
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)
|
79
79
|
|
80
80
|
return self._segment.convert_internal_to_physical(internal_value)
|
81
81
|
|
82
82
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
83
83
|
if not self._segment.physical_applies(physical_value):
|
84
|
-
odxraise(
|
84
|
+
odxraise(f"Cannot decode physical value {physical_value!r}", EncodeError)
|
85
85
|
|
86
86
|
return self._segment.convert_physical_to_internal(physical_value)
|
87
87
|
|
@@ -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
|
@@ -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)
|
odxtools/dataobjectproperty.py
CHANGED
@@ -84,6 +84,7 @@ class DataObjectProperty(DopBase):
|
|
84
84
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
85
85
|
result = super()._build_odxlinks()
|
86
86
|
result.update(self.diag_coded_type._build_odxlinks())
|
87
|
+
result.update(self.compu_method._build_odxlinks())
|
87
88
|
return result
|
88
89
|
|
89
90
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
@@ -91,6 +92,7 @@ class DataObjectProperty(DopBase):
|
|
91
92
|
super()._resolve_odxlinks(odxlinks)
|
92
93
|
|
93
94
|
self.diag_coded_type._resolve_odxlinks(odxlinks)
|
95
|
+
self.compu_method._resolve_odxlinks(odxlinks)
|
94
96
|
|
95
97
|
self._unit: Optional[Unit] = None
|
96
98
|
if self.unit_ref:
|
@@ -100,6 +102,7 @@ class DataObjectProperty(DopBase):
|
|
100
102
|
super()._resolve_snrefs(context)
|
101
103
|
|
102
104
|
self.diag_coded_type._resolve_snrefs(context)
|
105
|
+
self.compu_method._resolve_snrefs(context)
|
103
106
|
|
104
107
|
@property
|
105
108
|
def unit(self) -> Optional[Unit]:
|
@@ -33,7 +33,13 @@ class BaseVariantRaw(HierarchyElementRaw):
|
|
33
33
|
@staticmethod
|
34
34
|
def from_et(et_element: ElementTree.Element,
|
35
35
|
doc_frags: List[OdxDocFragment]) -> "BaseVariantRaw":
|
36
|
-
|
36
|
+
# objects contained by diagnostic layers exibit an additional
|
37
|
+
# document fragment for the diag layer, so we use the document
|
38
|
+
# fragments of the odx id of the diag layer for IDs of
|
39
|
+
# contained objects.
|
40
|
+
her = HierarchyElementRaw.from_et(et_element, doc_frags)
|
41
|
+
kwargs = dataclass_fields_asdict(her)
|
42
|
+
doc_frags = her.odx_id.doc_fragments
|
37
43
|
|
38
44
|
diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
|
39
45
|
if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
|
odxtools/diaglayers/diaglayer.py
CHANGED
@@ -13,6 +13,7 @@ from ..diagcomm import DiagComm
|
|
13
13
|
from ..diagdatadictionaryspec import DiagDataDictionarySpec
|
14
14
|
from ..diagservice import DiagService
|
15
15
|
from ..exceptions import DecodeError, odxassert, odxraise
|
16
|
+
from ..library import Library
|
16
17
|
from ..message import Message
|
17
18
|
from ..nameditemlist import NamedItemList, TNamed
|
18
19
|
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
@@ -259,6 +260,10 @@ class DiagLayer:
|
|
259
260
|
def import_refs(self) -> List[OdxLinkRef]:
|
260
261
|
return self.diag_layer_raw.import_refs
|
261
262
|
|
263
|
+
@property
|
264
|
+
def libraries(self) -> List[Library]:
|
265
|
+
return self.diag_layer_raw.libraries
|
266
|
+
|
262
267
|
@property
|
263
268
|
def sdgs(self) -> List[SpecialDataGroup]:
|
264
269
|
return self.diag_layer_raw.sdgs
|