odxtools 7.1.1__py3-none-any.whl → 7.2.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 (130) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +3 -5
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +10 -17
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +3 -2
  9. odxtools/cli/compare.py +1 -1
  10. odxtools/companydata.py +5 -7
  11. odxtools/companydocinfo.py +7 -8
  12. odxtools/companyrevisioninfo.py +3 -5
  13. odxtools/companyspecificinfo.py +8 -9
  14. odxtools/comparam.py +4 -6
  15. odxtools/comparaminstance.py +6 -8
  16. odxtools/comparamspec.py +14 -13
  17. odxtools/comparamsubset.py +17 -16
  18. odxtools/complexcomparam.py +5 -7
  19. odxtools/compumethods/compuconst.py +31 -0
  20. odxtools/compumethods/compudefaultvalue.py +27 -0
  21. odxtools/compumethods/compuinternaltophys.py +39 -0
  22. odxtools/compumethods/compuinversevalue.py +7 -0
  23. odxtools/compumethods/compumethod.py +67 -12
  24. odxtools/compumethods/compuphystointernal.py +39 -0
  25. odxtools/compumethods/compuscale.py +15 -26
  26. odxtools/compumethods/createanycompumethod.py +14 -160
  27. odxtools/compumethods/identicalcompumethod.py +31 -6
  28. odxtools/compumethods/linearcompumethod.py +69 -189
  29. odxtools/compumethods/linearsegment.py +193 -0
  30. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  31. odxtools/compumethods/tabintpcompumethod.py +119 -99
  32. odxtools/compumethods/texttablecompumethod.py +107 -43
  33. odxtools/createanydiagcodedtype.py +10 -67
  34. odxtools/database.py +68 -62
  35. odxtools/dataobjectproperty.py +10 -19
  36. odxtools/description.py +47 -0
  37. odxtools/determinenumberofitems.py +4 -5
  38. odxtools/diagcodedtype.py +29 -12
  39. odxtools/diagcomm.py +10 -6
  40. odxtools/diagdatadictionaryspec.py +20 -21
  41. odxtools/diaglayer.py +34 -5
  42. odxtools/diaglayercontainer.py +17 -11
  43. odxtools/diaglayerraw.py +20 -21
  44. odxtools/diagnostictroublecode.py +7 -8
  45. odxtools/diagservice.py +9 -7
  46. odxtools/docrevision.py +5 -7
  47. odxtools/dopbase.py +7 -8
  48. odxtools/dtcdop.py +5 -8
  49. odxtools/dynamicendmarkerfield.py +22 -9
  50. odxtools/dynamiclengthfield.py +5 -11
  51. odxtools/element.py +4 -3
  52. odxtools/endofpdufield.py +0 -2
  53. odxtools/environmentdatadescription.py +4 -6
  54. odxtools/exceptions.py +1 -1
  55. odxtools/field.py +9 -9
  56. odxtools/functionalclass.py +3 -5
  57. odxtools/inputparam.py +3 -5
  58. odxtools/leadinglengthinfotype.py +15 -2
  59. odxtools/loadfile.py +64 -0
  60. odxtools/minmaxlengthtype.py +20 -2
  61. odxtools/modification.py +3 -5
  62. odxtools/multiplexer.py +7 -14
  63. odxtools/multiplexercase.py +4 -6
  64. odxtools/multiplexerdefaultcase.py +4 -6
  65. odxtools/multiplexerswitchkey.py +4 -5
  66. odxtools/negoutputparam.py +3 -5
  67. odxtools/outputparam.py +3 -5
  68. odxtools/parameterinfo.py +3 -3
  69. odxtools/parameters/codedconstparameter.py +2 -14
  70. odxtools/parameters/lengthkeyparameter.py +3 -17
  71. odxtools/parameters/nrcconstparameter.py +2 -14
  72. odxtools/parameters/parameter.py +22 -22
  73. odxtools/parameters/parameterwithdop.py +6 -8
  74. odxtools/parameters/physicalconstantparameter.py +5 -8
  75. odxtools/parameters/reservedparameter.py +4 -3
  76. odxtools/parameters/tablekeyparameter.py +6 -9
  77. odxtools/parameters/tablestructparameter.py +6 -8
  78. odxtools/parameters/valueparameter.py +5 -8
  79. odxtools/paramlengthinfotype.py +19 -6
  80. odxtools/parentref.py +15 -1
  81. odxtools/physicaldimension.py +3 -5
  82. odxtools/progcode.py +18 -7
  83. odxtools/protstack.py +3 -5
  84. odxtools/relateddoc.py +7 -9
  85. odxtools/request.py +8 -0
  86. odxtools/response.py +8 -0
  87. odxtools/scaleconstr.py +3 -3
  88. odxtools/singleecujob.py +12 -10
  89. odxtools/snrefcontext.py +29 -0
  90. odxtools/specialdata.py +3 -5
  91. odxtools/specialdatagroup.py +5 -7
  92. odxtools/specialdatagroupcaption.py +3 -6
  93. odxtools/standardlengthtype.py +27 -2
  94. odxtools/state.py +3 -5
  95. odxtools/statechart.py +9 -11
  96. odxtools/statetransition.py +4 -9
  97. odxtools/staticfield.py +4 -8
  98. odxtools/table.py +7 -8
  99. odxtools/tablerow.py +7 -6
  100. odxtools/teammember.py +3 -5
  101. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +2 -5
  102. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +2 -5
  103. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -5
  104. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  105. odxtools/templates/macros/printCompuMethod.xml.jinja2 +153 -0
  106. odxtools/templates/macros/printDOP.xml.jinja2 +10 -132
  107. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  108. odxtools/templates/macros/printElementId.xml.jinja2 +3 -3
  109. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  110. odxtools/templates/macros/printTable.xml.jinja2 +2 -3
  111. odxtools/unit.py +3 -5
  112. odxtools/unitgroup.py +3 -5
  113. odxtools/unitspec.py +9 -10
  114. odxtools/utils.py +1 -26
  115. odxtools/version.py +2 -2
  116. odxtools/{write_pdx_file.py → writepdxfile.py} +19 -10
  117. odxtools/xdoc.py +3 -5
  118. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/METADATA +1 -1
  119. odxtools-7.2.0.dist-info/RECORD +192 -0
  120. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/WHEEL +1 -1
  121. odxtools/createcompanydatas.py +0 -17
  122. odxtools/createsdgs.py +0 -19
  123. odxtools/load_file.py +0 -13
  124. odxtools/load_odx_d_file.py +0 -6
  125. odxtools/load_pdx_file.py +0 -8
  126. odxtools-7.1.1.dist-info/RECORD +0 -186
  127. /odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +0 -0
  128. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/LICENSE +0 -0
  129. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/entry_points.txt +0 -0
  130. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,31 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from ..odxtypes import AtomicOdxType, DataType
