odxtools 8.1.0__py3-none-any.whl → 8.2.1__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 (43) hide show
  1. odxtools/audience.py +7 -8
  2. odxtools/compumethods/compucodecompumethod.py +63 -0
  3. odxtools/compumethods/compuinternaltophys.py +19 -2
  4. odxtools/compumethods/compumethod.py +28 -2
  5. odxtools/compumethods/compuphystointernal.py +19 -2
  6. odxtools/compumethods/createanycompumethod.py +12 -0
  7. odxtools/compumethods/linearcompumethod.py +2 -2
  8. odxtools/compumethods/ratfunccompumethod.py +106 -0
  9. odxtools/compumethods/ratfuncsegment.py +87 -0
  10. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  11. odxtools/dataobjectproperty.py +3 -0
  12. odxtools/diaglayers/basevariantraw.py +7 -1
  13. odxtools/diaglayers/diaglayer.py +5 -0
  14. odxtools/diaglayers/diaglayerraw.py +13 -1
  15. odxtools/diaglayers/ecushareddataraw.py +7 -1
  16. odxtools/diaglayers/ecuvariantraw.py +7 -1
  17. odxtools/diaglayers/functionalgroupraw.py +7 -1
  18. odxtools/diaglayers/hierarchyelementraw.py +7 -1
  19. odxtools/diaglayers/protocolraw.py +7 -1
  20. odxtools/diagservice.py +3 -1
  21. odxtools/dtcdop.py +6 -0
  22. odxtools/environmentdatadescription.py +11 -6
  23. odxtools/library.py +66 -0
  24. odxtools/minmaxlengthtype.py +1 -1
  25. odxtools/multiplexer.py +4 -4
  26. odxtools/multiplexercase.py +1 -1
  27. odxtools/odxtypes.py +14 -0
  28. odxtools/parameterinfo.py +132 -76
  29. odxtools/parameters/systemparameter.py +51 -8
  30. odxtools/progcode.py +8 -4
  31. odxtools/standardlengthtype.py +4 -1
  32. odxtools/templates/macros/printCompuMethod.xml.jinja2 +3 -2
  33. odxtools/templates/macros/printDiagLayer.xml.jinja2 +8 -0
  34. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  35. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -23
  36. odxtools/unitspec.py +10 -9
  37. odxtools/version.py +2 -2
  38. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/METADATA +1 -1
  39. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/RECORD +43 -37
  40. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/WHEEL +1 -1
  41. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/LICENSE +0 -0
  42. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/entry_points.txt +0 -0
  43. {odxtools-8.1.0.dist-info → odxtools-8.2.1.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from ..diagservice import DiagService
13
13
  from ..element import IdentifiableElement
14
14
  from ..exceptions import odxassert, odxraise, odxrequire
15
15
  from ..functionalclass import FunctionalClass
16
+ from ..library import Library
16
17
  from ..nameditemlist import NamedItemList
17
18
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
19
  from ..request import Request
@@ -47,7 +48,7 @@ class DiagLayerRaw(IdentifiableElement):
47
48
  state_charts: NamedItemList[StateChart]
48
49
  additional_audiences: NamedItemList[AdditionalAudience]
49
50
  # sub_components: List[DiagLayer] # TODO
50
- # libraries: List[DiagLayer] # TODO
51
+ libraries: NamedItemList[Library]
51
52
  sdgs: List[SpecialDataGroup]
52
53
 
53
54
  @property
@@ -149,6 +150,10 @@ class DiagLayerRaw(IdentifiableElement):
149
150
  for el in et_element.iterfind("ADDITIONAL-AUDIENCES/ADDITIONAL-AUDIENCE")
150
151
  ]
151
152
 
153
+ libraries = [
154
+ Library.from_et(el, doc_frags) for el in et_element.iterfind("LIBRARYS/LIBRARY")
155
+ ]
156
+
152
157
  sdgs = [
153
158
  SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
154
159
  ]
@@ -168,6 +173,7 @@ class DiagLayerRaw(IdentifiableElement):
168
173
  import_refs=import_refs,
