odxtools 9.2.0__py3-none-any.whl → 9.4.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/companyrevisioninfo.py +1 -1
- odxtools/comparaminstance.py +1 -6
- odxtools/comparamspec.py +3 -2
- odxtools/comparamsubset.py +3 -5
- odxtools/dataobjectproperty.py +12 -6
- odxtools/decodestate.py +120 -26
- odxtools/description.py +19 -2
- odxtools/diagcodedtype.py +9 -2
- odxtools/diagcomm.py +4 -4
- odxtools/diaglayercontainer.py +2 -2
- odxtools/diaglayers/diaglayerraw.py +2 -2
- odxtools/diaglayers/hierarchyelement.py +2 -0
- odxtools/diagnostictroublecode.py +4 -8
- odxtools/diagservice.py +70 -4
- odxtools/diagvariable.py +45 -7
- odxtools/encodestate.py +147 -51
- odxtools/encoding.py +56 -0
- odxtools/environmentdatadescription.py +77 -47
- odxtools/functionalclass.py +7 -2
- odxtools/inputparam.py +9 -3
- odxtools/leadinglengthinfotype.py +4 -0
- odxtools/minmaxlengthtype.py +57 -36
- odxtools/modification.py +3 -2
- odxtools/odxcategory.py +2 -2
- odxtools/odxlink.py +31 -7
- odxtools/odxtypes.py +1 -1
- odxtools/outputparam.py +8 -3
- odxtools/parameters/matchingrequestparameter.py +1 -0
- odxtools/parameters/physicalconstantparameter.py +1 -0
- odxtools/parameters/reservedparameter.py +1 -0
- odxtools/parameters/tableentryparameter.py +15 -4
- odxtools/parameters/tablekeyparameter.py +20 -17
- odxtools/paramlengthinfotype.py +5 -3
- odxtools/physicaltype.py +2 -1
- odxtools/scaleconstr.py +4 -4
- odxtools/standardlengthtype.py +98 -22
- odxtools/statetransition.py +24 -3
- odxtools/structure.py +10 -2
- odxtools/swvariable.py +3 -1
- odxtools/table.py +55 -3
- odxtools/tablerow.py +91 -8
- odxtools/templates/macros/printDOP.xml.jinja2 +2 -2
- odxtools/templates/macros/printDescription.xml.jinja2 +5 -1
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +1 -1
- odxtools/templates/macros/printDiagVariable.xml.jinja2 +12 -3
- odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -0
- odxtools/templates/macros/printParentRef.xml.jinja2 +27 -0
- odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
- odxtools/templates/macros/printService.xml.jinja2 +30 -0
- odxtools/templates/macros/printStructure.xml.jinja2 +2 -1
- odxtools/templates/macros/printTable.xml.jinja2 +43 -0
- odxtools/templates/macros/printUnitSpec.xml.jinja2 +4 -0
- odxtools/uds.py +2 -2
- odxtools/unit.py +8 -12
- odxtools/unitspec.py +5 -2
- odxtools/utils.py +44 -1
- odxtools/version.py +2 -2
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/METADATA +2 -2
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/RECORD +63 -62
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/WHEEL +1 -1
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/LICENSE +0 -0
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/entry_points.txt +0 -0
- {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/top_level.txt +0 -0
odxtools/diagvariable.py
CHANGED
@@ -9,11 +9,13 @@ from .commrelation import CommRelation
|
|
9
9
|
from .element import IdentifiableElement
|
10
10
|
from .exceptions import odxrequire
|
11
11
|
from .nameditemlist import NamedItemList
|
12
|
-
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
12
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
13
13
|
from .odxtypes import odxstr_to_bool
|
14
14
|
from .snrefcontext import SnRefContext
|
15
15
|
from .specialdatagroup import SpecialDataGroup
|
16
16
|
from .swvariable import SwVariable
|
17
|
+
from .table import Table
|
18
|
+
from .tablerow import TableRow
|
17
19
|
from .utils import dataclass_fields_asdict
|
18
20
|
from .variablegroup import VariableGroup
|
19
21
|
|
@@ -32,15 +34,30 @@ class DiagVariable(IdentifiableElement):
|
|
32
34
|
"""
|
33
35
|
|
34
36
|
admin_data: Optional[AdminData]
|
35
|
-
variable_group_ref: OdxLinkRef
|
37
|
+
variable_group_ref: Optional[OdxLinkRef]
|
36
38
|
sw_variables: List[SwVariable]
|
39
|
+
|
40
|
+
# a diag variable must specify either COMM-RELATIONS or a
|
41
|
+
# reference to a table row
|
37
42
|
comm_relations: List[CommRelation]
|
38
|
-
|
43
|
+
|
44
|
+
# these are nested inside the SNREF-TO-TABLEROW tag
|
45
|
+
table_snref: Optional[str]
|
46
|
+
table_row_snref: Optional[str]
|
47
|
+
|
39
48
|
sdgs: List[SpecialDataGroup]
|
40
49
|
is_read_before_write_raw: Optional[bool]
|
41
50
|
|
42
51
|
@property
|
43
|
-
def
|
52
|
+
def table(self) -> Optional[Table]:
|
53
|
+
return self._table
|
54
|
+
|
55
|
+
@property
|
56
|
+
def table_row(self) -> Optional[TableRow]:
|
57
|
+
return self._table_row
|
58
|
+
|
59
|
+
@property
|
60
|
+
def variable_group(self) -> Optional[VariableGroup]:
|
44
61
|
return self._variable_group
|
45
62
|
|
46
63
|
@property
|
@@ -52,8 +69,7 @@ class DiagVariable(IdentifiableElement):
|
|
52
69
|
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
53
70
|
|
54
71
|
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
55
|
-
variable_group_ref =
|
56
|
-
OdxLinkRef.from_et(et_element.find("VARIABLE-GROUP-REF"), doc_frags))
|
72
|
+
variable_group_ref = OdxLinkRef.from_et(et_element.find("VARIABLE-GROUP-REF"), doc_frags)
|
57
73
|
sw_variables = NamedItemList([
|
58
74
|
SwVariable.from_et(swv_elem, doc_frags)
|
59
75
|
for swv_elem in et_element.iterfind("SW-VARIABLES/SW-VARIABLE")
|
@@ -62,6 +78,16 @@ class DiagVariable(IdentifiableElement):
|
|
62
78
|
CommRelation.from_et(cr_elem, doc_frags)
|
63
79
|
for cr_elem in et_element.iterfind("COMM-RELATIONS/COMM-RELATION")
|
64
80
|
]
|
81
|
+
|
82
|
+
table_snref = None
|
83
|
+
table_row_snref = None
|
84
|
+
if (snref_to_tablerow_elem := et_element.find("SNREF-TO-TABLEROW")) is not None:
|
85
|
+
table_snref_elem = odxrequire(snref_to_tablerow_elem.find("TABLE-SNREF"))
|
86
|
+
table_snref = odxrequire(table_snref_elem.attrib.get("SHORT-NAME"))
|
87
|
+
|
88
|
+
table_row_snref_elem = odxrequire(snref_to_tablerow_elem.find("TABLE-ROW-SNREF"))
|
89
|
+
table_row_snref = odxrequire(table_row_snref_elem.attrib.get("SHORT-NAME"))
|
90
|
+
|
65
91
|
sdgs = [
|
66
92
|
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
67
93
|
]
|
@@ -72,6 +98,8 @@ class DiagVariable(IdentifiableElement):
|
|
72
98
|
variable_group_ref=variable_group_ref,
|
73
99
|
sw_variables=sw_variables,
|
74
100
|
comm_relations=comm_relations,
|
101
|
+
table_snref=table_snref,
|
102
|
+
table_row_snref=table_row_snref,
|
75
103
|
sdgs=sdgs,
|
76
104
|
is_read_before_write_raw=is_read_before_write_raw,
|
77
105
|
**kwargs)
|
@@ -91,7 +119,9 @@ class DiagVariable(IdentifiableElement):
|
|
91
119
|
return result
|
92
120
|
|
93
121
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
94
|
-
self._variable_group =
|
122
|
+
self._variable_group = None
|
123
|
+
if self.variable_group_ref is not None:
|
124
|
+
self._variable_group = odxlinks.resolve(self.variable_group_ref, VariableGroup)
|
95
125
|
|
96
126
|
if self.admin_data is not None:
|
97
127
|
self.admin_data._resolve_odxlinks(odxlinks)
|
@@ -111,3 +141,11 @@ class DiagVariable(IdentifiableElement):
|
|
111
141
|
|
112
142
|
for cr in self.comm_relations:
|
113
143
|
cr._resolve_snrefs(context)
|
144
|
+
|
145
|
+
self._table = None
|
146
|
+
self._table_row = None
|
147
|
+
if self.table_snref is not None:
|
148
|
+
ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
|
149
|
+
self._table = resolve_snref(self.table_snref, ddds.tables, Table)
|
150
|
+
self._table_row = resolve_snref(
|
151
|
+
odxrequire(self.table_row_snref), self._table.table_rows, TableRow)
|
odxtools/encodestate.py
CHANGED
@@ -3,6 +3,7 @@ import warnings
|
|
3
3
|
from dataclasses import dataclass, field
|
4
4
|
from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
|
5
5
|
|
6
|
+
from .encoding import Encoding, get_string_encoding
|
6
7
|
from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
|
7
8
|
from .odxtypes import AtomicOdxType, DataType, ParameterValue
|
8
9
|
|
@@ -86,6 +87,7 @@ class EncodeState:
|
|
86
87
|
internal_value: AtomicOdxType,
|
87
88
|
bit_length: int,
|
88
89
|
base_data_type: DataType,
|
90
|
+
base_type_encoding: Optional[Encoding],
|
89
91
|
is_highlow_byte_order: bool,
|
90
92
|
used_mask: Optional[bytes],
|
91
93
|
) -> None:
|
@@ -93,63 +95,139 @@ class EncodeState:
|
|
93
95
|
|
94
96
|
raw_value: AtomicOdxType
|
95
97
|
|
96
|
-
#
|
98
|
+
# Deal with raw byte fields, ...
|
97
99
|
if base_data_type == DataType.A_BYTEFIELD:
|
98
100
|
if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
|
99
|
-
odxraise()
|
100
|
-
|
101
|
-
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
|
102
|
-
f"({len(internal_value)} bytes)."
|
103
|
-
f" The maximum length is {bit_length//8}.")
|
104
|
-
raw_value = bytes(internal_value)
|
105
|
-
elif base_data_type == DataType.A_ASCIISTRING:
|
106
|
-
if not isinstance(internal_value, str):
|
107
|
-
odxraise()
|
101
|
+
odxraise(f"{internal_value!r} is not a bytefield", EncodeError)
|
102
|
+
return
|
108
103
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
104
|
+
odxassert(
|
105
|
+
base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP),
|
106
|
+
f"Illegal encoding '{base_type_encoding}' for A_BYTEFIELD")
|
107
|
+
|
108
|
+
# note that we do not ensure that BCD-encoded byte fields
|
109
|
+
# only represent "legal" values
|
110
|
+
raw_value = bytes(internal_value)
|
114
111
|
|
115
112
|
if 8 * len(raw_value) > bit_length:
|
116
|
-
|
117
|
-
|
118
|
-
|
113
|
+
odxraise(
|
114
|
+
f"The value '{internal_value!r}' cannot be encoded using "
|
115
|
+
f"{bit_length} bits.", EncodeError)
|
116
|
+
raw_value = raw_value[0:bit_length // 8]
|
117
|
+
|
118
|
+
# ... string types, ...
|
119
|
+
elif base_data_type in (DataType.A_UTF8STRING, DataType.A_ASCIISTRING,
|
120
|
+
DataType.A_UNICODE2STRING):
|
119
121
|
if not isinstance(internal_value, str):
|
120
|
-
odxraise()
|
122
|
+
odxraise(f"The internal value '{internal_value!r}' is not a string", EncodeError)
|
123
|
+
internal_value = str(internal_value)
|
121
124
|
|
122
|
-
|
125
|
+
str_encoding = get_string_encoding(base_data_type, base_type_encoding,
|
126
|
+
is_highlow_byte_order)
|
127
|
+
if str_encoding is not None:
|
128
|
+
raw_value = internal_value.encode(str_encoding)
|
129
|
+
else:
|
130
|
+
raw_value = b""
|
123
131
|
|
124
132
|
if 8 * len(raw_value) > bit_length:
|
125
|
-
|
126
|
-
|
133
|
+
odxraise(
|
134
|
+
f"The value '{internal_value!r}' cannot be encoded using "
|
135
|
+
f"{bit_length} bits.", EncodeError)
|
136
|
+
raw_value = raw_value[0:bit_length // 8]
|
127
137
|
|
128
|
-
|
129
|
-
|
130
|
-
|
138
|
+
# ... signed integers, ...
|
139
|
+
elif base_data_type == DataType.A_INT32:
|
140
|
+
if not isinstance(internal_value, int):
|
141
|
+
odxraise(
|
142
|
+
f"Internal value must be of integer type, not {type(internal_value).__name__}",
|
143
|
+
EncodeError)
|
144
|
+
internal_value = int(internal_value)
|
145
|
+
|
146
|
+
if base_type_encoding == Encoding.ONEC:
|
147
|
+
# one-complement
|
148
|
+
if internal_value >= 0:
|
149
|
+
raw_value = internal_value
|
150
|
+
else:
|
151
|
+
mask = (1 << bit_length) - 1
|
152
|
+
raw_value = mask + internal_value
|
153
|
+
elif base_type_encoding in (None, Encoding.TWOC):
|
154
|
+
# two-complement
|
155
|
+
if internal_value >= 0:
|
156
|
+
raw_value = internal_value
|
157
|
+
else:
|
158
|
+
mask = (1 << bit_length) - 1
|
159
|
+
raw_value = mask + internal_value + 1
|
160
|
+
elif base_type_encoding == Encoding.SM:
|
161
|
+
# sign-magnitude
|
162
|
+
if internal_value >= 0:
|
163
|
+
raw_value = internal_value
|
164
|
+
else:
|
165
|
+
raw_value = (1 << (bit_length - 1)) + abs(internal_value)
|
166
|
+
else:
|
167
|
+
odxraise(
|
168
|
+
f"Illegal encoding ({base_type_encoding and base_type_encoding.value}) specified for "
|
169
|
+
f"{base_data_type.value}")
|
131
170
|
|
132
|
-
|
133
|
-
|
171
|
+
if base_type_encoding == Encoding.BCD_P:
|
172
|
+
raw_value = self.__encode_bcd_p(abs(internal_value))
|
173
|
+
elif base_type_encoding == Encoding.BCD_UP:
|
174
|
+
raw_value = self.__encode_bcd_up(abs(internal_value))
|
175
|
+
else:
|
176
|
+
raw_value = internal_value
|
134
177
|
|
135
|
-
if
|
136
|
-
|
137
|
-
|
178
|
+
if raw_value.bit_length() > bit_length:
|
179
|
+
odxraise(
|
180
|
+
f"The value '{internal_value!r}' cannot be encoded using "
|
181
|
+
f"{bit_length} bits.", EncodeError)
|
182
|
+
raw_value &= (1 << bit_length) - 1
|
183
|
+
|
184
|
+
# ... unsigned integers, ...
|
185
|
+
elif base_data_type == DataType.A_UINT32:
|
186
|
+
if not isinstance(internal_value, int) or internal_value < 0:
|
187
|
+
odxraise(f"Internal value must be a positive integer, not {internal_value!r}")
|
188
|
+
internal_value = abs(int(internal_value))
|
189
|
+
|
190
|
+
if base_type_encoding == Encoding.BCD_P:
|
191
|
+
# packed BCD
|
192
|
+
raw_value = self.__encode_bcd_p(internal_value)
|
193
|
+
elif base_type_encoding == Encoding.BCD_UP:
|
194
|
+
# unpacked BCD
|
195
|
+
raw_value = self.__encode_bcd_up(internal_value)
|
196
|
+
elif base_type_encoding in (None, Encoding.NONE):
|
197
|
+
# no encoding
|
198
|
+
raw_value = internal_value
|
199
|
+
else:
|
200
|
+
odxraise(f"Illegal encoding ({base_type_encoding}) specified for "
|
201
|
+
f"{base_data_type.value}")
|
202
|
+
|
203
|
+
raw_value = internal_value
|
204
|
+
|
205
|
+
if raw_value.bit_length() > bit_length:
|
206
|
+
odxraise(
|
207
|
+
f"The value '{internal_value!r}' cannot be encoded using "
|
208
|
+
f"{bit_length} bits.", EncodeError)
|
209
|
+
raw_value &= (1 << bit_length) - 1
|
210
|
+
|
211
|
+
# ... and others (floating point values)
|
138
212
|
else:
|
139
|
-
|
213
|
+
odxassert(base_data_type in (DataType.A_FLOAT32, DataType.A_FLOAT64))
|
214
|
+
odxassert(base_type_encoding in (None, Encoding.NONE))
|
140
215
|
|
141
|
-
|
216
|
+
if base_data_type == DataType.A_FLOAT32 and bit_length != 32:
|
217
|
+
odxraise(f"Illegal bit length for a float32 object ({bit_length})")
|
218
|
+
bit_length = 32
|
219
|
+
elif base_data_type == DataType.A_FLOAT64 and bit_length != 64:
|
220
|
+
odxraise(f"Illegal bit length for a float64 object ({bit_length})")
|
221
|
+
bit_length = 64
|
222
|
+
|
223
|
+
raw_value = float(internal_value)
|
224
|
+
|
225
|
+
# If the bit length is zero, encode an empty value
|
142
226
|
if bit_length == 0:
|
143
|
-
if (base_data_type.value in [
|
144
|
-
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
145
|
-
] and base_data_type.value != 0):
|
146
|
-
odxraise(
|
147
|
-
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
|
148
|
-
EncodeError)
|
149
227
|
self.emplace_bytes(b'')
|
150
228
|
return
|
151
229
|
|
152
|
-
|
230
|
+
format_char = base_data_type.bitstruct_format_letter
|
153
231
|
padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
|
154
232
|
odxassert((0 <= padding and padding < 8 and
|
155
233
|
(padding + bit_length + self.cursor_bit_position) % 8 == 0),
|
@@ -157,19 +235,17 @@ class EncodeState:
|
|
157
235
|
left_pad = f"p{padding}" if padding > 0 else ""
|
158
236
|
|
159
237
|
# actually encode the value
|
160
|
-
coded = bitstruct.pack(f"{left_pad}{
|
238
|
+
coded = bitstruct.pack(f"{left_pad}{format_char}{bit_length}", raw_value)
|
161
239
|
|
162
240
|
# create the raw mask of used bits for numeric objects
|
163
241
|
used_mask_raw = used_mask
|
164
|
-
if base_data_type in [DataType.A_INT32, DataType.A_UINT32
|
165
|
-
] and (self.cursor_bit_position != 0 or
|
166
|
-
(self.cursor_bit_position + bit_length) % 8 != 0):
|
167
|
-
if used_mask is None:
|
168
|
-
tmp = (1 << bit_length) - 1
|
169
|
-
else:
|
170
|
-
tmp = int.from_bytes(used_mask, "big")
|
171
|
-
tmp <<= self.cursor_bit_position
|
172
242
|
|
243
|
+
if used_mask_raw is None:
|
244
|
+
used_mask_raw = ((1 << bit_length) - 1).to_bytes((bit_length + 7) // 8, "big")
|
245
|
+
|
246
|
+
if self.cursor_bit_position != 0:
|
247
|
+
tmp = int.from_bytes(used_mask_raw, "big")
|
248
|
+
tmp <<= self.cursor_bit_position
|
173
249
|
used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
|
174
250
|
|
175
251
|
# apply byte order to numeric objects
|
@@ -177,9 +253,7 @@ class EncodeState:
|
|
177
253
|
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
178
254
|
]:
|
179
255
|
coded = coded[::-1]
|
180
|
-
|
181
|
-
if used_mask_raw is not None:
|
182
|
-
used_mask_raw = used_mask_raw[::-1]
|
256
|
+
used_mask_raw = used_mask_raw[::-1]
|
183
257
|
|
184
258
|
self.cursor_bit_position = 0
|
185
259
|
self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
|
@@ -233,3 +307,25 @@ class EncodeState:
|
|
233
307
|
self.used_mask[pos + i] |= obj_used_mask[i]
|
234
308
|
|
235
309
|
self.cursor_byte_position += len(new_data)
|
310
|
+
|
311
|
+
@staticmethod
|
312
|
+
def __encode_bcd_p(value: int) -> int:
|
313
|
+
result = 0
|
314
|
+
shift = 0
|
315
|
+
while value > 0:
|
316
|
+
result |= (value % 10) << shift
|
317
|
+
shift += 4
|
318
|
+
value //= 10
|
319
|
+
|
320
|
+
return result
|
321
|
+
|
322
|
+
@staticmethod
|
323
|
+
def __encode_bcd_up(value: int) -> int:
|
324
|
+
result = 0
|
325
|
+
shift = 0
|
326
|
+
while value > 0:
|
327
|
+
result |= (value % 10) << shift
|
328
|
+
shift += 8
|
329
|
+
value //= 10
|
330
|
+
|
331
|
+
return result
|
odxtools/encoding.py
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from .exceptions import odxraise
|
6
|
+
from .odxtypes import DataType
|
7
|
+
|
8
|
+
|
9
|
+
class Encoding(Enum):
|
10
|
+
BCD_P = "BCD-P"
|
11
|
+
BCD_UP = "BCD-UP"
|
12
|
+
|
13
|
+
ONEC = "1C"
|
14
|
+
TWOC = "2C"
|
15
|
+
SM = "SM"
|
16
|
+
|
17
|
+
UTF8 = "UTF-8"
|
18
|
+
UCS2 = "UCS-2"
|
19
|
+
ISO_8859_1 = "ISO-8859-1"
|
20
|
+
ISO_8859_2 = "ISO-8859-2"
|
21
|
+
WINDOWS_1252 = "WINDOWS-1252"
|
22
|
+
|
23
|
+
NONE = "NONE"
|
24
|
+
|
25
|
+
|
26
|
+
def get_string_encoding(base_data_type: DataType, base_type_encoding: Optional[Encoding],
|
27
|
+
is_highlow_byte_order: bool) -> Optional[str]:
|
28
|
+
"""If the encoding is for a string, return the value for
|
29
|
+
`str.encode()`/`str.decode()` to convert the string object
|
30
|
+
to/from a byte array
|
31
|
+
"""
|
32
|
+
|
33
|
+
# note that the spec disallows certain combinations of
|
34
|
+
# base_data_type and encoding (e.g., A_ASCIISTRING encoded
|
35
|
+
# using UTF-8). Since in python3 strings are always
|
36
|
+
# capable of the full unicode character set, odxtools
|
37
|
+
# ignores these restrictions...
|
38
|
+
if base_type_encoding == Encoding.UTF8 or (base_data_type == DataType.A_UTF8STRING and
|
39
|
+
base_type_encoding is None):
|
40
|
+
return "utf-8"
|
41
|
+
elif base_type_encoding == Encoding.UCS2 or (base_data_type == DataType.A_UNICODE2STRING and
|
42
|
+
base_type_encoding is None):
|
43
|
+
return "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
44
|
+
elif base_type_encoding == Encoding.ISO_8859_1 or (base_data_type == DataType.A_ASCIISTRING and
|
45
|
+
base_type_encoding is None):
|
46
|
+
return "iso-8859-1"
|
47
|
+
elif base_type_encoding == Encoding.ISO_8859_2:
|
48
|
+
return "iso-8859-2"
|
49
|
+
elif base_type_encoding == Encoding.WINDOWS_1252:
|
50
|
+
return "cp1252"
|
51
|
+
else:
|
52
|
+
odxraise(f"Specified illegal encoding {base_type_encoding} for {base_data_type.value} "
|
53
|
+
f"string object")
|
54
|
+
return "iso-8859-1"
|
55
|
+
|
56
|
+
return None
|
@@ -6,6 +6,7 @@ from xml.etree import ElementTree
|
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
8
|
from .complexdop import ComplexDop
|
9
|
+
from .dataobjectproperty import DataObjectProperty
|
9
10
|
from .decodestate import DecodeState
|
10
11
|
from .dtcdop import DtcDop
|
11
12
|
from .encodestate import EncodeState
|
@@ -13,8 +14,12 @@ from .environmentdata import EnvironmentData
|
|
13
14
|
from .exceptions import odxraise, odxrequire
|
14
15
|
from .nameditemlist import NamedItemList
|
15
16
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
16
|
-
from .odxtypes import ParameterValue, ParameterValueDict
|
17
|
+
from .odxtypes import DataType, ParameterValue, ParameterValueDict
|
18
|
+
from .parameters.codedconstparameter import CodedConstParameter
|
17
19
|
from .parameters.parameter import Parameter
|
20
|
+
from .parameters.parameterwithdop import ParameterWithDOP
|
21
|
+
from .parameters.physicalconstantparameter import PhysicalConstantParameter
|
22
|
+
from .parameters.valueparameter import ValueParameter
|
18
23
|
from .snrefcontext import SnRefContext
|
19
24
|
from .utils import dataclass_fields_asdict
|
20
25
|
|
@@ -116,40 +121,26 @@ class EnvironmentDataDescription(ComplexDop):
|
|
116
121
|
"""Convert a physical value into bytes and emplace them into a PDU.
|
117
122
|
"""
|
118
123
|
|
119
|
-
# retrieve the
|
120
|
-
#
|
124
|
+
# retrieve the DTC as a numerical value from the referenced
|
125
|
+
# parameter (which must be located somewhere before the
|
126
|
+
# parameter using the environment data description)
|
121
127
|
if self.param_snref is None:
|
122
128
|
odxraise("Specifying the DTC parameter for environment data "
|
123
129
|
"descriptions via SNPATHREF is not supported yet")
|
124
130
|
return None
|
125
131
|
|
126
|
-
|
127
|
-
dtc_dop: Optional[DtcDop] = None
|
128
|
-
dtc_param_value: Optional[ParameterValue] = None
|
132
|
+
numerical_dtc_value: Optional[ParameterValue] = None
|
129
133
|
for prev_param, prev_param_value in reversed(encode_state.journal):
|
130
134
|
if prev_param.short_name == self.param_snref:
|
131
|
-
|
132
|
-
|
133
|
-
if not isinstance(prev_dop, DtcDop):
|
134
|
-
odxraise(f"The DOP of the parameter referenced by environment data "
|
135
|
-
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
|
136
|
-
return
|
137
|
-
dtc_dop = prev_dop
|
138
|
-
dtc_param_value = prev_param_value
|
135
|
+
numerical_dtc_value = self._get_numerical_dtc_from_parameter(
|
136
|
+
prev_param, prev_param_value)
|
139
137
|
break
|
140
138
|
|
141
|
-
if
|
139
|
+
if numerical_dtc_value is None:
|
142
140
|
odxraise("Environment data description parameters are only allowed following "
|
143
|
-
"the referenced
|
144
|
-
return
|
145
|
-
|
146
|
-
if dtc_param_value is None or dtc_dop is None:
|
147
|
-
# this should never happen
|
148
|
-
odxraise()
|
141
|
+
"the referenced parameter.")
|
149
142
|
return
|
150
143
|
|
151
|
-
numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
|
152
|
-
|
153
144
|
# deal with the "all value" environment data. This holds
|
154
145
|
# parameters that are common to all DTCs. Be aware that the
|
155
146
|
# specification mandates that there is at most one such
|
@@ -165,7 +156,7 @@ class EnvironmentDataDescription(ComplexDop):
|
|
165
156
|
# find the environment data corresponding to the given trouble
|
166
157
|
# code
|
167
158
|
for env_data in self.env_datas:
|
168
|
-
if
|
159
|
+
if numerical_dtc_value in env_data.dtc_values:
|
169
160
|
tmp = encode_state.allow_unknown_parameters
|
170
161
|
encode_state.allow_unknown_parameters = True
|
171
162
|
env_data.encode_into_pdu(physical_value, encode_state)
|
@@ -177,40 +168,26 @@ class EnvironmentDataDescription(ComplexDop):
|
|
177
168
|
"""Extract the bytes from a PDU and convert them to a physical value.
|
178
169
|
"""
|
179
170
|
|
180
|
-
# retrieve the
|
181
|
-
#
|
171
|
+
# retrieve the DTC as a numerical value from the referenced
|
172
|
+
# parameter (which must be located somewhere before the
|
173
|
+
# parameter using the environment data description)
|
182
174
|
if self.param_snref is None:
|
183
175
|
odxraise("Specifying the DTC parameter for environment data "
|
184
176
|
"descriptions via SNPATHREF is not supported yet")
|
185
177
|
return None
|
186
178
|
|
187
|
-
|
188
|
-
dtc_dop: Optional[DtcDop] = None
|
189
|
-
dtc_param_value: Optional[ParameterValue] = None
|
179
|
+
numerical_dtc_value: Optional[ParameterValue] = None
|
190
180
|
for prev_param, prev_param_value in reversed(decode_state.journal):
|
191
181
|
if prev_param.short_name == self.param_snref:
|
192
|
-
|
193
|
-
|
194
|
-
if not isinstance(prev_dop, DtcDop):
|
195
|
-
odxraise(f"The DOP of the parameter referenced by environment data "
|
196
|
-
f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
|
197
|
-
return
|
198
|
-
dtc_dop = prev_dop
|
199
|
-
dtc_param_value = prev_param_value
|
182
|
+
numerical_dtc_value = self._get_numerical_dtc_from_parameter(
|
183
|
+
prev_param, prev_param_value)
|
200
184
|
break
|
201
185
|
|
202
|
-
if
|
186
|
+
if numerical_dtc_value is None:
|
203
187
|
odxraise("Environment data description parameters are only allowed following "
|
204
|
-
"the referenced
|
188
|
+
"the referenced parameter.")
|
205
189
|
return
|
206
190
|
|
207
|
-
if dtc_param_value is None or dtc_dop is None:
|
208
|
-
# this should never happen
|
209
|
-
odxraise()
|
210
|
-
return
|
211
|
-
|
212
|
-
numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
|
213
|
-
|
214
191
|
result: ParameterValueDict = {}
|
215
192
|
|
216
193
|
# deal with the "all value" environment data. This holds
|
@@ -228,7 +205,7 @@ class EnvironmentDataDescription(ComplexDop):
|
|
228
205
|
# find the environment data corresponding to the given trouble
|
229
206
|
# code
|
230
207
|
for env_data in self.env_datas:
|
231
|
-
if
|
208
|
+
if numerical_dtc_value in env_data.dtc_values:
|
232
209
|
tmp = env_data.decode_from_pdu(decode_state)
|
233
210
|
if not isinstance(tmp, dict):
|
234
211
|
odxraise()
|
@@ -236,3 +213,56 @@ class EnvironmentDataDescription(ComplexDop):
|
|
236
213
|
break
|
237
214
|
|
238
215
|
return result
|
216
|
+
|
217
|
+
def _get_numerical_dtc_from_parameter(self, param: Parameter,
|
218
|
+
param_value: Optional[ParameterValue]) -> int:
|
219
|
+
if isinstance(param, ParameterWithDOP):
|
220
|
+
dop = param.dop
|
221
|
+
if not isinstance(dop, (DataObjectProperty, DtcDop)):
|
222
|
+
odxraise(f"The DOP of the parameter referenced by environment data descriptions "
|
223
|
+
f"must use either be DataObjectProperty or a DtcDop (encountered "
|
224
|
+
f"{type(param).__name__} for parameter '{self.param.short_name}' "
|
225
|
+
f"of ENV-DATA-DESC '{self.short_name}')")
|
226
|
+
return 0
|
227
|
+
|
228
|
+
if dop.diag_coded_type.base_data_type != DataType.A_UINT32:
|
229
|
+
odxraise(f"The data type used by the DOP of the parameter referenced "
|
230
|
+
f"by environment data descriptions must be A_UINT32 "
|
231
|
+
f"(encountered '{dop.diag_coded_type.base_data_type.value}')")
|
232
|
+
|
233
|
+
if param_value is None:
|
234
|
+
if isinstance(param, ValueParameter):
|
235
|
+
param_value = param.physical_default_value
|
236
|
+
elif isinstance(param, PhysicalConstantParameter):
|
237
|
+
param_value = param.physical_constant_value
|
238
|
+
else:
|
239
|
+
odxraise() # make mypy happy...
|
240
|
+
return
|
241
|
+
|
242
|
+
if isinstance(dop, DtcDop):
|
243
|
+
return dop.convert_to_numerical_trouble_code(odxrequire(param_value))
|
244
|
+
elif isinstance(dop, DataObjectProperty):
|
245
|
+
return int(dop.compu_method.convert_physical_to_internal(
|
246
|
+
param_value)) # type: ignore[arg-type]
|
247
|
+
|
248
|
+
odxraise() # not reachable...
|
249
|
+
|
250
|
+
elif isinstance(param, CodedConstParameter):
|
251
|
+
if param.diag_coded_type.base_data_type != DataType.A_UINT32:
|
252
|
+
odxraise(f"The data type used by the parameter referenced "
|
253
|
+
f"by environment data descriptions must be A_UINT32 "
|
254
|
+
f"(encountered '{param.diag_coded_type.base_data_type.value}')")
|
255
|
+
|
256
|
+
return param.coded_value
|
257
|
+
|
258
|
+
if not isinstance(param.coded_value, int):
|
259
|
+
odxraise()
|
260
|
+
|
261
|
+
return param.coded_value
|
262
|
+
|
263
|
+
else:
|
264
|
+
odxraise(f"The parameter referenced by environment data descriptions "
|
265
|
+
f"must be a parameter that either specifies a DOP or a constant "
|
266
|
+
f"(encountered {type(param).__name__} for reference '{self.param_snref}' of "
|
267
|
+
f"ENV-DATA-DESC '{self.short_name}')")
|
268
|
+
return 0
|
odxtools/functionalclass.py
CHANGED
@@ -1,8 +1,9 @@
|
|
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, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from .admindata import AdminData
|
6
7
|
from .element import IdentifiableElement
|
7
8
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
8
9
|
from .snrefcontext import SnRefContext
|
@@ -15,13 +16,17 @@ class FunctionalClass(IdentifiableElement):
|
|
15
16
|
Corresponds to FUNCT-CLASS.
|
16
17
|
"""
|
17
18
|
|
19
|
+
admin_data: Optional[AdminData]
|
20
|
+
|
18
21
|
@staticmethod
|
19
22
|
def from_et(et_element: ElementTree.Element,
|
20
23
|
doc_frags: List[OdxDocFragment]) -> "FunctionalClass":
|
21
24
|
|
22
25
|
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
23
26
|
|
24
|
-
|
27
|
+
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
28
|
+
|
29
|
+
return FunctionalClass(admin_data=admin_data, **kwargs)
|
25
30
|
|
26
31
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
27
32
|
return {self.odx_id: self}
|
odxtools/inputparam.py
CHANGED
@@ -3,6 +3,8 @@ from dataclasses import dataclass
|
|
3
3
|
from typing import Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from deprecation import deprecated
|
7
|
+
|
6
8
|
from .dopbase import DopBase
|
7
9
|
from .element import NamedElement
|
8
10
|
from .exceptions import odxrequire
|
@@ -38,12 +40,16 @@ class InputParam(NamedElement):
|
|
38
40
|
return {}
|
39
41
|
|
40
42
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
41
|
-
self.
|
43
|
+
self._dop = odxlinks.resolve(self.dop_base_ref, DopBase)
|
42
44
|
|
43
45
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
44
46
|
pass
|
45
47
|
|
46
48
|
@property
|
47
|
-
def
|
49
|
+
def dop(self) -> DopBase:
|
48
50
|
"""The data object property describing this parameter."""
|
49
|
-
return self.
|
51
|
+
return self._dop
|
52
|
+
|
53
|
+
@deprecated(details="use .dop") # type: ignore[misc]
|
54
|
+
def dop_base(self) -> DopBase:
|
55
|
+
return self._dop
|