odxtools 6.6.1__py3-none-any.whl → 6.7.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 +5 -5
- odxtools/basicstructure.py +7 -8
- odxtools/cli/_parser_utils.py +15 -0
- odxtools/cli/_print_utils.py +4 -3
- odxtools/cli/browse.py +19 -14
- odxtools/cli/compare.py +24 -16
- odxtools/cli/decode.py +2 -1
- odxtools/cli/dummy_sub_parser.py +3 -1
- odxtools/cli/find.py +2 -1
- odxtools/cli/list.py +2 -1
- odxtools/cli/main.py +1 -0
- odxtools/cli/snoop.py +4 -1
- odxtools/comparaminstance.py +7 -5
- odxtools/compumethods/compumethod.py +2 -4
- odxtools/compumethods/compuscale.py +45 -5
- odxtools/compumethods/createanycompumethod.py +27 -35
- odxtools/compumethods/limit.py +70 -36
- odxtools/compumethods/linearcompumethod.py +68 -59
- odxtools/compumethods/tabintpcompumethod.py +19 -8
- odxtools/compumethods/texttablecompumethod.py +32 -36
- odxtools/dataobjectproperty.py +13 -10
- odxtools/decodestate.py +6 -3
- odxtools/determinenumberofitems.py +1 -1
- odxtools/diagcodedtype.py +5 -4
- odxtools/diagdatadictionaryspec.py +108 -83
- odxtools/diaglayer.py +75 -35
- odxtools/diaglayertype.py +17 -5
- odxtools/diagservice.py +1 -1
- odxtools/dopbase.py +4 -2
- odxtools/dtcdop.py +7 -5
- odxtools/dynamiclengthfield.py +6 -5
- odxtools/endofpdufield.py +4 -4
- odxtools/environmentdatadescription.py +4 -2
- odxtools/inputparam.py +1 -1
- odxtools/internalconstr.py +14 -5
- odxtools/isotp_state_machine.py +14 -6
- odxtools/message.py +1 -1
- odxtools/multiplexer.py +18 -13
- odxtools/multiplexercase.py +27 -5
- odxtools/multiplexerswitchkey.py +1 -1
- odxtools/nameditemlist.py +7 -6
- odxtools/odxlink.py +2 -2
- odxtools/odxtypes.py +56 -3
- odxtools/outputparam.py +2 -2
- odxtools/parameterinfo.py +12 -5
- odxtools/parameters/codedconstparameter.py +33 -12
- odxtools/parameters/createanyparameter.py +19 -193
- odxtools/parameters/dynamicparameter.py +21 -1
- odxtools/parameters/lengthkeyparameter.py +28 -4
- odxtools/parameters/matchingrequestparameter.py +27 -9
- odxtools/parameters/nrcconstparameter.py +34 -11
- odxtools/parameters/parameter.py +58 -32
- odxtools/parameters/parameterwithdop.py +28 -15
- odxtools/parameters/physicalconstantparameter.py +28 -4
- odxtools/parameters/reservedparameter.py +32 -18
- odxtools/parameters/systemparameter.py +25 -2
- odxtools/parameters/tableentryparameter.py +45 -6
- odxtools/parameters/tablekeyparameter.py +43 -10
- odxtools/parameters/tablestructparameter.py +36 -14
- odxtools/parameters/valueparameter.py +24 -2
- odxtools/paramlengthinfotype.py +4 -1
- odxtools/parentref.py +4 -1
- odxtools/scaleconstr.py +11 -5
- odxtools/statetransition.py +1 -1
- odxtools/staticfield.py +101 -0
- odxtools/table.py +2 -1
- odxtools/tablerow.py +11 -4
- odxtools/templates/macros/printDOP.xml.jinja2 +30 -34
- odxtools/templates/macros/printMux.xml.jinja2 +3 -2
- odxtools/templates/macros/printParam.xml.jinja2 +9 -9
- odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
- odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
- odxtools/uds.py +2 -2
- odxtools/version.py +2 -2
- odxtools/write_pdx_file.py +3 -3
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/METADATA +28 -16
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/RECORD +81 -79
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/WHEEL +1 -1
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/LICENSE +0 -0
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.6.1.dist-info → odxtools-6.7.0.dist-info}/top_level.txt +0 -0
odxtools/dataobjectproperty.py
CHANGED
@@ -59,20 +59,20 @@ class DataObjectProperty(DopBase):
|
|
59
59
|
compu_method = create_any_compu_method_from_et(
|
60
60
|
odxrequire(et_element.find("COMPU-METHOD")),
|
61
61
|
doc_frags,
|
62
|
-
diag_coded_type.base_data_type,
|
63
|
-
physical_type.base_data_type,
|
62
|
+
internal_type=diag_coded_type.base_data_type,
|
63
|
+
physical_type=physical_type.base_data_type,
|
64
64
|
)
|
65
65
|
unit_ref = OdxLinkRef.from_et(et_element.find("UNIT-REF"), doc_frags)
|
66
66
|
|
67
67
|
internal_constr = None
|
68
68
|
if (internal_constr_elem := et_element.find("INTERNAL-CONSTR")) is not None:
|
69
|
-
internal_constr = InternalConstr.
|
70
|
-
internal_constr_elem,
|
69
|
+
internal_constr = InternalConstr.constr_from_et(
|
70
|
+
internal_constr_elem, doc_frags, value_type=diag_coded_type.base_data_type)
|
71
71
|
|
72
72
|
physical_constr = None
|
73
73
|
if (physical_constr_elem := et_element.find("PHYS-CONSTR")) is not None:
|
74
|
-
physical_constr = InternalConstr.
|
75
|
-
physical_constr_elem,
|
74
|
+
physical_constr = InternalConstr.constr_from_et(
|
75
|
+
physical_constr_elem, doc_frags, value_type=physical_type.base_data_type)
|
76
76
|
|
77
77
|
return DataObjectProperty(
|
78
78
|
diag_coded_type=diag_coded_type,
|
@@ -120,14 +120,17 @@ class DataObjectProperty(DopBase):
|
|
120
120
|
|
121
121
|
return self.compu_method.convert_physical_to_internal(physical_value)
|
122
122
|
|
123
|
-
def convert_physical_to_bytes(self,
|
124
|
-
|
123
|
+
def convert_physical_to_bytes(self,
|
124
|
+
physical_value: Any,
|
125
|
+
encode_state: EncodeState,
|
126
|
+
bit_position: int = 0) -> bytes:
|
125
127
|
"""
|
126
128
|
Convert a physical representation of a parameter to a string bytes that can be send over the wire
|
127
129
|
"""
|
128
130
|
if not self.is_valid_physical_value(physical_value):
|
129
|
-
raise EncodeError(
|
130
|
-
|
131
|
+
raise EncodeError(
|
132
|
+
f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
|
133
|
+
f" is not a valid.")
|
131
134
|
|
132
135
|
internal_val = self.convert_physical_to_internal(physical_value)
|
133
136
|
return self.diag_coded_type.convert_internal_to_bytes(
|
odxtools/decodestate.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass, field
|
3
|
-
from typing import TYPE_CHECKING, Dict
|
3
|
+
from typing import TYPE_CHECKING, Dict, cast
|
4
4
|
|
5
5
|
import odxtools.exceptions as exceptions
|
6
6
|
|
@@ -72,7 +72,7 @@ class DecodeState:
|
|
72
72
|
"""
|
73
73
|
# If the bit length is zero, return "empty" values of each type
|
74
74
|
if bit_length == 0:
|
75
|
-
return base_data_type.
|
75
|
+
return base_data_type.python_type()
|
76
76
|
|
77
77
|
byte_length = (bit_length + self.cursor_bit_position + 7) // 8
|
78
78
|
if self.cursor_byte_position + byte_length > len(self.coded_message):
|
@@ -100,6 +100,7 @@ class DecodeState:
|
|
100
100
|
|
101
101
|
text_errors = 'strict' if exceptions.strict_mode else 'replace'
|
102
102
|
if base_data_type == DataType.A_ASCIISTRING:
|
103
|
+
assert isinstance(internal_value, (bytes, bytearray))
|
103
104
|
# The spec says ASCII, meaning only byte values 0-127.
|
104
105
|
# But in practice, vendors use iso-8859-1, aka latin-1
|
105
106
|
# reason being iso-8859-1 never fails since it has a valid
|
@@ -107,9 +108,11 @@ class DecodeState:
|
|
107
108
|
text_encoding = 'iso-8859-1'
|
108
109
|
internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
|
109
110
|
elif base_data_type == DataType.A_UTF8STRING:
|
111
|
+
assert isinstance(internal_value, (bytes, bytearray))
|
110
112
|
text_encoding = "utf-8"
|
111
113
|
internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
|
112
114
|
elif base_data_type == DataType.A_UNICODE2STRING:
|
115
|
+
assert isinstance(internal_value, (bytes, bytearray))
|
113
116
|
# For UTF-16, we need to manually decode the extracted
|
114
117
|
# bytes to a string
|
115
118
|
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
@@ -118,4 +121,4 @@ class DecodeState:
|
|
118
121
|
self.cursor_byte_position += byte_length
|
119
122
|
self.cursor_bit_position = 0
|
120
123
|
|
121
|
-
return internal_value
|
124
|
+
return cast(AtomicOdxType, internal_value)
|
odxtools/diagcodedtype.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import abc
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast
|
5
5
|
|
6
6
|
from .decodestate import ODX_TYPE_TO_FORMAT_LETTER, DecodeState
|
7
7
|
from .encodestate import EncodeState
|
@@ -111,9 +111,9 @@ class DiagCodedType(abc.ABC):
|
|
111
111
|
|
112
112
|
# If the bit length is zero, return empty bytes
|
113
113
|
if bit_length == 0:
|
114
|
-
if (base_data_type in [
|
114
|
+
if (base_data_type.value in [
|
115
115
|
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
|
116
|
-
] and base_data_type != 0):
|
116
|
+
] and base_data_type.value != 0):
|
117
117
|
raise EncodeError(
|
118
118
|
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
|
119
119
|
return b''
|
@@ -136,12 +136,13 @@ class DiagCodedType(abc.ABC):
|
|
136
136
|
]:
|
137
137
|
coded = coded[::-1]
|
138
138
|
|
139
|
-
return coded
|
139
|
+
return cast(bytes, coded)
|
140
140
|
|
141
141
|
def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
|
142
142
|
"""Helper method to get the minimal byte length.
|
143
143
|
(needed for LeadingLength- and MinMaxLengthType)
|
144
144
|
"""
|
145
|
+
byte_length: int = -1
|
145
146
|
# A_BYTEFIELD, A_ASCIISTRING, A_UNICODE2STRING, A_UTF8STRING
|
146
147
|
if self.base_data_type == DataType.A_BYTEFIELD:
|
147
148
|
byte_length = len(internal_value)
|
@@ -4,6 +4,7 @@ from itertools import chain
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
+
from .admindata import AdminData
|
7
8
|
from .basicstructure import BasicStructure
|
8
9
|
from .createsdgs import create_sdgs_from_et
|
9
10
|
from .dataobjectproperty import DataObjectProperty
|
@@ -14,11 +15,11 @@ from .endofpdufield import EndOfPduField
|
|
14
15
|
from .environmentdata import EnvironmentData
|
15
16
|
from .environmentdatadescription import EnvironmentDataDescription
|
16
17
|
from .exceptions import odxraise
|
17
|
-
from .globals import logger
|
18
18
|
from .multiplexer import Multiplexer
|
19
19
|
from .nameditemlist import NamedItemList
|
20
20
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
21
21
|
from .specialdatagroup import SpecialDataGroup
|
22
|
+
from .staticfield import StaticField
|
22
23
|
from .structure import Structure
|
23
24
|
from .table import Table
|
24
25
|
from .unitspec import UnitSpec
|
@@ -29,35 +30,55 @@ if TYPE_CHECKING:
|
|
29
30
|
|
30
31
|
@dataclass
|
31
32
|
class DiagDataDictionarySpec:
|
33
|
+
admin_data: Optional[AdminData]
|
32
34
|
dtc_dops: NamedItemList[DtcDop]
|
35
|
+
env_data_descs: NamedItemList[EnvironmentDataDescription]
|
33
36
|
data_object_props: NamedItemList[DataObjectProperty]
|
34
37
|
structures: NamedItemList[BasicStructure]
|
35
|
-
|
38
|
+
static_fields: NamedItemList[StaticField]
|
36
39
|
dynamic_length_fields: NamedItemList[DynamicLengthField]
|
37
|
-
|
38
|
-
|
39
|
-
env_datas: NamedItemList[EnvironmentData]
|
40
|
+
#dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
|
41
|
+
end_of_pdu_fields: NamedItemList[EndOfPduField]
|
40
42
|
muxs: NamedItemList[Multiplexer]
|
43
|
+
env_datas: NamedItemList[EnvironmentData]
|
41
44
|
unit_spec: Optional[UnitSpec]
|
45
|
+
tables: NamedItemList[Table]
|
42
46
|
sdgs: List[SpecialDataGroup]
|
43
47
|
|
44
48
|
def __post_init__(self) -> None:
|
45
49
|
self._all_data_object_properties: NamedItemList[DopBase] = NamedItemList(
|
46
50
|
chain(
|
47
51
|
self.dtc_dops,
|
52
|
+
self.env_data_descs,
|
48
53
|
self.data_object_props,
|
49
54
|
self.structures,
|
50
|
-
self.
|
55
|
+
self.static_fields,
|
51
56
|
self.dynamic_length_fields,
|
52
|
-
self.
|
53
|
-
self.
|
57
|
+
#self.dynamic_endmarker_fields,
|
58
|
+
self.end_of_pdu_fields,
|
54
59
|
self.muxs,
|
60
|
+
self.env_datas,
|
55
61
|
))
|
56
62
|
|
57
63
|
@staticmethod
|
58
64
|
def from_et(et_element: ElementTree.Element,
|
59
65
|
doc_frags: List[OdxDocFragment]) -> "DiagDataDictionarySpec":
|
60
|
-
|
66
|
+
admin_data = None
|
67
|
+
if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
|
68
|
+
admin_data = AdminData.from_et(admin_data_elem, doc_frags)
|
69
|
+
|
70
|
+
dtc_dops = []
|
71
|
+
for dtc_dop_elem in et_element.iterfind("DTC-DOPS/DTC-DOP"):
|
72
|
+
dtc_dop = DtcDop.from_et(dtc_dop_elem, doc_frags)
|
73
|
+
if not isinstance(dtc_dop, DtcDop):
|
74
|
+
odxraise()
|
75
|
+
dtc_dops.append(dtc_dop)
|
76
|
+
|
77
|
+
env_data_descs = [
|
78
|
+
EnvironmentDataDescription.from_et(env_data_desc_element, doc_frags)
|
79
|
+
for env_data_desc_element in et_element.iterfind("ENV-DATA-DESCS/ENV-DATA-DESC")
|
80
|
+
]
|
81
|
+
|
61
82
|
data_object_props = [
|
62
83
|
DataObjectProperty.from_et(dop_element, doc_frags)
|
63
84
|
for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
|
@@ -68,9 +89,9 @@ class DiagDataDictionarySpec:
|
|
68
89
|
for structure_element in et_element.iterfind("STRUCTURES/STRUCTURE")
|
69
90
|
]
|
70
91
|
|
71
|
-
|
72
|
-
|
73
|
-
for
|
92
|
+
static_fields = [
|
93
|
+
StaticField.from_et(dl_element, doc_frags)
|
94
|
+
for dl_element in et_element.iterfind("STATIC-FIELDS/STATIC-FIELD")
|
74
95
|
]
|
75
96
|
|
76
97
|
dynamic_length_fields = [
|
@@ -78,21 +99,20 @@ class DiagDataDictionarySpec:
|
|
78
99
|
for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
|
79
100
|
]
|
80
101
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
dtc_dops.append(dtc_dop)
|
102
|
+
# TODO: dynamic endmarker fields
|
103
|
+
#dynamic_endmarker_fields = [
|
104
|
+
# DynamicEndmarkerField.from_et(dl_element, doc_frags)
|
105
|
+
# for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
|
106
|
+
#]
|
87
107
|
|
88
|
-
|
89
|
-
|
90
|
-
for
|
108
|
+
end_of_pdu_fields = [
|
109
|
+
EndOfPduField.from_et(eofp_element, doc_frags)
|
110
|
+
for eofp_element in et_element.iterfind("END-OF-PDU-FIELDS/END-OF-PDU-FIELD")
|
91
111
|
]
|
92
112
|
|
93
|
-
|
94
|
-
|
95
|
-
for
|
113
|
+
muxs = [
|
114
|
+
Multiplexer.from_et(mux_element, doc_frags)
|
115
|
+
for mux_element in et_element.iterfind("MUXS/MUX")
|
96
116
|
]
|
97
117
|
|
98
118
|
env_data_elements = chain(
|
@@ -105,42 +125,32 @@ class DiagDataDictionarySpec:
|
|
105
125
|
for env_data_element in env_data_elements
|
106
126
|
]
|
107
127
|
|
108
|
-
muxs = [
|
109
|
-
Multiplexer.from_et(mux_element, doc_frags)
|
110
|
-
for mux_element in et_element.iterfind("MUXS/MUX")
|
111
|
-
]
|
112
|
-
|
113
128
|
if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
|
114
129
|
unit_spec = UnitSpec.from_et(spec_elem, doc_frags)
|
115
130
|
else:
|
116
131
|
unit_spec = None
|
117
132
|
|
118
|
-
|
119
|
-
|
120
|
-
("
|
121
|
-
|
122
|
-
(
|
123
|
-
"DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD",
|
124
|
-
"dynamic endmarker fields",
|
125
|
-
),
|
126
|
-
]:
|
127
|
-
num = len(list(et_element.iterfind(path)))
|
128
|
-
if num > 0:
|
129
|
-
logger.info(f"Not implemented: Did not parse {num} {name}.")
|
133
|
+
tables = [
|
134
|
+
Table.from_et(table_element, doc_frags)
|
135
|
+
for table_element in et_element.iterfind("TABLES/TABLE")
|
136
|
+
]
|
130
137
|
|
131
138
|
sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
|
132
139
|
|
133
140
|
return DiagDataDictionarySpec(
|
141
|
+
admin_data=admin_data,
|
142
|
+
dtc_dops=NamedItemList(dtc_dops),
|
143
|
+
env_data_descs=NamedItemList(env_data_descs),
|
134
144
|
data_object_props=NamedItemList(data_object_props),
|
135
145
|
structures=NamedItemList(structures),
|
136
|
-
|
146
|
+
static_fields=NamedItemList(static_fields),
|
137
147
|
dynamic_length_fields=NamedItemList(dynamic_length_fields),
|
138
|
-
|
148
|
+
#dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
|
149
|
+
end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
|
150
|
+
muxs=NamedItemList(muxs),
|
151
|
+
env_datas=NamedItemList(env_datas),
|
139
152
|
unit_spec=unit_spec,
|
140
153
|
tables=NamedItemList(tables),
|
141
|
-
env_data_descs=NamedItemList(env_data_descs),
|
142
|
-
env_datas=NamedItemList(env_datas),
|
143
|
-
muxs=NamedItemList(muxs),
|
144
154
|
sdgs=sdgs,
|
145
155
|
)
|
146
156
|
|
@@ -148,81 +158,96 @@ class DiagDataDictionarySpec:
|
|
148
158
|
# note that DataDictionarySpec objects do not exhibit an ODXLINK id.
|
149
159
|
odxlinks = {}
|
150
160
|
|
151
|
-
|
152
|
-
odxlinks.update(
|
161
|
+
if self.admin_data is not None:
|
162
|
+
odxlinks.update(self.admin_data._build_odxlinks())
|
153
163
|
for dtc_dop in self.dtc_dops:
|
154
164
|
odxlinks.update(dtc_dop._build_odxlinks())
|
155
165
|
for env_data_desc in self.env_data_descs:
|
156
166
|
odxlinks.update(env_data_desc._build_odxlinks())
|
157
|
-
for
|
158
|
-
odxlinks.update(
|
159
|
-
for mux in self.muxs:
|
160
|
-
odxlinks.update(mux._build_odxlinks())
|
161
|
-
for sdg in self.sdgs:
|
162
|
-
odxlinks.update(sdg._build_odxlinks())
|
167
|
+
for data_object_prop in self.data_object_props:
|
168
|
+
odxlinks.update(data_object_prop._build_odxlinks())
|
163
169
|
for structure in self.structures:
|
164
170
|
odxlinks.update(structure._build_odxlinks())
|
171
|
+
for static_field in self.static_fields:
|
172
|
+
odxlinks.update(static_field._build_odxlinks())
|
165
173
|
for dynamic_length_field in self.dynamic_length_fields:
|
166
174
|
odxlinks.update(dynamic_length_field._build_odxlinks())
|
175
|
+
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
|
176
|
+
# odxlinks.update(dynamic_endmarker_field._build_odxlinks())
|
167
177
|
for end_of_pdu_field in self.end_of_pdu_fields:
|
168
178
|
odxlinks.update(end_of_pdu_field._build_odxlinks())
|
169
|
-
for
|
170
|
-
odxlinks.update(
|
171
|
-
|
179
|
+
for mux in self.muxs:
|
180
|
+
odxlinks.update(mux._build_odxlinks())
|
181
|
+
for env_data in self.env_datas:
|
182
|
+
odxlinks.update(env_data._build_odxlinks())
|
172
183
|
if self.unit_spec is not None:
|
173
184
|
odxlinks.update(self.unit_spec._build_odxlinks())
|
185
|
+
for table in self.tables:
|
186
|
+
odxlinks.update(table._build_odxlinks())
|
187
|
+
for sdg in self.sdgs:
|
188
|
+
odxlinks.update(sdg._build_odxlinks())
|
174
189
|
|
175
190
|
return odxlinks
|
176
191
|
|
177
192
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
178
|
-
|
179
|
-
|
193
|
+
if self.admin_data is not None:
|
194
|
+
self.admin_data._resolve_odxlinks(odxlinks)
|
180
195
|
for dtc_dop in self.dtc_dops:
|
181
196
|
dtc_dop._resolve_odxlinks(odxlinks)
|
197
|
+
for env_data_desc in self.env_data_descs:
|
198
|
+
env_data_desc._resolve_odxlinks(odxlinks)
|
199
|
+
for data_object_prop in self.data_object_props:
|
200
|
+
data_object_prop._resolve_odxlinks(odxlinks)
|
201
|
+
for structure in self.structures:
|
202
|
+
structure._resolve_odxlinks(odxlinks)
|
203
|
+
for static_field in self.static_fields:
|
204
|
+
static_field._resolve_odxlinks(odxlinks)
|
182
205
|
for dynamic_length_field in self.dynamic_length_fields:
|
183
206
|
dynamic_length_field._resolve_odxlinks(odxlinks)
|
207
|
+
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
|
208
|
+
# dynamic_endmarker_field._resolve_odxlinks(odxlinks)
|
184
209
|
for end_of_pdu_field in self.end_of_pdu_fields:
|
185
210
|
end_of_pdu_field._resolve_odxlinks(odxlinks)
|
186
|
-
for env_data_desc in self.env_data_descs:
|
187
|
-
env_data_desc._resolve_odxlinks(odxlinks)
|
188
|
-
for env_data in self.env_datas:
|
189
|
-
env_data._resolve_odxlinks(odxlinks)
|
190
211
|
for mux in self.muxs:
|
191
212
|
mux._resolve_odxlinks(odxlinks)
|
192
|
-
for
|
193
|
-
|
194
|
-
for structure in self.structures:
|
195
|
-
structure._resolve_odxlinks(odxlinks)
|
196
|
-
for table in self.tables:
|
197
|
-
table._resolve_odxlinks(odxlinks)
|
198
|
-
|
213
|
+
for env_data in self.env_datas:
|
214
|
+
env_data._resolve_odxlinks(odxlinks)
|
199
215
|
if self.unit_spec is not None:
|
200
216
|
self.unit_spec._resolve_odxlinks(odxlinks)
|
217
|
+
for table in self.tables:
|
218
|
+
table._resolve_odxlinks(odxlinks)
|
219
|
+
for sdg in self.sdgs:
|
220
|
+
sdg._resolve_odxlinks(odxlinks)
|
201
221
|
|
202
222
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
203
|
-
|
204
|
-
|
223
|
+
if self.admin_data is not None:
|
224
|
+
self.admin_data._resolve_snrefs(diag_layer)
|
205
225
|
for dtc_dop in self.dtc_dops:
|
206
226
|
dtc_dop._resolve_snrefs(diag_layer)
|
227
|
+
for env_data_desc in self.env_data_descs:
|
228
|
+
env_data_desc._resolve_snrefs(diag_layer)
|
229
|
+
for data_object_prop in self.data_object_props:
|
230
|
+
data_object_prop._resolve_snrefs(diag_layer)
|
231
|
+
for structure in self.structures:
|
232
|
+
structure._resolve_snrefs(diag_layer)
|
233
|
+
for static_field in self.static_fields:
|
234
|
+
static_field._resolve_snrefs(diag_layer)
|
207
235
|
for dynamic_length_field in self.dynamic_length_fields:
|
208
236
|
dynamic_length_field._resolve_snrefs(diag_layer)
|
237
|
+
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
|
238
|
+
# dynamic_endmarker_field._resolve_snrefs(diag_layer)
|
209
239
|
for end_of_pdu_field in self.end_of_pdu_fields:
|
210
240
|
end_of_pdu_field._resolve_snrefs(diag_layer)
|
211
|
-
for env_data_desc in self.env_data_descs:
|
212
|
-
env_data_desc._resolve_snrefs(diag_layer)
|
213
|
-
for env_data in self.env_datas:
|
214
|
-
env_data._resolve_snrefs(diag_layer)
|
215
241
|
for mux in self.muxs:
|
216
242
|
mux._resolve_snrefs(diag_layer)
|
217
|
-
for
|
218
|
-
|
219
|
-
for structure in self.structures:
|
220
|
-
structure._resolve_snrefs(diag_layer)
|
221
|
-
for table in self.tables:
|
222
|
-
table._resolve_snrefs(diag_layer)
|
223
|
-
|
243
|
+
for env_data in self.env_datas:
|
244
|
+
env_data._resolve_snrefs(diag_layer)
|
224
245
|
if self.unit_spec is not None:
|
225
246
|
self.unit_spec._resolve_snrefs(diag_layer)
|
247
|
+
for table in self.tables:
|
248
|
+
table._resolve_snrefs(diag_layer)
|
249
|
+
for sdg in self.sdgs:
|
250
|
+
sdg._resolve_snrefs(diag_layer)
|
226
251
|
|
227
252
|
@property
|
228
253
|
def all_data_object_properties(self) -> NamedItemList[DopBase]:
|
odxtools/diaglayer.py
CHANGED
@@ -20,7 +20,7 @@ from .diaglayerraw import DiagLayerRaw
|
|
20
20
|
from .diaglayertype import DiagLayerType
|
21
21
|
from .diagservice import DiagService
|
22
22
|
from .ecuvariantpattern import EcuVariantPattern
|
23
|
-
from .exceptions import DecodeError, OdxWarning, odxassert
|
23
|
+
from .exceptions import DecodeError, OdxWarning, odxassert, odxraise
|
24
24
|
from .functionalclass import FunctionalClass
|
25
25
|
from .message import Message
|
26
26
|
from .nameditemlist import NamedItemList, OdxNamed
|
@@ -37,7 +37,6 @@ from .table import Table
|
|
37
37
|
from .unitgroup import UnitGroup
|
38
38
|
from .unitspec import UnitSpec
|
39
39
|
|
40
|
-
T = TypeVar("T")
|
41
40
|
TNamed = TypeVar("TNamed", bound=OdxNamed)
|
42
41
|
|
43
42
|
PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
|
@@ -54,6 +53,9 @@ class DiagLayer:
|
|
54
53
|
|
55
54
|
diag_layer_raw: DiagLayerRaw
|
56
55
|
|
56
|
+
def __post_init__(self) -> None:
|
57
|
+
self._global_negative_responses: NamedItemList[Response]
|
58
|
+
|
57
59
|
@staticmethod
|
58
60
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayer":
|
59
61
|
diag_layer_raw = DiagLayerRaw.from_et(et_element, doc_frags)
|
@@ -193,9 +195,11 @@ class DiagLayer:
|
|
193
195
|
# inherited objects. To me, this seems rather inelegant, but
|
194
196
|
# hey, it's described like this in the standard.
|
195
197
|
self._diag_data_dictionary_spec = DiagDataDictionarySpec(
|
198
|
+
admin_data=None,
|
196
199
|
data_object_props=dops,
|
197
200
|
dtc_dops=dtc_dops,
|
198
201
|
structures=structures,
|
202
|
+
static_fields=NamedItemList(),
|
199
203
|
end_of_pdu_fields=end_of_pdu_fields,
|
200
204
|
dynamic_length_fields=dynamic_length_fields,
|
201
205
|
tables=tables,
|
@@ -350,7 +354,7 @@ class DiagLayer:
|
|
350
354
|
return self._global_negative_responses
|
351
355
|
|
352
356
|
@property
|
353
|
-
@deprecated(details="use diag_data_dictionary_spec.tables")
|
357
|
+
@deprecated(details="use diag_data_dictionary_spec.tables") # type: ignore[misc]
|
354
358
|
def tables(self) -> NamedItemList[Table]:
|
355
359
|
return self.diag_data_dictionary_spec.tables
|
356
360
|
|
@@ -389,9 +393,9 @@ class DiagLayer:
|
|
389
393
|
|
390
394
|
def _compute_available_objects(
|
391
395
|
self,
|
392
|
-
get_local_objects: Callable[["DiagLayer"], Iterable[
|
396
|
+
get_local_objects: Callable[["DiagLayer"], Iterable[TNamed]],
|
393
397
|
get_not_inherited: Callable[[ParentRef], Iterable[str]],
|
394
|
-
) -> Iterable[
|
398
|
+
) -> Iterable[TNamed]:
|
395
399
|
"""Helper method to compute the set of all objects applicable
|
396
400
|
to the DiagLayer if these objects are subject to the value
|
397
401
|
inheritance mechanism
|
@@ -410,31 +414,72 @@ class DiagLayer:
|
|
410
414
|
|
411
415
|
"""
|
412
416
|
|
413
|
-
|
417
|
+
local_objects = get_local_objects(self)
|
418
|
+
local_object_short_names = {x.short_name for x in local_objects}
|
419
|
+
result_dict: Dict[str, Tuple[TNamed, DiagLayer]] = {}
|
414
420
|
|
415
421
|
# populate the result dictionary with the inherited objects
|
416
|
-
|
417
|
-
# TODO (?): make sure that there are no "illegal" collisions
|
418
|
-
# i.e., different objects with the same short name stemming
|
419
|
-
# from parent layers exhibiting the same priority that are not
|
420
|
-
# overwritten by a locally defined object. (IMO, this is quite
|
421
|
-
# a corner case.)
|
422
|
-
for parent_ref in self._get_parent_refs_sorted_by_priority():
|
422
|
+
for parent_ref in self._get_parent_refs_sorted_by_priority(reverse=True):
|
423
423
|
parent_dl = parent_ref.layer
|
424
|
-
for dc in parent_dl._compute_available_objects(get_local_objects, get_not_inherited):
|
425
|
-
result_dict[dc.short_name] = dc # type: ignore[attr-defined]
|
426
424
|
|
427
|
-
#
|
428
|
-
|
429
|
-
|
430
|
-
del result_dict[sn]
|
425
|
+
# retrieve the set of short names of the objects which we
|
426
|
+
# are not supposed to inherit
|
427
|
+
not_inherited_short_names = set(get_not_inherited(parent_ref))
|
431
428
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
429
|
+
# compute the list of objects which we are supposed to
|
430
|
+
# inherit from this diagnostic layer
|
431
|
+
inherited_objects = [
|
432
|
+
x
|
433
|
+
for x in parent_dl._compute_available_objects(get_local_objects, get_not_inherited)
|
434
|
+
if x.short_name not in not_inherited_short_names
|
435
|
+
]
|
436
436
|
|
437
|
-
|
437
|
+
# update the result set with the objects from the current parent_ref
|
438
|
+
for obj in inherited_objects:
|
439
|
+
|
440
|
+
# no object with the given short name currently
|
441
|
+
# exits. add it to the result set and continue
|
442
|
+
if obj.short_name not in result_dict:
|
443
|
+
result_dict[obj.short_name] = (obj, parent_dl)
|
444
|
+
continue
|
445
|
+
|
446
|
+
# if an object with a given name already exists,
|
447
|
+
# there's no problem if it was inherited from a parent
|
448
|
+
# of different priority than the one currently
|
449
|
+
# considered
|
450
|
+
orig_prio = result_dict[obj.short_name][1].variant_type.inheritance_priority
|
451
|
+
new_prio = parent_dl.variant_type.inheritance_priority
|
452
|
+
if new_prio < orig_prio:
|
453
|
+
continue
|
454
|
+
elif orig_prio < new_prio:
|
455
|
+
result_dict[obj.short_name] = (obj, parent_dl)
|
456
|
+
continue
|
457
|
+
|
458
|
+
# if there is a conflict on the same priority level,
|
459
|
+
# it does not matter if the object is overridden
|
460
|
+
# locally anyway...
|
461
|
+
if obj.short_name in local_object_short_names:
|
462
|
+
continue
|
463
|
+
|
464
|
+
# if all of these conditions do not apply, and if the
|
465
|
+
# inherited objects are identical, there is no
|
466
|
+
# conflict. (note that value comparisons of complete
|
467
|
+
# complex objects tend to be expensive, so this test
|
468
|
+
# is done last.)
|
469
|
+
if obj == result_dict[obj.short_name][0]:
|
470
|
+
continue
|
471
|
+
|
472
|
+
odxraise(f"Diagnostic layer {self.short_name} cannot inherit object "
|
473
|
+
f"{obj.short_name} due to an unresolveable inheritance conflict between "
|
474
|
+
f"parent layers {result_dict[obj.short_name][1].short_name} "
|
475
|
+
f"and {parent_dl.short_name}")
|
476
|
+
|
477
|
+
# add the locally defined entries, overriding the inherited
|
478
|
+
# ones if necessary
|
479
|
+
for obj in local_objects:
|
480
|
+
result_dict[obj.short_name] = (obj, self)
|
481
|
+
|
482
|
+
return [x[0] for x in result_dict.values()]
|
438
483
|
|
439
484
|
def _get_local_diag_comms(self, odxlinks: OdxLinkDatabase) -> Iterable[DiagComm]:
|
440
485
|
"""Return the list of locally defined diagnostic communications.
|
@@ -767,7 +812,7 @@ class DiagLayer:
|
|
767
812
|
|
768
813
|
return int(result)
|
769
814
|
|
770
|
-
@deprecated(details="use get_can_receive_id()")
|
815
|
+
@deprecated(details="use get_can_receive_id()") # type: ignore[misc]
|
771
816
|
def get_receive_id(self) -> Optional[int]:
|
772
817
|
return self.get_can_receive_id()
|
773
818
|
|
@@ -796,7 +841,7 @@ class DiagLayer:
|
|
796
841
|
|
797
842
|
return int(result)
|
798
843
|
|
799
|
-
@deprecated(details="use get_can_send_id()")
|
844
|
+
@deprecated(details="use get_can_send_id()") # type: ignore[misc]
|
800
845
|
def get_send_id(self) -> Optional[int]:
|
801
846
|
return self.get_can_send_id()
|
802
847
|
|
@@ -1097,7 +1142,8 @@ class DiagLayer:
|
|
1097
1142
|
|
1098
1143
|
if len(decoded_messages) == 0:
|
1099
1144
|
raise DecodeError(
|
1100
|
-
f"None of the services {candidate_services} could parse {message.hex()}."
|
1145
|
+
f"None of the services {[x.short_name for x in candidate_services]} could parse {message.hex()}."
|
1146
|
+
)
|
1101
1147
|
|
1102
1148
|
return decoded_messages
|
1103
1149
|
|
@@ -1106,14 +1152,8 @@ class DiagLayer:
|
|
1106
1152
|
|
1107
1153
|
return self._decode(message, candidate_services)
|
1108
1154
|
|
1109
|
-
def decode_response(self, response: bytes, request:
|
1110
|
-
|
1111
|
-
candidate_services = [request.service]
|
1112
|
-
else:
|
1113
|
-
if not isinstance(request, (bytes, bytearray)):
|
1114
|
-
raise TypeError(f"Request parameter must have type "
|
1115
|
-
f"Message, bytes or bytearray but was {type(request)}")
|
1116
|
-
candidate_services = self._find_services_for_uds(request)
|
1155
|
+
def decode_response(self, response: bytes, request: bytes) -> List[Message]:
|
1156
|
+
candidate_services = self._find_services_for_uds(request)
|
1117
1157
|
if candidate_services is None:
|
1118
1158
|
raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
|
1119
1159
|
|