odxtools 10.1.1__py3-none-any.whl → 10.2.1__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 (112) hide show
  1. odxtools/addrdeffilter.py +33 -0
  2. odxtools/addrdefphyssegment.py +33 -0
  3. odxtools/checksum.py +67 -0
  4. odxtools/checksumresult.py +7 -0
  5. odxtools/database.py +24 -4
  6. odxtools/datablock.py +153 -0
  7. odxtools/datafile.py +23 -0
  8. odxtools/dataformat.py +39 -0
  9. odxtools/dataformatselection.py +9 -0
  10. odxtools/description.py +2 -5
  11. odxtools/diagdatadictionaryspec.py +1 -3
  12. odxtools/diaglayers/diaglayer.py +31 -11
  13. odxtools/diaglayers/ecuvariant.py +12 -19
  14. odxtools/diaglayers/hierarchyelement.py +5 -5
  15. odxtools/diaglayers/protocol.py +14 -0
  16. odxtools/direction.py +7 -0
  17. odxtools/ecumem.py +71 -0
  18. odxtools/ecumemconnector.py +136 -0
  19. odxtools/encryptcompressmethod.py +39 -0
  20. odxtools/encryptcompressmethodtype.py +13 -0
  21. odxtools/expectedident.py +40 -0
  22. odxtools/externflashdata.py +34 -0
  23. odxtools/filter.py +32 -0
  24. odxtools/flash.py +88 -0
  25. odxtools/flashclass.py +32 -0
  26. odxtools/flashdata.py +70 -0
  27. odxtools/fwchecksum.py +7 -0
  28. odxtools/fwsignature.py +7 -0
  29. odxtools/identdesc.py +54 -0
  30. odxtools/identvalue.py +32 -0
  31. odxtools/identvaluetype.py +14 -0
  32. odxtools/internflashdata.py +33 -0
  33. odxtools/loadfile.py +1 -1
  34. odxtools/mem.py +80 -0
  35. odxtools/modification.py +3 -2
  36. odxtools/negoffset.py +21 -0
  37. odxtools/odxlink.py +4 -2
  38. odxtools/ownident.py +38 -0
  39. odxtools/physicaltype.py +12 -10
  40. odxtools/physmem.py +52 -0
  41. odxtools/physsegment.py +42 -0
  42. odxtools/posoffset.py +21 -0
  43. odxtools/security.py +42 -0
  44. odxtools/securitymethod.py +7 -0
  45. odxtools/segment.py +63 -0
  46. odxtools/session.py +88 -0
  47. odxtools/sessiondesc.py +101 -0
  48. odxtools/sessionsubelemtype.py +14 -0
  49. odxtools/sizedeffilter.py +33 -0
  50. odxtools/sizedefphyssegment.py +33 -0
  51. odxtools/specialdata.py +2 -1
  52. odxtools/subcomponentparamconnector.py +1 -1
  53. odxtools/targetaddroffset.py +13 -0
  54. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +1 -0
  55. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +1 -0
  56. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +2 -1
  57. odxtools/templates/flash.odx-f.xml.jinja2 +42 -0
  58. odxtools/templates/macros/printAdminData.xml.jinja2 +4 -4
  59. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  60. odxtools/templates/macros/printChecksum.xml.jinja2 +36 -0
  61. odxtools/templates/macros/printComparam.xml.jinja2 +1 -1
  62. odxtools/templates/macros/printComparamRef.xml.jinja2 +1 -3
  63. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
  64. odxtools/templates/macros/printDOP.xml.jinja2 +3 -3
  65. odxtools/templates/macros/printDatablock.xml.jinja2 +78 -0
  66. odxtools/templates/macros/printDiagComm.xml.jinja2 +2 -2
  67. odxtools/templates/macros/printDiagLayer.xml.jinja2 +2 -1
  68. odxtools/templates/macros/printDiagVariable.xml.jinja2 +4 -4
  69. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +3 -3
  70. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +2 -2
  71. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +2 -2
  72. odxtools/templates/macros/printEcuMem.xml.jinja2 +24 -0
  73. odxtools/templates/macros/printEcuMemConnector.xml.jinja2 +58 -0
  74. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  75. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  76. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +21 -0
  77. odxtools/templates/macros/printFlashdata.xml.jinja2 +43 -0
  78. odxtools/templates/macros/printIdentDesc.xml.jinja2 +17 -0
  79. odxtools/templates/macros/printMem.xml.jinja2 +35 -0
  80. odxtools/templates/macros/printMux.xml.jinja2 +3 -3
  81. odxtools/templates/macros/printOdxCategory.xml.jinja2 +4 -4
  82. odxtools/templates/macros/printOwnIdent.xml.jinja2 +17 -0
  83. odxtools/templates/macros/printParam.xml.jinja2 +4 -4
  84. odxtools/templates/macros/printParentRef.xml.jinja2 +1 -5
  85. odxtools/templates/macros/printPhysMem.xml.jinja2 +20 -0
  86. odxtools/templates/macros/printPhysSegment.xml.jinja2 +33 -0
  87. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +1 -1
  88. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  89. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  90. odxtools/templates/macros/printSecurity.xml.jinja2 +37 -0
  91. odxtools/templates/macros/printSegment.xml.jinja2 +31 -0
  92. odxtools/templates/macros/printService.xml.jinja2 +3 -3
  93. odxtools/templates/macros/printSession.xml.jinja2 +45 -0
  94. odxtools/templates/macros/printSessionDesc.xml.jinja2 +40 -0
  95. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +3 -3
  96. odxtools/templates/macros/printSpecialData.xml.jinja2 +2 -2
  97. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +1 -1
  98. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  99. odxtools/templates/macros/printSubComponent.xml.jinja2 +3 -3
  100. odxtools/templates/macros/printTable.xml.jinja2 +6 -6
  101. odxtools/templates/macros/printUnitSpec.xml.jinja2 +2 -2
  102. odxtools/text.py +2 -6
  103. odxtools/utils.py +22 -1
  104. odxtools/validityfor.py +30 -0
  105. odxtools/version.py +2 -2
  106. odxtools/writepdxfile.py +70 -21
  107. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/METADATA +1 -1
  108. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/RECORD +112 -55
  109. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/WHEEL +1 -1
  110. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/entry_points.txt +0 -0
  111. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/licenses/LICENSE +0 -0
  112. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
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 .filter import Filter
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict, read_hex_binary
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class AddrdefFilter(Filter):
16
+ filter_end: int
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "AddrdefFilter":
20
+ kwargs = dataclass_fields_asdict(Filter.from_et(et_element, context))
21
+
22
+ filter_end = odxrequire(read_hex_binary(et_element.find("FILTER-END")))
23
+
24
+ return AddrdefFilter(filter_end=filter_end, **kwargs)
25
+
26
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
27
+ return super()._build_odxlinks()
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ super()._resolve_odxlinks(odxlinks)
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ super()._resolve_snrefs(context)
@@ -0,0 +1,33 @@
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 .physsegment import PhysSegment
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict, read_hex_binary
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class AddrdefPhysSegment(PhysSegment):
16
+ end_address: int
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "AddrdefPhysSegment":
20
+ kwargs = dataclass_fields_asdict(PhysSegment.from_et(et_element, context))
21
+
22
+ end_address = odxrequire(read_hex_binary(et_element.find("END-ADDRESS")))
23
+
24
+ return AddrdefPhysSegment(end_address=end_address, **kwargs)
25
+
26
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
27
+ return super()._build_odxlinks()
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ super()._resolve_odxlinks(odxlinks)
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ super()._resolve_snrefs(context)
odxtools/checksum.py ADDED
@@ -0,0 +1,67 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .checksumresult import ChecksumResult
7
+ from .element import IdentifiableElement
8
+ from .exceptions import odxrequire
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId
11
+ from .snrefcontext import SnRefContext
12
+ from .utils import dataclass_fields_asdict, read_hex_binary
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class Checksum(IdentifiableElement):
17
+ fillbyte: int | None = None
18
+ source_start_address: int
19
+ compressed_size: int | None = None
20
+ checksum_alg: str | None = None
21
+
22
+ # exactly one of the two next fields must be not None
23
+ source_end_address: int | None = None
24
+ uncompressed_size: int | None = None
25
+
26
+ checksum_result: ChecksumResult
27
+
28
+ @staticmethod
29
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Checksum":
30
+
31
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
32
+
33
+ fillbyte = read_hex_binary(et_element.find("FILLBYTE"))
34
+ source_start_address = odxrequire(read_hex_binary(et_element.find("SOURCE-START-ADDRESS")))
35
+ compressed_size = None
36
+ if (cs_elem := et_element.find("COMPRESSED-SIZE")) is not None:
37
+ compressed_size = int(odxrequire(cs_elem.text) or "0")
38
+ checksum_alg = et_element.findtext("CHECKSUM-ALG")
39
+
40
+ # exactly one of the two next fields must be not None
41
+ source_end_address = read_hex_binary(et_element.find("SOURCE-END-ADDRESS"))
42
+ uncompressed_size = None
43
+ if (ucs_elem := et_element.find("UNCOMPRESSED-SIZE")) is not None:
44
+ uncompressed_size = int(odxrequire(ucs_elem.text) or "0")
45
+ checksum_result = ChecksumResult.from_et(
46
+ odxrequire(et_element.find("CHECKSUM-RESULT")), context)
47
+
48
+ return Checksum(
49
+ fillbyte=fillbyte,
50
+ source_start_address=source_start_address,
51
+ compressed_size=compressed_size,
52
+ checksum_alg=checksum_alg,
53
+ source_end_address=source_end_address,
54
+ uncompressed_size=uncompressed_size,
55
+ checksum_result=checksum_result,
56
+ **kwargs)
57
+
58
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
59
+ odxlinks = {self.odx_id: self}
60
+
61
+ return odxlinks
62
+
63
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
64
+ pass
65
+
66
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
67
+ pass
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from .validityfor import ValidityFor
3
+
4
+ # Note that the ODX specification specifies a separate tag for this,
5
+ # but this tag is identical to VALIDITY-FOR, so let's use a type alias
6
+ # to reduce the amount of copy-and-pasted code
7
+ ChecksumResult = ValidityFor
odxtools/database.py CHANGED
@@ -19,6 +19,7 @@ from .diaglayers.ecuvariant import EcuVariant
19
19
  from .diaglayers.functionalgroup import FunctionalGroup
