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.
- odxtools/addrdeffilter.py +33 -0
- odxtools/addrdefphyssegment.py +33 -0
- odxtools/checksum.py +67 -0
- odxtools/checksumresult.py +7 -0
- odxtools/database.py +24 -4
- odxtools/datablock.py +153 -0
- odxtools/datafile.py +23 -0
- odxtools/dataformat.py +39 -0
- odxtools/dataformatselection.py +9 -0
- odxtools/description.py +2 -5
- odxtools/diagdatadictionaryspec.py +1 -3
- odxtools/diaglayers/diaglayer.py +31 -11
- odxtools/diaglayers/ecuvariant.py +12 -19
- odxtools/diaglayers/hierarchyelement.py +5 -5
- odxtools/diaglayers/protocol.py +14 -0
- odxtools/direction.py +7 -0
- odxtools/ecumem.py +71 -0
- odxtools/ecumemconnector.py +136 -0
- odxtools/encryptcompressmethod.py +39 -0
- odxtools/encryptcompressmethodtype.py +13 -0
- odxtools/expectedident.py +40 -0
- odxtools/externflashdata.py +34 -0
- odxtools/filter.py +32 -0
- odxtools/flash.py +88 -0
- odxtools/flashclass.py +32 -0
- odxtools/flashdata.py +70 -0
- odxtools/fwchecksum.py +7 -0
- odxtools/fwsignature.py +7 -0
- odxtools/identdesc.py +54 -0
- odxtools/identvalue.py +32 -0
- odxtools/identvaluetype.py +14 -0
- odxtools/internflashdata.py +33 -0
- odxtools/loadfile.py +1 -1
- odxtools/mem.py +80 -0
- odxtools/modification.py +3 -2
- odxtools/negoffset.py +21 -0
- odxtools/odxlink.py +4 -2
- odxtools/ownident.py +38 -0
- odxtools/physicaltype.py +12 -10
- odxtools/physmem.py +52 -0
- odxtools/physsegment.py +42 -0
- odxtools/posoffset.py +21 -0
- odxtools/security.py +42 -0
- odxtools/securitymethod.py +7 -0
- odxtools/segment.py +63 -0
- odxtools/session.py +88 -0
- odxtools/sessiondesc.py +101 -0
- odxtools/sessionsubelemtype.py +14 -0
- odxtools/sizedeffilter.py +33 -0
- odxtools/sizedefphyssegment.py +33 -0
- odxtools/specialdata.py +2 -1
- odxtools/subcomponentparamconnector.py +1 -1
- odxtools/targetaddroffset.py +13 -0
- odxtools/templates/comparam-spec.odx-c.xml.jinja2 +1 -0
- odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +1 -0
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +2 -1
- odxtools/templates/flash.odx-f.xml.jinja2 +42 -0
- odxtools/templates/macros/printAdminData.xml.jinja2 +4 -4
- odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
- odxtools/templates/macros/printChecksum.xml.jinja2 +36 -0
- odxtools/templates/macros/printComparam.xml.jinja2 +1 -1
- odxtools/templates/macros/printComparamRef.xml.jinja2 +1 -3
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
- odxtools/templates/macros/printDOP.xml.jinja2 +3 -3
- odxtools/templates/macros/printDatablock.xml.jinja2 +78 -0
- odxtools/templates/macros/printDiagComm.xml.jinja2 +2 -2
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +2 -1
- odxtools/templates/macros/printDiagVariable.xml.jinja2 +4 -4
- odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +3 -3
- odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +2 -2
- odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +2 -2
- odxtools/templates/macros/printEcuMem.xml.jinja2 +24 -0
- odxtools/templates/macros/printEcuMemConnector.xml.jinja2 +58 -0
- odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
- odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
- odxtools/templates/macros/printExpectedIdent.xml.jinja2 +21 -0
- odxtools/templates/macros/printFlashdata.xml.jinja2 +43 -0
- odxtools/templates/macros/printIdentDesc.xml.jinja2 +17 -0
- odxtools/templates/macros/printMem.xml.jinja2 +35 -0
- odxtools/templates/macros/printMux.xml.jinja2 +3 -3
- odxtools/templates/macros/printOdxCategory.xml.jinja2 +4 -4
- odxtools/templates/macros/printOwnIdent.xml.jinja2 +17 -0
- odxtools/templates/macros/printParam.xml.jinja2 +4 -4
- odxtools/templates/macros/printParentRef.xml.jinja2 +1 -5
- odxtools/templates/macros/printPhysMem.xml.jinja2 +20 -0
- odxtools/templates/macros/printPhysSegment.xml.jinja2 +33 -0
- odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +1 -1
- odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
- odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
- odxtools/templates/macros/printSecurity.xml.jinja2 +37 -0
- odxtools/templates/macros/printSegment.xml.jinja2 +31 -0
- odxtools/templates/macros/printService.xml.jinja2 +3 -3
- odxtools/templates/macros/printSession.xml.jinja2 +45 -0
- odxtools/templates/macros/printSessionDesc.xml.jinja2 +40 -0
- odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +3 -3
- odxtools/templates/macros/printSpecialData.xml.jinja2 +2 -2
- odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +1 -1
- odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
- odxtools/templates/macros/printSubComponent.xml.jinja2 +3 -3
- odxtools/templates/macros/printTable.xml.jinja2 +6 -6
- odxtools/templates/macros/printUnitSpec.xml.jinja2 +2 -2
- odxtools/text.py +2 -6
- odxtools/utils.py +22 -1
- odxtools/validityfor.py +30 -0
- odxtools/version.py +2 -2
- odxtools/writepdxfile.py +70 -21
- {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/METADATA +1 -1
- {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/RECORD +112 -55
- {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/WHEEL +1 -1
- {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/entry_points.txt +0 -0
- {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/licenses/LICENSE +0 -0
- {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.
|
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.
|
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
|
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
|
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
|
-
|
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 =
|
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/diaglayers/diaglayer.py
CHANGED
@@ -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
|
268
|
-
return self.diag_layer_raw.
|
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
|
276
|
-
return self.diag_layer_raw.
|
296
|
+
def libraries(self) -> NamedItemList[Library]:
|
297
|
+
return self.diag_layer_raw.libraries
|
277
298
|
|
278
299
|
@property
|
279
|
-
def
|
280
|
-
|
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"""
|