odxtools 9.4.1__py3-none-any.whl → 9.5.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.
- odxtools/basevariantpattern.py +38 -0
- odxtools/dataobjectproperty.py +2 -2
- odxtools/diaglayers/basevariant.py +5 -0
- odxtools/diaglayers/basevariantraw.py +7 -1
- odxtools/diaglayers/ecuvariant.py +16 -0
- odxtools/ecuvariantpattern.py +20 -9
- odxtools/encodestate.py +3 -2
- odxtools/matchingbasevariantparameter.py +38 -0
- odxtools/matchingparameter.py +108 -27
- odxtools/minmaxlengthtype.py +3 -3
- odxtools/odxtypes.py +4 -3
- odxtools/standardlengthtype.py +10 -10
- odxtools/subcomponent.py +6 -3
- odxtools/templates/macros/printBaseVariant.xml.jinja2 +4 -9
- odxtools/templates/macros/printBaseVariantPattern.xml.jinja2 +32 -0
- odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +7 -6
- odxtools/utils.py +4 -0
- odxtools/variantmatcher.py +209 -0
- odxtools/variantpattern.py +38 -0
- odxtools/version.py +2 -2
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/METADATA +1 -1
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/RECORD +26 -23
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/WHEEL +1 -1
- odxtools/createecuvariantpatterns.py +0 -18
- odxtools/ecuvariantmatcher.py +0 -171
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/LICENSE +0 -0
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/entry_points.txt +0 -0
- {odxtools-9.4.1.dist-info → odxtools-9.5.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,19 @@
|
|
1
|
+
|
1
2
|
{#- -*- mode: sgml; tab-width: 1; indent-tabs-mode: nil -*-
|
2
3
|
#
|
3
4
|
# SPDX-License-Identifier: MIT
|
4
5
|
-#}
|
5
6
|
|
6
|
-
{%- import('macros/printElementId.xml.jinja2') as peid %}
|
7
|
-
{%- import('macros/printState.xml.jinja2') as ps %}
|
8
|
-
{%- import('macros/printStateTransition.xml.jinja2') as pst %}
|
9
|
-
|
10
7
|
{%- macro printMatchingParameter(mp) -%}
|
11
8
|
<MATCHING-PARAMETER>
|
12
9
|
<EXPECTED-VALUE>{{mp.expected_value | e}}</EXPECTED-VALUE>
|
13
10
|
<DIAG-COMM-SNREF SHORT-NAME="{{mp.diag_comm_snref}}" />
|
14
|
-
{
|
15
|
-
<OUT-PARAM-IF-SNREF SHORT-NAME="{{mp.
|
11
|
+
{%- if mp.out_param_if_snref is not none %}
|
12
|
+
<OUT-PARAM-IF-SNREF SHORT-NAME="{{mp.out_param_if_snref}}" />
|
13
|
+
{%- endif %}
|
14
|
+
{%- if mp.out_param_if_snpathref is not none %}
|
15
|
+
<OUT-PARAM-IF-SNPATHREF SHORT-NAME-PATH="{{mp.out_param_if_snpathref}}" />
|
16
|
+
{%- endif %}
|
16
17
|
</MATCHING-PARAMETER>
|
17
18
|
{%- endmacro -%}
|
18
19
|
|
odxtools/utils.py
CHANGED
@@ -3,11 +3,15 @@ import dataclasses
|
|
3
3
|
import re
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
5
5
|
|
6
|
+
from typing_extensions import SupportsBytes
|
7
|
+
|
6
8
|
if TYPE_CHECKING:
|
7
9
|
from .database import Database
|
8
10
|
from .diaglayers.diaglayer import DiagLayer
|
9
11
|
from .snrefcontext import SnRefContext
|
10
12
|
|
13
|
+
BytesTypes = (bytearray, bytes, SupportsBytes)
|
14
|
+
|
11
15
|
|
12
16
|
def retarget_snrefs(database: "Database",
|
13
17
|
diag_layer: "DiagLayer",
|
@@ -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
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: odxtools
|
3
|
-
Version: 9.
|
3
|
+
Version: 9.5.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>
|
@@ -4,6 +4,7 @@ odxtools/additionalaudience.py,sha256=Xjy_PhTfTV23oAMvml5XeZw3ex8xssIhgRseSsxKcx
|
|
4
4
|
odxtools/admindata.py,sha256=ue5MnYJK_jxNq6Okxy4aGtH15uBAnWXIjMBFSh36Q3g,1879
|
5
5
|
odxtools/audience.py,sha256=_O6UPKnrDbwMMKRnCJ5b8M3_I2jU5jMR6Ss5zLzk9yM,3361
|
6
6
|
odxtools/basecomparam.py,sha256=VxpIsTr6-S4e8Vw72hJXSLUFU5TvHy5CM0sweVL-ZWE,2384
|
7
|
+
odxtools/basevariantpattern.py,sha256=c6XSjPGNYErOiyl9SrROwShkreoC9tIU3zmoXXAHimo,1462
|
7
8
|
odxtools/basicstructure.py,sha256=GcyZuasaH62JcINgyeD300N297q7_LP6iMfDy8fwzMg,5071
|
8
9
|
odxtools/codec.py,sha256=2aTfrQc0-cNuPAfXeDDyjxXAHjBJ1xy3ELCVeA9UQP0,8108
|
9
10
|
odxtools/commrelation.py,sha256=Gh3CqMuF9rS0X107oxUkISNT2VWDZVPCpOSu54q7hcs,4793
|
@@ -19,9 +20,8 @@ odxtools/complexcomparam.py,sha256=4EJQXVmi_i3Eas5LAF-sEC5XnAUMZKr2sS6LoDNskk0,3
|
|
19
20
|
odxtools/complexdop.py,sha256=AHkHMf_IEWfy-_zSiCuZXmdTNCXiCOpg5GRZ5uMVV5A,280
|
20
21
|
odxtools/createanycomparam.py,sha256=RP0XA2Ut4GL2NWy05RvKBrTnpbvvCG2XuNL8OO-9rbI,617
|
21
22
|
odxtools/createanydiagcodedtype.py,sha256=0DVmMj4UCDYIg0zu9pgTplyhHmRedz1KJcchq1uKMjg,1216
|
22
|
-
odxtools/createecuvariantpatterns.py,sha256=FXHLGR4M791MRkpU2_keck88hoMYkuyPiCUxuGbUasQ,559
|
23
23
|
odxtools/database.py,sha256=VJk-EOzHHtxjkhP7o0h470B0oyg73ZafK3w1UjnwRNM,9945
|
24
|
-
odxtools/dataobjectproperty.py,sha256=
|
24
|
+
odxtools/dataobjectproperty.py,sha256=OMbD5P2T1A5b7qoDmRik08jWGSZFvaBn4CQI2fGNMQA,6244
|
25
25
|
odxtools/decodestate.py,sha256=VdYB4Q3yuFqCNV0IK2QfS_120n9dIgyLnPOwc3Hy33U,8292
|
26
26
|
odxtools/description.py,sha256=xaSfOjRCWn7_9BhdTgI0ESPtaU-iUE815Z6WobYYL-A,1952
|
27
27
|
odxtools/determinenumberofitems.py,sha256=DxM9udr1MQfEojiLdc35bbjniQl1fkeHcTMpoo9AfPI,1558
|
@@ -39,10 +39,9 @@ odxtools/dynamicendmarkerfield.py,sha256=Q7KKOBNSrAg5cehEP3i4YTPiqz5UetEIJDGah60
|
|
39
39
|
odxtools/dynamiclengthfield.py,sha256=Us0qRvkRPId7xSNKrYdsr1AhVFdTBezRMCBhXl98E00,5273
|
40
40
|
odxtools/dyndefinedspec.py,sha256=efvUG2kMUdwfmMG5qxGHzMSZ5CxdSJhmtzHecopnpHg,7610
|
41
41
|
odxtools/dynenddopref.py,sha256=SYXrmRUQoLWD4UI3WV92oA5rNhxbDuRv6H1MeXeuWhM,1199
|
42
|
-
odxtools/
|
43
|
-
odxtools/ecuvariantpattern.py,sha256=Xp2NpS13sVvyOjy2PgxLvXPptAfmYSH_KDIjqYByZHs,863
|
42
|
+
odxtools/ecuvariantpattern.py,sha256=HxiDLdZWD2aOtBoCKJQjpTY8Fm1ptFtFK3-CcCj3hQo,1330
|
44
43
|
odxtools/element.py,sha256=nCJkQMIPC26XfD4w1VrTH3l7Cd_BemMM_5lALh5gC7E,1259
|
45
|
-
odxtools/encodestate.py,sha256=
|
44
|
+
odxtools/encodestate.py,sha256=iufi35usTYoZ58iR6m1cN01VNFGeeGCmWV00dnAvr-o,13558
|
46
45
|
odxtools/encoding.py,sha256=5gn6PDqwU9K2RzEfEO9kcBj1v55TSKI1nCXNcTiZE9s,1994
|
47
46
|
odxtools/endofpdufield.py,sha256=mGkhTj-YGdJ4EHMsK3AlWV6G_KLWI4ymiFKbY8U2W50,3359
|
48
47
|
odxtools/environmentdata.py,sha256=_wSKhV_NuMG6UPMnpOxz6mVjvrXsd2mtqqT7VKSSxHU,1414
|
@@ -57,9 +56,10 @@ odxtools/isotp_state_machine.py,sha256=O6v29IK7p_4gFqrr-fahnbp9Qmo78EEQfEwRsA_Vf
|
|
57
56
|
odxtools/leadinglengthinfotype.py,sha256=oD0uwLECssJZDKicKYlOZRbrcCeTfz-CiWa-SROfrIU,4013
|
58
57
|
odxtools/library.py,sha256=E_A6K6jcEoUdXh4h4sQILj7_JM-6X5OUUWSM47G69CU,2067
|
59
58
|
odxtools/loadfile.py,sha256=hi8jVE7B1G9gklsb7MrGrp4aOAI65j0kNwvDeEEEN2k,1744
|
60
|
-
odxtools/
|
59
|
+
odxtools/matchingbasevariantparameter.py,sha256=hpxAWJQfEGsChEkSos5K8pJDgblu4gbBsEG6f2VhMg8,1287
|
60
|
+
odxtools/matchingparameter.py,sha256=nwdshZUUMc13XIKPyalMRYa-F64edg89UPIp-WjfTZU,5762
|
61
61
|
odxtools/message.py,sha256=PDY-CDgTmJA6dyNacCV57M-p_ETtR8MdU7WZGEew4zM,855
|
62
|
-
odxtools/minmaxlengthtype.py,sha256=
|
62
|
+
odxtools/minmaxlengthtype.py,sha256=TPDHQbHqOJTWNr7NGZSgtoEoYW1Cijilz8LL_smSLVA,9718
|
63
63
|
odxtools/modification.py,sha256=9EJJUCNTzM05HGT6UZ7r2yLDm7voBFPO-hldBC9D4aw,875
|
64
64
|
odxtools/multiplexer.py,sha256=flHlorv9grEMj8pNAWQJNrxhptnDxSsFERO7gpKYRME,10235
|
65
65
|
odxtools/multiplexercase.py,sha256=qfxWJzfJLYAsnhFGheUWDsUaIte2dSRJ0nadiN9SXDw,3176
|
@@ -70,7 +70,7 @@ odxtools/negoutputparam.py,sha256=u2IhJvy6n19t3nyADGG0H0XTm7JNdQ0-95FawGAdtiE,13
|
|
70
70
|
odxtools/obd.py,sha256=WsUbhdonqQJtxf972CUdYL78AMMTVjMQhoCJbaZhRIo,1860
|
71
71
|
odxtools/odxcategory.py,sha256=5R5vUTcYnsgfzGD3c0TM6qFYXpbW-d-t1VpUmgZYHWU,3221
|
72
72
|
odxtools/odxlink.py,sha256=WVPOXNIldLdz0YnwVpRVe42ugC6G1WX4Ic2Khhz-ito,10194
|
73
|
-
odxtools/odxtypes.py,sha256=
|
73
|
+
odxtools/odxtypes.py,sha256=DUZSjmdeHSq0XCBgU4CvZTM7TvmcB2WtpE25QWm3NMs,8100
|
74
74
|
odxtools/outputparam.py,sha256=VqD7jJ5yDvIq9KAatGWD17CWfuSqySQl142FXZYeQHE,1468
|
75
75
|
odxtools/parameterinfo.py,sha256=PzhIZyYINNxykecPQWnRH1Ywrfgxx31DVax2VNN__CY,11183
|
76
76
|
odxtools/paramlengthinfotype.py,sha256=zJ3gRweIEngl2C4wJoT2uuTf05NRNSJ4RlDmC2L3UBE,4859
|
@@ -90,13 +90,13 @@ odxtools/snrefcontext.py,sha256=5ZbrZiX1gw6LC6o62RPuxzSo4tDR2LqRQ9w9Vi57FA8,953
|
|
90
90
|
odxtools/specialdata.py,sha256=5-1D_6dcEmi-TodCzCoBZK7gqeDY-cuRe1ybkTh4rBY,1033
|
91
91
|
odxtools/specialdatagroup.py,sha256=9pNCA89kGwhBiVN9nC_03ASHZT6q3FL8l2CxRf9jwU0,3070
|
92
92
|
odxtools/specialdatagroupcaption.py,sha256=pIa1l6EXVDXBisUtRkn6hTzAcUXFnXJoWstesayLfME,987
|
93
|
-
odxtools/standardlengthtype.py,sha256
|
93
|
+
odxtools/standardlengthtype.py,sha256=nT0s-cvPKaOxxRnWPocY9rqHRgxnzFlW3iuCe_B8w2Y,8107
|
94
94
|
odxtools/state.py,sha256=d--nCbV1qBmOmiTmUrQjDC_naispOM0a-H1AvCe5_xI,898
|
95
95
|
odxtools/statechart.py,sha256=ZT8amPjec25pexwnOAPiifGEmKa68rliOILLnB6tNJc,2956
|
96
96
|
odxtools/statetransition.py,sha256=Q7UaDksJmiKVGnO4GsxPH9pFIKt12ZOkDeJn4KICiGk,2599
|
97
97
|
odxtools/staticfield.py,sha256=NBZa5UZD4BD4UekpqF5bq_IzFjxUafmvpCHIEptOl-s,4434
|
98
98
|
odxtools/structure.py,sha256=kRBP00MDN1w9w3Vc2XnhZDC_SBBVCZwayGGJoKJ6U_c,866
|
99
|
-
odxtools/subcomponent.py,sha256=
|
99
|
+
odxtools/subcomponent.py,sha256=Sn4CH0FelZ8j_Ny9w7B7qeK6YJFs3FGcYSMPawmHUmc,10498
|
100
100
|
odxtools/swvariable.py,sha256=7vmVTAqtKIn81UpAZrboIZpbbD4jqwW0MEiK0m5bZNo,697
|
101
101
|
odxtools/table.py,sha256=UTNQMVuQOj3uOYos_V1eH3QgWXncv6MPRXUhTYUxiMA,6030
|
102
102
|
odxtools/tablerow.py,sha256=QWOQxd83EN6jnOXDAc3ogaFxCz9R416vZSXpmZKjjnQ,9759
|
@@ -105,9 +105,11 @@ odxtools/uds.py,sha256=ttjCaqwfAU-DlM5peFpZCcwzq60QjBMxVpaOyUyY48k,5567
|
|
105
105
|
odxtools/unit.py,sha256=n2aJdXWvtZbqvoGUXDRA6nXc5QwM25suZZg8dQG6y1A,3408
|
106
106
|
odxtools/unitgroup.py,sha256=_WNpnYFmkiHQnOY8XhIviiQLXziY3kplH--7ufAWXyI,2067
|
107
107
|
odxtools/unitspec.py,sha256=IAmDSLscVb4QUGgbQT-xXhtywW3qs0o9FfelnVYvimk,3074
|
108
|
-
odxtools/utils.py,sha256=
|
108
|
+
odxtools/utils.py,sha256=VBURockmg0E9NgaPZu177zA64QV6TxYRth1tpn7MfI0,2857
|
109
109
|
odxtools/variablegroup.py,sha256=--5sMDOMUg4I3hFyoVOFcGCTOrctrrMADdmH3Mn4wHk,848
|
110
|
-
odxtools/
|
110
|
+
odxtools/variantmatcher.py,sha256=a1c1NtZgxRBA7OsRhEBM-3mgKUHqSywYXggT7Q5alJo,8355
|
111
|
+
odxtools/variantpattern.py,sha256=f3t1KXXjrNkJLGmEc7iGG86Nj4h6Fdh9loFKTt-1Ffc,1431
|
112
|
+
odxtools/version.py,sha256=EXUTEMMlT-sZN4IxnqEb0scBddeq3W9TNIgCRDO-ezE,511
|
111
113
|
odxtools/writepdxfile.py,sha256=ZEoNkSxheqnXm836-HOpd04O6ipMXYKFzXZy6PM0YLc,7813
|
112
114
|
odxtools/xdoc.py,sha256=gDq-8l8x-Tj1ZJOttPxZchcO5_jEPwcXxMgT29VgTS0,1433
|
113
115
|
odxtools/cli/__init__.py,sha256=T7ano_FIyzBASxYpmcA5VJXU5bQLIy_Qk0HE_SDKelY,106
|
@@ -141,14 +143,14 @@ odxtools/compumethods/scalelinearcompumethod.py,sha256=o_Re17G4XlCn-XbStnvlTs9XV
|
|
141
143
|
odxtools/compumethods/scaleratfunccompumethod.py,sha256=3imvLJgN3gkc7pILPIwAILHw0Ie5Ih_vysVGA8wYRgk,4457
|
142
144
|
odxtools/compumethods/tabintpcompumethod.py,sha256=Gyf2qV5y53SHIcum5d5HQVGszoZCdu47jxMCQ6yKdQk,8065
|
143
145
|
odxtools/compumethods/texttablecompumethod.py,sha256=vDYafbM0YaipjfN0NSlCFMiIPvp8waMNj3thNcwuYgk,5929
|
144
|
-
odxtools/diaglayers/basevariant.py,sha256=
|
145
|
-
odxtools/diaglayers/basevariantraw.py,sha256=
|
146
|
+
odxtools/diaglayers/basevariant.py,sha256=x9WW-gpYl517la1nc2CJMj08-S_KGFb_8nwIOQN9Vg0,4645
|
147
|
+
odxtools/diaglayers/basevariantraw.py,sha256=ujW58iFFWFSL9dzgUM4f5bPOA8oTZ7BjbH5lUHtLMJQ,4970
|
146
148
|
odxtools/diaglayers/diaglayer.py,sha256=xs529IVG_18Fi8kbEZkD2vHjHRr57jeE4A_2lDLRKCQ,16254
|
147
149
|
odxtools/diaglayers/diaglayerraw.py,sha256=ihOq51PWZUi4L7k0R5k06aLxZ8ppUkRYFym4hK_gXq8,12985
|
148
150
|
odxtools/diaglayers/diaglayertype.py,sha256=FXL-EVBdrAURuHSHo9OthFuEYEybQO66aMB1o2GxVdI,1340
|
149
151
|
odxtools/diaglayers/ecushareddata.py,sha256=QoV50wQIrJ2-faLtU1P7RrjdacbhPY2BfUtVtIyWqCM,3347
|
150
152
|
odxtools/diaglayers/ecushareddataraw.py,sha256=cvkBhqTLOAey7Pt1bjQ3Jkkenp-D3Zh5djpB4ZWWUvw,3375
|
151
|
-
odxtools/diaglayers/ecuvariant.py,sha256=
|
153
|
+
odxtools/diaglayers/ecuvariant.py,sha256=SE1JYUo3F67WO1wu8hYekW34s6OV7ORdoKkL62fZchU,4863
|
152
154
|
odxtools/diaglayers/ecuvariantraw.py,sha256=bKlqFnBWkKdkM9EY5rFGOno3ClIVQNhgBoU5DseNnRc,4944
|
153
155
|
odxtools/diaglayers/functionalgroup.py,sha256=o5bUGGDYisr7CjHZZh83fokA80f0s0zpHC_501cg-f0,4093
|
154
156
|
odxtools/diaglayers/functionalgroupraw.py,sha256=H0-_XXlFTV8POGnh0S7CVBOKKQuyKHMB9lEyoAHuG_A,3963
|
@@ -177,7 +179,8 @@ odxtools/templates/diag_layer_container.odx-d.xml.jinja2,sha256=fDH9K2IdGfX-dVV0
|
|
177
179
|
odxtools/templates/index.xml.jinja2,sha256=Z1bcntvvky7NoH0Q0CRJgUORwf3g96PFPGeOpi7VATw,647
|
178
180
|
odxtools/templates/macros/printAdminData.xml.jinja2,sha256=YTB_CbhnALF6RIbE0XKQHRbC6c0PSZ_DxdrRHgqMD_s,2590
|
179
181
|
odxtools/templates/macros/printAudience.xml.jinja2,sha256=vaYL_-GPVdlLc7WAQTbkVZZbFuQptnCbOhXJb3tsHv8,1286
|
180
|
-
odxtools/templates/macros/printBaseVariant.xml.jinja2,sha256=
|
182
|
+
odxtools/templates/macros/printBaseVariant.xml.jinja2,sha256=Ovf7NvO5jzy_9w1-iXi_q2dedoEfsQOYCK_WASkcITQ,1451
|
183
|
+
odxtools/templates/macros/printBaseVariantPattern.xml.jinja2,sha256=foL_DmH4mWFzPObZ3VjPixdzbomAA5jK7GLpAhJsKVE,1192
|
181
184
|
odxtools/templates/macros/printBasicStructure.xml.jinja2,sha256=V5oWGZesU_U5pvPO3Rtlodc7sxe5mFWY-uURLnQ8OfQ,614
|
182
185
|
odxtools/templates/macros/printCompanyData.xml.jinja2,sha256=jcNmQBOGDw7wcKdam72NIJSOTxthfkPJvrmw24MBiYQ,2981
|
183
186
|
odxtools/templates/macros/printComparam.xml.jinja2,sha256=e3hPCswAymJteQIUNoc7FOwsK_FrbJOvJooWdEx_vuQ,2408
|
@@ -193,7 +196,7 @@ odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2,sha256=6BmxPKesQ
|
|
193
196
|
odxtools/templates/macros/printDynamicLengthField.xml.jinja2,sha256=bNvNh_4xyp4L6v7cxJ5cO1yOHYPnSFiaK9oY4l63fDU,784
|
194
197
|
odxtools/templates/macros/printEcuSharedData.xml.jinja2,sha256=WK08eoDFEjj9o3eOuy__H0r9ioo688rTuQp6uXE6Tdo,820
|
195
198
|
odxtools/templates/macros/printEcuVariant.xml.jinja2,sha256=Nsgj2VUWZvSDFPQsn6wv75M9TaryOB-tlQYCVq9hDEM,1594
|
196
|
-
odxtools/templates/macros/printEcuVariantPattern.xml.jinja2,sha256=
|
199
|
+
odxtools/templates/macros/printEcuVariantPattern.xml.jinja2,sha256=YoYyzmxerJLJzUNoQ8H-JBnOqiwpVdODqk2KNRdWKWo,844
|
197
200
|
odxtools/templates/macros/printElementId.xml.jinja2,sha256=Oe4sgtBB_VhTz6uJzaiTCpQki2ulKKfpBq56FVgBC-I,570
|
198
201
|
odxtools/templates/macros/printEndOfPdu.xml.jinja2,sha256=ptjwOxyNTABATOajiitcvlJRKVGP_osh2wSJTCbaa38,679
|
199
202
|
odxtools/templates/macros/printEnvData.xml.jinja2,sha256=2Goatqb66d-pCf9wqJDXn1EZWNhkQ8FJANKQMOnxuJs,609
|
@@ -221,9 +224,9 @@ odxtools/templates/macros/printStructure.xml.jinja2,sha256=M8p82SlgKaDk6nJA-0wr6
|
|
221
224
|
odxtools/templates/macros/printSubComponent.xml.jinja2,sha256=ETo-k0K7px5nu9kgVjgDnjPoy8rYRfzq5ojvsZhM3Rw,3416
|
222
225
|
odxtools/templates/macros/printTable.xml.jinja2,sha256=IMAaYNtpxs_6V7VxM_hfOeGkFfkyRjSmwvmr9_K8Ddc,3075
|
223
226
|
odxtools/templates/macros/printUnitSpec.xml.jinja2,sha256=KOjRVwtdIg7uGl4B9cLpvW68-TYeE33Tz9sWZdnwkBE,2878
|
224
|
-
odxtools-9.
|
225
|
-
odxtools-9.
|
226
|
-
odxtools-9.
|
227
|
-
odxtools-9.
|
228
|
-
odxtools-9.
|
229
|
-
odxtools-9.
|
227
|
+
odxtools-9.5.0.dist-info/LICENSE,sha256=NeGPFQdTa6EKeON3aShVlPAIquJnbbiOfj0suz6rzyQ,1074
|
228
|
+
odxtools-9.5.0.dist-info/METADATA,sha256=TG3F5Qqkpiu1FVtwEkeH1s3eKEyHWR6LR1eyBe6g3N0,44055
|
229
|
+
odxtools-9.5.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
230
|
+
odxtools-9.5.0.dist-info/entry_points.txt,sha256=_sBDzuNoT8LbbCjfc-OJiUt5WPrtOq_x-rr9txhrPjY,53
|
231
|
+
odxtools-9.5.0.dist-info/top_level.txt,sha256=pdS02kE5ZdgsaBRZDpX3NBFlaSx3zotsqX4E4V6tXEI,9
|
232
|
+
odxtools-9.5.0.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
2
|
-
from typing import List, Optional
|
3
|
-
from xml.etree import ElementTree
|
4
|
-
|
5
|
-
from .ecuvariantpattern import EcuVariantPattern
|
6
|
-
from .odxlink import OdxDocFragment
|
7
|
-
|
8
|
-
|
9
|
-
def create_ecu_variant_patterns_from_et(et_element: Optional[ElementTree.Element],
|
10
|
-
doc_frags: List[OdxDocFragment]) -> List[EcuVariantPattern]:
|
11
|
-
|
12
|
-
if et_element is None:
|
13
|
-
return []
|
14
|
-
|
15
|
-
return [
|
16
|
-
EcuVariantPattern.from_et(evp_elem, doc_frags)
|
17
|
-
for evp_elem in et_element.iterfind("ECU-VARIANT-PATTERN")
|
18
|
-
]
|
odxtools/ecuvariantmatcher.py
DELETED
@@ -1,171 +0,0 @@
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
2
|
-
from enum import Enum
|
3
|
-
from typing import Dict, Generator, List, Optional
|
4
|
-
|
5
|
-
from .diaglayers.diaglayer import DiagLayer
|
6
|
-
from .diaglayers.diaglayertype import DiagLayerType
|
7
|
-
from .diaglayers.ecuvariant import EcuVariant
|
8
|
-
from .diagservice import DiagService
|
9
|
-
from .exceptions import OdxError, odxassert, odxrequire
|
10
|
-
from .matchingparameter import MatchingParameter
|
11
|
-
from .odxtypes import ParameterValue
|
12
|
-
from .response import Response
|
13
|
-
|
14
|
-
|
15
|
-
class EcuVariantMatcher:
|
16
|
-
"""EcuVariantMatcher implements the matching algorithm of ecu variants according to their
|
17
|
-
ECU-VARIANT-PATTERN according to ISO 22901-1.
|
18
|
-
|
19
|
-
Usage (example):
|
20
|
-
|
21
|
-
```python
|
22
|
-
|
23
|
-
# initialize the matcher with a list of ecu variants,
|
24
|
-
# i.e., DiagLayer instances of variant_type == DiagLayerType.ECU-VARIANT
|
25
|
-
matcher = EcuVariantMatcher(ecu_variant_candidates=[...], use_cache=use_cache)
|
26
|
-
|
27
|
-
# run the request loop to obtain responses for every request
|
28
|
-
for req in matcher.request_loop():
|
29
|
-
resp = ... # make a UDS request
|
30
|
-
matcher.evaluate(resp)
|
31
|
-
|
32
|
-
# result
|
33
|
-
if matcher.has_match()
|
34
|
-
match = matcher.get_active_ecu_variant()
|
35
|
-
```
|
36
|
-
"""
|
37
|
-
|
38
|
-
class State(Enum):
|
39
|
-
PENDING = 0
|
40
|
-
NO_MATCH = 1
|
41
|
-
MATCH = 2
|
42
|
-
|
43
|
-
@staticmethod
|
44
|
-
def get_ident_service(diag_layer: DiagLayer, matching_param: MatchingParameter) -> DiagService:
|
45
|
-
service_name = matching_param.diag_comm_snref
|
46
|
-
service = odxrequire(diag_layer.services.get(service_name))
|
47
|
-
return service
|
48
|
-
|
49
|
-
@staticmethod
|
50
|
-
def encode_ident_request(diag_layer: DiagLayer, matching_param: MatchingParameter) -> bytes:
|
51
|
-
service = EcuVariantMatcher.get_ident_service(diag_layer, matching_param)
|
52
|
-
return bytes(service.encode_request())
|
53
|
-
|
54
|
-
@staticmethod
|
55
|
-
def decode_ident_response(
|
56
|
-
diag_layer: DiagLayer,
|
57
|
-
matching_param: MatchingParameter,
|
58
|
-
response_bytes: bytes,
|
59
|
-
) -> str:
|
60
|
-
"""Decode a binary response and extract the identification string according
|
61
|
-
to the snref or snpathref of the matching_param.
|
62
|
-
"""
|
63
|
-
service = EcuVariantMatcher.get_ident_service(diag_layer, matching_param)
|
64
|
-
|
65
|
-
# ISO 22901 requires that snref or snpathref is resolvable in at least one
|
66
|
-
# POS-RESPONSE or NEG-RESPONSE
|
67
|
-
pos_neg_responses: List[Response] = []
|
68
|
-
if service.positive_responses is not None:
|
69
|
-
pos_neg_responses.extend(service.positive_responses)
|
70
|
-
if service.negative_responses is not None:
|
71
|
-
pos_neg_responses.extend(service.negative_responses)
|
72
|
-
|
73
|
-
for any_response in pos_neg_responses:
|
74
|
-
decoded_val: Optional[ParameterValue] = any_response.decode(response_bytes)
|
75
|
-
# disassemble snref / snpathref
|
76
|
-
path_ref = matching_param.out_param_if.split(".")
|
77
|
-
for ref in path_ref:
|
78
|
-
if isinstance(decoded_val, dict) and ref in decoded_val:
|
79
|
-
decoded_val = decoded_val[ref]
|
80
|
-
else:
|
81
|
-
decoded_val = None
|
82
|
-
break
|
83
|
-
|
84
|
-
if decoded_val is not None:
|
85
|
-
if isinstance(decoded_val, str) or isinstance(decoded_val, int):
|
86
|
-
return str(decoded_val)
|
87
|
-
|
88
|
-
raise OdxError(f"The snref or snpathref '{matching_param.out_param_if}' is cannot be \
|
89
|
-
resolved for any positive or negative response.")
|
90
|
-
|
91
|
-
def __init__(self, ecu_variant_candidates: List[EcuVariant], use_cache: bool = True):
|
92
|
-
|
93
|
-
self.ecus = ecu_variant_candidates
|
94
|
-
for ecu in self.ecus:
|
95
|
-
odxassert(ecu.variant_type == DiagLayerType.ECU_VARIANT)
|
96
|
-
|
97
|
-
self.use_cache = use_cache
|
98
|
-
self.req_resp_cache: Dict[bytes, bytes] = {}
|
99
|
-
self._recent_ident_response: Optional[bytes] = None
|
100
|
-
|
101
|
-
self._state = EcuVariantMatcher.State.PENDING
|
102
|
-
|
103
|
-
def request_loop(self) -> Generator[bytes, None, None]:
|
104
|
-
"""The request loop yields byte sequences of requests, which shall be executed within the
|
105
|
-
loop body. It is required to pass the response back to the matcher using the evaluate method.
|
106
|
-
"""
|
107
|
-
if not self.is_pending():
|
108
|
-
return
|
109
|
-
|
110
|
-
for ecu in self.ecus:
|
111
|
-
any_match = False
|
112
|
-
for pattern in ecu.ecu_variant_patterns:
|
113
|
-
all_match = True
|
114
|
-
for matching_param in pattern.matching_parameters:
|
115
|
-
req_bytes = bytes(EcuVariantMatcher.encode_ident_request(ecu, matching_param))
|
116
|
-
if self.use_cache and req_bytes in self.req_resp_cache:
|
117
|
-
resp_bytes = self.req_resp_cache[req_bytes]
|
118
|
-
else:
|
119
|
-
yield req_bytes
|
120
|
-
resp_bytes = self._get_ident_response()
|
121
|
-
self._update_cache(req_bytes, resp_bytes)
|
122
|
-
ident_val = EcuVariantMatcher.decode_ident_response(
|
123
|
-
ecu, matching_param, resp_bytes)
|
124
|
-
all_match &= matching_param.is_match(ident_val)
|
125
|
-
if all_match:
|
126
|
-
any_match = True
|
127
|
-
break
|
128
|
-
if any_match:
|
129
|
-
self._state = EcuVariantMatcher.State.MATCH
|
130
|
-
self._match = ecu
|
131
|
-
break
|
132
|
-
if self.is_pending():
|
133
|
-
# no pattern has matched for any ecu variant
|
134
|
-
self._state = EcuVariantMatcher.State.NO_MATCH
|
135
|
-
|
136
|
-
def evaluate(self, resp_bytes: bytes) -> None:
|
137
|
-
"""Update the matcher with the response to a requst.
|
138
|
-
|
139
|
-
Warning: Use this method EXACTLY once within the loop body of the request loop.
|
140
|
-
"""
|
141
|
-
self._recent_ident_response = bytes(resp_bytes)
|
142
|
-
|
143
|
-
def is_pending(self) -> bool:
|
144
|
-
"""True iff request loop has not yet been run."""
|
145
|
-
return self._state == EcuVariantMatcher.State.PENDING
|
146
|
-
|
147
|
-
def has_match(self) -> bool:
|
148
|
-
"""Returns true iff the non-pending matcher found a matching ecu variant.
|
149
|
-
|
150
|
-
Raises a runtime error if the matcher is pending.
|
151
|
-
"""
|
152
|
-
if self.is_pending():
|
153
|
-
raise RuntimeError(
|
154
|
-
"EcuVariantMatcher is pending. Run the request_loop to determine the active ecu variant."
|
155
|
-
)
|
156
|
-
return self._state == EcuVariantMatcher.State.MATCH
|
157
|
-
|
158
|
-
def get_active_ecu_variant(self) -> DiagLayer:
|
159
|
-
"""Returns the matched, i.e., active ecu variant if such a variant has been found."""
|
160
|
-
odxassert(self.has_match())
|
161
|
-
return self._match
|
162
|
-
|
163
|
-
def _update_cache(self, req_bytes: bytes, resp_bytes: bytes) -> None:
|
164
|
-
if self.use_cache:
|
165
|
-
self.req_resp_cache[req_bytes] = resp_bytes
|
166
|
-
|
167
|
-
def _get_ident_response(self) -> bytes:
|
168
|
-
if not self._recent_ident_response:
|
169
|
-
raise RuntimeError(
|
170
|
-
"No response available. Did you forget to call 'evaluate()' in a loop?")
|
171
|
-
return self._recent_ident_response
|
File without changes
|
File without changes
|
File without changes
|