7
+
8
+
9
+ @dataclass
10
+ class CompuConst:
11
+ v: Optional[str]
12
+ vt: Optional[str]
13
+
14
+ data_type: DataType
15
+
16
+ @staticmethod
17
+ def compuvalue_from_et(et_element: ElementTree.Element, *, data_type: DataType) -> "CompuConst":
18
+
19
+ v = et_element.findtext("V")
20
+ vt = et_element.findtext("VT")
21
+
22
+ return CompuConst(v=v, vt=vt, data_type=data_type)
23
+
24
+ def __post_init__(self) -> None:
25
+ self._value: Optional[AtomicOdxType] = self.vt
26
+ if self.v is not None:
27
+ self._value = self.data_type.from_string(self.v)
28
+
29
+ @property
30
+ def value(self) -> Optional[AtomicOdxType]:
31
+ return self._value
@@ -0,0 +1,27 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from ..odxtypes import DataType
7
+ from ..utils import dataclass_fields_asdict
8
+ from .compuconst import CompuConst
9
+ from .compuinversevalue import CompuInverseValue
10
+
11
+
12
+ @dataclass
13
+ class CompuDefaultValue(CompuConst):
14
+ compu_inverse_value: Optional[CompuInverseValue]
15
+
16
+ @staticmethod
17
+ def compuvalue_from_et(et_element: ElementTree.Element, *,
18
+ data_type: DataType) -> "CompuDefaultValue":
19
+ kwargs = dataclass_fields_asdict(
20
+ CompuConst.compuvalue_from_et(et_element, data_type=data_type))
21
+
22
+ compu_inverse_value = None
23
+ if (civ_elem := et_element.find("COMPU-INVERSE-VALUE")) is not None:
24
+ compu_inverse_value = CompuInverseValue.compuvalue_from_et(
25
+ civ_elem, data_type=data_type)
26
+
27
+ return CompuDefaultValue(**kwargs, compu_inverse_value=compu_inverse_value)
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from ..odxlink import OdxDocFragment
7
+ from ..odxtypes import DataType
8
+ from ..progcode import ProgCode
9
+ from .compudefaultvalue import CompuDefaultValue
10
+ from .compuscale import CompuScale
11
+
12
+
13
+ @dataclass
14
+ class CompuInternalToPhys:
15
+ compu_scales: List[CompuScale]
16
+ prog_code: Optional[ProgCode]
17
+ compu_default_value: Optional[CompuDefaultValue]
18
+
19
+ @staticmethod
20
+ def compu_internal_to_phys_from_et(et_element: ElementTree.Element,
21
+ doc_frags: List[OdxDocFragment], *, internal_type: DataType,
22
+ physical_type: DataType) -> "CompuInternalToPhys":
23
+ compu_scales = [
24
+ CompuScale.compuscale_from_et(
25
+ cse, doc_frags, internal_type=internal_type, physical_type=physical_type)
26
+ for cse in et_element.iterfind("COMPU-SCALES/COMPU-SCALE")
27
+ ]
28
+
29
+ prog_code = None
30
+ if (pce := et_element.find("PROG-CODE")) is not None:
31
+ prog_code = ProgCode.from_et(pce, doc_frags)
32
+
33
+ compu_default_value = None
34
+ if (cdve := et_element.find("COMPU-DEFAULT-VALUE")) is not None:
35
+ compu_default_value = CompuDefaultValue.compuvalue_from_et(
36
+ cdve, data_type=physical_type)
37
+
38
+ return CompuInternalToPhys(
39
+ compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from .compuconst import CompuConst
4
+
5
+ # make CompuInverseValue an alias for CompuConst. The XSD defines two
6
+ # separate but identical groups for this (why?)...
7
+ CompuInverseValue = CompuConst
@@ -1,26 +1,81 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Literal
3
+ from enum import Enum
4
+ from typing import List, Optional
5
+ from xml.etree import ElementTree
4
6
 
7
+ from ..exceptions import odxraise
8
+ from ..odxlink import OdxDocFragment
5
9
  from ..odxtypes import AtomicOdxType, DataType
10
+ from .compuinternaltophys import CompuInternalToPhys
11
+ from .compuphystointernal import CompuPhysToInternal
6
12
 
7
- CompuMethodCategory = Literal[
8
- "IDENTICAL",
9
- "LINEAR",
10
- "SCALE-LINEAR",
11
- "TAB-INTP",
12
- "TEXTTABLE",
13
- ]
13
+
14
+ class CompuCategory(Enum):
15
+ IDENTICAL = "IDENTICAL"
16
+ LINEAR = "LINEAR"
17
+ SCALE_LINEAR = "SCALE-LINEAR"
18
+ TEXTTABLE = "TEXTTABLE"
19
+ COMPUCODE = "COMPUCODE"
20
+ TAB_INTP = "TAB-INTP"
21
+ RAT_FUNC = "RAT-FUNC"
22
+ SCALE_RAT_FUNC = "SCALE-RAT-FUNC"
14
23
 
15
24
 
16
25
  @dataclass
17
26
  class CompuMethod:
18
- internal_type: DataType
27
+ """A compu method translates between the internal representation
28
+ of a value and their physical representation.
29
+
30
+ There are many compu methods, but all of them are specified using
31
+ the same mechanism: The conversion from internal to physical
32
+ quantities is specified using the COMPU-INTERNAL-TO-PHYS subtag,
33
+ and the inverse is covered by
34
+ COMPU-PHYS-TO-INTERNAL. Alternatively to directly specifying the
35
+ parameters needed for conversion, it is also possible to specify a
36
+ Java program which does the conversion (doing this excludes using
37
+ ODX in non-Java contexts, though).
38
+
39
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.
40
+
41
+ """
42
+
43
+ category: CompuCategory
44
+ compu_internal_to_phys: Optional[CompuInternalToPhys]
45
+ compu_phys_to_internal: Optional[CompuPhysToInternal]
46
+
19
47
  physical_type: DataType
48
+ internal_type: DataType
20
49
 
21
- @property
22
- def category(self) -> CompuMethodCategory:
23
- raise NotImplementedError()
50
+ @staticmethod
51
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
52
+ internal_type: DataType, physical_type: DataType) -> "CompuMethod":
53
+ cat_text = et_element.findtext("CATEGORY")
54
+ if cat_text is None:
55
+ odxraise("No category specified for compu method")
56
+ cat_text = "IDENTICAL"
57
+
58
+ try:
59
+ category = CompuCategory(cat_text)
60
+ except ValueError:
61
+ odxraise(f"Encountered compu method of unknown category '{cat_text}'")
62
+ category = CompuCategory.IDENTICAL
63
+
64
+ compu_internal_to_phys = None
65
+ if (citp_elem := et_element.find("COMPU-INTERNAL-TO-PHYS")) is not None:
66
+ compu_internal_to_phys = CompuInternalToPhys.compu_internal_to_phys_from_et(
67
+ citp_elem, doc_frags, internal_type=internal_type, physical_type=physical_type)
68
+ compu_phys_to_internal = None
69
+ if (cpti_elem := et_element.find("COMPU-PHYS-TO-INTERNAL")) is not None:
70
+ compu_phys_to_internal = CompuPhysToInternal.compu_phys_to_internal_from_et(
71
+ cpti_elem, doc_frags, internal_type=internal_type, physical_type=physical_type)
72
+
73
+ return CompuMethod(
74
+ category=category,
75
+ compu_internal_to_phys=compu_internal_to_phys,
76
+ compu_phys_to_internal=compu_phys_to_internal,
77
+ physical_type=physical_type,
78
+ internal_type=internal_type)
24
79
 
