odxtools 6.6.1__py3-none-any.whl → 6.7.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 (81) hide show
  1. odxtools/__init__.py +5 -5
  2. odxtools/basicstructure.py +7 -8
  3. odxtools/cli/_parser_utils.py +15 -0
  4. odxtools/cli/_print_utils.py +4 -3
  5. odxtools/cli/browse.py +19 -14
  6. odxtools/cli/compare.py +24 -16
  7. odxtools/cli/decode.py +2 -1
  8. odxtools/cli/dummy_sub_parser.py +3 -1
  9. odxtools/cli/find.py +2 -1
  10. odxtools/cli/list.py +2 -1
  11. odxtools/cli/main.py +1 -0
  12. odxtools/cli/snoop.py +4 -1
  13. odxtools/comparaminstance.py +7 -5
  14. odxtools/compumethods/compumethod.py +2 -4
  15. odxtools/compumethods/compuscale.py +45 -5
  16. odxtools/compumethods/createanycompumethod.py +28 -36
  17. odxtools/compumethods/limit.py +70 -36
  18. odxtools/compumethods/linearcompumethod.py +68 -59
  19. odxtools/compumethods/tabintpcompumethod.py +19 -8
  20. odxtools/compumethods/texttablecompumethod.py +32 -36
  21. odxtools/dataobjectproperty.py +13 -10
  22. odxtools/decodestate.py +6 -3
  23. odxtools/determinenumberofitems.py +1 -1
  24. odxtools/diagcodedtype.py +5 -4
  25. odxtools/diagdatadictionaryspec.py +108 -83
  26. odxtools/diaglayer.py +75 -35
  27. odxtools/diaglayertype.py +17 -5
  28. odxtools/diagservice.py +1 -1
  29. odxtools/dopbase.py +4 -2
  30. odxtools/dtcdop.py +7 -5
  31. odxtools/dynamiclengthfield.py +6 -5
  32. odxtools/endofpdufield.py +4 -4
  33. odxtools/environmentdatadescription.py +4 -2
  34. odxtools/inputparam.py +1 -1
  35. odxtools/internalconstr.py +14 -5
  36. odxtools/isotp_state_machine.py +14 -6
  37. odxtools/message.py +1 -1
  38. odxtools/multiplexer.py +18 -13
  39. odxtools/multiplexercase.py +27 -5
  40. odxtools/multiplexerswitchkey.py +1 -1
  41. odxtools/nameditemlist.py +7 -6
  42. odxtools/odxlink.py +2 -2
  43. odxtools/odxtypes.py +56 -3
  44. odxtools/outputparam.py +2 -2
  45. odxtools/parameterinfo.py +12 -5
  46. odxtools/parameters/codedconstparameter.py +33 -12
  47. odxtools/parameters/createanyparameter.py +19 -193
  48. odxtools/parameters/dynamicparameter.py +21 -1
  49. odxtools/parameters/lengthkeyparameter.py +28 -4
  50. odxtools/parameters/matchingrequestparameter.py +27 -9
  51. odxtools/parameters/nrcconstparameter.py +34 -11
  52. odxtools/parameters/parameter.py +58 -32
  53. odxtools/parameters/parameterwithdop.py +28 -15
  54. odxtools/parameters/physicalconstantparameter.py +28 -4
  55. odxtools/parameters/reservedparameter.py +32 -18
  56. odxtools/parameters/systemparameter.py +25 -2
  57. odxtools/parameters/tableentryparameter.py +45 -6
  58. odxtools/parameters/tablekeyparameter.py +43 -10
  59. odxtools/parameters/tablestructparameter.py +36 -14
  60. odxtools/parameters/valueparameter.py +24 -2
  61. odxtools/paramlengthinfotype.py +4 -1
  62. odxtools/parentref.py +4 -1
  63. odxtools/scaleconstr.py +11 -5
  64. odxtools/statetransition.py +1 -1
  65. odxtools/staticfield.py +101 -0
  66. odxtools/table.py +2 -1
  67. odxtools/tablerow.py +11 -4
  68. odxtools/templates/macros/printDOP.xml.jinja2 +30 -34
  69. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  70. odxtools/templates/macros/printParam.xml.jinja2 +9 -9
  71. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  72. odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
  73. odxtools/uds.py +2 -2
  74. odxtools/version.py +2 -2
  75. odxtools/write_pdx_file.py +3 -3
  76. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/METADATA +28 -16
  77. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/RECORD +81 -79
  78. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/WHEEL +1 -1
  79. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/LICENSE +0 -0
  80. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/entry_points.txt +0 -0
  81. {odxtools-6.6.1.dist-info → odxtools-6.7.1.dist-info}/top_level.txt +0 -0