169
174
  state_charts=NamedItemList(state_charts),
170
175
  additional_audiences=NamedItemList(additional_audiences),
176
+ libraries=NamedItemList(libraries),
171
177
  sdgs=sdgs,
172
178
  **kwargs)
173
179
 
@@ -200,6 +206,8 @@ class DiagLayerRaw(IdentifiableElement):
200
206
  odxlinks.update(state_chart._build_odxlinks())
201
207
  for additional_audience in self.additional_audiences:
202
208
  odxlinks.update(additional_audience._build_odxlinks())
209
+ for library in self.libraries:
210
+ odxlinks.update(library._build_odxlinks())
203
211
  for sdg in self.sdgs:
204
212
  odxlinks.update(sdg._build_odxlinks())
205
213
 
@@ -252,6 +260,8 @@ class DiagLayerRaw(IdentifiableElement):
252
260
  state_chart._resolve_odxlinks(odxlinks)
253
261
  for additional_audience in self.additional_audiences:
254
262
  additional_audience._resolve_odxlinks(odxlinks)
263
+ for library in self.libraries:
264
+ library._resolve_odxlinks(odxlinks)
255
265
  for sdg in self.sdgs:
256
266
  sdg._resolve_odxlinks(odxlinks)
257
267
 
@@ -282,5 +292,7 @@ class DiagLayerRaw(IdentifiableElement):
282
292
  state_chart._resolve_snrefs(context)
283
293
  for additional_audience in self.additional_audiences:
284
294
  additional_audience._resolve_snrefs(context)
295
+ for library in self.libraries:
296
+ library._resolve_snrefs(context)
285
297
  for sdg in self.sdgs:
286
298
  sdg._resolve_snrefs(context)
@@ -28,7 +28,13 @@ class EcuSharedDataRaw(DiagLayerRaw):
28
28
  @staticmethod
29
29
  def from_et(et_element: ElementTree.Element,
30
30
  doc_frags: List[OdxDocFragment]) -> "EcuSharedDataRaw":
31
- kwargs = dataclass_fields_asdict(DiagLayerRaw.from_et(et_element, doc_frags))
31
+ # objects contained by diagnostic layers exibit an additional
32
+ # document fragment for the diag layer, so we use the document
33
+ # fragments of the odx id of the diag layer for IDs of
34
+ # contained objects.
35
+ dlr = DiagLayerRaw.from_et(et_element, doc_frags)
36
+ kwargs = dataclass_fields_asdict(dlr)
37
+ doc_frags = dlr.odx_id.doc_fragments
32
38
 
33
39
  diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
34
40
  if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
@@ -31,7 +31,13 @@ class EcuVariantRaw(HierarchyElementRaw):
31
31
  @staticmethod
32
32
  def from_et(et_element: ElementTree.Element,
33
33
  doc_frags: List[OdxDocFragment]) -> "EcuVariantRaw":
34
- kwargs = dataclass_fields_asdict(HierarchyElementRaw.from_et(et_element, doc_frags))
34
+ # objects contained by diagnostic layers exibit an additional
35
+ # document fragment for the diag layer, so we use the document
36
+ # fragments of the odx id of the diag layer for IDs of
37
+ # contained objects.
38
+ her = HierarchyElementRaw.from_et(et_element, doc_frags)
39
+ kwargs = dataclass_fields_asdict(her)
40
+ doc_frags = her.odx_id.doc_fragments
35
41
 
36
42
  diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
37
43
  if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
@@ -30,7 +30,13 @@ class FunctionalGroupRaw(HierarchyElementRaw):
30
30
  @staticmethod
31
31
  def from_et(et_element: ElementTree.Element,
32
32
  doc_frags: List[OdxDocFragment]) -> "FunctionalGroupRaw":
33
- kwargs = dataclass_fields_asdict(HierarchyElementRaw.from_et(et_element, doc_frags))
33
+ # objects contained by diagnostic layers exibit an additional
34
+ # document fragment for the diag layer, so we use the document
35
+ # fragments of the odx id of the diag layer for IDs of
36
+ # contained objects.
37
+ her = HierarchyElementRaw.from_et(et_element, doc_frags)
38
+ kwargs = dataclass_fields_asdict(her)
39
+ doc_frags = her.odx_id.doc_fragments
34
40
 
