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
odxtools/unitgroup.py CHANGED
@@ -28,13 +28,14 @@ class UnitGroup(NamedElement):
28
28
  unit_refs: List[OdxLinkRef]
29
29
  oid: Optional[str]
30
30
 
31
- def __post_init__(self) -> None:
32
- self._units = NamedItemList[Unit]()
31
+ @property
32
+ def units(self) -> NamedItemList[Unit]:
33
+ return self._units
33
34
 
34
35
  @staticmethod
35
36
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "UnitGroup":
36
- oid = et_element.get("OID")
37
37
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
38
+
38
39
  category_str = odxrequire(et_element.findtext("CATEGORY"))
39
40
  try:
40
41
  category = UnitGroupCategory(category_str)
@@ -42,10 +43,11 @@ class UnitGroup(NamedElement):
42
43
  category = cast(UnitGroupCategory, None)
43
44
  odxraise(f"Encountered unknown unit group category '{category_str}'")
44
45
 
45
- unit_refs: List[OdxLinkRef] = [
46
+ unit_refs = [
46
47
  odxrequire(OdxLinkRef.from_et(el, doc_frags))
47
48
  for el in et_element.iterfind("UNIT-REFS/UNIT-REF")
48
49
  ]
50
+ oid = et_element.get("OID")
49
51
 
50
52
  return UnitGroup(category=category, unit_refs=unit_refs, oid=oid, **kwargs)
51
53
 
@@ -57,7 +59,3 @@ class UnitGroup(NamedElement):
57
59
 
58
60
  def _resolve_snrefs(self, context: SnRefContext) -> None:
59
61
  pass
60
-
61
- @property
62
- def units(self) -> NamedItemList[Unit]:
63
- return self._units
@@ -0,0 +1,209 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from copy import copy
3
+ from enum import Enum
4
+ from typing import Dict, Generator, List, Optional, Tuple, Union
5
+
6
+ from .diaglayers.basevariant import BaseVariant
7
+ from .diaglayers.ecuvariant import EcuVariant
8
+ from .exceptions import DecodeError, odxraise
9
+ from .matchingparameter import MatchingParameter
10
+ from .response import Response
11
+
12
+
13
+ class VariantMatcher:
14
+ """VariantMatcher implements the matching algorithm of ECU and
15
+ base variants according to their ECU-VARIANT-PATTERNs or
16
+ BASE-VARIANT-PATTERNs according to ISO 22901-1.
17
+
18
+ Usage (example):
19
+
20
+ ```python
21
+
22
+ # initialize the matcher with a list of base or ECU variants
23
+ matcher = VariantMatcher(candidates=[...], use_cache=use_cache)
24
+
25
+ # run the request loop to obtain responses for every request
26
+ for use_physical_addressing, encoded_request in matcher.request_loop():
27
+ if use_physical_addressing:
28
+ resp = send_to_ecu_using_physical_addressing(encoded_request)
29
+ else:
30
+ resp = send_to_ecu_using_functional_addressing(encoded_request)
31
+ matcher.evaluate(resp)
32
+
33
+ # result
34
+ if matcher.has_match():
35
+ if isinstance(matcher.matching_variant, BaseVariant):
36
+ print(f"Match found for base variant "
37
+ f"{matcher.matching_variant.short_name}")
38
+ elif isinstance(matcher.matching_variant, EcuVariant):
39
+ print(f"Match found for ECU variant "
40
+ f"{matcher.matching_variant.short_name}")
41
+ else:
42
+ print(f"Match found for unknown diag layer type "
43
+ f"{type(matcher.matching_variant).__name__}")
44
+ else:
45
+ print("No matching base- or ECU variant found")
46
+ ```
47
+
48
+ TODO: Note that only patterns that exclusivly reference diagnostic
49
+ services (i.e., no single-ECU jobs) in their matching parameters
50
+ are currently supported.
51
+ """
52
+
53
+ class State(Enum):
54
+ PENDING = 0
55
+ NO_MATCH = 1
56
+ MATCH = 2
57
+
58
+ def __init__(self,
59
+ variant_candidates: Union[List[EcuVariant], List[BaseVariant]],
60
+ use_cache: bool = True):
61
+
62
+ self.variant_candidates = variant_candidates
63
+ self.use_cache = use_cache
64
+ self.req_resp_cache: Dict[bytes, bytes] = {}
65
+ self._recent_ident_response: Optional[bytes] = None
66
+
67
+ self._state = VariantMatcher.State.PENDING
68
+ self._matching_variant: Optional[Union[EcuVariant, BaseVariant]] = None
69
+
70
+ def request_loop(self) -> Generator[Tuple[bool, bytes], None, None]:
71
+ """The request loop yielding tuples of byte sequences of
72
+ requests and the whether physical addressing ought to be used
73
+ to send them
74
+
75
+ Each of these requests needs to be send to the ECU to be
76
+ identified using the specified addressing scheme. The response
77
+ of the ECU is then required to be passed to the matcher using
78
+ the `evaluate()` method.
79
+ """
80
+ from .basevariantpattern import BaseVariantPattern
81
+ from .ecuvariantpattern import EcuVariantPattern
82
+ from .matchingbasevariantparameter import MatchingBaseVariantParameter
83
+
84
+ if not self.is_pending():
85
+ return
86
+
87
+ self._matching_variant = None
88
+ for variant in self.variant_candidates:
89
+ variant_patterns: Union[List[EcuVariantPattern], List[BaseVariantPattern]]
90
+ if isinstance(variant, EcuVariant):
91
+ variant_patterns = variant.ecu_variant_patterns
92
+ elif isinstance(variant, BaseVariant):
93
+ if variant.base_variant_pattern is None:
94
+ variant_patterns = []
95
+ else:
96
+ variant_patterns = [variant.base_variant_pattern]
97
+ else:
98
+ odxraise(f"Only EcuVariant and BaseVariant are supported "
99
+ f"for pattern matching, not {type(self).__name__}")
100
+ self._state = VariantMatcher.State.NO_MATCH
101
+ return
102
+
103
+ any_pattern_matches = False
104
+ for pattern in variant_patterns:
105
+ all_params_match = True
106
+ for matching_param in pattern.get_matching_parameters():
107
+ req_bytes = matching_param.get_ident_service(variant).encode_request()
108
+
109
+ if self.use_cache and req_bytes in self.req_resp_cache:
110
+ resp_values = copy(self.req_resp_cache[req_bytes])
111
+ else:
112
+ if isinstance(matching_param, MatchingBaseVariantParameter):
113
+ yield matching_param.use_physical_addressing, req_bytes
114
+ else:
115
+ yield True, req_bytes
116
+ resp_values = self._get_ident_response()
117
+ self._update_cache(req_bytes, copy(resp_values))
118
+
119
+ cur_response_matches = self._ident_response_matches(
120
+ variant, matching_param, resp_values)
121
+ all_params_match = all_params_match and cur_response_matches
122
+ if not all_params_match:
123
+ break
124
+
125
+ if all_params_match:
126
+ any_pattern_matches = True
127
+ break
128
+
129
+ if any_pattern_matches:
130
+ self._state = VariantMatcher.State.MATCH
131
+ self._matching_variant = variant
132
+ break
133
+
134
+ if self.is_pending():
135
+ # no pattern has matched for any ecu variant
136
+ self._state = VariantMatcher.State.NO_MATCH
137
+
138
+ def evaluate(self, resp_bytes: bytes) -> None:
139
+ """Update the matcher with the response to a requst.
140
+
141
+ Warning: Use this method EXACTLY once within the loop body of the request loop.
142
+ """
143
+ self._recent_ident_response = bytes(resp_bytes)
144
+
145
+ def is_pending(self) -> bool:
146
+ """True iff request loop has not yet been run."""
147
+ return self._state == VariantMatcher.State.PENDING
148
+
149
+ def has_match(self) -> bool:
150
+ """Returns true iff the non-pending matcher found a matching ecu variant.
151
+
152
+ Raises a runtime error if the matcher is pending.
153
+ """
154
+ if self.is_pending():
155
+ raise RuntimeError(
156
+ "EcuVariantMatcher is pending. Run the request_loop to determine the active ecu variant."
157
+ )
158
+ return self._state == VariantMatcher.State.MATCH
159
+
160
+ @property
161
+ def matching_variant(self) -> Optional[Union[EcuVariant, BaseVariant]]:
162
+ """Returns the matched, i.e., active ecu variant if such a variant has been found."""
163
+ return self._matching_variant
164
+
165
+ def _ident_response_matches(
166
+ self,
167
+ variant: Union[EcuVariant, BaseVariant],
168
+ matching_param: MatchingParameter,
169
+ response_bytes: bytes,
170
+ ) -> bool:
171
+ """Decode a binary response and extract the identification string according
172
+ to the snref or snpathref of the matching_param.
173
+ """
174
+ service = matching_param.get_ident_service(variant)
175
+
176
+ # ISO 22901 requires that snref or snpathref is resolvable in
177
+ # at least one POS-RESPONSE or NEG-RESPONSE
178
+ all_responses: List[Response] = []
179
+ all_responses.extend(service.positive_responses)
180
+ all_responses.extend(service.negative_responses)
181
+ all_responses.extend(variant.global_negative_responses)
182
+
183
+ for cur_response in all_responses:
184
+ try:
185
+ decoded_vals = cur_response.decode(response_bytes)
186
+ except DecodeError:
187
+ # the current response object could not decode the received
188
+ # data. Ignore it.
189
+ continue
190
+
191
+ if not matching_param.matches(decoded_vals):
192
+ # This particular response object does not match the
193
+ # expected value of the specified matching
194
+ # parameter. Ignore it.
195
+ continue
196
+
197
+ return True
198
+
199
+ return False
200
+
201
+ def _update_cache(self, req_bytes: bytes, resp_bytes: bytes) -> None:
202
+ if self.use_cache:
203
+ self.req_resp_cache[req_bytes] = resp_bytes
204
+
205
+ def _get_ident_response(self) -> bytes:
206
+ if not self._recent_ident_response:
207
+ raise RuntimeError(
208
+ "No response available. Did you forget to call 'evaluate()' in a loop?")
209
+ return self._recent_ident_response
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, List, Union
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxraise
7
+ from .matchingparameter import MatchingParameter
8
+ from .odxlink import OdxDocFragment
9
+
10
+ if TYPE_CHECKING:
11
+ from .matchingbasevariantparameter import MatchingBaseVariantParameter
12
+ from .matchingparameter import MatchingParameter
13
+
14
+
15
+ @dataclass
16
+ class VariantPattern:
17
+ """Variant patterns are used to identify the concrete variant of an ECU
18
+
19
+ This is done by observing the responses after sending it some
20
+ requests. There are two kinds of variant patterns:
21
+ `BaseVariantPattern`s which are used to identify the applicable
22
+ base variant and ECU variant patterns which can be used identify
23
+ concrete revisions of the ECU in question. (Both types of pattern
24
+ are optional, i.e., it might not be possible to identify the base-
25
+ or ECU variant present.)
26
+ """
27
+
28
+ def get_matching_parameters(
29
+ self) -> Union[List["MatchingParameter"], List["MatchingBaseVariantParameter"]]:
30
+ odxraise(
31
+ f"VariantPattern subclass `{type(self).__name__}` does not "
32
+ f"implement `.get_match_parameters()`", RuntimeError)
33
+ return []
34
+
35
+ @staticmethod
36
+ def from_et(et_element: ElementTree.Element,
37
+ doc_frags: List[OdxDocFragment]) -> "VariantPattern":
38
+ return VariantPattern()
odxtools/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '9.4.1'
21
- __version_tuple__ = version_tuple = (9, 4, 1)
20
+ __version__ = version = '9.6.0'
21
+ __version_tuple__ = version_tuple = (9, 6, 0)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: odxtools
3
- Version: 9.4.1
3
+ Version: 9.6.0
4
4
  Summary: Utilities to work with the ODX standard for automotive diagnostics
5
5
  Author-email: Katrin Bauer <katrin.bauer@mbition.io>, Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>
6
6
  Maintainer-email: Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>
@@ -43,6 +43,7 @@ Provides-Extra: examples
43
43
  Requires-Dist: can-isotp>=1.9; extra == "examples"
44
44
  Provides-Extra: all
45
45
  Requires-Dist: odxtools[browse-tool,examples,test]; extra == "all"
46
+ Dynamic: license-file
46
47
 
47
48
  <!-- SPDX-License-Identifier: MIT -->
48
49
  [![PyPi - Version](https://img.shields.io/pypi/v/odxtools)](https://pypi.org/project/odxtools)