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.
Files changed (68) hide show
  1. odxtools/cli/browse.py +4 -2
  2. odxtools/cli/compare.py +3 -3
  3. odxtools/compositecodec.py +1 -1
  4. odxtools/configdata.py +70 -0
  5. odxtools/configdatadictionaryspec.py +57 -0
  6. odxtools/configiditem.py +18 -0
  7. odxtools/configitem.py +85 -0
  8. odxtools/configrecord.py +146 -0
  9. odxtools/database.py +21 -0
  10. odxtools/dataiditem.py +18 -0
  11. odxtools/datarecord.py +132 -0
  12. odxtools/decodestate.py +1 -1
  13. odxtools/diagcommdataconnector.py +61 -0
  14. odxtools/diaglayers/diaglayer.py +39 -17
  15. odxtools/diaglayers/ecuvariant.py +12 -19
  16. odxtools/diaglayers/hierarchyelement.py +5 -5
  17. odxtools/diaglayers/protocol.py +14 -0
  18. odxtools/diagservice.py +10 -10
  19. odxtools/ecuconfig.py +89 -0
  20. odxtools/encryptcompressmethod.py +16 -3
  21. odxtools/externflashdata.py +21 -2
  22. odxtools/flashdata.py +40 -2
  23. odxtools/identvalue.py +16 -3
  24. odxtools/internflashdata.py +4 -0
  25. odxtools/isotp_state_machine.py +11 -11
  26. odxtools/itemvalue.py +77 -0
  27. odxtools/nameditemlist.py +2 -2
  28. odxtools/odxlink.py +4 -2
  29. odxtools/optionitem.py +79 -0
  30. odxtools/parameters/codedconstparameter.py +2 -3
  31. odxtools/readdiagcommconnector.py +79 -0
  32. odxtools/readparamvalue.py +52 -0
  33. odxtools/request.py +3 -3
  34. odxtools/response.py +8 -4
  35. odxtools/statemachine.py +3 -2
  36. odxtools/subcomponentparamconnector.py +1 -1
  37. odxtools/systemitem.py +23 -0
  38. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -0
  39. odxtools/templates/ecu_config.odx-e.xml.jinja2 +38 -0
  40. odxtools/templates/flash.odx-f.xml.jinja2 +2 -2
  41. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  42. odxtools/templates/macros/printChecksum.xml.jinja2 +1 -1
  43. odxtools/templates/macros/printCompuMethod.xml.jinja2 +2 -2
  44. odxtools/templates/macros/printConfigData.xml.jinja2 +39 -0
  45. odxtools/templates/macros/printConfigDataDictionarySpec.xml.jinja2 +22 -0
  46. odxtools/templates/macros/printConfigItems.xml.jinja2 +66 -0
  47. odxtools/templates/macros/printConfigRecord.xml.jinja2 +73 -0
  48. odxtools/templates/macros/printDOP.xml.jinja2 +5 -6
  49. odxtools/templates/macros/printDataRecord.xml.jinja2 +35 -0
  50. odxtools/templates/macros/printDiagCommDataConnector.xml.jinja2 +66 -0
  51. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +1 -1
  52. odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
  53. odxtools/templates/macros/printItemValue.xml.jinja2 +31 -0
  54. odxtools/templates/macros/printOwnIdent.xml.jinja2 +1 -1
  55. odxtools/templates/macros/printSecurity.xml.jinja2 +4 -4
  56. odxtools/templates/macros/printSegment.xml.jinja2 +1 -1
  57. odxtools/validbasevariant.py +62 -0
  58. odxtools/validityfor.py +16 -3
  59. odxtools/variantmatcher.py +4 -4
  60. odxtools/version.py +2 -2
  61. odxtools/writediagcommconnector.py +77 -0
  62. odxtools/writepdxfile.py +15 -0
  63. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/METADATA +2 -1
  64. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/RECORD +68 -44
  65. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/WHEEL +1 -1
  66. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/entry_points.txt +0 -0
  67. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/licenses/LICENSE +0 -0
  68. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/top_level.txt +0 -0
@@ -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) -> bytes | None:
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, cast, overload, runtime_checkable
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 cast(T | None, self._item_dict.get(key, default))
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
- odxassert(isinstance(obj, expected_type))
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, cast
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 = cast(
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'') -> bytes:
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, coded_request: bytes | None = None, **kwargs: ParameterValue) -> bytearray:
113
- encode_state = EncodeState(triggering_request=coded_request, is_end_of_pdu=True)
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'') -> bytes:
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(self, service: "DiagService", **service_params: Any
101
- ) -> Generator[bytes, bytes | bytearray | ParameterValueDict, None]:
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 not None:
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-cs file for a communication
6
- # parameter subset.
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
- <AUDIENCE {{-make_bool_xml_attrib("IS-SUPPLIER", audience.is_supplier_raw)}}
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
- </AUDIENCE>
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.value}}</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 %}