odxtools 9.4.1__py3-none-any.whl → 9.6.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 (113) hide show
  1. odxtools/additionalaudience.py +2 -2
  2. odxtools/admindata.py +3 -0
  3. odxtools/audience.py +9 -13
  4. odxtools/basecomparam.py +1 -2
  5. odxtools/basevariantpattern.py +38 -0
  6. odxtools/basicstructure.py +34 -35
  7. odxtools/commrelation.py +2 -1
  8. odxtools/companydata.py +1 -2
  9. odxtools/companyspecificinfo.py +3 -0
  10. odxtools/comparam.py +16 -8
  11. odxtools/comparaminstance.py +12 -12
  12. odxtools/comparamspec.py +4 -3
  13. odxtools/comparamsubset.py +26 -24
  14. odxtools/compumethods/compuconst.py +4 -4
  15. odxtools/compumethods/limit.py +9 -9
  16. odxtools/compumethods/linearsegment.py +8 -8
  17. odxtools/dataobjectproperty.py +16 -18
  18. odxtools/description.py +4 -2
  19. odxtools/determinenumberofitems.py +4 -4
  20. odxtools/diagcodedtype.py +20 -20
  21. odxtools/diagcomm.py +61 -41
  22. odxtools/diagdatadictionaryspec.py +51 -55
  23. odxtools/diaglayercontainer.py +25 -25
  24. odxtools/diaglayers/basevariant.py +5 -0
  25. odxtools/diaglayers/basevariantraw.py +7 -1
  26. odxtools/diaglayers/diaglayerraw.py +26 -27
  27. odxtools/diaglayers/ecuvariant.py +16 -0
  28. odxtools/diagnostictroublecode.py +13 -10
  29. odxtools/diagservice.py +48 -50
  30. odxtools/diagvariable.py +10 -8
  31. odxtools/docrevision.py +5 -5
  32. odxtools/dtcdop.py +17 -17
  33. odxtools/dynamicendmarkerfield.py +8 -8
  34. odxtools/dynamiclengthfield.py +2 -0
  35. odxtools/dyndefinedspec.py +21 -8
  36. odxtools/ecuvariantpattern.py +20 -9
  37. odxtools/encodestate.py +3 -3
  38. odxtools/endofpdufield.py +7 -9
  39. odxtools/environmentdatadescription.py +9 -20
  40. odxtools/field.py +21 -21
  41. odxtools/inputparam.py +15 -14
  42. odxtools/leadinglengthinfotype.py +4 -4
  43. odxtools/matchingbasevariantparameter.py +38 -0
  44. odxtools/matchingparameter.py +108 -28
  45. odxtools/minmaxlengthtype.py +6 -6
  46. odxtools/multiplexer.py +38 -39
  47. odxtools/multiplexercase.py +3 -6
  48. odxtools/multiplexerdefaultcase.py +3 -6
  49. odxtools/multiplexerswitchkey.py +4 -4
  50. odxtools/negoutputparam.py +6 -9
  51. odxtools/odxlink.py +21 -5
  52. odxtools/odxtypes.py +7 -6
  53. odxtools/outputparam.py +9 -8
  54. odxtools/parameterinfo.py +1 -1
  55. odxtools/parameters/codedconstparameter.py +28 -27
  56. odxtools/parameters/dynamicparameter.py +9 -9
  57. odxtools/parameters/lengthkeyparameter.py +18 -18
  58. odxtools/parameters/matchingrequestparameter.py +15 -15
  59. odxtools/parameters/nrcconstparameter.py +32 -24
  60. odxtools/parameters/parameter.py +35 -37
  61. odxtools/parameters/parameterwithdop.py +6 -6
  62. odxtools/parameters/physicalconstantparameter.py +19 -20
  63. odxtools/parameters/reservedparameter.py +10 -11
  64. odxtools/parameters/systemparameter.py +10 -11
  65. odxtools/parameters/tableentryparameter.py +19 -20
  66. odxtools/parameters/tablekeyparameter.py +0 -2
  67. odxtools/parameters/tablestructparameter.py +27 -21
  68. odxtools/parameters/valueparameter.py +20 -20
  69. odxtools/parentref.py +6 -7
  70. odxtools/physicaldimension.py +11 -11
  71. odxtools/physicaltype.py +9 -14
  72. odxtools/preconditionstateref.py +85 -0
  73. odxtools/progcode.py +1 -2
  74. odxtools/protstack.py +4 -4
  75. odxtools/relateddoc.py +3 -4
  76. odxtools/scaleconstr.py +0 -1
  77. odxtools/singleecujob.py +8 -4
  78. odxtools/specialdata.py +10 -9
  79. odxtools/specialdatagroup.py +1 -0
  80. odxtools/standardlengthtype.py +18 -18
  81. odxtools/statechart.py +10 -6
  82. odxtools/statemachine.py +186 -0
  83. odxtools/statetransitionref.py +231 -0
  84. odxtools/structure.py +4 -4
  85. odxtools/subcomponent.py +78 -11
  86. odxtools/table.py +23 -13
  87. odxtools/tablerow.py +86 -69
  88. odxtools/teammember.py +4 -4
  89. odxtools/templates/macros/printBaseVariant.xml.jinja2 +4 -9
  90. odxtools/templates/macros/printBaseVariantPattern.xml.jinja2 +32 -0
  91. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  92. odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
  93. odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
  94. odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
  95. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +7 -6
  96. odxtools/templates/macros/printParam.xml.jinja2 +5 -5
  97. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
  98. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
  99. odxtools/templates/macros/printTable.xml.jinja2 +13 -9
  100. odxtools/text.py +35 -0
  101. odxtools/unit.py +1 -3
  102. odxtools/unitgroup.py +6 -8
  103. odxtools/variantmatcher.py +209 -0
  104. odxtools/variantpattern.py +38 -0
  105. odxtools/version.py +2 -2
  106. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
  107. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/RECORD +111 -102
  108. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
  109. odxtools/createecuvariantpatterns.py +0 -18
  110. odxtools/ecuvariantmatcher.py +0 -171
  111. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
  112. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
  113. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,14 @@ class DynamicEndmarkerField(Field):