35
41
  diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
36
42
  if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
@@ -22,7 +22,13 @@ class HierarchyElementRaw(DiagLayerRaw):
22
22
  @staticmethod
23
23
  def from_et(et_element: ElementTree.Element,
24
24
  doc_frags: List[OdxDocFragment]) -> "HierarchyElementRaw":
25
- kwargs = dataclass_fields_asdict(DiagLayerRaw.from_et(et_element, doc_frags))
25
+ # objects contained by diagnostic layers exibit an additional
26
+ # document fragment for the diag layer, so we use the document
27
+ # fragments of the odx id of the diag layer for IDs of
28
+ # contained objects.
29
+ dlr = DiagLayerRaw.from_et(et_element, doc_frags)
30
+ kwargs = dataclass_fields_asdict(dlr)
31
+ doc_frags = dlr.odx_id.doc_fragments
26
32
 
27
33
  comparam_refs = [
28
34
  ComparamInstance.from_et(el, doc_frags)
@@ -37,7 +37,13 @@ class ProtocolRaw(HierarchyElementRaw):
37
37
 
38
38
  @staticmethod
39
39
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ProtocolRaw":
40
- kwargs = dataclass_fields_asdict(HierarchyElementRaw.from_et(et_element, doc_frags))
40
+ # objects contained by diagnostic layers exibit an additional
41
+ # document fragment for the diag layer, so we use the document
42
+ # fragments of the odx id of the diag layer for IDs of
43
+ # contained objects.
44
+ her = HierarchyElementRaw.from_et(et_element, doc_frags)
45
+ kwargs = dataclass_fields_asdict(her)
46
+ doc_frags = her.odx_id.doc_fragments
41
47
 
42
48
  comparam_spec_ref = OdxLinkRef.from_et(
43
49
  odxrequire(et_element.find("COMPARAM-SPEC-REF")), doc_frags)
odxtools/diagservice.py CHANGED
@@ -42,7 +42,9 @@ class DiagService(DiagComm):
42
42
  pos_response_refs: List[OdxLinkRef]
43
43
  neg_response_refs: List[OdxLinkRef]
44
44
 
45
- # TODO: pos_response_suppressable: Optional[PosResponseSuppressable] # (sic!)
45
+ # note that the spec has a typo here: it calls the corresponding
46
+ # XML tag POS-RESPONSE-SUPPRESSABLE...
47
+ # TODO: pos_response_suppressible: Optional[PosResponseSuppressible]
46
48
 
47
49
  is_cyclic_raw: Optional[bool]
48
50
  is_multiple_raw: Optional[bool]
odxtools/dtcdop.py CHANGED
@@ -178,6 +178,8 @@ class DtcDop(DopBase):
178
178
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
179
179
  odxlinks = super()._build_odxlinks()
180
180
 
181
+ odxlinks.update(self.compu_method._build_odxlinks())
182
+
181
183
  for dtc_proxy in self.dtcs_raw:
182
184
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
183
185
  odxlinks.update(dtc_proxy._build_odxlinks())
@@ -187,6 +189,8 @@ class DtcDop(DopBase):
187
189
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
188
190
  super()._resolve_odxlinks(odxlinks)
189
191
 
192
+ self.compu_method._resolve_odxlinks(odxlinks)
193
+
190
194
  self._dtcs = NamedItemList[DiagnosticTroubleCode]()
191
195
  for dtc_proxy in self.dtcs_raw:
192
196
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
@@ -202,6 +206,8 @@ class DtcDop(DopBase):
202
206
  def _resolve_snrefs(self, context: SnRefContext) -> None:
203
207
  super()._resolve_snrefs(context)
204
208
 
209
+ self.compu_method._resolve_snrefs(context)
210
+
205
211
  for dtc_proxy in self.dtcs_raw:
206
212
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
207
213
  dtc_proxy._resolve_snrefs(context)
@@ -11,6 +11,7 @@ from .dtcdop import DtcDop
11
11
  from .encodestate import EncodeState
12
12
  from .environmentdata import EnvironmentData
13
13
  from .exceptions import odxraise, odxrequire
14
+ from .nameditemlist import NamedItemList
14
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
15
16
  from .odxtypes import ParameterValue, ParameterValueDict
16
17
  from .parameters.parameter import Parameter
@@ -35,7 +36,7 @@ class EnvironmentDataDescription(ComplexDop):
35
36
  # in ODX 2.0.0, ENV-DATAS seems to be a mandatory
36
37
  # sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
37
38
  # present
38
- env_datas: List[EnvironmentData]
39
+ env_datas: NamedItemList[EnvironmentData]
39
40
  env_data_refs: List[OdxLinkRef]
40
41
 
41
42
  @property
@@ -62,16 +63,20 @@ class EnvironmentDataDescription(ComplexDop):
62
63
  param_snpathref = None
63
64
  if (param_snpathref_elem := et_element.find("PARAM-SNPATHREF")) is not None:
64
65
  param_snpathref = odxrequire(param_snpathref_elem.get("SHORT-NAME-PATH"))
66
+
67
+ # ODX 2.0 mandates ENV-DATA-DESC to contain a list of
68
+ # ENV-DATAS and no ENV-DATA-REFS while for ODX 2.2 the
69
+ # situation is reversed. This means that we will create one
70
+ # empty and one non-empty list here. (Which is which depends
71
+ # on the version of the standard used by the file.)
65
72
  env_data_refs = [
66
73
  odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
67
74
  for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
68
75
  ]
69
-
70
- # ODX 2.0.0 says ENV-DATA-DESC could contain a list of ENV-DATAS
71
- env_datas = [
76
+ env_datas = NamedItemList([
72
77
  EnvironmentData.from_et(env_data_elem, doc_frags)
73
78
  for env_data_elem in et_element.iterfind("ENV-DATAS/ENV-DATA")
74
- ]
79
+ ])
75
80
 
76
81
  return EnvironmentDataDescription(
77
82
  param_snref=param_snref,
@@ -93,7 +98,7 @@ class EnvironmentDataDescription(ComplexDop):
93
98
  # ODX 2.0 specifies environment data objects here, ODX 2.2
94
99
  # uses references
95
100
  if self.env_data_refs:
96
- self.env_datas = [odxlinks.resolve(x) for x in self.env_data_refs]
101
+ self.env_datas = NamedItemList([odxlinks.resolve(x) for x in self.env_data_refs])
97
102
  else:
98
103
  for ed in self.env_datas:
99
104
  ed._resolve_odxlinks(odxlinks)
odxtools/library.py ADDED
@@ -0,0 +1,66 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional, cast
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import IdentifiableElement
7
+ from .exceptions import odxraise, odxrequire
8
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
+ from .snrefcontext import SnRefContext
10
+ from .utils import dataclass_fields_asdict
11
+
12
+
13
+ @dataclass
14
+ class Library(IdentifiableElement):
15
+ """
16
+ A library defines a shared library used for single ECU jobs etc.
17
+
18
+ It this is basically equivalent to ProgCode.
19
+ """
20
+
21
+ code_file: str
22
+ encryption: Optional[str]
23
+ syntax: str
24
+ revision: str
25
+ entrypoint: Optional[str]
26
+
27
+ @property
28
+ def code(self) -> bytes:
29
+ return self._code
30
+
31
+ @staticmethod
32
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Library":
33
+
34
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
35
+
36
+ code_file = odxrequire(et_element.findtext("CODE-FILE"))
37
+ encryption = et_element.findtext("ENCRYPTION")
38
+ syntax = odxrequire(et_element.findtext("SYNTAX"))
39
+ revision = odxrequire(et_element.findtext("REVISION"))
40
+ entrypoint = et_element.findtext("ENTRYPOINT")
41
+
42
+ return Library(
43
+ code_file=code_file,
44
+ encryption=encryption,
45
+ syntax=syntax,
46
+ revision=revision,
47
+ entrypoint=entrypoint,
48
+ **kwargs)
49
+
50
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
51
+ return {self.odx_id: self}
52
+
53
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
54
+ pass
55
+
56
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
57
+ aux_file = odxrequire(context.database).auxiliary_files.get(self.code_file)
58
+
59
+ if aux_file is None:
60
+ odxraise(f"Reference to auxiliary file '{self.code_file}' "
61
+ f"could not be resolved")
62
+ self._code: bytes = cast(bytes, None)
63
+ return
64
+
65
+ self._code = aux_file.read()
66
+ aux_file.seek(0)
@@ -74,7 +74,7 @@ class MinMaxLengthType(DiagCodedType):
74
74
  @override
75
75
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
76
76
 
77
- if not isinstance(internal_value, (bytes, str)):
77
+ if not isinstance(internal_value, (bytes, str, bytearray)):
78
78
  odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
79
79
  EncodeError)
80
80
 
odxtools/multiplexer.py CHANGED
@@ -12,6 +12,7 @@ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequir
12
12
  from .multiplexercase import MultiplexerCase
13
13
  from .multiplexerdefaultcase import MultiplexerDefaultCase
14
14
  from .multiplexerswitchkey import MultiplexerSwitchKey
15
+ from .nameditemlist import NamedItemList
15
16
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
17
  from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
17
18
  from .snrefcontext import SnRefContext
@@ -30,7 +31,7 @@ class Multiplexer(ComplexDop):
30
31
  byte_position: int
31
32
  switch_key: MultiplexerSwitchKey
32
33
  default_case: Optional[MultiplexerDefaultCase]
33
- cases: List[MultiplexerCase]
34
+ cases: NamedItemList[MultiplexerCase]
34
35
  is_visible_raw: Optional[bool]
35
36
 
36
37
  @staticmethod
@@ -48,9 +49,8 @@ class Multiplexer(ComplexDop):
48
49
  if (dc_elem := et_element.find("DEFAULT-CASE")) is not None:
49
50
  default_case = MultiplexerDefaultCase.from_et(dc_elem, doc_frags)
50
51
 
51
- cases = []
52
- if (cases_elem := et_element.find("CASES")) is not None:
53
- cases = [MultiplexerCase.from_et(el, doc_frags) for el in cases_elem.iterfind("CASE")]
52
+ cases = NamedItemList(
53
+ [MultiplexerCase.from_et(el, doc_frags) for el in et_element.iterfind("CASES/CASE")])
54
54
 
55
55
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
56
56
 
@@ -15,7 +15,7 @@ from .utils import dataclass_fields_asdict
15
15
 
16
16
  @dataclass
17
17
  class MultiplexerCase(NamedElement):
18
- """This class represents a Case which represents multiple options in a Multiplexer."""
18
+ """This class represents a case which represents a range of keys of a multiplexer."""
19
19
 
20
20
  structure_ref: Optional[OdxLinkRef]
21
21
  structure_snref: Optional[str]
odxtools/odxtypes.py CHANGED
@@ -241,3 +241,17 @@ class DataType(Enum):
241
241
  return True
242
242
  else:
243
243
  return False
244
+
245
+ def __str__(self) -> str:
246
+ if self == DataType.A_INT32:
247
+ return "int"
248
+ elif self == DataType.A_UINT32:
249
+ return "uint"
250
+ elif self in (DataType.A_FLOAT32, DataType.A_FLOAT64):
251
+ return "float"
252
+ elif self == DataType.A_BYTEFIELD:
253
+ return "bytefield"
254
+ elif self in (DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING, DataType.A_UTF8STRING):
255
+ return "string"
256
+ else:
257
+ return f"<unknown type '{self.value}'>"
odxtools/parameterinfo.py CHANGED
@@ -3,9 +3,15 @@ import textwrap
3
3
  from io import StringIO
4
4
  from typing import Iterable
5
5
 
6
+ from .compumethods.compucodecompumethod import CompuCodeCompuMethod
6
7
  from .compumethods.identicalcompumethod import IdenticalCompuMethod
7
8
  from .compumethods.limit import IntervalType
8
9
  from .compumethods.linearcompumethod import LinearCompuMethod
10
+ from .compumethods.linearsegment import LinearSegment
11
+ from .compumethods.ratfunccompumethod import RatFuncCompuMethod
12
+ from .compumethods.ratfuncsegment import RatFuncSegment
13
+ from .compumethods.scalelinearcompumethod import ScaleLinearCompuMethod
14
+ from .compumethods.scaleratfunccompumethod import ScaleRatFuncCompuMethod
9
15
  from .compumethods.texttablecompumethod import TexttableCompuMethod
10
16
  from .dataobjectproperty import DataObjectProperty
11
17
  from .dtcdop import DtcDop
@@ -13,19 +19,55 @@ from .dynamiclengthfield import DynamicLengthField
13
19
  from .endofpdufield import EndOfPduField
14
20
  from .exceptions import odxrequire
15
21
  from .multiplexer import Multiplexer
16
- from .odxtypes import DataType
17
22
  from .parameters.codedconstparameter import CodedConstParameter
18
23
  from .parameters.matchingrequestparameter import MatchingRequestParameter
19
24
  from .parameters.nrcconstparameter import NrcConstParameter
20
25
  from .parameters.parameter import Parameter
21
26
  from .parameters.parameterwithdop import ParameterWithDOP
22
27
  from .parameters.reservedparameter import ReservedParameter
28
+ from .parameters.systemparameter import SystemParameter
23
29
  from .parameters.tablekeyparameter import TableKeyParameter
24
30
  from .parameters.tablestructparameter import TableStructParameter
25
31
  from .paramlengthinfotype import ParamLengthInfoType
26
32
  from .staticfield import StaticField
27
33
 
28
34
 
35
+ def _get_linear_segment_info(segment: LinearSegment) -> str:
36
+ ll = segment.physical_lower_limit
37
+ ul = segment.physical_upper_limit
38
+ if ll is None or ll.interval_type == IntervalType.INFINITE:
39
+ ll_str = "(-inf"
40
+ else:
41
+ ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
42
+ ll_str = f"{ll_delim}{ll._value!r}"
43
+
44
+ if ul is None or ul.interval_type == IntervalType.INFINITE:
45
+ ul_str = "inf)"
46
+ else:
47
+ ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
48
+ ul_str = f"{ul._value!r}{ul_delim}"
49
+
50
+ return f"{ll_str}, {ul_str}"
51
+
52
+
53
+ def _get_rat_func_segment_info(segment: RatFuncSegment) -> str:
54
+ ll = segment.lower_limit
55
+ ul = segment.upper_limit
56
+ if ll is None or ll.interval_type == IntervalType.INFINITE:
57
+ ll_str = "(-inf"
58
+ else:
59
+ ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
60
+ ll_str = f"{ll_delim}{ll._value!r}"
61
+
62
+ if ul is None or ul.interval_type == IntervalType.INFINITE:
63
+ ul_str = "inf)"
64
+ else:
65
+ ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
66
+ ul_str = f"{ul._value!r}{ul_delim}"
67
+
68
+ return f"{ll_str}, {ul_str}"
69
+
70
+
29
71
  def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False) -> str:
30
72
  q = "'" if quoted_names else ""
31
73
  of = StringIO()
@@ -42,6 +84,11 @@ def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False)
42
84
  elif isinstance(param, ReservedParameter):
43
85
  of.write(f"{q}{param.short_name}{q}: <reserved>\n")
44
86
  continue
87
+ elif isinstance(param, SystemParameter):
88
+ of.write(
89
+ f"{q}{param.short_name}{q}: <system; kind = \"{param.sysparam}\">; required = {param.is_required}\n"
90
+ )
91
+ continue
45
92
  elif isinstance(param, TableKeyParameter):
46
93
  of.write(
47
94
  f"{q}{param.short_name}{q}: <optional> table key; table = '{param.table.short_name}'; choices:\n"
@@ -69,7 +116,10 @@ def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False)
69
116
  continue
70
117
 
71
118
  dop = param.dop
72
- if isinstance(dop, EndOfPduField):
119
+ if dop is None:
120
+ of.write("{q}{param.short_name}{q}: <no DOP>\n")
121
+ continue
122
+ elif isinstance(dop, EndOfPduField):
73
123
  of.write(f"{q}{param.short_name}{q}: list({{\n")
74
124
  of.write(textwrap.indent(parameter_info(dop.structure.parameters, True), " "))
75
125
  of.write(f"}})\n")
