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,25 +1,36 @@
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
4
  from xml.etree import ElementTree
5
5
 
6
- from ..exceptions import odxrequire
6
+ from ..exceptions import odxassert, odxrequire
7
7
  from ..odxlink import OdxDocFragment
8
+ from ..odxtypes import DataType
8
9
 
9
10
 
10
11
  @dataclass
11
12
  class CompuRationalCoeffs:
12
- numerators: List[float]
13
- denominators: List[float]
13
+ value_type: DataType
14
+
15
+ numerators: List[Union[int, float]]
16
+ denominators: List[Union[int, float]]
14
17
 
15
18
  @staticmethod
16
- def from_et(et_element: ElementTree.Element,
17
- doc_frags: List[OdxDocFragment]) -> "CompuRationalCoeffs":
19
+ def coeffs_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
20
+ value_type: DataType) -> "CompuRationalCoeffs":
21
+ odxassert(
22
+ value_type
23
+ in (DataType.A_UINT32, DataType.A_INT32, DataType.A_FLOAT32, DataType.A_FLOAT64),
24
+ "Rational coefficients must be of numeric type.")
25
+
18
26
  numerators = [
19
- float(odxrequire(elem.text)) for elem in et_element.iterfind("COMPU-NUMERATOR/V")
27
+ cast(float, value_type.from_string(odxrequire(elem.text)))
28
+ for elem in et_element.iterfind("COMPU-NUMERATOR/V")
20
29
  ]
21
30
  denominators = [
22
- float(odxrequire(elem.text)) for elem in et_element.iterfind("COMPU-DENOMINATOR/V")
31
+ cast(float, value_type.from_string(odxrequire(elem.text)))
32
+ for elem in et_element.iterfind("COMPU-DENOMINATOR/V")
23
33
  ]
24
34
 
25
- return CompuRationalCoeffs(numerators=numerators, denominators=denominators)
35
+ return CompuRationalCoeffs(
36
+ value_type=value_type, numerators=numerators, denominators=denominators)
@@ -3,9 +3,11 @@ from dataclasses import dataclass
3
3
  from typing import List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from ..description import Description
6
7
  from ..odxlink import OdxDocFragment
7
8
  from ..odxtypes import AtomicOdxType, DataType
8
- from ..utils import create_description_from_et
9
+ from .compuconst import CompuConst
10
+ from .compuinversevalue import CompuInverseValue
9
11
  from .compurationalcoeffs import CompuRationalCoeffs
10
12
  from .limit import Limit
11
13
 
@@ -13,49 +15,51 @@ from .limit import Limit
13
15
  @dataclass
14
16
  class CompuScale:
15
17
  """A COMPU-SCALE represents one value range of a COMPU-METHOD.
16
-
17
- Example:
18
-
19
- For a TEXTTABLE compu method a compu scale within COMPU-INTERNAL-TO-PHYS
20
- can be defined with
21
- ```
22
- scale = CompuScale(
23
- short_label="example_label", # optional: provide a label
24
- description="<p>fancy description</p>", # optional: provide a description
25
- lower_limit=Limit(0), # required: lower limit
26
- upper_limit=Limit(3), # required: upper limit
27
- compu_inverse_value=2, # required if lower_limit != upper_limit
28
- compu_const="true", # required: physical value to be shown to the user
29
- )
30
- ```
31
-
32
- Almost all attributes are optional but there are compu-method-specific restrictions.
33
- E.g., lower_limit must always be defined unless the COMPU-METHOD is of CATEGORY LINEAR or RAT-FUNC.
34
- Either `compu_const` or `compu_rational_coeffs` must be defined but never both.
35
18
  """
36
19
 
37
20
  short_label: Optional[str]
38
- description: Optional[str]
21
+ description: Optional[Description]
39
22
  lower_limit: Optional[Limit]
40
23
  upper_limit: Optional[Limit]
41
- compu_inverse_value: Optional[AtomicOdxType]
42
- compu_const: Optional[AtomicOdxType]
24
+ compu_inverse_value: Optional[CompuInverseValue]
25
+ compu_const: Optional[CompuConst]
43
26
  compu_rational_coeffs: Optional[CompuRationalCoeffs]
44
27
 
