odxtools 10.1.1__py3-none-any.whl → 10.2.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 (106) 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/direction.py +7 -0
  13. odxtools/ecumem.py +71 -0
  14. odxtools/ecumemconnector.py +136 -0
  15. odxtools/encryptcompressmethod.py +39 -0
  16. odxtools/encryptcompressmethodtype.py +13 -0
  17. odxtools/expectedident.py +40 -0
  18. odxtools/externflashdata.py +34 -0
  19. odxtools/filter.py +32 -0
  20. odxtools/flash.py +88 -0
  21. odxtools/flashclass.py +32 -0
  22. odxtools/flashdata.py +70 -0
  23. odxtools/fwchecksum.py +7 -0
  24. odxtools/fwsignature.py +7 -0
  25. odxtools/identdesc.py +54 -0
  26. odxtools/identvalue.py +32 -0
  27. odxtools/identvaluetype.py +14 -0
  28. odxtools/internflashdata.py +33 -0
  29. odxtools/loadfile.py +1 -1
  30. odxtools/mem.py +80 -0
  31. odxtools/modification.py +3 -2
  32. odxtools/negoffset.py +21 -0
  33. odxtools/ownident.py +38 -0
  34. odxtools/physicaltype.py +12 -10
  35. odxtools/physmem.py +52 -0
  36. odxtools/physsegment.py +42 -0
  37. odxtools/posoffset.py +21 -0
  38. odxtools/security.py +42 -0
  39. odxtools/securitymethod.py +7 -0
  40. odxtools/segment.py +63 -0
  41. odxtools/session.py +88 -0
  42. odxtools/sessiondesc.py +101 -0
  43. odxtools/sessionsubelemtype.py +14 -0
  44. odxtools/sizedeffilter.py +33 -0
  45. odxtools/sizedefphyssegment.py +33 -0
  46. odxtools/specialdata.py +2 -1
  47. odxtools/targetaddroffset.py +13 -0
  48. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +1 -0
  49. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +1 -0
  50. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +2 -1
  51. odxtools/templates/flash.odx-f.xml.jinja2 +42 -0
  52. odxtools/templates/macros/printAdminData.xml.jinja2 +4 -4
  53. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  54. odxtools/templates/macros/printChecksum.xml.jinja2 +36 -0
  55. odxtools/templates/macros/printComparam.xml.jinja2 +1 -1
  56. odxtools/templates/macros/printComparamRef.xml.jinja2 +1 -3
  57. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
  58. odxtools/templates/macros/printDOP.xml.jinja2 +3 -3
  59. odxtools/templates/macros/printDatablock.xml.jinja2 +78 -0
  60. odxtools/templates/macros/printDiagComm.xml.jinja2 +2 -2
  61. odxtools/templates/macros/printDiagLayer.xml.jinja2 +2 -1
  62. odxtools/templates/macros/printDiagVariable.xml.jinja2 +4 -4
  63. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +3 -3
  64. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +2 -2
  65. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +2 -2
  66. odxtools/templates/macros/printEcuMem.xml.jinja2 +24 -0
  67. odxtools/templates/macros/printEcuMemConnector.xml.jinja2 +58 -0
  68. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  69. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  70. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +21 -0
  71. odxtools/templates/macros/printFlashdata.xml.jinja2 +43 -0
  72. odxtools/templates/macros/printIdentDesc.xml.jinja2 +17 -0
  73. odxtools/templates/macros/printMem.xml.jinja2 +35 -0
  74. odxtools/templates/macros/printMux.xml.jinja2 +3 -3
  75. odxtools/templates/macros/printOdxCategory.xml.jinja2 +4 -4
  76. odxtools/templates/macros/printOwnIdent.xml.jinja2 +17 -0
  77. odxtools/templates/macros/printParam.xml.jinja2 +4 -4
  78. odxtools/templates/macros/printParentRef.xml.jinja2 +1 -5
  79. odxtools/templates/macros/printPhysMem.xml.jinja2 +20 -0
  80. odxtools/templates/macros/printPhysSegment.xml.jinja2 +33 -0
  81. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +1 -1
  82. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  83. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  84. odxtools/templates/macros/printSecurity.xml.jinja2 +37 -0
  85. odxtools/templates/macros/printSegment.xml.jinja2 +31 -0
  86. odxtools/templates/macros/printService.xml.jinja2 +3 -3
  87. odxtools/templates/macros/printSession.xml.jinja2 +45 -0
  88. odxtools/templates/macros/printSessionDesc.xml.jinja2 +40 -0
  89. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +3 -3
  90. odxtools/templates/macros/printSpecialData.xml.jinja2 +2 -2
  91. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +1 -1
  92. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  93. odxtools/templates/macros/printSubComponent.xml.jinja2 +3 -3
  94. odxtools/templates/macros/printTable.xml.jinja2 +6 -6
  95. odxtools/templates/macros/printUnitSpec.xml.jinja2 +2 -2
  96. odxtools/text.py +2 -6
  97. odxtools/utils.py +22 -1
  98. odxtools/validityfor.py +30 -0
  99. odxtools/version.py +2 -2
  100. odxtools/writepdxfile.py +70 -21
  101. {odxtools-10.1.1.dist-info → odxtools-10.2.0.dist-info}/METADATA +1 -1
  102. {odxtools-10.1.1.dist-info → odxtools-10.2.0.dist-info}/RECORD +106 -49
  103. {odxtools-10.1.1.dist-info → odxtools-10.2.0.dist-info}/WHEEL +1 -1
  104. {odxtools-10.1.1.dist-info → odxtools-10.2.0.dist-info}/entry_points.txt +0 -0
  105. {odxtools-10.1.1.dist-info → odxtools-10.2.0.dist-info}/licenses/LICENSE +0 -0
  106. {odxtools-10.1.1.dist-info → odxtools-10.2.0.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)