25
80
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
26
81
  raise NotImplementedError()
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from ..odxlink import OdxDocFragment
7
+ from ..odxtypes import DataType
8
+ from ..progcode import ProgCode
9
+ from .compudefaultvalue import CompuDefaultValue
10
+ from .compuscale import CompuScale
11
+
12
+
13
+ @dataclass
14
+ class CompuPhysToInternal:
15
+ compu_scales: List[CompuScale]
16
+ prog_code: Optional[ProgCode]
17
+ compu_default_value: Optional[CompuDefaultValue]
18
+
19
+ @staticmethod
20
+ def compu_phys_to_internal_from_et(et_element: ElementTree.Element,
21
+ doc_frags: List[OdxDocFragment], *, internal_type: DataType,
22
+ physical_type: DataType) -> "CompuPhysToInternal":
23
+ compu_scales = [
24
+ CompuScale.compuscale_from_et(
25
+ cse, doc_frags, internal_type=internal_type, physical_type=physical_type)
26
+ for cse in et_element.iterfind("COMPU-SCALES/COMPU-SCALE")
27
+ ]
28
+
29
+ prog_code = None
30
+ if (pce := et_element.find("PROG-CODE")) is not None:
31
+ prog_code = ProgCode.from_et(pce, doc_frags)
32
+
33
+ compu_default_value = None
34
+ if (cdve := et_element.find("COMPU-DEFAULT-VALUE")) is not None:
35
+ compu_default_value = CompuDefaultValue.compuvalue_from_et(
36
+ cdve, data_type=internal_type)
37
+
38
+ return CompuPhysToInternal(
39
+ compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
@@ -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,33 +15,14 @@ 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
 
45
28
  # the following two attributes are not specified for COMPU-SCALE
@@ -52,15 +35,21 @@ class CompuScale:
52
35
  def compuscale_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
53
36
  internal_type: DataType, physical_type: DataType) -> "CompuScale":
