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.
Files changed (43) hide show
  1. odxtools/audience.py +7 -8
  2. odxtools/compumethods/compucodecompumethod.py +63 -0
  3. odxtools/compumethods/compuinternaltophys.py +19 -2
  4. odxtools/compumethods/compumethod.py +28 -2
  5. odxtools/compumethods/compuphystointernal.py +19 -2
  6. odxtools/compumethods/createanycompumethod.py +12 -0
  7. odxtools/compumethods/linearcompumethod.py +2 -2
  8. odxtools/compumethods/ratfunccompumethod.py +106 -0
  9. odxtools/compumethods/ratfuncsegment.py +87 -0
  10. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  11. odxtools/dataobjectproperty.py +3 -0
  12. odxtools/diaglayers/basevariantraw.py +7 -1
  13. odxtools/diaglayers/diaglayer.py +5 -0
  14. odxtools/diaglayers/diaglayerraw.py +13 -1
  15. odxtools/diaglayers/ecushareddataraw.py +7 -1
  16. odxtools/diaglayers/ecuvariantraw.py +7 -1
  17. odxtools/diaglayers/functionalgroupraw.py +7 -1
  18. odxtools/diaglayers/hierarchyelementraw.py +7 -1
  19. odxtools/diaglayers/protocolraw.py +7 -1
  20. odxtools/diagservice.py +3 -1
  21. odxtools/dtcdop.py +6 -0
  22. odxtools/environmentdatadescription.py +11 -6
  23. odxtools/library.py +66 -0
  24. odxtools/minmaxlengthtype.py +1 -1
  25. odxtools/multiplexer.py +4 -4
  26. odxtools/multiplexercase.py +1 -1
  27. odxtools/odxtypes.py +14 -0
  28. odxtools/parameterinfo.py +132 -76
  29. odxtools/parameters/systemparameter.py +51 -8
  30. odxtools/progcode.py +8 -4
  31. odxtools/standardlengthtype.py +4 -1
  32. odxtools/templates/macros/printCompuMethod.xml.jinja2 +3 -2
  33. odxtools/templates/macros/printDiagLayer.xml.jinja2 +8 -0
  34. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  35. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -23
  36. odxtools/unitspec.py +10 -9
  37. odxtools/version.py +2 -2
  38. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/METADATA +1 -1
  39. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/RECORD +43 -37
  40. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/WHEEL +1 -1
  41. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/LICENSE +0 -0
  42. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/entry_points.txt +0 -0
  43. {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) -> List[AdditionalAudience]:
49
+ def enabled_audiences(self) -> NamedItemList[AdditionalAudience]:
49
50
  return self._enabled_audiences
50
51
 
51
52
  @property
52
- def disabled_audiences(self) -> List[AdditionalAudience]:
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
- self._disabled_audiences = [
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(r"Cannot decode internal value {internal_value}", DecodeError)
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(r"Cannot decode physical value {physical_value}", EncodeError)
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)
@@ -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
- kwargs = dataclass_fields_asdict(HierarchyElementRaw.from_et(et_element, doc_frags))
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:
@@ -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