odxtools 7.4.1__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/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/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.1.dist-info → odxtools-8.0.0.dist-info}/METADATA +1 -1
- {odxtools-7.4.1.dist-info → odxtools-8.0.0.dist-info}/RECORD +47 -29
- odxtools/templates/macros/printVariant.xml.jinja2 +0 -241
- /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
- {odxtools-7.4.1.dist-info → odxtools-8.0.0.dist-info}/LICENSE +0 -0
- {odxtools-7.4.1.dist-info → odxtools-8.0.0.dist-info}/WHEEL +0 -0
- {odxtools-7.4.1.dist-info → odxtools-8.0.0.dist-info}/entry_points.txt +0 -0
- {odxtools-7.4.1.dist-info → odxtools-8.0.0.dist-info}/top_level.txt +0 -0
@@ -1,149 +1,96 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import re
|
3
3
|
import warnings
|
4
|
-
from copy import
|
4
|
+
from copy import deepcopy
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from functools import cached_property
|
7
|
-
from itertools import chain
|
8
7
|
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar,
|
9
8
|
Union, cast)
|
10
9
|
from xml.etree import ElementTree
|
11
10
|
|
12
11
|
from deprecation import deprecated
|
13
12
|
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
25
|
-
from
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from
|
29
|
-
from
|
30
|
-
from
|
31
|
-
from
|
32
|
-
from .
|
33
|
-
from .
|
34
|
-
from .request import Request
|
35
|
-
from .response import Response
|
36
|
-
from .servicebinner import ServiceBinner
|
37
|
-
from .singleecujob import SingleEcuJob
|
38
|
-
from .snrefcontext import SnRefContext
|
39
|
-
from .specialdatagroup import SpecialDataGroup
|
40
|
-
from .statechart import StateChart
|
41
|
-
from .table import Table
|
42
|
-
from .unitgroup import UnitGroup
|
43
|
-
from .unitspec import UnitSpec
|
13
|
+
from ..additionalaudience import AdditionalAudience
|
14
|
+
from ..admindata import AdminData
|
15
|
+
from ..comparaminstance import ComparamInstance
|
16
|
+
from ..diagcomm import DiagComm
|
17
|
+
from ..diagdatadictionaryspec import DiagDataDictionarySpec
|
18
|
+
from ..diagservice import DiagService
|
19
|
+
from ..exceptions import OdxWarning, odxassert, odxraise
|
20
|
+
from ..functionalclass import FunctionalClass
|
21
|
+
from ..nameditemlist import NamedItemList, OdxNamed
|
22
|
+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
23
|
+
from ..parentref import ParentRef
|
24
|
+
from ..response import Response
|
25
|
+
from ..singleecujob import SingleEcuJob
|
26
|
+
from ..snrefcontext import SnRefContext
|
27
|
+
from ..specialdatagroup import SpecialDataGroup
|
28
|
+
from ..statechart import StateChart
|
29
|
+
from ..unitgroup import UnitGroup
|
30
|
+
from ..unitspec import UnitSpec
|
31
|
+
from .diaglayer import DiagLayer
|
32
|
+
from .hierarchyelementraw import HierarchyElementRaw
|
44
33
|
|
45
34
|
if TYPE_CHECKING:
|
46
35
|
from .database import Database
|
36
|
+
from .protocol import Protocol
|
47
37
|
|
48
38
|
TNamed = TypeVar("TNamed", bound=OdxNamed)
|
49
39
|
|
50
|
-
PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
|
51
|
-
|
52
40
|
|
53
41
|
@dataclass
|
54
|
-
class DiagLayer:
|
55
|
-
"""This class
|
56
|
-
according to the ODX standard.
|
57
|
-
|
58
|
-
i.e. it handles the value inheritance, communication parameters,
|
59
|
-
encoding/decoding of data, etc.
|
42
|
+
class HierarchyElement(DiagLayer):
|
43
|
+
"""This is the base class for diagnostic layers that may be involved in value inheritance
|
60
44
|
"""
|
61
45
|
|
62
|
-
|
46
|
+
@property
|
47
|
+
def hierarchy_element_raw(self) -> HierarchyElementRaw:
|
48
|
+
return cast(HierarchyElementRaw, self.diag_layer_raw)
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def from_et(et_element: ElementTree.Element,
|
52
|
+
doc_frags: List[OdxDocFragment]) -> "HierarchyElement":
|
53
|
+
hierarchy_element_raw = HierarchyElementRaw.from_et(et_element, doc_frags)
|
54
|
+
|
55
|
+
return HierarchyElement(diag_layer_raw=hierarchy_element_raw)
|
63
56
|
|
64
57
|
def __post_init__(self) -> None:
|
65
|
-
|
58
|
+
super().__post_init__()
|
66
59
|
|
67
|
-
|
68
|
-
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
|
69
|
-
diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
|
60
|
+
self._global_negative_responses: NamedItemList[Response]
|
70
61
|
|
71
|
-
|
72
|
-
|
62
|
+
odxassert(
|
63
|
+
isinstance(self.diag_layer_raw, HierarchyElementRaw),
|
64
|
+
"The raw diagnostic layer passed to HierarchyElement "
|
65
|
+
"must be a HierarchyElementRaw")
|
73
66
|
|
74
67
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
75
|
-
|
76
|
-
result = self.diag_layer_raw._build_odxlinks()
|
77
|
-
|
78
|
-
# we want to get the full diag layer, not just the raw layer
|
79
|
-
# when referencing...
|
80
|
-
result[self.odx_id] = self
|
68
|
+
result = super()._build_odxlinks()
|
81
69
|
|
82
70
|
return result
|
83
71
|
|
84
72
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# explicitly specify the DOCREF attribute in the
|
90
|
-
# reference. This mechanism can thus be seen as a kind of
|
91
|
-
# "poor man's inheritance".
|
92
|
-
if self.import_refs:
|
93
|
-
imported_links: Dict[OdxLinkId, Any] = {}
|
94
|
-
for import_ref in self.import_refs:
|
95
|
-
imported_dl = odxlinks.resolve(import_ref, DiagLayer)
|
96
|
-
|
97
|
-
odxassert(
|
98
|
-
imported_dl.variant_type == DiagLayerType.ECU_SHARED_DATA,
|
99
|
-
f"Tried to import references from diagnostic layer "
|
100
|
-
f"'{imported_dl.short_name}' of type {imported_dl.variant_type.value}. "
|
101
|
-
f"Only ECU-SHARED-DATA layers may be referenced using the "
|
102
|
-
f"IMPORT-REF mechanism")
|
103
|
-
|
104
|
-
# TODO: ensure that the imported diagnostic layer has
|
105
|
-
# not been referenced in any PARENT-REF of the current
|
106
|
-
# layer or any of its parents.
|
107
|
-
|
108
|
-
# TODO: detect and complain about cyclic IMPORT-REFs
|
109
|
-
|
110
|
-
# TODO (?): detect conflicts with locally-defined
|
111
|
-
# objects
|
112
|
-
|
113
|
-
imported_dl_links = imported_dl._build_odxlinks()
|
114
|
-
for link_id, obj in imported_dl_links.items():
|
115
|
-
# the imported objects shall behave as if they
|
116
|
-
# were defined by the importing layer. IOW, they
|
117
|
-
# must be visible in the same document fragments.
|
118
|
-
link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
|
119
|
-
imported_links[link_id] = obj
|
120
|
-
|
121
|
-
# We need to copy the odxlink database here since this
|
122
|
-
# function must not modify its argument because the
|
123
|
-
# imported references only apply within this specific
|
124
|
-
# diagnostic layer
|
125
|
-
extended_odxlinks = copy(odxlinks)
|
126
|
-
extended_odxlinks.update(imported_links)
|
127
|
-
|
128
|
-
self.diag_layer_raw._resolve_odxlinks(extended_odxlinks)
|
129
|
-
return
|
130
|
-
|
131
|
-
self.diag_layer_raw._resolve_odxlinks(odxlinks)
|
73
|
+
super()._resolve_odxlinks(odxlinks)
|
74
|
+
|
75
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
76
|
+
super()._resolve_snrefs(context)
|
132
77
|
|
133
78
|
def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
|
134
|
-
"""Create a deep copy of the
|
79
|
+
"""Create a deep copy of the hierarchy element
|
135
80
|
|
136
81
|
Note that the copied diagnostic layer is not fully
|
137
82
|
initialized, so `_finalize_init()` should to be called on it
|
138
83
|
before it can be used normally.
|
139
84
|
"""
|
140
|
-
cls = self.__class__
|
141
|
-
result = cls.__new__(cls)
|
142
|
-
memo[id(self)] = result
|
143
85
|
|
144
|
-
|
86
|
+
new_he = super().__deepcopy__(memo)
|
145
87
|
|
146
|
-
|
88
|
+
# note that the self.hierarchy_element_raw object is *not*
|
89
|
+
# copied at this place because the attribute points to the
|
90
|
+
# same object as self.diag_layer_raw.
|
91
|
+
new_he.hierarchy_element_raw = deepcopy(self.hierarchy_element_raw)
|
92
|
+
|
93
|
+
return new_he
|
147
94
|
|
148
95
|
def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
|
149
96
|
"""This method deals with everything inheritance related and
|
@@ -165,27 +112,7 @@ class DiagLayer:
|
|
165
112
|
# fill in all applicable objects that use value inheritance
|
166
113
|
#####
|
167
114
|
|
168
|
-
|
169
|
-
diag_comms = self._compute_available_diag_comms(odxlinks)
|
170
|
-
self._diag_comms = NamedItemList(diag_comms)
|
171
|
-
|
172
|
-
# filter the diag comms for services and single-ECU jobs
|
173
|
-
diag_services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
|
174
|
-
single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
|
175
|
-
self._diag_services = NamedItemList(diag_services)
|
176
|
-
self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
|
177
|
-
|
178
|
-
global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
|
179
|
-
self._global_negative_responses = NamedItemList(global_negative_responses)
|
180
|
-
|
181
|
-
functional_classes = self._compute_available_functional_classes()
|
182
|
-
self._functional_classes = NamedItemList(functional_classes)
|
183
|
-
|
184
|
-
additional_audiences = self._compute_available_additional_audiences()
|
185
|
-
self._additional_audiences = NamedItemList(additional_audiences)
|
186
|
-
|
187
|
-
state_charts = self._compute_available_state_charts()
|
188
|
-
self._state_charts = NamedItemList(state_charts)
|
115
|
+
self._compute_value_inheritance(odxlinks)
|
189
116
|
|
190
117
|
############
|
191
118
|
# create a new unit_spec object. This is necessary because
|
@@ -234,6 +161,10 @@ class DiagLayer:
|
|
234
161
|
lambda ddd_spec: ddd_spec.dtc_dops,
|
235
162
|
lambda parent_ref: parent_ref.not_inherited_dops,
|
236
163
|
)
|
164
|
+
static_fields = self._compute_available_ddd_spec_items(
|
165
|
+
lambda ddd_spec: ddd_spec.static_fields,
|
166
|
+
lambda parent_ref: parent_ref.not_inherited_dops,
|
167
|
+
)
|
237
168
|
end_of_pdu_fields = self._compute_available_ddd_spec_items(
|
238
169
|
lambda ddd_spec: ddd_spec.end_of_pdu_fields,
|
239
170
|
lambda parent_ref: parent_ref.not_inherited_dops,
|
@@ -256,21 +187,22 @@ class DiagLayer:
|
|
256
187
|
lambda ddd_spec: ddd_spec.muxs, lambda parent_ref: parent_ref.not_inherited_dops)
|
257
188
|
tables = self._compute_available_ddd_spec_items(
|
258
189
|
lambda ddd_spec: ddd_spec.tables, lambda parent_ref: parent_ref.not_inherited_tables)
|
259
|
-
|
190
|
+
|
191
|
+
ddds_admin_data: Optional[AdminData] = None
|
192
|
+
ddds_sdgs: List[SpecialDataGroup] = []
|
260
193
|
if self.diag_layer_raw.diag_data_dictionary_spec:
|
194
|
+
ddds_admin_data = self.diag_layer_raw.diag_data_dictionary_spec.admin_data
|
261
195
|
ddds_sdgs = self.diag_layer_raw.diag_data_dictionary_spec.sdgs
|
262
|
-
else:
|
263
|
-
ddds_sdgs = []
|
264
196
|
|
265
197
|
# create a DiagDataDictionarySpec which includes all the
|
266
198
|
# inherited objects. To me, this seems rather inelegant, but
|
267
199
|
# hey, it's described like this in the standard.
|
268
200
|
self._diag_data_dictionary_spec = DiagDataDictionarySpec(
|
269
|
-
admin_data=
|
201
|
+
admin_data=ddds_admin_data,
|
270
202
|
data_object_props=dops,
|
271
203
|
dtc_dops=dtc_dops,
|
272
204
|
structures=structures,
|
273
|
-
static_fields=
|
205
|
+
static_fields=static_fields,
|
274
206
|
end_of_pdu_fields=end_of_pdu_fields,
|
275
207
|
dynamic_endmarker_fields=dynamic_endmarker_fields,
|
276
208
|
dynamic_length_fields=dynamic_length_fields,
|
@@ -299,175 +231,38 @@ class DiagLayer:
|
|
299
231
|
#####
|
300
232
|
context = SnRefContext(database=database)
|
301
233
|
context.diag_layer = self
|
302
|
-
self.
|
234
|
+
self._resolve_snrefs(context)
|
303
235
|
context.diag_layer = None
|
304
236
|
|
305
237
|
#####
|
306
|
-
# <
|
307
|
-
#####
|
308
|
-
@cached_property
|
309
|
-
def service_groups(self) -> ServiceBinner:
|
310
|
-
return ServiceBinner(self.services)
|
311
|
-
|
312
|
-
#####
|
313
|
-
# </convenience functionality>
|
314
|
-
#####
|
315
|
-
|
316
|
-
#####
|
317
|
-
# <properties forwarded to the "raw" diag layer>
|
318
|
-
#####
|
319
|
-
@property
|
320
|
-
def variant_type(self) -> DiagLayerType:
|
321
|
-
return self.diag_layer_raw.variant_type
|
322
|
-
|
323
|
-
@property
|
324
|
-
def odx_id(self) -> OdxLinkId:
|
325
|
-
return self.diag_layer_raw.odx_id
|
326
|
-
|
327
|
-
@property
|
328
|
-
def short_name(self) -> str:
|
329
|
-
return self.diag_layer_raw.short_name
|
330
|
-
|
331
|
-
@property
|
332
|
-
def long_name(self) -> Optional[str]:
|
333
|
-
return self.diag_layer_raw.long_name
|
334
|
-
|
335
|
-
@property
|
336
|
-
def description(self) -> Optional[Description]:
|
337
|
-
return self.diag_layer_raw.description
|
338
|
-
|
339
|
-
@property
|
340
|
-
def admin_data(self) -> Optional[AdminData]:
|
341
|
-
return self.diag_layer_raw.admin_data
|
342
|
-
|
343
|
-
@property
|
344
|
-
def company_datas(self) -> NamedItemList[CompanyData]:
|
345
|
-
return self.diag_layer_raw.company_datas
|
346
|
-
|
347
|
-
@property
|
348
|
-
def requests(self) -> NamedItemList[Request]:
|
349
|
-
return self.diag_layer_raw.requests
|
350
|
-
|
351
|
-
@property
|
352
|
-
def positive_responses(self) -> NamedItemList[Response]:
|
353
|
-
return self.diag_layer_raw.positive_responses
|
354
|
-
|
355
|
-
@property
|
356
|
-
def negative_responses(self) -> NamedItemList[Response]:
|
357
|
-
return self.diag_layer_raw.negative_responses
|
358
|
-
|
359
|
-
@property
|
360
|
-
def import_refs(self) -> List[OdxLinkRef]:
|
361
|
-
return self.diag_layer_raw.import_refs
|
362
|
-
|
363
|
-
@property
|
364
|
-
def sdgs(self) -> List[SpecialDataGroup]:
|
365
|
-
return self.diag_layer_raw.sdgs
|
366
|
-
|
367
|
-
@property
|
368
|
-
def parent_refs(self) -> List[ParentRef]:
|
369
|
-
return self.diag_layer_raw.parent_refs
|
370
|
-
|
371
|
-
@property
|
372
|
-
def ecu_variant_patterns(self) -> List[EcuVariantPattern]:
|
373
|
-
return self.diag_layer_raw.ecu_variant_patterns
|
374
|
-
|
375
|
-
@property
|
376
|
-
def comparam_spec_ref(self) -> Optional[OdxLinkRef]:
|
377
|
-
return self.diag_layer_raw.comparam_spec_ref
|
378
|
-
|
379
|
-
@property
|
380
|
-
def prot_stack_snref(self) -> Optional[str]:
|
381
|
-
return self.diag_layer_raw.prot_stack_snref
|
382
|
-
|
383
|
-
@property
|
384
|
-
def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
|
385
|
-
return self.diag_layer_raw.comparam_spec
|
386
|
-
|
387
|
-
@property
|
388
|
-
def prot_stack(self) -> Optional[ProtStack]:
|
389
|
-
return self.diag_layer_raw.prot_stack
|
390
|
-
|
391
|
-
#####
|
392
|
-
# </properties forwarded to the "raw" diag layer>
|
238
|
+
# <value inheritance mechanism helpers>
|
393
239
|
#####
|
240
|
+
def _compute_value_inheritance(self, odxlinks: OdxLinkDatabase) -> None:
|
241
|
+
# diagnostic communication objects with the ODXLINKs resolved
|
242
|
+
diag_comms = self._compute_available_diag_comms(odxlinks)
|
243
|
+
self._diag_comms = NamedItemList[DiagComm](diag_comms)
|
394
244
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
"""All diagnostic communication primitives applicable to this DiagLayer
|
401
|
-
|
402
|
-
Diagnostic communication primitives are diagnostic services as
|
403
|
-
well as single-ECU jobs. This list has all references
|
404
|
-
resolved.
|
405
|
-
"""
|
406
|
-
return self._diag_comms
|
407
|
-
|
408
|
-
@property
|
409
|
-
def services(self) -> NamedItemList[DiagService]:
|
410
|
-
"""This property is an alias for `.diag_services`"""
|
411
|
-
return self._diag_services
|
412
|
-
|
413
|
-
@property
|
414
|
-
def diag_services(self) -> NamedItemList[DiagService]:
|
415
|
-
"""All diagnostic services applicable to this DiagLayer
|
416
|
-
|
417
|
-
This is a subset of all diagnostic communication
|
418
|
-
primitives. All references are resolved in the list returned.
|
419
|
-
"""
|
420
|
-
return self._diag_services
|
421
|
-
|
422
|
-
@property
|
423
|
-
def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
|
424
|
-
"""All single-ECU jobs applicable to this DiagLayer
|
425
|
-
|
426
|
-
This is a subset of all diagnostic communication
|
427
|
-
primitives. All references are resolved in the list returned.
|
428
|
-
"""
|
429
|
-
return self._single_ecu_jobs
|
430
|
-
|
431
|
-
@property
|
432
|
-
def global_negative_responses(self) -> NamedItemList[Response]:
|
433
|
-
"""All global negative responses applicable to this DiagLayer"""
|
434
|
-
return self._global_negative_responses
|
435
|
-
|
436
|
-
@property
|
437
|
-
@deprecated(details="use diag_data_dictionary_spec.tables") # type: ignore[misc]
|
438
|
-
def tables(self) -> NamedItemList[Table]:
|
439
|
-
return self.diag_data_dictionary_spec.tables
|
440
|
-
|
441
|
-
@property
|
442
|
-
def functional_classes(self) -> NamedItemList[FunctionalClass]:
|
443
|
-
"""All functional classes applicable to this DiagLayer"""
|
444
|
-
return self._functional_classes
|
245
|
+
# filter the diag comms for services and single-ECU jobs
|
246
|
+
diag_services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
|
247
|
+
single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
|
248
|
+
self._diag_services = NamedItemList(diag_services)
|
249
|
+
self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
|
445
250
|
|
446
|
-
|
447
|
-
|
448
|
-
"""All state charts applicable to this DiagLayer"""
|
449
|
-
return self._state_charts
|
251
|
+
global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
|
252
|
+
self._global_negative_responses = NamedItemList(global_negative_responses)
|
450
253
|
|
451
|
-
|
452
|
-
|
453
|
-
"""All audiences applicable to this DiagLayer"""
|
454
|
-
return self._additional_audiences
|
254
|
+
functional_classes = self._compute_available_functional_classes()
|
255
|
+
self._functional_classes = NamedItemList(functional_classes)
|
455
256
|
|
456
|
-
|
457
|
-
|
458
|
-
"""The DiagDataDictionarySpec applicable to this DiagLayer"""
|
459
|
-
return self._diag_data_dictionary_spec
|
257
|
+
additional_audiences = self._compute_available_additional_audiences()
|
258
|
+
self._additional_audiences = NamedItemList(additional_audiences)
|
460
259
|
|
461
|
-
|
462
|
-
|
463
|
-
#######
|
260
|
+
state_charts = self._compute_available_state_charts()
|
261
|
+
self._state_charts = NamedItemList(state_charts)
|
464
262
|
|
465
|
-
#####
|
466
|
-
# <value inheritance mechanism helpers>
|
467
|
-
#####
|
468
263
|
def _get_parent_refs_sorted_by_priority(self, reverse: bool = False) -> Iterable[ParentRef]:
|
469
264
|
return sorted(
|
470
|
-
self.diag_layer_raw
|
265
|
+
getattr(self.diag_layer_raw, "parent_refs", []),
|
471
266
|
key=lambda pr: pr.layer.variant_type.inheritance_priority,
|
472
267
|
reverse=reverse)
|
473
268
|
|
@@ -561,41 +356,6 @@ class DiagLayer:
|
|
561
356
|
|
562
357
|
return [x[0] for x in result_dict.values()]
|
563
358
|
|
564
|
-
def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
|
565
|
-
"""Return the list of locally defined diagnostic communications.
|
566
|
-
|
567
|
-
This is not completely trivial as it requires to resolving the
|
568
|
-
references specified in the <DIAG-COMMS> XML tag.
|
569
|
-
"""
|
570
|
-
result_dict: Dict[str, DiagComm] = {}
|
571
|
-
|
572
|
-
# TODO (?): add objects from the import-refs
|
573
|
-
|
574
|
-
for dc_proxy in self.diag_layer_raw.diag_comms:
|
575
|
-
if isinstance(dc_proxy, OdxLinkRef):
|
576
|
-
dc = odxlinks.resolve(dc_proxy)
|
577
|
-
else:
|
578
|
-
dc = dc_proxy
|
579
|
-
|
580
|
-
odxassert(isinstance(dc, DiagComm))
|
581
|
-
odxassert(
|
582
|
-
dc.short_name not in result_dict,
|
583
|
-
f"Multiple definitions of DIAG-COMM '{dc.short_name}' in "
|
584
|
-
f"layer '{self.short_name}'")
|
585
|
-
result_dict[dc.short_name] = dc
|
586
|
-
|
587
|
-
return result_dict.values()
|
588
|
-
|
589
|
-
def _get_local_unit_groups(self) -> Iterable[UnitGroup]:
|
590
|
-
if self.diag_layer_raw.diag_data_dictionary_spec is None:
|
591
|
-
return []
|
592
|
-
|
593
|
-
unit_spec = self.diag_layer_raw.diag_data_dictionary_spec.unit_spec
|
594
|
-
if unit_spec is None:
|
595
|
-
return []
|
596
|
-
|
597
|
-
return unit_spec.unit_groups
|
598
|
-
|
599
359
|
def _compute_available_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
|
600
360
|
|
601
361
|
def get_local_objects_fn(dl: DiagLayer) -> Iterable[DiagComm]:
|
@@ -675,6 +435,70 @@ class DiagLayer:
|
|
675
435
|
# </value inheritance mechanism helpers>
|
676
436
|
#####
|
677
437
|
|
438
|
+
#######
|
439
|
+
# <properties subject to value inheritance>
|
440
|
+
#######
|
441
|
+
@property
|
442
|
+
def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
|
443
|
+
return self._diag_data_dictionary_spec
|
444
|
+
|
445
|
+
@property
|
446
|
+
def diag_comms(self) -> NamedItemList[DiagComm]:
|
447
|
+
"""All diagnostic communication primitives applicable to this DiagLayer
|
448
|
+
|
449
|
+
Diagnostic communication primitives are diagnostic services as
|
450
|
+
well as single-ECU jobs. This list has all references
|
451
|
+
resolved.
|
452
|
+
"""
|
453
|
+
return self._diag_comms
|
454
|
+
|
455
|
+
@property
|
456
|
+
def services(self) -> NamedItemList[DiagService]:
|
457
|
+
"""This property is an alias for `.diag_services`"""
|
458
|
+
return self._diag_services
|
459
|
+
|
460
|
+
@property
|
461
|
+
def diag_services(self) -> NamedItemList[DiagService]:
|
462
|
+
"""All diagnostic services applicable to this DiagLayer
|
463
|
+
|
464
|
+
This is a subset of all diagnostic communication
|
465
|
+
primitives. All references are resolved in the list returned.
|
466
|
+
"""
|
467
|
+
return self._diag_services
|
468
|
+
|
469
|
+
@property
|
470
|
+
def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
|
471
|
+
"""All single-ECU jobs applicable to this DiagLayer
|
472
|
+
|
473
|
+
This is a subset of all diagnostic communication
|
474
|
+
primitives. All references are resolved in the list returned.
|
475
|
+
"""
|
476
|
+
return self._single_ecu_jobs
|
477
|
+
|
478
|
+
@property
|
479
|
+
def global_negative_responses(self) -> NamedItemList[Response]:
|
480
|
+
"""All global negative responses applicable to this DiagLayer"""
|
481
|
+
return self._global_negative_responses
|
482
|
+
|
483
|
+
@property
|
484
|
+
def functional_classes(self) -> NamedItemList[FunctionalClass]:
|
485
|
+
"""All functional classes applicable to this DiagLayer"""
|
486
|
+
return self._functional_classes
|
487
|
+
|
488
|
+
@property
|
489
|
+
def state_charts(self) -> NamedItemList[StateChart]:
|
490
|
+
"""All state charts applicable to this DiagLayer"""
|
491
|
+
return self._state_charts
|
492
|
+
|
493
|
+
@property
|
494
|
+
def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
|
495
|
+
"""All audiences applicable to this DiagLayer"""
|
496
|
+
return self._additional_audiences
|
497
|
+
|
498
|
+
#######
|
499
|
+
# </properties subject to value inheritance>
|
500
|
+
#######
|
501
|
+
|
678
502
|
#####
|
679
503
|
# <communication parameter handling>
|
680
504
|
#####
|
@@ -708,11 +532,14 @@ class DiagLayer:
|
|
708
532
|
# parameters. First fetch the communication parameters from
|
709
533
|
# low priority parents, then update with increasing priority.
|
710
534
|
for parent_ref in self._get_parent_refs_sorted_by_priority():
|
711
|
-
|
535
|
+
parent_layer = parent_ref.layer
|
536
|
+
if not isinstance(parent_layer, HierarchyElement):
|
537
|
+
continue
|
538
|
+
for cp in parent_layer._compute_available_commmunication_parameters():
|
712
539
|
com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
|
713
540
|
|
714
541
|
# finally, handle the locally defined communication parameters
|
715
|
-
for cp in self.
|
542
|
+
for cp in getattr(self.hierarchy_element_raw, "comparam_refs", []):
|
716
543
|
com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
|
717
544
|
|
718
545
|
return list(com_params_dict.values())
|
@@ -728,19 +555,22 @@ class DiagLayer:
|
|
728
555
|
return self._comparam_refs
|
729
556
|
|
730
557
|
@cached_property
|
731
|
-
def protocols(self) -> NamedItemList["
|
558
|
+
def protocols(self) -> NamedItemList["Protocol"]:
|
732
559
|
"""Return the set of all protocols which are applicable to the diagnostic layer
|
733
560
|
|
734
|
-
Note that protocols are *not* explicitly
|
735
|
-
but the parent
|
561
|
+
Note that protocols are *not* explicitly defined by the XML,
|
562
|
+
but they are the parent layers of variant type "PROTOCOL".
|
563
|
+
|
736
564
|
"""
|
737
|
-
|
565
|
+
from .protocol import Protocol
|
738
566
|
|
739
|
-
|
740
|
-
|
567
|
+
result_dict: Dict[str, Protocol] = {}
|
568
|
+
|
569
|
+
for parent_ref in getattr(self, "parent_refs", []):
|
570
|
+
for prot in getattr(parent_ref.layer, "protocols", []):
|
741
571
|
result_dict[prot.short_name] = prot
|
742
572
|
|
743
|
-
if self
|
573
|
+
if isinstance(self, Protocol):
|
744
574
|
result_dict[self.diag_layer_raw.short_name] = self
|
745
575
|
|
746
576
|
return NamedItemList(result_dict.values())
|
@@ -749,14 +579,16 @@ class DiagLayer:
|
|
749
579
|
self,
|
750
580
|
cp_short_name: str,
|
751
581
|
*,
|
752
|
-
protocol: Optional[Union[str, "
|
582
|
+
protocol: Optional[Union[str, "Protocol"]] = None,
|
753
583
|
) -> Optional[ComparamInstance]:
|
754
584
|
"""Find a specific communication parameter according to some criteria.
|
755
585
|
|
756
586
|
Setting a given parameter to `None` means "don't care"."""
|
757
587
|
|
588
|
+
from .protocol import Protocol
|
589
|
+
|
758
590
|
protocol_name: Optional[str]
|
759
|
-
if isinstance(protocol,
|
591
|
+
if isinstance(protocol, Protocol):
|
760
592
|
protocol_name = protocol.short_name
|
761
593
|
else:
|
762
594
|
protocol_name = protocol
|
@@ -780,7 +612,7 @@ class DiagLayer:
|
|
780
612
|
|
781
613
|
def get_max_can_payload_size(self,
|
782
614
|
protocol: Optional[Union[str,
|
783
|
-
"
|
615
|
+
"Protocol"]] = None) -> Optional[int]:
|
784
616
|
"""Return the maximum size of a CAN frame payload that can be
|
785
617
|
transmitted in bytes.
|
786
618
|
|
@@ -810,13 +642,13 @@ class DiagLayer:
|
|
810
642
|
# unexpected format of parameter value
|
811
643
|
return 8
|
812
644
|
|
813
|
-
def uses_can(self, protocol: Optional[Union[str, "
|
645
|
+
def uses_can(self, protocol: Optional[Union[str, "Protocol"]] = None) -> bool:
|
814
646
|
"""
|
815
647
|
Check if CAN ought to be used as the link layer protocol.
|
816
648
|
"""
|
817
649
|
return self.get_can_receive_id(protocol=protocol) is not None
|
818
650
|
|
819
|
-
def uses_can_fd(self, protocol: Optional[Union[str, "
|
651
|
+
def uses_can_fd(self, protocol: Optional[Union[str, "Protocol"]] = None) -> bool:
|
820
652
|
"""Check if CAN-FD ought to be used.
|
821
653
|
|
822
654
|
If the ECU is not using CAN-FD for the specified protocol, `False`
|
@@ -835,7 +667,7 @@ class DiagLayer:
|
|
835
667
|
|
836
668
|
return "CANFD" in com_param.value
|
837
669
|
|
838
|
-
def get_can_baudrate(self, protocol: Optional[Union[str, "
|
670
|
+
def get_can_baudrate(self, protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
|
839
671
|
"""Baudrate of the CAN bus which is used by the ECU [bits/s]
|
840
672
|
|
841
673
|
If the ECU is not using CAN for the specified protocol, None
|
@@ -853,7 +685,7 @@ class DiagLayer:
|
|
853
685
|
return int(val)
|
854
686
|
|
855
687
|
def get_can_fd_baudrate(self,
|
856
|
-
protocol: Optional[Union[str, "
|
688
|
+
protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
|
857
689
|
"""Data baudrate of the CAN bus which is used by the ECU [bits/s]
|
858
690
|
|
859
691
|
If the ECU is not using CAN-FD for the specified protocol,
|
@@ -873,7 +705,7 @@ class DiagLayer:
|
|
873
705
|
return int(val)
|
874
706
|
|
875
707
|
def get_can_receive_id(self,
|
876
|
-
protocol: Optional[Union[str, "
|
708
|
+
protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
|
877
709
|
"""CAN ID to which the ECU listens for diagnostic messages"""
|
878
710
|
com_param = self.get_comparam("CP_UniqueRespIdTable", protocol=protocol)
|
879
711
|
if com_param is None:
|
@@ -896,7 +728,7 @@ class DiagLayer:
|
|
896
728
|
def get_receive_id(self) -> Optional[int]:
|
897
729
|
return self.get_can_receive_id()
|
898
730
|
|
899
|
-
def get_can_send_id(self, protocol: Optional[Union[str, "
|
731
|
+
def get_can_send_id(self, protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
|
900
732
|
"""CAN ID to which the ECU sends replies to diagnostic messages"""
|
901
733
|
|
902
734
|
# this hopefully resolves to the 'CP_UniqueRespIdTable'
|
@@ -926,7 +758,7 @@ class DiagLayer:
|
|
926
758
|
return self.get_can_send_id()
|
927
759
|
|
928
760
|
def get_can_func_req_id(self,
|
929
|
-
protocol: Optional[Union[str, "
|
761
|
+
protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
|
930
762
|
"""CAN Functional Request Id."""
|
931
763
|
com_param = self.get_comparam("CP_CanFuncReqId", protocol=protocol)
|
932
764
|
if com_param is None:
|
@@ -940,8 +772,8 @@ class DiagLayer:
|
|
940
772
|
return int(result)
|
941
773
|
|
942
774
|
def get_doip_logical_ecu_address(self,
|
943
|
-
protocol: Optional[Union[str,
|
944
|
-
|
775
|
+
protocol: Optional[Union[str,
|
776
|
+
"Protocol"]] = None) -> Optional[int]:
|
945
777
|
"""Return the address of the ECU when using functional addressing.
|
946
778
|
|
947
779
|
The parameter protocol is used to distinguish between
|
@@ -972,7 +804,7 @@ class DiagLayer:
|
|
972
804
|
return int(ecu_addr)
|
973
805
|
|
974
806
|
def get_doip_logical_gateway_address(self,
|
975
|
-
protocol: Optional[Union[str, "
|
807
|
+
protocol: Optional[Union[str, "Protocol"]] = None
|
976
808
|
) -> Optional[int]:
|
977
809
|
"""The logical gateway address for the diagnosis over IP transport protocol"""
|
978
810
|
|
@@ -990,7 +822,7 @@ class DiagLayer:
|
|
990
822
|
return int(result)
|
991
823
|
|
992
824
|
def get_doip_logical_tester_address(self,
|
993
|
-
protocol: Optional[Union[str, "
|
825
|
+
protocol: Optional[Union[str, "Protocol"]] = None
|
994
826
|
) -> Optional[int]:
|
995
827
|
"""DoIp logical gateway address"""
|
996
828
|
|
@@ -1008,7 +840,7 @@ class DiagLayer:
|
|
1008
840
|
return int(result)
|
1009
841
|
|
1010
842
|
def get_doip_logical_functional_address(self,
|
1011
|
-
protocol: Optional[Union[str, "
|
843
|
+
protocol: Optional[Union[str, "Protocol"]] = None
|
1012
844
|
) -> Optional[int]:
|
1013
845
|
"""The logical functional DoIP address of the ECU."""
|
1014
846
|
|
@@ -1029,7 +861,7 @@ class DiagLayer:
|
|
1029
861
|
return int(result)
|
1030
862
|
|
1031
863
|
def get_doip_routing_activation_timeout(self,
|
1032
|
-
protocol: Optional[Union[str, "
|
864
|
+
protocol: Optional[Union[str, "Protocol"]] = None
|
1033
865
|
) -> Optional[float]:
|
1034
866
|
"""The timout for the DoIP routing activation request in seconds"""
|
1035
867
|
|
@@ -1047,7 +879,7 @@ class DiagLayer:
|
|
1047
879
|
return float(result) / 1e6
|
1048
880
|
|
1049
881
|
def get_doip_routing_activation_type(self,
|
1050
|
-
protocol: Optional[Union[str, "
|
882
|
+
protocol: Optional[Union[str, "Protocol"]] = None
|
1051
883
|
) -> Optional[int]:
|
1052
884
|
"""The DoIP routing activation type
|
1053
885
|
|
@@ -1074,7 +906,7 @@ class DiagLayer:
|
|
1074
906
|
|
1075
907
|
def get_tester_present_time(self,
|
1076
908
|
protocol: Optional[Union[str,
|
1077
|
-
"
|
909
|
+
"Protocol"]] = None) -> Optional[float]:
|
1078
910
|
"""Timeout on inactivity in seconds.
|
1079
911
|
|
1080
912
|
This is defined by the communication parameter "CP_TesterPresentTime".
|
@@ -1101,150 +933,3 @@ class DiagLayer:
|
|
1101
933
|
#####
|
1102
934
|
# </communication parameter handling>
|
1103
935
|
#####
|
1104
|
-
|
1105
|
-
#####
|
1106
|
-
# <PDU decoding>
|
1107
|
-
#####
|
1108
|
-
|
1109
|
-
@cached_property
|
1110
|
-
def _prefix_tree(self) -> PrefixTree:
|
1111
|
-
"""Constructs the coded prefix tree of the services.
|
1112
|
-
|
1113
|
-
Each leaf node is a list of `DiagService`s. (This is because
|
1114
|
-
navigating from a service to the request/ responses is easier
|
1115
|
-
than finding the service for a given request/response object.)
|
1116
|
-
|
1117
|
-
Example:
|
1118
|
-
Let there be four services with corresponding requests:
|
1119
|
-
* Request 1 has the coded constant prefix `12 34`.
|
1120
|
-
* Request 2 has the coded constant prefix `12 34`.
|
1121
|
-
* Request 3 has the coded constant prefix `12 56`.
|
1122
|
-
* Request 4 has the coded constant prefix `12 56 00`.
|
1123
|
-
|
1124
|
-
Then, the constructed prefix tree is the dict
|
1125
|
-
```
|
1126
|
-
{0x12: {0x34: {-1: [<Service 1>, <Service 2>]},
|
1127
|
-
0x56: {-1: [<Service 3>],
|
1128
|
-
0x0: {-1: [<Service 4>]}
|
1129
|
-
}}}
|
1130
|
-
```
|
1131
|
-
Note, that the inner `-1` are constant to distinguish them
|
1132
|
-
from possible service IDs.
|
1133
|
-
|
1134
|
-
Also note, that it is actually allowed that
|
1135
|
-
(a) SIDs for different services are the same like for service 1 and 2 (thus each leaf node is a list) and
|
1136
|
-
(b) one SID is the prefix of another SID like for service 3 and 4 (thus the constant `-1` key).
|
1137
|
-
|
1138
|
-
"""
|
1139
|
-
prefix_tree: PrefixTree = {}
|
1140
|
-
for s in self.services:
|
1141
|
-
# Compute prefixes for the service's request and all
|
1142
|
-
# possible responses. We need to consider the global
|
1143
|
-
# negative responses here, because they might contain
|
1144
|
-
# MATCHING-REQUEST parameters. If these global responses
|
1145
|
-
# do not contain such parameters, this will potentially
|
1146
|
-
# result in an enormous amount of decoded messages for
|
1147
|
-
# global negative responses. (I.e., one for each
|
1148
|
-
# service. This can be avoided by specifying the
|
1149
|
-
# corresponding request for `decode_response()`.)
|
1150
|
-
request_prefix = b''
|
1151
|
-
if s.request is not None:
|
1152
|
-
request_prefix = s.request.coded_const_prefix()
|
1153
|
-
prefixes = [request_prefix]
|
1154
|
-
prefixes += [
|
1155
|
-
x.coded_const_prefix(request_prefix=request_prefix) for x in chain(
|
1156
|
-
s.positive_responses, s.negative_responses, self.global_negative_responses)
|
1157
|
-
]
|
1158
|
-
for coded_prefix in prefixes:
|
1159
|
-
self._extend_prefix_tree(prefix_tree, coded_prefix, s)
|
1160
|
-
|
1161
|
-
return prefix_tree
|
1162
|
-
|
1163
|
-
@staticmethod
|
1164
|
-
def _extend_prefix_tree(prefix_tree: PrefixTree, coded_prefix: bytes,
|
1165
|
-
service: DiagService) -> None:
|
1166
|
-
|
1167
|
-
# make sure that tree has an entry for the given prefix
|
1168
|
-
sub_tree = prefix_tree
|
1169
|
-
for b in coded_prefix:
|
1170
|
-
if b not in sub_tree:
|
1171
|
-
sub_tree[b] = {}
|
1172
|
-
sub_tree = cast(PrefixTree, sub_tree[b])
|
1173
|
-
|
1174
|
-
# Store the object as in the prefix tree. This is done by
|
1175
|
-
# assigning the list of possible objects to the key -1 of the
|
1176
|
-
# dictionary (this is quite hacky...)
|
1177
|
-
if sub_tree.get(-1) is None:
|
1178
|
-
sub_tree[-1] = [service]
|
1179
|
-
else:
|
1180
|
-
cast(List[DiagService], sub_tree[-1]).append(service)
|
1181
|
-
|
1182
|
-
def _find_services_for_uds(self, message: bytes) -> List[DiagService]:
|
1183
|
-
prefix_tree = self._prefix_tree
|
1184
|
-
|
1185
|
-
# Find matching service(s) in prefix tree
|
1186
|
-
possible_services: List[DiagService] = []
|
1187
|
-
for b in message:
|
1188
|
-
if b in prefix_tree:
|
1189
|
-
odxassert(isinstance(prefix_tree[b], dict))
|
1190
|
-
prefix_tree = cast(PrefixTree, prefix_tree[b])
|
1191
|
-
else:
|
1192
|
-
break
|
1193
|
-
if -1 in prefix_tree:
|
1194
|
-
possible_services += cast(List[DiagService], prefix_tree[-1])
|
1195
|
-
return possible_services
|
1196
|
-
|
1197
|
-
def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> List[Message]:
|
1198
|
-
decoded_messages: List[Message] = []
|
1199
|
-
|
1200
|
-
for service in candidate_services:
|
1201
|
-
try:
|
1202
|
-
decoded_messages.append(service.decode_message(message))
|
1203
|
-
except DecodeError as e:
|
1204
|
-
# check if the message can be decoded as a global
|
1205
|
-
# negative response for the service
|
1206
|
-
gnr_found = False
|
1207
|
-
for gnr in self.global_negative_responses:
|
1208
|
-
try:
|
1209
|
-
decoded_gnr = gnr.decode(message)
|
1210
|
-
gnr_found = True
|
1211
|
-
if not isinstance(decoded_gnr, dict):
|
1212
|
-
odxraise(
|
1213
|
-
f"Expected the decoded value of a global "
|
1214
|
-
f"negative response to be a dictionary, "
|
1215
|
-
f"got {type(decoded_gnr)} for {self.short_name}", DecodeError)
|
1216
|
-
|
1217
|
-
decoded_messages.append(
|
1218
|
-
Message(
|
1219
|
-
coded_message=message,
|
1220
|
-
service=service,
|
1221
|
-
coding_object=gnr,
|
1222
|
-
param_dict=decoded_gnr))
|
1223
|
-
except DecodeError:
|
1224
|
-
pass
|
1225
|
-
|
1226
|
-
if not gnr_found:
|
1227
|
-
raise e
|
1228
|
-
|
1229
|
-
if len(decoded_messages) == 0:
|
1230
|
-
raise DecodeError(
|
1231
|
-
f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
|
1232
|
-
)
|
1233
|
-
|
1234
|
-
return decoded_messages
|
1235
|
-
|
1236
|
-
def decode(self, message: bytes) -> List[Message]:
|
1237
|
-
candidate_services = self._find_services_for_uds(message)
|
1238
|
-
|
1239
|
-
return self._decode(message, candidate_services)
|
1240
|
-
|
1241
|
-
def decode_response(self, response: bytes, request: bytes) -> List[Message]:
|
1242
|
-
candidate_services = self._find_services_for_uds(request)
|
1243
|
-
if candidate_services is None:
|
1244
|
-
raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
|
1245
|
-
|
1246
|
-
return self._decode(response, candidate_services)
|
1247
|
-
|
1248
|
-
#####
|
1249
|
-
# </PDU decoding>
|
1250
|
-
#####
|