odxtools 8.1.0__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.
- odxtools/audience.py +7 -8
- odxtools/compumethods/compucodecompumethod.py +63 -0
- odxtools/compumethods/compuinternaltophys.py +19 -2
- odxtools/compumethods/compumethod.py +28 -2
- odxtools/compumethods/compuphystointernal.py +19 -2
- odxtools/compumethods/createanycompumethod.py +12 -0
- odxtools/compumethods/linearcompumethod.py +2 -2
- odxtools/compumethods/ratfunccompumethod.py +106 -0
- odxtools/compumethods/ratfuncsegment.py +87 -0
- odxtools/compumethods/scaleratfunccompumethod.py +113 -0
- odxtools/dataobjectproperty.py +3 -0
- odxtools/diaglayers/basevariantraw.py +7 -1
- odxtools/diaglayers/ecushareddataraw.py +7 -1
- odxtools/diaglayers/ecuvariantraw.py +7 -1
- odxtools/diaglayers/functionalgroupraw.py +7 -1
- odxtools/diaglayers/hierarchyelementraw.py +7 -1
- odxtools/diaglayers/protocolraw.py +7 -1
- odxtools/dtcdop.py +6 -0
- odxtools/environmentdatadescription.py +11 -6
- odxtools/multiplexer.py +4 -4
- odxtools/multiplexercase.py +1 -1
- odxtools/odxtypes.py +14 -0
- odxtools/parameterinfo.py +132 -76
- odxtools/parameters/systemparameter.py +51 -8
- odxtools/standardlengthtype.py +4 -1
- odxtools/unitspec.py +10 -9
- odxtools/version.py +2 -2
- {odxtools-8.1.0.dist-info → odxtools-8.2.0.dist-info}/METADATA +1 -1
- {odxtools-8.1.0.dist-info → odxtools-8.2.0.dist-info}/RECORD +33 -29
- {odxtools-8.1.0.dist-info → odxtools-8.2.0.dist-info}/WHEEL +1 -1
- {odxtools-8.1.0.dist-info → odxtools-8.2.0.dist-info}/LICENSE +0 -0
- {odxtools-8.1.0.dist-info → odxtools-8.2.0.dist-info}/entry_points.txt +0 -0
- {odxtools-8.1.0.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
|
-
|
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
|
-
|
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)
|
@@ -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:
|
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:
|
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
|
-
|
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
|
|
odxtools/multiplexercase.py
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
126
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
140
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
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()
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|
odxtools/standardlengthtype.py
CHANGED
@@ -88,7 +88,10 @@ class StandardLengthType(DiagCodedType):
|
|
88
88
|
else:
|
89
89
|
sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
|
90
90
|
|
91
|
-
|
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:
|
odxtools/unitspec.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Any, Dict, List
|
3
|
+
from typing import Any, Dict, List
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from .nameditemlist import NamedItemList
|
@@ -25,9 +25,9 @@ class UnitSpec:
|
|
25
25
|
"""
|
26
26
|
|
27
27
|
# TODO (?): Why are there type errors...
|
28
|
-
unit_groups:
|
29
|
-
units:
|
30
|
-
physical_dimensions:
|
28
|
+
unit_groups: NamedItemList[UnitGroup]
|
29
|
+
units: NamedItemList[Unit]
|
30
|
+
physical_dimensions: NamedItemList[PhysicalDimension]
|
31
31
|
sdgs: List[SpecialDataGroup]
|
32
32
|
|
33
33
|
def __post_init__(self) -> None:
|
@@ -38,14 +38,15 @@ class UnitSpec:
|
|
38
38
|
@staticmethod
|
39
39
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "UnitSpec":
|
40
40
|
|
41
|
-
unit_groups = [
|
41
|
+
unit_groups = NamedItemList([
|
42
42
|
UnitGroup.from_et(el, doc_frags) for el in et_element.iterfind("UNIT-GROUPS/UNIT-GROUP")
|
43
|
-
]
|
44
|
-
units =
|
45
|
-
|
43
|
+
])
|
44
|
+
units = NamedItemList(
|
45
|
+
[Unit.from_et(el, doc_frags) for el in et_element.iterfind("UNITS/UNIT")])
|
46
|
+
physical_dimensions = NamedItemList([
|
46
47
|
PhysicalDimension.from_et(el, doc_frags)
|
47
48
|
for el in et_element.iterfind("PHYSICAL-DIMENSIONS/PHYSICAL-DIMENSION")
|
48
|
-
]
|
49
|
+
])
|
49
50
|
sdgs = [
|
50
51
|
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
51
52
|
]
|
odxtools/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: odxtools
|
3
|
-
Version: 8.
|
3
|
+
Version: 8.2.0
|
4
4
|
Summary: Utilities to work with the ODX standard for automotive diagnostics
|
5
5
|
Author-email: Katrin Bauer <katrin.bauer@mbition.io>, Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>
|
6
6
|
Maintainer-email: Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>
|