odxtools 6.7.0__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.
Files changed (213) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,145 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import List, Union, cast
4
+ from xml.etree import ElementTree
4
5
 
5
- from ..exceptions import odxassert
6
- from ..odxtypes import AtomicOdxType
7
- from .compumethod import CompuMethod, CompuMethodCategory
8
- from .linearcompumethod import LinearCompuMethod
6
+ from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
7
+ from ..odxlink import OdxDocFragment
8
+ from ..odxtypes import AtomicOdxType, DataType
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuCategory, CompuMethod
11
+ from .limit import IntervalType
12
+ from .linearsegment import LinearSegment
9
13
 
10
14
 
11
15
  @dataclass
12
16
  class ScaleLinearCompuMethod(CompuMethod):
13
- linear_methods: List[LinearCompuMethod]
17
+ """A piecewise linear compu method which may feature discontinuities.
18
+
19
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.4.
20
+ """
14
21
 
15
22
  @property
16
- def category(self) -> CompuMethodCategory:
17
- return "SCALE-LINEAR"
18
-
19
- def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
20
- odxassert(
21
- self.is_valid_physical_value(physical_value),
22
- f"cannot convert the invalid physical value {physical_value!r} "
23
- f"of type {type(physical_value)}")
24
- lin_method = next(
25
- scale for scale in self.linear_methods if scale.is_valid_physical_value(physical_value))
26
- return lin_method.convert_physical_to_internal(physical_value)
27
-
28
- def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
29
- lin_method = next(
30
- scale for scale in self.linear_methods if scale.is_valid_internal_value(internal_value))
31
- return lin_method.convert_internal_to_physical(internal_value)
23
+ def segments(self) -> List[LinearSegment]:
24
+ return self._segments
25
+
26
+ @staticmethod
27
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
28
+ internal_type: DataType,
29
+ physical_type: DataType) -> "ScaleLinearCompuMethod":
30
+ cm = CompuMethod.compu_method_from_et(
31
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
32
+ kwargs = dataclass_fields_asdict(cm)
33
+
34
+ return ScaleLinearCompuMethod(**kwargs)
35
+
36
+ def __post_init__(self) -> None:
37
+ self._segments: List[LinearSegment] = []
38
+
39
+ odxassert(self.category == CompuCategory.SCALE_LINEAR,
40
+ "ScaleLinearCompuMethod must exibit SCALE-LINEAR category")
41
+
42
+ odxassert(self.physical_type in [
43
+ DataType.A_FLOAT32,
44
+ DataType.A_FLOAT64,
45
+ DataType.A_INT32,
46
+ DataType.A_UINT32,
47
+ ])
48
+ odxassert(self.internal_type in [
49
+ DataType.A_FLOAT32,
50
+ DataType.A_FLOAT64,
51
+ DataType.A_INT32,
52
+ DataType.A_UINT32,
53
+ ])
54
+
55
+ if self.compu_internal_to_phys is None:
56
+ odxraise("SCALE-LINEAR compu methods require COMPU-INTERNAL-TO-PHYS")
57
+ return
58
+
59
+ compu_scales = self.compu_internal_to_phys.compu_scales
60
+
61
+ for scale in compu_scales:
62
+ self._segments.append(
63
+ LinearSegment.from_compu_scale(
64
+ scale, internal_type=self.internal_type, physical_type=self.physical_type))
65
+
66
+ # find out if the transfer function is invertible (i.e. if it
67
+ # can be encoded by normal means). section 7.3.6.6.4 of the
68
+ # ODX specification states that the condition for
69
+ # invertibility is that adjacent COMPU-SCALES exhibit the same
70
+ # values on their common boundaries and that the slope in all
71
+ # intervals exhibit the same sign (or are 0). For segments
72
+ # with a slope of zero, COMPU-INVERSE-VALUE shall be used.
73
+ self._is_invertible = True
74
+ ref_factor = self._segments[0].factor
75
+ for i in range(0, len(self._segments) - 1):
76
+ s0 = self.segments[i]
77
+ s1 = self.segments[i + 1]
78
+
79
+ if ref_factor * s1.factor < 0:
80
+ self._is_invertible = False
81
+ break
82
+ if s1.factor != 0:
83
+ ref_factor = s1.factor
84
+
85
+ # both interval boundaries must not be infinite
86
+ if s0.internal_upper_limit is None or \
87
+ s1.internal_lower_limit is None:
88
+ self._is_invertible = False
89
+ break
90
+ elif s0.internal_upper_limit.value is None or \
91
+ s1.internal_lower_limit.value is None or \
92
+ s0.internal_upper_limit.interval_type == IntervalType.INFINITE or \
93
+ s1.internal_lower_limit.interval_type == IntervalType.INFINITE:
94
+ self._is_invertible = False
95
+ break
96
+
97
+ # the intervals must use the same reference point
98
+ if (x := s0.internal_upper_limit.value) != s1.internal_lower_limit.value:
99
+ self._is_invertible = False
100
+ break
101
+
102
+ if not isinstance(x, (int, float)):
103
+ odxraise("Linear segments must use int or float for all quantities")
104
+
105
+ # the respective function value at the interval's
106
+ # reference point must be identical
107
+ y0 = s0.convert_internal_to_physical(x)
108
+ y1 = s1.convert_internal_to_physical(x)
109
+ if abs(y0 - y1) < 1e-10:
110
+ self._is_invertible = False
111
+ break
112
+
113
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[float, int]:
114
+ if not self._is_invertible:
115
+ odxraise(
116
+ f"Trying to encode value {physical_value!r} using a non-invertible "
117
+ f"SCALE-LINEAR transfer function", EncodeError)
118
+
119
+ applicable_segments = [
120
+ seg for seg in self._segments if seg.physical_applies(physical_value)
121
+ ]
122
+ if not applicable_segments:
123
+ odxraise(r"No applicable segment for value {physical_value} found", EncodeError)
124
+ return cast(int, None)
125
+
126
+ seg = applicable_segments[0]
127
+
128
+ return seg.convert_physical_to_internal(physical_value)
129
+
130
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> Union[float, int]:
131
+ applicable_segments = [
132
+ seg for seg in self._segments if seg.internal_applies(internal_value)
133
+ ]
134
+ if not applicable_segments:
135
+ odxraise(r"No applicable segment for value {internal_value} found", DecodeError)
136
+ return cast(int, None)
137
+
138
+ seg = applicable_segments[0]
139
+ return seg.convert_internal_to_physical(internal_value)
32
140
 
33
141
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
34
- return any(
35
- True for scale in self.linear_methods if scale.is_valid_physical_value(physical_value))
142
+ return any(True for seg in self._segments if seg.physical_applies(physical_value))
36
143
 
37
144
  def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
38
- return any(
39
- True for scale in self.linear_methods if scale.is_valid_internal_value(internal_value))
145
+ return any(True for seg in self._segments if seg.internal_applies(internal_value))
@@ -0,0 +1,113 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional, cast
4
+ from xml.etree import ElementTree
5
+
6
+ from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
7
+ from ..odxlink import OdxDocFragment
8
+ from ..odxtypes import AtomicOdxType, DataType
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuCategory, CompuMethod
11
+ from .ratfuncsegment import RatFuncSegment
12
+
13
+
14
+ @dataclass
15
+ class ScaleRatFuncCompuMethod(CompuMethod):
16
+ """A compu method using a piecewise rational function
17
+
18
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.5.
19
+ """
20
+
21
+ @property
22
+ def int_to_phys_segments(self) -> List[RatFuncSegment]:
23
+ return self._int_to_phys_segments
24
+
25
+ @property
26
+ def phys_to_int_segments(self) -> Optional[List[RatFuncSegment]]:
27
+ return self._phys_to_int_segments
28
+
29
+ @staticmethod
30
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
31
+ internal_type: DataType,
32
+ physical_type: DataType) -> "ScaleRatFuncCompuMethod":
33
+ cm = CompuMethod.compu_method_from_et(
34
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
35
+ kwargs = dataclass_fields_asdict(cm)
36
+
37
+ return ScaleRatFuncCompuMethod(**kwargs)
38
+
39
+ def __post_init__(self) -> None:
40
+ odxassert(self.category == CompuCategory.SCALE_RAT_FUNC,
41
+ "ScaleRatFuncCompuMethod must exhibit SCALE-RAT-FUNC category")
42
+
43
+ odxassert(self.physical_type in [
44
+ DataType.A_FLOAT32,
45
+ DataType.A_FLOAT64,
46
+ DataType.A_INT32,
47
+ DataType.A_UINT32,
48
+ ])
49
+ odxassert(self.internal_type in [
50
+ DataType.A_FLOAT32,
51
+ DataType.A_FLOAT64,
52
+ DataType.A_INT32,
53
+ DataType.A_UINT32,
54
+ ])
55
+
56
+ if self.compu_internal_to_phys is None:
57
+ odxraise("RAT-FUNC compu methods require COMPU-INTERNAL-TO-PHYS")
58
+ return
59
+
60
+ int_to_phys_scales = self.compu_internal_to_phys.compu_scales
61
+ if len(int_to_phys_scales) < 1:
62
+ odxraise("RAT-FUNC compu methods expect at least one compu scale within "
63
+ "COMPU-INTERNAL-TO-PHYS")
64
+ return
65
+
66
+ self._int_to_phys_segments = [
67
+ RatFuncSegment.from_compu_scale(scale, value_type=self.physical_type)
68
+ for scale in int_to_phys_scales
69
+ ]
70
+
71
+ self._phys_to_int_segments = None
72
+ if self.compu_phys_to_internal is not None:
73
+ phys_to_int_scales = self.compu_phys_to_internal.compu_scales
74
+ if len(phys_to_int_scales) < 1:
75
+ odxraise("SCALE-RAT-FUNC compu methods expect at least one compu scale within "
76
+ "COMPU-PHYS-TO-INTERNAL")
77
+ return
78
+
79
+ self._phys_to_int_segments = [
80
+ RatFuncSegment.from_compu_scale(scale, value_type=self.internal_type)
81
+ for scale in phys_to_int_scales
82
+ ]
83
+
84
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
85
+ for seg in self._int_to_phys_segments:
86
+ if seg.applies(internal_value):
87
+ return seg.convert(internal_value)
88
+
89
+ odxraise(f"Internal value {internal_value!r} be decoded using this compumethod",
90
+ DecodeError)
91
+ return cast(AtomicOdxType, None)
92
+
93
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
94
+ if self._phys_to_int_segments is None:
95
+ odxraise(f"Physical values cannot be encoded using this compumethod", EncodeError)
96
+ return cast(AtomicOdxType, None)
97
+
98
+ for seg in self._phys_to_int_segments:
99
+ if seg.applies(physical_value):
100
+ return seg.convert(physical_value)
101
+
102
+ odxraise(f"Physical values {physical_value!r} be decoded using this compumethod",
103
+ EncodeError)
104
+ return cast(AtomicOdxType, None)
105
+
106
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
107
+ return any(seg.applies(internal_value) for seg in self._int_to_phys_segments)
108
+
109
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
110
+ if self._phys_to_int_segments is None:
111
+ return False
112
+
113
+ return any(seg.applies(physical_value) for seg in self._phys_to_int_segments)
@@ -1,107 +1,114 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Tuple, Union
3
+ from typing import List, Union
4
+ from xml.etree import ElementTree
4
5
 
5
- from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
6
+ from ..exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
7
+ from ..odxlink import OdxDocFragment
6
8
  from ..odxtypes import AtomicOdxType, DataType
7
- from .compumethod import CompuMethod, CompuMethodCategory
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuCategory, CompuMethod
8
11
  from .limit import IntervalType, Limit
9
12
 
10
13
 
11
14
  @dataclass
12
15
  class TabIntpCompuMethod(CompuMethod):
13
- """
14
- A compu method of type Tab Interpolated is used for linear interpolation.
15
-
16
- A `TabIntpCompuMethod` is defined by a set of points. Each point is an (internal, physical) value pair.
17
- When converting from internal to physical or vice-versa, the result is linearly interpolated.
18
-
19
- The function defined by a `TabIntpCompuMethod` is similar to the one of a `ScaleLinearCompuMethod` with the following differences:
20
-
21
- * `TabIntpCompuMethod`s are always continuous whereas `ScaleLinearCompuMethod` might have jumps
22
- * `TabIntpCompuMethod`s are always invertible: Even if the linear interpolation is not monotonic, the first matching interval is taken.
23
-
24
- Refer to ASAM MCD-2 D (ODX) Specification, section 7.3.6.6.8 for details.
25
-
26
- Examples
27
- --------
28
-
29
- Create a TabIntpCompuMethod defined by the points (0, -1), (10, 1), (30, 2)::
30
-
31
- method = TabIntpCompuMethod(
32
- internal_type=DataType.A_UINT32,
33
- physical_type=DataType.A_UINT32,
34
- internal_points=[0, 10, 30],
35
- physical_points=[-1, 1, 2]
36
- )
37
-
38
- Note that the points are given as two lists. The equivalent odx definition is::
39
-
40
- <COMPU-METHOD>
41
- <CATEGORY>TAB-INTP</CATEGORY>
42
- <COMPU-INTERNAL-TO-PHYS>
43
- <COMPU-SCALES>
44
- <COMPU-SCALE>
45
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">0</LOWER-LIMIT>
46
- <COMPU-CONST>
47
- <V>-1</V>
48
- </COMPU-CONST>
49
- </COMPU-SCALE>
50
- <COMPU-SCALE>
51
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">10</LOWER-LIMIT>
52
- <COMPU-CONST>
53
- <V>1</V>
54
- </COMPU-CONST>
55
- </COMPU-SCALE>
56
- <COMPU-SCALE>
57
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">30</LOWER-LIMIT>
58
- <COMPU-CONST>
59
- <V>2</V>
60
- </COMPU-CONST>
61
- </COMPU-SCALE>
62
- </COMPU-SCALES>
63
- </COMPU-INTERNAL-TO-PHYS>
64
- </COMPU-METHOD>
16
+ """A table-based interpolated compu method provides a continuous
17
+ transfer function based on piecewise linear interpolation.
18
+
19
+ A `TabIntpCompuMethod` is defined by a set of points. Each point
20
+ is an (internal, physical) value pair. When converting from
21
+ internal to physical or vice-versa, the result is linearly
22
+ interpolated.
23
+
24
+ The function defined by a `TabIntpCompuMethod` is similar to the
25
+ one of a `ScaleLinearCompuMethod` with the following differences:
26
+
27
+ * `TabIntpCompuMethod`s are always continuous whereas
28
+ `ScaleLinearCompuMethod` might exhibit gaps
29
+ * `TabIntpCompuMethod`s are always invertible: Even if the linear
30
+ interpolation is not monotonic, the first matching interval is
31
+ used.
32
+
33
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.8.
65
34
 
66
35
  """
67
36
 
68
- internal_points: List[Union[float, int]]
69
- physical_points: List[Union[float, int]]
37
+ @property
38
+ def internal_points(self) -> List[Union[float, int]]:
39
+ return self._internal_points
40
+
41
+ @property
42
+ def physical_points(self) -> List[Union[float, int]]:
43
+ return self._physical_points
44
+
45
+ @property
46
+ def internal_lower_limit(self) -> Limit:
47
+ return self._internal_lower_limit
48
+
49
+ @property
50
+ def internal_upper_limit(self) -> Limit:
51
+ return self._internal_upper_limit
52
+
53
+ @property
54
+ def physical_lower_limit(self) -> Limit:
55
+ return self._physical_lower_limit
56
+
57
+ @property
58
+ def physical_upper_limit(self) -> Limit:
59
+ return self._physical_upper_limit
60
+
61
+ @staticmethod
62
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
63
+ internal_type: DataType,
64
+ physical_type: DataType) -> "TabIntpCompuMethod":
65
+ cm = CompuMethod.compu_method_from_et(
66
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
67
+ kwargs = dataclass_fields_asdict(cm)
68
+
69
+ return TabIntpCompuMethod(**kwargs)
70
70
 
71
71
  def __post_init__(self) -> None:
72
+ odxassert(self.category == CompuCategory.TAB_INTP,
73
+ "TabIntpCompuMethod must exibit TAB-INTP category")
74
+
75
+ self._internal_points: List[Union[int, float]] = []
76
+ self._physical_points: List[Union[int, float]] = []
77
+ for scale in odxrequire(self.compu_internal_to_phys).compu_scales:
78
+ internal_point = odxrequire(scale.lower_limit).value
79
+ physical_point = odxrequire(scale.compu_const).value
80
+
81
+ if not isinstance(internal_point, (float, int)):
82
+ odxraise("The type of values of tab-intp compumethods must "
83
+ "either int or float")
84
+ if not isinstance(physical_point, (float, int)):
85
+ odxraise("The type of values of tab-intp compumethods must "
86
+ "either int or float")
87
+
88
+ self._internal_points.append(internal_point)
89
+ self._physical_points.append(physical_point)
90
+
72
91
  self._physical_lower_limit = Limit(
73
- value_raw=str(min(self.physical_points)),
92
+ value_raw=str(min(self._physical_points)),
74
93
  value_type=self.physical_type,
75
94
  interval_type=IntervalType.CLOSED)
76
95
  self._physical_upper_limit = Limit(
77
- value_raw=str(max(self.physical_points)),
96
+ value_raw=str(max(self._physical_points)),
78
97
  value_type=self.physical_type,
79
98
  interval_type=IntervalType.CLOSED)
80
99
 
81
100
  self._internal_lower_limit = Limit(
82
- value_raw=str(min(self.internal_points)),
101
+ value_raw=str(min(self._internal_points)),
83
102
  value_type=self.internal_type,
84
103
  interval_type=IntervalType.CLOSED)
85
104
  self._internal_upper_limit = Limit(
86
- value_raw=str(max(self.internal_points)),
105
+ value_raw=str(max(self._internal_points)),
87
106
  value_type=self.internal_type,
88
107
  interval_type=IntervalType.CLOSED)
89
108
 
90
- self._assert_validity()
91
-
92
- @property
93
- def category(self) -> CompuMethodCategory:
94
- return "TAB-INTP"
95
-
96
- @property
97
- def physical_lower_limit(self) -> Limit:
98
- return self._physical_lower_limit
109
+ self.__assert_validity()
99
110
 
100
- @property
101
- def physical_upper_limit(self) -> Limit:
102
- return self._physical_upper_limit
103
-
104
- def _assert_validity(self) -> None:
111
+ def __assert_validity(self) -> None:
105
112
  odxassert(len(self.internal_points) == len(self.physical_points))
106
113
 
107
114
  odxassert(
@@ -110,7 +117,7 @@ class TabIntpCompuMethod(CompuMethod):
110
117
  DataType.A_UINT32,
111
118
  DataType.A_FLOAT32,
112
119
  DataType.A_FLOAT64,
113
- ], "Internal data type of tab-intp compumethod must be one of"
120
+ ], "Internal data type of TAB-INTP compumethod must be one of"
114
121
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
115
122
  odxassert(
116
123
  self.physical_type in [
@@ -118,51 +125,64 @@ class TabIntpCompuMethod(CompuMethod):
118
125
  DataType.A_UINT32,
119
126
  DataType.A_FLOAT32,
120
127
  DataType.A_FLOAT64,
121
- ], "Physical data type of tab-intp compumethod must be one of"
128
+ ], "Physical data type of TAB-INTP compumethod must be one of"
122
129
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
123
130
 
124
- def _piecewise_linear_interpolate(self, x: Union[int, float],
125
- points: List[Tuple[Union[int, float],
126
- Union[int, float]]]) -> Union[float, None]:
127
- for ((x0, y0), (x1, y1)) in zip(points[:-1], points[1:]):
128
- if x0 <= x and x <= x1:
131
+ def __piecewise_linear_interpolate(self, x: Union[int, float],
132
+ range_samples: List[Union[int, float]],
133
+ domain_samples: List[Union[int,
134
+ float]]) -> Union[float, None]:
135
+ for i in range(0, len(range_samples) - 1):
136
+ if (x0 := range_samples[i]) <= x and x <= (x1 := range_samples[i + 1]):
137
+ y0 = domain_samples[i]
138
+ y1 = domain_samples[i + 1]
129
139
  return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
130
140
 
131
141
  return None
132
142
 
133
143
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
134
144
  if not isinstance(physical_value, (int, float)):
135
- raise EncodeError("The type of values of tab-intp compumethods must "
136
- "either int or float")
145
+ odxraise("The type of values of tab-intp compumethods must "
146
+ "either int or float", EncodeError)
147
+ return None
137
148
 
138
- reference_points = list(zip(self.physical_points, self.internal_points))
139
149
  odxassert(
140
150
  isinstance(physical_value, (int, float)),
141
- "Only integers and floats can be piecewise linearly interpolated")
142
- result = self._piecewise_linear_interpolate(physical_value, reference_points)
151
+ "Only integers and floats can be piecewise linearly interpolated", EncodeError)
152
+ result = self.__piecewise_linear_interpolate(physical_value, self._physical_points,
153
+ self._internal_points)
143
154
 
144
155
  if result is None:
145
- raise EncodeError(f"Internal value {physical_value!r} must be inside the range"
146
- f" [{min(self.physical_points)}, {max(self.physical_points)}]")
156
+ odxraise(
157
+ f"Internal value {physical_value!r} must be inside the range"
158
+ f" [{min(self.physical_points)}, {max(self.physical_points)}]", EncodeError)
159
+
147
160
  res = self.internal_type.make_from(result)
148
- if not isinstance(res, (int, float)):
149
- odxraise()
161
+
150
162
  return res
151
163
 
152
164
  def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
153
165
  if not isinstance(internal_value, (int, float)):
154
- raise EncodeError("The internal type of values of tab-intp compumethods must "
155
- "either int or float")
166
+ odxraise(
167
+ "The internal type of values of tab-intp compumethods must "
168
+ "either int or float", EncodeError)
169
+ return None
170
+
171
+ odxassert(
172
+ isinstance(internal_value, (int, float)),
173
+ "Only integers and floats can be piecewise linearly interpolated", DecodeError)
156
174
 
157
- reference_points = list(zip(self.internal_points, self.physical_points))
158
- result = self._piecewise_linear_interpolate(internal_value, reference_points)
175
+ result = self.__piecewise_linear_interpolate(internal_value, self._internal_points,
176
+ self._physical_points)
159
177
 
160
178
  if result is None:
161
- raise DecodeError(f"Internal value {internal_value!r} must be inside the range"
162
- f" [{min(self.internal_points)}, {max(self.internal_points)}]")
179
+ odxraise(
180
+ f"Internal value {internal_value!r} must be inside the range"
181
+ f" [{min(self.internal_points)}, {max(self.internal_points)}]", DecodeError)
182
+ return None
183
+
163
184
  res = self.physical_type.make_from(result)
164
- if not isinstance(res, (int, float)):
165
- odxraise()
185
+
166
186
  return res
167
187
 
168
188
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool: