odxtools 6.6.1__py3-none-any.whl → 9.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odxtools/__init__.py +7 -5
- odxtools/additionalaudience.py +3 -5
- odxtools/admindata.py +5 -7
- odxtools/audience.py +10 -13
- odxtools/basecomparam.py +3 -5
- odxtools/basicstructure.py +55 -241
- odxtools/cli/_parser_utils.py +16 -1
- odxtools/cli/_print_utils.py +169 -134
- odxtools/cli/browse.py +127 -103
- odxtools/cli/compare.py +114 -87
- odxtools/cli/decode.py +2 -1
- odxtools/cli/dummy_sub_parser.py +3 -1
- odxtools/cli/find.py +2 -1
- odxtools/cli/list.py +26 -16
- odxtools/cli/main.py +1 -0
- odxtools/cli/snoop.py +32 -6
- odxtools/codec.py +211 -0
- odxtools/commrelation.py +122 -0
- odxtools/companydata.py +5 -7
- odxtools/companydocinfo.py +7 -8
- odxtools/companyrevisioninfo.py +3 -5
- odxtools/companyspecificinfo.py +8 -9
- odxtools/comparam.py +4 -6
- odxtools/comparaminstance.py +14 -14
- odxtools/comparamspec.py +16 -54
- odxtools/comparamsubset.py +22 -62
- odxtools/complexcomparam.py +5 -7
- odxtools/compumethods/compucodecompumethod.py +63 -0
- odxtools/compumethods/compuconst.py +31 -0
- odxtools/compumethods/compudefaultvalue.py +27 -0
- odxtools/compumethods/compuinternaltophys.py +56 -0
- odxtools/compumethods/compuinversevalue.py +7 -0
- odxtools/compumethods/compumethod.py +94 -15
- odxtools/compumethods/compuphystointernal.py +56 -0
- odxtools/compumethods/compurationalcoeffs.py +20 -9
- odxtools/compumethods/compuscale.py +67 -32
- odxtools/compumethods/createanycompumethod.py +31 -172
- odxtools/compumethods/identicalcompumethod.py +31 -6
- odxtools/compumethods/limit.py +70 -36
- odxtools/compumethods/linearcompumethod.py +70 -181
- odxtools/compumethods/linearsegment.py +190 -0
- odxtools/compumethods/ratfunccompumethod.py +106 -0
- odxtools/compumethods/ratfuncsegment.py +87 -0
- odxtools/compumethods/scalelinearcompumethod.py +132 -26
- odxtools/compumethods/scaleratfunccompumethod.py +113 -0
- odxtools/compumethods/tabintpcompumethod.py +123 -92
- odxtools/compumethods/texttablecompumethod.py +117 -57
- odxtools/createanydiagcodedtype.py +10 -67
- odxtools/database.py +167 -87
- odxtools/dataobjectproperty.py +25 -32
- odxtools/decodestate.py +14 -17
- odxtools/description.py +47 -0
- odxtools/determinenumberofitems.py +4 -5
- odxtools/diagcodedtype.py +37 -106
- odxtools/diagcomm.py +24 -12
- odxtools/diagdatadictionaryspec.py +120 -96
- odxtools/diaglayercontainer.py +46 -54
- odxtools/diaglayers/basevariant.py +128 -0
- odxtools/diaglayers/basevariantraw.py +123 -0
- odxtools/diaglayers/diaglayer.py +432 -0
- odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
- odxtools/diaglayers/diaglayertype.py +42 -0
- odxtools/diaglayers/ecushareddata.py +96 -0
- odxtools/diaglayers/ecushareddataraw.py +87 -0
- odxtools/diaglayers/ecuvariant.py +124 -0
- odxtools/diaglayers/ecuvariantraw.py +129 -0
- odxtools/diaglayers/functionalgroup.py +110 -0
- odxtools/diaglayers/functionalgroupraw.py +106 -0
- odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
- odxtools/diaglayers/hierarchyelementraw.py +58 -0
- odxtools/diaglayers/protocol.py +64 -0
- odxtools/diaglayers/protocolraw.py +91 -0
- odxtools/diagnostictroublecode.py +8 -9
- odxtools/diagservice.py +57 -44
- odxtools/diagvariable.py +113 -0
- odxtools/docrevision.py +5 -7
- odxtools/dopbase.py +15 -15
- odxtools/dtcdop.py +170 -50
- odxtools/dynamicendmarkerfield.py +134 -0
- odxtools/dynamiclengthfield.py +47 -42
- odxtools/dyndefinedspec.py +177 -0
- odxtools/dynenddopref.py +38 -0
- odxtools/ecuvariantmatcher.py +6 -7
- odxtools/element.py +13 -15
- odxtools/encodestate.py +199 -22
- odxtools/endofpdufield.py +31 -18
- odxtools/environmentdata.py +8 -1
- odxtools/environmentdatadescription.py +198 -36
- odxtools/exceptions.py +11 -2
- odxtools/field.py +10 -10
- odxtools/functionalclass.py +3 -5
- odxtools/inputparam.py +3 -12
- odxtools/internalconstr.py +14 -5
- odxtools/isotp_state_machine.py +14 -6
- odxtools/leadinglengthinfotype.py +37 -18
- odxtools/library.py +66 -0
- odxtools/loadfile.py +64 -0
- odxtools/matchingparameter.py +3 -3
- odxtools/message.py +0 -7
- odxtools/minmaxlengthtype.py +61 -33
- odxtools/modification.py +3 -5
- odxtools/multiplexer.py +135 -75
- odxtools/multiplexercase.py +39 -18
- odxtools/multiplexerdefaultcase.py +15 -12
- odxtools/multiplexerswitchkey.py +4 -5
- odxtools/nameditemlist.py +33 -8
- odxtools/negoutputparam.py +3 -5
- odxtools/odxcategory.py +83 -0
- odxtools/odxlink.py +62 -53
- odxtools/odxtypes.py +93 -8
- odxtools/outputparam.py +5 -16
- odxtools/parameterinfo.py +219 -61
- odxtools/parameters/codedconstparameter.py +45 -32
- odxtools/parameters/createanyparameter.py +19 -193
- odxtools/parameters/dynamicparameter.py +25 -4
- odxtools/parameters/lengthkeyparameter.py +83 -25
- odxtools/parameters/matchingrequestparameter.py +48 -18
- odxtools/parameters/nrcconstparameter.py +76 -54
- odxtools/parameters/parameter.py +97 -73
- odxtools/parameters/parameterwithdop.py +41 -38
- odxtools/parameters/physicalconstantparameter.py +41 -20
- odxtools/parameters/reservedparameter.py +36 -18
- odxtools/parameters/systemparameter.py +74 -7
- odxtools/parameters/tableentryparameter.py +47 -7
- odxtools/parameters/tablekeyparameter.py +142 -55
- odxtools/parameters/tablestructparameter.py +79 -58
- odxtools/parameters/valueparameter.py +39 -21
- odxtools/paramlengthinfotype.py +56 -33
- odxtools/parentref.py +20 -3
- odxtools/physicaldimension.py +3 -8
- odxtools/progcode.py +26 -11
- odxtools/protstack.py +3 -5
- odxtools/py.typed +0 -0
- odxtools/relateddoc.py +7 -9
- odxtools/request.py +120 -10
- odxtools/response.py +123 -23
- odxtools/scaleconstr.py +14 -8
- odxtools/servicebinner.py +1 -1
- odxtools/singleecujob.py +12 -10
- odxtools/snrefcontext.py +29 -0
- odxtools/specialdata.py +3 -5
- odxtools/specialdatagroup.py +7 -9
- odxtools/specialdatagroupcaption.py +3 -6
- odxtools/standardlengthtype.py +80 -14
- odxtools/state.py +3 -5
- odxtools/statechart.py +13 -19
- odxtools/statetransition.py +8 -18
- odxtools/staticfield.py +107 -0
- odxtools/subcomponent.py +288 -0
- odxtools/swvariable.py +21 -0
- odxtools/table.py +9 -9
- odxtools/tablerow.py +30 -15
- odxtools/teammember.py +3 -5
- odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
- odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
- odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
- odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
- odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
- odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
- odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
- odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
- odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
- odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
- odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
- odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
- odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
- odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
- odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
- odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
- odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
- odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
- odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
- odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
- odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
- odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
- odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
- odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
- odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
- odxtools/templates/macros/printMux.xml.jinja2 +5 -3
- odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
- odxtools/templates/macros/printParam.xml.jinja2 +18 -19
- odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
- odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
- odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
- odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
- odxtools/templates/macros/printService.xml.jinja2 +3 -2
- odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
- odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
- odxtools/templates/macros/printState.xml.jinja2 +1 -1
- odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
- odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
- odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
- odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
- odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
- odxtools/templates/macros/printTable.xml.jinja2 +4 -5
- odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
- odxtools/uds.py +2 -10
- odxtools/unit.py +4 -8
- odxtools/unitgroup.py +3 -5
- odxtools/unitspec.py +17 -17
- odxtools/utils.py +38 -20
- odxtools/variablegroup.py +32 -0
- odxtools/version.py +2 -2
- odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
- odxtools/xdoc.py +3 -5
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
- odxtools-9.3.0.dist-info/RECORD +228 -0
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
- odxtools/createcompanydatas.py +0 -17
- odxtools/createsdgs.py +0 -19
- odxtools/diaglayertype.py +0 -30
- odxtools/load_file.py +0 -13
- odxtools/load_odx_d_file.py +0 -6
- odxtools/load_pdx_file.py +0 -8
- odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
- odxtools-6.6.1.dist-info/RECORD +0 -180
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
odxtools/library.py
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Any, Dict, List, Optional, cast
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .element import IdentifiableElement
|
7
|
+
from .exceptions import odxraise, odxrequire
|
8
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
9
|
+
from .snrefcontext import SnRefContext
|
10
|
+
from .utils import dataclass_fields_asdict
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Library(IdentifiableElement):
|
15
|
+
"""
|
16
|
+
A library defines a shared library used for single ECU jobs etc.
|
17
|
+
|
18
|
+
It this is basically equivalent to ProgCode.
|
19
|
+
"""
|
20
|
+
|
21
|
+
code_file: str
|
22
|
+
encryption: Optional[str]
|
23
|
+
syntax: str
|
24
|
+
revision: str
|
25
|
+
entrypoint: Optional[str]
|
26
|
+
|
27
|
+
@property
|
28
|
+
def code(self) -> bytes:
|
29
|
+
return self._code
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Library":
|
33
|
+
|
34
|
+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
35
|
+
|
36
|
+
code_file = odxrequire(et_element.findtext("CODE-FILE"))
|
37
|
+
encryption = et_element.findtext("ENCRYPTION")
|
38
|
+
syntax = odxrequire(et_element.findtext("SYNTAX"))
|
39
|
+
revision = odxrequire(et_element.findtext("REVISION"))
|
40
|
+
entrypoint = et_element.findtext("ENTRYPOINT")
|
41
|
+
|
42
|
+
return Library(
|
43
|
+
code_file=code_file,
|
44
|
+
encryption=encryption,
|
45
|
+
syntax=syntax,
|
46
|
+
revision=revision,
|
47
|
+
entrypoint=entrypoint,
|
48
|
+
**kwargs)
|
49
|
+
|
50
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
51
|
+
return {self.odx_id: self}
|
52
|
+
|
53
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
54
|
+
pass
|
55
|
+
|
56
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
57
|
+
aux_file = odxrequire(context.database).auxiliary_files.get(self.code_file)
|
58
|
+
|
59
|
+
if aux_file is None:
|
60
|
+
odxraise(f"Reference to auxiliary file '{self.code_file}' "
|
61
|
+
f"could not be resolved")
|
62
|
+
self._code: bytes = cast(bytes, None)
|
63
|
+
return
|
64
|
+
|
65
|
+
self._code = aux_file.read()
|
66
|
+
aux_file.seek(0)
|
odxtools/loadfile.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
import os
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
from .database import Database
|
7
|
+
|
8
|
+
|
9
|
+
def load_pdx_file(pdx_file: Union[str, Path]) -> Database:
|
10
|
+
db = Database()
|
11
|
+
db.add_pdx_file(str(pdx_file))
|
12
|
+
db.refresh()
|
13
|
+
return db
|
14
|
+
|
15
|
+
|
16
|
+
def load_odx_d_file(odx_d_file_name: Union[str, Path]) -> Database:
|
17
|
+
db = Database()
|
18
|
+
db.add_odx_file(str(odx_d_file_name))
|
19
|
+
db.refresh()
|
20
|
+
|
21
|
+
return db
|
22
|
+
|
23
|
+
|
24
|
+
def load_file(file_name: Union[str, Path]) -> Database:
|
25
|
+
if str(file_name).lower().endswith(".pdx"):
|
26
|
+
return load_pdx_file(str(file_name))
|
27
|
+
elif str(file_name).lower().endswith(".odx-d"):
|
28
|
+
return load_odx_d_file(str(file_name))
|
29
|
+
else:
|
30
|
+
raise RuntimeError(f"Could not guess the file format of file '{file_name}'!")
|
31
|
+
|
32
|
+
|
33
|
+
def load_files(*file_names: Union[str, Path]) -> Database:
|
34
|
+
db = Database()
|
35
|
+
for file_name in file_names:
|
36
|
+
p = Path(file_name)
|
37
|
+
if p.suffix.lower() == ".pdx":
|
38
|
+
db.add_pdx_file(str(file_name))
|
39
|
+
elif p.suffix.lower().startswith(".odx"):
|
40
|
+
db.add_odx_file(str(file_name))
|
41
|
+
elif p.name.lower() != "index.xml":
|
42
|
+
db.add_auxiliary_file(str(file_name))
|
43
|
+
|
44
|
+
db.refresh()
|
45
|
+
return db
|
46
|
+
|
47
|
+
|
48
|
+
def load_directory(dir_name: Union[str, Path]) -> Database:
|
49
|
+
db = Database()
|
50
|
+
for file_name in os.listdir(str(dir_name)):
|
51
|
+
p = Path(dir_name) / file_name
|
52
|
+
|
53
|
+
if not p.is_file():
|
54
|
+
continue
|
55
|
+
|
56
|
+
if p.suffix.lower() == ".pdx":
|
57
|
+
db.add_pdx_file(str(p))
|
58
|
+
elif p.suffix.lower().startswith(".odx"):
|
59
|
+
db.add_odx_file(str(p))
|
60
|
+
elif p.name.lower() != "index.xml":
|
61
|
+
db.add_auxiliary_file(p.name, open(str(p), "rb"))
|
62
|
+
|
63
|
+
db.refresh()
|
64
|
+
return db
|
odxtools/matchingparameter.py
CHANGED
@@ -12,7 +12,7 @@ from .utils import is_short_name, is_short_name_path
|
|
12
12
|
class MatchingParameter:
|
13
13
|
"""According to ISO 22901, a MatchingParameter contains a string value identifying
|
14
14
|
the active ECU variant. Moreover, it references a DIAG-COMM via snref and one of its
|
15
|
-
|
15
|
+
positive response's OUT-PARAM-IF via snref or snpathref.
|
16
16
|
|
17
17
|
Unlike other parameters defined in the `parameters` package, a MatchingParameter is
|
18
18
|
not transferred over the network.
|
@@ -32,8 +32,8 @@ class MatchingParameter:
|
|
32
32
|
doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
|
33
33
|
|
34
34
|
expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
|
35
|
-
|
36
|
-
diag_comm_snref = odxrequire(
|
35
|
+
diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
|
36
|
+
diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME"))
|
37
37
|
out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF")
|
38
38
|
out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF")
|
39
39
|
out_param_if = None
|
odxtools/message.py
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from typing import TYPE_CHECKING, Union
|
4
4
|
|
5
|
-
from deprecation import deprecated
|
6
|
-
|
7
5
|
from .odxtypes import ParameterValue, ParameterValueDict
|
8
6
|
|
9
7
|
if TYPE_CHECKING:
|
@@ -29,8 +27,3 @@ class Message:
|
|
29
27
|
|
30
28
|
def __getitem__(self, key: str) -> ParameterValue:
|
31
29
|
return self.param_dict[key]
|
32
|
-
|
33
|
-
@property
|
34
|
-
@deprecated("use .coding_object")
|
35
|
-
def structure(self) -> Union["Request", "Response"]:
|
36
|
-
return self.coding_object
|
odxtools/minmaxlengthtype.py
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Optional
|
3
|
+
from typing import List, Optional
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from typing_extensions import override
|
4
7
|
|
5
8
|
from .decodestate import DecodeState
|
6
9
|
from .diagcodedtype import DctType, DiagCodedType
|
7
10
|
from .encodestate import EncodeState
|
8
|
-
from .exceptions import DecodeError, EncodeError, odxassert, odxraise
|
11
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
12
|
+
from .odxlink import OdxDocFragment
|
9
13
|
from .odxtypes import AtomicOdxType, DataType
|
14
|
+
from .utils import dataclass_fields_asdict
|
10
15
|
|
11
16
|
|
12
17
|
@dataclass
|
@@ -15,6 +20,25 @@ class MinMaxLengthType(DiagCodedType):
|
|
15
20
|
max_length: Optional[int]
|
16
21
|
termination: str
|
17
22
|
|
23
|
+
@property
|
24
|
+
def dct_type(self) -> DctType:
|
25
|
+
return "MIN-MAX-LENGTH-TYPE"
|
26
|
+
|
27
|
+
@staticmethod
|
28
|
+
@override
|
29
|
+
def from_et(et_element: ElementTree.Element,
|
30
|
+
doc_frags: List[OdxDocFragment]) -> "MinMaxLengthType":
|
31
|
+
kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
|
32
|
+
|
33
|
+
min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
|
34
|
+
max_length = None
|
35
|
+
if et_element.find("MAX-LENGTH") is not None:
|
36
|
+
max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
|
37
|
+
termination = odxrequire(et_element.get("TERMINATION"))
|
38
|
+
|
39
|
+
return MinMaxLengthType(
|
40
|
+
min_length=min_length, max_length=max_length, termination=termination, **kwargs)
|
41
|
+
|
18
42
|
def __post_init__(self) -> None:
|
19
43
|
odxassert(self.max_length is None or self.min_length <= self.max_length)
|
20
44
|
odxassert(
|
@@ -30,10 +54,6 @@ class MinMaxLengthType(DiagCodedType):
|
|
30
54
|
"END-OF-PDU",
|
31
55
|
], f"A min-max length type cannot have the termination {self.termination}")
|
32
56
|
|
33
|
-
@property
|
34
|
-
def dct_type(self) -> DctType:
|
35
|
-
return "MIN-MAX-LENGTH-TYPE"
|
36
|
-
|
37
57
|
def __termination_sequence(self) -> bytes:
|
38
58
|
"""Returns the termination byte sequence if it isn't defined."""
|
39
59
|
# The termination sequence is actually not specified by ASAM
|
@@ -51,9 +71,10 @@ class MinMaxLengthType(DiagCodedType):
|
|
51
71
|
termination_sequence = bytes([0xFF, 0xFF])
|
52
72
|
return termination_sequence
|
53
73
|
|
54
|
-
|
55
|
-
|
56
|
-
|
74
|
+
@override
|
75
|
+
def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
|
76
|
+
|
77
|
+
if not isinstance(internal_value, (bytes, str, bytearray)):
|
57
78
|
odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
|
58
79
|
EncodeError)
|
59
80
|
|
@@ -62,20 +83,24 @@ class MinMaxLengthType(DiagCodedType):
|
|
62
83
|
else:
|
63
84
|
data_length = len(internal_value)
|
64
85
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
86
|
+
orig_cursor = encode_state.cursor_byte_position
|
87
|
+
encode_state.emplace_atomic_value(
|
88
|
+
internal_value=internal_value,
|
89
|
+
used_mask=None,
|
90
|
+
bit_length=8 * data_length,
|
91
|
+
base_data_type=self.base_data_type,
|
92
|
+
is_highlow_byte_order=self.is_highlow_byte_order,
|
93
|
+
)
|
94
|
+
value_len = encode_state.cursor_byte_position - orig_cursor
|
73
95
|
|
74
96
|
# TODO: ensure that the termination delimiter is not
|
75
97
|
# encountered within the encoded value.
|
76
98
|
|
77
|
-
odxassert(
|
78
|
-
|
99
|
+
odxassert(
|
100
|
+
self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
|
101
|
+
"Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
|
102
|
+
"which is not located at the end of the PDU")
|
103
|
+
if encode_state.is_end_of_pdu or value_len == self.max_length:
|
79
104
|
# All termination types may be ended by the end of the PDU
|
80
105
|
# or once reaching the maximum length. In this case, we
|
81
106
|
# must not add the termination sequence
|
@@ -85,20 +110,23 @@ class MinMaxLengthType(DiagCodedType):
|
|
85
110
|
|
86
111
|
# ensure that we don't try to encode an odd-length
|
87
112
|
# value when using a two-byte terminator
|
88
|
-
odxassert(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
113
|
+
odxassert(value_len % len(termination_sequence) == 0)
|
114
|
+
|
115
|
+
value_len += len(termination_sequence)
|
116
|
+
encode_state.emplace_bytes(termination_sequence)
|
117
|
+
|
118
|
+
if value_len < self.min_length:
|
119
|
+
odxraise(
|
120
|
+
f"Encoded value for MinMaxLengthType "
|
121
|
+
f"must be at least {self.min_length} bytes long. "
|
122
|
+
f"(Is: {value_len} bytes.)", EncodeError)
|
123
|
+
return
|
124
|
+
elif self.max_length is not None and value_len > self.max_length:
|
125
|
+
odxraise(
|
126
|
+
f"Encoded value for MinMaxLengthType "
|
127
|
+
f"must not be longer than {self.max_length} bytes. "
|
128
|
+
f"(Is: {value_len} bytes.)", EncodeError)
|
129
|
+
return
|
102
130
|
|
103
131
|
def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
|
104
132
|
odxassert(decode_state.cursor_bit_position == 0,
|
odxtools/modification.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
3
|
+
from typing import Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
7
|
-
|
8
|
-
if TYPE_CHECKING:
|
9
|
-
from .diaglayer import DiagLayer
|
7
|
+
from .snrefcontext import SnRefContext
|
10
8
|
|
11
9
|
|
12
10
|
@dataclass
|
@@ -27,5 +25,5 @@ class Modification:
|
|
27
25
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
28
26
|
pass
|
29
27
|
|
30
|
-
def _resolve_snrefs(self,
|
28
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
31
29
|
pass
|
odxtools/multiplexer.py
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from typing_extensions import override
|
7
|
+
|
6
8
|
from .complexdop import ComplexDop
|
7
9
|
from .decodestate import DecodeState
|
8
10
|
from .encodestate import EncodeState
|
9
|
-
from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
|
11
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
|
10
12
|
from .multiplexercase import MultiplexerCase
|
11
13
|
from .multiplexerdefaultcase import MultiplexerDefaultCase
|
12
14
|
from .multiplexerswitchkey import MultiplexerSwitchKey
|
15
|
+
from .nameditemlist import NamedItemList
|
13
16
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
14
17
|
from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
|
18
|
+
from .snrefcontext import SnRefContext
|
15
19
|
from .utils import dataclass_fields_asdict
|
16
20
|
|
17
|
-
if TYPE_CHECKING:
|
18
|
-
from .diaglayer import DiagLayer
|
19
|
-
|
20
21
|
|
21
22
|
@dataclass
|
22
23
|
class Multiplexer(ComplexDop):
|
@@ -30,13 +31,15 @@ class Multiplexer(ComplexDop):
|
|
30
31
|
byte_position: int
|
31
32
|
switch_key: MultiplexerSwitchKey
|
32
33
|
default_case: Optional[MultiplexerDefaultCase]
|
33
|
-
cases:
|
34
|
+
cases: NamedItemList[MultiplexerCase]
|
34
35
|
is_visible_raw: Optional[bool]
|
35
36
|
|
36
37
|
@staticmethod
|
38
|
+
@override
|
37
39
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
|
38
40
|
"""Reads a Multiplexer from Diag Layer."""
|
39
|
-
|
41
|
+
base_obj = ComplexDop.from_et(et_element, doc_frags)
|
42
|
+
kwargs = dataclass_fields_asdict(base_obj)
|
40
43
|
|
41
44
|
byte_position = int(et_element.findtext("BYTE-POSITION", "0"))
|
42
45
|
switch_key = MultiplexerSwitchKey.from_et(
|
@@ -46,9 +49,8 @@ class Multiplexer(ComplexDop):
|
|
46
49
|
if (dc_elem := et_element.find("DEFAULT-CASE")) is not None:
|
47
50
|
default_case = MultiplexerDefaultCase.from_et(dc_elem, doc_frags)
|
48
51
|
|
49
|
-
cases =
|
50
|
-
|
51
|
-
cases = [MultiplexerCase.from_et(el, doc_frags) for el in cases_elem.iterfind("CASE")]
|
52
|
+
cases = NamedItemList(
|
53
|
+
[MultiplexerCase.from_et(el, doc_frags) for el in et_element.iterfind("CASES/CASE")])
|
52
54
|
|
53
55
|
is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
|
54
56
|
|
@@ -66,88 +68,143 @@ class Multiplexer(ComplexDop):
|
|
66
68
|
|
67
69
|
def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
|
68
70
|
key_type = self.switch_key.dop.physical_type.base_data_type
|
69
|
-
lower_limit = key_type.make_from(case.lower_limit)
|
70
|
-
upper_limit = key_type.make_from(case.upper_limit)
|
71
|
+
lower_limit = key_type.make_from(case.lower_limit.value)
|
72
|
+
upper_limit = key_type.make_from(case.upper_limit.value)
|
71
73
|
if not isinstance(lower_limit, type(upper_limit)) and not isinstance(
|
72
74
|
upper_limit, type(lower_limit)):
|
73
75
|
odxraise("Upper and lower bounds of limits must compareable")
|
74
76
|
return lower_limit, upper_limit
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
if
|
80
|
-
raise EncodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
|
81
|
-
f"{self.short_name} was passed the bit position
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
78
|
+
@override
|
79
|
+
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
|
80
|
+
|
81
|
+
if encode_state.cursor_bit_position != 0:
|
82
|
+
raise EncodeError(f"Multiplexer parameters must be aligned, i.e. bit_position=0, but "
|
83
|
+
f"{self.short_name} was passed the bit position "
|
84
|
+
f"{encode_state.cursor_bit_position}")
|
85
|
+
|
86
|
+
orig_origin = encode_state.origin_byte_position
|
87
|
+
encode_state.origin_byte_position = encode_state.cursor_byte_position
|
88
|
+
|
89
|
+
if isinstance(physical_value, (list, tuple)) and len(physical_value) == 2:
|
90
|
+
case_spec, case_value = physical_value
|
91
|
+
elif isinstance(physical_value, dict) and len(physical_value) == 1:
|
92
|
+
case_spec, case_value = next(iter(physical_value.items()))
|
93
|
+
else:
|
94
|
+
raise EncodeError(
|
95
|
+
f"Values of multiplexer parameters must be defined as a "
|
96
|
+
f"(case_name, content_value) tuple instead of as '{physical_value!r}'")
|
97
|
+
|
98
|
+
mux_case: Union[MultiplexerCase, MultiplexerDefaultCase]
|
99
|
+
applicable_cases: List[Union[MultiplexerCase, MultiplexerDefaultCase]]
|
100
|
+
|
101
|
+
if isinstance(case_spec, str):
|
102
|
+
applicable_cases = [x for x in self.cases if x.short_name == case_spec]
|
103
|
+
if not applicable_cases and self.default_case:
|
104
|
+
applicable_cases.append(self.default_case)
|
105
|
+
if len(applicable_cases) == 0:
|
106
|
+
raise EncodeError(
|
107
|
+
f"Multiplexer {self.short_name} does not know any case called {case_spec}")
|
108
|
+
|
109
|
+
odxassert(len(applicable_cases) == 1)
|
110
|
+
mux_case = applicable_cases[0]
|
111
|
+
if isinstance(mux_case, MultiplexerCase):
|
98
112
|
key_value, _ = self._get_case_limits(mux_case)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
113
|
+
else:
|
114
|
+
key_value = 0
|
115
|
+
elif isinstance(case_spec, int):
|
116
|
+
applicable_cases = []
|
117
|
+
for x in self.cases:
|
118
|
+
lower, upper = cast(Tuple[int, int], self._get_case_limits(x))
|
119
|
+
if lower <= case_spec and case_spec <= upper:
|
120
|
+
applicable_cases.append(x)
|
121
|
+
|
122
|
+
if len(applicable_cases) == 0:
|
123
|
+
if self.default_case is None:
|
124
|
+
raise EncodeError(
|
125
|
+
f"Multiplexer {self.short_name} does not know any case called {case_spec}")
|
126
|
+
mux_case = self.default_case
|
127
|
+
key_value = case_spec
|
128
|
+
else:
|
129
|
+
mux_case = applicable_cases[0]
|
130
|
+
key_value = case_spec
|
131
|
+
elif isinstance(case_spec, MultiplexerCase):
|
132
|
+
mux_case = case_spec
|
133
|
+
key_value, _ = self._get_case_limits(mux_case)
|
134
|
+
elif case_spec is None:
|
135
|
+
if self.default_case is None:
|
136
|
+
raise EncodeError(f"Multiplexer {self.short_name} does not define a default case")
|
137
|
+
key_value = 0
|
138
|
+
else:
|
139
|
+
raise EncodeError(f"Illegal case specification '{case_spec}' for "
|
140
|
+
f"multiplexer {self.short_name}")
|
141
|
+
|
142
|
+
# the byte position of the switch key is relative to
|
143
|
+
# the multiplexer's position
|
144
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
|
145
|
+
encode_state.cursor_bit_position = self.switch_key.bit_position or 0
|
146
|
+
self.switch_key.dop.encode_into_pdu(physical_value=key_value, encode_state=encode_state)
|
147
|
+
encode_state.cursor_bit_position = 0
|
148
|
+
|
149
|
+
if mux_case.structure is not None:
|
150
|
+
# the byte position of the content is specified by the
|
151
|
+
# BYTE-POSITION attribute of the multiplexer
|
152
|
+
encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
|
153
|
+
mux_case.structure.encode_into_pdu(physical_value=case_value, encode_state=encode_state)
|
154
|
+
|
155
|
+
encode_state.origin_byte_position = orig_origin
|
156
|
+
|
157
|
+
@override
|
111
158
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
112
|
-
|
113
|
-
# multiplexers are structures and thus the origin position
|
114
|
-
# must be moved to the start of the multiplexer
|
115
159
|
orig_origin = decode_state.origin_byte_position
|
116
|
-
orig_cursor = decode_state.cursor_byte_position
|
117
|
-
if self.byte_position is not None:
|
118
|
-
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
|
119
160
|
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
120
161
|
|
162
|
+
# Decode the switch key. Its BYTE-POSITION is relative to the
|
163
|
+
# that of the multiplexer.
|
164
|
+
if self.switch_key.byte_position is not None:
|
165
|
+
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.switch_key.byte_position
|
166
|
+
decode_state.cursor_bit_position = self.switch_key.bit_position or 0
|
121
167
|
key_value = self.switch_key.dop.decode_from_pdu(decode_state)
|
168
|
+
decode_state.cursor_bit_position = 0
|
122
169
|
|
123
170
|
if not isinstance(key_value, int):
|
124
171
|
odxraise(f"Multiplexer keys must be integers (is '{type(key_value).__name__}'"
|
125
172
|
f" for multiplexer '{self.short_name}')")
|
126
173
|
|
127
|
-
|
128
|
-
|
129
|
-
|
174
|
+
# "If a matching CASE is found, the referenced STRUCTURE is
|
175
|
+
# analyzed at the BYTE-POSITION (child element of MUX)
|
176
|
+
# relatively to the byte position of the MUX."
|
177
|
+
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
|
178
|
+
|
179
|
+
applicable_case: Optional[Union[MultiplexerCase, MultiplexerDefaultCase]] = None
|
180
|
+
for mux_case in self.cases:
|
181
|
+
lower, upper = self._get_case_limits(mux_case)
|
130
182
|
if lower <= key_value and key_value <= upper: # type: ignore[operator]
|
131
|
-
|
132
|
-
case_value = case._structure.decode_from_pdu(decode_state)
|
183
|
+
applicable_case = mux_case
|
133
184
|
break
|
134
185
|
|
135
|
-
if
|
136
|
-
|
137
|
-
|
186
|
+
if applicable_case is None:
|
187
|
+
applicable_case = self.default_case
|
188
|
+
|
189
|
+
if applicable_case is None:
|
190
|
+
odxraise(
|
191
|
+
f"Cannot find an applicable case for value {key_value} in "
|
192
|
+
f"multiplexer {self.short_name}", DecodeError)
|
193
|
+
decode_state.origin_byte_position = orig_origin
|
194
|
+
return (None, None)
|
138
195
|
|
139
|
-
if
|
140
|
-
|
141
|
-
|
196
|
+
if applicable_case.structure is not None:
|
197
|
+
case_value = applicable_case.structure.decode_from_pdu(decode_state)
|
198
|
+
else:
|
199
|
+
case_value = {}
|
142
200
|
|
143
|
-
|
201
|
+
result = (applicable_case.short_name, case_value)
|
144
202
|
|
145
|
-
# go back to the original origin
|
146
203
|
decode_state.origin_byte_position = orig_origin
|
147
|
-
decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
|
148
204
|
|
149
|
-
return
|
205
|
+
return result
|
150
206
|
|
207
|
+
@override
|
151
208
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
152
209
|
odxlinks = super()._build_odxlinks()
|
153
210
|
|
@@ -157,6 +214,7 @@ class Multiplexer(ComplexDop):
|
|
157
214
|
|
158
215
|
return odxlinks
|
159
216
|
|
217
|
+
@override
|
160
218
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
161
219
|
super()._resolve_odxlinks(odxlinks)
|
162
220
|
|
@@ -164,15 +222,17 @@ class Multiplexer(ComplexDop):
|
|
164
222
|
if self.default_case is not None:
|
165
223
|
self.default_case._resolve_odxlinks(odxlinks)
|
166
224
|
|
167
|
-
for
|
168
|
-
|
225
|
+
for mux_case in self.cases:
|
226
|
+
mux_case._mux_case_resolve_odxlinks(
|
227
|
+
odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
|
169
228
|
|
170
|
-
|
171
|
-
|
229
|
+
@override
|
230
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
231
|
+
super()._resolve_snrefs(context)
|
172
232
|
|
173
|
-
self.switch_key._resolve_snrefs(
|
233
|
+
self.switch_key._resolve_snrefs(context)
|
174
234
|
if self.default_case is not None:
|
175
|
-
self.default_case._resolve_snrefs(
|
235
|
+
self.default_case._resolve_snrefs(context)
|
176
236
|
|
177
|
-
for
|
178
|
-
|
237
|
+
for mux_case in self.cases:
|
238
|
+
mux_case._resolve_snrefs(context)
|