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.
Files changed (74) 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 +40 -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 +9 -14
  15. odxtools/diagservice.py +10 -10
  16. odxtools/ecuconfig.py +89 -0
  17. odxtools/encryptcompressmethod.py +16 -3
  18. odxtools/externflashdata.py +21 -2
  19. odxtools/flashdata.py +40 -2
  20. odxtools/identvalue.py +16 -3
  21. odxtools/internflashdata.py +4 -0
  22. odxtools/isotp_state_machine.py +11 -11
  23. odxtools/itemvalue.py +77 -0
  24. odxtools/multipleecujob.py +178 -0
  25. odxtools/multipleecujobspec.py +142 -0
  26. odxtools/nameditemlist.py +2 -2
  27. odxtools/optionitem.py +79 -0
  28. odxtools/outputparam.py +1 -1
  29. odxtools/parameters/codedconstparameter.py +2 -3
  30. odxtools/readdiagcommconnector.py +79 -0
  31. odxtools/readparamvalue.py +52 -0
  32. odxtools/request.py +3 -3
  33. odxtools/response.py +8 -4
  34. odxtools/snrefcontext.py +2 -0
  35. odxtools/statemachine.py +3 -2
  36. odxtools/systemitem.py +23 -0
  37. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -0
  38. odxtools/templates/ecu_config.odx-e.xml.jinja2 +38 -0
  39. odxtools/templates/flash.odx-f.xml.jinja2 +2 -2
  40. odxtools/templates/macros/printAdminData.xml.jinja2 +1 -1
  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/printDiagDataDictionarySpec.xml.jinja2 +107 -0
  52. odxtools/templates/macros/printDiagLayer.xml.jinja2 +11 -97
  53. odxtools/templates/macros/printElementId.xml.jinja2 +2 -0
  54. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +1 -1
  55. odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
  56. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -4
  57. odxtools/templates/macros/printItemValue.xml.jinja2 +31 -0
  58. odxtools/templates/macros/printMultipleEcuJob.xml.jinja2 +77 -0
  59. odxtools/templates/macros/printOwnIdent.xml.jinja2 +1 -1
  60. odxtools/templates/macros/printSecurity.xml.jinja2 +4 -4
  61. odxtools/templates/macros/printSegment.xml.jinja2 +1 -1
  62. odxtools/templates/multiple-ecu-job-spec.odx-m.xml.jinja2 +51 -0
  63. odxtools/validbasevariant.py +62 -0
  64. odxtools/validityfor.py +16 -3
  65. odxtools/variantmatcher.py +4 -4
  66. odxtools/version.py +2 -2
  67. odxtools/writediagcommconnector.py +77 -0
  68. odxtools/writepdxfile.py +58 -27
  69. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/METADATA +2 -1
  70. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/RECORD +74 -45
  71. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/WHEEL +1 -1
  72. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/entry_points.txt +0 -0
  73. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/licenses/LICENSE +0 -0
  74. {odxtools-10.2.1.dist-info → odxtools-10.4.0.dist-info}/top_level.txt +0 -0
odxtools/cli/browse.py CHANGED
@@ -255,8 +255,10 @@ def encode_message_from_string_values(
255
255
  print(f"The value specified for parameter {inner_param_sn} is not a string")
256
256
  continue
257
257
 
258
- typed_dict[inner_param_sn] = _convert_string_to_odx_type(
259
- inner_param_value, inner_param.physical_type.base_data_type)
258
+ if isinstance(inner_param,
259
+ ParameterWithDOP) and inner_param.physical_type is not None:
260
+ typed_dict[inner_param_sn] = _convert_string_to_odx_type(
261
+ inner_param_value, inner_param.physical_type.base_data_type)
260
262
  parameter_values[parameter.short_name] = typed_dict
261
263
  else:
262
264
  if not isinstance(parameter_value, str):
odxtools/cli/compare.py CHANGED
@@ -425,10 +425,10 @@ class Comparison(Display):
425
425
  changed_parameters_of_service=services_with_param_changes)
426
426
  dl1_service_names = [service.short_name for service in dl1.services]
427
427
 