20
20
  from .diaglayers.protocol import Protocol
21
21
  from .exceptions import odxraise, odxrequire
22
+ from .flash import Flash
22
23
  from .nameditemlist import NamedItemList
23
24
  from .odxdoccontext import OdxDocContext
24
25
  from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
@@ -39,6 +40,7 @@ class Database:
39
40
  self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
40
41
  self._comparam_subsets = NamedItemList[ComparamSubset]()
41
42
  self._comparam_specs = NamedItemList[ComparamSpec]()
43
+ self._flashs = NamedItemList[Flash]()
42
44
  self._short_name = "odx_database"
43
45
 
44
46
  def add_pdx_file(self, pdx_file: Union[str, "PathLike[Any]", IO[bytes], ZipFile]) -> None:
@@ -57,7 +59,7 @@ class Database:
57
59
  p = Path(zip_member)
58
60
  if p.suffix.lower().startswith(".odx"):
59
61
  root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
60
- self._process_xml_tree(root)
62
+ self.add_xml_tree(root)
61
63
  elif p.name.lower() == "index.xml":
62
64
  root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
63
65
  db_short_name = odxrequire(root.findtext("SHORT-NAME"))
@@ -66,7 +68,7 @@ class Database:
66
68
  self.add_auxiliary_file(zip_member, pdx_zip.open(zip_member))