23
23
 
24
24
  dyn_end_dop_ref: DynEndDopRef
25
25
 
26
+ @property
27
+ def dyn_end_dop(self) -> DataObjectProperty:
28
+ return self._dyn_end_dop
29
+
30
+ @property
31
+ def termination_value(self) -> AtomicOdxType:
32
+ return self._termination_value
33
+
26
34
  @staticmethod
27
35
  def from_et(et_element: ElementTree.Element,
28
36
  doc_frags: List[OdxDocFragment]) -> "DynamicEndmarkerField":
@@ -52,14 +60,6 @@ class DynamicEndmarkerField(Field):
52
60
  def _resolve_snrefs(self, context: SnRefContext) -> None:
53
61
  super()._resolve_snrefs(context)
54
62
 
55
- @property
56
- def dyn_end_dop(self) -> DataObjectProperty:
57
- return self._dyn_end_dop
58
-
59
- @property
60
- def termination_value(self) -> AtomicOdxType:
61
- return self._termination_value
62
-
63
63
  @override
64
64
  def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
65
65
 
@@ -26,11 +26,13 @@ class DynamicLengthField(Field):
26
26
  def from_et(et_element: ElementTree.Element,
27
27
  doc_frags: List[OdxDocFragment]) -> "DynamicLengthField":
28
28
  kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
29
+
29
30
  offset = int(odxrequire(et_element.findtext('OFFSET')))
30
31
  determine_number_of_items = DetermineNumberOfItems.from_et(
31
32
  odxrequire(et_element.find('DETERMINE-NUMBER-OF-ITEMS')),
32
33
  doc_frags,
33
34
  )
35
+
34
36
  return DynamicLengthField(
35
37
  offset=offset, determine_number_of_items=determine_number_of_items, **kwargs)
36
38
 
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Union
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .diagcomm import DiagClassType, DiagComm
7
- from .exceptions import odxraise, odxrequire
7
+ from .exceptions import odxassert, odxraise, odxrequire
8
8
  from .nameditemlist import NamedItemList
9
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
10
  from .snrefcontext import SnRefContext
@@ -50,17 +50,20 @@ class DynIdDefModeInfo:
50
50
 
51
51
  clear_dyn_def_message_ref = OdxLinkRef.from_et(
52
52
  et_element.find("CLEAR-DYN-DEF-MESSAGE-REF"), doc_frags)
53
+ clear_dyn_def_message_snref = None
53
54
  if (snref_elem := et_element.find("CLEAR-DYN-DEF-MESSAGE-SNREF")) is not None:
54
- clear_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
55
+ clear_dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
55
56
 
56
57
  read_dyn_def_message_ref = OdxLinkRef.from_et(
57
58
  et_element.find("READ-DYN-DEF-MESSAGE-REF"), doc_frags)
59
+ read_dyn_def_message_snref = None
58
60
  if (snref_elem := et_element.find("READ-DYN-DEF-MESSAGE-SNREF")) is not None:
59
- read_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
61
+ read_dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
60
62
 
61
63
  dyn_def_message_ref = OdxLinkRef.from_et(et_element.find("DYN-DEF-MESSAGE-REF"), doc_frags)
64
+ dyn_def_message_snref = None
62
65
  if (snref_elem := et_element.find("DYN-DEF-MESSAGE-SNREF")) is not None:
63
- dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
66
+ dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
64
67
 
65
68
  supported_dyn_ids = [
66
69
  bytes.fromhex(odxrequire(x.text))
@@ -89,14 +92,23 @@ class DynIdDefModeInfo:
89
92
  selection_table_refs=selection_table_refs,
90
93
  )
91
94
 
95
+ def __post_init__(self) -> None:
96
+ odxassert(
97
+ self.clear_dyn_def_message_ref is not None or
98
+ self.clear_dyn_def_message_snref is not None,
99
+ "A CLEAR-DYN-DEF-MESSAGE must be specified")
100
+ odxassert(
101
+ self.read_dyn_def_message_ref is not None or
102
+ self.read_dyn_def_message_snref is not None, "A READ-DYN-DEF-MESSAGE must be specified")
103
+ odxassert(self.dyn_def_message_ref is not None or self.dyn_def_message_snref is not None,
104
+ "A DYN-DEF-MESSAGE must be specified")
105
+
92
106
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
93
107
  result: Dict[OdxLinkId, Any] = {}
94
108
 
95
109
  return result
96
110
 
97
111
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
98
- self._selection_tables = NamedItemList[Table]()
99
-
100
112
  if self.clear_dyn_def_message_ref is not None:
101
113
  self._clear_dyn_def_message = odxlinks.resolve(self.clear_dyn_def_message_ref, DiagComm)
102
114
 
@@ -106,7 +118,8 @@ class DynIdDefModeInfo:
106
118
  if self.dyn_def_message_ref is not None:
107
119
  self._dyn_def_message = odxlinks.resolve(self.dyn_def_message_ref, DiagComm)
108
120
 
109
- # resolve the selection tables referenced using ODXLINK
121
+ # resolve the selection tables that are referenced via ODXLINK
122
+ self._selection_tables = NamedItemList[Table]()
110
123
  for x in self.selection_table_refs:
111
124
  if isinstance(x, OdxLinkRef):
112
125
  self._selection_tables.append(odxlinks.resolve(x, Table))
@@ -141,9 +154,9 @@ class DynIdDefModeInfo:
141
154
  f"DYN-DEF-MESSAGE)")
142
155
 
143
156
  # resolve the remaining selection tables that are referenced via SNREF
157
+ ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
144
158
  for i, x in enumerate(self.selection_table_refs):
145
159
  if isinstance(x, str):
146
- ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
147
160
  self._selection_tables.insert(i, resolve_snref(x, ddd_spec.tables, Table))
148
161
 
149
162
 
@@ -1,27 +1,38 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import List, Union
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .exceptions import odxassert, odxrequire
6
+ from typing_extensions import override
7
+
8
+ from .exceptions import odxassert
9
+ from .matchingbasevariantparameter import MatchingBaseVariantParameter
7
10
  from .matchingparameter import MatchingParameter
8
11
  from .odxlink import OdxDocFragment
12
+ from .variantpattern import VariantPattern
9
13
 
10
14
 
11
15
  @dataclass
12
- class EcuVariantPattern:
16
+ class EcuVariantPattern(VariantPattern):
17
+ """ECU variant patterns are variant patterns used to identify the
18
+ concrete variant of an ECU.
19
+ """
13
20
  matching_parameters: List[MatchingParameter]
14
21
 
22
+ @override
23
+ def get_matching_parameters(
24
+ self) -> Union[List[MatchingParameter], List[MatchingBaseVariantParameter]]:
25
+ return self.matching_parameters
26
+
15
27
  @staticmethod
16
28
  def from_et(et_element: ElementTree.Element,
17
29
  doc_frags: List[OdxDocFragment]) -> "EcuVariantPattern":
18
30
 
19
- mp_collection_el = odxrequire(et_element.find("MATCHING-PARAMETERS"))
20
-
21
- matching_params = [
31
+ matching_parameters = [
22
32
  MatchingParameter.from_et(mp_el, doc_frags)
23
- for mp_el in mp_collection_el.iterfind("MATCHING-PARAMETER")
33
+ for mp_el in et_element.iterfind("MATCHING-PARAMETERS/"
34
+ "MATCHING-PARAMETER")
24
35
  ]
25
36
 
26
- odxassert(len(matching_params) > 0) # required by ISO 22901-1 Figure 141
27
- return EcuVariantPattern(matching_params)
37
+ odxassert(len(matching_parameters) > 0) # required by ISO 22901-1 Figure 141
38
+ return EcuVariantPattern(matching_parameters=matching_parameters)
odxtools/encodestate.py CHANGED
@@ -1,11 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
4
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
5
5
 
6
6
  from .encoding import Encoding, get_string_encoding
7
7
  from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
8
- from .odxtypes import AtomicOdxType, DataType, ParameterValue
8
+ from .odxtypes import AtomicOdxType, BytesTypes, DataType, ParameterValue
9
9
 
10
10
  try:
11
11
  import bitstruct.c as bitstruct
@@ -97,7 +97,7 @@ class EncodeState:
97
97
 
98
98
  # Deal with raw byte fields, ...
99
99
  if base_data_type == DataType.A_BYTEFIELD:
100
- if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
100
+ if not isinstance(internal_value, BytesTypes):
101
101
  odxraise(f"{internal_value!r} is not a bytefield", EncodeError)
102
102
  return
103
103
 
odxtools/endofpdufield.py CHANGED
@@ -17,30 +17,28 @@ from .utils import dataclass_fields_asdict
17
17
  @dataclass
18
18
  class EndOfPduField(Field):
19
19
  """End of PDU fields are structures that are repeated until the end of the PDU"""
20
- min_number_of_items: Optional[int]
21
20
  max_number_of_items: Optional[int]
21
+ min_number_of_items: Optional[int]
22
22
 
23
23
  @staticmethod
24
24
  def from_et(et_element: ElementTree.Element,
25
25
  doc_frags: List[OdxDocFragment]) -> "EndOfPduField":
26
26
  kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
27
27
 
28
- if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
29
- min_number_of_items = int(min_n_str)
30
- else:
31
- min_number_of_items = None
32
28
  if (max_n_str := et_element.findtext("MAX-NUMBER-OF-ITEMS")) is not None:
33
29
  max_number_of_items = int(max_n_str)
34
30
  else:
35
31
  max_number_of_items = None
32
+ if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
33
+ min_number_of_items = int(min_n_str)
34
+ else:
35
+ min_number_of_items = None
36
36
 
37
- eopf = EndOfPduField(
38
- min_number_of_items=min_number_of_items,
37
+ return EndOfPduField(
39
38
  max_number_of_items=max_number_of_items,
39
+ min_number_of_items=min_number_of_items,
40
40
  **kwargs)
41
41
 
42
- return eopf
43
-
44
42
  @override
45
43
  def encode_into_pdu(self, physical_value: Optional[ParameterValue],
46
44
  encode_state: EncodeState) -> None:
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional, cast
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -44,18 +44,6 @@ class EnvironmentDataDescription(ComplexDop):
44
44
  env_datas: NamedItemList[EnvironmentData]
45
45
  env_data_refs: List[OdxLinkRef]
46
46
 
47
- @property
48
- def param(self) -> Parameter:
49
- # the parameter referenced via SNREF cannot be resolved here
50
- # because the relevant list of parameters depends on the
51
- # concrete codec object processed, whilst an environment data
52
- # description object can be featured in an arbitrary number of
53
- # responses. Instead, lookup of the appropriate parameter is
54
- # done within the encode and decode methods.
55
- odxraise("The parameter of ENV-DATA-DESC objects cannot be resolved "
56
- "because it depends on the context")
57
- return cast(None, Parameter)
58
-
59
47
  @staticmethod
60
48
  def from_et(et_element: ElementTree.Element,
61
49
  doc_frags: List[OdxDocFragment]) -> "EnvironmentDataDescription":
@@ -74,20 +62,20 @@ class EnvironmentDataDescription(ComplexDop):
74
62
  # situation is reversed. This means that we will create one
75
63
  # empty and one non-empty list here. (Which is which depends
76
64
  # on the version of the standard used by the file.)
77
- env_data_refs = [
78
- odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
79
- for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
80
- ]
81
65
  env_datas = NamedItemList([
82
66
  EnvironmentData.from_et(env_data_elem, doc_frags)
83
67
  for env_data_elem in et_element.iterfind("ENV-DATAS/ENV-DATA")
84
68
  ])
69
+ env_data_refs = [
70
+ odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
71
+ for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
72
+ ]
85
73
 
86
74
  return EnvironmentDataDescription(
87
75
  param_snref=param_snref,
88
76
  param_snpathref=param_snpathref,
89
- env_data_refs=env_data_refs,
90
77
  env_datas=env_datas,
78
+ env_data_refs=env_data_refs,
91
79
  **kwargs)
92
80
 
93
81
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -103,7 +91,8 @@ class EnvironmentDataDescription(ComplexDop):
103
91
  # ODX 2.0 specifies environment data objects here, ODX 2.2
104
92
  # uses references
105
93
  if self.env_data_refs:
106
- self.env_datas = NamedItemList([odxlinks.resolve(x) for x in self.env_data_refs])
94
+ self.env_datas = NamedItemList(
95
+ [odxlinks.resolve(x, EnvironmentData) for x in self.env_data_refs])
107
96
  else:
108
97
  for ed in self.env_datas:
109
98
  ed._resolve_odxlinks(odxlinks)
@@ -221,7 +210,7 @@ class EnvironmentDataDescription(ComplexDop):
221
210
  if not isinstance(dop, (DataObjectProperty, DtcDop)):
222
211
  odxraise(f"The DOP of the parameter referenced by environment data descriptions "
223
212
  f"must use either be DataObjectProperty or a DtcDop (encountered "
224
- f"{type(param).__name__} for parameter '{self.param.short_name}' "
213
+ f"{type(param).__name__} for parameter '{param.short_name}' "
225
214
  f"of ENV-DATA-DESC '{self.short_name}')")
226
215
  return 0
227
216
 
odxtools/field.py CHANGED
@@ -21,19 +21,14 @@ class Field(ComplexDop):
21
21
  env_data_desc_snref: Optional[str]
22
22
  is_visible_raw: Optional[bool]
23
23
 
24
- def __post_init__(self) -> None:
25
- self._structure: Optional[BasicStructure] = None
26
- self._env_data_desc: Optional[EnvironmentDataDescription] = None
27
- num_struct_refs = 0 if self.structure_ref is None else 1
28
- num_struct_refs += 0 if self.structure_snref is None else 1
29
-
30
- num_edd_refs = 0 if self.env_data_desc_ref is None else 1
31
- num_edd_refs += 0 if self.env_data_desc_snref is None else 1
24
+ @property
25
+ def structure(self) -> BasicStructure:
26
+ """may be a Structure or a env-data-desc"""
27
+ return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
32
28
 
33
- odxassert(
34
- num_struct_refs + num_edd_refs == 1,
35
- "FIELDs need to specify exactly one reference to a "
36
- "structure or an environment data description")
29
+ @property
30
+ def is_visible(self) -> bool:
31
+ return self.is_visible_raw in (None, True)
37
32
 
38
33
  @staticmethod
39
34
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Field":
@@ -58,17 +53,19 @@ class Field(ComplexDop):
58
53
  is_visible_raw=is_visible_raw,
59
54
  **kwargs)
60
55
 
61
- @property
62
- def is_visible(self) -> bool:
63
- return self.is_visible_raw in (None, True)
56
+ def __post_init__(self) -> None:
57
+ self._structure: Optional[BasicStructure] = None
58
+ self._env_data_desc: Optional[EnvironmentDataDescription] = None
59
+ num_struct_refs = 0 if self.structure_ref is None else 1
60
+ num_struct_refs += 0 if self.structure_snref is None else 1
64
61
 
65
- @property
66
- def structure(self) -> BasicStructure:
67
- """may be a Structure or a env-data-desc"""
68
- return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
62
+ num_edd_refs = 0 if self.env_data_desc_ref is None else 1
63
+ num_edd_refs += 0 if self.env_data_desc_snref is None else 1
69
64
 
70
- def get_static_bit_length(self) -> Optional[int]:
71
- return None
65
+ odxassert(
66
+ num_struct_refs + num_edd_refs == 1,
67
+ "FIELDs need to specify exactly one reference to a "
68
+ "structure or an environment data description")
72
69
 
73
70
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
74
71
  """Recursively resolve any odxlinks references"""
@@ -89,3 +86,6 @@ class Field(ComplexDop):
89
86
  if self.env_data_desc_snref is not None:
90
87
  self._env_data_desc = resolve_snref(self.env_data_desc_snref, ddds.env_data_descs,
91
88
  EnvironmentDataDescription)
89
+
90
+ def get_static_bit_length(self) -> Optional[int]:
91
+ return None
odxtools/inputparam.py CHANGED
@@ -15,25 +15,35 @@ from .utils import dataclass_fields_asdict
15
15
 
16
16
  @dataclass
17
17
  class InputParam(NamedElement):
18
+ physical_default_value: Optional[str]
18
19
  dop_base_ref: OdxLinkRef
19
20
  oid: Optional[str]
20
21
  semantic: Optional[str]
21
- physical_default_value: Optional[str]
22
+
23
+ @property
24
+ def dop(self) -> DopBase:
25
+ """The data object property describing this parameter."""
26
+ return self._dop
27
+
28
+ @deprecated(details="use .dop") # type: ignore[misc]
29
+ def dop_base(self) -> DopBase:
30
+ return self._dop
22
31
 
23
32
  @staticmethod
24
33
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "InputParam":
25
34
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
26
- dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
35
+
27
36
  physical_default_value = et_element.findtext("PHYSICAL-DEFAULT-VALUE")
37
+ dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
28
38
 
29
- semantic = et_element.get("SEMANTIC")
30
39
  oid = et_element.get("OID")
40
+ semantic = et_element.get("SEMANTIC")
31
41
 
32
42
  return InputParam(
33
- dop_base_ref=dop_base_ref,
34
43
  physical_default_value=physical_default_value,
35
- semantic=semantic,
44
+ dop_base_ref=dop_base_ref,
36
45
  oid=oid,
46
+ semantic=semantic,
37
47
  **kwargs)
38
48
 
39
49
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -44,12 +54,3 @@ class InputParam(NamedElement):
44
54
 
45
55
  def _resolve_snrefs(self, context: SnRefContext) -> None:
46
56
  pass
47
-
48
- @property
49
- def dop(self) -> DopBase:
50
- """The data object property describing this parameter."""
51
- return self._dop
52
-
53
- @deprecated(details="use .dop") # type: ignore[misc]
54
- def dop_base(self) -> DopBase:
55
- return self._dop
@@ -23,6 +23,10 @@ class LeadingLengthInfoType(DiagCodedType):
23
23
  #: object.
24
24
  bit_length: int
25
25
 
26
+ @property
27
+ def dct_type(self) -> DctType:
28
+ return "LEADING-LENGTH-INFO-TYPE"
29
+
26
30
  @staticmethod
27
31
  @override
28
32
  def from_et(et_element: ElementTree.Element,
@@ -46,10 +50,6 @@ class LeadingLengthInfoType(DiagCodedType):
46
50
  f"A leading length info type cannot have the base data type {self.base_data_type.name}."
47
51
  )
48
52
 
49
- @property
50
- def dct_type(self) -> DctType:
51
- return "LEADING-LENGTH-INFO-TYPE"
52
-
53
53
  @override
54
54
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
55
55
 
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .matchingparameter import MatchingParameter
7
+ from .odxlink import OdxDocFragment
8
+ from .odxtypes import odxstr_to_bool
9
+ from .utils import dataclass_fields_asdict
10
+
11
+
12
+ @dataclass
13
+ class MatchingBaseVariantParameter(MatchingParameter):
14
+ """A description of a parameter used for base variant matching.
15
+
16
+ This is very similar to `MatchingParameter` for ECU variant
17
+ matching, but `MatchingBaseVariantParameter` features the
18
+ additional subtag `USE-PHYSICAL-ADDRESSING`.
19
+ """
20
+
21
+ use_physical_addressing_raw: Optional[bool]
22
+
23
+ @property
24
+ def use_physical_addressing(self) -> bool:
25
+ return self.use_physical_addressing_raw in [None, True]
26
+
27
+ @staticmethod
28
+ def from_et(et_element: ElementTree.Element,
29
+ doc_frags: List[OdxDocFragment]) -> "MatchingBaseVariantParameter":
30
+
31
+ kwargs = dataclass_fields_asdict(MatchingParameter.from_et(et_element, doc_frags))
32
+
33
+ use_physical_addressing_raw = odxstr_to_bool(et_element.findtext("USE-PHYSICAL-ADDRESSING"))
34
+
35
+ return MatchingBaseVariantParameter(
36
+ use_physical_addressing_raw=use_physical_addressing_raw,
37
+ **kwargs,
38
+ )
@@ -1,58 +1,138 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .exceptions import odxassert, odxraise, odxrequire
7
- from .odxlink import OdxDocFragment
8
- from .utils import is_short_name, is_short_name_path
6
+ from .diaglayers.diaglayer import DiagLayer
7
+ from .diagnostictroublecode import DiagnosticTroubleCode
8
+ from .diagservice import DiagService
9
+ from .exceptions import odxraise, odxrequire
10
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
11
+ from .odxtypes import BytesTypes, ParameterValue, ParameterValueDict
12
+ from .snrefcontext import SnRefContext
9
13
 
10
14
 
11
15
  @dataclass
12
16
  class MatchingParameter:
13
- """According to ISO 22901, a MatchingParameter contains a string value identifying
14
- the active ECU variant. Moreover, it references a DIAG-COMM via snref and one of its
15
- positive response's OUT-PARAM-IF via snref or snpathref.
17
+ """According to ISO 22901, a MatchingParameter contains a string
18
+ value identifying the active ECU or base variant. Moreover, it
19
+ references a DIAG-COMM via snref and one of its positive
20
+ response's OUT-PARAM-IF via snref or snpathref.
21
+
22
+ Unlike other parameters defined in the `parameters` package, a
23
+ MatchingParameter is not transferred over the network.
16
24
 
17
- Unlike other parameters defined in the `parameters` package, a MatchingParameter is
18
- not transferred over the network.
19
25
  """
20
26
 
21
- # datatype according to ISO 22901-1 Figure 141
22
27
  expected_value: str
23
28
  diag_comm_snref: str
24
- out_param_if: str
25
29
 
26
- def __post_init__(self) -> None:
27
- odxassert(is_short_name(self.diag_comm_snref))
28
- odxassert(is_short_name_path(self.out_param_if))
30
+ # be aware that the checker rules 23 and 110 contradict each other
31
+ # here: the first specifies that the referenced parameter must be
32
+ # present in *all* positive responses of the referenced service,
33
+ # whilst the latter mandates it to be present least one positive
34
+ # or negative response. What it probably actually wants to say is
35
+ # that any response that can possibly be received shall exhibit
36
+ # the referenced parameter.
37
+ out_param_if_snref: Optional[str]
38
+ out_param_if_snpathref: Optional[str]
29
39
 
30
40
  @staticmethod
31
41
  def from_et(et_element: ElementTree.Element,
32
42
  doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
33
43
 
34
44
  expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
35
- diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
- diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME"))
37
- out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF")
38
- out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF")
39
- out_param_if = None
40
- if out_param_snref_el is not None:
41
- out_param_if = out_param_snref_el.get("SHORT-NAME")
42
- elif out_param_snpathref_el is not None:
43
- out_param_if = out_param_snpathref_el.get("SHORT-NAME-PATH")
44
- if out_param_if is None:
45
- odxraise("Output parameter must not left unspecified")
45
+ diag_comm_snref = odxrequire(
46
+ odxrequire(et_element.find("DIAG-COMM-SNREF")).get("SHORT-NAME"))
47
+
48
+ out_param_if_snref = None
49
+ out_param_if_snpathref = None
50
+ if (out_param_snref_el := et_element.find("OUT-PARAM-IF-SNREF")) is not None:
51
+ out_param_if_snref = odxrequire(out_param_snref_el.get("SHORT-NAME"))
52
+ elif (out_param_snpathref_el := et_element.find("OUT-PARAM-IF-SNPATHREF")) is not None:
53
+ out_param_if_snpathref = odxrequire(out_param_snpathref_el.get("SHORT-NAME-PATH"))
54
+ else:
55
+ odxraise("Output parameter must not be left unspecified")
46
56
 
47
57
  return MatchingParameter(
48
58
  expected_value=expected_value,
49
59
  diag_comm_snref=diag_comm_snref,
50
- out_param_if=out_param_if,
60
+ out_param_if_snref=out_param_if_snref,
61
+ out_param_if_snpathref=out_param_if_snpathref,
51
62
  )
52
63
 
53
- def is_match(self, ident_value: str) -> bool:
64
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
65
+ return {}
66
+
67
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
68
+ pass
69
+
70
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
71
+ # note that we do not resolve the SNREF to the diag layer here
72
+ # because it is a component which changes depending on the
73
+ # execution context and for variant patterns the execution
74
+ # context is highly volatile w.r.t. the diag layer in question
75
+ pass
76
+
77
+ def get_ident_service(self, diag_layer: DiagLayer) -> DiagService:
78
+ return resolve_snref(self.diag_comm_snref, diag_layer.services, DiagService)
79
+
80
+ def matches(self, param_dict: ParameterValueDict) -> bool:
54
81
  """
55
82
  Returns true iff the provided identification value matches this MatchingParameter's
56
83
  expected value.
57
84
  """
58
- return self.expected_value == ident_value
85
+
86
+ if self.out_param_if_snref is not None:
87
+ snpath_chunks = [self.out_param_if_snref]
88
+ elif self.out_param_if_snpathref is not None:
89
+ snpath_chunks = self.out_param_if_snpathref.split(".")
90
+ else:
91
+ odxraise("no out_param_if specified")
92
+ return False
93
+
94
+ return self.__matches(param_dict, snpath_chunks)
95
+
96
+ def __matches(self, param_dict: ParameterValue, snpath_chunks: List[str]) -> bool:
97
+ if len(snpath_chunks) == 0:
98
+ parameter_value = param_dict
99
+ if isinstance(parameter_value, dict):
100
+ odxraise(f"Parameter must not be a structure")
101
+ return False
102
+
103
+ if isinstance(parameter_value, float):
104
+ # allow a slight tolerance if the expected value is
105
+ # floating point
106
+ return abs(float(self.expected_value) - parameter_value) < 1e-8
107
+ elif isinstance(parameter_value, BytesTypes):
108
+ return parameter_value.hex().upper() == self.expected_value.upper()
109
+ elif isinstance(parameter_value, DiagnosticTroubleCode):
110
+ # TODO: what happens if non-numerical DTCs like
111
+ # "U123456" are specified? Is specifying DTCs even
112
+ # allowed in variant patterns?
113
+ return hex(parameter_value.trouble_code).upper() == self.expected_value.upper()
114
+ else:
115
+ return self.expected_value == str(parameter_value)
116
+
117
+ if not isinstance(param_dict, dict):
118
+ odxraise(f"Parameter {snpath_chunks[0]} must be a structure")
119
+ return False
120
+
121
+ sub_value = param_dict.get(snpath_chunks[0])
122
+ if sub_value is None:
123
+ return False
124
+
125
+ if isinstance(sub_value, tuple) and len(sub_value) == 2:
126
+ # table struct parameter
127
+ sub_value = sub_value[1]
128
+
129
+ if isinstance(sub_value, list):
130
+ # the spec mandates that if any item of a field matches,
131
+ # the parameter as a whole matches
132
+ for x in sub_value:
133
+ if self.__matches(x, snpath_chunks[1:]):
134
+ return True
135
+
136
+ return False
137
+
138
+ return self.__matches(cast(ParameterValue, sub_value), snpath_chunks[1:])