@@ -112,89 +162,95 @@ def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False)
112
162
  of.write(textwrap.indent(parameter_info(struc.parameters, True), " "))
113
163
  of.write(f" }})\n")
114
164
  continue
165
+ elif isinstance(dop, DataObjectProperty):
166
+ # a "simple" DOP
167
+ if (cm := dop.compu_method) is None:
168
+ of.write(f"{q}{param.short_name}{q}: <no compu method>\n")
169
+ continue
115
170
 
116
- of.write(f"{q}{param.short_name}{q}")
171
+ if isinstance(cm, TexttableCompuMethod):
172
+ of.write(f"{q}{param.short_name}{q}: enum; choices:\n")
173
+ for scale in odxrequire(cm.compu_internal_to_phys).compu_scales:
174
+ val_str = ""
175
+ if scale.lower_limit is not None:
176
+ val_str = f"({repr(scale.lower_limit.value)})"
117
177
 
118
- if dop is None:
119
- of.write(": <no DOP>\n")
120
- continue
121
- elif not isinstance(dop, DataObjectProperty):
122
- of.write(f": <unhandled DOP '{type(dop).__name__}'>\n")
123
- continue
178
+ if scale.compu_const is None:
179
+ of.write(f" <ERROR in ODX data: no value specified>\n")
180
+ else:
181
+ vt = scale.compu_const.vt
182
+ v = scale.compu_const.v
183
+ if vt is not None:
184
+ of.write(f" \"{vt}\" {val_str}\n")
185
+ else:
186
+ of.write(f" {v}\n")
124
187
 