67
69
 
68
70
  def add_odx_file(self, odx_file_name: Union[str, "PathLike[Any]"]) -> None:
69
- self._process_xml_tree(ElementTree.parse(odx_file_name).getroot())
71
+ self.add_xml_tree(ElementTree.parse(odx_file_name).getroot())
70
72
 
71
73
  def add_auxiliary_file(self,
72
74
  aux_file_name: Union[str, "PathLike[Any]"],
@@ -76,7 +78,7 @@ class Database:
76
78
 
77
79
  self.auxiliary_files[str(aux_file_name)] = aux_file_obj
78
80
 
79
- def _process_xml_tree(self, root: ElementTree.Element) -> None:
81
+ def add_xml_tree(self, root: ElementTree.Element) -> None:
80
82
  # ODX spec version
81
83
  model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
82
84
  if self.model_version is not None and self.model_version != model_version:
@@ -112,6 +114,9 @@ class Database:
112
114
  self._comparam_subsets.append(ComparamSubset.from_et(category_et, context))
113
115
  else:
114
116
  self._comparam_specs.append(ComparamSpec.from_et(category_et, context))
117
+ elif category_tag == "FLASH":
118
+ context = OdxDocContext(model_version, (OdxDocFragment(category_sn, DocType.FLASH),))
119
+ self._flashs.append(Flash.from_et(category_et, context))
115
120
 
116
121
  def refresh(self) -> None:
117
122
  # Create wrapper objects
@@ -143,6 +148,9 @@ class Database:
143
148
  for dlc in self.diag_layer_containers:
144
149
  dlc._resolve_odxlinks(self._odxlinks)
145
150
 
151
+ for flash in self.flashs:
152
+ flash._resolve_odxlinks(self._odxlinks)
153
+
146
154
  # resolve short name references for containers which do not do
147
155
  # inheritance (we can call directly call _resolve_snrefs())
148
156
  context = SnRefContext()
@@ -155,6 +163,8 @@ class Database:
155
163
  spec._finalize_init(self, self._odxlinks)
156
164
  for dlc in self.diag_layer_containers:
157
165
  dlc._finalize_init(self, self._odxlinks)
166
+ for flash in self.flashs:
167
+ flash._finalize_init(self, self._odxlinks)
158
168
 
159
169
  for subset in self.comparam_subsets:
160
170
  subset._resolve_snrefs(context)
@@ -162,6 +172,8 @@ class Database:
162
172
  spec._resolve_snrefs(context)
163
173
  for dlc in self.diag_layer_containers:
164
174
  dlc._resolve_snrefs(context)
175
+ for flash in self.flashs:
176
+ flash._resolve_snrefs(context)
165
177
 
166
178
  def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
167
179
  result: dict[OdxLinkId, Any] = {}
@@ -175,6 +187,9 @@ class Database:
175
187
  for dlc in self.diag_layer_containers:
176
188
  result.update(dlc._build_odxlinks())
177
189
 
190
+ for flash in self.flashs:
191
+ result.update(flash._build_odxlinks())
192
+
178
193
  return result
179
194
 
180
195
  @property
@@ -248,10 +263,15 @@ class Database:
248
263
  def comparam_specs(self) -> NamedItemList[ComparamSpec]:
249
264
  return self._comparam_specs
250
265
 
266
+ @property
267
+ def flashs(self) -> NamedItemList[Flash]:
268
+ return self._flashs
269
+
251
270
  def __repr__(self) -> str:
252
271
  return f"Database(model_version={self.model_version}, " \
253
272
  f"protocols={[x.short_name for x in self.protocols]}, " \
254
273
  f"ecus={[x.short_name for x in self.ecus]}, " \
255
274
  f"diag_layer_containers={repr(self.diag_layer_containers)}, " \
256
275
  f"comparam_subsets={repr(self.comparam_subsets)}, " \
257
- f"comparam_specs={repr(self.comparam_specs)})"
276
+ f"comparam_specs={repr(self.comparam_specs)}, " \
277
+ f"flashs={repr(self.flashs)})"
odxtools/datablock.py ADDED
@@ -0,0 +1,153 @@
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 .addrdeffilter import AddrdefFilter
7
+ from .audience import Audience
8
+ from .element import IdentifiableElement
9
+ from .exceptions import odxraise, odxrequire
10
+ from .filter import Filter
11
+ from .flashdata import Flashdata
12
+ from .globals import xsi
13
+ from .nameditemlist import NamedItemList
14
+ from .negoffset import NegOffset
15
+ from .odxdoccontext import OdxDocContext
16
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
17
+ from .ownident import OwnIdent
18
+ from .posoffset import PosOffset
19
+ from .security import Security
20
+ from .segment import Segment
21
+ from .sizedeffilter import SizedefFilter
22
+ from .snrefcontext import SnRefContext
23
+ from .specialdatagroup import SpecialDataGroup
24
+ from .targetaddroffset import TargetAddrOffset
25
+ from .utils import dataclass_fields_asdict, read_hex_binary
26
+
27
+
28
+ @dataclass(kw_only=True)
29
+ class Datablock(IdentifiableElement):
30
+ logical_block_index: int | None = None
31
+ flashdata_ref: OdxLinkRef | None = None
32
+ filters: list[Filter] = field(default_factory=list)
33
+ segments: NamedItemList[Segment] = field(default_factory=NamedItemList)
34
+
35
+ # the specification does not define the content of
36
+ # TARGET-ADDR-OFFSET, i.e., if it is defined, it must be one of
37
+ # its specializations
38
+ target_addr_offset: TargetAddrOffset | None = None
39
+
40
+ own_idents: NamedItemList[OwnIdent] = field(default_factory=NamedItemList)
41
+ securities: list[Security] = field(default_factory=list)
42
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
43
+ audience: Audience | None = None
44
+
45
+ # note that the spec says this attribute is named "TYPE", but in
46
+ # python, "type" is a build-in function...
47
+ data_type: str
48
+
49
+ @property
50
+ def flashdata(self) -> Flashdata | None:
51
+ return self._flashdata
52
+
53
+ @staticmethod
54
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Datablock":
55
+
56
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
57
+
58
+ logical_block_index = read_hex_binary(et_element.find("LOGICAL-BLOCK-INDEX"))
59
+ flashdata_ref = OdxLinkRef.from_et(et_element.find("FLASHDATA-REF"), context)
60
+ filters: list[Filter] = []
61
+ for filter_elem in et_element.iterfind("FILTERS/FILTER"):
62
+ filter_type = filter_elem.attrib.get(f"{xsi}type")
63
+ if filter_type == "ADDRDEF-FILTER":
64
+ filters.append(AddrdefFilter.from_et(filter_elem, context))
65
+ elif filter_type == "SIZEDEF-FILTER":
66
+ filters.append(SizedefFilter.from_et(filter_elem, context))
67
+ else:
68
+ odxraise(f"Encountered filter of illegal type {filter_type}")
69
+ filters.append(Filter.from_et(filter_elem, context))
70
+ segments = NamedItemList([
71
+ Segment.from_et(segment_elem, context)
72
+ for segment_elem in et_element.iterfind("SEGMENTS/SEGMENT")
73
+ ])
74
+ target_addr_offset: TargetAddrOffset | None = None
75
+ if (tao_elem := et_element.find("TARGET-ADDR-OFFSET")) is not None:
76
+ tao_type = tao_elem.attrib.get(f"{xsi}type")
77
+ if tao_type == "POS-OFFSET":
78
+ target_addr_offset = PosOffset.from_et(tao_elem, context)
79
+ elif tao_type == "NEG-OFFSET":
80
+ target_addr_offset = NegOffset.from_et(tao_elem, context)
81
+ else:
82
+ odxraise(f"Unknown TARGET-ADDR-OFFSET type '{tao_type}'")
83
+
84
+ own_idents = NamedItemList([
85
+ OwnIdent.from_et(own_ident_elem, context)
86
+ for own_ident_elem in et_element.iterfind("OWN-IDENTS/OWN-IDENT")
87
+ ])
88
+ securities = [
89
+ Security.from_et(security_elem, context)
90
+ for security_elem in et_element.iterfind("SECURITYS/SECURITY")
91
+ ]
92
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
93
+ audience = None
94
+ if (audience_elem := et_element.find("AUDIENCE")) is not None:
95
+ audience = Audience.from_et(audience_elem, context)
96
+ data_type = odxrequire(et_element.attrib.get("TYPE"))
97
+
98
+ return Datablock(
99
+ logical_block_index=logical_block_index,
100
+ flashdata_ref=flashdata_ref,
101
+ filters=filters,
102
+ segments=segments,
103
+ own_idents=own_idents,
104
+ securities=securities,
105
+ target_addr_offset=target_addr_offset,
106
+ sdgs=sdgs,
107
+ audience=audience,
108
+ data_type=data_type,
109
+ **kwargs)
110
+
111
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
112
+ odxlinks = {self.odx_id: self}
113
+
114
+ for odxfilter in self.filters:
115
+ odxlinks.update(odxfilter._build_odxlinks())
116
+ for segment in self.segments:
117
+ odxlinks.update(segment._build_odxlinks())
118
+ for own_indent in self.own_idents:
119
+ odxlinks.update(own_indent._build_odxlinks())
120
+ for sdg in self.sdgs:
121
+ odxlinks.update(sdg._build_odxlinks())
122
+ if self.audience is not None:
123
+ odxlinks.update(self.audience._build_odxlinks())
124
+
125
+ return odxlinks
126
+
127
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
128
+ self._flashdata = None
129
+ if self.flashdata_ref is not None:
130
+ self._flashdata = odxlinks.resolve(self.flashdata_ref, Flashdata)
131
+
132
+ for odxfilter in self.filters:
133
+ odxfilter._resolve_odxlinks(odxlinks)
134
+ for segment in self.segments:
135
+ segment._resolve_odxlinks(odxlinks)
136
+ for own_indent in self.own_idents:
137
+ own_indent._resolve_odxlinks(odxlinks)
138
+ for sdg in self.sdgs:
139
+ sdg._resolve_odxlinks(odxlinks)
140
+ if self.audience is not None:
141
+ self.audience._resolve_odxlinks(odxlinks)
142
+
143
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
144
+ for odxfilter in self.filters:
145
+ odxfilter._resolve_snrefs(context)
146
+ for segment in self.segments:
147
+ segment._resolve_snrefs(context)
148
+ for own_indent in self.own_idents:
149
+ own_indent._resolve_snrefs(context)
150
+ for sdg in self.sdgs:
151
+ sdg._resolve_snrefs(context)
152
+ if self.audience is not None:
153
+ self.audience._resolve_snrefs(context)
odxtools/datafile.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 .exceptions import odxrequire
6
+ from .odxdoccontext import OdxDocContext
7
+ from .odxtypes import odxstr_to_bool
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class Datafile:
12
+ value: str
13
+ latebound_datafile: bool
14
+
15
+ @staticmethod
16
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Datafile":
17
+ value = et_element.text or ""
18
+ latebound_datafile = odxrequire(odxstr_to_bool(et_element.attrib.get("LATEBOUND-DATAFILE")))
19
+
20
+ return Datafile(
21
+ value=value,
22
+ latebound_datafile=latebound_datafile,
23
+ )
odxtools/dataformat.py ADDED
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, cast
4
+ from xml.etree import ElementTree
5
+
6
+ from .dataformatselection import DataformatSelection
7
+ from .exceptions import odxraise, odxrequire
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+
12
+
13
+ @dataclass(kw_only=True)
14
+ class Dataformat:
15
+ selection: DataformatSelection
16
+ user_selection: str | None = None
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Dataformat":
20
+ selection_str = odxrequire(et_element.attrib.get("SELECTION"))
21
+ try:
22
+ selection = DataformatSelection(selection_str)
23
+ except ValueError:
24
+ selection = cast(DataformatSelection, None)
25
+ odxraise(f"Encountered unknown data format selection '{selection_str}'")
26
+ user_selection = et_element.attrib.get("USER-SELECTION")
27
+
28
+ return Dataformat(selection=selection, user_selection=user_selection)
29
+
30
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
31
+ odxlinks: dict[OdxLinkId, Any] = {}
32
+
33
+ return odxlinks
34
+
35
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
36
+ pass
37
+
38
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
39
+ pass
@@ -0,0 +1,9 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from enum import Enum
3
+
4
+
5
+ class DataformatSelection(Enum):
6
+ INTEL_HEX = "INTEL-HEX"
7
+ MOTOROLA_S = "MOTOROLA-S"
8
+ BINARY = "BINARY"
9
+ USER_DEFINED = "USER-DEFINED"
odxtools/description.py CHANGED
@@ -5,6 +5,7 @@ from xml.etree import ElementTree
5
5
  from .exceptions import odxrequire
6
6
  from .externaldoc import ExternalDoc
7
7
  from .odxdoccontext import OdxDocContext
8
+ from .utils import strip_indent
8
9
 
9
10
 
10
11
  @dataclass(kw_only=True)
@@ -27,11 +28,7 @@ class Description:
27
28
  break
28
29
  raw_string += ElementTree.tostring(e, encoding="unicode")
29
30
 
30
- # remove white spaces at the beginning and at the end of all
31
- # extracted lines
32
- stripped_lines = [x.strip() for x in raw_string.split("\n")]
33
-
34
- text = "\n".join(stripped_lines).strip()
31
+ text = strip_indent(raw_string)
35
32
 
36
33
  external_docs = \
37
34
  [
@@ -46,9 +46,7 @@ class DiagDataDictionarySpec:
46
46
  @staticmethod
47
47
  def from_et(et_element: ElementTree.Element,
48
48
  context: OdxDocContext) -> "DiagDataDictionarySpec":
49
- admin_data = None
50
- if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
51
- admin_data = AdminData.from_et(admin_data_elem, context)
49
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), context)
52
50
 
