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.
Files changed (63) hide show
  1. odxtools/companyrevisioninfo.py +1 -1
  2. odxtools/comparaminstance.py +1 -6
  3. odxtools/comparamspec.py +3 -2
  4. odxtools/comparamsubset.py +3 -5
  5. odxtools/dataobjectproperty.py +12 -6
  6. odxtools/decodestate.py +120 -26
  7. odxtools/description.py +19 -2
  8. odxtools/diagcodedtype.py +9 -2
  9. odxtools/diagcomm.py +4 -4
  10. odxtools/diaglayercontainer.py +2 -2
  11. odxtools/diaglayers/diaglayerraw.py +2 -2
  12. odxtools/diaglayers/hierarchyelement.py +2 -0
  13. odxtools/diagnostictroublecode.py +4 -8
  14. odxtools/diagservice.py +70 -4
  15. odxtools/diagvariable.py +45 -7
  16. odxtools/encodestate.py +147 -51
  17. odxtools/encoding.py +56 -0
  18. odxtools/environmentdatadescription.py +77 -47
  19. odxtools/functionalclass.py +7 -2
  20. odxtools/inputparam.py +9 -3
  21. odxtools/leadinglengthinfotype.py +4 -0
  22. odxtools/minmaxlengthtype.py +57 -36
  23. odxtools/modification.py +3 -2
  24. odxtools/odxcategory.py +2 -2
  25. odxtools/odxlink.py +31 -7
  26. odxtools/odxtypes.py +1 -1
  27. odxtools/outputparam.py +8 -3
  28. odxtools/parameters/matchingrequestparameter.py +1 -0
  29. odxtools/parameters/physicalconstantparameter.py +1 -0
  30. odxtools/parameters/reservedparameter.py +1 -0
  31. odxtools/parameters/tableentryparameter.py +15 -4
  32. odxtools/parameters/tablekeyparameter.py +20 -17
  33. odxtools/paramlengthinfotype.py +5 -3
  34. odxtools/physicaltype.py +2 -1
  35. odxtools/scaleconstr.py +4 -4
  36. odxtools/standardlengthtype.py +98 -22
  37. odxtools/statetransition.py +24 -3
  38. odxtools/structure.py +10 -2
  39. odxtools/swvariable.py +3 -1
  40. odxtools/table.py +55 -3
  41. odxtools/tablerow.py +91 -8
  42. odxtools/templates/macros/printDOP.xml.jinja2 +2 -2
  43. odxtools/templates/macros/printDescription.xml.jinja2 +5 -1
  44. odxtools/templates/macros/printDiagLayer.xml.jinja2 +1 -1
  45. odxtools/templates/macros/printDiagVariable.xml.jinja2 +12 -3
  46. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -0
  47. odxtools/templates/macros/printParentRef.xml.jinja2 +27 -0
  48. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  49. odxtools/templates/macros/printService.xml.jinja2 +30 -0
  50. odxtools/templates/macros/printStructure.xml.jinja2 +2 -1
  51. odxtools/templates/macros/printTable.xml.jinja2 +43 -0
  52. odxtools/templates/macros/printUnitSpec.xml.jinja2 +4 -0
  53. odxtools/uds.py +2 -2
  54. odxtools/unit.py +8 -12
  55. odxtools/unitspec.py +5 -2
  56. odxtools/utils.py +44 -1
  57. odxtools/version.py +2 -2
  58. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/METADATA +2 -2
  59. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/RECORD +63 -62
  60. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/WHEEL +1 -1
  61. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/LICENSE +0 -0
  62. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/entry_points.txt +0 -0
  63. {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
- #snref_to_tablerow: Optional[SnrefToTableRow] # TODO
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 variable_group(self) -> VariableGroup:
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 = odxrequire(
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 = odxlinks.resolve(self.variable_group_ref, VariableGroup)
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
- # Check that bytes and strings actually fit into the bit length
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
- if 8 * len(internal_value) > bit_length:
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
- # The spec says ASCII, meaning only byte values 0-127.
110
- # But in practice, vendors use iso-8859-1, aka latin-1
111
- # reason being iso-8859-1 never fails since it has a valid
112
- # character mapping for every possible byte sequence.
113
- raw_value = internal_value.encode("iso-8859-1")
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
- raise EncodeError(f"The string {repr(internal_value)} is too large."
117
- f" The maximum number of characters is {bit_length//8}.")
118
- elif base_data_type == DataType.A_UTF8STRING:
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
- raw_value = internal_value.encode("utf-8")
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
- raise EncodeError(f"The string {repr(internal_value)} is too large."
126
- f" The maximum number of bytes is {bit_length//8}.")
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
- elif base_data_type == DataType.A_UNICODE2STRING:
129
- if not isinstance(internal_value, str):
130
- odxraise()
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
- text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
133
- raw_value = internal_value.encode(text_encoding)
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 8 * len(raw_value) > bit_length:
136
- raise EncodeError(f"The string {repr(internal_value)} is too large."
137
- f" The maximum number of characters is {bit_length//16}.")
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
- raw_value = internal_value
213
+ odxassert(base_data_type in (DataType.A_FLOAT32, DataType.A_FLOAT64))
214
+ odxassert(base_type_encoding in (None, Encoding.NONE))
140
215
 
141
- # If the bit length is zero, return empty bytes
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
- char = base_data_type.bitstruct_format_letter
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}{char}{bit_length}", raw_value)
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 relevant DTC parameter which must be located in
120
- # front of the environment data description.
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
- dtc_param: Optional[Parameter] = None
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
- dtc_param = prev_param
132
- prev_dop = getattr(prev_param, "dop", None)
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 dtc_param is None:
139
+ if numerical_dtc_value is None:
142
140
  odxraise("Environment data description parameters are only allowed following "
143
- "the referenced value parameter.")
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 numerical_dtc in env_data.dtc_values:
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 relevant DTC parameter which must be located in
181
- # front of the environment data description.
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
- dtc_param: Optional[Parameter] = None
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
- dtc_param = prev_param
193
- prev_dop = getattr(prev_param, "dop", None)
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 dtc_param is None:
186
+ if numerical_dtc_value is None:
203
187
  odxraise("Environment data description parameters are only allowed following "
204
- "the referenced value parameter.")
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 numerical_dtc in env_data.dtc_values:
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
@@ -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
- return FunctionalClass(**kwargs)
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._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
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 dop_base(self) -> DopBase:
49
+ def dop(self) -> DopBase:
48
50
  """The data object property describing this parameter."""
49
- return self._dop_base
51
+ return self._dop
52
+
53
+ @deprecated(details="use .dop") # type: ignore[misc]
54
+ def dop_base(self) -> DopBase:
55
+ return self._dop