54
37
  short_label = et_element.findtext("SHORT-LABEL")
55
- description = create_description_from_et(et_element.find("DESC"))
38
+ description = Description.from_et(et_element.find("DESC"), doc_frags)
56
39
 
57
40
  lower_limit = Limit.limit_from_et(
58
41
  et_element.find("LOWER-LIMIT"), doc_frags, value_type=internal_type)
59
42
  upper_limit = Limit.limit_from_et(
60
43
  et_element.find("UPPER-LIMIT"), doc_frags, value_type=internal_type)
61
44
 
62
- compu_inverse_value = internal_type.create_from_et(et_element.find("COMPU-INVERSE-VALUE"))
63
- compu_const = physical_type.create_from_et(et_element.find("COMPU-CONST"))
45
+ compu_inverse_value = None
46
+ if (cive := et_element.find("COMPU-INVERSE-VALUE")) is not None:
47
+ compu_inverse_value = CompuInverseValue.compuvalue_from_et(
48
+ cive, data_type=internal_type)
49
+
50
+ compu_const = None
51
+ if (cce := et_element.find("COMPU-CONST")) is not None:
52
+ compu_const = CompuConst.compuvalue_from_et(cce, data_type=physical_type)
64
53
 
65
54
  compu_rational_coeffs: Optional[CompuRationalCoeffs] = None
