odxtools 8.0.6__py3-none-any.whl → 8.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 (66) 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/ecushareddataraw.py +7 -1
  14. odxtools/diaglayers/ecuvariantraw.py +7 -1
  15. odxtools/diaglayers/functionalgroupraw.py +7 -1
  16. odxtools/diaglayers/hierarchyelementraw.py +7 -1
  17. odxtools/diaglayers/protocolraw.py +7 -1
  18. odxtools/dtcdop.py +6 -0
  19. odxtools/element.py +9 -12
  20. odxtools/environmentdatadescription.py +11 -6
  21. odxtools/multiplexer.py +4 -4
  22. odxtools/multiplexercase.py +1 -1
  23. odxtools/odxtypes.py +14 -0
  24. odxtools/outputparam.py +1 -3
  25. odxtools/parameterinfo.py +132 -76
  26. odxtools/parameters/parameter.py +3 -0
  27. odxtools/parameters/systemparameter.py +51 -8
  28. odxtools/physicaldimension.py +1 -4
  29. odxtools/standardlengthtype.py +4 -1
  30. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  31. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  32. odxtools/templates/macros/printComparam.xml.jinja2 +2 -2
  33. odxtools/templates/macros/printDOP.xml.jinja2 +1 -1
  34. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  35. odxtools/templates/macros/printDiagLayer.xml.jinja2 +1 -1
  36. odxtools/templates/macros/printDiagVariable.xml.jinja2 +3 -3
  37. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +1 -1
  38. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  39. odxtools/templates/macros/printElementId.xml.jinja2 +5 -0
  40. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  41. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  42. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  43. odxtools/templates/macros/printMux.xml.jinja2 +1 -1
  44. odxtools/templates/macros/printParam.xml.jinja2 +2 -2
  45. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  46. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  47. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  48. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +1 -1
  49. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  50. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  51. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  52. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  53. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  54. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  55. odxtools/templates/macros/printTable.xml.jinja2 +2 -2
  56. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  57. odxtools/unit.py +1 -3
  58. odxtools/unitspec.py +10 -9
  59. odxtools/version.py +2 -2
  60. odxtools/writepdxfile.py +1 -0
  61. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/METADATA +1 -1
  62. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/RECORD +66 -62
  63. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/WHEEL +1 -1
  64. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/LICENSE +0 -0
  65. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/entry_points.txt +0 -0
  66. {odxtools-8.0.6.dist-info → odxtools-8.2.0.dist-info}/top_level.txt +0 -0
@@ -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/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)
odxtools/element.py CHANGED
@@ -15,10 +15,7 @@ class NamedElement:
15
15
  description: Optional[Description]
16
16
 
17
17
  @staticmethod
18
- def from_et(
19
- et_element: ElementTree.Element,
20
- doc_frags: List[OdxDocFragment],
21
- ) -> "NamedElement":
18
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "NamedElement":
22
19
 
23
20
  return NamedElement(
24
21
  short_name=odxrequire(et_element.findtext("SHORT-NAME")),
@@ -30,15 +27,15 @@ class NamedElement:
30
27
  @dataclass
31
28
  class IdentifiableElement(NamedElement):
32
29
  odx_id: OdxLinkId
30
+ oid: Optional[str]
33
31
 
34
32
  @staticmethod
35
- def from_et(
36
- et_element: ElementTree.Element,
37
- doc_frags: List[OdxDocFragment],
38
- ) -> "IdentifiableElement":
33
+ def from_et(et_element: ElementTree.Element,
34
+ doc_frags: List[OdxDocFragment]) -> "IdentifiableElement":
39
35
 
40
36
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
41
- return IdentifiableElement(
42
- **kwargs,
43
- odx_id=odxrequire(OdxLinkId.from_et(et_element, doc_frags)),
44
- )
37
+
38
+ odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags))
39
+ oid = et_element.get("OID")
40
+
41
+ return IdentifiableElement(**kwargs, odx_id=odx_id, oid=oid)
@@ -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/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/outputparam.py CHANGED
@@ -16,7 +16,6 @@ from .utils import dataclass_fields_asdict
16
16
  @dataclass
