odxtools 9.3.0__py3-none-any.whl → 9.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. odxtools/companyrevisioninfo.py +1 -1
  2. odxtools/comparaminstance.py +1 -6
  3. odxtools/comparamspec.py +3 -2
  4. odxtools/comparamsubset.py +9 -7
  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/functionalclass.py +7 -2
  19. odxtools/inputparam.py +9 -3
  20. odxtools/leadinglengthinfotype.py +4 -0
  21. odxtools/minmaxlengthtype.py +57 -36
  22. odxtools/modification.py +3 -2
  23. odxtools/odxcategory.py +2 -2
  24. odxtools/odxlink.py +31 -7
  25. odxtools/odxtypes.py +1 -1
  26. odxtools/outputparam.py +8 -3
  27. odxtools/parameters/matchingrequestparameter.py +1 -0
  28. odxtools/parameters/physicalconstantparameter.py +1 -0
  29. odxtools/parameters/reservedparameter.py +1 -0
  30. odxtools/parameters/tableentryparameter.py +15 -4
  31. odxtools/parameters/tablekeyparameter.py +20 -17
  32. odxtools/paramlengthinfotype.py +5 -3
  33. odxtools/physicaltype.py +2 -1
  34. odxtools/scaleconstr.py +4 -4
  35. odxtools/standardlengthtype.py +98 -22
  36. odxtools/statetransition.py +24 -3
  37. odxtools/structure.py +10 -2
  38. odxtools/swvariable.py +3 -1
  39. odxtools/table.py +55 -3
  40. odxtools/tablerow.py +91 -8
  41. odxtools/templates/macros/printDOP.xml.jinja2 +2 -2
  42. odxtools/templates/macros/printDescription.xml.jinja2 +5 -1
  43. odxtools/templates/macros/printDiagLayer.xml.jinja2 +1 -1
  44. odxtools/templates/macros/printDiagVariable.xml.jinja2 +12 -3
  45. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -0
  46. odxtools/templates/macros/printParentRef.xml.jinja2 +27 -0
  47. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  48. odxtools/templates/macros/printService.xml.jinja2 +30 -0
  49. odxtools/templates/macros/printStructure.xml.jinja2 +2 -1
  50. odxtools/templates/macros/printTable.xml.jinja2 +43 -0
  51. odxtools/templates/macros/printUnitSpec.xml.jinja2 +4 -0
  52. odxtools/unit.py +8 -12
  53. odxtools/unitspec.py +5 -2
  54. odxtools/version.py +9 -4
  55. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/METADATA +1 -1
  56. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/RECORD +60 -59
  57. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/WHEEL +1 -1
  58. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/LICENSE +0 -0
  59. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/entry_points.txt +0 -0
  60. {odxtools-9.3.0.dist-info → odxtools-9.4.1.dist-info}/top_level.txt +0 -0
@@ -25,7 +25,7 @@ class CompanyRevisionInfo:
25
25
 
26
26
  company_data_ref = odxrequire(
27
27
  OdxLinkRef.from_et(et_element.find("COMPANY-DATA-REF"), doc_frags))
28
- revision_label = et_element.findtext("REVISION_LABEL")
28
+ revision_label = et_element.findtext("REVISION-LABEL")
29
29
  state = et_element.findtext("STATE")
30
30
 
31
31
  return CompanyRevisionInfo(
@@ -138,9 +138,4 @@ class ComparamInstance:
138
138
 
139
139
  @property
140
140
  def short_name(self) -> str:
141
- if self.spec:
142
- return self.spec.short_name
143
-
144
- # ODXLINK IDs allow dots and hyphens, but short names do not.
145
- # (This should not happen anyway in a correct PDX...)
146
- return self.spec_ref.ref_id.replace(".", "__").replace("-", "_")
141
+ return self.spec.short_name
odxtools/comparamspec.py CHANGED
@@ -5,7 +5,7 @@ from xml.etree import ElementTree
5
5
 
6
6
  from .nameditemlist import NamedItemList
7
7
  from .odxcategory import OdxCategory
8
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
8
+ from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
9
  from .protstack import ProtStack
10
10
  from .snrefcontext import SnRefContext
11
11
  from .utils import dataclass_fields_asdict
@@ -16,12 +16,13 @@ if TYPE_CHECKING:
16
16
 
17
17
  @dataclass
18
18
  class ComparamSpec(OdxCategory):
19
+
19
20
  prot_stacks: NamedItemList[ProtStack]
20
21
 
21
22
  @staticmethod
22
23
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ComparamSpec":
23
24
 
24
- cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="COMPARAM-SPEC")
25
+ cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type=DocType.COMPARAM_SPEC)
25
26
  doc_frags = cat.odx_id.doc_fragments