66
55
  if (crc_elem := et_element.find("COMPU-RATIONAL-COEFFS")) is not None:
@@ -1,184 +1,38 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from typing import Any, Dict, List, Optional
2
+ from typing import List
3
3
  from xml.etree import ElementTree
4
4
 
5
- from ..exceptions import odxassert, odxraise, odxrequire
5
+ from ..exceptions import odxraise, odxrequire
6
6
  from ..odxlink import OdxDocFragment
7
7
  from ..odxtypes import DataType
8
8
  from .compumethod import CompuMethod
9
- from .compuscale import CompuScale
10
9
  from .identicalcompumethod import IdenticalCompuMethod
11
- from .limit import Limit
12
10
  from .linearcompumethod import LinearCompuMethod
13
11
  from .scalelinearcompumethod import ScaleLinearCompuMethod
14
12
  from .tabintpcompumethod import TabIntpCompuMethod
15
13
  from .texttablecompumethod import TexttableCompuMethod
16
14
 
17
15
 
18
- def _parse_compu_scale_to_linear_compu_method(
19
- et_element: ElementTree.Element,
20
- doc_frags: List[OdxDocFragment],
21
- *,
22
- internal_type: DataType,
23
- physical_type: DataType,
24
- is_scale_linear: bool = False,
25
- **kwargs: Any,
26
- ) -> LinearCompuMethod:
27
- odxassert(physical_type in [
28
- DataType.A_FLOAT32,
29
- DataType.A_FLOAT64,
30
- DataType.A_INT32,
31
- DataType.A_UINT32,
32
- ])
33
- odxassert(internal_type in [
34
- DataType.A_FLOAT32,
35
- DataType.A_FLOAT64,
36
- DataType.A_INT32,
37
- DataType.A_UINT32,
38
- ])
39
-
40
- if physical_type.python_type == float:
41
- computation_python_type = physical_type.from_string
42
- else:
43
- computation_python_type = internal_type.from_string
44
-
45
- kwargs = kwargs.copy()
46
- kwargs["internal_type"] = internal_type
47
- kwargs["physical_type"] = physical_type
48
-
49
- coeffs = odxrequire(et_element.find("COMPU-RATIONAL-COEFFS"))
50
- nums = coeffs.iterfind("COMPU-NUMERATOR/V")
51
-
52
- offset = computation_python_type(odxrequire(next(nums).text))
53
- factor_el = next(nums, None)
54
- factor = computation_python_type(odxrequire(factor_el.text) if factor_el is not None else "0")
55
- denominator = 1.0
56
- if (string := coeffs.findtext("COMPU-DENOMINATOR/V")) is not None:
57
- denominator = float(string)
58
- if denominator == 0:
59
- odxraise("CompuMethod: A denominator of zero will lead to divisions by zero.")
60
-
61
- # Read lower limit
62
- internal_lower_limit = Limit.limit_from_et(
63
- et_element.find("LOWER-LIMIT"),
64
- doc_frags,
65
- value_type=internal_type,
66
- )
67
-
68
- kwargs["internal_lower_limit"] = internal_lower_limit
69
-
70
- # Read upper limit
71
- internal_upper_limit = Limit.limit_from_et(
72
- et_element.find("UPPER-LIMIT"),
73
- doc_frags,
74
- value_type=internal_type,
75
- )
76
-
77
- kwargs["internal_upper_limit"] = internal_upper_limit
78
- kwargs["denominator"] = denominator
79
- kwargs["factor"] = factor
80
- kwargs["offset"] = offset
81
-
82
- return LinearCompuMethod(**kwargs)
83
-
84
-
85
- def create_compu_default_value(et_element: Optional[ElementTree.Element],
86
- doc_frags: List[OdxDocFragment], internal_type: DataType, *,
87
- physical_type: DataType) -> Optional[CompuScale]:
88
- if et_element is None:
89
- return None
90
- compu_const = physical_type.create_from_et(et_element)
91
- scale = CompuScale.compuscale_from_et(
92
- et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
93
- scale.compu_const = compu_const
94
- return scale
95
-
96
-
97
16
  def create_any_compu_method_from_et(et_element: ElementTree.Element,
98
17
  doc_frags: List[OdxDocFragment], *, internal_type: DataType,
99
18
  physical_type: DataType) -> CompuMethod:
100
- compu_category = et_element.findtext("CATEGORY")
101
- odxassert(compu_category in [
102
- "IDENTICAL",
103
- "LINEAR",
104
- "SCALE-LINEAR",
105
- "TEXTTABLE",
106
- "COMPUCODE",
107
- "TAB-INTP",
108
- "RAT-FUNC",
109
- "SCALE-RAT-FUNC",
110
- ])
111
-
112
- if et_element.find("COMPU-PHYS-TO-INTERNAL") is not None: # TODO: Is this never used?
113
- raise NotImplementedError(f"Found COMPU-PHYS-TO-INTERNAL for category {compu_category}")
114
-
115
- kwargs: Dict[str, Any] = {
116
- "physical_type": physical_type,
117
- "internal_type": internal_type,
118
- }
19
+ compu_category = odxrequire(et_element.findtext("CATEGORY"))
119
20
 
120
21
  if compu_category == "IDENTICAL":
121
- odxassert(
122
- internal_type == physical_type or
123
- (internal_type in [DataType.A_ASCIISTRING, DataType.A_UTF8STRING] and
124
- physical_type == DataType.A_UNICODE2STRING),
125
- f"Internal type '{internal_type}' and physical type '{physical_type}'"
126
- f" must be the same for compu methods of category '{compu_category}'")
127
- return IdenticalCompuMethod(internal_type=internal_type, physical_type=physical_type)
128
-
129
- if compu_category == "TEXTTABLE":
130
- odxassert(physical_type == DataType.A_UNICODE2STRING)
131
- compu_internal_to_phys = odxrequire(et_element.find("COMPU-INTERNAL-TO-PHYS"))
132
-
133
- internal_to_phys: List[CompuScale] = []
134
- for scale_elem in compu_internal_to_phys.iterfind("COMPU-SCALES/COMPU-SCALE"):
135
- internal_to_phys.append(
136
- CompuScale.compuscale_from_et(
137
- scale_elem, doc_frags, internal_type=internal_type,
138
- physical_type=physical_type))
139
- compu_default_value = create_compu_default_value(
140
- et_element.find("COMPU-INTERNAL-TO-PHYS/COMPU-DEFAULT-VALUE"), doc_frags, **kwargs)
141
-
142
- return TexttableCompuMethod(
143
- internal_to_phys=internal_to_phys,
144
- compu_default_value=compu_default_value,
145
- **kwargs,
146
- )
147
-
22
+ return IdenticalCompuMethod.compu_method_from_et(
23
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
148
24
  elif compu_category == "LINEAR":
149
- # Compu method can be described by the function f(x) = (offset + factor * x) / denominator
150
-
151
- scale_elem = odxrequire(et_element.find("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE"))
152
- return _parse_compu_scale_to_linear_compu_method(scale_elem, doc_frags, **kwargs)
153
-
25
+ return LinearCompuMethod.compu_method_from_et(
26
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
154
27
  elif compu_category == "SCALE-LINEAR":
155
-
156
- scale_elems = et_element.iterfind("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE")
157
- linear_methods = [
158
- _parse_compu_scale_to_linear_compu_method(scale_elem, doc_frags, **kwargs)
159
- for scale_elem in scale_elems
160
- ]
161
- return ScaleLinearCompuMethod(linear_methods=linear_methods, **kwargs)
162
-
28
+ return ScaleLinearCompuMethod.compu_method_from_et(
29
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
30
+ elif compu_category == "TEXTTABLE":
31
+ return TexttableCompuMethod.compu_method_from_et(
32
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
163
33
  elif compu_category == "TAB-INTP":
164
- internal_points = []
165
- physical_points = []
166
- for scale_elem in et_element.iterfind("COMPU-INTERNAL-TO-PHYS/COMPU-SCALES/COMPU-SCALE"):
167
- internal_point = internal_type.from_string(
168
- odxrequire(scale_elem.findtext("LOWER-LIMIT")))
169
- physical_point = physical_type.create_from_et(
170
- odxrequire(scale_elem.find("COMPU-CONST")))
171
-
172
- if not isinstance(internal_point, (float, int)):
173
- odxraise()
174
- if not isinstance(physical_point, (float, int)):
175
- odxraise()
176
-
177
- internal_points.append(internal_point)
178
- physical_points.append(physical_point)
179
-
180
- return TabIntpCompuMethod(
181
- internal_points=internal_points, physical_points=physical_points, **kwargs)
34
+ return TabIntpCompuMethod.compu_method_from_et(
35
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
182
36
 
183
37
  # TODO: Implement all categories (never instantiate the CompuMethod base class!)
184
38
  odxraise(f"Warning: Computation category {compu_category} is not implemented!")
@@ -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