17
17
  class OutputParam(IdentifiableElement):
18
18
  dop_base_ref: OdxLinkRef
19
- oid: Optional[str]
20
19
  semantic: Optional[str]
21
20
 
22
21
  @staticmethod
@@ -25,9 +24,8 @@ class OutputParam(IdentifiableElement):
25
24
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
26
25
  dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
27
26
  semantic = et_element.get("SEMANTIC")
28
- oid = et_element.get("OID")
29
27
 
30
- return OutputParam(dop_base_ref=dop_base_ref, semantic=semantic, oid=oid, **kwargs)
28
+ return OutputParam(dop_base_ref=dop_base_ref, semantic=semantic, **kwargs)
31
29
 
32
30
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
33
31
  return {}
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()
@@ -40,6 +40,7 @@ class Parameter(NamedElement):
40
40
  define any non-positionable parameter types.
41
41
 
42
42
  """
43
+ oid: Optional[str]
43
44
  byte_position: Optional[int]
44
45
  bit_position: Optional[int]
45
46
  semantic: Optional[str]
@@ -51,6 +52,7 @@ class Parameter(NamedElement):
51
52
 
52
53
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
53
54
 
55
+ oid = et_element.get("OID")
54
56
  semantic = et_element.get("SEMANTIC")
55
57
  sdgs = [
56
58
  SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
@@ -63,6 +65,7 @@ class Parameter(NamedElement):
63
65
  bit_position = int(bit_position_str) if bit_position_str is not None else None
64
66
 
65
67
  return Parameter(
68
+ oid=oid,
66
69
  byte_position=byte_position,
67
70
  bit_position=bit_position,
68
71
  semantic=semantic,
@@ -1,19 +1,29 @@
1
1
  # SPDX-License-Identifier: MIT
2
+ import getpass
2
3
  from dataclasses import dataclass
4
+ from datetime import datetime
3
5
  from typing import List, Optional
4
6
  from xml.etree import ElementTree
5
7
 
6
8
  from typing_extensions import override
7
9
 
8
- from ..decodestate import DecodeState
9
10
  from ..encodestate import EncodeState
10
- from ..exceptions import odxrequire
11
+ from ..exceptions import odxraise, odxrequire
11
12
  from ..odxlink import OdxDocFragment
12
13
  from ..odxtypes import ParameterValue
13
14
  from ..utils import dataclass_fields_asdict
14
15
  from .parameter import ParameterType
15
16
  from .parameterwithdop import ParameterWithDOP
16
17
 
18
+ # The SYSTEM parameter types mandated by the ODX 2.2 standard. Users
19
+ # are free to specify additional types, but these must be handled
20
+ # (cf. table 5 in section 7.3.5.4 of the ASAM ODX 2.2 specification
21
+ # document.)
22
+ PREDEFINED_SYSPARAM_VALUES = [
23
+ "TIMESTAMP", "SECOND", "MINUTE", "HOUR", "TIMEZONE", "DAY", "WEEK", "MONTH", "YEAR", "CENTURY",
24
+ "TESTERID", "USERID"
25
+ ]
26
+
17
27
 
18
28
  @dataclass
19
29
  class SystemParameter(ParameterWithDOP):
@@ -38,18 +48,51 @@ class SystemParameter(ParameterWithDOP):
38
48
  @property
39
49
  @override
40
50
  def is_required(self) -> bool:
41
- raise NotImplementedError("SystemParameter.is_required is not implemented yet.")
51
+ # if a SYSTEM parameter is not specified explicitly, its value
52
+ # can be determined from the operating system if it is type is
53
+ # predefined
54
+ return self.sysparam not in PREDEFINED_SYSPARAM_VALUES
42
55
 
43
56
  @property
44
57
  @override
45
58
  def is_settable(self) -> bool:
46
- raise NotImplementedError("SystemParameter.is_settable is not implemented yet.")
59
+ return True
47
60
 
48
61
  @override
49
62
  def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
50
63
  encode_state: EncodeState) -> None:
51
- raise NotImplementedError("Encoding a SystemParameter is not implemented yet.")
64
+ if physical_value is None:
65
+ # determine the value to be encoded automatically
66
+ now = datetime.now()
67
+ if self.sysparam == "TIMESTAMP":
68
+ physical_value = round(now.timestamp() * 1000).to_bytes(8, "big")
69
+ elif self.sysparam == "SECOND":
70
+ physical_value = now.second
71
+ elif self.sysparam == "MINUTE":
72
+ physical_value = now.minute
73
+ elif self.sysparam == "HOUR":
74
+ physical_value = now.hour
75
+ elif self.sysparam == "TIMEZONE":
76
+ if (utc_offset := now.astimezone().utcoffset()) is not None:
77
+ physical_value = utc_offset.seconds // 60
78
+ else:
79
+ physical_value = 0
80
+ elif self.sysparam == "DAY":
81
+ physical_value = now.day
82
+ elif self.sysparam == "WEEK":
83
+ physical_value = now.isocalendar()[1]
84
+ elif self.sysparam == "MONTH":
85
+ physical_value = now.month
86
+ elif self.sysparam == "YEAR":
87
+ physical_value = now.year
88
+ elif self.sysparam == "CENTURY":
89
+ physical_value = now.year // 100
90
+ elif self.sysparam == "TESTERID":
91
+ physical_value = "odxtools".encode("latin1")
92
+ elif self.sysparam == "USERID":
93
+ physical_value = getpass.getuser().encode("latin1")
94
+ else:
95
+ odxraise(f"Unknown system parameter type '{self.sysparam}'")
96
+ physical_value = 0
52
97
 
53
- @override
54
- def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
55
- raise NotImplementedError("Decoding SystemParameter is not implemented yet.")
98
+ self.dop.encode_into_pdu(physical_value, encode_state=encode_state)
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
@@ -41,7 +41,6 @@ class PhysicalDimension(IdentifiableElement):
41
41
  )
42
42
  ```
43
43
  """