@@ -59,20 +59,20 @@ class DataObjectProperty(DopBase):
59
59
  compu_method = create_any_compu_method_from_et(
60
60
  odxrequire(et_element.find("COMPU-METHOD")),
61
61
  doc_frags,
62
- diag_coded_type.base_data_type,
63
- physical_type.base_data_type,
62
+ internal_type=diag_coded_type.base_data_type,
63
+ physical_type=physical_type.base_data_type,
64
64
  )
65
65
  unit_ref = OdxLinkRef.from_et(et_element.find("UNIT-REF"), doc_frags)
66
66
 
67
67
  internal_constr = None
68
68
  if (internal_constr_elem := et_element.find("INTERNAL-CONSTR")) is not None:
69
- internal_constr = InternalConstr.from_et(
70
- internal_constr_elem, internal_type=diag_coded_type.base_data_type)
69
+ internal_constr = InternalConstr.constr_from_et(
70
+ internal_constr_elem, doc_frags, value_type=diag_coded_type.base_data_type)
71
71
 
72
72
  physical_constr = None
73
73
  if (physical_constr_elem := et_element.find("PHYS-CONSTR")) is not None:
74
- physical_constr = InternalConstr.from_et(
75
- physical_constr_elem, internal_type=physical_type.base_data_type)
74
+ physical_constr = InternalConstr.constr_from_et(
75
+ physical_constr_elem, doc_frags, value_type=physical_type.base_data_type)
76
76
 