28
+ # the following two attributes are not specified for COMPU-SCALE
29
+ # tags in the XML, but they are required to do anything useful
30
+ # with compu scales: The domain type is the input set of the
31
+ # function associated with the compu scale object, whilst the
32
+ # range type represents the output set. IOW, for scales contained
33
+ # by the internal-to-physical mapping function, the domain type is
34
+ # the internal and the range type is the physical type of the
35
+ # compu method. (Vice versa for scales specified by the
36
+ # physical-to-internal mapping function.)
37
+ domain_type: DataType
38
+ range_type: DataType
39
+
45
40
  @staticmethod
46
- def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
47
- internal_type: DataType, physical_type: DataType) -> "CompuScale":
41
+ def compuscale_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
42
+ domain_type: DataType, range_type: DataType) -> "CompuScale":
48
43
  short_label = et_element.findtext("SHORT-LABEL")
49
- description = create_description_from_et(et_element.find("DESC"))
50
- lower_limit = Limit.from_et(et_element.find("LOWER-LIMIT"), internal_type=internal_type)
51
- upper_limit = Limit.from_et(et_element.find("UPPER-LIMIT"), internal_type=internal_type)
44
+ description = Description.from_et(et_element.find("DESC"), doc_frags)
45
+
46
+ lower_limit = Limit.limit_from_et(
47
+ et_element.find("LOWER-LIMIT"), doc_frags, value_type=domain_type)
48
+ upper_limit = Limit.limit_from_et(
49
+ et_element.find("UPPER-LIMIT"), doc_frags, value_type=domain_type)
52
50
 
53
- compu_inverse_value = internal_type.create_from_et(et_element.find("COMPU-INVERSE-VALUE"))
54
- compu_const = physical_type.create_from_et(et_element.find("COMPU-CONST"))
51
+ compu_inverse_value = None
52
+ if (cive := et_element.find("COMPU-INVERSE-VALUE")) is not None:
53
+ compu_inverse_value = CompuInverseValue.compuvalue_from_et(cive, data_type=domain_type)
54
+
55
+ compu_const = None
56
+ if (cce := et_element.find("COMPU-CONST")) is not None:
57
+ compu_const = CompuConst.compuvalue_from_et(cce, data_type=range_type)
55
58
 
56
59
  compu_rational_coeffs: Optional[CompuRationalCoeffs] = None
57
60
  if (crc_elem := et_element.find("COMPU-RATIONAL-COEFFS")) is not None:
58
- compu_rational_coeffs = CompuRationalCoeffs.from_et(crc_elem, doc_frags)
61
+ compu_rational_coeffs = CompuRationalCoeffs.coeffs_from_et(
62
+ crc_elem, doc_frags, value_type=range_type)
59
63
 
60
64
  return CompuScale(
61
65
  short_label=short_label,
@@ -64,4 +68,35 @@ class CompuScale:
64
68
  upper_limit=upper_limit,
65
69
  compu_inverse_value=compu_inverse_value,
66
70
  compu_const=compu_const,
67
- compu_rational_coeffs=compu_rational_coeffs)
71
+ compu_rational_coeffs=compu_rational_coeffs,
72
+ domain_type=domain_type,
73
+ range_type=range_type)
74
+
75
+ def applies(self, internal_value: AtomicOdxType) -> bool:
76
+
77
+ if self.lower_limit is None and self.upper_limit is None:
78
+ # Everything is allowed: No limits have been specified
79
+ return True
80
+ elif self.upper_limit is None:
81
+ # no upper limit has been specified. the spec says that
82
+ # the value specified by the lower limit is the only one
83
+ # which is allowed (cf section 7.3.6.6.1)
84
+ assert self.lower_limit is not None
85
+
86
+ return internal_value == self.lower_limit.value
87
+ elif self.lower_limit is None:
88
+ # only the upper limit has been specified. the spec is
89
+ # ambiguous: it only says that if no upper limit is
90
+ # defined, the lower limit shall also be used as the upper
91
+ # limit and a closed interval type ought to be assumed,
92
+ # but it does not say what happens if the lower limit is
93
+ # not defined (which is allowed by the XSD). We thus
94
+ # assume that if only the upper limit is defined, is
95
+ # treated the same way as if only the lower limit is
96
+ # specified.
97
+ assert self.upper_limit is not None
98
+
99
+ return internal_value == self.upper_limit.value
100
+
101
+ return self.lower_limit.complies_to_lower(internal_value) and \
102
+ self.upper_limit.complies_to_upper(internal_value)
@@ -1,194 +1,53 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import warnings
3
- from typing import Any, Dict, List, Optional
2
+ from typing import List
4
3
  from xml.etree import ElementTree
5
4
 