428
- dl1_request_prefixes: list[bytes | None] = [
428
+ dl1_request_prefixes: list[bytes | bytearray | None] = [
429
429
  None if s.request is None else s.request.coded_const_prefix() for s in dl1.services
430
430
  ]
431
- dl2_request_prefixes: list[bytes | None] = [
431
+ dl2_request_prefixes: list[bytes | bytearray | None] = [
432
432
  None if s.request is None else s.request.coded_const_prefix() for s in dl2.services
433
433
  ]
434
434
 
@@ -436,7 +436,7 @@ class Comparison(Display):
436
436
  for service1 in dl1.services:
437
437
 
438
438
  # check for added diagnostic services
439
- rq_prefix: bytes
439
+ rq_prefix: bytes | bytearray
440
440
  if service1.request is not None:
441
441
  rq_prefix = service1.request.coded_const_prefix()
442
442
 
@@ -79,7 +79,7 @@ def composite_codec_get_free_parameters(codec: CompositeCodec) -> list[Parameter
79
79
 
80
80
 
81
81
  def composite_codec_get_coded_const_prefix(codec: CompositeCodec,
82
- request_prefix: bytes = b'') -> bytes:
82
+ request_prefix: bytes = b'') -> bytearray:
83
83
  encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
84
84
 
85
85
  for param in codec.parameters:
odxtools/configdata.py ADDED
@@ -0,0 +1,70 @@
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 .configrecord import ConfigRecord
7
+ from .element import NamedElement
8
+ from .nameditemlist import NamedItemList
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId
11
+ from .snrefcontext import SnRefContext
12
+ from .specialdatagroup import SpecialDataGroup
13
+ from .utils import dataclass_fields_asdict
14
+ from .validbasevariant import ValidBaseVariant
15
+
16
+
17
+ @dataclass(kw_only=True)
18
+ class ConfigData(NamedElement):
19
+ """This class represents a CONFIG-DATA."""
20
+ valid_base_variants: list[ValidBaseVariant]
21
+ config_records: NamedItemList[ConfigRecord]
22
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
23
+
24
+ @staticmethod
25
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigData":
26
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
27
+
28
+ valid_base_variants = [
29
+ ValidBaseVariant.from_et(vbv_elem, context)
30
+ for vbv_elem in et_element.iterfind("VALID-BASE-VARIANTS/VALID-BASE-VARIANT")
31
+ ]
32
+ config_records = NamedItemList([
33
+ ConfigRecord.from_et(cr_elem, context)
34
+ for cr_elem in et_element.iterfind("CONFIG-RECORDS/CONFIG-RECORD")
35
+ ])
36
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
37
+
38
+ return ConfigData(
39
+ valid_base_variants=valid_base_variants,
40
+ config_records=config_records,
41
+ sdgs=sdgs,
42
+ **kwargs)
43
+
44
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
45
+ result = {}
46
+
47
+ for valid_base_variant in self.valid_base_variants:
48
+ result.update(valid_base_variant._build_odxlinks())
49
+ for config_record in self.config_records:
50
+ result.update(config_record._build_odxlinks())
51
+ for sdg in self.sdgs:
52
+ result.update(sdg._build_odxlinks())
53
+
54
+ return result
55
+
56
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
57
+ for valid_base_variant in self.valid_base_variants:
58
+ valid_base_variant._resolve_odxlinks(odxlinks)
59
+ for config_record in self.config_records:
60
+ config_record._resolve_odxlinks(odxlinks)
61
+ for sdg in self.sdgs:
62
+ sdg._resolve_odxlinks(odxlinks)
63
+
64
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
65
+ for valid_base_variant in self.valid_base_variants:
66
+ valid_base_variant._resolve_snrefs(context)
67
+ for config_record in self.config_records:
68
+ config_record._resolve_snrefs(context)
69
+ for sdg in self.sdgs:
70
+ sdg._resolve_snrefs(context)
@@ -0,0 +1,57 @@
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 .dataobjectproperty import DataObjectProperty
7
+ from .nameditemlist import NamedItemList
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+ from .unitspec import UnitSpec
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class ConfigDataDictionarySpec:
16
+ data_object_props: NamedItemList[DataObjectProperty] = field(default_factory=NamedItemList)
17
+ unit_spec: UnitSpec | None = None
18
+
19
+ @staticmethod
20
+ def from_et(et_element: ElementTree.Element,
21
+ context: OdxDocContext) -> "ConfigDataDictionarySpec":
22
+ data_object_props = NamedItemList([
23
+ DataObjectProperty.from_et(dop_element, context)
24
+ for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
25
+ ])
26
+
27
+ if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
28
+ unit_spec = UnitSpec.from_et(spec_elem, context)
29
+ else:
30
+ unit_spec = None
31
+
32
+ return ConfigDataDictionarySpec(
33
+ data_object_props=data_object_props,
34
+ unit_spec=unit_spec,
35
+ )
36
+
37
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
38
+ odxlinks = {}
39
+
40
+ for data_object_prop in self.data_object_props:
41
+ odxlinks.update(data_object_prop._build_odxlinks())
42
+ if self.unit_spec is not None:
43
+ odxlinks.update(self.unit_spec._build_odxlinks())
44
+
45
+ return odxlinks
46
+
47
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
48
+ for data_object_prop in self.data_object_props:
49
+ data_object_prop._resolve_odxlinks(odxlinks)
50
+ if self.unit_spec is not None:
51
+ self.unit_spec._resolve_odxlinks(odxlinks)
52
+
53
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
54
+ for data_object_prop in self.data_object_props:
55
+ data_object_prop._resolve_snrefs(context)
56
+ if self.unit_spec is not None:
57
+ self.unit_spec._resolve_snrefs(context)
@@ -0,0 +1,18 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .configitem import ConfigItem
6
+ from .odxdoccontext import OdxDocContext
7
+ from .utils import dataclass_fields_asdict
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class ConfigIdItem(ConfigItem):
12
+ """This class represents a CONFIG-ID-ITEM."""
13
+
14
+ @staticmethod
15
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigIdItem":
16
+ kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
17
+
18
+ return ConfigIdItem(**kwargs)
odxtools/configitem.py ADDED
@@ -0,0 +1,85 @@
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 .dopbase import DopBase
7
+ from .element import NamedElement
8
+ from .exceptions import odxrequire
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
11
+ from .snrefcontext import SnRefContext
12
+ from .specialdatagroup import SpecialDataGroup
13
+ from .utils import dataclass_fields_asdict
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class ConfigItem(NamedElement):
18
+ """This class represents a CONFIG-ITEM.
19
+
20
+ CONFIG-ITEM is the base class for CONFIG-ID-ITEM, DATA-ID-ITEM,
21
+ OPTION-ITEM, and SYSTEM-ITEM.
22
+ """
23
+ byte_position: int | None = None
24
+ bit_position: int | None = None
25
+
26
+ # according to the spec exactly one of the following two
27
+ # attributes is not None...x
28
+ data_object_prop_ref: OdxLinkRef | None = None
29
+ data_object_prop_snref: str | None = None
30
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
31
+
32
+ @property
33
+ def data_object_prop(self) -> DopBase:
34
+ return self._data_object_prop
35
+
36
+ @staticmethod
37
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigItem":
38
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
39
+
40
+ byte_position = None
41
+ if (byte_pos_elem := et_element.findtext("BYTE-POSITION")) is not None:
42
+ byte_position = int(byte_pos_elem)
43
+
44
+ bit_position = None
45
+ if (bit_pos_elem := et_element.findtext("BIT-POSITION")) is not None:
46
+ bit_position = int(bit_pos_elem)
47
+
48
+ data_object_prop_ref = OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), context)
49
+ data_object_prop_snref = None
50
+ if (data_object_prop_snref_elem := et_element.find("DATA-OBJECT-PROP-SNREF")) is not None:
51
+ data_object_prop_snref = odxrequire(
52
+ data_object_prop_snref_elem.attrib.get("SHORT-NAME"))
53
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
54
+
55
+ return ConfigItem(
56
+ byte_position=byte_position,
57
+ bit_position=bit_position,
58
+ data_object_prop_ref=data_object_prop_ref,
59
+ data_object_prop_snref=data_object_prop_snref,
60
+ sdgs=sdgs,
61
+ **kwargs)
62
+
63
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
64
+ result = {}
65
+
66
+ for sdg in self.sdgs:
67
+ result.update(sdg._build_odxlinks())
68
+
69
+ return result
70
+
71
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
72
+ if self.data_object_prop_ref is not None:
73
+ self._data_object_prop = odxlinks.resolve(self.data_object_prop_ref, DopBase)
74
+
75
+ for sdg in self.sdgs:
76
+ sdg._resolve_odxlinks(odxlinks)
77
+
78
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
79
+ if self.data_object_prop_snref is not None:
80
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
81
+ self._data_object_prop = resolve_snref(self.data_object_prop_snref,
82
+ ddds.all_data_object_properties, DopBase)
83
+
84
+ for sdg in self.sdgs:
85
+ sdg._resolve_snrefs(context)
@@ -0,0 +1,146 @@
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 .configiditem import ConfigIdItem
8
+ from .dataiditem import DataIdItem
9
+ from .datarecord import DataRecord
10
+ from .diagcommdataconnector import DiagCommDataConnector
11
+ from .element import NamedElement
12
+ from .exceptions import odxrequire
13
+ from .identvalue import IdentValue
14
+ from .nameditemlist import NamedItemList
15
+ from .odxdoccontext import OdxDocContext
16
+ from .odxlink import OdxLinkDatabase, OdxLinkId
17
+ from .optionitem import OptionItem
18
+ from .snrefcontext import SnRefContext
19
+ from .specialdatagroup import SpecialDataGroup
20
+ from .systemitem import SystemItem
21
+ from .utils import dataclass_fields_asdict
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class ConfigRecord(NamedElement):
26
+ """This class represents a CONFIG-RECORD."""
27
+ config_id_item: ConfigIdItem | None = None
28
+ diag_comm_data_connectors: list[DiagCommDataConnector] = field(default_factory=list)
29
+ config_id: IdentValue | None = None
30
+ data_records: NamedItemList[DataRecord] = field(default_factory=NamedItemList)
31
+ audience: Audience | None = None
32
+ system_items: NamedItemList[SystemItem] = field(default_factory=NamedItemList)
33
+ data_id_item: DataIdItem | None = None
34
+ option_items: NamedItemList[OptionItem] = field(default_factory=NamedItemList)
35
+ default_data_record_snref: str | None = None
36
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
37
+
38
+ @staticmethod
39
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigRecord":
40
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
41
+
42
+ config_id_item = None
43
+ if (cid_elem := et_element.find("CONFIG-ID-ITEM")) is not None:
44
+ config_id_item = ConfigIdItem.from_et(cid_elem, context)
45
+ diag_comm_data_connectors = [
46
+ DiagCommDataConnector.from_et(dcdc_elem, context) for dcdc_elem in et_element.iterfind(
47
+ "DIAG-COMM-DATA-CONNECTORS/DIAG-COMM-DATA-CONNECTOR")
48
+ ]
49
+ config_id = None
50
+ if (cid_elem := et_element.find("CONFIG-ID")) is not None:
51
+ config_id = IdentValue.from_et(cid_elem, context)
52
+ data_records = NamedItemList([
53
+ DataRecord.from_et(dr_elem, context)
54
+ for dr_elem in et_element.iterfind("DATA-RECORDS/DATA-RECORD")
55
+ ])
56
+ audience = None
57
+ if (aud_elem := et_element.find("AUDIENCE")) is not None:
58
+ audience = Audience.from_et(aud_elem, context)
59
+ system_items = NamedItemList([
60
+ SystemItem.from_et(si_elem, context)
61
+ for si_elem in et_element.iterfind("SYSTEM-ITEMS/SYSTEM-ITEM")
62
+ ])
63
+ data_id_item = None
64
+ if (dii_elem := et_element.find("DATA-ID-ITEM")) is not None:
65
+ data_id_item = DataIdItem.from_et(dii_elem, context)
66
+ option_items = NamedItemList([
67
+ OptionItem.from_et(si_elem, context)
68
+ for si_elem in et_element.iterfind("OPTION-ITEMS/OPTION-ITEM")
69
+ ])
70
+ default_data_record_snref = None
71
+ if (default_data_record_snref_elem :=
72
+ et_element.find("DEFAULT-DATA-RECORD-SNREF")) is not None:
73
+ default_data_record_snref = odxrequire(
74
+ default_data_record_snref_elem.attrib.get("SHORT-NAME"))
75
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
76
+
77
+ return ConfigRecord(
78
+ config_id_item=config_id_item,
79
+ diag_comm_data_connectors=diag_comm_data_connectors,
80
+ config_id=config_id,
81
+ data_records=data_records,
82
+ audience=audience,
83
+ system_items=system_items,
84
+ data_id_item=data_id_item,
85
+ option_items=option_items,
86
+ default_data_record_snref=default_data_record_snref,
87
+ sdgs=sdgs,
88
+ **kwargs)
89
+
90
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
91
+ result = {}
92
+
93
+ if self.config_id_item is not None:
94
+ result.update(self.config_id_item._build_odxlinks())
95
+ for diag_comm_data_connector in self.diag_comm_data_connectors:
96
+ result.update(diag_comm_data_connector._build_odxlinks())
97
+ for data_record in self.data_records:
98
+ result.update(data_record._build_odxlinks())
99
+ if self.audience is not None:
100
+ result.update(self.audience._build_odxlinks())
101
+ for system_item in self.system_items:
102
+ result.update(system_item._build_odxlinks())
103
+ if self.data_id_item is not None:
104
+ result.update(self.data_id_item._build_odxlinks())
105
+ for option_item in self.option_items:
106
+ result.update(option_item._build_odxlinks())
107
+ for sdg in self.sdgs:
108
+ result.update(sdg._build_odxlinks())
109
+
110
+ return result
111
+
112
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
113
+ if self.config_id_item is not None:
114
+ self.config_id_item._resolve_odxlinks(odxlinks)
115
+ for diag_comm_data_connector in self.diag_comm_data_connectors:
116
+ diag_comm_data_connector._resolve_odxlinks(odxlinks)
117
+ for data_record in self.data_records:
118
+ data_record._resolve_odxlinks(odxlinks)
119
+ if self.audience is not None:
120
+ self.audience._resolve_odxlinks(odxlinks)
121
+ for system_item in self.system_items:
122
+ system_item._resolve_odxlinks(odxlinks)
123
+ if self.data_id_item is not None:
124
+ self.data_id_item._resolve_odxlinks(odxlinks)
125
+ for option_item in self.option_items:
126
+ option_item._resolve_odxlinks(odxlinks)
127
+ for sdg in self.sdgs:
128
+ sdg._resolve_odxlinks(odxlinks)
129
+
130
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
131
+ if self.config_id_item is not None:
132
+ self.config_id_item._resolve_snrefs(context)
133
+ for diag_comm_data_connector in self.diag_comm_data_connectors:
134
+ diag_comm_data_connector._resolve_snrefs(context)
135
+ for data_record in self.data_records:
136
+ data_record._resolve_snrefs(context)
137
+ if self.audience is not None:
138
+ self.audience._resolve_snrefs(context)
139
+ for system_item in self.system_items:
140
+ system_item._resolve_snrefs(context)
141
+ if self.data_id_item is not None:
142
+ self.data_id_item._resolve_snrefs(context)
143
+ for option_item in self.option_items:
144
+ option_item._resolve_snrefs(context)
145
+ for sdg in self.sdgs:
146
+ sdg._resolve_snrefs(context)
odxtools/database.py CHANGED
@@ -18,8 +18,10 @@ from .diaglayers.ecushareddata import EcuSharedData
18
18
  from .diaglayers.ecuvariant import EcuVariant