77
77
  return DataObjectProperty(
78
78
  diag_coded_type=diag_coded_type,
@@ -120,14 +120,17 @@ class DataObjectProperty(DopBase):
120
120
 
121
121
  return self.compu_method.convert_physical_to_internal(physical_value)
122
122
 
123
- def convert_physical_to_bytes(self, physical_value: Any, encode_state: EncodeState,
124
- bit_position: int) -> bytes:
123
+ def convert_physical_to_bytes(self,
124
+ physical_value: Any,
125
+ encode_state: EncodeState,
126
+ bit_position: int = 0) -> bytes:
125
127
  """
126
128
  Convert a physical representation of a parameter to a string bytes that can be send over the wire
127
129
  """
128
130
  if not self.is_valid_physical_value(physical_value):
129
- raise EncodeError(f"The value {repr(physical_value)} of type {type(physical_value)}"
130
- f" is not a valid.")
131
+ raise EncodeError(
132
+ f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
133
+ f" is not a valid.")
131
134
 
132
135
  internal_val = self.convert_physical_to_internal(physical_value)
133
136
  return self.diag_coded_type.convert_internal_to_bytes(
odxtools/decodestate.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass, field
3
- from typing import TYPE_CHECKING, Dict
3
+ from typing import TYPE_CHECKING, Dict, cast
4
4
 
5
5
  import odxtools.exceptions as exceptions
6
6
 
@@ -72,7 +72,7 @@ class DecodeState:
72
72
  """
73
73
  # If the bit length is zero, return "empty" values of each type
74
74
  if bit_length == 0:
75
- return base_data_type.as_python_type()()
75
+ return base_data_type.python_type()
76
76
 
77
77
  byte_length = (bit_length + self.cursor_bit_position + 7) // 8
78
78
  if self.cursor_byte_position + byte_length > len(self.coded_message):
@@ -100,6 +100,7 @@ class DecodeState:
100
100
 
101
101
  text_errors = 'strict' if exceptions.strict_mode else 'replace'
102
102
  if base_data_type == DataType.A_ASCIISTRING:
103
+ assert isinstance(internal_value, (bytes, bytearray))
103
104
  # The spec says ASCII, meaning only byte values 0-127.
104
105
  # But in practice, vendors use iso-8859-1, aka latin-1
105
106
  # reason being iso-8859-1 never fails since it has a valid
@@ -107,9 +108,11 @@ class DecodeState:
107
108
  text_encoding = 'iso-8859-1'
108
109
  internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
109
110
  elif base_data_type == DataType.A_UTF8STRING:
111
+ assert isinstance(internal_value, (bytes, bytearray))
110
112
  text_encoding = "utf-8"
111
113
  internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
112
114
  elif base_data_type == DataType.A_UNICODE2STRING:
115
+ assert isinstance(internal_value, (bytes, bytearray))
113
116
  # For UTF-16, we need to manually decode the extracted
114
117
  # bytes to a string
115
118
  text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
@@ -118,4 +121,4 @@ class DecodeState:
118
121
  self.cursor_byte_position += byte_length
119
122
  self.cursor_bit_position = 0
120
123
 
121
- return internal_value
124
+ return cast(AtomicOdxType, internal_value)
@@ -7,7 +7,7 @@ from .exceptions import odxrequire
7
7
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
8
 
9
9
  if TYPE_CHECKING:
10
- from ..diaglayer import DiagLayer
10
+ from .diaglayer import DiagLayer
11
11
 
12
12
 
13
13
  @dataclass
odxtools/diagcodedtype.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast
5
5
 
6
6
  from .decodestate import ODX_TYPE_TO_FORMAT_LETTER, DecodeState
7
7
  from .encodestate import EncodeState
@@ -111,9 +111,9 @@ class DiagCodedType(abc.ABC):
111
111
 
112
112
  # If the bit length is zero, return empty bytes
113
113
  if bit_length == 0:
114
- if (base_data_type in [
114
+ if (base_data_type.value in [
115
115
  DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
116
- ] and base_data_type != 0):
116
+ ] and base_data_type.value != 0):
117
117
  raise EncodeError(
118
118
  f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
119
119
  return b''
@@ -136,12 +136,13 @@ class DiagCodedType(abc.ABC):
136
136
  ]:
137
137
  coded = coded[::-1]
138
138
 
139
- return coded
139
+ return cast(bytes, coded)
140
140
 
141
141
  def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
142
142
  """Helper method to get the minimal byte length.
143
143
  (needed for LeadingLength- and MinMaxLengthType)
144
144
  """
145
+ byte_length: int = -1
145
146
  # A_BYTEFIELD, A_ASCIISTRING, A_UNICODE2STRING, A_UTF8STRING
146
147
  if self.base_data_type == DataType.A_BYTEFIELD:
147
148
  byte_length = len(internal_value)
@@ -4,6 +4,7 @@ from itertools import chain
4
4
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
+ from .admindata import AdminData
7
8
  from .basicstructure import BasicStructure
8
9
  from .createsdgs import create_sdgs_from_et
9
10
  from .dataobjectproperty import DataObjectProperty
@@ -14,11 +15,11 @@ from .endofpdufield import EndOfPduField
14
15
  from .environmentdata import EnvironmentData
15
16
  from .environmentdatadescription import EnvironmentDataDescription
16
17
  from .exceptions import odxraise
17
- from .globals import logger
18
18
  from .multiplexer import Multiplexer
19
19
  from .nameditemlist import NamedItemList
20
20
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
21
21
  from .specialdatagroup import SpecialDataGroup
22
+ from .staticfield import StaticField
22
23
  from .structure import Structure
23
24
  from .table import Table
24
25
  from .unitspec import UnitSpec
@@ -29,35 +30,55 @@ if TYPE_CHECKING:
29
30
 
30
31
  @dataclass
31
32
  class DiagDataDictionarySpec:
33
+ admin_data: Optional[AdminData]
32
34
  dtc_dops: NamedItemList[DtcDop]
35
+ env_data_descs: NamedItemList[EnvironmentDataDescription]
33
36
  data_object_props: NamedItemList[DataObjectProperty]
34
37
  structures: NamedItemList[BasicStructure]
35
- end_of_pdu_fields: NamedItemList[EndOfPduField]
38
+ static_fields: NamedItemList[StaticField]
36
39
  dynamic_length_fields: NamedItemList[DynamicLengthField]
37
- tables: NamedItemList[Table]
38
- env_data_descs: NamedItemList[EnvironmentDataDescription]
39
- env_datas: NamedItemList[EnvironmentData]
40
+ #dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
+ end_of_pdu_fields: NamedItemList[EndOfPduField]
40
42
  muxs: NamedItemList[Multiplexer]
43
+ env_datas: NamedItemList[EnvironmentData]
41
44
  unit_spec: Optional[UnitSpec]
45
+ tables: NamedItemList[Table]
42
46
  sdgs: List[SpecialDataGroup]
43
47
 
44
48
  def __post_init__(self) -> None:
45
49
  self._all_data_object_properties: NamedItemList[DopBase] = NamedItemList(
46
50
  chain(
47
51
  self.dtc_dops,
52
+ self.env_data_descs,
48
53
  self.data_object_props,
49
54
  self.structures,
50
- self.end_of_pdu_fields,
55
+ self.static_fields,
51
56
  self.dynamic_length_fields,
52
- self.env_data_descs,
53
- self.env_datas,
57
+ #self.dynamic_endmarker_fields,
58
+ self.end_of_pdu_fields,
54
59
  self.muxs,
60
+ self.env_datas,
55
61
  ))
56
62
 
57
63
  @staticmethod
58
64
  def from_et(et_element: ElementTree.Element,
59
65
  doc_frags: List[OdxDocFragment]) -> "DiagDataDictionarySpec":
60
- # Parse DOP-BASEs
66
+ admin_data = None
67
+ if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
68
+ admin_data = AdminData.from_et(admin_data_elem, doc_frags)
69
+
70
+ dtc_dops = []
71
+ for dtc_dop_elem in et_element.iterfind("DTC-DOPS/DTC-DOP"):
72
+ dtc_dop = DtcDop.from_et(dtc_dop_elem, doc_frags)
73
+ if not isinstance(dtc_dop, DtcDop):
74
+ odxraise()
75
+ dtc_dops.append(dtc_dop)
76
+
77
+ env_data_descs = [
78
+ EnvironmentDataDescription.from_et(env_data_desc_element, doc_frags)
79
+ for env_data_desc_element in et_element.iterfind("ENV-DATA-DESCS/ENV-DATA-DESC")
80
+ ]
81
+
61
82
  data_object_props = [
62
83
  DataObjectProperty.from_et(dop_element, doc_frags)
63
84
  for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
@@ -68,9 +89,9 @@ class DiagDataDictionarySpec:
68
89
  for structure_element in et_element.iterfind("STRUCTURES/STRUCTURE")
69
90
  ]
70
91
 
71
- end_of_pdu_fields = [
72
- EndOfPduField.from_et(eofp_element, doc_frags)
73
- for eofp_element in et_element.iterfind("END-OF-PDU-FIELDS/END-OF-PDU-FIELD")
92
+ static_fields = [
93
+ StaticField.from_et(dl_element, doc_frags)
94
+ for dl_element in et_element.iterfind("STATIC-FIELDS/STATIC-FIELD")
74
95
  ]
75
96
 
76
97
  dynamic_length_fields = [
@@ -78,21 +99,20 @@ class DiagDataDictionarySpec:
78
99
  for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
79
100
  ]
80
101
 
81
- dtc_dops = []
82
- for dtc_dop_elem in et_element.iterfind("DTC-DOPS/DTC-DOP"):
83
- dtc_dop = DtcDop.from_et(dtc_dop_elem, doc_frags)
84
- if not isinstance(dtc_dop, DtcDop):
85
- odxraise()
86
- dtc_dops.append(dtc_dop)
102
+ # TODO: dynamic endmarker fields
103
+ #dynamic_endmarker_fields = [
104
+ # DynamicEndmarkerField.from_et(dl_element, doc_frags)
105
+ # for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
+ #]
87
107
 
88
- tables = [
89
- Table.from_et(table_element, doc_frags)
90
- for table_element in et_element.iterfind("TABLES/TABLE")
108
+ end_of_pdu_fields = [
109
+ EndOfPduField.from_et(eofp_element, doc_frags)
110
+ for eofp_element in et_element.iterfind("END-OF-PDU-FIELDS/END-OF-PDU-FIELD")
91
111
  ]
92
112
 
93
- env_data_descs = [
94
- EnvironmentDataDescription.from_et(env_data_desc_element, doc_frags)
95
- for env_data_desc_element in et_element.iterfind("ENV-DATA-DESCS/ENV-DATA-DESC")
113
+ muxs = [
114
+ Multiplexer.from_et(mux_element, doc_frags)
115
+ for mux_element in et_element.iterfind("MUXS/MUX")
96
116
  ]
97
117
 
98
118
  env_data_elements = chain(
@@ -105,42 +125,32 @@ class DiagDataDictionarySpec:
105
125
  for env_data_element in env_data_elements
106
126
  ]
107
127
 
108
- muxs = [
109
- Multiplexer.from_et(mux_element, doc_frags)
110
- for mux_element in et_element.iterfind("MUXS/MUX")
111
- ]
112
-
113
128
  if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
114
129
  unit_spec = UnitSpec.from_et(spec_elem, doc_frags)
115
130
  else:
116
131
  unit_spec = None
117
132
 
118
- # TODO: Parse different specs.. Which of them are needed?
119
- for (path, name) in [
120
- ("STATIC-FIELDS", "static fields"),
121
- ("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD", "dynamic length fields"),
122
- (
123
- "DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD",
124
- "dynamic endmarker fields",
125
- ),
126
- ]:
127
- num = len(list(et_element.iterfind(path)))
128
- if num > 0:
129
- logger.info(f"Not implemented: Did not parse {num} {name}.")
133
+ tables = [
134
+ Table.from_et(table_element, doc_frags)
135
+ for table_element in et_element.iterfind("TABLES/TABLE")
136
+ ]
130
137
 
131
138
  sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
132
139
 
133
140
  return DiagDataDictionarySpec(
141
+ admin_data=admin_data,
142
+ dtc_dops=NamedItemList(dtc_dops),
143
+ env_data_descs=NamedItemList(env_data_descs),
134
144
  data_object_props=NamedItemList(data_object_props),
135
145
  structures=NamedItemList(structures),
136
- end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
146
+ static_fields=NamedItemList(static_fields),
137
147
  dynamic_length_fields=NamedItemList(dynamic_length_fields),
138
- dtc_dops=NamedItemList(dtc_dops),
148
+ #dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
149
+ end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
150
+ muxs=NamedItemList(muxs),
151
+ env_datas=NamedItemList(env_datas),
139
152
  unit_spec=unit_spec,
140
153
  tables=NamedItemList(tables),
141
- env_data_descs=NamedItemList(env_data_descs),
142
- env_datas=NamedItemList(env_datas),
143
- muxs=NamedItemList(muxs),
144
154
  sdgs=sdgs,
145
155
  )
146
156
 
@@ -148,81 +158,96 @@ class DiagDataDictionarySpec:
148
158
  # note that DataDictionarySpec objects do not exhibit an ODXLINK id.
149
159
  odxlinks = {}
150
160
 
151
- for data_object_prop in self.data_object_props:
152
- odxlinks.update(data_object_prop._build_odxlinks())
161
+ if self.admin_data is not None:
162
+ odxlinks.update(self.admin_data._build_odxlinks())
153
163
  for dtc_dop in self.dtc_dops:
154
164
  odxlinks.update(dtc_dop._build_odxlinks())
155
165
  for env_data_desc in self.env_data_descs:
156
166
  odxlinks.update(env_data_desc._build_odxlinks())
157
- for env_data in self.env_datas:
158
- odxlinks.update(env_data._build_odxlinks())
159
- for mux in self.muxs:
160
- odxlinks.update(mux._build_odxlinks())
161
- for sdg in self.sdgs:
162
- odxlinks.update(sdg._build_odxlinks())
167
+ for data_object_prop in self.data_object_props:
168
+ odxlinks.update(data_object_prop._build_odxlinks())
163
169
  for structure in self.structures:
164
170
  odxlinks.update(structure._build_odxlinks())
171
+ for static_field in self.static_fields:
172
+ odxlinks.update(static_field._build_odxlinks())
165
173
  for dynamic_length_field in self.dynamic_length_fields:
166
174
  odxlinks.update(dynamic_length_field._build_odxlinks())
175
+ #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
+ # odxlinks.update(dynamic_endmarker_field._build_odxlinks())
167
177
  for end_of_pdu_field in self.end_of_pdu_fields:
168
178
  odxlinks.update(end_of_pdu_field._build_odxlinks())
169
- for table in self.tables:
170
- odxlinks.update(table._build_odxlinks())
171
-
179
+ for mux in self.muxs:
180
+ odxlinks.update(mux._build_odxlinks())
181
+ for env_data in self.env_datas:
182
+ odxlinks.update(env_data._build_odxlinks())
172
183
  if self.unit_spec is not None:
173
184
  odxlinks.update(self.unit_spec._build_odxlinks())
185
+ for table in self.tables:
186
+ odxlinks.update(table._build_odxlinks())
187
+ for sdg in self.sdgs:
188
+ odxlinks.update(sdg._build_odxlinks())
174
189
 
175
190
  return odxlinks
176
191
 
177
192
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
178
- for data_object_prop in self.data_object_props:
179
- data_object_prop._resolve_odxlinks(odxlinks)
193
+ if self.admin_data is not None:
194
+ self.admin_data._resolve_odxlinks(odxlinks)
180
195
  for dtc_dop in self.dtc_dops:
181
196
  dtc_dop._resolve_odxlinks(odxlinks)
197
+ for env_data_desc in self.env_data_descs:
198
+ env_data_desc._resolve_odxlinks(odxlinks)
199
+ for data_object_prop in self.data_object_props:
200
+ data_object_prop._resolve_odxlinks(odxlinks)
201
+ for structure in self.structures:
202
+ structure._resolve_odxlinks(odxlinks)
203
+ for static_field in self.static_fields:
204
+ static_field._resolve_odxlinks(odxlinks)
182
205
  for dynamic_length_field in self.dynamic_length_fields:
183
206
  dynamic_length_field._resolve_odxlinks(odxlinks)
207
+ #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
+ # dynamic_endmarker_field._resolve_odxlinks(odxlinks)
184
209
  for end_of_pdu_field in self.end_of_pdu_fields:
185
210
  end_of_pdu_field._resolve_odxlinks(odxlinks)
186
- for env_data_desc in self.env_data_descs:
187
- env_data_desc._resolve_odxlinks(odxlinks)
188
- for env_data in self.env_datas:
189
- env_data._resolve_odxlinks(odxlinks)
190
211
  for mux in self.muxs:
191
212
  mux._resolve_odxlinks(odxlinks)
192
- for sdg in self.sdgs:
193
- sdg._resolve_odxlinks(odxlinks)
194
- for structure in self.structures:
195
- structure._resolve_odxlinks(odxlinks)
196
- for table in self.tables:
197
- table._resolve_odxlinks(odxlinks)
198
-
213
+ for env_data in self.env_datas:
214
+ env_data._resolve_odxlinks(odxlinks)
199
215
  if self.unit_spec is not None:
200
216
  self.unit_spec._resolve_odxlinks(odxlinks)
217
+ for table in self.tables:
218
+ table._resolve_odxlinks(odxlinks)
219
+ for sdg in self.sdgs:
220
+ sdg._resolve_odxlinks(odxlinks)
201
221
 
202
222
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
203
- for data_object_prop in self.data_object_props:
204
- data_object_prop._resolve_snrefs(diag_layer)
223
+ if self.admin_data is not None:
224
+ self.admin_data._resolve_snrefs(diag_layer)
205
225
  for dtc_dop in self.dtc_dops:
206
226
  dtc_dop._resolve_snrefs(diag_layer)
227
+ for env_data_desc in self.env_data_descs:
228
+ env_data_desc._resolve_snrefs(diag_layer)
229
+ for data_object_prop in self.data_object_props:
230
+ data_object_prop._resolve_snrefs(diag_layer)
231
+ for structure in self.structures:
232
+ structure._resolve_snrefs(diag_layer)
233
+ for static_field in self.static_fields:
234
+ static_field._resolve_snrefs(diag_layer)
207
235
  for dynamic_length_field in self.dynamic_length_fields:
208
236
  dynamic_length_field._resolve_snrefs(diag_layer)
237
+ #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
+ # dynamic_endmarker_field._resolve_snrefs(diag_layer)
209
239
  for end_of_pdu_field in self.end_of_pdu_fields:
210
240
  end_of_pdu_field._resolve_snrefs(diag_layer)
211
- for env_data_desc in self.env_data_descs:
212
- env_data_desc._resolve_snrefs(diag_layer)
213
- for env_data in self.env_datas:
214
- env_data._resolve_snrefs(diag_layer)
215
241
  for mux in self.muxs:
216
242
  mux._resolve_snrefs(diag_layer)
217
- for sdg in self.sdgs:
218
- sdg._resolve_snrefs(diag_layer)
219
- for structure in self.structures:
220
- structure._resolve_snrefs(diag_layer)
221
- for table in self.tables:
222
- table._resolve_snrefs(diag_layer)
223
-
243
+ for env_data in self.env_datas:
244
+ env_data._resolve_snrefs(diag_layer)
224
245
  if self.unit_spec is not None:
225
246
  self.unit_spec._resolve_snrefs(diag_layer)
247
+ for table in self.tables:
248
+ table._resolve_snrefs(diag_layer)
249
+ for sdg in self.sdgs:
250
+ sdg._resolve_snrefs(diag_layer)
226
251
 
227
252
  @property
228
253
  def all_data_object_properties(self) -> NamedItemList[DopBase]:
odxtools/diaglayer.py CHANGED
@@ -20,7 +20,7 @@ from .diaglayerraw import DiagLayerRaw
20
20
  from .diaglayertype import DiagLayerType
21
21
  from .diagservice import DiagService
22
22
  from .ecuvariantpattern import EcuVariantPattern
23
- from .exceptions import DecodeError, OdxWarning, odxassert
23
+ from .exceptions import DecodeError, OdxWarning, odxassert, odxraise
24
24
  from .functionalclass import FunctionalClass
25
25
  from .message import Message
26
26
  from .nameditemlist import NamedItemList, OdxNamed
@@ -37,7 +37,6 @@ from .table import Table
37
37
  from .unitgroup import UnitGroup
38
38
  from .unitspec import UnitSpec
39
39
 
40
- T = TypeVar("T")
41
40
  TNamed = TypeVar("TNamed", bound=OdxNamed)
42
41
 
43
42
  PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
@@ -54,6 +53,9 @@ class DiagLayer:
54
53
 
55
54
  diag_layer_raw: DiagLayerRaw
56
55
 
56
+ def __post_init__(self) -> None:
57
+ self._global_negative_responses: NamedItemList[Response]
58
+
57
59
  @staticmethod
58
60
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
59
61
  diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
@@ -193,9 +195,11 @@ class DiagLayer:
193
195
  # inherited objects. To me, this seems rather inelegant, but
194
196
  # hey, it's described like this in the standard.
195
197
  self._diag_data_dictionary_spec = DiagDataDictionarySpec(
198
+ admin_data=None,
196
199
  data_object_props=dops,
197
200
  dtc_dops=dtc_dops,
198
201
  structures=structures,
202
+ static_fields=NamedItemList(),
199
203
  end_of_pdu_fields=end_of_pdu_fields,
200
204
  dynamic_length_fields=dynamic_length_fields,
201
205
  tables=tables,
@@ -350,7 +354,7 @@ class DiagLayer:
350
354
  return self._global_negative_responses
351
355
 
352
356
  @property
353
- @deprecated(details="use diag_data_dictionary_spec.tables")
357
+ @deprecated(details="use diag_data_dictionary_spec.tables") # type: ignore[misc]
354
358
  def tables(self) -> NamedItemList[Table]:
355
359
  return self.diag_data_dictionary_spec.tables
356
360
 
@@ -389,9 +393,9 @@ class DiagLayer:
389
393
 
390
394
  def _compute_available_objects(
391
395
  self,
392
- get_local_objects: Callable[["DiagLayer"], Iterable[T]],
396
+ get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
393
397
  get_not_inherited: Callable[[ParentRef], Iterable[str]],
394
- ) -> Iterable[T]:
398
+ ) -> Iterable[TNamed]:
395
399
  """Helper method to compute the set of all objects applicable
396
400
  to the DiagLayer if these objects are subject to the value
397
401
  inheritance mechanism
@@ -410,31 +414,72 @@ class DiagLayer:
410
414
 
411
415
  """
412
416
 
413
- result_dict: Dict[str, T] = {}
417
+ local_objects = get_local_objects(self)
418
+ local_object_short_names = {x.short_name for x in local_objects}
419
+ result_dict: Dict[str, Tuple[TNamed, DiagLayer]] = {}
414
420
 
415
421
  # populate the result dictionary with the inherited objects
416
- #
417
- # TODO (?): make sure that there are no "illegal" collisions
418
- # i.e., different objects with the same short name stemming
419
- # from parent layers exhibiting the same priority that are not
420
- # overwritten by a locally defined object. (IMO, this is quite
421
- # a corner case.)
422
- for parent_ref in self._get_parent_refs_sorted_by_priority():
422
+ for parent_ref in self._get_parent_refs_sorted_by_priority(reverse=True):
423
423
  parent_dl = parent_ref.layer
424
- for dc in parent_dl._compute_available_objects(get_local_objects, get_not_inherited):
425
- result_dict[dc.short_name] = dc # type: ignore[attr-defined]
426
424
 
427
- # remove the explictly not inherited objects
428
- for sn in get_not_inherited(parent_ref):
429
- if sn in result_dict:
430
- del result_dict[sn]
425
+ # retrieve the set of short names of the objects which we
426
+ # are not supposed to inherit
427
+ not_inherited_short_names = set(get_not_inherited(parent_ref))
431
428
 
432
- # consider the locally defined objects (override the
433
- # inherited entries or add new ones)
434
- for obj in get_local_objects(self):
435
- result_dict[obj.short_name] = obj # type: ignore[attr-defined]
429
+ # compute the list of objects which we are supposed to
430
+ # inherit from this diagnostic layer
431
+ inherited_objects = [
432
+ x
433
+ for x in parent_dl._compute_available_objects(get_local_objects, get_not_inherited)
434
+ if x.short_name not in not_inherited_short_names
435
+ ]
436
436
 
437
- return result_dict.values()
437
+ # update the result set with the objects from the current parent_ref
438
+ for obj in inherited_objects:
439
+
440
+ # no object with the given short name currently
441
+ # exits. add it to the result set and continue
442
+ if obj.short_name not in result_dict:
443
+ result_dict[obj.short_name] = (obj, parent_dl)
444
+ continue
445
+
446
+ # if an object with a given name already exists,
447
+ # there's no problem if it was inherited from a parent
448
+ # of different priority than the one currently
449
+ # considered
450
+ orig_prio = result_dict[obj.short_name][1].variant_type.inheritance_priority
451
+ new_prio = parent_dl.variant_type.inheritance_priority
452
+ if new_prio < orig_prio:
453
+ continue
454
+ elif orig_prio < new_prio:
455
+ result_dict[obj.short_name] = (obj, parent_dl)
456
+ continue
457
+
458
+ # if there is a conflict on the same priority level,
459
+ # it does not matter if the object is overridden
460
+ # locally anyway...
461
+ if obj.short_name in local_object_short_names:
462
+ continue
463
+
464
+ # if all of these conditions do not apply, and if the
465
+ # inherited objects are identical, there is no
466
+ # conflict. (note that value comparisons of complete
467
+ # complex objects tend to be expensive, so this test
468
+ # is done last.)
469
+ if obj == result_dict[obj.short_name][0]:
470
+ continue
471
+
472
+ odxraise(f"Diagnostic layer {self.short_name} cannot inherit object "
473
+ f"{obj.short_name} due to an unresolveable inheritance conflict between "
474
+ f"parent layers {result_dict[obj.short_name][1].short_name} "
475
+ f"and {parent_dl.short_name}")
476
+
477
+ # add the locally defined entries, overriding the inherited
478
+ # ones if necessary
479
+ for obj in local_objects:
480
+ result_dict[obj.short_name] = (obj, self)
481
+
482
+ return [x[0] for x in result_dict.values()]
438
483
 
439
484
  def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
440
485
  """Return the list of locally defined diagnostic communications.
@@ -767,7 +812,7 @@ class DiagLayer:
767
812
 
768
813
  return int(result)
769
814
 
770
- @deprecated(details="use get_can_receive_id()")
815
+ @deprecated(details="use get_can_receive_id()") # type: ignore[misc]
771
816
  def get_receive_id(self) -> Optional[int]:
772
817
  return self.get_can_receive_id()
773
818
 
@@ -796,7 +841,7 @@ class DiagLayer:
796
841
 
797
842
  return int(result)
798
843
 
799
- @deprecated(details="use get_can_send_id()")
844
+ @deprecated(details="use get_can_send_id()") # type: ignore[misc]
800
845
  def get_send_id(self) -> Optional[int]:
801
846
  return self.get_can_send_id()
802
847
 
@@ -1097,7 +1142,8 @@ class DiagLayer:
1097
1142
 
1098
1143
  if len(decoded_messages) == 0:
1099
1144
  raise DecodeError(
1100
- f"None of the services {candidate_services} could parse {message.hex()}.")
1145
+ f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
1146
+ )
1101
1147
 
1102
1148
  return decoded_messages
1103
1149
 
@@ -1106,14 +1152,8 @@ class DiagLayer:
1106
1152
 
1107
1153
  return self._decode(message, candidate_services)
1108
1154
 
1109
- def decode_response(self, response: bytes, request: Union[bytes, Message]) -> List[Message]:
1110
- if isinstance(request, Message):
1111
- candidate_services = [request.service]
1112
- else:
1113
- if not isinstance(request, (bytes, bytearray)):
1114
- raise TypeError(f"Request parameter must have type "
1115
- f"Message, bytes or bytearray but was {type(request)}")
1116
- candidate_services = self._find_services_for_uds(request)
1155
+ def decode_response(self, response: bytes, request: bytes) -> List[Message]:
1156
+ candidate_services = self._find_services_for_uds(request)
1117
1157
  if candidate_services is None:
1118
1158
  raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
1119
1159