odxtools 5.2.6__py3-none-any.whl → 5.3.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 (73) hide show
  1. odxtools/basicstructure.py +2 -2
  2. odxtools/cli/browse.py +18 -20
  3. odxtools/cli/dummy_sub_parser.py +3 -3
  4. odxtools/cli/main.py +1 -1
  5. odxtools/compumethods/compuscale.py +1 -2
  6. odxtools/compumethods/createanycompumethod.py +0 -1
  7. odxtools/compumethods/identicalcompumethod.py +0 -2
  8. odxtools/compumethods/limit.py +1 -1
  9. odxtools/compumethods/linearcompumethod.py +1 -1
  10. odxtools/compumethods/scalelinearcompumethod.py +0 -1
  11. odxtools/compumethods/tabintpcompumethod.py +0 -1
  12. odxtools/determinenumberofitems.py +18 -0
  13. odxtools/diagdatadictionaryspec.py +14 -4
  14. odxtools/diaglayer.py +54 -59
  15. odxtools/dynamiclengthfield.py +59 -0
  16. odxtools/element.py +2 -4
  17. odxtools/endofpdufield.py +6 -77
  18. odxtools/field.py +94 -0
  19. odxtools/isotp_state_machine.py +20 -24
  20. odxtools/matchingparameter.py +1 -1
  21. odxtools/multiplexer.py +6 -18
  22. odxtools/multiplexercase.py +22 -6
  23. odxtools/multiplexerdefaultcase.py +10 -2
  24. odxtools/multiplexerswitchkey.py +8 -43
  25. odxtools/odxlink.py +8 -4
  26. odxtools/odxtypes.py +1 -1
  27. odxtools/parameters/codedconstparameter.py +4 -2
  28. odxtools/parameters/dynamicparameter.py +6 -4
  29. odxtools/parameters/lengthkeyparameter.py +8 -3
  30. odxtools/parameters/matchingrequestparameter.py +5 -3
  31. odxtools/parameters/nrcconstparameter.py +4 -2
  32. odxtools/parameters/parameter.py +21 -6
  33. odxtools/parameters/parameterwithdop.py +6 -1
  34. odxtools/parameters/physicalconstantparameter.py +4 -2
  35. odxtools/parameters/reservedparameter.py +4 -2
  36. odxtools/parameters/systemparameter.py +5 -3
  37. odxtools/parameters/tableentryparameter.py +5 -3
  38. odxtools/parameters/tablekeyparameter.py +8 -4
  39. odxtools/parameters/tablestructparameter.py +4 -2
  40. odxtools/parameters/valueparameter.py +5 -3
  41. odxtools/positioneddataobjectproperty.py +74 -0
  42. odxtools/progcode.py +2 -3
  43. odxtools/tablerow.py +1 -2
  44. odxtools/templates/macros/printAudience.xml.jinja2 +3 -9
  45. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -27
  46. odxtools/templates/macros/printComparam.xml.jinja2 +4 -18
  47. odxtools/templates/macros/printDOP.xml.jinja2 +3 -9
  48. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +22 -0
  49. odxtools/templates/macros/printElementID.xml.jinja2 +6 -6
  50. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +3 -2
  51. odxtools/templates/macros/printEnvData.xml.jinja2 +2 -2
  52. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +3 -2
  53. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +3 -9
  54. odxtools/templates/macros/printMux.xml.jinja2 +13 -6
  55. odxtools/templates/macros/printParam.xml.jinja2 +2 -7
  56. odxtools/templates/macros/printRequest.xml.jinja2 +2 -9
  57. odxtools/templates/macros/printResponse.xml.jinja2 +2 -9
  58. odxtools/templates/macros/printService.xml.jinja2 +2 -9
  59. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  60. odxtools/templates/macros/printState.xml.jinja2 +3 -9
  61. odxtools/templates/macros/printStateChart.xml.jinja2 +2 -9
  62. odxtools/templates/macros/printStateTransition.xml.jinja2 +3 -9
  63. odxtools/templates/macros/printStructure.xml.jinja2 +2 -4
  64. odxtools/templates/macros/printTable.xml.jinja2 +2 -7
  65. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -3
  66. odxtools/templates/macros/printVariant.xml.jinja2 +10 -9
  67. odxtools/version.py +4 -2
  68. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/METADATA +70 -13
  69. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/RECORD +73 -68
  70. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/LICENSE +0 -0
  71. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/WHEEL +0 -0
  72. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/entry_points.txt +0 -0
  73. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/top_level.txt +0 -0