26
27
  kwargs = dataclass_fields_asdict(cat)
27
28
 
@@ -8,7 +8,7 @@ from .complexcomparam import ComplexComparam
8
8
  from .dataobjectproperty import DataObjectProperty
9
9
  from .nameditemlist import NamedItemList
10
10
  from .odxcategory import OdxCategory
11
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
11
+ from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
12
  from .snrefcontext import SnRefContext
13
13
  from .unitspec import UnitSpec
14
14
  from .utils import dataclass_fields_asdict
@@ -19,24 +19,26 @@ if TYPE_CHECKING:
19
19
 
20
20
  @dataclass
21
21
  class ComparamSubset(OdxCategory):
22
- # mandatory in ODX 2.2, but non-existent in ODX 2.0
23
- category: Optional[str]
24
-
25
22
  comparams: NamedItemList[Comparam]
26
23
  complex_comparams: NamedItemList[ComplexComparam]
27
24
  data_object_props: NamedItemList[DataObjectProperty]
28
25
  unit_spec: Optional[UnitSpec]
26
+ category: Optional[str] # mandatory in ODX 2.2, but non-existent in ODX 2.0
29
27
 
30
28
  @staticmethod
31
29
  def from_et(et_element: ElementTree.Element,
32
30
  doc_frags: List[OdxDocFragment]) -> "ComparamSubset":
33
31
 
34
- cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="COMPARAM-SUBSET")
32
+ category = et_element.get("CATEGORY")
33
+
34
+ # In ODX 2.0, COMPARAM-SPEC is used, whereas in ODX 2.2, it refers to something else and has been replaced by COMPARAM-SUBSET.
35
+ # - If 'category' is missing (ODX 2.0), use COMPARAM_SPEC,
36
+ # - else (ODX 2.2), use COMPARAM_SUBSET.
37
+ doc_type = DocType.COMPARAM_SUBSET if category else DocType.COMPARAM_SPEC
38
+ cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type=doc_type)
35
39
  doc_frags = cat.odx_id.doc_fragments
36
40
  kwargs = dataclass_fields_asdict(cat)
37
41
 