125
- if (cm := dop.compu_method) is None:
126
- of.write(": <no compu method>\n")
127
- continue
188
+ elif isinstance(cm, IdenticalCompuMethod):
189
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}\n")
128
190
 
129
- if isinstance(cm, TexttableCompuMethod):
130
- of.write(f": enum; choices:\n")
131
- for scale in odxrequire(cm.compu_internal_to_phys).compu_scales:
132
- val_str = ""
133
- if scale.lower_limit is not None:
134
- val_str = f"({repr(scale.lower_limit.value)})"
191
+ elif isinstance(cm, ScaleLinearCompuMethod):
192
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}")
193
+ seg_list = [_get_linear_segment_info(x) for x in cm.segments]
194
+ of.write(f"; ranges = {{ {', '.join(seg_list)} }}")
135
195
 
136
- if scale.compu_const is None:
137
- of.write(f" <ERROR in ODX data: no value specified>\n")
196
+ unit = dop.unit
197
+ unit_str = unit.display_name if unit is not None else None
198
+ if unit_str is not None:
199
+ of.write(f"; unit: {unit_str}")
200
+
201
+ of.write("\n")
202
+
203
+ elif isinstance(cm, LinearCompuMethod):
204
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}")
205
+ of.write(f"; range: {_get_linear_segment_info(cm.segment)}")
206
+
207
+ unit = dop.unit
208
+ unit_str = unit.display_name if unit is not None else None
209
+ if unit_str is not None:
210
+ of.write(f"; unit: {unit_str}")
211
+
212
+ of.write("\n")
213
+
214
+ elif isinstance(cm, ScaleRatFuncCompuMethod):
215
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}")
216
+ if cm._phys_to_int_segments is None:
217
+ of.write("<NOT ENCODABLE>")
138
218
  else:
139
- vt = scale.compu_const.vt
140
- v = scale.compu_const.v
141
- if vt is not None:
142
- of.write(f" \"{vt}\" {val_str}\n")
143
- else:
144
- of.write(f" {v}\n")
145
-
146
- elif isinstance(cm, IdenticalCompuMethod):
147
- bdt = dop.physical_type.base_data_type
148
- if bdt in (DataType.A_UTF8STRING, DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING):
149
- of.write(f": str")
150
- elif bdt == DataType.A_BYTEFIELD:
151
- of.write(f": bytes")
152
- elif bdt.name.startswith("A_FLOAT"):
153
- of.write(f": float")
154
- elif bdt.name.startswith("A_UINT"):
155
- of.write(f": uint")
156
- elif bdt.name.startswith("A_INT"):
157
- of.write(f": int")
158
- else:
159
- of.write(f": <unknown type {{ bdt.name }}>")
160
-
161
- of.write("\n")
162
-
163
- elif isinstance(cm, LinearCompuMethod):
164
- bdt = dop.physical_type.base_data_type
165
- if bdt in (DataType.A_UTF8STRING, DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING):
166
- of.write(f": str")
167
- elif bdt in (DataType.A_BYTEFIELD,):
168
- of.write(f": bytes")
169
- elif bdt.name.startswith("A_FLOAT"):
170
- of.write(f": float")
171
- elif bdt.name.startswith("A_UINT"):
172
- of.write(f": uint")
173
- elif bdt.name.startswith("A_INT"):
174
- of.write(f": int")
175
- else:
176
- of.write(f": <unknown type>")
219
+ seg_list = [_get_rat_func_segment_info(x) for x in cm._phys_to_int_segments]
220
+ of.write(f"; ranges = {{ {', '.join(seg_list)} }}")
177
221
 