@@ -77,7 +77,7 @@ class BasicStructure(DopBase):
77
77
  def required_parameters(self) -> List[Parameter]:
78
78
  """Return the list of parameters which are required for
79
79
  encoding the structure."""
80
- return [p for p in self.parameters if p.is_required()]
80
+ return [p for p in self.parameters if p.is_required]
81
81
 
82
82
  @property
83
83
  def free_parameters(self) -> List[Union[Parameter, "EndOfPduField"]]: # type: ignore
@@ -96,7 +96,7 @@ class BasicStructure(DopBase):
96
96
  if isinstance(param, EndOfPduField):
97
97
  result.append(param)
98
98
  continue
99
- elif not param.is_required():
99
+ elif not param.is_required:
100
100
  continue
101
101
  # The user cannot specify MatchingRequestParameters freely!
102
102
  elif isinstance(param, MatchingRequestParameter):
odxtools/cli/browse.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  import sys
5
5
  from typing import Dict, List, Union
6
6
 
7
- import PyInquirer
7
+ import PyInquirer.prompt as PI_prompt
8
8
 
9
9
  from ..database import Database
10
10
  from ..diaglayer import DiagLayer
@@ -38,7 +38,7 @@ def _convert_string_to_bytes(string_value):
38
38
 
39
39
 
40
40
  def _validate_string_value(input, parameter):
41
- if parameter.is_optional() and input == "":
41
+ if parameter.is_optional and input == "":
42
42
  return True
43
43
  elif isinstance(parameter, ParameterWithDOP):
44
44
  try:
@@ -62,7 +62,7 @@ def prompt_single_parameter_value(parameter):
62
62
  parameter.short_name,
63
63
  "message":
64
64
  f"Value for parameter '{parameter.short_name}' (Type: {parameter.physical_type.base_data_type})"
65
- + (f"[optional]" if parameter.is_optional() else ""),
65
+ + (f"[optional]" if parameter.is_optional else ""),
66
66
  # TODO: improve validation
67
67
  "validate":
68
68
  lambda x: _validate_string_value(x, parameter),
@@ -79,8 +79,8 @@ def prompt_single_parameter_value(parameter):
79
79
  "message": f"Value for parameter '{parameter.short_name}'",
80
80
  "choices": parameter.get_valid_physical_values(),
81
81
  }]
82
- answer = PyInquirer.prompt(param_prompt)
83
- if answer.get(parameter.short_name) == "" and parameter.is_optional():
82
+ answer = PI_prompt.prompt(param_prompt)
83
+ if answer.get(parameter.short_name) == "" and parameter.is_optional:
84
84
  return None
85
85
  elif parameter.physical_type.base_data_type is not None:
86
86
  return _convert_string_to_odx_type(
@@ -97,15 +97,13 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
97
97
  raise SystemError("This command can only be used in an interactive shell!")
98
98
  param_dict = sub_service.parameter_dict()
99
99
 
100
- # list(filter(lambda p: p.is_required()
101
- # or p.is_optional(), sub_service.parameters))
102
100
  exists_definable_param = False
103
101
  for k, param_or_dict in param_dict.items():
104
102
  if isinstance(param_or_dict, dict):
105
103
  for k, param in param_or_dict.items():
106
- if param.is_required() or param.is_optional():
104
+ if param.is_settable:
107
105
  exists_definable_param = True
108
- elif param_or_dict.is_required() or param_or_dict.is_optional():
106
+ elif param_or_dict.is_settable:
109
107
  exists_definable_param = True
110
108
 
111
109
  param_values = {}
@@ -118,7 +116,7 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
118
116
  "message": f"Do you want to encode a message? [y/n]",
119
117
  "choices": ["yes", "no"],
120
118
  }]
121
- answer = PyInquirer.prompt(encode_message_prompt)
119
+ answer = PI_prompt.prompt(encode_message_prompt)
122
120
  if answer.get("yes_no_prompt") == "no":
123
121
  return
124
122
 
@@ -133,7 +131,7 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
133
131
  "filter":
134
132
  lambda input: _convert_string_to_bytes(input),
135
133
  }]
136
- answer = PyInquirer.prompt(answered_request_prompt)
134
+ answer = PI_prompt.prompt(answered_request_prompt)
137
135
  answered_request = answer.get("request")
138
136
  print(f"Input interpretation as list: {list(answered_request)}")
139
137
 
@@ -146,13 +144,12 @@ def encode_message_interactively(sub_service, ask_user_confirmation=False):
146
144
  )
147
145
  structure_param_values = {}
148
146
  for param_sn, param in param_or_structure.items():
149
- if param.is_required() or param.is_optional():
147
+ if param.is_settable:
150
148
  val = prompt_single_parameter_value(param)
151
149
  if val is not None:
152
150
  structure_param_values[param_sn] = val
153
151
  param_values[key] = structure_param_values
154
- elif (param_or_structure.is_required() or param_or_structure.is_optional()
155
- ) and param_or_structure.parameter_type != "MATCHING-REQUEST-PARAM":
152
+ elif param_or_structure.is_settable:
156
153
  # param_or_structure is a parameter
157
154
  val = prompt_single_parameter_value(param_or_structure)
158
155
  if val is not None:
@@ -183,12 +180,13 @@ def encode_message_from_string_values(
183
180
  structured_value = parameter_values.get(parameter_sn)
184
181
  if not isinstance(simple_param, Parameter):
185
182
  continue
186
- if simple_param.is_required() and (not isinstance(structured_value, dict) or
187
- structured_value.get(simple_param_sn) is None):
183
+ if simple_param.is_required and (not isinstance(structured_value, dict) or
184
+ structured_value.get(simple_param_sn) is None):
188
185
  missing_parameter_names.append(f"{parameter_sn} :: {simple_param_sn}")
189
186
  else:
190
- if parameter.is_required() and parameter_values.get(parameter_sn) is None:
187
+ if parameter.is_required and parameter_values.get(parameter_sn) is None:
191
188
  missing_parameter_names.append(parameter_sn)
189
+
192
190
  if len(missing_parameter_names) > 0:
193
191
  print("The following parameters are required but missing!")
194
192
  print(" - " + "\n - ".join(missing_parameter_names))
@@ -245,7 +243,7 @@ def browse(odxdb: Database):
245
243
  "message": "Select a Variant.",
246
244
  "choices": list(dl_names) + ["[exit]"],
247
245
  }]
248
- answer = PyInquirer.prompt(selection)
246
+ answer = PI_prompt.prompt(selection)
249
247
  if answer.get("variant") == "[exit]":
250
248
  return
251
249
 
@@ -281,7 +279,7 @@ def browse(odxdb: Database):
281
279
  f"The variant {variant.short_name} offers the following services. Select one!",
282
280
  "choices": [s.short_name for s in services] + ["[back]"],
283
281
  }]
284
- answer = PyInquirer.prompt(selection)
282
+ answer = PI_prompt.prompt(selection)
285
283
  if answer.get("service") == "[back]":
286
284
  break
287
285
 
@@ -315,7 +313,7 @@ def browse(odxdb: Database):
315
313
  "short": f"Negative response: {nr.short_name}",
316
314
  } for nr in service.negative_responses] + ["[back]"], # type: ignore
317
315
  }]
318
- answer = PyInquirer.prompt(selection)
316
+ answer = PI_prompt.prompt(selection)
319
317
  if answer.get("message_type") == "[back]":
320
318
  continue
321
319
 
@@ -14,11 +14,11 @@ class DummyTool:
14
14
  should bail out.
15
15
  """
16
16
 
17
- def __init__(self, tool_name, error):
17
+ def __init__(self, tool_name: str, error: Exception):
18
18
  self._odxtools_tool_name_ = tool_name
19
19
  self._error = error
20
20
 
21
- def add_subparser(self, subparser_list):
21
+ def add_subparser(self, subparser_list: "argparse._SubParsersAction") -> None:
22
22
  subparser_list.add_parser(
23
23
  self._odxtools_tool_name_,
24
24
  description=f"Tool '{self._odxtools_tool_name_}' is unavailable: {self._error}",
@@ -26,7 +26,7 @@ class DummyTool:
26
26
  formatter_class=argparse.RawTextHelpFormatter,
27
27
  )
28
28
 
29
- def run(self, args: argparse.Namespace):
29
+ def run(self, args: argparse.Namespace) -> None:
30
30
  print(
31
31
  f"Error: Tool '{self._odxtools_tool_name_}' is unavailable: {self._error}",
32
32
  file=sys.stderr,
odxtools/cli/main.py CHANGED
@@ -16,7 +16,7 @@ for tool_name in ["list", "browse", "snoop", "find"]:
16
16
  tool_modules.append(DummyTool(tool_name, e))
17
17
 
18
18
 
19
- def start_cli():
19
+ def start_cli() -> None:
20
20
  argparser = argparse.ArgumentParser(
21
21
  description="\n".join([
22
22
  "Utilities to interact with automotive diagnostic descriptions based on the ODX standard.",
@@ -1,9 +1,8 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, NamedTuple, Optional, Union
3
+ from typing import List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from ..exceptions import odxrequire
7
6
  from ..odxlink import OdxDocFragment
8
7
  from ..odxtypes import AtomicOdxType, DataType
9
8
  from ..utils import create_description_from_et
@@ -7,7 +7,6 @@ from ..exceptions import OdxWarning, odxassert, odxraise, odxrequire
7
7
  from ..globals import logger
8
8
  from ..odxlink import OdxDocFragment
9
9
  from ..odxtypes import DataType
10
- from ..utils import create_description_from_et
11
10
  from .compumethod import CompuMethod
12
11
  from .compuscale import CompuScale
13
12
  from .identicalcompumethod import IdenticalCompuMethod
@@ -1,8 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Union
4
3
 
5
- from ..odxtypes import DataType
6
4
  from .compumethod import CompuMethod, CompuMethodCategory
7
5
 
8
6
 
@@ -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 NamedTuple, Optional, Union
4
+ from typing import Optional, Union
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from ..exceptions import odxassert, odxraise, odxrequire
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Optional, Union
3
+ from typing import Union
4
4
 
5
5
  from ..exceptions import odxassert
6
6
  from ..odxtypes import DataType
@@ -3,7 +3,6 @@ from dataclasses import dataclass
3
3
  from typing import List
4
4
 
5
5
  from ..exceptions import odxassert
6
- from ..globals import logger
7
6
  from .compumethod import CompuMethod, CompuMethodCategory
8
7
  from .linearcompumethod import LinearCompuMethod
9
8
 
@@ -3,7 +3,6 @@ from dataclasses import dataclass
3
3
  from typing import List, Tuple, Union
4
4
 
5
5
  from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
6
- from ..globals import logger
7
6
  from ..odxtypes import DataType
8
7
  from .compumethod import CompuMethod, CompuMethodCategory
9
8
  from .limit import IntervalType, Limit
@@ -0,0 +1,18 @@
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+ from xml.etree import ElementTree
4
+
5
+ from .odxlink import OdxDocFragment
6
+ from .positioneddataobjectproperty import PositionedDataObjectProperty
7
+ from .utils import dataclass_fields_asdict
8
+
9
+
10
+ @dataclass
11
+ class DetermineNumberOfItems(PositionedDataObjectProperty):
12
+
13
+ @staticmethod
14
+ def from_et(et_element: ElementTree.Element,
15
+ doc_frags: List[OdxDocFragment]) -> "DetermineNumberOfItems":
16
+ kwargs = dataclass_fields_asdict(
17
+ PositionedDataObjectProperty.from_et(et_element, doc_frags))
18
+ return DetermineNumberOfItems(**kwargs)
@@ -9,6 +9,7 @@ from .createanystructure import create_any_structure_from_et
9
9
  from .createsdgs import create_sdgs_from_et
10
10
  from .dataobjectproperty import DataObjectProperty
11
11
  from .dtcdop import DtcDop
12
+ from .dynamiclengthfield import DynamicLengthField
12
13
  from .endofpdufield import EndOfPduField
13
14
  from .environmentdata import EnvironmentData
14
15
  from .environmentdatadescription import EnvironmentDataDescription
@@ -31,6 +32,7 @@ class DiagDataDictionarySpec:
31
32
  data_object_props: NamedItemList[DataObjectProperty]
32
33
  structures: NamedItemList[BasicStructure]
33
34
  end_of_pdu_fields: NamedItemList[EndOfPduField]
35
+ dynamic_length_fields: NamedItemList[DynamicLengthField]
34
36
  tables: NamedItemList[Table]
35
37
  env_data_descs: NamedItemList[EnvironmentDataDescription]
36
38
  env_datas: NamedItemList[EnvironmentData]
@@ -44,6 +46,7 @@ class DiagDataDictionarySpec:
44
46
  self.data_object_props,
45
47
  self.structures,
46
48
  self.end_of_pdu_fields,
49
+ self.dynamic_length_fields,
47
50
  self.dtc_dops,
48
51
  self.tables,
49
52
  ),)
@@ -67,6 +70,11 @@ class DiagDataDictionarySpec:
67
70
  for eofp_element in et_element.iterfind("END-OF-PDU-FIELDS/END-OF-PDU-FIELD")
68
71
  ]
69
72
 
73
+ dynamic_length_fields = [
74
+ DynamicLengthField.from_et(dl_element, doc_frags)
75
+ for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
76
+ ]
77
+
70
78
  dtc_dops = []
71
79
  for dtc_dop_elem in et_element.iterfind("DTC-DOPS/DTC-DOP"):
72
80
  dtc_dop = DtcDop.from_et(dtc_dop_elem, doc_frags)
@@ -123,6 +131,7 @@ class DiagDataDictionarySpec:
123
131
  data_object_props=NamedItemList(data_object_props),
124
132
  structures=NamedItemList(structures),
125
133
  end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
134
+ dynamic_length_fields=NamedItemList(dynamic_length_fields),
126
135
  dtc_dops=NamedItemList(dtc_dops),
127
136
  unit_spec=unit_spec,
128
137
  tables=NamedItemList(tables),
@@ -145,6 +154,7 @@ class DiagDataDictionarySpec:
145
154
  self.sdgs,
146
155
  self.structures,
147
156
  self.end_of_pdu_fields,
157
+ self.dynamic_length_fields,
148
158
  self.tables,
149
159
  ):
150
160
  odxlinks.update(obj._build_odxlinks())
@@ -157,8 +167,8 @@ class DiagDataDictionarySpec:
157
167
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
158
168
 
159
169
  for obj in chain(self.data_object_props, self.dtc_dops, self.end_of_pdu_fields,
160
- self.env_data_descs, self.env_datas, self.muxs, self.sdgs, self.structures,
161
- self.tables):
170
+ self.dynamic_length_fields, self.env_data_descs, self.env_datas, self.muxs,
171
+ self.sdgs, self.structures, self.tables):
162
172
  obj._resolve_odxlinks(odxlinks)
163
173
 
164
174
  if self.unit_spec is not None:
@@ -166,8 +176,8 @@ class DiagDataDictionarySpec:
166
176
 
167
177
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
168
178
  for obj in chain(self.data_object_props, self.dtc_dops, self.end_of_pdu_fields,
169
- self.env_data_descs, self.env_datas, self.muxs, self.sdgs, self.structures,
170
- self.tables):
179
+ self.dynamic_length_fields, self.env_data_descs, self.env_datas, self.muxs,
180
+ self.sdgs, self.structures, self.tables):
171
181
  obj._resolve_snrefs(diag_layer)
172
182
 
173
183
  if self.unit_spec is not None:
odxtools/diaglayer.py CHANGED
@@ -10,24 +10,17 @@ from deprecation import deprecated
10
10
 
11
11
  from .additionalaudience import AdditionalAudience
12
12
  from .admindata import AdminData
13
- from .basicstructure import BasicStructure
14
13
  from .communicationparameterref import CommunicationParameterRef
15
14
  from .companydata import CompanyData
16
- from .dataobjectproperty import DataObjectProperty
17
15
  from .diagdatadictionaryspec import DiagDataDictionarySpec
18
16
  from .diaglayerraw import DiagLayerRaw
19
17
  from .diaglayertype import DiagLayerType
20
18
  from .diagservice import DiagService
21
- from .dtcdop import DtcDop
22
19
  from .ecuvariantpattern import EcuVariantPattern
23
- from .endofpdufield import EndOfPduField
24
- from .environmentdata import EnvironmentData
25
- from .environmentdatadescription import EnvironmentDataDescription
26
20
  from .exceptions import DecodeError, OdxWarning, odxassert
27
21
  from .functionalclass import FunctionalClass
28
22
  from .message import Message
29
- from .multiplexer import Multiplexer
30
- from .nameditemlist import NamedItemList
23
+ from .nameditemlist import NamedItemList, OdxNamed
31
24
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
32
25
  from .parentref import ParentRef
33
26
  from .request import Request
@@ -40,6 +33,7 @@ from .unitgroup import UnitGroup
40
33
  from .unitspec import UnitSpec
41
34
 
42
35
  T = TypeVar("T")
36
+ TNamed = TypeVar("TNamed", bound=OdxNamed)
43
37
 
44
38
  PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
45
39
 
@@ -154,47 +148,58 @@ class DiagLayer:
154
148
  sdgs=[])
155
149
  ############
156
150
 
157
- dops = NamedItemList(self._compute_available_data_object_props())
158
- tables = NamedItemList(self._compute_available_tables())
159
- dtc_dops: NamedItemList[DtcDop]
160
- structures: NamedItemList[BasicStructure]
161
- end_of_pdu_fields: NamedItemList[EndOfPduField]
162
- env_data_descs: NamedItemList[EnvironmentDataDescription]
163
- env_datas: NamedItemList[EnvironmentData]
164
- muxs: NamedItemList[Multiplexer]
151
+ dops = self._compute_available_ddd_spec_items(
152
+ lambda ddd_spec: ddd_spec.data_object_props,
153
+ lambda parent_ref: parent_ref.not_inherited_dops,
154
+ )
155
+ structures = self._compute_available_ddd_spec_items(
156
+ lambda ddd_spec: ddd_spec.structures,
157
+ lambda parent_ref: parent_ref.not_inherited_dops,
158
+ )
159
+ dtc_dops = self._compute_available_ddd_spec_items(
160
+ lambda ddd_spec: ddd_spec.dtc_dops,
161
+ lambda parent_ref: parent_ref.not_inherited_dops,
162
+ )
163
+ end_of_pdu_fields = self._compute_available_ddd_spec_items(
164
+ lambda ddd_spec: ddd_spec.end_of_pdu_fields,
165
+ lambda parent_ref: parent_ref.not_inherited_dops,
166
+ )
167
+ dynamic_length_fields = self._compute_available_ddd_spec_items(
168
+ lambda ddd_spec: ddd_spec.dynamic_length_fields,
169
+ lambda parent_ref: parent_ref.not_inherited_dops,
170
+ )
171
+ env_data_descs = self._compute_available_ddd_spec_items(
172
+ lambda ddd_spec: ddd_spec.env_data_descs,
173
+ lambda parent_ref: parent_ref.not_inherited_dops,
174
+ )
175
+ env_datas = self._compute_available_ddd_spec_items(
176
+ lambda ddd_spec: ddd_spec.env_datas, lambda parent_ref: parent_ref.not_inherited_dops)
177
+ muxs = self._compute_available_ddd_spec_items(
178
+ lambda ddd_spec: ddd_spec.muxs, lambda parent_ref: parent_ref.not_inherited_dops)
179
+ tables = self._compute_available_ddd_spec_items(
180
+ lambda ddd_spec: ddd_spec.tables, lambda parent_ref: parent_ref.not_inherited_tables)
165
181
  ddds_sdgs: List[SpecialDataGroup]
166
182
  if self.diag_layer_raw.diag_data_dictionary_spec:
167
- dtc_dops = self.diag_layer_raw.diag_data_dictionary_spec.dtc_dops
168
- structures = self.diag_layer_raw.diag_data_dictionary_spec.structures
169
- end_of_pdu_fields = self.diag_layer_raw.diag_data_dictionary_spec.end_of_pdu_fields
170
- env_data_descs = self.diag_layer_raw.diag_data_dictionary_spec.env_data_descs
171
- env_datas = self.diag_layer_raw.diag_data_dictionary_spec.env_datas
172
- muxs = self.diag_layer_raw.diag_data_dictionary_spec.muxs
173
183
  ddds_sdgs = self.diag_layer_raw.diag_data_dictionary_spec.sdgs
174
184
  else:
175
- dtc_dops = NamedItemList()
176
- structures = NamedItemList()
177
- end_of_pdu_fields = NamedItemList()
178
- env_data_descs = NamedItemList()
179
- env_datas = NamedItemList()
180
- muxs = NamedItemList()
181
185
  ddds_sdgs = []
182
186
 
183
187
  # create a DiagDataDictionarySpec which includes all the
184
188
  # inherited objects. To me, this seems rather inelegant, but
185
189
  # hey, it's described like this in the standard.
186
- self._diag_data_dictionary_spec = \
187
- DiagDataDictionarySpec(
188
- data_object_props=dops,
189
- dtc_dops=dtc_dops,
190
- structures=structures,
191
- end_of_pdu_fields=end_of_pdu_fields,
192
- tables=tables,
193
- env_data_descs=env_data_descs,
194
- env_datas=env_datas,
195
- muxs=muxs,
196
- unit_spec=unit_spec,
197
- sdgs=ddds_sdgs)
190
+ self._diag_data_dictionary_spec = DiagDataDictionarySpec(
191
+ data_object_props=dops,
192
+ dtc_dops=dtc_dops,
193
+ structures=structures,
194
+ end_of_pdu_fields=end_of_pdu_fields,
195
+ dynamic_length_fields=dynamic_length_fields,
196
+ tables=tables,
197
+ env_data_descs=env_data_descs,
198
+ env_datas=env_datas,
199
+ muxs=muxs,
200
+ unit_spec=unit_spec,
201
+ sdgs=ddds_sdgs,
202
+ )
198
203
 
199
204
  #####
200
205
  # compute the communication parameters applicable to the
@@ -457,29 +462,19 @@ class DiagLayer:
457
462
 
458
463
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
459
464
 
460
- def _compute_available_data_object_props(self) -> Iterable[DataObjectProperty]:
461
-
462
- def get_local_objects_fn(dl):
463
- if dl.diag_layer_raw.diag_data_dictionary_spec is None:
464
- return []
465
- return dl.diag_layer_raw.diag_data_dictionary_spec.data_object_props
466
-
467
- def not_inherited_fn(parent_ref):
468
- return parent_ref.not_inherited_dops
469
-
470
- return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
471
-
472
- def _compute_available_tables(self) -> Iterable[Table]:
465
+ def _compute_available_ddd_spec_items(
466
+ self,
467
+ include: Callable[[DiagDataDictionarySpec], Iterable[TNamed]],
468
+ exclude: Callable[["ParentRef"], List[str]],
469
+ ) -> NamedItemList[TNamed]:
473
470
 
474
- def get_local_objects_fn(dl):
471
+ def get_local_objects_fn(dl: "DiagLayer"):
475
472
  if dl.diag_layer_raw.diag_data_dictionary_spec is None:
476
473
  return []
477
- return dl.diag_layer_raw.diag_data_dictionary_spec.tables
474
+ return include(dl.diag_layer_raw.diag_data_dictionary_spec)
478
475
 
479
- def not_inherited_fn(parent_ref):
480
- return parent_ref.not_inherited_tables
481
-
482
- return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
476
+ found = self._compute_available_objects(get_local_objects_fn, exclude)
477
+ return NamedItemList(found)
483
478
 
484
479
  def _compute_available_functional_classes(self) -> Iterable[FunctionalClass]:
485
480
 
@@ -0,0 +1,59 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List
4
+ from xml.etree import ElementTree
5
+
6
+ from .decodestate import DecodeState
7
+ from .determinenumberofitems import DetermineNumberOfItems
8
+ from .encodestate import EncodeState
9
+ from .exceptions import odxrequire
10
+ from .field import Field
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
+ from .odxtypes import ParameterValueDict
13
+ from .utils import dataclass_fields_asdict
14
+
15
+ if TYPE_CHECKING:
16
+ from .diaglayer import DiagLayer
17
+
18
+
19
+ @dataclass
20
+ class DynamicLengthField(Field):
21
+ """Array of structure with length field"""
22
+ offset: int
23
+ determine_number_of_items: DetermineNumberOfItems
24
+
25
+ @staticmethod
26
+ def from_et(et_element: ElementTree.Element,
27
+ doc_frags: List[OdxDocFragment]) -> "DynamicLengthField":
28
+ kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
29
+ offset = int(odxrequire(et_element.findtext('OFFSET')))
30
+ determine_number_of_items = DetermineNumberOfItems.from_et(
31
+ odxrequire(et_element.find('DETERMINE-NUMBER-OF-ITEMS')),
32
+ doc_frags,
33
+ )
34
+ return DynamicLengthField(
35
+ offset=offset, determine_number_of_items=determine_number_of_items, **kwargs)
36
+
37
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
38
+ odxlinks = super()._build_odxlinks()
39
+ odxlinks.update(self.determine_number_of_items._build_odxlinks())
40
+ return odxlinks
41
+
42
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
43
+ super()._resolve_odxlinks(odxlinks)
44
+ self.determine_number_of_items._resolve_odxlinks(odxlinks)
45
+
46
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
47
+ super()._resolve_snrefs(diag_layer)
48
+ self.determine_number_of_items._resolve_snrefs(diag_layer)
49
+
50
+ def convert_physical_to_bytes(
51
+ self,
52
+ physical_value: List[ParameterValueDict],
53
+ encode_state: EncodeState,
54
+ bit_position: int = 0,
55
+ ) -> bytes:
56
+ raise NotImplementedError()
57
+
58
+ def convert_bytes_to_physical(self, decode_state: DecodeState, bit_position: int = 0):
59
+ raise NotImplementedError()
odxtools/element.py CHANGED
@@ -2,11 +2,9 @@ from dataclasses import dataclass
2
2
  from typing import List, Optional
3
3
  from xml.etree import ElementTree
4
4
 
5
- from odxtools.exceptions import odxrequire
6
- from odxtools.utils import create_description_from_et
7
-
5
+ from .exceptions import odxrequire
8
6
  from .odxlink import OdxDocFragment, OdxLinkId
9
- from .utils import dataclass_fields_asdict
7
+ from .utils import create_description_from_et, dataclass_fields_asdict
10
8
 
11
9
 
12
10
  @dataclass