53
51
  dtc_dops = NamedItemList([
54
52
  DtcDop.from_et(dtc_dop_elem, context)
@@ -7,6 +7,7 @@ from itertools import chain
7
7
  from typing import Any, Union, cast
8
8
  from xml.etree import ElementTree
9
9
 
10
+ from ..additionalaudience import AdditionalAudience
10
11
  from ..admindata import AdminData
11
12
  from ..companydata import CompanyData
12
13
  from ..description import Description
@@ -14,6 +15,7 @@ from ..diagcomm import DiagComm
14
15
  from ..diagdatadictionaryspec import DiagDataDictionarySpec
15
16
  from ..diagservice import DiagService
16
17
  from ..exceptions import DecodeError, odxassert, odxraise
18
+ from ..functionalclass import FunctionalClass
17
19
  from ..library import Library
18
20
  from ..message import Message
19
21
  from ..nameditemlist import NamedItemList, TNamed
@@ -26,6 +28,7 @@ from ..servicebinner import ServiceBinner
26
28
  from ..singleecujob import SingleEcuJob
27
29
  from ..snrefcontext import SnRefContext
28
30
  from ..specialdatagroup import SpecialDataGroup
31
+ from ..statechart import StateChart
29
32
  from ..subcomponent import SubComponent
30
33
  from ..unitgroup import UnitGroup
31
34
  from .diaglayerraw import DiagLayerRaw
@@ -223,12 +226,30 @@ class DiagLayer:
223
226
  def admin_data(self) -> AdminData | None:
224
227
  return self.diag_layer_raw.admin_data
225
228
 
229
+ @property
230
+ def company_datas(self) -> NamedItemList[CompanyData]:
231
+ return self.diag_layer_raw.company_datas
232
+
233
+ @property
234
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
235
+ return self.diag_layer_raw.functional_classes
236
+
237
+ @property
238
+ def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
239
+ """The DiagDataDictionarySpec applicable to this DiagLayer"""
240
+ return self._diag_data_dictionary_spec
241
+
242
+ @property
243
+ def diag_comms_raw(self) -> list[OdxLinkRef | DiagComm]:
244
+ return self.diag_layer_raw.diag_comms_raw
245
+
226
246
  @property
227
247
  def diag_comms(self) -> NamedItemList[DiagComm]:
228
248
  return self.diag_layer_raw.diag_comms
229
249
 
230
250
  @property
231
251
  def services(self) -> NamedItemList[DiagService]:
252
+ """This is an alias for `.diag_services`"""
232
253
  return self.diag_layer_raw.services
233
254
 
234
255
  @property
@@ -239,10 +260,6 @@ class DiagLayer:
239
260
  def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
240
261
  return self.diag_layer_raw.single_ecu_jobs
241
262
 
242
- @property
243
- def company_datas(self) -> NamedItemList[CompanyData]:
244
- return self.diag_layer_raw.company_datas
245
-
246
263
  @property
247
264
  def requests(self) -> NamedItemList[Request]:
248
265
  return self.diag_layer_raw.requests
@@ -264,21 +281,24 @@ class DiagLayer:
264
281
  return self.diag_layer_raw.import_refs
265
282
 
266
283
  @property
267
- def libraries(self) -> NamedItemList[Library]:
268
- return self.diag_layer_raw.libraries
284
+ def state_charts(self) -> NamedItemList[StateChart]:
285
+ return self.diag_layer_raw.state_charts
286
+
287
+ @property
288
+ def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
289
+ return self.diag_layer_raw.additional_audiences
269
290
 
270
291
  @property
271
292
  def sub_components(self) -> NamedItemList[SubComponent]:
272
293
  return self.diag_layer_raw.sub_components
273
294
 
274
295
  @property
275
- def sdgs(self) -> list[SpecialDataGroup]:
276
- return self.diag_layer_raw.sdgs
296
+ def libraries(self) -> NamedItemList[Library]:
297
+ return self.diag_layer_raw.libraries
277
298
 
278
299
  @property
279
- def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
280
- """The DiagDataDictionarySpec applicable to this DiagLayer"""
281
- return self._diag_data_dictionary_spec
300
+ def sdgs(self) -> list[SpecialDataGroup]:
301
+ return self.diag_layer_raw.sdgs
282
302
 
283
303
  #####
284
304
  # </properties forwarded to the "raw" diag layer>
@@ -33,6 +33,18 @@ class EcuVariant(HierarchyElement):
33
33
  def diag_variables_raw(self) -> list[DiagVariable | OdxLinkRef]:
34
34
  return self.ecu_variant_raw.diag_variables_raw
35
35
 
36
+ @property
37
+ def diag_variables(self) -> NamedItemList[DiagVariable]:
38
+ return self._diag_variables
39
+
40
+ @property
41
+ def variable_groups(self) -> NamedItemList[VariableGroup]:
42
+ return self._variable_groups
43
+
44
+ @property
45
+ def ecu_variant_patterns(self) -> list[EcuVariantPattern]:
46
+ return self.ecu_variant_raw.ecu_variant_patterns
47
+
36
48
  @property
37
49
  def dyn_defined_spec(self) -> DynDefinedSpec | None:
38
50
  return self.ecu_variant_raw.dyn_defined_spec
@@ -56,25 +68,6 @@ class EcuVariant(HierarchyElement):
56
68
 
57
69
  return None
58
70
 
59
- @property
60
- def ecu_variant_patterns(self) -> list[EcuVariantPattern]:
61
- return self.ecu_variant_raw.ecu_variant_patterns
62
-
63
- #######
64
- # <properties subject to value inheritance>
65
- #######
66
- @property
67
- def diag_variables(self) -> NamedItemList[DiagVariable]:
68
- return self._diag_variables
69
-
70
- @property
71
- def variable_groups(self) -> NamedItemList[VariableGroup]:
72
- return self._variable_groups
73
-
74
- #######
75
- # </properties subject to value inheritance>
76
- #######
77
-
78
71
  @staticmethod
79
72
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "EcuVariant":
80
73
  ecu_variant_raw = EcuVariantRaw.from_et(et_element, context)
@@ -438,6 +438,11 @@ class HierarchyElement(DiagLayer):
438
438
  #######
439
439
  # <properties subject to value inheritance>
440
440
  #######
441
+ @property
442
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
443
+ """All functional classes applicable to this DiagLayer"""
444
+ return self._functional_classes
445
+
441
446
  @property
442
447
  def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
443
448
  return self._diag_data_dictionary_spec
@@ -480,11 +485,6 @@ class HierarchyElement(DiagLayer):
480
485
  """All global negative responses applicable to this DiagLayer"""
481
486
  return self._global_negative_responses
482
487
 
483
- @property
484
- def functional_classes(self) -> NamedItemList[FunctionalClass]:
485
- """All functional classes applicable to this DiagLayer"""
486
- return self._functional_classes
487
-
488
488
  @property
489
489
  def state_charts(self) -> NamedItemList[StateChart]:
490
490
  """All state charts applicable to this DiagLayer"""