178
- ll = cm.segment.physical_lower_limit
179
- ul = cm.segment.physical_upper_limit
180
- if ll is None or ll.interval_type == IntervalType.INFINITE:
181
- ll_str = "(-inf"
182
- else:
183
- ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
184
- ll_str = f"{ll_delim}{ll._value!r}"
222
+ unit = dop.unit
223
+ unit_str = unit.display_name if unit is not None else None
224
+ if unit_str is not None:
225
+ of.write(f"; unit: {unit_str}")
185
226
 
186
- if ul is None or ul.interval_type == IntervalType.INFINITE:
187
- ul_str = "inf)"
188
- else:
189
- ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
190
- ul_str = f"{ul._value!r}{ul_delim}"
191
- of.write(f"; range: {ll_str}, {ul_str}")
227
+ of.write("\n")
228
+
229
+ elif isinstance(cm, RatFuncCompuMethod):
230
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}")
231
+ if cm._phys_to_int_segment is None:
232
+ of.write("<NOT ENCODABLE>")
233
+ else:
234
+ of.write(f"; range: {_get_rat_func_segment_info(cm._phys_to_int_segment)}")
235
+
236
+ unit = dop.unit
237
+ unit_str = unit.display_name if unit is not None else None
238
+ if unit_str is not None:
239
+ of.write(f"; unit: {unit_str}")
192
240
 
193
- unit = dop.unit
194
- unit_str = unit.display_name if unit is not None else None
195
- if unit_str is not None:
196
- of.write(f"; unit: {unit_str}")
241
+ of.write("\n")
197
242
 
198
- of.write("\n")
243
+ elif isinstance(cm, CompuCodeCompuMethod):
244
+ of.write(f"{q}{param.short_name}{q}: {dop.physical_type.base_data_type}")
245
+ of.write(f"; <programmatic translation>")
246
+
247
+ of.write("\n")
248
+
249
+ else:
250
+ of.write(
251
+ f"{q}{param.short_name}{q}: unknown compu method {type(dop.compu_method).__name__}\n"
252
+ )
253
+ else:
254
+ of.write(f"{q}{param.short_name}{q}: <unhandled DOP '{type(dop).__name__}'>\n")
199
255
 
200
256
  return of.getvalue()