38
- category = et_element.get("CATEGORY")
39
-
40
42
  data_object_props = NamedItemList([
41
43
  DataObjectProperty.from_et(el, doc_frags)
42
44
  for el in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
@@ -10,7 +10,7 @@ from .decodestate import DecodeState
10
10
  from .diagcodedtype import DiagCodedType
11
11
  from .dopbase import DopBase
12
12
  from .encodestate import EncodeState
13
- from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
13
+ from .exceptions import EncodeError, odxraise, odxrequire
14
14
  from .internalconstr import InternalConstr
15
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
16
16
  from .odxtypes import AtomicOdxType, ParameterValue
@@ -136,11 +136,17 @@ class DataObjectProperty(DopBase):
136
136
 
137
137
  if self.compu_method.is_valid_internal_value(internal):
138
138
  return self.compu_method.convert_internal_to_physical(internal)
139
- else:
140
- # TODO: How to prevent this?
141
- raise DecodeError(
142
- f"DOP {self.short_name} could not convert the coded value "
143
- f" {repr(internal)} to physical type {self.physical_type.base_data_type}.")
139
+
140
+ internal_to_phys = self.compu_method.compu_internal_to_phys
141
+ default_value = internal_to_phys.compu_default_value if internal_to_phys else None
142
+
143
+ if default_value and default_value.value is not None:
144
+ return default_value.value
145
+
146
+ odxraise(f"DOP {self.short_name} could not convert the coded value "
147
+ f"{repr(internal)} to physical type {self.physical_type.base_data_type}.")
148
+
149
+ return None
144
150
 
145
151
  def is_valid_physical_value(self, physical_value: ParameterValue) -> bool:
146
152
  return self.compu_method.is_valid_physical_value(cast(AtomicOdxType, physical_value))
odxtools/decodestate.py CHANGED
@@ -1,10 +1,9 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass, field
3
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, cast
3
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
4
4
 
5
- import odxtools.exceptions as exceptions
6
-
7
- from .exceptions import DecodeError
5
+ from .encoding import Encoding, get_string_encoding
6
+ from .exceptions import DecodeError, odxassert, odxraise, strict_mode
8
7
  from .odxtypes import AtomicOdxType, DataType, ParameterValue
9
8
 
10
9
  try:
@@ -54,8 +53,10 @@ class DecodeState:
54
53
 
55
54
  def extract_atomic_value(
56
55
  self,
56
+ *,
57
57
  bit_length: int,
58
58
  base_data_type: DataType,
59
+ base_type_encoding: Optional[Encoding],
59
60
  is_highlow_byte_order: bool,
60
61
  ) -> AtomicOdxType:
61
62
  """Extract an internal value from a blob of raw bytes.
@@ -68,6 +69,13 @@ class DecodeState:
68
69
  if bit_length == 0:
69
70
  return base_data_type.python_type()
70
71
 
72
+ if base_data_type == DataType.A_FLOAT32 and bit_length != 32:
73
+ odxraise("The bit length of FLOAT32 values must be 32 bits")
74
+ bit_length = 32
75
+ elif base_data_type == DataType.A_FLOAT64 and bit_length != 64:
76
+ odxraise("The bit length of FLOAT64 values must be 64 bits")
77
+ bit_length = 64
78
+
71
79
  byte_length = (bit_length + self.cursor_bit_position + 7) // 8
72
80
  if self.cursor_byte_position + byte_length > len(self.coded_message):
73
81
  raise DecodeError(f"Expected a longer message.")
@@ -87,32 +95,118 @@ class DecodeState:
87
95
  extracted_bytes = extracted_bytes[::-1]
88
96
 
89
97
  padding = (8 - (bit_length + self.cursor_bit_position) % 8) % 8
90
- internal_value, = bitstruct.unpack_from(
98
+ raw_value, = bitstruct.unpack_from(
91
99
  f"{base_data_type.bitstruct_format_letter}{bit_length}",
92
100
  extracted_bytes,
93
101
  offset=padding)
94
-
95
- text_errors = 'strict' if exceptions.strict_mode else 'replace'
96
- if base_data_type == DataType.A_ASCIISTRING:
97
- assert isinstance(internal_value, (bytes, bytearray))
98
- # The spec says ASCII, meaning only byte values 0-127.
99
- # But in practice, vendors use iso-8859-1, aka latin-1
100
- # reason being iso-8859-1 never fails since it has a valid
101
- # character mapping for every possible byte sequence.
102
- text_encoding = 'iso-8859-1'
103
- internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
104
- elif base_data_type == DataType.A_UTF8STRING:
105
- assert isinstance(internal_value, (bytes, bytearray))
106
- text_encoding = "utf-8"
107
- internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
108
- elif base_data_type == DataType.A_UNICODE2STRING:
109
- assert isinstance(internal_value, (bytes, bytearray))
110
- # For UTF-16, we need to manually decode the extracted
111
- # bytes to a string
112
- text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
113
- internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
102
+ internal_value: AtomicOdxType
103
+
104
+ # Deal with raw byte fields, ...
105
+ if base_data_type == DataType.A_BYTEFIELD:
106
+ odxassert(
107
+ base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP),
108
+ f"Illegal encoding '{base_type_encoding}' for A_BYTEFIELD")
109
+
110
+ # note that we do not ensure that BCD-encoded byte fields
111
+ # only represent "legal" values
112
+ internal_value = raw_value
113
+
114
+ # ... string types, ...
115
+ elif base_data_type in (DataType.A_UTF8STRING, DataType.A_ASCIISTRING,
116
+ DataType.A_UNICODE2STRING):
117
+ text_errors = 'strict' if strict_mode else 'replace'
118
+ str_encoding = get_string_encoding(base_data_type, base_type_encoding,
119
+ is_highlow_byte_order)
120
+ if str_encoding is not None:
121
+ internal_value = raw_value.decode(str_encoding, errors=text_errors)
122
+ else:
123
+ internal_value = "ERROR"
124
+
125
+ # ... signed integers, ...
126
+ elif base_data_type == DataType.A_INT32:
127
+ if base_type_encoding == Encoding.ONEC:
128
+ # one-complement
129
+ sign_bit = 1 << (bit_length - 1)
130
+ if raw_value < sign_bit:
131
+ internal_value = raw_value
132
+ else:
133
+ # python defines the bitwise inversion of a
134
+ # positive integer value x as ~x = -(x + 1).
135
+ internal_value = -((1 << bit_length) - raw_value - 1)
136
+ elif base_type_encoding in (None, Encoding.TWOC):
137
+ # two-complement
138
+ sign_bit = 1 << (bit_length - 1)
139
+ if raw_value < sign_bit:
140
+ internal_value = raw_value
141
+ else:
142
+ internal_value = -((1 << bit_length) - raw_value)
143
+ elif base_type_encoding == Encoding.SM:
144
+ # sign-magnitude
145
+ sign_bit = 1 << (bit_length - 1)
146
+ if raw_value < sign_bit:
147
+ internal_value = raw_value
148
+ else:
149
+ internal_value = -(raw_value - sign_bit)
150
+ else:
151
+ odxraise(f"Illegal encoding ({base_type_encoding}) specified for "
152
+ f"{base_data_type.value}")
153
+
154
+ if base_type_encoding == Encoding.BCD_P:
155
+ internal_value = self.__decode_bcd_p(raw_value)
156
+ elif base_type_encoding == Encoding.BCD_UP:
157
+ internal_value = self.__decode_bcd_up(raw_value)
158
+ else:
159
+ internal_value = raw_value
160
+
161
+ # ... unsigned integers, ...
162
+ elif base_data_type == DataType.A_UINT32:
163
+ if base_type_encoding == Encoding.BCD_P:
164
+ internal_value = self.__decode_bcd_p(raw_value)
165
+ elif base_type_encoding == Encoding.BCD_UP:
166
+ internal_value = self.__decode_bcd_up(raw_value)
167
+ elif base_type_encoding in (None, Encoding.NONE):
168
+ # no encoding
169
+ internal_value = raw_value
170
+ else:
171
+ odxraise(f"Illegal encoding ({base_type_encoding}) specified for "
172
+ f"{base_data_type.value}")
173
+
174
+ internal_value = raw_value
175
+
176
+ # ... and others (floating point values)
177
+ else:
178
+ odxassert(base_data_type in (DataType.A_FLOAT32, DataType.A_FLOAT64))
179
+ odxassert(
180
+ base_type_encoding in (None, Encoding.NONE),
181
+ f"Specified illegal encoding '{base_type_encoding}' for float object")
182
+
183
+ internal_value = float(raw_value)
114
184
 
115
185
  self.cursor_byte_position += byte_length
116
186
  self.cursor_bit_position = 0
117
187
 
118
- return cast(AtomicOdxType, internal_value)
188
+ return internal_value
189
+
190
+ @staticmethod
191
+ def __decode_bcd_p(value: int) -> int:
192
+ # packed BCD
193
+ result = 0
194
+ factor = 1
195
+ while value > 0:
196
+ result += (value & 0xf) * factor
197
+ factor *= 10
198
+ value >>= 4
199
+
200
+ return result
201
+
202
+ @staticmethod
203
+ def __decode_bcd_up(value: int) -> int:
204
+ # unpacked BCD
205
+ result = 0
206
+ factor = 1
207
+ while value > 0:
208
+ result += (value & 0xf) * factor
209
+ factor *= 10
210
+ value >>= 8
211
+
212
+ return result
odxtools/description.py CHANGED
@@ -6,10 +6,27 @@ from .exceptions import odxrequire
6
6
  from .odxlink import OdxDocFragment
7
7
 
8
8
 
9
+ @dataclass
10
+ class ExternalDoc:
11
+ description: Optional[str]
12
+ href: str
13
+
14
+ @staticmethod
15
+ def from_et(et_element: Optional[ElementTree.Element],
16
+ doc_frags: List[OdxDocFragment]) -> Optional["ExternalDoc"]:
17
+ if et_element is None:
18
+ return None
19
+
20
+ description = et_element.text
21
+ href = odxrequire(et_element.get("HREF"))
22
+
23
+ return ExternalDoc(description=description, href=href)
24
+
25
+
9
26
  @dataclass
10
27
  class Description:
11
28
  text: str
12
- external_docs: List[str]
29
+ external_docs: List[ExternalDoc]
13
30
  text_identifier: Optional[str]
14
31
 
15
32
  @staticmethod
@@ -35,7 +52,7 @@ class Description:
35
52
 
36
53
  external_docs = \
37
54
  [
38
- odxrequire(ed.get("HREF")) for ed in et_element.iterfind("EXTERNAL-DOCS/EXTERNAL-DOC")
55
+ odxrequire(ExternalDoc.from_et(ed, doc_frags)) for ed in et_element.iterfind("EXTERNAL-DOCS/EXTERNAL-DOC")
39
56
  ]
40
57
  return Description(text=text, text_identifier=text_identifier, external_docs=external_docs)
41
58
 
odxtools/diagcodedtype.py CHANGED
@@ -5,6 +5,7 @@ from xml.etree import ElementTree
5
5
 
6
6
  from .decodestate import DecodeState
7
7
  from .encodestate import EncodeState
8
+ from .encoding import Encoding
8
9
  from .exceptions import odxassert, odxraise, odxrequire
9
10
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
10
11
  from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
@@ -23,7 +24,7 @@ DctType = Literal[
23
24
  class DiagCodedType:
24
25
 
25
26
  base_data_type: DataType
26
- base_type_encoding: Optional[str]
27
+ base_type_encoding: Optional[Encoding]
27
28
  is_highlow_byte_order_raw: Optional[bool]
28
29
 
29
30
  @staticmethod
@@ -36,7 +37,13 @@ class DiagCodedType:
36
37
  odxraise(f"Unknown base data type {base_data_type_str}")
37
38
  base_data_type = cast(DataType, None)
38
39
 
39
- base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
40
+ base_type_encoding = None
41
+ if (base_type_encoding_str := et_element.get("BASE-TYPE-ENCODING")) is not None:
42
+ try:
43
+ base_type_encoding = Encoding(base_type_encoding_str)
44
+ except ValueError:
45
+ odxraise(f"Encountered unknown BASE-TYPE-ENCODING '{base_type_encoding_str}'")
46
+
40
47
  is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
41
48
 
42
49
  return DiagCodedType(
odxtools/diagcomm.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
@@ -61,8 +61,8 @@ class DiagComm(IdentifiableElement):
61
61
  audience: Optional[Audience]
62
62
  protocol_snrefs: List[str]
63
63
  related_diag_comm_refs: List[RelatedDiagCommRef]
64
- pre_condition_state_refs: Iterable[OdxLinkRef]
65
- state_transition_refs: Iterable[OdxLinkRef]
64
+ pre_condition_state_refs: List[OdxLinkRef]
65
+ state_transition_refs: List[OdxLinkRef]
66
66
 
67
67
  # attributes
68
68
  semantic: Optional[str]
@@ -119,7 +119,7 @@ class DiagComm(IdentifiableElement):
119
119
  odxraise(f"Encountered unknown diagnostic class type '{diagnostic_class_str}'")
120
120
 
121
121
  is_mandatory_raw = odxstr_to_bool(et_element.get("IS-MANDATORY"))
122
- is_executable_raw = odxstr_to_bool(et_element.get("IS-MANDATORY"))
122
+ is_executable_raw = odxstr_to_bool(et_element.get("IS-EXECUTABLE"))
123
123
  is_final_raw = odxstr_to_bool(et_element.get("IS-FINAL"))
124
124
 
125
125
  return DiagComm(
@@ -12,7 +12,7 @@ from .diaglayers.functionalgroup import FunctionalGroup
12
12
  from .diaglayers.protocol import Protocol
13
13
  from .nameditemlist import NamedItemList
14
14
  from .odxcategory import OdxCategory
15
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
+ from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
16
  from .snrefcontext import SnRefContext
17
17
  from .utils import dataclass_fields_asdict
18
18
 
@@ -48,7 +48,7 @@ class DiagLayerContainer(OdxCategory):
48
48
  def from_et(et_element: ElementTree.Element,
49
49
  doc_frags: List[OdxDocFragment]) -> "DiagLayerContainer":
50
50
 
51
- cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="CONTAINER")
51
+ cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type=DocType.CONTAINER)
52
52
  doc_frags = cat.odx_id.doc_fragments
53
53
  kwargs = dataclass_fields_asdict(cat)
54
54
 
@@ -15,7 +15,7 @@ from ..exceptions import odxassert, odxraise, odxrequire
15
15
  from ..functionalclass import FunctionalClass
16
16
  from ..library import Library
17
17
  from ..nameditemlist import NamedItemList
18
- from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
+ from ..odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
19
19
  from ..request import Request
20
20
  from ..response import Response
21
21
  from ..singleecujob import SingleEcuJob
@@ -81,7 +81,7 @@ class DiagLayerRaw(IdentifiableElement):
81
81
 
82
82
  # extend the applicable ODX "document fragments" for the diag layer objects
83
83
  doc_frags = copy(doc_frags)
84
- doc_frags.append(OdxDocFragment(short_name, "LAYER"))
84
+ doc_frags.append(OdxDocFragment(short_name, DocType.LAYER))
85
85
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
86
86
 
87
87
  admin_data = None
@@ -137,6 +137,7 @@ class HierarchyElement(DiagLayer):
137
137
  unit_groups=NamedItemList(unit_groups),
138
138
  units=NamedItemList([]),
139
139
  physical_dimensions=NamedItemList([]),
140
+ admin_data=None,
140
141
  sdgs=[])
141
142
  else:
142
143
  # locally defined unit spec and inherited unit groups
@@ -144,6 +145,7 @@ class HierarchyElement(DiagLayer):
144
145
  unit_groups=NamedItemList(unit_groups),
145
146
  units=local_unit_spec.units,
146
147
  physical_dimensions=local_unit_spec.physical_dimensions,
148
+ admin_data=None,
147
149
  sdgs=[])
148
150
  ############
149
151
 
@@ -15,7 +15,7 @@ from .utils import dataclass_fields_asdict
15
15
  @dataclass
16
16
  class DiagnosticTroubleCode(IdentifiableElement):
17
17
  trouble_code: int
18
- text: Optional[str]
18
+ text: str
19
19
  display_trouble_code: Optional[str]
20
20
  level: Optional[int]
21
21
  is_temporary_raw: Optional[bool]
@@ -29,15 +29,11 @@ class DiagnosticTroubleCode(IdentifiableElement):
29
29
  def from_et(et_element: ElementTree.Element,
30
30
  doc_frags: List[OdxDocFragment]) -> "DiagnosticTroubleCode":
31
31
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
32
- if et_element.find("DISPLAY-TROUBLE-CODE") is not None:
33
- display_trouble_code = et_element.findtext("DISPLAY-TROUBLE-CODE")
34
- else:
35
- display_trouble_code = None
32
+ display_trouble_code = et_element.findtext("DISPLAY-TROUBLE-CODE")
36
33
 
34
+ level = None
37
35
  if (level_str := et_element.findtext("LEVEL")) is not None:
38
36
  level = int(level_str)
39
- else:
40
- level = None
41
37
 
42
38
  is_temporary_raw = odxstr_to_bool(et_element.get("IS-TEMPORARY"))
43
39
  sdgs = [
@@ -46,7 +42,7 @@ class DiagnosticTroubleCode(IdentifiableElement):
46
42
 
47
43
  return DiagnosticTroubleCode(
48
44
  trouble_code=int(odxrequire(et_element.findtext("TROUBLE-CODE"))),
49
- text=et_element.findtext("TEXT"),
45
+ text=odxrequire(et_element.findtext("TEXT")),
50
46
  display_trouble_code=display_trouble_code,
51
47
  level=level,
52
48
  is_temporary_raw=is_temporary_raw,
odxtools/diagservice.py CHANGED
@@ -31,6 +31,71 @@ class TransMode(Enum):
31
31
  SEND_OR_RECEIVE = "SEND-OR-RECEIVE"
32
32
 
33
33
 
34
+ # note that the spec has a typo here: it calls the corresponding
35
+ # XML tag POS-RESPONSE-SUPPRESSABLE...
36
+ @dataclass
37
+ class PosResponseSuppressible:
38
+ bit_mask: int
39
+
40
+ coded_const_snref: Optional[str]
41
+ coded_const_snpathref: Optional[str]
42
+
43
+ value_snref: Optional[str]
44
+ value_snpathref: Optional[str]
45
+
46
+ phys_const_snref: Optional[str]
47
+ phys_const_snpathref: Optional[str]
48
+
49
+ table_key_snref: Optional[str]
50
+ table_key_snpathref: Optional[str]
51
+
52
+ @staticmethod
53
+ def from_et(et_element: ElementTree.Element,
54
+ doc_frags: List[OdxDocFragment]) -> "PosResponseSuppressible":
55
+
56
+ bit_mask = int(odxrequire(et_element.findtext("BITMASK")))
57
+
58
+ coded_const_snref = None
59
+ if (cc_snref_elem := et_element.find("CODED-CONST-SNREF")) is not None:
60
+ coded_const_snref = cc_snref_elem.attrib["SHORT-NAME"]
61
+ coded_const_snpathref = None
62
+ if (cc_snpathref_elem := et_element.find("CODED-CONST-SNPATHREF")) is not None:
63
+ coded_const_snpathref = cc_snpathref_elem.attrib["SHORT-NAME-PATH"]
64
+
65
+ value_snref = None
66
+ if (cc_snref_elem := et_element.find("VALUE-SNREF")) is not None:
67
+ value_snref = cc_snref_elem.attrib["SHORT-NAME"]
68
+ value_snpathref = None
69
+ if (cc_snpathref_elem := et_element.find("VALUE-SNPATHREF")) is not None:
70
+ value_snpathref = cc_snpathref_elem.attrib["SHORT-NAME-PATH"]
71
+
72
+ phys_const_snref = None
73
+ if (cc_snref_elem := et_element.find("PHYS-CONST-SNREF")) is not None:
74
+ phys_const_snref = cc_snref_elem.attrib["SHORT-NAME"]
75
+ phys_const_snpathref = None
76
+ if (cc_snpathref_elem := et_element.find("PHYS-CONST-SNPATHREF")) is not None:
77
+ phys_const_snpathref = cc_snpathref_elem.attrib["SHORT-NAME-PATH"]
78
+
79
+ table_key_snref = None
80
+ if (cc_snref_elem := et_element.find("TABLE-KEY-SNREF")) is not None:
81
+ table_key_snref = cc_snref_elem.attrib["SHORT-NAME"]
82
+ table_key_snpathref = None
83
+ if (cc_snpathref_elem := et_element.find("TABLE-KEY-SNPATHREF")) is not None:
84
+ table_key_snpathref = cc_snpathref_elem.attrib["SHORT-NAME-PATH"]
85
+
86
+ return PosResponseSuppressible(
87
+ bit_mask=bit_mask,
88
+ coded_const_snref=coded_const_snref,
89
+ coded_const_snpathref=coded_const_snpathref,
90
+ value_snref=value_snref,
91
+ value_snpathref=value_snpathref,
92
+ phys_const_snref=phys_const_snref,
93
+ phys_const_snpathref=phys_const_snpathref,
94
+ table_key_snref=table_key_snref,
95
+ table_key_snpathref=table_key_snpathref,
96
+ )
97
+
98
+
34
99
  @dataclass
35
100
  class DiagService(DiagComm):
36
101
  """Representation of a diagnostic service description.
@@ -42,9 +107,7 @@ class DiagService(DiagComm):
42
107
  pos_response_refs: List[OdxLinkRef]
43
108
  neg_response_refs: List[OdxLinkRef]
44
109
 
45
- # note that the spec has a typo here: it calls the corresponding
46
- # XML tag POS-RESPONSE-SUPPRESSABLE...
47
- # TODO: pos_response_suppressible: Optional[PosResponseSuppressible]
110
+ pos_response_suppressible: Optional[PosResponseSuppressible]
48
111
 
49
112
  is_cyclic_raw: Optional[bool]
50
113
  is_multiple_raw: Optional[bool]
@@ -73,7 +136,9 @@ class DiagService(DiagComm):
73
136
  for el in et_element.iterfind("NEG-RESPONSE-REFS/NEG-RESPONSE-REF")
74
137
  ]
75
138
 
76
- # TODO: POS-RESPONSE-SUPPRESSABLE
139
+ pos_response_suppressible = None
140
+ if (prs_elem := et_element.find("POS-RESPONSE-SUPPRESSABLE")) is not None:
141
+ pos_response_suppressible = PosResponseSuppressible.from_et(prs_elem, doc_frags)
77
142
 
78
143
  is_cyclic_raw = odxstr_to_bool(et_element.get("IS-CYCLIC"))
79
144
  is_multiple_raw = odxstr_to_bool(et_element.get("IS-MULTIPLE"))
@@ -99,6 +164,7 @@ class DiagService(DiagComm):
99
164
  request_ref=request_ref,
100
165
  pos_response_refs=pos_response_refs,
101
166
  neg_response_refs=neg_response_refs,
167
+ pos_response_suppressible=pos_response_suppressible,
102
168
  is_cyclic_raw=is_cyclic_raw,
103
169
  is_multiple_raw=is_multiple_raw,
104
170
  addressing_raw=addressing_raw,