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
@@ -1,107 +1,114 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Tuple, Union
3
+ from typing import List, Union
4
+ from xml.etree import ElementTree
4
5
 
5
- from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
6
+ from ..exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
7
+ from ..odxlink import OdxDocFragment
6
8
  from ..odxtypes import AtomicOdxType, DataType
7
- from .compumethod import CompuMethod, CompuMethodCategory
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuCategory, CompuMethod
8
11
  from .limit import IntervalType, Limit
9
12
 
10
13
 
11
14
  @dataclass
12
15
  class TabIntpCompuMethod(CompuMethod):
13
- """
14
- A compu method of type Tab Interpolated is used for linear interpolation.
15
-
16
- A `TabIntpCompuMethod` is defined by a set of points. Each point is an (internal, physical) value pair.
17
- When converting from internal to physical or vice-versa, the result is linearly interpolated.
18
-
19
- The function defined by a `TabIntpCompuMethod` is similar to the one of a `ScaleLinearCompuMethod` with the following differences:
20
-
21
- * `TabIntpCompuMethod`s are always continuous whereas `ScaleLinearCompuMethod` might have jumps
22
- * `TabIntpCompuMethod`s are always invertible: Even if the linear interpolation is not monotonic, the first matching interval is taken.
23
-
24
- Refer to ASAM MCD-2 D (ODX) Specification, section 7.3.6.6.8 for details.
25
-
26
- Examples
27
- --------
28
-
29
- Create a TabIntpCompuMethod defined by the points (0, -1), (10, 1), (30, 2)::
30
-
31
- method = TabIntpCompuMethod(
32
- internal_type=DataType.A_UINT32,
33
- physical_type=DataType.A_UINT32,
34
- internal_points=[0, 10, 30],
35
- physical_points=[-1, 1, 2]
36
- )
37
-
38
- Note that the points are given as two lists. The equivalent odx definition is::
39
-
40
- <COMPU-METHOD>
41
- <CATEGORY>TAB-INTP</CATEGORY>
42
- <COMPU-INTERNAL-TO-PHYS>
43
- <COMPU-SCALES>
44
- <COMPU-SCALE>
45
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">0</LOWER-LIMIT>
46
- <COMPU-CONST>
47
- <V>-1</V>
48
- </COMPU-CONST>
49
- </COMPU-SCALE>
50
- <COMPU-SCALE>
51
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">10</LOWER-LIMIT>
52
- <COMPU-CONST>
53
- <V>1</V>
54
- </COMPU-CONST>
55
- </COMPU-SCALE>
56
- <COMPU-SCALE>
57
- <LOWER-LIMIT INTERVAL-TYPE = "CLOSED">30</LOWER-LIMIT>
58
- <COMPU-CONST>
59
- <V>2</V>
60
- </COMPU-CONST>
61
- </COMPU-SCALE>
62
- </COMPU-SCALES>
63
- </COMPU-INTERNAL-TO-PHYS>
64
- </COMPU-METHOD>
16
+ """A table-based interpolated compu method provides a continuous
17
+ transfer function based on piecewise linear interpolation.
18
+
19
+ A `TabIntpCompuMethod` is defined by a set of points. Each point
20
+ is an (internal, physical) value pair. When converting from
21
+ internal to physical or vice-versa, the result is linearly
22
+ interpolated.
23
+
24
+ The function defined by a `TabIntpCompuMethod` is similar to the
25
+ one of a `ScaleLinearCompuMethod` with the following differences:
26
+
27
+ * `TabIntpCompuMethod`s are always continuous whereas
28
+ `ScaleLinearCompuMethod` might exhibit gaps
29
+ * `TabIntpCompuMethod`s are always invertible: Even if the linear
30
+ interpolation is not monotonic, the first matching interval is
31
+ used.
32
+
33
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.8.
65
34
 
66
35
  """
67
36
 
68
- internal_points: List[Union[float, int]]
69
- physical_points: List[Union[float, int]]
37
+ @property
38
+ def internal_points(self) -> List[Union[float, int]]:
39
+ return self._internal_points
40
+
41
+ @property
42
+ def physical_points(self) -> List[Union[float, int]]:
43
+ return self._physical_points
44
+
45
+ @property
46
+ def internal_lower_limit(self) -> Limit:
47
+ return self._internal_lower_limit
48
+
49
+ @property
50
+ def internal_upper_limit(self) -> Limit:
51
+ return self._internal_upper_limit
52
+
53
+ @property
54
+ def physical_lower_limit(self) -> Limit:
55
+ return self._physical_lower_limit
56
+
57
+ @property
58
+ def physical_upper_limit(self) -> Limit:
59
+ return self._physical_upper_limit
60
+
61
+ @staticmethod
62
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
63
+ internal_type: DataType,
64
+ physical_type: DataType) -> "TabIntpCompuMethod":
65
+ cm = CompuMethod.compu_method_from_et(
66
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
67
+ kwargs = dataclass_fields_asdict(cm)
68
+
69
+ return TabIntpCompuMethod(**kwargs)
70
70
 
71
71
  def __post_init__(self) -> None:
72
+ odxassert(self.category == CompuCategory.TAB_INTP,
73
+ "TabIntpCompuMethod must exibit TAB-INTP category")
74
+
75
+ self._internal_points: List[Union[int, float]] = []
76
+ self._physical_points: List[Union[int, float]] = []
77
+ for scale in odxrequire(self.compu_internal_to_phys).compu_scales:
78
+ internal_point = odxrequire(scale.lower_limit).value
79
+ physical_point = odxrequire(scale.compu_const).value
80
+
81
+ if not isinstance(internal_point, (float, int)):
82
+ odxraise("The type of values of tab-intp compumethods must "
83
+ "either int or float")
84
+ if not isinstance(physical_point, (float, int)):
85
+ odxraise("The type of values of tab-intp compumethods must "
86
+ "either int or float")
87
+
88
+ self._internal_points.append(internal_point)
89
+ self._physical_points.append(physical_point)
90
+
72
91
  self._physical_lower_limit = Limit(
73
- value_raw=str(min(self.physical_points)),
92
+ value_raw=str(min(self._physical_points)),
74
93
  value_type=self.physical_type,
75
94
  interval_type=IntervalType.CLOSED)
76
95
  self._physical_upper_limit = Limit(
77
- value_raw=str(max(self.physical_points)),
96
+ value_raw=str(max(self._physical_points)),
78
97
  value_type=self.physical_type,
79
98
  interval_type=IntervalType.CLOSED)
80
99
 
81
100
  self._internal_lower_limit = Limit(
82
- value_raw=str(min(self.internal_points)),
101
+ value_raw=str(min(self._internal_points)),
83
102
  value_type=self.internal_type,
84
103
  interval_type=IntervalType.CLOSED)
85
104
  self._internal_upper_limit = Limit(
86
- value_raw=str(max(self.internal_points)),
105
+ value_raw=str(max(self._internal_points)),
87
106
  value_type=self.internal_type,
88
107
  interval_type=IntervalType.CLOSED)
89
108
 
90
- self._assert_validity()
91
-
92
- @property
93
- def category(self) -> CompuMethodCategory:
94
- return "TAB-INTP"
95
-
96
- @property
97
- def physical_lower_limit(self) -> Limit:
98
- return self._physical_lower_limit
109
+ self.__assert_validity()
99
110
 
100
- @property
101
- def physical_upper_limit(self) -> Limit:
102
- return self._physical_upper_limit
103
-
104
- def _assert_validity(self) -> None:
111
+ def __assert_validity(self) -> None:
105
112
  odxassert(len(self.internal_points) == len(self.physical_points))
106
113
 
107
114
  odxassert(
@@ -110,7 +117,7 @@ class TabIntpCompuMethod(CompuMethod):
110
117
  DataType.A_UINT32,
111
118
  DataType.A_FLOAT32,
112
119
  DataType.A_FLOAT64,
113
- ], "Internal data type of tab-intp compumethod must be one of"
120
+ ], "Internal data type of TAB-INTP compumethod must be one of"
114
121
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
115
122
  odxassert(
116
123
  self.physical_type in [
@@ -118,51 +125,64 @@ class TabIntpCompuMethod(CompuMethod):
118
125
  DataType.A_UINT32,
119
126
  DataType.A_FLOAT32,
120
127
  DataType.A_FLOAT64,
121
- ], "Physical data type of tab-intp compumethod must be one of"
128
+ ], "Physical data type of TAB-INTP compumethod must be one of"
122
129
  " [A_INT32, A_UINT32, A_FLOAT32, A_FLOAT64]")
123
130
 
124
- def _piecewise_linear_interpolate(self, x: Union[int, float],
125
- points: List[Tuple[Union[int, float],
126
- Union[int, float]]]) -> Union[float, None]:
127
- for ((x0, y0), (x1, y1)) in zip(points[:-1], points[1:]):
128
- if x0 <= x and x <= x1:
131
+ def __piecewise_linear_interpolate(self, x: Union[int, float],
132
+ range_samples: List[Union[int, float]],
133
+ domain_samples: List[Union[int,
134
+ float]]) -> Union[float, None]:
135
+ for i in range(0, len(range_samples) - 1):
136
+ if (x0 := range_samples[i]) <= x and x <= (x1 := range_samples[i + 1]):
137
+ y0 = domain_samples[i]
138
+ y1 = domain_samples[i + 1]
129
139
  return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
130
140
 
131
141
  return None
132
142
 
133
143
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
134
144
  if not isinstance(physical_value, (int, float)):
135
- raise EncodeError("The type of values of tab-intp compumethods must "
136
- "either int or float")
145
+ odxraise("The type of values of tab-intp compumethods must "
146
+ "either int or float", EncodeError)
147
+ return None
137
148
 
138
- reference_points = list(zip(self.physical_points, self.internal_points))
139
149
  odxassert(
140
150
  isinstance(physical_value, (int, float)),
141
- "Only integers and floats can be piecewise linearly interpolated")
142
- result = self._piecewise_linear_interpolate(physical_value, reference_points)
151
+ "Only integers and floats can be piecewise linearly interpolated", EncodeError)
152
+ result = self.__piecewise_linear_interpolate(physical_value, self._physical_points,
153
+ self._internal_points)
143
154
 
144
155
  if result is None:
145
- raise EncodeError(f"Internal value {physical_value!r} must be inside the range"
146
- f" [{min(self.physical_points)}, {max(self.physical_points)}]")
156
+ odxraise(
157
+ f"Internal value {physical_value!r} must be inside the range"
158
+ f" [{min(self.physical_points)}, {max(self.physical_points)}]", EncodeError)
159
+
147
160
  res = self.internal_type.make_from(result)
148
- if not isinstance(res, (int, float)):
149
- odxraise()
161
+
150
162
  return res
151
163
 
152
164
  def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
153
165
  if not isinstance(internal_value, (int, float)):
154
- raise EncodeError("The internal type of values of tab-intp compumethods must "
155
- "either int or float")
166
+ odxraise(
167
+ "The internal type of values of tab-intp compumethods must "
168
+ "either int or float", EncodeError)
169
+ return None
170
+
171
+ odxassert(
172
+ isinstance(internal_value, (int, float)),
173
+ "Only integers and floats can be piecewise linearly interpolated", DecodeError)
156
174
 
157
- reference_points = list(zip(self.internal_points, self.physical_points))
158
- result = self._piecewise_linear_interpolate(internal_value, reference_points)
175
+ result = self.__piecewise_linear_interpolate(internal_value, self._internal_points,
176
+ self._physical_points)
159
177
 
160
178
  if result is None:
161
- raise DecodeError(f"Internal value {internal_value!r} must be inside the range"
162
- f" [{min(self.internal_points)}, {max(self.internal_points)}]")
179
+ odxraise(
180
+ f"Internal value {internal_value!r} must be inside the range"
181
+ f" [{min(self.internal_points)}, {max(self.internal_points)}]", DecodeError)
182
+ return None
183
+
163
184
  res = self.physical_type.make_from(result)
164
- if not isinstance(res, (int, float)):
165
- odxraise()
185
+
166
186
  return res
167
187
 
168
188
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
@@ -1,76 +1,140 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional, cast
3
+ from typing import List, cast
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 .compuscale import CompuScale
9
12
 
10
13
 
11
14
  @dataclass
12
15
  class TexttableCompuMethod(CompuMethod):
16
+ """Text table compute methods translate numbers to human readable
17
+ textual descriptions.
13
18
 
