odxtools 10.2.1__py3-none-any.whl → 10.4.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/browse.py +4 -2
- odxtools/cli/compare.py +3 -3
- odxtools/compositecodec.py +1 -1
- odxtools/configdata.py +70 -0
- odxtools/configdatadictionaryspec.py +57 -0
- odxtools/configiditem.py +18 -0
- odxtools/configitem.py +85 -0
- odxtools/configrecord.py +146 -0
- odxtools/database.py +40 -0
- odxtools/dataiditem.py +18 -0
- odxtools/datarecord.py +132 -0
- odxtools/decodestate.py +1 -1
- odxtools/diagcommdataconnector.py +61 -0
- odxtools/diaglayers/diaglayer.py +9 -14
- odxtools/diagservice.py +10 -10
- odxtools/ecuconfig.py +89 -0
- odxtools/encryptcompressmethod.py +16 -3
- odxtools/externflashdata.py +21 -2
- odxtools/flashdata.py +40 -2
- odxtools/identvalue.py +16 -3
- odxtools/internflashdata.py +4 -0
- odxtools/isotp_state_machine.py +11 -11
- odxtools/itemvalue.py +77 -0
- odxtools/multipleecujob.py +178 -0
- odxtools/multipleecujobspec.py +142 -0
- odxtools/nameditemlist.py +2 -2
- odxtools/optionitem.py +79 -0
- odxtools/outputparam.py +1 -1
- odxtools/parameters/codedconstparameter.py +2 -3
- odxtools/readdiagcommconnector.py +79 -0
- odxtools/readparamvalue.py +52 -0
- odxtools/request.py +3 -3
- odxtools/response.py +8 -4
- odxtools/snrefcontext.py +2 -0
- odxtools/statemachine.py +3 -2
- odxtools/systemitem.py +23 -0
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -0
- odxtools/templates/ecu_config.odx-e.xml.jinja2 +38 -0
- odxtools/templates/flash.odx-f.xml.jinja2 +2 -2
- odxtools/templates/macros/printAdminData.xml.jinja2 +1 -1
- odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
- odxtools/templates/macros/printChecksum.xml.jinja2 +1 -1
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +2 -2
- odxtools/templates/macros/printConfigData.xml.jinja2 +39 -0
- odxtools/templates/macros/printConfigDataDictionarySpec.xml.jinja2 +22 -0
- odxtools/templates/macros/printConfigItems.xml.jinja2 +66 -0
- odxtools/templates/macros/printConfigRecord.xml.jinja2 +73 -0
- odxtools/templates/macros/printDOP.xml.jinja2 +5 -6
- odxtools/templates/macros/printDataRecord.xml.jinja2 +35 -0
- odxtools/templates/macros/printDiagCommDataConnector.xml.jinja2 +66 -0
- odxtools/templates/macros/printDiagDataDictionarySpec.xml.jinja2 +107 -0
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +11 -97
- odxtools/templates/macros/printElementId.xml.jinja2 +2 -0
- odxtools/templates/macros/printExpectedIdent.xml.jinja2 +1 -1
- odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
- odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -4
- odxtools/templates/macros/printItemValue.xml.jinja2 +31 -0
- odxtools/templates/macros/printMultipleEcuJob.xml.jinja2 +77 -0
- odxtools/templates/macros/printOwnIdent.xml.jinja2 +1 -1
- odxtools/templates/macros/printSecurity.xml.jinja2 +4 -4
- odxtools/templates/macros/printSegment.xml.jinja2 +1 -1
- odxtools/templates/multiple-ecu-job-spec.odx-m.xml.jinja2 +51 -0
- odxtools/validbasevariant.py +62 -0
- odxtools/validityfor.py +16 -3
- odxtools/variantmatcher.py +4 -4
- odxtools/version.py +2 -2
- odxtools/writediagcommconnector.py +77 -0
- odxtools/writepdxfile.py +58 -27
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/METADATA +2 -1
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/RECORD +74 -45
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/WHEEL +1 -1
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/entry_points.txt +0 -0
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/licenses/LICENSE +0 -0
- {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/top_level.txt +0 -0
odxtools/isotp_state_machine.py
CHANGED
@@ -44,7 +44,7 @@ class IsoTpStateMachine:
|
|
44
44
|
self._telegram_data: list[bytearray | None] = [None] * len(can_rx_ids)
|
45
45
|
self._telegram_last_rx_fragment_idx = [0] * len(can_rx_ids)
|
46
46
|
|
47
|
-
def decode_rx_frame(self, rx_id: int, data: bytes) -> Iterable[tuple[int, bytes]]:
|
47
|
+
def decode_rx_frame(self, rx_id: int, data: bytes | bytearray) -> Iterable[tuple[int, bytes]]:
|
48
48
|
"""Handle the ISO-TP state transitions caused by a CAN frame.
|
49
49
|
|
50
50
|
E.g., add some data to a telegram, etc. Returns a generator of
|
@@ -67,7 +67,7 @@ class IsoTpStateMachine:
|
|
67
67
|
self.on_single_frame(telegram_idx, data[1:1 + telegram_len])
|
68
68
|
self.on_telegram_complete(telegram_idx, data[1:1 + telegram_len])
|
69
69
|
|
70
|
-
yield (rx_id, data[1:1 + telegram_len])
|
70
|
+
yield (rx_id, bytes(data[1:1 + telegram_len]))
|
71
71
|
|
72
72
|
elif frame_type == IsoTp.FRAME_TYPE_FIRST:
|
73
73
|
frame_type, telegram_len = bitstruct.unpack("u4u12", data)
|
@@ -105,7 +105,7 @@ class IsoTpStateMachine:
|
|
105
105
|
self.on_sequence_error(telegram_idx, expected_segment_idx, rx_segment_idx)
|
106
106
|
elif len(telegram_data) == n:
|
107
107
|
self.on_telegram_complete(telegram_idx, telegram_data)
|
108
|
-
yield (rx_id, telegram_data)
|
108
|
+
yield (rx_id, bytes(telegram_data))
|
109
109
|
|
110
110
|
elif frame_type == IsoTp.FRAME_TYPE_FLOW_CONTROL:
|
111
111
|
frame_type, flow_control_flag = bitstruct.unpack("u4u4", data)
|
@@ -185,7 +185,7 @@ class IsoTpStateMachine:
|
|
185
185
|
"""
|
186
186
|
return self._can_rx_ids[telegram_idx]
|
187
187
|
|
188
|
-
def telegram_data(self, telegram_idx: int) ->
|
188
|
+
def telegram_data(self, telegram_idx: int) -> bytearray | None:
|
189
189
|
"""Given a Telegram index, returns the data received for this telegram
|
190
190
|
so far.
|
191
191
|
|
@@ -196,16 +196,16 @@ class IsoTpStateMachine:
|
|
196
196
|
##############
|
197
197
|
# Callbacks
|
198
198
|
##############
|
199
|
-
def on_single_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
199
|
+
def on_single_frame(self, telegram_idx: int, frame_payload: bytes | bytearray) -> None:
|
200
200
|
"""Callback method for when an ISO-TP message of type "single frame" has been received"""
|
201
201
|
pass
|
202
202
|
|
203
|
-
def on_first_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
203
|
+
def on_first_frame(self, telegram_idx: int, frame_payload: bytes | bytearray) -> None:
|
204
204
|
"""Callback method for when an ISO-TP message of type "first frame" has been received"""
|
205
205
|
pass
|
206
206
|
|
207
207
|
def on_consecutive_frame(self, telegram_idx: int, segment_idx: int,
|
208
|
-
frame_payload: bytes) -> None:
|
208
|
+
frame_payload: bytes | bytearray) -> None:
|
209
209
|
"""Callback method for when an ISO-TP message of type "consecutive frame" has been received"""
|
210
210
|
pass
|
211
211
|
|
@@ -221,7 +221,7 @@ class IsoTpStateMachine:
|
|
221
221
|
"""Method called when a frame exhibiting an unknown frame type has been received"""
|
222
222
|
pass
|
223
223
|
|
224
|
-
def on_telegram_complete(self, telegram_idx: int, telegram_payload: bytes) -> None:
|
224
|
+
def on_telegram_complete(self, telegram_idx: int, telegram_payload: bytes | bytearray) -> None:
|
225
225
|
"""Method called when an ISO-TP telegram has been fully received"""
|
226
226
|
pass
|
227
227
|
|
@@ -264,7 +264,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
264
264
|
"""
|
265
265
|
return self._can_tx_ids[telegram_idx]
|
266
266
|
|
267
|
-
def on_single_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
267
|
+
def on_single_frame(self, telegram_idx: int, frame_payload: bytes | bytearray) -> None:
|
268
268
|
# send ACK
|
269
269
|
# rx_id = self.can_rx_id(telegram_idx)
|
270
270
|
tx_id = self.can_tx_id(telegram_idx)
|
@@ -283,7 +283,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
283
283
|
|
284
284
|
super().on_first_frame(telegram_idx, frame_payload)
|
285
285
|
|
286
|
-
def on_first_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
286
|
+
def on_first_frame(self, telegram_idx: int, frame_payload: bytes | bytearray) -> None:
|
287
287
|
# send ACK
|
288
288
|
# rx_id = self.can_rx_id(telegram_idx)
|
289
289
|
tx_id = self.can_tx_id(telegram_idx)
|
@@ -306,7 +306,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
306
306
|
super().on_first_frame(telegram_idx, frame_payload)
|
307
307
|
|
308
308
|
def on_consecutive_frame(self, telegram_idx: int, segment_idx: int,
|
309
|
-
frame_payload: bytes) -> None:
|
309
|
+
frame_payload: bytes | bytearray) -> None:
|
310
310
|
num_received = self._frames_received[telegram_idx]
|
311
311
|
if num_received is None:
|
312
312
|
# consequtive frame received before a first frame.
|
odxtools/itemvalue.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .audience import Audience
|
7
|
+
from .odxdoccontext import OdxDocContext
|
8
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
9
|
+
from .snrefcontext import SnRefContext
|
10
|
+
from .specialdatagroup import SpecialDataGroup
|
11
|
+
from .text import Text
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(kw_only=True)
|
15
|
+
class ItemValue:
|
16
|
+
"""This class represents a ITEM-VALUE."""
|
17
|
+
|
18
|
+
phys_constant_value: str | None
|
19
|
+
meaning: Text | None = None
|
20
|
+
key: str | None = None
|
21
|
+
rule: str | None = None
|
22
|
+
description: Text | None = None
|
23
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
24
|
+
audience: Audience | None = None
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ItemValue":
|
28
|
+
phys_constant_value = et_element.findtext("PHYS-CONSTANT-VALUE")
|
29
|
+
|
30
|
+
meaning = None
|
31
|
+
if (meaning_elem := et_element.find("MEANING")) is not None:
|
32
|
+
meaning = Text.from_et(meaning_elem, context)
|
33
|
+
|
34
|
+
key = et_element.findtext("KEY")
|
35
|
+
rule = et_element.findtext("RULE")
|
36
|
+
|
37
|
+
description = None
|
38
|
+
if (description_elem := et_element.find("DESCRIPTION")) is not None:
|
39
|
+
description = Text.from_et(description_elem, context)
|
40
|
+
|
41
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
42
|
+
|
43
|
+
audience = None
|
44
|
+
if (aud_elem := et_element.find("AUDIENCE")) is not None:
|
45
|
+
audience = Audience.from_et(aud_elem, context)
|
46
|
+
|
47
|
+
return ItemValue(
|
48
|
+
phys_constant_value=phys_constant_value,
|
49
|
+
meaning=meaning,
|
50
|
+
key=key,
|
51
|
+
rule=rule,
|
52
|
+
description=description,
|
53
|
+
sdgs=sdgs,
|
54
|
+
audience=audience,
|
55
|
+
)
|
56
|
+
|
57
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
58
|
+
result = {}
|
59
|
+
|
60
|
+
if self.audience is not None:
|
61
|
+
result.update(self.audience._build_odxlinks())
|
62
|
+
for sdg in self.sdgs:
|
63
|
+
result.update(sdg._build_odxlinks())
|
64
|
+
|
65
|
+
return result
|
66
|
+
|
67
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
68
|
+
if self.audience is not None:
|
69
|
+
self.audience._resolve_odxlinks(odxlinks)
|
70
|
+
for sdg in self.sdgs:
|
71
|
+
sdg._resolve_odxlinks(odxlinks)
|
72
|
+
|
73
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
74
|
+
if self.audience is not None:
|
75
|
+
self.audience._resolve_snrefs(context)
|
76
|
+
for sdg in self.sdgs:
|
77
|
+
sdg._resolve_snrefs(context)
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .admindata import AdminData
|
7
|
+
from .audience import Audience
|
8
|
+
from .diaglayers.diaglayer import DiagLayer
|
9
|
+
from .element import IdentifiableElement
|
10
|
+
from .exceptions import odxrequire
|
11
|
+
from .functionalclass import FunctionalClass
|
12
|
+
from .inputparam import InputParam
|
13
|
+
from .nameditemlist import NamedItemList
|
14
|
+
from .negoutputparam import NegOutputParam
|
15
|
+
from .odxdoccontext import OdxDocContext
|
16
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
17
|
+
from .odxtypes import odxstr_to_bool
|
18
|
+
from .outputparam import OutputParam
|
19
|
+
from .progcode import ProgCode
|
20
|
+
from .snrefcontext import SnRefContext
|
21
|
+
from .specialdatagroup import SpecialDataGroup
|
22
|
+
from .utils import dataclass_fields_asdict
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass(kw_only=True)
|
26
|
+
class MultipleEcuJob(IdentifiableElement):
|
27
|
+
"""A multiple ECU job is a diagnostic communication primitive.
|
28
|
+
|
29
|
+
A multiple ECU job is more complex than a diagnostic service and is
|
30
|
+
not provided natively by the ECU. In particular, the job is
|
31
|
+
defined in external programs which are referenced by the attribute
|
32
|
+
`.prog_codes`.
|
33
|
+
|
34
|
+
In contrast to "single ECU jobs", a multiple ECU job only involves
|
35
|
+
calls to services provided by more than one ECU.
|
36
|
+
|
37
|
+
Multiple ECU jobs are defined in section 7.3.11 of the ASAM MCD-2
|
38
|
+
standard.
|
39
|
+
"""
|
40
|
+
|
41
|
+
admin_data: AdminData | None = None
|
42
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
43
|
+
functional_class_refs: list[OdxLinkRef] = field(default_factory=list)
|
44
|
+
prog_codes: list[ProgCode] = field(default_factory=list)
|
45
|
+
input_params: NamedItemList[InputParam] = field(default_factory=NamedItemList)
|
46
|
+
output_params: NamedItemList[OutputParam] = field(default_factory=NamedItemList)
|
47
|
+
neg_output_params: NamedItemList[NegOutputParam] = field(default_factory=NamedItemList)
|
48
|
+
diag_layer_refs: list[OdxLinkRef] = field(default_factory=list)
|
49
|
+
audience: Audience | None = None
|
50
|
+
semantic: str | None = None
|
51
|
+
is_executable_raw: bool | None = None
|
52
|
+
|
53
|
+
@property
|
54
|
+
def functional_classes(self) -> NamedItemList[FunctionalClass]:
|
55
|
+
return self._functional_classes
|
56
|
+
|
57
|
+
@property
|
58
|
+
def diag_layers(self) -> NamedItemList[DiagLayer]:
|
59
|
+
return self._diag_layers
|
60
|
+
|
61
|
+
@property
|
62
|
+
def is_executable(self) -> bool:
|
63
|
+
return self.is_executable_raw in (True, None)
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MultipleEcuJob":
|
67
|
+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
|
68
|
+
|
69
|
+
admin_data = None
|
70
|
+
if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
|
71
|
+
admin_data = AdminData.from_et(admin_data_elem, context)
|
72
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
73
|
+
functional_class_refs = [
|
74
|
+
odxrequire(OdxLinkRef.from_et(el, context))
|
75
|
+
for el in et_element.iterfind("FUNCT-CLASS-REFS/FUNCT-CLASS-REF")
|
76
|
+
]
|
77
|
+
prog_codes = [
|
78
|
+
ProgCode.from_et(pc_elem, context)
|
79
|
+
for pc_elem in et_element.iterfind("PROG-CODES/PROG-CODE")
|
80
|
+
]
|
81
|
+
|
82
|
+
input_params = NamedItemList([
|
83
|
+
InputParam.from_et(el, context)
|
84
|
+
for el in et_element.iterfind("INPUT-PARAMS/INPUT-PARAM")
|
85
|
+
])
|
86
|
+
output_params = NamedItemList([
|
87
|
+
OutputParam.from_et(el, context)
|
88
|
+
for el in et_element.iterfind("OUTPUT-PARAMS/OUTPUT-PARAM")
|
89
|
+
])
|
90
|
+
neg_output_params = NamedItemList([
|
91
|
+
NegOutputParam.from_et(el, context)
|
92
|
+
for el in et_element.iterfind("NEG-OUTPUT-PARAMS/NEG-OUTPUT-PARAM")
|
93
|
+
])
|
94
|
+
diag_layer_refs = [
|
95
|
+
odxrequire(OdxLinkRef.from_et(el, context))
|
96
|
+
for el in et_element.iterfind("DIAG-LAYER-REFS/DIAG-LAYER-REF")
|
97
|
+
]
|
98
|
+
audience = None
|
99
|
+
if (aud_elem := et_element.find("AUDIENCE")) is not None:
|
100
|
+
audience = Audience.from_et(aud_elem, context)
|
101
|
+
|
102
|
+
semantic = et_element.attrib.get("SEMANTIC")
|
103
|
+
is_executable_raw = odxstr_to_bool(et_element.attrib.get("IS-EXECUTABLE"))
|
104
|
+
|
105
|
+
return MultipleEcuJob(
|
106
|
+
admin_data=admin_data,
|
107
|
+
sdgs=sdgs,
|
108
|
+
functional_class_refs=functional_class_refs,
|
109
|
+
prog_codes=prog_codes,
|
110
|
+
input_params=input_params,
|
111
|
+
output_params=output_params,
|
112
|
+
neg_output_params=neg_output_params,
|
113
|
+
diag_layer_refs=diag_layer_refs,
|
114
|
+
audience=audience,
|
115
|
+
semantic=semantic,
|
116
|
+
is_executable_raw=is_executable_raw,
|
117
|
+
**kwargs)
|
118
|
+
|
119
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
120
|
+
result = {self.odx_id: self}
|
121
|
+
|
122
|
+
if self.admin_data is not None:
|
123
|
+
result.update(self.admin_data._build_odxlinks())
|
124
|
+
for sdg in self.sdgs:
|
125
|
+
result.update(sdg._build_odxlinks())
|
126
|
+
for prog_code in self.prog_codes:
|
127
|
+
result.update(prog_code._build_odxlinks())
|
128
|
+
for input_param in self.input_params:
|
129
|
+
result.update(input_param._build_odxlinks())
|
130
|
+
for output_param in self.output_params:
|
131
|
+
result.update(output_param._build_odxlinks())
|
132
|
+
for neg_output_param in self.neg_output_params:
|
133
|
+
result.update(neg_output_param._build_odxlinks())
|
134
|
+
if self.audience is not None:
|
135
|
+
result.update(self.audience._build_odxlinks())
|
136
|
+
|
137
|
+
return result
|
138
|
+
|
139
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
140
|
+
if self.admin_data is not None:
|
141
|
+
self.admin_data._resolve_odxlinks(odxlinks)
|
142
|
+
for sdg in self.sdgs:
|
143
|
+
sdg._resolve_odxlinks(odxlinks)
|
144
|
+
for prog_code in self.prog_codes:
|
145
|
+
prog_code._resolve_odxlinks(odxlinks)
|
146
|
+
for input_param in self.input_params:
|
147
|
+
input_param._resolve_odxlinks(odxlinks)
|
148
|
+
for output_param in self.output_params:
|
149
|
+
output_param._resolve_odxlinks(odxlinks)
|
150
|
+
for neg_output_param in self.neg_output_params:
|
151
|
+
neg_output_param._resolve_odxlinks(odxlinks)
|
152
|
+
if self.audience is not None:
|
153
|
+
self.audience._resolve_odxlinks(odxlinks)
|
154
|
+
|
155
|
+
self._functional_classes = NamedItemList(
|
156
|
+
[odxlinks.resolve(fc_ref, FunctionalClass) for fc_ref in self.functional_class_refs])
|
157
|
+
self._diag_layers = NamedItemList(
|
158
|
+
[odxlinks.resolve(dl_ref, DiagLayer) for dl_ref in self.diag_layer_refs])
|
159
|
+
|
160
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
161
|
+
context.multiple_ecu_job = self
|
162
|
+
|
163
|
+
if self.admin_data is not None:
|
164
|
+
self.admin_data._resolve_snrefs(context)
|
165
|
+
for sdg in self.sdgs:
|
166
|
+
sdg._resolve_snrefs(context)
|
167
|
+
for prog_code in self.prog_codes:
|
168
|
+
prog_code._resolve_snrefs(context)
|
169
|
+
for input_param in self.input_params:
|
170
|
+
input_param._resolve_snrefs(context)
|
171
|
+
for output_param in self.output_params:
|
172
|
+
output_param._resolve_snrefs(context)
|
173
|
+
for neg_output_param in self.neg_output_params:
|
174
|
+
neg_output_param._resolve_snrefs(context)
|
175
|
+
if self.audience is not None:
|
176
|
+
self.audience._resolve_snrefs(context)
|
177
|
+
|
178
|
+
context.multiple_ecu_job = None
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from copy import copy
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
|
+
from xml.etree import ElementTree
|
6
|
+
|
7
|
+
from .additionalaudience import AdditionalAudience
|
8
|
+
from .diagdatadictionaryspec import DiagDataDictionarySpec
|
9
|
+
from .diaglayers.ecushareddata import EcuSharedData
|
10
|
+
from .functionalclass import FunctionalClass
|
11
|
+
from .multipleecujob import MultipleEcuJob
|
12
|
+
from .nameditemlist import NamedItemList
|
13
|
+
from .odxcategory import OdxCategory
|
14
|
+
from .odxdoccontext import OdxDocContext
|
15
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
16
|
+
from .snrefcontext import SnRefContext
|
17
|
+
from .utils import dataclass_fields_asdict
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from .database import Database
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass(kw_only=True)
|
24
|
+
class MultipleEcuJobSpec(OdxCategory):
|
25
|
+
multiple_ecu_jobs: NamedItemList[MultipleEcuJob] = field(default_factory=NamedItemList)
|
26
|
+
diag_data_dictionary_spec: DiagDataDictionarySpec | None = None
|
27
|
+
functional_classes: NamedItemList[FunctionalClass] = field(default_factory=NamedItemList)
|
28
|
+
additional_audiences: NamedItemList[AdditionalAudience] = field(default_factory=NamedItemList)
|
29
|
+
import_refs: list[OdxLinkRef] = field(default_factory=list)
|
30
|
+
|
31
|
+
@property
|
32
|
+
def imported_layers(self) -> NamedItemList[EcuSharedData]:
|
33
|
+
"""The resolved IMPORT-REFs
|
34
|
+
|
35
|
+
ODXLINK defined by ECU-SHARED-DATA layers referenced via
|
36
|
+
IMPORT-REF ought to be treated as if they where defined
|
37
|
+
locally.
|
38
|
+
"""
|
39
|
+
return self._imported_layers
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MultipleEcuJobSpec":
|
43
|
+
|
44
|
+
base_obj = OdxCategory.from_et(et_element, context)
|
45
|
+
kwargs = dataclass_fields_asdict(base_obj)
|
46
|
+
|
47
|
+
multiple_ecu_jobs = NamedItemList([
|
48
|
+
MultipleEcuJob.from_et(el, context)
|
49
|
+
for el in et_element.iterfind("MULTIPLE-ECU-JOBS/MULTIPLE-ECU-JOB")
|
50
|
+
])
|
51
|
+
diag_data_dictionary_spec = None
|
52
|
+
if (ddds_elem := et_element.find("DIAG-DATA-DICTIONARY-SPEC")) is not None:
|
53
|
+
diag_data_dictionary_spec = DiagDataDictionarySpec.from_et(ddds_elem, context)
|
54
|
+
functional_classes = NamedItemList([
|
55
|
+
FunctionalClass.from_et(el, context)
|
56
|
+
for el in et_element.iterfind("FUNCT-CLASSS/FUNCT-CLASS")
|
57
|
+
])
|
58
|
+
additional_audiences = NamedItemList([
|
59
|
+
AdditionalAudience.from_et(el, context)
|
60
|
+
for el in et_element.iterfind("ADDITIONAL-AUDIENCES/ADDITIONAL-AUDIENCE")
|
61
|
+
])
|
62
|
+
import_refs = [
|
63
|
+
OdxLinkRef.from_et(el, context) for el in et_element.iterfind("IMPORT-REFS/IMPORT-REF")
|
64
|
+
]
|
65
|
+
|
66
|
+
return MultipleEcuJobSpec(
|
67
|
+
multiple_ecu_jobs=multiple_ecu_jobs,
|
68
|
+
diag_data_dictionary_spec=diag_data_dictionary_spec,
|
69
|
+
functional_classes=functional_classes,
|
70
|
+
additional_audiences=additional_audiences,
|
71
|
+
import_refs=import_refs,
|
72
|
+
**kwargs)
|
73
|
+
|
74
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
75
|
+
odxlinks = super()._build_odxlinks()
|
76
|
+
|
77
|
+
for multiple_ecu_job in self.multiple_ecu_jobs:
|
78
|
+
odxlinks.update(multiple_ecu_job._build_odxlinks())
|
79
|
+
|
80
|
+
if self.diag_data_dictionary_spec is not None:
|
81
|
+
odxlinks.update(self.diag_data_dictionary_spec._build_odxlinks())
|
82
|
+
|
83
|
+
for functional_class in self.functional_classes:
|
84
|
+
odxlinks.update(functional_class._build_odxlinks())
|
85
|
+
|
86
|
+
for additional_audience in self.additional_audiences:
|
87
|
+
odxlinks.update(additional_audience._build_odxlinks())
|
88
|
+
|
89
|
+
return odxlinks
|
90
|
+
|
91
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
92
|
+
# deal with the import refs: IDs defined therein are to be
|
93
|
+
# handled like they were local.
|
94
|
+
self._imported_layers: NamedItemList[EcuSharedData] = NamedItemList()
|
95
|
+
if self.import_refs:
|
96
|
+
extended_odxlinks = copy(odxlinks)
|
97
|
+
imported_links: dict[OdxLinkId, Any] = {}
|
98
|
+
for import_ref in self.import_refs:
|
99
|
+
imported_dl = odxlinks.resolve(import_ref, EcuSharedData)
|
100
|
+
self._imported_layers.append(imported_dl)
|
101
|
+
|
102
|
+
# replace the document fragments of the ODX id with
|
103
|
+
# the those of the muliple-ecu-spec. (be aware that
|
104
|
+
# the "original" locations are still available.)
|
105
|
+
imported_dl_links = imported_dl._build_odxlinks()
|
106
|
+
for link_id, obj in imported_dl_links.items():
|
107
|
+
link_id = OdxLinkId(link_id.local_id, self.odx_id.doc_fragments)
|
108
|
+
imported_links[link_id] = obj
|
109
|
+
|
110
|
+
extended_odxlinks.update(imported_links, overwrite=False)
|
111
|
+
else:
|
112
|
+
extended_odxlinks = odxlinks
|
113
|
+
|
114
|
+
for multiple_ecu_job in self.multiple_ecu_jobs:
|
115
|
+
multiple_ecu_job._resolve_odxlinks(extended_odxlinks)
|
116
|
+
|
117
|
+
if self.diag_data_dictionary_spec is not None:
|
118
|
+
self.diag_data_dictionary_spec._resolve_odxlinks(extended_odxlinks)
|
119
|
+
|
120
|
+
for functional_class in self.functional_classes:
|
121
|
+
functional_class._resolve_odxlinks(extended_odxlinks)
|
122
|
+
|
123
|
+
for additional_audiences in self.additional_audiences:
|
124
|
+
additional_audiences._resolve_odxlinks(extended_odxlinks)
|
125
|
+
|
126
|
+
def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
|
127
|
+
super()._finalize_init(database, odxlinks)
|
128
|
+
|
129
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
130
|
+
super()._resolve_snrefs(context)
|
131
|
+
|
132
|
+
for multiple_ecu_job in self.multiple_ecu_jobs:
|
133
|
+
multiple_ecu_job._resolve_snrefs(context)
|
134
|
+
|
135
|
+
if self.diag_data_dictionary_spec is not None:
|
136
|
+
self.diag_data_dictionary_spec._resolve_snrefs(context)
|
137
|
+
|
138
|
+
for functional_class in self.functional_classes:
|
139
|
+
functional_class._resolve_snrefs(context)
|
140
|
+
|
141
|
+
for additional_audiences in self.additional_audiences:
|
142
|
+
additional_audiences._resolve_snrefs(context)
|
odxtools/nameditemlist.py
CHANGED
@@ -4,7 +4,7 @@ import typing
|
|
4
4
|
from collections.abc import Collection, Iterable
|
5
5
|
from copy import deepcopy
|
6
6
|
from keyword import iskeyword
|
7
|
-
from typing import Any, SupportsIndex, TypeVar,
|
7
|
+
from typing import Any, SupportsIndex, TypeVar, overload, runtime_checkable
|
8
8
|
|
9
9
|
from .exceptions import odxraise
|
10
10
|
|
@@ -159,7 +159,7 @@ class ItemAttributeList(list[T]):
|
|
159
159
|
return super().__getitem__(key)
|
160
160
|
return default
|
161
161
|
else:
|
162
|
-
return
|
162
|
+
return self._item_dict.get(key, default)
|
163
163
|
|
164
164
|
def __eq__(self, other: object) -> bool:
|
165
165
|
"""
|
odxtools/optionitem.py
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .audience import Audience
|
7
|
+
from .configitem import ConfigItem
|
8
|
+
from .itemvalue import ItemValue
|
9
|
+
from .odxdoccontext import OdxDocContext
|
10
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
11
|
+
from .snrefcontext import SnRefContext
|
12
|
+
from .utils import dataclass_fields_asdict
|
13
|
+
|
14
|
+
|
15
|
+
@dataclass(kw_only=True)
|
16
|
+
class OptionItem(ConfigItem):
|
17
|
+
"""This class represents a OPTION-ITEM."""
|
18
|
+
|
19
|
+
physical_default_value: str | None = None
|
20
|
+
item_values: list[ItemValue] = field(default_factory=list)
|
21
|
+
write_audience: Audience | None = None
|
22
|
+
read_audience: Audience | None = None
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "OptionItem":
|
26
|
+
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
|
27
|
+
|
28
|
+
physical_default_value = et_element.findtext("PHYSICAL-DEFAULT-VALUE")
|
29
|
+
|
30
|
+
item_values = [
|
31
|
+
ItemValue.from_et(el, context) for el in et_element.iterfind("ITEM-VALUES/ITEM-VALUE")
|
32
|
+
]
|
33
|
+
|
34
|
+
write_audience = None
|
35
|
+
if (wa_elem := et_element.find("WRITE-AUDIENCE")) is not None:
|
36
|
+
write_audience = Audience.from_et(wa_elem, context)
|
37
|
+
|
38
|
+
read_audience = None
|
39
|
+
if (ra_elem := et_element.find("READ-AUDIENCE")) is not None:
|
40
|
+
read_audience = Audience.from_et(ra_elem, context)
|
41
|
+
|
42
|
+
return OptionItem(
|
43
|
+
physical_default_value=physical_default_value,
|
44
|
+
item_values=item_values,
|
45
|
+
write_audience=write_audience,
|
46
|
+
read_audience=read_audience,
|
47
|
+
**kwargs)
|
48
|
+
|
49
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
50
|
+
result = super()._build_odxlinks()
|
51
|
+
|
52
|
+
for item_value in self.item_values:
|
53
|
+
result.update(item_value._build_odxlinks())
|
54
|
+
if self.write_audience is not None:
|
55
|
+
result.update(self.write_audience._build_odxlinks())
|
56
|
+
if self.read_audience is not None:
|
57
|
+
result.update(self.read_audience._build_odxlinks())
|
58
|
+
|
59
|
+
return result
|
60
|
+
|
61
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
62
|
+
super()._resolve_odxlinks(odxlinks)
|
63
|
+
|
64
|
+
for item_value in self.item_values:
|
65
|
+
item_value._resolve_odxlinks(odxlinks)
|
66
|
+
if self.write_audience is not None:
|
67
|
+
self.write_audience._resolve_odxlinks(odxlinks)
|
68
|
+
if self.read_audience is not None:
|
69
|
+
self.read_audience._resolve_odxlinks(odxlinks)
|
70
|
+
|
71
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
72
|
+
super()._resolve_snrefs(context)
|
73
|
+
|
74
|
+
for item_value in self.item_values:
|
75
|
+
item_value._resolve_snrefs(context)
|
76
|
+
if self.write_audience is not None:
|
77
|
+
self.write_audience._resolve_snrefs(context)
|
78
|
+
if self.read_audience is not None:
|
79
|
+
self.read_audience._resolve_snrefs(context)
|
odxtools/outputparam.py
CHANGED
@@ -38,7 +38,7 @@ class OutputParam(IdentifiableElement):
|
|
38
38
|
return OutputParam(dop_base_ref=dop_base_ref, semantic=semantic, **kwargs)
|
39
39
|
|
40
40
|
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
41
|
-
return {}
|
41
|
+
return {self.odx_id: self}
|
42
42
|
|
43
43
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
44
44
|
self._dop = odxlinks.resolve(self.dop_base_ref, DopBase)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import warnings
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import Any
|
4
|
+
from typing import Any
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
7
|
from typing_extensions import override
|
@@ -57,8 +57,7 @@ class CodedConstParameter(Parameter):
|
|
57
57
|
coded_value_raw=coded_value_raw, diag_coded_type=diag_coded_type, **kwargs)
|
58
58
|
|
59
59
|
def __post_init__(self) -> None:
|
60
|
-
self._coded_value =
|
61
|
-
AtomicOdxType, self.diag_coded_type.base_data_type.from_string(self.coded_value_raw))
|
60
|
+
self._coded_value = self.diag_coded_type.base_data_type.from_string(self.coded_value_raw)
|
62
61
|
|
63
62
|
@override
|
64
63
|
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|