odxtools/direction.py ADDED
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from enum import Enum
3
+
4
+
5
+ class Direction(Enum):
6
+ UPLOAD = "UPLOAD"
7
+ DOWNLOAD = "DOWNLOAD"
odxtools/ecumem.py ADDED
@@ -0,0 +1,71 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass, field
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .admindata import AdminData
7
+ from .element import IdentifiableElement
8
+ from .exceptions import odxrequire
9
+ from .mem import Mem
10
+ from .odxdoccontext import OdxDocContext
11
+ from .odxlink import OdxLinkDatabase, OdxLinkId
12
+ from .physmem import PhysMem
13
+ from .snrefcontext import SnRefContext
14
+ from .specialdatagroup import SpecialDataGroup
15
+ from .utils import dataclass_fields_asdict
16
+
17
+
18
+ @dataclass(kw_only=True)
19
+ class EcuMem(IdentifiableElement):
20
+ admin_data: AdminData | None = None
21
+ mem: Mem
22
+ phys_mem: PhysMem | None = None
23
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
24
+
25
+ @staticmethod
26
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "EcuMem":
27
+
28
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
29
+
30
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), context)
31
+ mem = Mem.from_et(odxrequire(et_element.find("MEM")), context)
32
+ phys_mem = None
33
+ if (phys_mem_elem := et_element.find("PHYS-MEM")) is not None:
34
+ phys_mem = PhysMem.from_et(phys_mem_elem, context)
35
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
36
+
37
+ return EcuMem(admin_data=admin_data, mem=mem, phys_mem=phys_mem, sdgs=sdgs, **kwargs)
38
+
39
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
40
+ odxlinks = {self.odx_id: self}
41
+
42
+ if self.admin_data is not None:
43
+ odxlinks.update(self.admin_data._build_odxlinks())
44
+
45
+ odxlinks.update(self.mem._build_odxlinks())
46
+ if self.phys_mem is not None:
47
+ odxlinks.update(self.phys_mem._build_odxlinks())
48
+ for sdg in self.sdgs:
49
+ odxlinks.update(sdg._build_odxlinks())
50
+
51
+ return odxlinks
52
+
53
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
54
+ if self.admin_data is not None:
55
+ self.admin_data._resolve_odxlinks(odxlinks)
56
+
57
+ self.mem._resolve_odxlinks(odxlinks)
58
+ if self.phys_mem is not None:
59
+ self.phys_mem._resolve_odxlinks(odxlinks)
60
+ for sdg in self.sdgs:
61
+ sdg._resolve_odxlinks(odxlinks)
62
+
63
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
64
+ if self.admin_data is not None:
65
+ self.admin_data._resolve_snrefs(context)
66
+
67
+ self.mem._resolve_snrefs(context)
68
+ if self.phys_mem is not None:
69
+ self.phys_mem._resolve_snrefs(context)
70
+ for sdg in self.sdgs:
71
+ sdg._resolve_snrefs(context)