odxtools 6.6.1__py3-none-any.whl → 9.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. odxtools/__init__.py +7 -5
  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 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.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,82 +1,54 @@
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
70
40
 
71
- def __post_init__(self) -> None:
72
- self._physical_lower_limit = Limit(min(self.physical_points), IntervalType.CLOSED)
73
- self._physical_upper_limit = Limit(max(self.physical_points), IntervalType.CLOSED)
41
+ @property
42
+ def physical_points(self) -> List[Union[float, int]]:
43
+ return self._physical_points
74
44
 
75
- self._assert_validity()
45
+ @property
46
+ def internal_lower_limit(self) -> Limit:
47
+ return self._internal_lower_limit
76
48
 
77
49
  @property
78
- def category(self) -> CompuMethodCategory:
79
- return "TAB-INTP"
50
+ def internal_upper_limit(self) -> Limit:
51
+ return self._internal_upper_limit
80
52
 
81
53
  @property
82
54
  def physical_lower_limit(self) -> Limit:
@@ -86,7 +58,57 @@ class TabIntpCompuMethod(CompuMethod):
86
58
  def physical_upper_limit(self) -> Limit:
87
59
  return self._physical_upper_limit
88
60
 
89
- def _assert_validity(self) -> None:
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
+
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
+
91
+ self._physical_lower_limit = Limit(
92
+ value_raw=str(min(self._physical_points)),
93
+ value_type=self.physical_type,
94
+ interval_type=IntervalType.CLOSED)
95
+ self._physical_upper_limit = Limit(
96
+ value_raw=str(max(self._physical_points)),
97
+ value_type=self.physical_type,
98
+ interval_type=IntervalType.CLOSED)
99
+
100
+ self._internal_lower_limit = Limit(
101
+ value_raw=str(min(self._internal_points)),
102
+ value_type=self.internal_type,
103
+ interval_type=IntervalType.CLOSED)
104
+ self._internal_upper_limit = Limit(
105
+ value_raw=str(max(self._internal_points)),
106
+ value_type=self.internal_type,
107
+ interval_type=IntervalType.CLOSED)
108
+
109
+ self.__assert_validity()
110
+
111
+ def __assert_validity(self) -> None:
90
112
  odxassert(len(self.internal_points) == len(self.physical_points))
91
113
 
92
114
  odxassert(
@@ -95,7 +117,7 @@ class TabIntpCompuMethod(CompuMethod):
95
117
  DataType.A_UINT32,
96
118
  DataType.A_FLOAT32,
97
119
  DataType.A_FLOAT64,
98
- ], "Internal data type of tab-intp compumethod must be one of"
120
+ ], "Internal data type of TAB-INTP compumethod must be one of"
99
121
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
100
122
  odxassert(
101
123
  self.physical_type in [
@@ -103,55 +125,64 @@ class TabIntpCompuMethod(CompuMethod):
103
125
  DataType.A_UINT32,
104
126
  DataType.A_FLOAT32,
105
127
  DataType.A_FLOAT64,
106
- ], "Physical data type of tab-intp compumethod must be one of"
128
+ ], "Physical data type of TAB-INTP compumethod must be one of"
107
129
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
108
130
 
109
- def _piecewise_linear_interpolate(self, x: Union[int, float],
110
- points: List[Tuple[Union[int, float],
111
- Union[int, float]]]) -> Union[float, None]:
112
- for ((x0, y0), (x1, y1)) in zip(points[:-1], points[1:]):
113
- 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]
114
139
  return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
115
140
 
116
141
  return None
117
142
 
118
143
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
119
144
  if not isinstance(physical_value, (int, float)):
120
- raise EncodeError("The type of values of tab-intp compumethods must "
121
- "either int or float")
145
+ odxraise("The type of values of tab-intp compumethods must "
146
+ "either int or float", EncodeError)
147
+ return None
122
148
 
123
- reference_points = list(zip(self.physical_points, self.internal_points))
124
149
  odxassert(
125
150
  isinstance(physical_value, (int, float)),
126
- "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)
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)
130
154
 
131
155
  if result is None:
132
- raise EncodeError(f"Internal value {physical_value!r} must be inside the range"
133
- 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
+
134
160
  res = self.internal_type.make_from(result)
135
- if not isinstance(res, (int, float)):
136
- odxraise()
161
+
137
162
  return res
138
163
 
139
164
  def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
140
165
  if not isinstance(internal_value, (int, float)):
141
- raise EncodeError("The internal type of values of tab-intp compumethods must "
142
- "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
143
170
 
144
- 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)
171
+ odxassert(
172
+ isinstance(internal_value, (int, float)),
173
+ "Only integers and floats can be piecewise linearly interpolated", DecodeError)
174
+
175
+ result = self.__piecewise_linear_interpolate(internal_value, self._internal_points,
176
+ self._physical_points)
148
177
 
149
178
  if result is None:
150
- raise DecodeError(f"Internal value {internal_value!r} must be inside the range"
151
- 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
+
152
184
  res = self.physical_type.make_from(result)
153
- if not isinstance(res, (int, float)):
154
- odxraise()
185
+
155
186
  return res
156
187
 
157
188
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool: