odxtools 7.4.0__py3-none-any.whl → 8.0.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/cli/_print_utils.py +4 -3
- odxtools/cli/browse.py +13 -11
- odxtools/cli/compare.py +1 -1
- odxtools/cli/list.py +20 -10
- odxtools/cli/snoop.py +28 -5
- odxtools/database.py +46 -10
- odxtools/diagcomm.py +4 -4
- odxtools/diaglayercontainer.py +23 -11
- odxtools/diaglayers/basevariant.py +128 -0
- odxtools/diaglayers/basevariantraw.py +117 -0
- odxtools/diaglayers/diaglayer.py +422 -0
- odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +18 -156
- odxtools/diaglayers/ecushareddata.py +96 -0
- odxtools/diaglayers/ecushareddataraw.py +81 -0
- odxtools/diaglayers/ecuvariant.py +124 -0
- odxtools/diaglayers/ecuvariantraw.py +123 -0
- odxtools/diaglayers/functionalgroup.py +110 -0
- odxtools/diaglayers/functionalgroupraw.py +100 -0
- odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +183 -498
- odxtools/diaglayers/hierarchyelementraw.py +52 -0
- odxtools/diaglayers/protocol.py +64 -0
- odxtools/diaglayers/protocolraw.py +85 -0
- odxtools/diagvariable.py +10 -1
- odxtools/ecuvariantmatcher.py +6 -7
- odxtools/field.py +3 -4
- odxtools/matchingparameter.py +1 -1
- odxtools/nameditemlist.py +4 -3
- odxtools/parameterinfo.py +10 -1
- odxtools/parameters/parameterwithdop.py +2 -3
- odxtools/parentref.py +1 -1
- odxtools/snrefcontext.py +1 -1
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +11 -6
- odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +206 -0
- odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
- odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
- odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
- odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
- odxtools/variablegroup.py +11 -1
- odxtools/version.py +2 -2
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/METADATA +1 -1
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/RECORD +49 -31
- odxtools/templates/macros/printVariant.xml.jinja2 +0 -241
- /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/LICENSE +0 -0
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/WHEEL +0 -0
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/entry_points.txt +0 -0
- {odxtools-7.4.0.dist-info → odxtools-8.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,422 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from copy import copy, deepcopy
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from functools import cached_property
|
5
|
+
from itertools import chain
|
6
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
|
7
|
+
from xml.etree import ElementTree
|
8
|
+
|
9
|
+
from ..admindata import AdminData
|
10
|
+
from ..companydata import CompanyData
|
11
|
+
from ..description import Description
|
12
|
+
from ..diagcomm import DiagComm
|
13
|
+
from ..diagdatadictionaryspec import DiagDataDictionarySpec
|
14
|
+
from ..diagservice import DiagService
|
15
|
+
from ..exceptions import DecodeError, odxassert, odxraise
|
16
|
+
from ..message import Message
|
17
|
+
from ..nameditemlist import NamedItemList, TNamed
|
18
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
19
|
+
from ..parentref import ParentRef
|
20
|
+
from ..request import Request
|
21
|
+
from ..response import Response
|
22
|
+
from ..servicebinner import ServiceBinner
|
23
|
+
from ..singleecujob import SingleEcuJob
|
24
|
+
from ..snrefcontext import SnRefContext
|
25
|
+
from ..specialdatagroup import SpecialDataGroup
|
26
|
+
from ..unitgroup import UnitGroup
|
27
|
+
from .diaglayerraw import DiagLayerRaw
|
28
|
+
from .diaglayertype import DiagLayerType
|
29
|
+
|
30
|
+
PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass
|
34
|
+
class DiagLayer:
|
35
|
+
"""This class represents a "logical view" upon a diagnostic layer
|
36
|
+
according to the ODX standard.
|
37
|
+
|
38
|
+
i.e. it handles the value inheritance, communication parameters,
|
39
|
+
encoding/decoding of data, etc.
|
40
|
+
"""
|
41
|
+
|
42
|
+
diag_layer_raw: DiagLayerRaw
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
|
46
|
+
diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
|
47
|
+
|
48
|
+
# Create DiagLayer
|
49
|
+
return DiagLayer(diag_layer_raw=diag_layer_raw)
|
50
|
+
|
51
|
+
def __post_init__(self) -> None:
|
52
|
+
if self.diag_layer_raw.diag_data_dictionary_spec is None:
|
53
|
+
# create an empry DiagDataDictionarySpec object if the raw
|
54
|
+
# layer does not define a DDDS...
|
55
|
+
self._diag_data_dictionary_spec = DiagDataDictionarySpec(
|
56
|
+
admin_data=None,
|
57
|
+
data_object_props=NamedItemList(),
|
58
|
+
dtc_dops=NamedItemList(),
|
59
|
+
structures=NamedItemList(),
|
60
|
+
static_fields=NamedItemList(),
|
61
|
+
end_of_pdu_fields=NamedItemList(),
|
62
|
+
dynamic_endmarker_fields=NamedItemList(),
|
63
|
+
dynamic_length_fields=NamedItemList(),
|
64
|
+
tables=NamedItemList(),
|
65
|
+
env_data_descs=NamedItemList(),
|
66
|
+
env_datas=NamedItemList(),
|
67
|
+
muxs=NamedItemList(),
|
68
|
+
unit_spec=None,
|
69
|
+
sdgs=[])
|
70
|
+
else:
|
71
|
+
self._diag_data_dictionary_spec = self.diag_layer_raw.diag_data_dictionary_spec
|
72
|
+
|
73
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
74
|
+
"""Construct a mapping from IDs to all objects that are contained in this diagnostic layer."""
|
75
|
+
result = self.diag_layer_raw._build_odxlinks()
|
76
|
+
|
77
|
+
# we want to get the full diag layer, not just the raw layer
|
78
|
+
# when referencing...
|
79
|
+
result[self.odx_id] = self
|
80
|
+
|
81
|
+
return result
|
82
|
+
|
83
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
84
|
+
"""Recursively resolve all ODXLINK references."""
|
85
|
+
|
86
|
+
# deal with the import references: these basically extend the
|
87
|
+
# pool of objects that are referenceable without having to
|
88
|
+
# explicitly specify the DOCREF attribute in the
|
89
|
+
# reference. This mechanism can thus be seen as a kind of
|
90
|
+
# "poor man's inheritance".
|
91
|
+
if self.import_refs:
|
92
|
+
imported_links: Dict[OdxLinkId, Any] = {}
|
93
|
+
for import_ref in self.import_refs:
|
94
|
+
imported_dl = odxlinks.resolve(import_ref, DiagLayer)
|
95
|
+
|
96
|
+
odxassert(
|
97
|
+
imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
|
98
|
+
f"Tried to import references from diagnostic layer "
|
99
|
+
f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
|
100
|
+
f"Only ECU-SHARED-DATA layers may be referenced using the "
|
101
|
+
f"IMPORT-REF mechanism")
|
102
|
+
|
103
|
+
# TODO: ensure that the imported diagnostic layer has
|
104
|
+
# not been referenced in any PARENT-REF of the current
|
105
|
+
# layer or any of its parents.
|
106
|
+
|
107
|
+
# TODO: detect and complain about cyclic IMPORT-REFs
|
108
|
+
|
109
|
+
# TODO (?): detect conflicts with locally-defined
|
110
|
+
# objects
|
111
|
+
|
112
|
+
imported_dl_links = imported_dl._build_odxlinks()
|
113
|
+
for link_id, obj in imported_dl_links.items():
|
114
|
+
# the imported objects shall behave as if they
|
115
|
+
# were defined by the importing layer. IOW, they
|
116
|
+
# must be visible in the same document fragments.
|
117
|
+
link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
|
118
|
+
imported_links[link_id] = obj
|
119
|
+
|
120
|
+
# We need to copy the odxlink database here since this
|
121
|
+
# function must not modify its argument because the
|
122
|
+
# imported references only apply within this specific
|
123
|
+
# diagnostic layer
|
124
|
+
extended_odxlinks = copy(odxlinks)
|
125
|
+
extended_odxlinks.update(imported_links)
|
126
|
+
|
127
|
+
self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
|
128
|
+
return
|
129
|
+
|
130
|
+
self.diag_layer_raw._resolve_odxlinks(odxlinks)
|
131
|
+
|
132
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
133
|
+
self.diag_layer_raw._resolve_snrefs(context)
|
134
|
+
|
135
|
+
def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
|
136
|
+
"""Return the list of locally defined diagnostic communications.
|
137
|
+
|
138
|
+
This is not completely trivial as it requires to resolving the
|
139
|
+
references specified in the <DIAG-COMMS> XML tag.
|
140
|
+
"""
|
141
|
+
return self.diag_layer_raw.diag_comms
|
142
|
+
|
143
|
+
def _get_local_unit_groups(self) -> Iterable[UnitGroup]:
|
144
|
+
if self.diag_layer_raw.diag_data_dictionary_spec is None:
|
145
|
+
return []
|
146
|
+
|
147
|
+
unit_spec = self.diag_layer_raw.diag_data_dictionary_spec.unit_spec
|
148
|
+
if unit_spec is None:
|
149
|
+
return []
|
150
|
+
|
151
|
+
return unit_spec.unit_groups
|
152
|
+
|
153
|
+
def _compute_available_objects(
|
154
|
+
self,
|
155
|
+
get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
|
156
|
+
get_not_inherited: Callable[[ParentRef], Iterable[str]],
|
157
|
+
) -> Iterable[TNamed]:
|
158
|
+
"""Helper method to compute the set of all objects applicable
|
159
|
+
to the DiagLayer if these objects are subject to the value
|
160
|
+
inheritance mechanism
|
161
|
+
|
162
|
+
This is the simplified version for diag layers which do not
|
163
|
+
have parents and thus do not deal with value inheritance
|
164
|
+
(i.e., ECU-SHARED-DATA).
|
165
|
+
|
166
|
+
"""
|
167
|
+
return get_local_objects(self)
|
168
|
+
|
169
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
|
170
|
+
"""Create a deep copy of the diagnostic layer
|
171
|
+
|
172
|
+
Note that the copied diagnostic layer is not fully
|
173
|
+
initialized, so `_finalize_init()` should to be called on it
|
174
|
+
before it can be used normally.
|
175
|
+
"""
|
176
|
+
cls = self.__class__
|
177
|
+
result = cls.__new__(cls)
|
178
|
+
memo[id(self)] = result
|
179
|
+
|
180
|
+
result.diag_layer_raw = deepcopy(self.diag_layer_raw, memo)
|
181
|
+
|
182
|
+
return result
|
183
|
+
|
184
|
+
#####
|
185
|
+
# <convenience functionality>
|
186
|
+
#####
|
187
|
+
@cached_property
|
188
|
+
def service_groups(self) -> ServiceBinner:
|
189
|
+
return ServiceBinner(self.services)
|
190
|
+
|
191
|
+
#####
|
192
|
+
# </convenience functionality>
|
193
|
+
#####
|
194
|
+
|
195
|
+
#####
|
196
|
+
# <properties forwarded to the "raw" diag layer>
|
197
|
+
#####
|
198
|
+
@property
|
199
|
+
def variant_type(self) -> DiagLayerType:
|
200
|
+
return self.diag_layer_raw.variant_type
|
201
|
+
|
202
|
+
@property
|
203
|
+
def odx_id(self) -> OdxLinkId:
|
204
|
+
return self.diag_layer_raw.odx_id
|
205
|
+
|
206
|
+
@property
|
207
|
+
def short_name(self) -> str:
|
208
|
+
return self.diag_layer_raw.short_name
|
209
|
+
|
210
|
+
@property
|
211
|
+
def long_name(self) -> Optional[str]:
|
212
|
+
return self.diag_layer_raw.long_name
|
213
|
+
|
214
|
+
@property
|
215
|
+
def description(self) -> Optional[Description]:
|
216
|
+
return self.diag_layer_raw.description
|
217
|
+
|
218
|
+
@property
|
219
|
+
def admin_data(self) -> Optional[AdminData]:
|
220
|
+
return self.diag_layer_raw.admin_data
|
221
|
+
|
222
|
+
@property
|
223
|
+
def diag_comms(self) -> NamedItemList[DiagComm]:
|
224
|
+
return self.diag_layer_raw.diag_comms
|
225
|
+
|
226
|
+
@property
|
227
|
+
def services(self) -> NamedItemList[DiagService]:
|
228
|
+
return self.diag_layer_raw.services
|
229
|
+
|
230
|
+
@property
|
231
|
+
def diag_services(self) -> NamedItemList[DiagService]:
|
232
|
+
return self.diag_layer_raw.diag_services
|
233
|
+
|
234
|
+
@property
|
235
|
+
def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
|
236
|
+
return self.diag_layer_raw.single_ecu_jobs
|
237
|
+
|
238
|
+
@property
|
239
|
+
def company_datas(self) -> NamedItemList[CompanyData]:
|
240
|
+
return self.diag_layer_raw.company_datas
|
241
|
+
|
242
|
+
@property
|
243
|
+
def requests(self) -> NamedItemList[Request]:
|
244
|
+
return self.diag_layer_raw.requests
|
245
|
+
|
246
|
+
@property
|
247
|
+
def positive_responses(self) -> NamedItemList[Response]:
|
248
|
+
return self.diag_layer_raw.positive_responses
|
249
|
+
|
250
|
+
@property
|
251
|
+
def negative_responses(self) -> NamedItemList[Response]:
|
252
|
+
return self.diag_layer_raw.negative_responses
|
253
|
+
|
254
|
+
@property
|
255
|
+
def global_negative_responses(self) -> NamedItemList[Response]:
|
256
|
+
return self.diag_layer_raw.global_negative_responses
|
257
|
+
|
258
|
+
@property
|
259
|
+
def import_refs(self) -> List[OdxLinkRef]:
|
260
|
+
return self.diag_layer_raw.import_refs
|
261
|
+
|
262
|
+
@property
|
263
|
+
def sdgs(self) -> List[SpecialDataGroup]:
|
264
|
+
return self.diag_layer_raw.sdgs
|
265
|
+
|
266
|
+
@property
|
267
|
+
def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
|
268
|
+
"""The DiagDataDictionarySpec applicable to this DiagLayer"""
|
269
|
+
return self._diag_data_dictionary_spec
|
270
|
+
|
271
|
+
#####
|
272
|
+
# </properties forwarded to the "raw" diag layer>
|
273
|
+
#####
|
274
|
+
|
275
|
+
#####
|
276
|
+
# <PDU decoding>
|
277
|
+
#####
|
278
|
+
@cached_property
|
279
|
+
def _prefix_tree(self) -> PrefixTree:
|
280
|
+
"""Constructs the coded prefix tree of the services.
|
281
|
+
|
282
|
+
Each leaf node is a list of `DiagService`s. (This is because
|
283
|
+
navigating from a service to the request/ responses is easier
|
284
|
+
than finding the service for a given request/response object.)
|
285
|
+
|
286
|
+
Example:
|
287
|
+
Let there be four services with corresponding requests:
|
288
|
+
* Request 1 has the coded constant prefix `12 34`.
|
289
|
+
* Request 2 has the coded constant prefix `12 34`.
|
290
|
+
* Request 3 has the coded constant prefix `12 56`.
|
291
|
+
* Request 4 has the coded constant prefix `12 56 00`.
|
292
|
+
|
293
|
+
Then, the constructed prefix tree is the dict
|
294
|
+
```
|
295
|
+
{0x12: {0x34: {-1: [<Service 1>, <Service 2>]},
|
296
|
+
0x56: {-1: [<Service 3>],
|
297
|
+
0x0: {-1: [<Service 4>]}
|
298
|
+
}}}
|
299
|
+
```
|
300
|
+
Note, that the inner `-1` are constant to distinguish them
|
301
|
+
from possible service IDs.
|
302
|
+
|
303
|
+
Also note, that it is actually allowed that
|
304
|
+
(a) SIDs for different services are the same like for service
|
305
|
+
1 and 2 (thus each leaf node is a list) and
|
306
|
+
(b) one SID is the prefix of another SID like for service 3
|
307
|
+
and 4 (thus the constant `-1` key).
|
308
|
+
|
309
|
+
"""
|
310
|
+
prefix_tree: PrefixTree = {}
|
311
|
+
for s in self.services:
|
312
|
+
# Compute prefixes for the service's request and all
|
313
|
+
# possible responses. We need to consider the global
|
314
|
+
# negative responses here, because they might contain
|
315
|
+
# MATCHING-REQUEST parameters. If these global responses
|
316
|
+
# do not contain such parameters, this will potentially
|
317
|
+
# result in an enormous amount of decoded messages for
|
318
|
+
# global negative responses. (I.e., one for each
|
319
|
+
# service. This can be avoided by specifying the
|
320
|
+
# corresponding request for `decode_response()`.)
|
321
|
+
request_prefix = b''
|
322
|
+
if s.request is not None:
|
323
|
+
request_prefix = s.request.coded_const_prefix()
|
324
|
+
prefixes = [request_prefix]
|
325
|
+
gnrs = getattr(self, "global_negative_responses", [])
|
326
|
+
prefixes += [
|
327
|
+
x.coded_const_prefix(request_prefix=request_prefix)
|
328
|
+
for x in chain(s.positive_responses, s.negative_responses, gnrs)
|
329
|
+
]
|
330
|
+
for coded_prefix in prefixes:
|
331
|
+
self._extend_prefix_tree(prefix_tree, coded_prefix, s)
|
332
|
+
|
333
|
+
return prefix_tree
|
334
|
+
|
335
|
+
@staticmethod
|
336
|
+
def _extend_prefix_tree(prefix_tree: PrefixTree, coded_prefix: bytes,
|
337
|
+
service: DiagService) -> None:
|
338
|
+
|
339
|
+
# make sure that tree has an entry for the given prefix
|
340
|
+
sub_tree = prefix_tree
|
341
|
+
for b in coded_prefix:
|
342
|
+
if b not in sub_tree:
|
343
|
+
sub_tree[b] = {}
|
344
|
+
sub_tree = cast(PrefixTree, sub_tree[b])
|
345
|
+
|
346
|
+
# Store the object as in the prefix tree. This is done by
|
347
|
+
# assigning the list of possible objects to the key -1 of the
|
348
|
+
# dictionary (this is quite hacky...)
|
349
|
+
if sub_tree.get(-1) is None:
|
350
|
+
sub_tree[-1] = [service]
|
351
|
+
else:
|
352
|
+
cast(List[DiagService], sub_tree[-1]).append(service)
|
353
|
+
|
354
|
+
def _find_services_for_uds(self, message: bytes) -> List[DiagService]:
|
355
|
+
prefix_tree = self._prefix_tree
|
356
|
+
|
357
|
+
# Find matching service(s) in prefix tree
|
358
|
+
possible_services: List[DiagService] = []
|
359
|
+
for b in message:
|
360
|
+
if b in prefix_tree:
|
361
|
+
odxassert(isinstance(prefix_tree[b], dict))
|
362
|
+
prefix_tree = cast(PrefixTree, prefix_tree[b])
|
363
|
+
else:
|
364
|
+
break
|
365
|
+
if -1 in prefix_tree:
|
366
|
+
possible_services += cast(List[DiagService], prefix_tree[-1])
|
367
|
+
return possible_services
|
368
|
+
|
369
|
+
def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> List[Message]:
|
370
|
+
decoded_messages: List[Message] = []
|
371
|
+
|
372
|
+
for service in candidate_services:
|
373
|
+
try:
|
374
|
+
decoded_messages.append(service.decode_message(message))
|
375
|
+
except DecodeError as e:
|
376
|
+
# check if the message can be decoded as a global
|
377
|
+
# negative response for the service
|
378
|
+
gnr_found = False
|
379
|
+
for gnr in self.global_negative_responses:
|
380
|
+
try:
|
381
|
+
decoded_gnr = gnr.decode(message)
|
382
|
+
gnr_found = True
|
383
|
+
if not isinstance(decoded_gnr, dict):
|
384
|
+
odxraise(
|
385
|
+
f"Expected the decoded value of a global "
|
386
|
+
f"negative response to be a dictionary, "
|
387
|
+
f"got {type(decoded_gnr)} for {self.short_name}", DecodeError)
|
388
|
+
|
389
|
+
decoded_messages.append(
|
390
|
+
Message(
|
391
|
+
coded_message=message,
|
392
|
+
service=service,
|
393
|
+
coding_object=gnr,
|
394
|
+
param_dict=decoded_gnr))
|
395
|
+
except DecodeError:
|
396
|
+
pass
|
397
|
+
|
398
|
+
if not gnr_found:
|
399
|
+
raise e
|
400
|
+
|
401
|
+
if len(decoded_messages) == 0:
|
402
|
+
raise DecodeError(
|
403
|
+
f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
|
404
|
+
)
|
405
|
+
|
406
|
+
return decoded_messages
|
407
|
+
|
408
|
+
def decode(self, message: bytes) -> List[Message]:
|
409
|
+
candidate_services = self._find_services_for_uds(message)
|
410
|
+
|
411
|
+
return self._decode(message, candidate_services)
|
412
|
+
|
413
|
+
def decode_response(self, response: bytes, request: bytes) -> List[Message]:
|
414
|
+
candidate_services = self._find_services_for_uds(request)
|
415
|
+
if candidate_services is None:
|
416
|
+
raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
|
417
|
+
|
418
|
+
return self._decode(response, candidate_services)
|
419
|
+
|
420
|
+
#####
|
421
|
+
# </PDU decoding>
|
422
|
+
#####
|
@@ -4,34 +4,25 @@ from dataclasses import dataclass
|
|
4
4
|
from typing import Any, Dict, List, Optional, Union, cast
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
7
|
+
from ..additionalaudience import AdditionalAudience
|
8
|
+
from ..admindata import AdminData
|
9
|
+
from ..companydata import CompanyData
|
10
|
+
from ..diagcomm import DiagComm
|
11
|
+
from ..diagdatadictionaryspec import DiagDataDictionarySpec
|
12
|
+
from ..diagservice import DiagService
|
13
|
+
from ..element import IdentifiableElement
|
14
|
+
from ..exceptions import odxassert, odxraise, odxrequire
|
15
|
+
from ..functionalclass import FunctionalClass
|
16
|
+
from ..nameditemlist import NamedItemList
|
17
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
18
|
+
from ..request import Request
|
19
|
+
from ..response import Response
|
20
|
+
from ..singleecujob import SingleEcuJob
|
21
|
+
from ..snrefcontext import SnRefContext
|
22
|
+
from ..specialdatagroup import SpecialDataGroup
|
23
|
+
from ..statechart import StateChart
|
24
|
+
from ..utils import dataclass_fields_asdict
|
15
25
|
from .diaglayertype import DiagLayerType
|
16
|
-
from .diagservice import DiagService
|
17
|
-
from .diagvariable import DiagVariable
|
18
|
-
from .dyndefinedspec import DynDefinedSpec
|
19
|
-
from .ecuvariantpattern import EcuVariantPattern
|
20
|
-
from .element import IdentifiableElement
|
21
|
-
from .exceptions import odxassert, odxraise, odxrequire
|
22
|
-
from .functionalclass import FunctionalClass
|
23
|
-
from .nameditemlist import NamedItemList
|
24
|
-
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
25
|
-
from .parentref import ParentRef
|
26
|
-
from .protstack import ProtStack
|
27
|
-
from .request import Request
|
28
|
-
from .response import Response
|
29
|
-
from .singleecujob import SingleEcuJob
|
30
|
-
from .snrefcontext import SnRefContext
|
31
|
-
from .specialdatagroup import SpecialDataGroup
|
32
|
-
from .statechart import StateChart
|
33
|
-
from .utils import dataclass_fields_asdict
|
34
|
-
from .variablegroup import VariableGroup
|
35
26
|
|
36
27
|
|
37
28
|
@dataclass
|
@@ -59,18 +50,6 @@ class DiagLayerRaw(IdentifiableElement):
|
|
59
50
|
# libraries: List[DiagLayer] # TODO
|
60
51
|
sdgs: List[SpecialDataGroup]
|
61
52
|
|
62
|
-
# these attributes are only defined for some kinds of diag layers!
|
63
|
-
# TODO: make a proper class hierarchy!
|
64
|
-
parent_refs: List[ParentRef]
|
65
|
-
comparam_refs: List[ComparamInstance]
|
66
|
-
ecu_variant_patterns: List[EcuVariantPattern]
|
67
|
-
comparam_spec_ref: Optional[OdxLinkRef]
|
68
|
-
prot_stack_snref: Optional[str]
|
69
|
-
diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]]
|
70
|
-
variable_groups: NamedItemList[VariableGroup]
|
71
|
-
dyn_defined_spec: Optional[DynDefinedSpec]
|
72
|
-
# base_variant_patterns: List[EcuVariantPattern] # TODO
|
73
|
-
|
74
53
|
@property
|
75
54
|
def diag_comms(self) -> NamedItemList[DiagComm]:
|
76
55
|
return self._diag_comms
|
@@ -88,10 +67,6 @@ class DiagLayerRaw(IdentifiableElement):
|
|
88
67
|
def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
|
89
68
|
return self._single_ecu_jobs
|
90
69
|
|
91
|
-
@property
|
92
|
-
def diag_variables(self) -> NamedItemList[DiagVariable]:
|
93
|
-
return self._diag_variables
|
94
|
-
|
95
70
|
@staticmethod
|
96
71
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayerRaw":
|
97
72
|
try:
|
@@ -178,53 +153,6 @@ class DiagLayerRaw(IdentifiableElement):
|
|
178
153
|
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
179
154
|
]
|
180
155
|
|
181
|
-
parent_refs = [
|
182
|
-
ParentRef.from_et(pr_el, doc_frags)
|
183
|
-
for pr_el in et_element.iterfind("PARENT-REFS/PARENT-REF")
|
184
|
-
]
|
185
|
-
|
186
|
-
comparam_refs = [
|
187
|
-
ComparamInstance.from_et(el, doc_frags)
|
188
|
-
for el in et_element.iterfind("COMPARAM-REFS/COMPARAM-REF")
|
189
|
-
]
|
190
|
-
|
191
|
-
ecu_variant_patterns = [
|
192
|
-
EcuVariantPattern.from_et(el, doc_frags)
|
193
|
-
for el in et_element.iterfind("ECU-VARIANT-PATTERNS/ECU-VARIANT-PATTERN")
|
194
|
-
]
|
195
|
-
if variant_type is not DiagLayerType.ECU_VARIANT:
|
196
|
-
odxassert(
|
197
|
-
len(ecu_variant_patterns) == 0,
|
198
|
-
"DiagLayer of type other than 'ECU-VARIANT' must not define a ECU-VARIANT-PATTERN")
|
199
|
-
|
200
|
-
comparam_spec_ref = OdxLinkRef.from_et(et_element.find("COMPARAM-SPEC-REF"), doc_frags)
|
201
|
-
prot_stack_snref: Optional[str] = None
|
202
|
-
if (prot_stack_snref_elem := et_element.find("PROT-STACK-SNREF")) is not None:
|
203
|
-
prot_stack_snref = odxrequire(prot_stack_snref_elem.get("SHORT-NAME"))
|
204
|
-
|
205
|
-
diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
|
206
|
-
if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
|
207
|
-
for dv_proxy_elem in dv_elems:
|
208
|
-
dv_proxy: Union[OdxLinkRef, DiagVariable]
|
209
|
-
if dv_proxy_elem.tag == "DIAG-VARIABLE-REF":
|
210
|
-
dv_proxy = OdxLinkRef.from_et(dv_proxy_elem, doc_frags)
|
211
|
-
elif dv_proxy_elem.tag == "DIAG-VARIABLE":
|
212
|
-
dv_proxy = DiagVariable.from_et(dv_proxy_elem, doc_frags)
|
213
|
-
else:
|
214
|
-
odxraise("DIAG-VARIABLES tags may only contain "
|
215
|
-
"DIAG-VARIABLE and DIAG-VARIABLE-REF subtags")
|
216
|
-
|
217
|
-
diag_variables_raw.append(dv_proxy)
|
218
|
-
|
219
|
-
variable_groups = NamedItemList([
|
220
|
-
VariableGroup.from_et(vg_elem, doc_frags)
|
221
|
-
for vg_elem in et_element.iterfind("VARIABLE-GROUPS/VARIABLE-GROUP")
|
222
|
-
])
|
223
|
-
|
224
|
-
dyn_defined_spec = None
|
225
|
-
if (dds_elem := et_element.find("DYN-DEFINED-SPEC")) is not None:
|
226
|
-
dyn_defined_spec = DynDefinedSpec.from_et(dds_elem, doc_frags)
|
227
|
-
|
228
156
|
# Create DiagLayer
|
229
157
|
return DiagLayerRaw(
|
230
158
|
variant_type=variant_type,
|
@@ -241,14 +169,6 @@ class DiagLayerRaw(IdentifiableElement):
|
|
241
169
|
state_charts=NamedItemList(state_charts),
|
242
170
|
additional_audiences=NamedItemList(additional_audiences),
|
243
171
|
sdgs=sdgs,
|
244
|
-
parent_refs=parent_refs,
|
245
|
-
comparam_refs=comparam_refs,
|
246
|
-
ecu_variant_patterns=ecu_variant_patterns,
|
247
|
-
comparam_spec_ref=comparam_spec_ref,
|
248
|
-
prot_stack_snref=prot_stack_snref,
|
249
|
-
diag_variables_raw=diag_variables_raw,
|
250
|
-
variable_groups=variable_groups,
|
251
|
-
dyn_defined_spec=dyn_defined_spec,
|
252
172
|
**kwargs)
|
253
173
|
|
254
174
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
@@ -282,27 +202,12 @@ class DiagLayerRaw(IdentifiableElement):
|
|
282
202
|
odxlinks.update(additional_audience._build_odxlinks())
|
283
203
|
for sdg in self.sdgs:
|
284
204
|
odxlinks.update(sdg._build_odxlinks())
|
285
|
-
for parent_ref in self.parent_refs:
|
286
|
-
odxlinks.update(parent_ref._build_odxlinks())
|
287
|
-
for comparam in self.comparam_refs:
|
288
|
-
odxlinks.update(comparam._build_odxlinks())
|
289
|
-
for dv_proxy in self.diag_variables_raw:
|
290
|
-
if not isinstance(dv_proxy, OdxLinkRef):
|
291
|
-
odxlinks.update(dv_proxy._build_odxlinks())
|
292
|
-
if self.dyn_defined_spec is not None:
|
293
|
-
odxlinks.update(self.dyn_defined_spec._build_odxlinks())
|
294
205
|
|
295
206
|
return odxlinks
|
296
207
|
|
297
208
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
298
209
|
"""Recursively resolve all references."""
|
299
210
|
|
300
|
-
if self.comparam_spec_ref is not None:
|
301
|
-
spec = odxlinks.resolve(self.comparam_spec_ref)
|
302
|
-
if not isinstance(spec, (ComparamSubset, ComparamSpec)):
|
303
|
-
odxraise(f"Type {type(spec).__name__} is not allowed for comparam specs")
|
304
|
-
self._comparam_spec = spec
|
305
|
-
|
306
211
|
# do ODXLINK reference resolution
|
307
212
|
if self.admin_data is not None:
|
308
213
|
self.admin_data._resolve_odxlinks(odxlinks)
|
@@ -349,32 +254,8 @@ class DiagLayerRaw(IdentifiableElement):
|
|
349
254
|
additional_audience._resolve_odxlinks(odxlinks)
|
350
255
|
for sdg in self.sdgs:
|
351
256
|
sdg._resolve_odxlinks(odxlinks)
|
352
|
-
for parent_ref in self.parent_refs:
|
353
|
-
parent_ref._resolve_odxlinks(odxlinks)
|
354
|
-
for comparam in self.comparam_refs:
|
355
|
-
comparam._resolve_odxlinks(odxlinks)
|
356
|
-
|
357
|
-
self._diag_variables: NamedItemList[DiagVariable] = NamedItemList()
|
358
|
-
for dv_proxy in self.diag_variables_raw:
|
359
|
-
if isinstance(dv_proxy, OdxLinkRef):
|
360
|
-
dv = odxlinks.resolve(dv_proxy, DiagVariable)
|
361
|
-
else:
|
362
|
-
dv_proxy._resolve_odxlinks(odxlinks)
|
363
|
-
dv = dv_proxy
|
364
|
-
|
365
|
-
self._diag_variables.append(dv)
|
366
|
-
|
367
|
-
if self.dyn_defined_spec is not None:
|
368
|
-
self.dyn_defined_spec._resolve_odxlinks(odxlinks)
|
369
257
|
|
370
258
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
371
|
-
self._prot_stack: Optional[ProtStack] = None
|
372
|
-
if self.prot_stack_snref is not None:
|
373
|
-
cp_spec = self.comparam_spec
|
374
|
-
if isinstance(cp_spec, ComparamSpec):
|
375
|
-
self._prot_stack = resolve_snref(self.prot_stack_snref, cp_spec.prot_stacks,
|
376
|
-
ProtStack)
|
377
|
-
|
378
259
|
# do short-name reference resolution
|
379
260
|
if self.admin_data is not None:
|
380
261
|
self.admin_data._resolve_snrefs(context)
|
@@ -403,22 +284,3 @@ class DiagLayerRaw(IdentifiableElement):
|
|
403
284
|
additional_audience._resolve_snrefs(context)
|
404
285
|
for sdg in self.sdgs:
|
405
286
|
sdg._resolve_snrefs(context)
|
406
|
-
for parent_ref in self.parent_refs:
|
407
|
-
parent_ref._resolve_snrefs(context)
|
408
|
-
for comparam in self.comparam_refs:
|
409
|
-
comparam._resolve_snrefs(context)
|
410
|
-
|
411
|
-
for dv_proxy in self.diag_variables_raw:
|
412
|
-
if not isinstance(dv_proxy, OdxLinkRef):
|
413
|
-
dv_proxy._resolve_snrefs(context)
|
414
|
-
|
415
|
-
if self.dyn_defined_spec is not None:
|
416
|
-
self.dyn_defined_spec._resolve_snrefs(context)
|
417
|
-
|
418
|
-
@property
|
419
|
-
def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
|
420
|
-
return self._comparam_spec
|
421
|
-
|
422
|
-
@property
|
423
|
-
def prot_stack(self) -> Optional[ProtStack]:
|
424
|
-
return self._prot_stack
|