odxtools 10.2.0__py3-none-any.whl → 10.3.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 +21 -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 +39 -17
- odxtools/diaglayers/ecuvariant.py +12 -19
- odxtools/diaglayers/hierarchyelement.py +5 -5
- odxtools/diaglayers/protocol.py +14 -0
- 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/nameditemlist.py +2 -2
- odxtools/odxlink.py +4 -2
- odxtools/optionitem.py +79 -0
- 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/statemachine.py +3 -2
- odxtools/subcomponentparamconnector.py +1 -1
- 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/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/printExpectedIdent.xml.jinja2 +1 -1
- odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
- odxtools/templates/macros/printItemValue.xml.jinja2 +31 -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/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 +15 -0
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/METADATA +2 -1
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/RECORD +68 -44
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/WHEEL +1 -1
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/entry_points.txt +0 -0
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/licenses/LICENSE +0 -0
- {odxtools-10.2.0.dist-info → odxtools-10.3.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)
|
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/odxlink.py
CHANGED
@@ -204,8 +204,10 @@ class OdxLinkDatabase:
|
|
204
204
|
# locate an object exhibiting with the referenced local ID
|
205
205
|
# in the ID database for the document fragment
|
206
206
|
if (obj := doc_frag_db.get(ref.ref_id)) is not None:
|
207
|
-
if expected_type is not None:
|
208
|
-
|
207
|
+
if expected_type is not None and not isinstance(obj, expected_type):
|
208
|
+
odxraise(f"Referenced object for link {ref.ref_id} is of type "
|
209
|
+
f"{type(obj).__name__} which is not a subclass of expected "
|
210
|
+
f"type {expected_type.__name__}")
|
209
211
|
|
210
212
|
return obj
|
211
213
|
|
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)
|
@@ -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]:
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .diagcomm import DiagComm
|
7
|
+
from .exceptions import odxrequire
|
8
|
+
from .odxdoccontext import OdxDocContext
|
9
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
10
|
+
from .readparamvalue import ReadParamValue
|
11
|
+
from .snrefcontext import SnRefContext
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(kw_only=True)
|
15
|
+
class ReadDiagCommConnector:
|
16
|
+
read_param_values: list[ReadParamValue]
|
17
|
+
|
18
|
+
# exactly one of the following attributes must be non-None
|
19
|
+
read_diag_comm_ref: OdxLinkRef | None = None
|
20
|
+
read_diag_comm_snref: str | None = None
|
21
|
+
|
22
|
+
# exactly one of the following attributes must be non-None
|
23
|
+
read_data_snref: str | None = None
|
24
|
+
read_data_snpathref: str | None = None
|
25
|
+
|
26
|
+
@property
|
27
|
+
def read_diag_comm(self) -> DiagComm | None:
|
28
|
+
return self._read_diag_comm
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ReadDiagCommConnector":
|
32
|
+
read_param_values = [
|
33
|
+
ReadParamValue.from_et(el, context)
|
34
|
+
for el in et_element.iterfind("READ-PARAM-VALUES/READ-PARAM-VALUE")
|
35
|
+
]
|
36
|
+
|
37
|
+
read_diag_comm_ref = OdxLinkRef.from_et(et_element.find("READ-DIAG-COMM-REF"), context)
|
38
|
+
read_diag_comm_snref = None
|
39
|
+
if (read_diag_comm_snref_elem := et_element.find("READ-DIAG-COMM-SNREF")) is not None:
|
40
|
+
read_diag_comm_snref = odxrequire(read_diag_comm_snref_elem.attrib.get("SHORT-NAME"))
|
41
|
+
|
42
|
+
read_data_snref = None
|
43
|
+
if (read_data_snref_elem := et_element.find("READ-DATA-SNREF")) is not None:
|
44
|
+
read_data_snref = odxrequire(read_data_snref_elem.attrib.get("SHORT-NAME"))
|
45
|
+
|
46
|
+
read_data_snpathref = None
|
47
|
+
if (read_data_snpathref_elem := et_element.find("READ-DATA-SNPATHREF")) is not None:
|
48
|
+
read_data_snpathref = odxrequire(read_data_snpathref_elem.attrib.get("SHORT-NAME-PATH"))
|
49
|
+
|
50
|
+
return ReadDiagCommConnector(
|
51
|
+
read_param_values=read_param_values,
|
52
|
+
read_diag_comm_ref=read_diag_comm_ref,
|
53
|
+
read_diag_comm_snref=read_diag_comm_snref,
|
54
|
+
read_data_snref=read_data_snref,
|
55
|
+
read_data_snpathref=read_data_snpathref,
|
56
|
+
)
|
57
|
+
|
58
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
59
|
+
odxlinks = {}
|
60
|
+
|
61
|
+
for rpv in self.read_param_values:
|
62
|
+
odxlinks.update(rpv._build_odxlinks())
|
63
|
+
|
64
|
+
return odxlinks
|
65
|
+
|
66
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
67
|
+
self._read_diag_comm = None
|
68
|
+
if self.read_diag_comm_ref is not None:
|
69
|
+
self._read_diag_comm = odxlinks.resolve(self.read_diag_comm_ref, DiagComm)
|
70
|
+
|
71
|
+
for rpv in self.read_param_values:
|
72
|
+
rpv._resolve_odxlinks(odxlinks)
|
73
|
+
|
74
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
75
|
+
# read_diag_comm_snref cannot be uniquely resolved ahead of
|
76
|
+
# time as it depends on the diag layer which is used
|
77
|
+
|
78
|
+
for rpv in self.read_param_values:
|
79
|
+
rpv._resolve_snrefs(context)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .exceptions import odxrequire
|
7
|
+
from .odxdoccontext import OdxDocContext
|
8
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
9
|
+
from .snrefcontext import SnRefContext
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass(kw_only=True)
|
13
|
+
class ReadParamValue:
|
14
|
+
phys_constant_value: str
|
15
|
+
|
16
|
+
# exactly one of the following attributes must be non-None
|
17
|
+
in_param_if_snref: str | None = None
|
18
|
+
in_param_if_snpathref: str | None = None
|
19
|
+
|
20
|
+
semantic: str
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ReadParamValue":
|
24
|
+
phys_constant_value = odxrequire(et_element.findtext("PHYS-CONSTANT-VALUE"))
|
25
|
+
|
26
|
+
in_param_if_snref = None
|
27
|
+
if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
|
28
|
+
in_param_if_snref = odxrequire(in_param_if_snref_elem.attrib.get("SHORT-NAME"))
|
29
|
+
|
30
|
+
in_param_if_snpathref = None
|
31
|
+
if (in_param_if_snpathref_elem := et_element.find("IN-PARAM-IF-SNPATHREF")) is not None:
|
32
|
+
in_param_if_snpathref = odxrequire(
|
33
|
+
in_param_if_snpathref_elem.attrib.get("SHORT-NAME-PATH"))
|
34
|
+
|
35
|
+
semantic = odxrequire(et_element.attrib.get("SEMANTIC"))
|
36
|
+
|
37
|
+
return ReadParamValue(
|
38
|
+
phys_constant_value=phys_constant_value,
|
39
|
+
in_param_if_snref=in_param_if_snref,
|
40
|
+
in_param_if_snpathref=in_param_if_snpathref,
|
41
|
+
semantic=semantic,
|
42
|
+
)
|
43
|
+
|
44
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
45
|
+
odxlinks: dict[OdxLinkId, Any] = {}
|
46
|
+
return odxlinks
|
47
|
+
|
48
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
49
|
+
pass
|
50
|
+
|
51
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
52
|
+
pass
|
odxtools/request.py
CHANGED
@@ -113,8 +113,8 @@ class Request(IdentifiableElement):
|
|
113
113
|
|
114
114
|
return encode_state.coded_message
|
115
115
|
|
116
|
-
def decode(self, message: bytes) -> ParameterValueDict:
|
117
|
-
decode_state = DecodeState(coded_message=message)
|
116
|
+
def decode(self, message: bytes | bytearray) -> ParameterValueDict:
|
117
|
+
decode_state = DecodeState(coded_message=bytes(message))
|
118
118
|
param_values = self.decode_from_pdu(decode_state)
|
119
119
|
|
120
120
|
if not isinstance(param_values, dict):
|
@@ -129,5 +129,5 @@ class Request(IdentifiableElement):
|
|
129
129
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
130
130
|
return composite_codec_decode_from_pdu(self, decode_state)
|
131
131
|
|
132
|
-
def coded_const_prefix(self, request_prefix: bytes = b'') ->
|
132
|
+
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytearray:
|
133
133
|
return composite_codec_get_coded_const_prefix(self, request_prefix)
|
odxtools/response.py
CHANGED
@@ -109,14 +109,18 @@ class Response(IdentifiableElement):
|
|
109
109
|
context.response = None
|
110
110
|
context.parameters = None
|
111
111
|
|
112
|
-
def encode(self,
|
113
|
-
|
112
|
+
def encode(self,
|
113
|
+
coded_request: bytes | bytearray | None = None,
|
114
|
+
**kwargs: ParameterValue) -> bytearray:
|
115
|
+
encode_state = EncodeState(
|
116
|
+
triggering_request=bytes(coded_request) if coded_request is not None else None,
|
117
|
+
is_end_of_pdu=True)
|
114
118
|
|
115
119
|
self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
|
116
120
|
|
117
121
|
return encode_state.coded_message
|
118
122
|
|
119
|
-
def decode(self, message: bytes) -> ParameterValueDict:
|
123
|
+
def decode(self, message: bytes | bytearray) -> ParameterValueDict:
|
120
124
|
decode_state = DecodeState(coded_message=message)
|
121
125
|
param_values = self.decode_from_pdu(decode_state)
|
122
126
|
|
@@ -151,5 +155,5 @@ class Response(IdentifiableElement):
|
|
151
155
|
|
152
156
|
print(parameter_info(self.free_parameters), end="")
|
153
157
|
|
154
|
-
def coded_const_prefix(self, request_prefix: bytes = b'') ->
|
158
|
+
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytearray:
|
155
159
|
return composite_codec_get_coded_const_prefix(self, request_prefix)
|
odxtools/statemachine.py
CHANGED
@@ -97,8 +97,9 @@ class StateMachine:
|
|
97
97
|
self._state_chart = state_chart
|
98
98
|
self._active_state = state_chart.start_state
|
99
99
|
|
100
|
-
def execute(
|
101
|
-
|
100
|
+
def execute(
|
101
|
+
self, service: "DiagService", **service_params: Any
|
102
|
+
) -> Generator[bytes | bytearray, bytes | bytearray | ParameterValueDict, None]:
|
102
103
|
"""Run a diagnostic service and update the state machine
|
103
104
|
depending on the outcome.
|
104
105
|
|
@@ -77,7 +77,7 @@ class SubComponentParamConnector(IdentifiableElement):
|
|
77
77
|
odxrequire(context.diag_layer).diag_comms, DiagService)
|
78
78
|
self._service = service
|
79
79
|
|
80
|
-
if self._service.request is
|
80
|
+
if self._service.request is None:
|
81
81
|
odxraise()
|
82
82
|
return
|
83
83
|
if not self._service.positive_responses:
|
odxtools/systemitem.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from .configitem import ConfigItem
|
6
|
+
from .exceptions import odxrequire
|
7
|
+
from .odxdoccontext import OdxDocContext
|
8
|
+
from .utils import dataclass_fields_asdict
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass(kw_only=True)
|
12
|
+
class SystemItem(ConfigItem):
|
13
|
+
"""This class represents a SYSTEM-ITEM."""
|
14
|
+
|
15
|
+
sysparam: str
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "SystemItem":
|
19
|
+
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
|
20
|
+
|
21
|
+
sysparam = odxrequire(et_element.attrib.get("SYSPARAM"))
|
22
|
+
|
23
|
+
return SystemItem(sysparam=sysparam, **kwargs)
|
@@ -1,6 +1,10 @@
|
|
1
1
|
{#- -*- mode: sgml; tab-width: 1; indent-tabs-mode: nil -*-
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: MIT
|
4
|
+
#
|
5
|
+
# This template writes an .odx-d file containing a diag layer container, i.e.,
|
6
|
+
# a collection of descriptions of the diagnostic interactions which are
|
7
|
+
# supported by variants of ECUs.
|
4
8
|
-#}
|
5
9
|
{%- import('macros/printOdxCategory.xml.jinja2') as poc %}
|
6
10
|
{%- import('macros/printEcuSharedData.xml.jinja2') as pecusd -%}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
{#- -*- mode: sgml; tab-width: 1; indent-tabs-mode: nil -*-
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
#
|
5
|
+
# This template writes an .odx-e file containing an ECU-CONFIG description
|
6
|
+
# for variant coding.
|
7
|
+
-#}
|
8
|
+
{%- import('macros/printOdxCategory.xml.jinja2') as poc %}
|
9
|
+
{%- import('macros/printConfigData.xml.jinja2') as pcd %}
|
10
|
+
{%- import('macros/printConfigDataDictionarySpec.xml.jinja2') as pcdds %}
|
11
|
+
{%- import('macros/printAudience.xml.jinja2') as paud %}
|
12
|
+
{#- -#}
|
13
|
+
|
14
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
15
|
+
<!-- Written using odxtools {{odxtools_version}} -->
|
16
|
+
<ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
|
17
|
+
{{- set_category_docfrag(ecu_config.short_name, "ECU-CONFIG") }}
|
18
|
+
<ECU-CONFIG {{- poc.printOdxCategoryAttribs(ecu_config) }}>
|
19
|
+
{{- poc.printOdxCategorySubtags(ecu_config)|indent(4) }}
|
20
|
+
{%- if ecu_config.config_datas %}
|
21
|
+
<CONFIG-DATAS>
|
22
|
+
{%- for config_data in ecu_config.config_datas %}
|
23
|
+
{{ pcd.printConfigData(config_data) | indent(6) }}
|
24
|
+
{%- endfor %}
|
25
|
+
</CONFIG-DATAS>
|
26
|
+
{%- endif %}
|
27
|
+
{%- if ecu_config.additional_audiences %}
|
28
|
+
<ADDITIONAL-AUDIENCES>
|
29
|
+
{%- for additional_audience in ecu_config.additional_audiences %}
|
30
|
+
{{ paud.printAdditionalAudience(additional_audience) | indent(6) }}
|
31
|
+
{%- endfor %}
|
32
|
+
</ADDITIONAL-AUDIENCES>
|
33
|
+
{%- endif %}
|
34
|
+
{%- if ecu_config.config_data_dictionary_spec %}
|
35
|
+
{{ pcdds.printConfigDataDictionarySpec(ecu_config.config_data_dictionary_spec) | indent(4) }}
|
36
|
+
{%- endif %}
|
37
|
+
</ECU-CONFIG>
|
38
|
+
</ODX>
|
@@ -2,8 +2,8 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: MIT
|
4
4
|
#
|
5
|
-
# This template writes an .odx-
|
6
|
-
#
|
5
|
+
# This template writes an .odx-f file containing descriptions of firmware
|
6
|
+
# blobs that can be flashed to ECUs.
|
7
7
|
-#}
|
8
8
|
{%- import('macros/printOdxCategory.xml.jinja2') as poc %}
|
9
9
|
{%- import('macros/printEcuMem.xml.jinja2') as pem %}
|
@@ -11,8 +11,8 @@
|
|
11
11
|
</ADDITIONAL-AUDIENCE>
|
12
12
|
{%- endmacro -%}
|
13
13
|
|
14
|
-
{%- macro printAudience(audience) -%}
|
15
|
-
<
|
14
|
+
{%- macro printAudience(audience, tag_name="AUDIENCE") -%}
|
15
|
+
<{{tag_name}} {{-make_bool_xml_attrib("IS-SUPPLIER", audience.is_supplier_raw)}}
|
16
16
|
{{-make_bool_xml_attrib("IS-DEVELOPMENT", audience.is_development_raw)}}
|
17
17
|
{{-make_bool_xml_attrib("IS-MANUFACTURING", audience.is_manufacturing_raw)}}
|
18
18
|
{{-make_bool_xml_attrib("IS-AFTERSALES", audience.is_aftersales_raw)}}
|
@@ -31,5 +31,5 @@
|
|
31
31
|
{%- endfor %}
|
32
32
|
</DISABLED-AUDIENCE-REFS>
|
33
33
|
{%- endif%}
|
34
|
-
</
|
34
|
+
</{{tag_name}}>
|
35
35
|
{%- endmacro -%}
|
@@ -31,6 +31,6 @@
|
|
31
31
|
{%- if checksum.checksum_result.value_type is not none %}
|
32
32
|
{#- #} TYPE="{{ checksum.checksum_result.value_type.value }}"
|
33
33
|
{%- endif %}
|
34
|
-
{#- #}>{{checksum.checksum_result.
|
34
|
+
{#- #}>{{checksum.checksum_result.value_raw}}</CHECKSUM-RESULT>
|
35
35
|
</CHECKSUM>
|
36
36
|
{%- endmacro -%}
|
@@ -110,7 +110,7 @@
|
|
110
110
|
</PROG-CODE>
|
111
111
|
{%- endmacro -%}
|
112
112
|
|
113
|
-
{%- macro printCompuMethod(cm)
|
113
|
+
{%- macro printCompuMethod(cm) %}
|
114
114
|
<COMPU-METHOD>
|
115
115
|
<CATEGORY>{{cm.category.value}}</CATEGORY>
|
116
116
|
{%- if cm.compu_internal_to_phys is not none %}
|
@@ -144,4 +144,4 @@
|
|
144
144
|
</COMPU-PHYS-TO-INTERNAL>
|
145
145
|
{%- endif %}
|
146
146
|
</COMPU-METHOD>
|
147
|
-
{%- endmacro
|
147
|
+
{%- endmacro %}
|