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
@@ -0,0 +1,177 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .diagcomm import DiagClassType, DiagComm
|
7
|
+
from .exceptions import odxraise, odxrequire
|
8
|
+
from .nameditemlist import NamedItemList
|
9
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
10
|
+
from .snrefcontext import SnRefContext
|
11
|
+
from .table import Table
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class DynIdDefModeInfo:
|
16
|
+
def_mode: str
|
17
|
+
|
18
|
+
clear_dyn_def_message_ref: Optional[OdxLinkRef]
|
19
|
+
clear_dyn_def_message_snref: Optional[str]
|
20
|
+
|
21
|
+
read_dyn_def_message_ref: Optional[OdxLinkRef]
|
22
|
+
read_dyn_def_message_snref: Optional[str]
|
23
|
+
|
24
|
+
dyn_def_message_ref: Optional[OdxLinkRef]
|
25
|
+
dyn_def_message_snref: Optional[str]
|
26
|
+
|
27
|
+
supported_dyn_ids: List[bytes]
|
28
|
+
selection_table_refs: List[Union[OdxLinkRef, str]]
|
29
|
+
|
30
|
+
@property
|
31
|
+
def clear_dyn_def_message(self) -> DiagComm:
|
32
|
+
return self._clear_dyn_def_message
|
33
|
+
|
34
|
+
@property
|
35
|
+
def read_dyn_def_message(self) -> DiagComm:
|
36
|
+
return self._read_dyn_def_message
|
37
|
+
|
38
|
+
@property
|
39
|
+
def dyn_def_message(self) -> DiagComm:
|
40
|
+
return self._dyn_def_message
|
41
|
+
|
42
|
+
@property
|
43
|
+
def selection_tables(self) -> NamedItemList[Table]:
|
44
|
+
return self._selection_tables
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def from_et(et_element: ElementTree.Element,
|
48
|
+
doc_frags: List[OdxDocFragment]) -> "DynIdDefModeInfo":
|
49
|
+
def_mode = odxrequire(et_element.findtext("DEF-MODE"))
|
50
|
+
|
51
|
+
clear_dyn_def_message_ref = OdxLinkRef.from_et(
|
52
|
+
et_element.find("CLEAR-DYN-DEF-MESSAGE-REF"), doc_frags)
|
53
|
+
if (snref_elem := et_element.find("CLEAR-DYN-DEF-MESSAGE-SNREF")) is not None:
|
54
|
+
clear_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
55
|
+
|
56
|
+
read_dyn_def_message_ref = OdxLinkRef.from_et(
|
57
|
+
et_element.find("READ-DYN-DEF-MESSAGE-REF"), doc_frags)
|
58
|
+
if (snref_elem := et_element.find("READ-DYN-DEF-MESSAGE-SNREF")) is not None:
|
59
|
+
read_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
60
|
+
|
61
|
+
dyn_def_message_ref = OdxLinkRef.from_et(et_element.find("DYN-DEF-MESSAGE-REF"), doc_frags)
|
62
|
+
if (snref_elem := et_element.find("DYN-DEF-MESSAGE-SNREF")) is not None:
|
63
|
+
dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
|
64
|
+
|
65
|
+
supported_dyn_ids = [
|
66
|
+
bytes.fromhex(odxrequire(x.text))
|
67
|
+
for x in et_element.iterfind("SUPPORTED-DYN-IDS/SUPPORTED-DYN-ID")
|
68
|
+
]
|
69
|
+
|
70
|
+
selection_table_refs: List[Union[OdxLinkRef, str]] = []
|
71
|
+
if (st_elems := et_element.find("SELECTION-TABLE-REFS")) is not None:
|
72
|
+
for st_elem in st_elems:
|
73
|
+
if st_elem.tag == "SELECTION-TABLE-REF":
|
74
|
+
selection_table_refs.append(OdxLinkRef.from_et(st_elem, doc_frags))
|
75
|
+
elif st_elem.tag == "SELECTION-TABLE-SNREF":
|
76
|
+
selection_table_refs.append(odxrequire(st_elem.get("SHORT-NAME")))
|
77
|
+
else:
|
78
|
+
odxraise()
|
79
|
+
|
80
|
+
return DynIdDefModeInfo(
|
81
|
+
def_mode=def_mode,
|
82
|
+
clear_dyn_def_message_ref=clear_dyn_def_message_ref,
|
83
|
+
clear_dyn_def_message_snref=clear_dyn_def_message_snref,
|
84
|
+
read_dyn_def_message_ref=read_dyn_def_message_ref,
|
85
|
+
read_dyn_def_message_snref=read_dyn_def_message_snref,
|
86
|
+
dyn_def_message_ref=dyn_def_message_ref,
|
87
|
+
dyn_def_message_snref=dyn_def_message_snref,
|
88
|
+
supported_dyn_ids=supported_dyn_ids,
|
89
|
+
selection_table_refs=selection_table_refs,
|
90
|
+
)
|
91
|
+
|
92
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
93
|
+
result: Dict[OdxLinkId, Any] = {}
|
94
|
+
|
95
|
+
return result
|
96
|
+
|
97
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
98
|
+
self._selection_tables = NamedItemList[Table]()
|
99
|
+
|
100
|
+
if self.clear_dyn_def_message_ref is not None:
|
101
|
+
self._clear_dyn_def_message = odxlinks.resolve(self.clear_dyn_def_message_ref, DiagComm)
|
102
|
+
|
103
|
+
if self.read_dyn_def_message_ref is not None:
|
104
|
+
self._read_dyn_def_message = odxlinks.resolve(self.read_dyn_def_message_ref, DiagComm)
|
105
|
+
|
106
|
+
if self.dyn_def_message_ref is not None:
|
107
|
+
self._dyn_def_message = odxlinks.resolve(self.dyn_def_message_ref, DiagComm)
|
108
|
+
|
109
|
+
# resolve the selection tables referenced using ODXLINK
|
110
|
+
for x in self.selection_table_refs:
|
111
|
+
if isinstance(x, OdxLinkRef):
|
112
|
+
self._selection_tables.append(odxlinks.resolve(x, Table))
|
113
|
+
|
114
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
115
|
+
diag_layer = odxrequire(context.diag_layer)
|
116
|
+
|
117
|
+
if self.clear_dyn_def_message_snref is not None:
|
118
|
+
self._clear_dyn_def_message = resolve_snref(self.clear_dyn_def_message_snref,
|
119
|
+
diag_layer.diag_comms, DiagComm)
|
120
|
+
|
121
|
+
if self.read_dyn_def_message_snref is not None:
|
122
|
+
self._read_dyn_def_message = resolve_snref(self.read_dyn_def_message_snref,
|
123
|
+
diag_layer.diag_comms, DiagComm)
|
124
|
+
|
125
|
+
if self.dyn_def_message_snref is not None:
|
126
|
+
self._dyn_def_message = resolve_snref(self.dyn_def_message_snref, diag_layer.diag_comms,
|
127
|
+
DiagComm)
|
128
|
+
|
129
|
+
if self._clear_dyn_def_message.diagnostic_class != DiagClassType.CLEAR_DYN_DEF_MESSAGE:
|
130
|
+
odxraise(
|
131
|
+
f"Diagnostic communication object of wrong type referenced: "
|
132
|
+
f"({odxrequire(self._clear_dyn_def_message.diagnostic_class).value} instead of "
|
133
|
+
f"CLEAR-DYN-DEF-MESSAGE)")
|
134
|
+
if self._read_dyn_def_message.diagnostic_class != DiagClassType.READ_DYN_DEFINED_MESSAGE:
|
135
|
+
odxraise(f"Diagnostic communication object of wrong type referenced: "
|
136
|
+
f"({odxrequire(self._read_dyn_def_message.diagnostic_class).value} instead of "
|
137
|
+
f"READ-DYN-DEFINED-MESSAGE)")
|
138
|
+
if self._dyn_def_message.diagnostic_class != DiagClassType.DYN_DEF_MESSAGE:
|
139
|
+
odxraise(f"Diagnostic communication object of wrong type referenced: "
|
140
|
+
f"({odxrequire(self._dyn_def_message.diagnostic_class).value} instead of "
|
141
|
+
f"DYN-DEF-MESSAGE)")
|
142
|
+
|
143
|
+
# resolve the remaining selection tables that are referenced via SNREF
|
144
|
+
for i, x in enumerate(self.selection_table_refs):
|
145
|
+
if isinstance(x, str):
|
146
|
+
ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
|
147
|
+
self._selection_tables.insert(i, resolve_snref(x, ddd_spec.tables, Table))
|
148
|
+
|
149
|
+
|
150
|
+
@dataclass
|
151
|
+
class DynDefinedSpec:
|
152
|
+
dyn_id_def_mode_infos: List[DynIdDefModeInfo]
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def from_et(et_element: ElementTree.Element,
|
156
|
+
doc_frags: List[OdxDocFragment]) -> "DynDefinedSpec":
|
157
|
+
dyn_id_def_mode_infos = [
|
158
|
+
DynIdDefModeInfo.from_et(x, doc_frags)
|
159
|
+
for x in et_element.iterfind("DYN-ID-DEF-MODE-INFOS/DYN-ID-DEF-MODE-INFO")
|
160
|
+
]
|
161
|
+
return DynDefinedSpec(dyn_id_def_mode_infos=dyn_id_def_mode_infos)
|
162
|
+
|
163
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
164
|
+
result: Dict[OdxLinkId, Any] = {}
|
165
|
+
|
166
|
+
for didmi in self.dyn_id_def_mode_infos:
|
167
|
+
result.update(didmi._build_odxlinks())
|
168
|
+
|
169
|
+
return result
|
170
|
+
|
171
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
172
|
+
for didmi in self.dyn_id_def_mode_infos:
|
173
|
+
didmi._resolve_odxlinks(odxlinks)
|
174
|
+
|
175
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
176
|
+
for didmi in self.dyn_id_def_mode_infos:
|
177
|
+
didmi._resolve_snrefs(context)
|
odxtools/dynenddopref.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import List, Optional, overload
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .exceptions import odxraise, odxrequire
|
7
|
+
from .odxlink import OdxDocFragment, OdxLinkRef
|
8
|
+
from .utils import dataclass_fields_asdict
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class DynEndDopRef(OdxLinkRef):
|
13
|
+
termination_value_raw: str
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
@overload
|
17
|
+
def from_et(et_element: None, source_doc_frags: List[OdxDocFragment]) -> None:
|
18
|
+
...
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
@overload
|
22
|
+
def from_et(et_element: ElementTree.Element,
|
23
|
+
source_doc_frags: List[OdxDocFragment]) -> "DynEndDopRef":
|
24
|
+
...
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def from_et(et_element: Optional[ElementTree.Element],
|
28
|
+
source_doc_frags: List[OdxDocFragment]) -> Optional["DynEndDopRef"]:
|
29
|
+
|
30
|
+
if et_element is None:
|
31
|
+
odxraise("Mandatory DYN-END-DOP-REF tag is missing")
|
32
|
+
return None
|
33
|
+
|
34
|
+
kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, source_doc_frags))
|
35
|
+
|
36
|
+
termination_value_raw = odxrequire(et_element.findtext("TERMINATION-VALUE"))
|
37
|
+
|
38
|
+
return DynEndDopRef(termination_value_raw=termination_value_raw, **kwargs)
|
odxtools/ecuvariantmatcher.py
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
from enum import Enum
|
3
3
|
from typing import Dict, Generator, List, Optional
|
4
4
|
|
5
|
-
from .diaglayer import DiagLayer
|
6
|
-
from .diaglayertype import DiagLayerType
|
5
|
+
from .diaglayers.diaglayer import DiagLayer
|
6
|
+
from .diaglayers.diaglayertype import DiagLayerType
|
7
|
+
from .diaglayers.ecuvariant import EcuVariant
|
7
8
|
from .diagservice import DiagService
|
8
|
-
from .exceptions import OdxError, odxassert
|
9
|
+
from .exceptions import OdxError, odxassert, odxrequire
|
9
10
|
from .matchingparameter import MatchingParameter
|
10
11
|
from .odxtypes import ParameterValue
|
11
12
|
from .response import Response
|
@@ -42,9 +43,7 @@ class EcuVariantMatcher:
|
|
42
43
|
@staticmethod
|
43
44
|
def get_ident_service(diag_layer: DiagLayer, matching_param: MatchingParameter) -> DiagService:
|
44
45
|
service_name = matching_param.diag_comm_snref
|
45
|
-
|
46
|
-
service = diag_layer.services[service_name]
|
47
|
-
odxassert(isinstance(service, DiagService))
|
46
|
+
service = odxrequire(diag_layer.services.get(service_name))
|
48
47
|
return service
|
49
48
|
|
50
49
|
@staticmethod
|
@@ -89,7 +88,7 @@ class EcuVariantMatcher:
|
|
89
88
|
raise OdxError(f"The snref or snpathref '{matching_param.out_param_if}' is cannot be \
|
90
89
|
resolved for any positive or negative response.")
|
91
90
|
|
92
|
-
def __init__(self, ecu_variant_candidates: List[
|
91
|
+
def __init__(self, ecu_variant_candidates: List[EcuVariant], use_cache: bool = True):
|
93
92
|
|
94
93
|
self.ecus = ecu_variant_candidates
|
95
94
|
for ecu in self.ecus:
|
odxtools/element.py
CHANGED
@@ -2,42 +2,40 @@ from dataclasses import dataclass
|
|
2
2
|
from typing import List, Optional
|
3
3
|
from xml.etree import ElementTree
|
4
4
|
|
5
|
+
from .description import Description
|
5
6
|
from .exceptions import odxrequire
|
6
7
|
from .odxlink import OdxDocFragment, OdxLinkId
|
7
|
-
from .utils import
|
8
|
+
from .utils import dataclass_fields_asdict
|
8
9
|
|
9
10
|
|
10
11
|
@dataclass
|
11
12
|
class NamedElement:
|
12
13
|
short_name: str
|
13
14
|
long_name: Optional[str]
|
14
|
-
description: Optional[
|
15
|
+
description: Optional[Description]
|
15
16
|
|
16
17
|
@staticmethod
|
17
|
-
def from_et(
|
18
|
-
et_element: ElementTree.Element,
|
19
|
-
doc_frags: List[OdxDocFragment],
|
20
|
-
) -> "NamedElement":
|
18
|
+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "NamedElement":
|
21
19
|
|
22
20
|
return NamedElement(
|
23
21
|
short_name=odxrequire(et_element.findtext("SHORT-NAME")),
|
24
22
|
long_name=et_element.findtext("LONG-NAME"),
|
25
|
-
description=
|
23
|
+
description=Description.from_et(et_element.find("DESC"), doc_frags),
|
26
24
|
)
|
27
25
|
|
28
26
|
|
29
27
|
@dataclass
|
30
28
|
class IdentifiableElement(NamedElement):
|
31
29
|
odx_id: OdxLinkId
|
30
|
+
oid: Optional[str]
|
32
31
|
|
33
32
|
@staticmethod
|
34
|
-
def from_et(
|
35
|
-
|
36
|
-
doc_frags: List[OdxDocFragment],
|
37
|
-
) -> "IdentifiableElement":
|
33
|
+
def from_et(et_element: ElementTree.Element,
|
34
|
+
doc_frags: List[OdxDocFragment]) -> "IdentifiableElement":
|
38
35
|
|
39
36
|
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
|
38
|
+
odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags))
|
39
|
+
oid = et_element.get("OID")
|
40
|
+
|
41
|
+
return IdentifiableElement(**kwargs, odx_id=odx_id, oid=oid)
|
odxtools/encodestate.py
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import warnings
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import TYPE_CHECKING,
|
4
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
|
5
5
|
|
6
|
-
from .exceptions import OdxWarning
|
6
|
+
from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
|
7
|
+
from .odxtypes import AtomicOdxType, DataType, ParameterValue
|
8
|
+
|
9
|
+
try:
|
10
|
+
import bitstruct.c as bitstruct
|
11
|
+
except ImportError:
|
12
|
+
import bitstruct
|
7
13
|
|
8
14
|
if TYPE_CHECKING:
|
9
|
-
from .
|
15
|
+
from .parameters.parameter import Parameter
|
10
16
|
|
11
17
|
|
12
18
|
@dataclass
|
@@ -15,10 +21,23 @@ class EncodeState:
|
|
15
21
|
"""
|
16
22
|
|
17
23
|
#: payload that has been constructed so far
|
18
|
-
coded_message: bytearray
|
24
|
+
coded_message: bytearray = field(default_factory=bytearray)
|
25
|
+
|
26
|
+
#: the bits of the payload that are used
|
27
|
+
used_mask: bytearray = field(default_factory=bytearray)
|
28
|
+
|
29
|
+
#: The absolute position in bytes from the beginning of the PDU to
|
30
|
+
#: which relative positions refer to, e.g., the beginning of the
|
31
|
+
#: structure.
|
32
|
+
origin_byte_position: int = 0
|
33
|
+
|
34
|
+
#: The absolute position in bytes from the beginning of the PDU
|
35
|
+
#: where the next object ought to be placed into the PDU
|
36
|
+
cursor_byte_position: int = 0
|
19
37
|
|
20
|
-
#:
|
21
|
-
|
38
|
+
#: The bit position [0-7] where the next object ought to be
|
39
|
+
#: placed into the PDU
|
40
|
+
cursor_bit_position: int = 0
|
22
41
|
|
23
42
|
#: If encoding a response: request that triggered the response
|
24
43
|
triggering_request: Optional[bytes] = None
|
@@ -28,31 +47,189 @@ class EncodeState:
|
|
28
47
|
length_keys: Dict[str, int] = field(default_factory=dict)
|
29
48
|
|
30
49
|
#: Mapping from the short name of a table-key parameter to the
|
31
|
-
#: corresponding row of the table (specified by
|
32
|
-
|
50
|
+
#: short name of the corresponding row of the table (specified by
|
51
|
+
#: TableKeyParameter)
|
52
|
+
table_keys: Dict[str, str] = field(default_factory=dict)
|
53
|
+
|
54
|
+
#: The cursor position where a given length- or table key is located
|
55
|
+
#: in the PDU
|
56
|
+
key_pos: Dict[str, int] = field(default_factory=dict)
|
33
57
|
|
34
58
|
#: Flag whether we are currently the last parameter of the PDU
|
35
|
-
#: (needed for MinMaxLengthType)
|
36
|
-
is_end_of_pdu: bool =
|
59
|
+
#: (needed for MinMaxLengthType, EndOfPduField, etc.)
|
60
|
+
is_end_of_pdu: bool = True
|
61
|
+
|
62
|
+
#: list of parameters that have been encoded so far. The journal
|
63
|
+
#: is used by some types of parameters which depend on the values of
|
64
|
+
#: other parameters; e.g., environment data description parameters
|
65
|
+
journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
|
66
|
+
|
67
|
+
#: If this is True, specifying unknown parameters for encoding
|
68
|
+
#: will raise an OdxError exception in strict mode.
|
69
|
+
allow_unknown_parameters = False
|
70
|
+
|
71
|
+
def __post_init__(self) -> None:
|
72
|
+
# if a coded message has been specified, but no used_mask, we
|
73
|
+
# assume that all of the bits of the coded message are
|
74
|
+
# currently used.
|
75
|
+
if len(self.coded_message) > len(self.used_mask):
|
76
|
+
self.used_mask += b'\xff' * (len(self.coded_message) - len(self.used_mask))
|
77
|
+
if len(self.coded_message) < len(self.used_mask):
|
78
|
+
odxraise(f"The specified bit mask 0x{self.used_mask.hex()} for used bits "
|
79
|
+
f"is not suitable for representing the coded_message "
|
80
|
+
f"0x{self.coded_message.hex()}")
|
81
|
+
self.used_mask = self.used_mask[:len(self.coded_message)]
|
82
|
+
|
83
|
+
def emplace_atomic_value(
|
84
|
+
self,
|
85
|
+
*,
|
86
|
+
internal_value: AtomicOdxType,
|
87
|
+
bit_length: int,
|
88
|
+
base_data_type: DataType,
|
89
|
+
is_highlow_byte_order: bool,
|
90
|
+
used_mask: Optional[bytes],
|
91
|
+
) -> None:
|
92
|
+
"""Convert the internal_value to bytes and emplace this into the PDU"""
|
93
|
+
|
94
|
+
raw_value: AtomicOdxType
|
95
|
+
|
96
|
+
# Check that bytes and strings actually fit into the bit length
|
97
|
+
if base_data_type == DataType.A_BYTEFIELD:
|
98
|
+
if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
|
99
|
+
odxraise()
|
100
|
+
if 8 * len(internal_value) > bit_length:
|
101
|
+
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
|
102
|
+
f"({len(internal_value)} bytes)."
|
103
|
+
f" The maximum length is {bit_length//8}.")
|
104
|
+
raw_value = bytes(internal_value)
|
105
|
+
elif base_data_type == DataType.A_ASCIISTRING:
|
106
|
+
if not isinstance(internal_value, str):
|
107
|
+
odxraise()
|
108
|
+
|
109
|
+
# The spec says ASCII, meaning only byte values 0-127.
|
110
|
+
# But in practice, vendors use iso-8859-1, aka latin-1
|
111
|
+
# reason being iso-8859-1 never fails since it has a valid
|
112
|
+
# character mapping for every possible byte sequence.
|
113
|
+
raw_value = internal_value.encode("iso-8859-1")
|
114
|
+
|
115
|
+
if 8 * len(raw_value) > bit_length:
|
116
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
117
|
+
f" The maximum number of characters is {bit_length//8}.")
|
118
|
+
elif base_data_type == DataType.A_UTF8STRING:
|
119
|
+
if not isinstance(internal_value, str):
|
120
|
+
odxraise()
|
121
|
+
|
122
|
+
raw_value = internal_value.encode("utf-8")
|
123
|
+
|
124
|
+
if 8 * len(raw_value) > bit_length:
|
125
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
126
|
+
f" The maximum number of bytes is {bit_length//8}.")
|
37
127
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
128
|
+
elif base_data_type == DataType.A_UNICODE2STRING:
|
129
|
+
if not isinstance(internal_value, str):
|
130
|
+
odxraise()
|
131
|
+
|
132
|
+
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
133
|
+
raw_value = internal_value.encode(text_encoding)
|
134
|
+
|
135
|
+
if 8 * len(raw_value) > bit_length:
|
136
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
137
|
+
f" The maximum number of characters is {bit_length//16}.")
|
138
|
+
else:
|
139
|
+
raw_value = internal_value
|
140
|
+
|
141
|
+
# If the bit length is zero, return empty bytes
|
142
|
+
if bit_length == 0:
|
143
|
+
if (base_data_type.value in [
|
144
|
+
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
145
|
+
] and base_data_type.value != 0):
|
146
|
+
odxraise(
|
147
|
+
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
|
148
|
+
EncodeError)
|
149
|
+
self.emplace_bytes(b'')
|
150
|
+
return
|
151
|
+
|
152
|
+
char = base_data_type.bitstruct_format_letter
|
153
|
+
padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
|
154
|
+
odxassert((0 <= padding and padding < 8 and
|
155
|
+
(padding + bit_length + self.cursor_bit_position) % 8 == 0),
|
156
|
+
f"Incorrect padding {padding}")
|
157
|
+
left_pad = f"p{padding}" if padding > 0 else ""
|
158
|
+
|
159
|
+
# actually encode the value
|
160
|
+
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
|
161
|
+
|
162
|
+
# create the raw mask of used bits for numeric objects
|
163
|
+
used_mask_raw = used_mask
|
164
|
+
if base_data_type in [DataType.A_INT32, DataType.A_UINT32
|
165
|
+
] and (self.cursor_bit_position != 0 or
|
166
|
+
(self.cursor_bit_position + bit_length) % 8 != 0):
|
167
|
+
if used_mask is None:
|
168
|
+
tmp = (1 << bit_length) - 1
|
169
|
+
else:
|
170
|
+
tmp = int.from_bytes(used_mask, "big")
|
171
|
+
tmp <<= self.cursor_bit_position
|
172
|
+
|
173
|
+
used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
|
174
|
+
|
175
|
+
# apply byte order to numeric objects
|
176
|
+
if not is_highlow_byte_order and base_data_type in [
|
177
|
+
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
178
|
+
]:
|
179
|
+
coded = coded[::-1]
|
180
|
+
|
181
|
+
if used_mask_raw is not None:
|
182
|
+
used_mask_raw = used_mask_raw[::-1]
|
183
|
+
|
184
|
+
self.cursor_bit_position = 0
|
185
|
+
self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
|
186
|
+
|
187
|
+
def emplace_bytes(self,
|
188
|
+
new_data: bytes,
|
189
|
+
obj_name: Optional[str] = None,
|
190
|
+
obj_used_mask: Optional[bytes] = None) -> None:
|
191
|
+
if self.cursor_bit_position != 0:
|
192
|
+
odxraise("EncodeState.emplace_bytes can only be called "
|
193
|
+
"for a bit position of 0!", RuntimeError)
|
194
|
+
|
195
|
+
pos = self.cursor_byte_position
|
44
196
|
|
45
197
|
# Make blob longer if necessary
|
46
198
|
min_length = pos + len(new_data)
|
47
199
|
if len(self.coded_message) < min_length:
|
48
|
-
|
200
|
+
pad = b'\x00' * (min_length - len(self.coded_message))
|
201
|
+
self.coded_message += pad
|
202
|
+
self.used_mask += pad
|
203
|
+
|
204
|
+
if obj_used_mask is None:
|
205
|
+
# Happy path for when no obj_used_mask has been
|
206
|
+
# specified. In this case we assume that all bits of the
|
207
|
+
# new data to be emplaced are used.
|
208
|
+
n = len(new_data)
|
49
209
|
|
50
|
-
|
51
|
-
# insert byte value
|
52
|
-
if self.coded_message[byte_idx_rpc] & new_data[byte_idx_val] != 0:
|
210
|
+
if self.used_mask[pos:pos + n] != b'\x00' * n:
|
53
211
|
warnings.warn(
|
54
|
-
f"
|
212
|
+
f"Overlapping objects detected in between bytes {pos} and "
|
213
|
+
f"{pos+n}",
|
55
214
|
OdxWarning,
|
56
215
|
stacklevel=1,
|
57
216
|
)
|
58
|
-
self.coded_message[
|
217
|
+
self.coded_message[pos:pos + n] = new_data
|
218
|
+
self.used_mask[pos:pos + n] = b'\xff' * n
|
219
|
+
else:
|
220
|
+
# insert data the hard way, i.e. we have to look at each
|
221
|
+
# individual byte to determine if it has already been used
|
222
|
+
# somewhere else (it would be nice if bytearrays supported
|
223
|
+
# bitwise operations!)
|
224
|
+
for i in range(len(new_data)):
|
225
|
+
if self.used_mask[pos + i] & obj_used_mask[i] != 0:
|
226
|
+
warnings.warn(
|
227
|
+
f"Overlapping objects detected at position {pos + i}",
|
228
|
+
OdxWarning,
|
229
|
+
stacklevel=1,
|
230
|
+
)
|
231
|
+
self.coded_message[pos + i] &= ~obj_used_mask[i]
|
232
|
+
self.coded_message[pos + i] |= new_data[i] & obj_used_mask[i]
|
233
|
+
self.used_mask[pos + i] |= obj_used_mask[i]
|
234
|
+
|
235
|
+
self.cursor_byte_position += len(new_data)
|
odxtools/endofpdufield.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional
|
3
|
+
from typing import List, Optional, Sequence
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
+
from typing_extensions import override
|
7
|
+
|
6
8
|
from .decodestate import DecodeState
|
7
9
|
from .encodestate import EncodeState
|
8
10
|
from .exceptions import EncodeError, odxassert, odxraise
|
@@ -39,30 +41,39 @@ class EndOfPduField(Field):
|
|
39
41
|
|
40
42
|
return eopf
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
encode_state
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
"Is there an error in reading the .odx?", EncodeError)
|
52
|
-
if not isinstance(physical_values, list):
|
44
|
+
@override
|
45
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
46
|
+
encode_state: EncodeState) -> None:
|
47
|
+
odxassert(not encode_state.cursor_bit_position,
|
48
|
+
"No bit position can be specified for end-of-pdu fields!")
|
49
|
+
odxassert(encode_state.is_end_of_pdu,
|
50
|
+
"End-of-pdu fields can only be located at the end of PDUs!")
|
51
|
+
|
52
|
+
if not isinstance(physical_value, Sequence):
|
53
53
|
odxraise(
|
54
|
-
f"
|
55
|
-
f"
|
54
|
+
f"Invalid type {type(physical_value).__name__} of physical "
|
55
|
+
f"value for end-of-pdu field, expected a list", EncodeError)
|
56
|
+
return
|
57
|
+
|
58
|
+
orig_is_end_of_pdu = encode_state.is_end_of_pdu
|
59
|
+
encode_state.is_end_of_pdu = False
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return coded_message
|
61
|
+
for i, value in enumerate(physical_value):
|
62
|
+
if i == len(physical_value) - 1:
|
63
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
61
64
|
|
65
|
+
self.structure.encode_into_pdu(value, encode_state)
|
66
|
+
|
67
|
+
encode_state.is_end_of_pdu = orig_is_end_of_pdu
|
68
|
+
|
69
|
+
@override
|
62
70
|
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
63
71
|
odxassert(not decode_state.cursor_bit_position,
|
64
72
|
"No bit position can be specified for end-of-pdu fields!")
|
65
73
|
|
74
|
+
orig_origin = decode_state.origin_byte_position
|
75
|
+
decode_state.origin_byte_position = decode_state.cursor_byte_position
|
76
|
+
|
66
77
|
result: List[ParameterValue] = []
|
67
78
|
while decode_state.cursor_byte_position < len(decode_state.coded_message):
|
68
79
|
# ATTENTION: the ODX specification is very misleading
|
@@ -71,4 +82,6 @@ class EndOfPduField(Field):
|
|
71
82
|
# repeated are identical, not their values
|
72
83
|
result.append(self.structure.decode_from_pdu(decode_state))
|
73
84
|
|
85
|
+
decode_state.origin_byte_position = orig_origin
|
86
|
+
|
74
87
|
return result
|
odxtools/environmentdata.py
CHANGED
@@ -11,7 +11,14 @@ from .utils import dataclass_fields_asdict
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class EnvironmentData(BasicStructure):
|
14
|
-
"""This class represents Environment Data that describes the
|
14
|
+
"""This class represents Environment Data that describes the
|
15
|
+
circumstances in which the error occurred.
|
16
|
+
|
17
|
+
This is one of the many multiplexer mechanisms specified by the
|
18
|
+
ODX standard, because an environment data parameter must only be
|
19
|
+
used if a DTC parameter has a certain set of values. (In this
|
20
|
+
sense, it is quite similar to NRC-CONST parameters.)
|
21
|
+
"""
|
15
22
|
|
16
23
|
all_value: Optional[bool]
|
17
24
|
dtc_values: List[int]
|