6
- from ..exceptions import OdxWarning, odxassert, odxraise, odxrequire
7
- from ..globals import logger
5
+ from ..exceptions import odxraise, odxrequire
8
6
  from ..odxlink import OdxDocFragment
9
7
  from ..odxtypes import DataType
8
+ from .compucodecompumethod import CompuCodeCompuMethod
10
9
  from .compumethod import CompuMethod
11
- from .compuscale import CompuScale
12
10
  from .identicalcompumethod import IdenticalCompuMethod
13
- from .limit import IntervalType, Limit
14
11
  from .linearcompumethod import LinearCompuMethod
12
+ from .ratfunccompumethod import RatFuncCompuMethod
15
13
  from .scalelinearcompumethod import ScaleLinearCompuMethod
14
+ from .scaleratfunccompumethod import ScaleRatFuncCompuMethod
16
15
  from .tabintpcompumethod import TabIntpCompuMethod
17
16
  from .texttablecompumethod import TexttableCompuMethod
18
17
 
19
18
 
20
- def _parse_compu_scale_to_linear_compu_method(
21
- *,
22
- scale_element: ElementTree.Element,
23
- internal_type: DataType,
24
- physical_type: DataType,
25
- is_scale_linear: bool = False,
26
- **kwargs: Any,
27
- ) -> LinearCompuMethod:
28
- odxassert(physical_type in [
29
- DataType.A_FLOAT32,
30
- DataType.A_FLOAT64,
31
- DataType.A_INT32,
32
- DataType.A_UINT32,
33
- ])
34
- odxassert(internal_type in [
35
- DataType.A_FLOAT32,
36
- DataType.A_FLOAT64,
37
- DataType.A_INT32,
38
- DataType.A_UINT32,
39
- ])
40
-
41
- if physical_type.as_python_type() == float:
42
- computation_python_type = physical_type.from_string
43
- else:
44
- computation_python_type = internal_type.from_string
45
-
46
- kwargs = kwargs.copy()
47
- kwargs["internal_type"] = internal_type
48
- kwargs["physical_type"] = physical_type
49
-
50
- coeffs = odxrequire(scale_element.find("COMPU-RATIONAL-COEFFS"))
51
- nums = coeffs.iterfind("COMPU-NUMERATOR/V")
52
-
53
- offset = computation_python_type(odxrequire(next(nums).text))
54
- factor_el = next(nums, None)
55
- factor = computation_python_type(odxrequire(factor_el.text) if factor_el is not None else "0")
56
- denominator = 1.0
57
- if (string := coeffs.findtext("COMPU-DENOMINATOR/V")) is not None:
58
- denominator = float(string)
59
- if denominator == 0:
60
- warnings.warn(
61
- "CompuMethod: A denominator of zero will lead to divisions by zero.",
62
- OdxWarning,
63
- stacklevel=1)
64
- # Read lower limit
65
- internal_lower_limit = Limit.from_et(
66
- scale_element.find("LOWER-LIMIT"),
67
- internal_type=internal_type,
68
- )
69
- if internal_lower_limit is None:
70
- internal_lower_limit = Limit(0, IntervalType.INFINITE)
71
- kwargs["internal_lower_limit"] = internal_lower_limit
72
-
73
- # Read upper limit
74
- internal_upper_limit = Limit.from_et(
75
- scale_element.find("UPPER-LIMIT"),
76
- internal_type=internal_type,
77
- )
78
- if internal_upper_limit is None:
79
- if not is_scale_linear:
80
- internal_upper_limit = Limit(0, IntervalType.INFINITE)
81
- else:
82
- odxassert(internal_lower_limit is not None and
83
- internal_lower_limit.interval_type == IntervalType.CLOSED)
84
- logger.info("Scale linear without UPPER-LIMIT")
85
- internal_upper_limit = internal_lower_limit
86
- kwargs["internal_upper_limit"] = internal_upper_limit
87
- kwargs["denominator"] = denominator
88
- kwargs["factor"] = factor
89
- kwargs["offset"] = offset
90
-
91
- return LinearCompuMethod(**kwargs)
92
-
93
-
94
- def create_compu_default_value(et_element: Optional[ElementTree.Element],
95
- doc_frags: List[OdxDocFragment], internal_type: DataType,
96
- physical_type: DataType) -> Optional[CompuScale]:
97
- if et_element is None:
98
- return None
99
- compu_const = physical_type.create_from_et(et_element)
100
- scale = CompuScale.from_et(
101
- et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
102
- scale.compu_const = compu_const
103
- return scale
104
-
105
-
106
19
  def create_any_compu_method_from_et(et_element: ElementTree.Element,
107
- doc_frags: List[OdxDocFragment], internal_type: DataType,
20
+ doc_frags: List[OdxDocFragment], *, internal_type: DataType,
108
21
  physical_type: DataType) -> CompuMethod:
109
- compu_category = et_element.findtext("CATEGORY")
110
- odxassert(compu_category in [
111
- "IDENTICAL",
112
- "LINEAR",
113
- "SCALE-LINEAR",
114
- "TEXTTABLE",
115
- "COMPUCODE",
116
- "TAB-INTP",
117
- "RAT-FUNC",
118
- "SCALE-RAT-FUNC",
119
- ])
120
-
121
- if et_element.find("COMPU-PHYS-TO-INTERNAL") is not None: # TODO: Is this never used?
122
- raise NotImplementedError(f"Found COMPU-PHYS-TO-INTERNAL for category {compu_category}")
123
-
124
- kwargs: Dict[str, Any] = {
125
- "physical_type": physical_type,
126
- "internal_type": internal_type,
127
- }
22
+ compu_category = odxrequire(et_element.findtext("CATEGORY"))
128
23
 
129
24
  if compu_category == "IDENTICAL":
130
- odxassert(
131
- internal_type == physical_type or
132
- (internal_type in [DataType.A_ASCIISTRING, DataType.A_UTF8STRING] and
133
- physical_type == DataType.A_UNICODE2STRING),
134
- f"Internal type '{internal_type}' and physical type '{physical_type}'"
135
- f" must be the same for compu methods of category '{compu_category}'")
136
- return IdenticalCompuMethod(internal_type=internal_type, physical_type=physical_type)
137
-
138
- if compu_category == "TEXTTABLE":
139
- odxassert(physical_type == DataType.A_UNICODE2STRING)
140
- compu_internal_to_phys = odxrequire(et_element.find("COMPU-INTERNAL-TO-PHYS"))
141
-
142
- internal_to_phys: List[CompuScale] = []
143
- for scale_elem in compu_internal_to_phys.iterfind("COMPU-SCALES/COMPU-SCALE"):
144
- internal_to_phys.append(
145
- CompuScale.from_et(
146
- scale_elem, doc_frags, internal_type=internal_type,
147
- physical_type=physical_type))
148
- compu_default_value = create_compu_default_value(
149
- et_element.find("COMPU-DEFAULT-VALUE"), doc_frags, **kwargs)
150
-
151
- return TexttableCompuMethod(
152
- internal_to_phys=internal_to_phys,
153
- compu_default_value=compu_default_value,
154
- **kwargs,
155
- )
156
-
25
+ return IdenticalCompuMethod.compu_method_from_et(
26
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
157
27
  elif compu_category == "LINEAR":
158
- # Compu method can be described by the function f(x) = (offset + factor * x) / denominator
159
-
160
- scale_elem = odxrequire(et_element.find("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE"))
161
- return _parse_compu_scale_to_linear_compu_method(scale_element=scale_elem, **kwargs)
162
-
28
+ return LinearCompuMethod.compu_method_from_et(
29
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
163
30
  elif compu_category == "SCALE-LINEAR":
164
-
165
- scale_elems = et_element.iterfind("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE")
166
- linear_methods = [
167
- _parse_compu_scale_to_linear_compu_method(scale_element=scale_elem, **kwargs)
168
- for scale_elem in scale_elems
169
- ]
170
- return ScaleLinearCompuMethod(linear_methods=linear_methods, **kwargs)
171
-
31
+ return ScaleLinearCompuMethod.compu_method_from_et(
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)
39
+ elif compu_category == "TEXTTABLE":
40
+ return TexttableCompuMethod.compu_method_from_et(
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)
172
45
  elif compu_category == "TAB-INTP":
173
- internal_points = []
174
- physical_points = []
175
- for scale_elem in et_element.iterfind("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE"):
176
- internal_point = internal_type.from_string(
177
- odxrequire(scale_elem.findtext("LOWER-LIMIT")))
178
- physical_point = physical_type.create_from_et(
179
- odxrequire(scale_elem.find("COMPU-CONST")))
46
+ return TabIntpCompuMethod.compu_method_from_et(
47
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
180
48
 
181
- if not isinstance(internal_point, (float, int)):
182
- odxraise()
183
- if not isinstance(physical_point, (float, int)):
184
- odxraise()
49
+ # TODO: Implement all categories (never instantiate the CompuMethod base class!)
50
+ odxraise(f"Warning: Computation category {compu_category} is not implemented!")
185
51
 
186
- internal_points.append(internal_point)
187
- physical_points.append(physical_point)
188
-
189
- return TabIntpCompuMethod(
190
- internal_points=internal_points, physical_points=physical_points, **kwargs)
191
-
192
- # TODO: Implement other categories (never instantiate CompuMethod)
193
- logger.warning(f"Warning: Computation category {compu_category} is not implemented!")
194
- return IdenticalCompuMethod(internal_type=DataType.A_UINT32, physical_type=DataType.A_UINT32)
52
+ return IdenticalCompuMethod.compu_method_from_et(
53
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
@@ -1,16 +1,41 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
+ from typing import List
4
+ from xml.etree import ElementTree
3
5
 
4
- from ..odxtypes import AtomicOdxType
5
- from .compumethod import CompuMethod, CompuMethodCategory
6
+ from ..exceptions import odxassert
7
+ from ..odxlink import OdxDocFragment
8
+ from ..odxtypes import AtomicOdxType, DataType
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuMethod
6
11
 
7
12
 
8
13
  @dataclass
9
14
  class IdenticalCompuMethod(CompuMethod):
10
-
11
- @property
12
- def category(self) -> CompuMethodCategory:
13
- return "IDENTICAL"
15
+ """Identical compu methods just pass through the internal value.
16
+
17
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.2.
18
+ """
19
+
20
+ @staticmethod
21
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
22
+ internal_type: DataType,
23
+ physical_type: DataType) -> "IdenticalCompuMethod":
24
+ cm = CompuMethod.compu_method_from_et(
25
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
26
+ kwargs = dataclass_fields_asdict(cm)
27
+
28
+ odxassert(
29
+ internal_type == physical_type or
30
+ (internal_type
31
+ in [DataType.A_ASCIISTRING, DataType.A_UTF8STRING, DataType.A_UNICODE2STRING] and
32
+ physical_type
33
+ in [DataType.A_ASCIISTRING, DataType.A_UTF8STRING, DataType.A_UNICODE2STRING]),
34
+ f"Internal type and physical type must be the same for compu methods of category "
35
+ f"'{cm.category}' (internal type: '{internal_type.value}', physical type: "
36
+ f"'{physical_type.value}')")
37
+
38
+ return IdenticalCompuMethod(**kwargs)
14
39
 
15
40
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
16
41
  return physical_value
@@ -1,11 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Optional
4
+ from typing import List, Optional, overload
5
5
  from xml.etree import ElementTree
6
6
 
7
- from ..exceptions import odxassert, odxraise, odxrequire
8
- from ..odxtypes import AtomicOdxType, DataType
7
+ from ..exceptions import odxraise
8
+ from ..odxlink import OdxDocFragment
9
+ from ..odxtypes import AtomicOdxType, DataType, compare_odx_values
9
10
 
10
11
 
11
12
  class IntervalType(Enum):
@@ -16,42 +17,54 @@ class IntervalType(Enum):
16
17
 
17
18
  @dataclass
18
19
  class Limit:
19
- value: AtomicOdxType
20
- interval_type: IntervalType = IntervalType.CLOSED
20
+ value_raw: Optional[str]
21
+ value_type: Optional[DataType]
22
+ interval_type: Optional[IntervalType]
21
23
 
22
24
  def __post_init__(self) -> None:
23
- if self.interval_type == IntervalType.INFINITE:
24
- self.value = 0
25
+ self._value: Optional[AtomicOdxType] = None
26
+
27
+ if self.value_type is not None:
28
+ self.set_value_type(self.value_type)
29
+
30
+ @staticmethod
31
+ @overload
32
+ def limit_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment],
33
+ value_type: Optional[DataType]) -> "Limit":
34
+ ...
35
+
36
+ @staticmethod
37
+ @overload
38
+ def limit_from_et(et_element: None, doc_frags: List[OdxDocFragment],
39
+ value_type: Optional[DataType]) -> None:
40
+ ...
25
41
 
26
42
  @staticmethod
27
- def from_et(et_element: Optional[ElementTree.Element], *,
28
- internal_type: DataType) -> Optional["Limit"]:
43
+ def limit_from_et(et_element: Optional[ElementTree.Element], doc_frags: List[OdxDocFragment],
44
+ value_type: Optional[DataType]) -> Optional["Limit"]:
29
45
 
30
46
  if et_element is None:
31
47
  return None
32
48
 
49
+ interval_type = None
33
50
  if (interval_type_str := et_element.get("INTERVAL-TYPE")) is not None:
34
51
  try:
35
52
  interval_type = IntervalType(interval_type_str)
36
53
  except ValueError:
37
- interval_type = IntervalType.CLOSED
38
54
  odxraise(f"Encountered unknown interval type '{interval_type_str}'")
39
- else:
40
- interval_type = IntervalType.CLOSED
41
-
42
- if interval_type == IntervalType.INFINITE:
43
- if et_element.tag == "LOWER-LIMIT":
44
- return Limit(float("-inf"), interval_type)
45
- else:
46
- odxassert(et_element.tag == "UPPER-LIMIT")
47
- return Limit(float("inf"), interval_type)
48
- elif internal_type == DataType.A_BYTEFIELD:
49
- hex_text = odxrequire(et_element.text)
50
- if len(hex_text) % 2 == 1:
51
- hex_text = "0" + hex_text
52
- return Limit(bytes.fromhex(hex_text), interval_type)
53
- else:
54
- return Limit(internal_type.from_string(odxrequire(et_element.text)), interval_type)
55
+
56
+ value_raw = et_element.text
57
+
58
+ return Limit(value_raw=value_raw, interval_type=interval_type, value_type=value_type)
59
+
60
+ def set_value_type(self, value_type: DataType) -> None:
61
+ self.value_type = value_type
62
+ if self.value_raw is not None:
63
+ self._value = value_type.from_string(self.value_raw)
64
+
65
+ @property
66
+ def value(self) -> Optional[AtomicOdxType]:
67
+ return self._value
55
68
 
56
69
  def complies_to_upper(self, value: AtomicOdxType) -> bool:
57
70
  """Checks if the value is in the range w.r.t. the upper limit.
@@ -60,13 +73,23 @@ class Limit:
60
73
  * If the interval type is open, return `value < limit.value`.
61
74
  * If the interval type is infinite, return `True`.
62
75
  """
63
- if self.interval_type == IntervalType.CLOSED:
64
- return value <= self.value # type: ignore[operator]
65
- elif self.interval_type == IntervalType.OPEN:
66
- return value < self.value # type: ignore[operator]
67
- elif self.interval_type == IntervalType.INFINITE:
76
+ if self._value is None:
77
+ # if no value is specified, assume interval type INFINITE
78
+ # (what are we supposed to compare against?)
68
79
  return True
69
80
 
81
+ if self.interval_type is None or self.interval_type == IntervalType.CLOSED:
82
+ # assume interval type CLOSED if a value was specified,
83
+ # but no interval type
84
+ return compare_odx_values(value, self._value) <= 0
85
+ elif self.interval_type == IntervalType.OPEN:
86
+ return compare_odx_values(value, self._value) < 0
87
+
88
+ if self.interval_type != IntervalType.INFINITE:
89
+ odxraise("Unhandled interval type {self.interval_type}")
90
+
91
+ return True
92
+
70
93
  def complies_to_lower(self, value: AtomicOdxType) -> bool:
71
94
  """Checks if the value is in the range w.r.t. the lower limit.
72
95
 
@@ -74,9 +97,20 @@ class Limit:
74
97
  * If the interval type is open, return `limit.value < value`.
75
98
  * If the interval type is infinite, return `True`.
76
99
  """
77
- if self.interval_type == IntervalType.CLOSED:
78
- return self.value <= value # type: ignore[operator]
79
- elif self.interval_type == IntervalType.OPEN:
80
- return self.value < value # type: ignore[operator]
81
- elif self.interval_type == IntervalType.INFINITE:
100
+
101
+ if self._value is None:
102
+ # if no value is specified, assume interval type INFINITE
103
+ # (what are we supposed to compare against?)
82
104
  return True
105
+
106
+ if self.interval_type is None or self.interval_type == IntervalType.CLOSED:
107
+ # assume interval type CLOSED if a value was specified,
108
+ # but no interval type
109
+ return compare_odx_values(value, self._value) >= 0
110
+ elif self.interval_type == IntervalType.OPEN:
111
+ return compare_odx_values(value, self._value) > 0
112
+
113
+ if self.interval_type != IntervalType.INFINITE:
114
+ odxraise("Unhandled interval type {self.interval_type}")
115
+
116
+ return True