44
- oid: Optional[str]
45
44
  length_exp: int
46
45
  mass_exp: int
47
46
  time_exp: int
@@ -54,7 +53,6 @@ class PhysicalDimension(IdentifiableElement):
54
53
  def from_et(et_element: ElementTree.Element,
55
54
  doc_frags: List[OdxDocFragment]) -> "PhysicalDimension":
56
55
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
57
- oid = et_element.get("OID")
58
56
 
59
57
  def read_optional_int(element: ElementTree.Element, name: str) -> int:
60
58
  if val_str := element.findtext(name):
@@ -71,7 +69,6 @@ class PhysicalDimension(IdentifiableElement):
71
69
  luminous_intensity_exp = read_optional_int(et_element, "LUMINOUS-INTENSITY-EXP")
72
70
 
73
71
  return PhysicalDimension(
74
- oid=oid,
75
72
  length_exp=length_exp,
76
73
  mass_exp=mass_exp,
77
74
  time_exp=time_exp,
@@ -88,7 +88,10 @@ class StandardLengthType(DiagCodedType):
88
88
  else:
89
89
  sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
90
90
 
91
- return self.bit_mask.to_bytes(sz, endianness)
91
+ max_value = (1 << (sz * 8)) - 1
92
+ bit_mask = self.bit_mask & max_value
93
+
94
+ return bit_mask.to_bytes(sz, endianness)
92
95
 
93
96
  def __apply_mask(self, internal_value: AtomicOdxType) -> AtomicOdxType:
94
97
  if self.bit_mask is None:
@@ -6,7 +6,7 @@
6
6
  {%- import('macros/printElementId.xml.jinja2') as peid %}
7
7
 
8
8
  {%- macro printAdditionalAudience(audience) -%}
9
- <ADDITIONAL-AUDIENCE ID="{{audience.odx_id.local_id}}">
9
+ <ADDITIONAL-AUDIENCE {{-peid.printElementIdAttribs(audience)}}>
10
10
  {{ peid.printElementIdSubtags(audience)|indent(1) }}
11
11
  </ADDITIONAL-AUDIENCE>
12
12
  {%- endmacro -%}