19
19
  from .diaglayers.functionalgroup import FunctionalGroup
20
20
  from .diaglayers.protocol import Protocol
21
+ from .ecuconfig import EcuConfig
21
22
  from .exceptions import odxraise, odxrequire
22
23
  from .flash import Flash
24
+ from .multipleecujobspec import MultipleEcuJobSpec
23
25
  from .nameditemlist import NamedItemList
24
26
  from .odxdoccontext import OdxDocContext
25
27
  from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
@@ -40,7 +42,9 @@ class Database:
40
42
  self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
41
43
  self._comparam_subsets = NamedItemList[ComparamSubset]()
42
44
  self._comparam_specs = NamedItemList[ComparamSpec]()
45
+ self._ecu_configs = NamedItemList[EcuConfig]()
43
46
  self._flashs = NamedItemList[Flash]()
47
+ self._multiple_ecu_job_specs = NamedItemList[MultipleEcuJobSpec]()
44
48
  self._short_name = "odx_database"
45
49
 
46
50
  def add_pdx_file(self, pdx_file: Union[str, "PathLike[Any]", IO[bytes], ZipFile]) -> None:
@@ -114,9 +118,17 @@ class Database:
114
118
  self._comparam_subsets.append(ComparamSubset.from_et(category_et, context))
115
119
  else:
116
120
  self._comparam_specs.append(ComparamSpec.from_et(category_et, context))
121
+ elif category_tag == "ECU-CONFIG":
122
+ context = OdxDocContext(model_version,
123
+ (OdxDocFragment(category_sn, DocType.ECU_CONFIG),))
124
+ self._ecu_configs.append(EcuConfig.from_et(category_et, context))
117
125
  elif category_tag == "FLASH":
118
126
  context = OdxDocContext(model_version, (OdxDocFragment(category_sn, DocType.FLASH),))
119
127
  self._flashs.append(Flash.from_et(category_et, context))
128
+ elif category_tag == "MULTIPLE-ECU-JOB-SPEC":
129
+ context = OdxDocContext(model_version,
130
+ (OdxDocFragment(category_sn, DocType.MULTIPLE_ECU_JOB_SPEC),))
131
+ self._multiple_ecu_job_specs.append(MultipleEcuJobSpec.from_et(category_et, context))
120
132
 
121
133
  def refresh(self) -> None:
122
134
  # Create wrapper objects
@@ -148,9 +160,15 @@ class Database:
148
160
  for dlc in self.diag_layer_containers:
149
161
  dlc._resolve_odxlinks(self._odxlinks)
150
162
 
163
+ for ecu_config in self.ecu_configs:
164
+ ecu_config._resolve_odxlinks(self._odxlinks)
165
+
151
166
  for flash in self.flashs:
152
167
  flash._resolve_odxlinks(self._odxlinks)
153
168
 
169
+ for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
170
+ multiple_ecu_job_spec._resolve_odxlinks(self._odxlinks)
171
+
154
172
  # resolve short name references for containers which do not do
155
173
  # inheritance (we can call directly call _resolve_snrefs())
156
174
  context = SnRefContext()
@@ -163,8 +181,12 @@ class Database:
163
181
  spec._finalize_init(self, self._odxlinks)
164
182
  for dlc in self.diag_layer_containers:
165
183
  dlc._finalize_init(self, self._odxlinks)
184
+ for ecu_config in self.ecu_configs:
185
+ ecu_config._finalize_init(self, self._odxlinks)
166
186
  for flash in self.flashs:
167
187
  flash._finalize_init(self, self._odxlinks)
188
+ for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
189
+ multiple_ecu_job_spec._finalize_init(self, self._odxlinks)
168
190
 
169
191
  for subset in self.comparam_subsets:
170
192
  subset._resolve_snrefs(context)
@@ -172,8 +194,12 @@ class Database:
172
194
  spec._resolve_snrefs(context)
173
195
  for dlc in self.diag_layer_containers:
174
196
  dlc._resolve_snrefs(context)
197
+ for ecu_config in self.ecu_configs:
198
+ ecu_config._resolve_snrefs(context)
175
199
  for flash in self.flashs:
176
200
  flash._resolve_snrefs(context)
201
+ for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
202
+ multiple_ecu_job_spec._resolve_snrefs(context)
177
203
 
178
204
  def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
179
205
  result: dict[OdxLinkId, Any] = {}
@@ -187,8 +213,13 @@ class Database:
187
213
  for dlc in self.diag_layer_containers:
188
214
  result.update(dlc._build_odxlinks())
189
215
 
216
+ for ecu_config in self.ecu_configs:
217
+ result.update(ecu_config._build_odxlinks())
218
+
190
219
  for flash in self.flashs:
191
220
  result.update(flash._build_odxlinks())
221
+ for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
222
+ result.update(multiple_ecu_job_spec._build_odxlinks())
192
223
 
193
224
  return result
194
225
 
@@ -263,10 +294,18 @@ class Database:
263
294
  def comparam_specs(self) -> NamedItemList[ComparamSpec]:
264
295
  return self._comparam_specs
265
296
 
297
+ @property
298
+ def ecu_configs(self) -> NamedItemList[EcuConfig]:
299
+ return self._ecu_configs
300
+
266
301
  @property
267
302
  def flashs(self) -> NamedItemList[Flash]:
268
303
  return self._flashs
269
304
 
305
+ @property
306
+ def multiple_ecu_job_specs(self) -> NamedItemList[MultipleEcuJobSpec]:
307
+ return self._multiple_ecu_job_specs
308
+
270
309
  def __repr__(self) -> str:
271
310
  return f"Database(model_version={self.model_version}, " \
272
311
  f"protocols={[x.short_name for x in self.protocols]}, " \
@@ -274,4 +313,5 @@ class Database:
274
313
  f"diag_layer_containers={repr(self.diag_layer_containers)}, " \
275
314
  f"comparam_subsets={repr(self.comparam_subsets)}, " \
276
315
  f"comparam_specs={repr(self.comparam_specs)}, " \
316
+ f"ecu_configs={repr(self.ecu_configs)}, " \
277
317
  f"flashs={repr(self.flashs)})"
odxtools/dataiditem.py ADDED
@@ -0,0 +1,18 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .configitem import ConfigItem
6
+ from .odxdoccontext import OdxDocContext
7
+ from .utils import dataclass_fields_asdict
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class DataIdItem(ConfigItem):
12
+ """This class represents a DATA-ID-ITEM."""
13
+
14
+ @staticmethod
15
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DataIdItem":
16
+ kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
17
+
18
+ return DataIdItem(**kwargs)