14
- internal_to_phys: List[CompuScale]
15
- # For compu_default_value, the compu_const is always defined
16
- # the compu_inverse_value is optional
17
- compu_default_value: Optional[CompuScale]
19
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.7.
20
+
21
+ """
22
+
23
+ @staticmethod
24
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
25
+ internal_type: DataType,
26
+ physical_type: DataType) -> "TexttableCompuMethod":
27
+ cm = CompuMethod.compu_method_from_et(
28
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
29
+ kwargs = dataclass_fields_asdict(cm)
30
+
31
+ return TexttableCompuMethod(**kwargs)
18
32
 
19
33
  def __post_init__(self) -> None:
20
- odxassert(self.physical_type == DataType.A_UNICODE2STRING,
21
- "TEXTTABLE must have A_UNICODE2STRING as its physical datatype.")
34
+ odxassert(self.category == CompuCategory.TEXTTABLE,
35
+ "TexttableCompuMethod must exhibit TEXTTABLE category")
36
+
37
+ # the spec says that the physical data type shall be
38
+ # A_UNICODE2STRING, but we are a bit more lenient and allow
39
+ # any kind of string...
40
+ odxassert(
41
+ self.physical_type
42
+ in [DataType.A_UNICODE2STRING, DataType.A_UTF8STRING, DataType.A_ASCIISTRING],
43
+ "TEXTTABLE must have string type as its physical datatype.")
44
+
45
+ if self.compu_internal_to_phys is None:
46
+ odxraise("TEXTTABLE compu methods must exhibit a COMPU-INTERNAL-TO-PHYS subtag.")
47
+ scales = []
48
+ else:
49
+ scales = self.compu_internal_to_phys.compu_scales
50
+
22
51
  odxassert(
23
- all(scale.lower_limit is not None or scale.upper_limit is not None
24
- for scale in self.internal_to_phys),
25
- "Text table compu method doesn't have expected format!")
52
+ all(scale.lower_limit is not None or scale.upper_limit is not None for scale in scales),
53
+ "All scales of TEXTTABLE compu methods must provide limits!")
54
+
55
+ self._compu_physical_default_value = None
56
+ citp = odxrequire(self.compu_internal_to_phys)
57
+ if (cdv := citp.compu_default_value) is not None:
58
+ self._compu_physical_default_value = self.physical_type.from_string(odxrequire(cdv.vt))
26
59
 
27
- @property
28
- def category(self) -> CompuMethodCategory:
29
- return "TEXTTABLE"
60
+ self._compu_internal_default_value = None
61
+ cpti = self.compu_phys_to_internal
62
+ if cpti is not None and (cdv := cpti.compu_default_value) is not None:
63
+ self._compu_internal_default_value = self.internal_type.from_string(odxrequire(cdv.v))
30
64
 
31
65
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
32
- matching_scales = [x for x in self.internal_to_phys if x.compu_const == physical_value]
33
- for scale in matching_scales:
34
- if scale.compu_inverse_value is not None:
35
- return scale.compu_inverse_value
36
- elif scale.lower_limit is not None and scale.lower_limit._value is not None:
37
- return scale.lower_limit._value
38
- elif scale.upper_limit is not None and scale.upper_limit._value is not None:
39
- return scale.upper_limit._value
40
-
41
- if self.compu_default_value is not None and self.compu_default_value.compu_inverse_value is not None:
42
- return self.compu_default_value.compu_inverse_value
43
-
44
- raise EncodeError(f"Texttable compu method could not encode '{physical_value!r}'.")
45
-
46
- def __is_internal_in_scale(self, internal_value: AtomicOdxType, scale: CompuScale) -> bool:
47
- if scale == self.compu_default_value:
48
- return True
66
+ scales = []
67
+ if (citp := self.compu_internal_to_phys) is not None:
68
+ scales = citp.compu_scales
69
+ matching_scales = [
70
+ x for x in scales if x.compu_const is not None and x.compu_const.value == physical_value
71
+ ]
72
+
73
+ if len(matching_scales) == 0:
74
+ if self._compu_internal_default_value is None:
75
+ odxraise(f"Texttable could not encode {physical_value!r}.", EncodeError)
76
+ return cast(None, AtomicOdxType)
77
+
78
+ return self._compu_internal_default_value
79
+ elif len(matching_scales) > 1:
80
+ odxraise(f"Texttable could not uniquely encode {physical_value!r}.", EncodeError)
49
81
 
50
- return scale.applies(internal_value)
82
+ scale = matching_scales[0]
83
+ if scale.compu_inverse_value is not None and (civ :=
84
+ scale.compu_inverse_value.value) is not None:
85
+ return civ
86
+ elif scale.lower_limit is not None and scale.lower_limit._value is not None:
87
+ return scale.lower_limit._value
88
+ elif scale.upper_limit is not None and scale.upper_limit._value is not None:
89
+ return scale.upper_limit._value
90
+
91
+ odxraise(f"Texttable compu method could not encode '{physical_value!r}'.", EncodeError)
51
92
 
52
93
  def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
53
- matching_scales = [x for x in self.internal_to_phys if x.applies(internal_value)]
94
+ scales = []
95
+ if (citp := self.compu_internal_to_phys) is not None:
96
+ scales = citp.compu_scales
97
+ matching_scales: List[CompuScale] = [x for x in scales if x.applies(internal_value)]
98
+
54
99
  if len(matching_scales) == 0:
55
- if self.compu_default_value is None or self.compu_default_value.compu_const is None:
100
+ if self._compu_physical_default_value is None:
56
101
  odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
57
102
  return cast(None, AtomicOdxType)
58
103
 
59
- return self.compu_default_value.compu_const
104
+ return self._compu_physical_default_value
60
105
 
61
- if len(matching_scales) != 1 or matching_scales[0].compu_const is None:
62
- odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
106
+ if len(matching_scales) > 1:
107
+ odxraise(f"Texttable could not uniquely decode {internal_value!r}.", DecodeError)
63
108
 
64
- return matching_scales[0].compu_const
109
+ scale = matching_scales[0]
110
+
111
+ if scale.compu_const is None:
112
+ odxraise(f"Encountered a COMPU-SCALE with no COMPU-CONST.")
113
+ return cast(None, AtomicOdxType)
114
+
115
+ if scale.compu_const.value is not None:
116
+ return scale.compu_const.value
117
+
118
+ odxraise(f"Texttable compu method could not decode '{internal_value!r}'.", EncodeError)
65
119
 
66
120
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
67
- if self.compu_default_value is not None:
121
+ if self._compu_physical_default_value is not None:
68
122
  return True
69
123
 
70
- return any(x.compu_const == physical_value for x in self.internal_to_phys)
124
+ scales = []
125
+ if (cpti := self.compu_internal_to_phys) is not None:
126
+ scales = cpti.compu_scales
127
+
128
+ return any(scale.compu_const.value == physical_value
129
+ for scale in scales
130
+ if scale.compu_const is not None)
71
131
 
72
132
  def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
73
- if self.compu_default_value is not None:
133
+ if self._compu_internal_default_value is not None:
74
134
  return True
75
135
 
76
- return any(scale.applies(internal_value) for scale in self.internal_to_phys)
136
+ scales = []
137
+ if (citp := self.compu_internal_to_phys) is not None:
138
+ scales = citp.compu_scales
139
+
140
+ return any(scale.applies(internal_value) for scale in scales)
@@ -1,85 +1,28 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from typing import List, cast
2
+ from typing import List
3
3
  from xml.etree import ElementTree
4
4
 
5
5
  from .diagcodedtype import DiagCodedType
6
- from .exceptions import odxraise, odxrequire
6
+ from .exceptions import odxraise
7
7
  from .globals import xsi
8
8
  from .leadinglengthinfotype import LeadingLengthInfoType
9
9
  from .minmaxlengthtype import MinMaxLengthType
10
- from .odxlink import OdxDocFragment, OdxLinkRef
11
- from .odxtypes import DataType, odxstr_to_bool
10
+ from .odxlink import OdxDocFragment
12
11
  from .paramlengthinfotype import ParamLengthInfoType
13
12
  from .standardlengthtype import StandardLengthType
14
13
 
15
14
 
16
15
  def create_any_diag_coded_type_from_et(et_element: ElementTree.Element,
17
16
  doc_frags: List[OdxDocFragment]) -> DiagCodedType:
18
- base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
19
-
20
- base_data_type_str = odxrequire(et_element.get("BASE-DATA-TYPE"))
21
- try:
22
- base_data_type = DataType(base_data_type_str)
23
- except ValueError:
24
- base_data_type = cast(DataType, None)
25
- odxraise(f"Unknown base data type {base_data_type_str}")
26
-
27
- is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
28
-
29
17
  dct_type = et_element.get(f"{xsi}type")
30
- bit_length = None
31
18
  if dct_type == "LEADING-LENGTH-INFO-TYPE":
32
- bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
33
- return LeadingLengthInfoType(
34
- base_data_type=base_data_type,
35
- bit_length=bit_length,
36
- base_type_encoding=base_type_encoding,
37
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
38
- )
19
+ return LeadingLengthInfoType.from_et(et_element, doc_frags)
39
20
  elif dct_type == "MIN-MAX-LENGTH-TYPE":
40
- min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
41
- max_length = None
42
- if et_element.find("MAX-LENGTH") is not None:
43
- max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
44
- termination = odxrequire(et_element.get("TERMINATION"))
45
-
46
- return MinMaxLengthType(
47
- base_data_type=base_data_type,
48
- min_length=min_length,
49
- max_length=max_length,
50
- termination=termination,
51
- base_type_encoding=base_type_encoding,
52
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
53
- )
21
+ return MinMaxLengthType.from_et(et_element, doc_frags)
54
22
  elif dct_type == "PARAM-LENGTH-INFO-TYPE":
55
- length_key_ref = odxrequire(
56
- OdxLinkRef.from_et(et_element.find("LENGTH-KEY-REF"), doc_frags))
57
-
58
- return ParamLengthInfoType(
59
- base_data_type=base_data_type,
60
- length_key_ref=length_key_ref,
61
- base_type_encoding=base_type_encoding,
62
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
63
- )
23
+ return ParamLengthInfoType.from_et(et_element, doc_frags)
64
24
  elif dct_type == "STANDARD-LENGTH-TYPE":
65
- bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
66
- bit_mask = None
67
- if (bit_mask_str := et_element.findtext("BIT-MASK")) is not None:
68
- # The XSD uses the type xsd:hexBinary
69
- # xsd:hexBinary allows for leading/trailing whitespace, empty strings, and it only allows an even
70
- # number of hex digits, while some of the examples shown in the ODX specification exhibit an
71
- # odd number of hex digits.
72
- # This causes a validation paradox, so we try to be flexible
73
- bit_mask_str = bit_mask_str.strip()
74
- if len(bit_mask_str):
75
- bit_mask = int(bit_mask_str, 16)
76
- is_condensed_raw = odxstr_to_bool(et_element.get("CONDENSED"))
77
- return StandardLengthType(
78
- base_data_type=base_data_type,
79
- bit_length=bit_length,
80
- bit_mask=bit_mask,
81
- is_condensed_raw=is_condensed_raw,
82
- base_type_encoding=base_type_encoding,
83
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
84
- )
85
- raise NotImplementedError(f"I do not know the diag-coded-type {dct_type}")
25
+ return StandardLengthType.from_et(et_element, doc_frags)
26
+
27
+ odxraise(f"Unknown DIAG-CODED-TYPE {dct_type}", NotImplementedError)
28
+ return DiagCodedType.from_et(et_element, doc_frags)