odxtools 6.6.0__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 +14 -8
- 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 +30 -10
- 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 +27 -3
- 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.0.dist-info → odxtools-6.7.0.dist-info}/METADATA +28 -16
- {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/RECORD +81 -79
- {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/WHEEL +1 -1
- {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/LICENSE +0 -0
- {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.6.0.dist-info → odxtools-6.7.0.dist-info}/top_level.txt +0 -0
odxtools/diaglayertype.py
CHANGED
@@ -20,11 +20,23 @@ class DiagLayerType(Enum):
|
|
20
20
|
"""
|
21
21
|
|
22
22
|
PRIORITY_OF_DIAG_LAYER_TYPE: Dict[DiagLayerType, int] = {
|
23
|
-
DiagLayerType.
|
24
|
-
|
25
|
-
DiagLayerType.FUNCTIONAL_GROUP:
|
26
|
-
|
27
|
-
DiagLayerType.
|
23
|
+
DiagLayerType.PROTOCOL:
|
24
|
+
1,
|
25
|
+
DiagLayerType.FUNCTIONAL_GROUP:
|
26
|
+
2,
|
27
|
+
DiagLayerType.BASE_VARIANT:
|
28
|
+
3,
|
29
|
+
DiagLayerType.ECU_VARIANT:
|
30
|
+
4,
|
31
|
+
|
32
|
+
# ECU shared data layers are a bit weird (see section
|
33
|
+
# 7.3.2.4.4 of the ASAM specification): they can be
|
34
|
+
# inherited from by any other layer but they will
|
35
|
+
# override any objects which are also provided by any of
|
36
|
+
# the other parent layers. tl;dr: When it comes to
|
37
|
+
# inheritance, they have the highest priority.
|
38
|
+
DiagLayerType.ECU_SHARED_DATA:
|
39
|
+
100,
|
28
40
|
}
|
29
41
|
|
30
42
|
return PRIORITY_OF_DIAG_LAYER_TYPE[self]
|
odxtools/diagservice.py
CHANGED
@@ -167,7 +167,7 @@ class DiagService(DiagComm):
|
|
167
167
|
for cpr in self.comparam_refs:
|
168
168
|
cpr._resolve_odxlinks(odxlinks)
|
169
169
|
|
170
|
-
self._request = odxlinks.resolve(self.request_ref)
|
170
|
+
self._request = odxlinks.resolve(self.request_ref, Request)
|
171
171
|
|
172
172
|
self._positive_responses = NamedItemList[Response](
|
173
173
|
[odxlinks.resolve(x, Response) for x in self.pos_response_refs])
|
odxtools/dopbase.py
CHANGED
@@ -64,8 +64,10 @@ class DopBase(IdentifiableElement):
|
|
64
64
|
"""
|
65
65
|
raise NotImplementedError
|
66
66
|
|
67
|
-
def convert_physical_to_bytes(self,
|
68
|
-
|
67
|
+
def convert_physical_to_bytes(self,
|
68
|
+
physical_value: ParameterValue,
|
69
|
+
encode_state: EncodeState,
|
70
|
+
bit_position: int = 0) -> bytes:
|
69
71
|
"""Convert the physical value into bytes."""
|
70
72
|
raise NotImplementedError
|
71
73
|
|
odxtools/dtcdop.py
CHANGED
@@ -46,8 +46,8 @@ class DtcDop(DopBase):
|
|
46
46
|
compu_method = create_any_compu_method_from_et(
|
47
47
|
odxrequire(et_element.find("COMPU-METHOD")),
|
48
48
|
doc_frags,
|
49
|
-
diag_coded_type.base_data_type,
|
50
|
-
physical_type.base_data_type,
|
49
|
+
internal_type=diag_coded_type.base_data_type,
|
50
|
+
physical_type=physical_type.base_data_type,
|
51
51
|
)
|
52
52
|
dtcs_raw: List[Union[DiagnosticTroubleCode, OdxLinkRef]] = []
|
53
53
|
if (dtcs_elem := et_element.find("DTCS")) is not None:
|
@@ -59,7 +59,7 @@ class DtcDop(DopBase):
|
|
59
59
|
|
60
60
|
# TODO: NOT-INHERITED-DTC-SNREFS
|
61
61
|
linked_dtc_dop_refs = [
|
62
|
-
|
62
|
+
OdxLinkRef.from_et(dtc_ref_elem, doc_frags)
|
63
63
|
for dtc_ref_elem in et_element.iterfind("LINKED-DTC-DOPS/"
|
64
64
|
"LINKED-DTC-DOP/"
|
65
65
|
"DTC-DOP-REF")
|
@@ -128,8 +128,10 @@ class DtcDop(DopBase):
|
|
128
128
|
|
129
129
|
return dtc
|
130
130
|
|
131
|
-
def convert_physical_to_bytes(self,
|
132
|
-
|
131
|
+
def convert_physical_to_bytes(self,
|
132
|
+
physical_value: ParameterValue,
|
133
|
+
encode_state: EncodeState,
|
134
|
+
bit_position: int = 0) -> bytes:
|
133
135
|
if isinstance(physical_value, DiagnosticTroubleCode):
|
134
136
|
trouble_code = physical_value.trouble_code
|
135
137
|
elif isinstance(physical_value, int):
|
@@ -137,13 +139,17 @@ class DtcDop(DopBase):
|
|
137
139
|
trouble_code = physical_value
|
138
140
|
elif isinstance(physical_value, str):
|
139
141
|
# assume that physical value is the short_name
|
140
|
-
|
141
|
-
|
142
|
+
dtcs = [dtc for dtc in self.dtcs if dtc.short_name == physical_value]
|
143
|
+
odxassert(len(dtcs) == 1)
|
144
|
+
trouble_code = dtcs[0].trouble_code
|
142
145
|
else:
|
143
146
|
raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
|
144
147
|
f" DiagnosticTroubleCode but got {physical_value!r}.")
|
145
148
|
|
146
|
-
|
149
|
+
internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
|
150
|
+
|
151
|
+
return self.diag_coded_type.convert_internal_to_bytes(
|
152
|
+
internal_trouble_code, encode_state=encode_state, bit_position=bit_position)
|
147
153
|
|
148
154
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
149
155
|
odxlinks = super()._build_odxlinks()
|
odxtools/dynamiclengthfield.py
CHANGED
@@ -99,6 +99,7 @@ class DynamicLengthField(Field):
|
|
99
99
|
decode_state.cursor_bit_position = det_num_items.bit_position or 0
|
100
100
|
|
101
101
|
n = det_num_items.dop.decode_from_pdu(decode_state)
|
102
|
+
result: List[ParameterValue] = []
|
102
103
|
|
103
104
|
if not isinstance(n, int):
|
104
105
|
odxraise(f"Number of items specified by a dynamic length field {self.short_name} "
|
@@ -107,11 +108,11 @@ class DynamicLengthField(Field):
|
|
107
108
|
odxraise(
|
108
109
|
f"Number of items specified by a dynamic length field {self.short_name} "
|
109
110
|
f"must be positive (is: {n})", DecodeError)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
111
|
+
n = 0
|
112
|
+
|
113
|
+
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.offset
|
114
|
+
for _ in range(n):
|
115
|
+
result.append(self.structure.decode_from_pdu(decode_state))
|
115
116
|
|
116
117
|
decode_state.origin_byte_position = orig_origin
|
117
118
|
decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
|
odxtools/endofpdufield.py
CHANGED
@@ -41,7 +41,7 @@ class EndOfPduField(Field):
|
|
41
41
|
|
42
42
|
def convert_physical_to_bytes(
|
43
43
|
self,
|
44
|
-
|
44
|
+
physical_value: ParameterValue,
|
45
45
|
encode_state: EncodeState,
|
46
46
|
bit_position: int = 0,
|
47
47
|
) -> bytes:
|
@@ -49,13 +49,13 @@ class EndOfPduField(Field):
|
|
49
49
|
odxassert(
|
50
50
|
bit_position == 0, "End of PDU field must be byte aligned. "
|
51
51
|
"Is there an error in reading the .odx?", EncodeError)
|
52
|
-
if not isinstance(
|
52
|
+
if not isinstance(physical_value, list):
|
53
53
|
odxraise(
|
54
54
|
f"Expected a list of values for end-of-pdu field {self.short_name}, "
|
55
|
-
f"got {type(
|
55
|
+
f"got {type(physical_value)}", EncodeError)
|
56
56
|
|
57
57
|
coded_message = b''
|
58
|
-
for value in
|
58
|
+
for value in physical_value:
|
59
59
|
coded_message += self.structure.convert_physical_to_bytes(value, encode_state)
|
60
60
|
return coded_message
|
61
61
|
|
@@ -86,8 +86,10 @@ class EnvironmentDataDescription(ComplexDop):
|
|
86
86
|
for ed in self.env_datas:
|
87
87
|
ed._resolve_snrefs(diag_layer)
|
88
88
|
|
89
|
-
def convert_physical_to_bytes(self,
|
90
|
-
|
89
|
+
def convert_physical_to_bytes(self,
|
90
|
+
physical_value: ParameterValue,
|
91
|
+
encode_state: EncodeState,
|
92
|
+
bit_position: int = 0) -> bytes:
|
91
93
|
"""Convert the physical value into bytes.
|
92
94
|
|
93
95
|
Since environmental data is supposed to never appear on the
|
odxtools/inputparam.py
CHANGED
odxtools/internalconstr.py
CHANGED
@@ -4,6 +4,7 @@ from typing import List, Optional
|
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from .compumethods.limit import Limit
|
7
|
+
from .odxlink import OdxDocFragment
|
7
8
|
from .odxtypes import DataType
|
8
9
|
from .scaleconstr import ScaleConstr
|
9
10
|
|
@@ -19,16 +20,24 @@ class InternalConstr:
|
|
19
20
|
upper_limit: Optional[Limit]
|
20
21
|
scale_constrs: List[ScaleConstr]
|
21
22
|
|
23
|
+
value_type: DataType
|
24
|
+
|
22
25
|
@staticmethod
|
23
|
-
def
|
26
|
+
def constr_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
27
|
+
value_type: DataType) -> "InternalConstr":
|
24
28
|
|
25
|
-
lower_limit = Limit.
|
26
|
-
|
29
|
+
lower_limit = Limit.limit_from_et(
|
30
|
+
et_element.find("LOWER-LIMIT"), doc_frags, value_type=value_type)
|
31
|
+
upper_limit = Limit.limit_from_et(
|
32
|
+
et_element.find("UPPER-LIMIT"), doc_frags, value_type=value_type)
|
27
33
|
|
28
34
|
scale_constrs = [
|
29
|
-
ScaleConstr.
|
35
|
+
ScaleConstr.scale_constr_from_et(sc_el, doc_frags, value_type=value_type)
|
30
36
|
for sc_el in et_element.iterfind("SCALE-CONSTRS/SCALE-CONSTR")
|
31
37
|
]
|
32
38
|
|
33
39
|
return InternalConstr(
|
34
|
-
lower_limit=lower_limit,
|
40
|
+
lower_limit=lower_limit,
|
41
|
+
upper_limit=upper_limit,
|
42
|
+
scale_constrs=scale_constrs,
|
43
|
+
value_type=value_type)
|
odxtools/isotp_state_machine.py
CHANGED
@@ -55,10 +55,13 @@ class IsoTpStateMachine:
|
|
55
55
|
return # unknown CAN ID
|
56
56
|
|
57
57
|
# decode the isotp segment
|
58
|
-
|
58
|
+
frame_type, _ = bitstruct.unpack("u4u4", data)
|
59
|
+
assert isinstance(frame_type, int)
|
60
|
+
|
59
61
|
telegram_len = None
|
60
62
|
if frame_type == IsoTp.FRAME_TYPE_SINGLE:
|
61
63
|
frame_type, telegram_len = bitstruct.unpack("u4u4", data)
|
64
|
+
assert isinstance(telegram_len, int)
|
62
65
|
|
63
66
|
self.on_single_frame(telegram_idx, data[1:1 + telegram_len])
|
64
67
|
self.on_telegram_complete(telegram_idx, data[1:1 + telegram_len])
|
@@ -67,6 +70,7 @@ class IsoTpStateMachine:
|
|
67
70
|
|
68
71
|
elif frame_type == IsoTp.FRAME_TYPE_FIRST:
|
69
72
|
frame_type, telegram_len = bitstruct.unpack("u4u12", data)
|
73
|
+
assert isinstance(telegram_len, int)
|
70
74
|
|
71
75
|
self._telegram_specified_len[telegram_idx] = telegram_len
|
72
76
|
self._telegram_data[telegram_idx] = bytearray(data[2:])
|
@@ -76,11 +80,13 @@ class IsoTpStateMachine:
|
|
76
80
|
|
77
81
|
elif frame_type == IsoTp.FRAME_TYPE_CONSECUTIVE:
|
78
82
|
frame_type, rx_segment_idx = bitstruct.unpack("u4u4", data)
|
83
|
+
assert isinstance(rx_segment_idx, int)
|
79
84
|
|
80
85
|
expected_segment_idx = (self._telegram_last_rx_fragment_idx[telegram_idx] + 1) % 16
|
81
86
|
telegram_data = self._telegram_data[telegram_idx]
|
82
87
|
assert isinstance(telegram_data, bytearray)
|
83
88
|
|
89
|
+
n = -1
|
84
90
|
if expected_segment_idx == rx_segment_idx:
|
85
91
|
self._telegram_last_rx_fragment_idx[telegram_idx] = rx_segment_idx
|
86
92
|
telegram_data += data[1:]
|
@@ -102,6 +108,8 @@ class IsoTpStateMachine:
|
|
102
108
|
|
103
109
|
elif frame_type == IsoTp.FRAME_TYPE_FLOW_CONTROL:
|
104
110
|
frame_type, flow_control_flag = bitstruct.unpack("u4u4", data)
|
111
|
+
assert isinstance(flow_control_flag, int)
|
112
|
+
|
105
113
|
self.on_flow_control_frame(telegram_idx, flow_control_flag)
|
106
114
|
else:
|
107
115
|
self.on_frame_type_error(telegram_idx, frame_type)
|
@@ -139,7 +147,7 @@ class IsoTpStateMachine:
|
|
139
147
|
return
|
140
148
|
|
141
149
|
if m := self.can_normal_frame_re.match(cur_line.strip()):
|
142
|
-
#frame_interface = m.group(1)
|
150
|
+
# frame_interface = m.group(1)
|
143
151
|
frame_id = int(m.group(2), 16)
|
144
152
|
|
145
153
|
frame_data_formatted = m.group(3).strip()
|
@@ -151,7 +159,7 @@ class IsoTpStateMachine:
|
|
151
159
|
elif (m := self.can_log_frame_re.match(
|
152
160
|
cur_line.strip())) or (m := self.can_fd_log_frame_re.match(
|
153
161
|
cur_line.strip())):
|
154
|
-
#frame_interface = m.group(2)
|
162
|
+
# frame_interface = m.group(2)
|
155
163
|
frame_id = int(m.group(2), 16)
|
156
164
|
|
157
165
|
frame_data_formatted = m.group(3).strip()
|
@@ -257,7 +265,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
257
265
|
|
258
266
|
def on_single_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
259
267
|
# send ACK
|
260
|
-
#rx_id = self.can_rx_id(telegram_idx)
|
268
|
+
# rx_id = self.can_rx_id(telegram_idx)
|
261
269
|
tx_id = self.can_tx_id(telegram_idx)
|
262
270
|
block_size = 0xFF
|
263
271
|
min_separation_time = 0 # ms
|
@@ -276,7 +284,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
276
284
|
|
277
285
|
def on_first_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
|
278
286
|
# send ACK
|
279
|
-
#rx_id = self.can_rx_id(telegram_idx)
|
287
|
+
# rx_id = self.can_rx_id(telegram_idx)
|
280
288
|
tx_id = self.can_tx_id(telegram_idx)
|
281
289
|
block_size = 0xFF # default value, can be overwritten later
|
282
290
|
min_separation_time = 0 # ms
|
@@ -308,7 +316,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
|
|
308
316
|
# send new ACK if necessary
|
309
317
|
block_size = self._block_size[telegram_idx]
|
310
318
|
if block_size is not None and num_received >= block_size:
|
311
|
-
#rx_id = self.can_rx_id(telegram_idx)
|
319
|
+
# rx_id = self.can_rx_id(telegram_idx)
|
312
320
|
tx_id = self.can_tx_id(telegram_idx)
|
313
321
|
min_separation_time = 0 # ms
|
314
322
|
fc_payload = bitstruct.pack(
|
odxtools/message.py
CHANGED
odxtools/multiplexer.py
CHANGED
@@ -36,7 +36,8 @@ class Multiplexer(ComplexDop):
|
|
36
36
|
@staticmethod
|
37
37
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
|
38
38
|
"""Reads a Multiplexer from Diag Layer."""
|
39
|
-
|
39
|
+
base_obj = ComplexDop.from_et(et_element, doc_frags)
|
40
|
+
kwargs = dataclass_fields_asdict(base_obj)
|
40
41
|
|
41
42
|
byte_position = int(et_element.findtext("BYTE-POSITION", "0"))
|
42
43
|
switch_key = MultiplexerSwitchKey.from_et(
|
@@ -73,8 +74,10 @@ class Multiplexer(ComplexDop):
|
|
73
74
|
odxraise("Upper and lower bounds of limits must compareable")
|
74
75
|
return lower_limit, upper_limit
|
75
76
|
|
76
|
-
def convert_physical_to_bytes(self,
|
77
|
-
|
77
|
+
def convert_physical_to_bytes(self,
|
78
|
+
physical_value: ParameterValue,
|
79
|
+
encode_state: EncodeState,
|
80
|
+
bit_position: int = 0) -> bytes:
|
78
81
|
|
79
82
|
if bit_position != 0:
|
80
83
|
raise EncodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
|
@@ -125,22 +128,23 @@ class Multiplexer(ComplexDop):
|
|
125
128
|
f" for multiplexer '{self.short_name}')")
|
126
129
|
|
127
130
|
case_value: Optional[ParameterValue] = None
|
128
|
-
|
129
|
-
|
131
|
+
mux_case = None
|
132
|
+
for mux_case in self.cases or []:
|
133
|
+
lower, upper = self._get_case_limits(mux_case)
|
130
134
|
if lower <= key_value and key_value <= upper: # type: ignore[operator]
|
131
|
-
if
|
132
|
-
case_value =
|
135
|
+
if mux_case._structure:
|
136
|
+
case_value = mux_case._structure.decode_from_pdu(decode_state)
|
133
137
|
break
|
134
138
|
|
135
139
|
if case_value is None and self.default_case is not None:
|
136
140
|
if self.default_case._structure:
|
137
141
|
case_value = self.default_case._structure.decode_from_pdu(decode_state)
|
138
142
|
|
139
|
-
if case_value is None:
|
143
|
+
if mux_case is None or case_value is None:
|
140
144
|
odxraise(f"Failed to find a matching case in {self.short_name} for value {key_value!r}",
|
141
145
|
DecodeError)
|
142
146
|
|
143
|
-
mux_value = (
|
147
|
+
mux_value = (mux_case.short_name, case_value)
|
144
148
|
|
145
149
|
# go back to the original origin
|
146
150
|
decode_state.origin_byte_position = orig_origin
|
@@ -164,8 +168,9 @@ class Multiplexer(ComplexDop):
|
|
164
168
|
if self.default_case is not None:
|
165
169
|
self.default_case._resolve_odxlinks(odxlinks)
|
166
170
|
|
167
|
-
for
|
168
|
-
|
171
|
+
for mux_case in self.cases:
|
172
|
+
mux_case._mux_case_resolve_odxlinks(
|
173
|
+
odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
|
169
174
|
|
170
175
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
171
176
|
super()._resolve_snrefs(diag_layer)
|
@@ -174,5 +179,5 @@ class Multiplexer(ComplexDop):
|
|
174
179
|
if self.default_case is not None:
|
175
180
|
self.default_case._resolve_snrefs(diag_layer)
|
176
181
|
|
177
|
-
for
|
178
|
-
|
182
|
+
for mux_case in self.cases:
|
183
|
+
mux_case._resolve_snrefs(diag_layer)
|
odxtools/multiplexercase.py
CHANGED
@@ -4,9 +4,11 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
6
|
from .basicstructure import BasicStructure
|
7
|
+
from .compumethods.limit import Limit
|
7
8
|
from .element import NamedElement
|
8
9
|
from .exceptions import odxrequire
|
9
10
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
11
|
+
from .odxtypes import AtomicOdxType, DataType
|
10
12
|
from .utils import dataclass_fields_asdict
|
11
13
|
|
12
14
|
if TYPE_CHECKING:
|
@@ -19,8 +21,8 @@ class MultiplexerCase(NamedElement):
|
|
19
21
|
|
20
22
|
structure_ref: Optional[OdxLinkRef]
|
21
23
|
structure_snref: Optional[str]
|
22
|
-
lower_limit:
|
23
|
-
upper_limit:
|
24
|
+
lower_limit: Limit
|
25
|
+
upper_limit: Limit
|
24
26
|
|
25
27
|
def __post_init__(self) -> None:
|
26
28
|
self._structure: BasicStructure
|
@@ -28,15 +30,23 @@ class MultiplexerCase(NamedElement):
|
|
28
30
|
@staticmethod
|
29
31
|
def from_et(et_element: ElementTree.Element,
|
30
32
|
doc_frags: List[OdxDocFragment]) -> "MultiplexerCase":
|
31
|
-
"""Reads a
|
33
|
+
"""Reads a case for a Multiplexer."""
|
32
34
|
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
|
33
35
|
structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
|
34
36
|
structure_snref = None
|
35
37
|
if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
|
36
38
|
structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
|
37
39
|
|
38
|
-
lower_limit =
|
39
|
-
|
40
|
+
lower_limit = Limit.limit_from_et(
|
41
|
+
odxrequire(et_element.find("LOWER-LIMIT")),
|
42
|
+
doc_frags,
|
43
|
+
value_type=None,
|
44
|
+
)
|
45
|
+
upper_limit = Limit.limit_from_et(
|
46
|
+
odxrequire(et_element.find("UPPER-LIMIT")),
|
47
|
+
doc_frags,
|
48
|
+
value_type=None,
|
49
|
+
)
|
40
50
|
|
41
51
|
return MultiplexerCase(
|
42
52
|
structure_ref=structure_ref,
|
@@ -49,14 +59,26 @@ class MultiplexerCase(NamedElement):
|
|
49
59
|
return {}
|
50
60
|
|
51
61
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
62
|
+
raise RuntimeError("Calling MultiplexerCase._resolve_odxlinks() is not allowed. "
|
63
|
+
"Use ._mux_case_resolve_odxlinks()().")
|
64
|
+
|
65
|
+
def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *,
|
66
|
+
key_physical_type: DataType) -> None:
|
52
67
|
if self.structure_ref:
|
53
68
|
self._structure = odxlinks.resolve(self.structure_ref)
|
54
69
|
|
70
|
+
self.lower_limit.set_value_type(key_physical_type)
|
71
|
+
self.upper_limit.set_value_type(key_physical_type)
|
72
|
+
|
55
73
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
56
74
|
if self.structure_snref:
|
57
75
|
ddds = diag_layer.diag_data_dictionary_spec
|
58
76
|
self._structure = odxrequire(ddds.structures.get(self.structure_snref))
|
59
77
|
|
78
|
+
def applies(self, value: AtomicOdxType) -> bool:
|
79
|
+
return self.lower_limit.complies_to_lower(value) \
|
80
|
+
and self.upper_limit.complies_to_upper(value)
|
81
|
+
|
60
82
|
@property
|
61
83
|
def structure(self) -> BasicStructure:
|
62
84
|
return self._structure
|
odxtools/multiplexerswitchkey.py
CHANGED
odxtools/nameditemlist.py
CHANGED
@@ -12,7 +12,7 @@ class OdxNamed(Protocol):
|
|
12
12
|
|
13
13
|
@property
|
14
14
|
def short_name(self) -> str:
|
15
|
-
|
15
|
+
...
|
16
16
|
|
17
17
|
|
18
18
|
T = TypeVar("T")
|
@@ -127,7 +127,7 @@ class ItemAttributeList(List[T]):
|
|
127
127
|
result.update(self._item_dict)
|
128
128
|
return result
|
129
129
|
|
130
|
-
@overload
|
130
|
+
@overload
|
131
131
|
def __getitem__(self, key: SupportsIndex) -> T:
|
132
132
|
...
|
133
133
|
|
@@ -139,7 +139,8 @@ class ItemAttributeList(List[T]):
|
|
139
139
|
def __getitem__(self, key: slice) -> List[T]:
|
140
140
|
...
|
141
141
|
|
142
|
-
def __getitem__(
|
142
|
+
def __getitem__( # type: ignore
|
143
|
+
self, key: Union[SupportsIndex, str, slice]) -> Union[T, List[T]]:
|
143
144
|
if isinstance(key, (SupportsIndex, slice)):
|
144
145
|
return super().__getitem__(key)
|
145
146
|
else:
|
@@ -177,7 +178,7 @@ class ItemAttributeList(List[T]):
|
|
177
178
|
|
178
179
|
class NamedItemList(ItemAttributeList[T]):
|
179
180
|
|
180
|
-
def _get_item_key(self,
|
181
|
+
def _get_item_key(self, item: T) -> str:
|
181
182
|
"""Transform an object's `short_name` attribute into a valid
|
182
183
|
python identifier
|
183
184
|
|
@@ -187,9 +188,9 @@ class NamedItemList(ItemAttributeList[T]):
|
|
187
188
|
such short names.
|
188
189
|
|
189
190
|
"""
|
190
|
-
if not isinstance(
|
191
|
+
if not isinstance(item, OdxNamed):
|
191
192
|
odxraise()
|
192
|
-
sn =
|
193
|
+
sn = item.short_name
|
193
194
|
if not isinstance(sn, str):
|
194
195
|
odxraise()
|
195
196
|
|
odxtools/odxlink.py
CHANGED
@@ -181,7 +181,7 @@ class OdxLinkDatabase:
|
|
181
181
|
def resolve(self, ref: OdxLinkRef, expected_type: Type[T]) -> T:
|
182
182
|
...
|
183
183
|
|
184
|
-
def resolve(self, ref: OdxLinkRef, expected_type: Optional[
|
184
|
+
def resolve(self, ref: OdxLinkRef, expected_type: Optional[Any] = None) -> Any:
|
185
185
|
"""
|
186
186
|
Resolve a reference to an object
|
187
187
|
|
@@ -223,7 +223,7 @@ class OdxLinkDatabase:
|
|
223
223
|
|
224
224
|
def resolve_lenient(self,
|
225
225
|
ref: OdxLinkRef,
|
226
|
-
expected_type: Optional[
|
226
|
+
expected_type: Optional[Any] = None) -> Optional[Any]:
|
227
227
|
"""
|
228
228
|
Resolve a reference to an object
|
229
229
|
|
odxtools/odxtypes.py
CHANGED
@@ -102,6 +102,58 @@ _ODX_TYPE_TO_PYTHON_TYPE: Dict[str, Type[AtomicOdxType]] = {
|
|
102
102
|
}
|
103
103
|
|
104
104
|
|
105
|
+
def compare_odx_values(a: AtomicOdxType, b: AtomicOdxType) -> int:
|
106
|
+
# this function implements the comparison according to the ODX
|
107
|
+
# specification. (cf section 7.3.6.5)
|
108
|
+
|
109
|
+
# numeric values are compared numerically (duh!)
|
110
|
+
if isinstance(a, (int, float)):
|
111
|
+
if not isinstance(b, (int, float)):
|
112
|
+
odxraise()
|
113
|
+
|
114
|
+
tmp = a - b
|
115
|
+
if tmp < 0:
|
116
|
+
return -1
|
117
|
+
elif tmp > 0:
|
118
|
+
return 1
|
119
|
+
return 0
|
120
|
+
|
121
|
+
# strings are compared lexicographically. (the spec only allows
|
122
|
+
# equals, but this cannot easily implemented using a single
|
123
|
+
# comparison function.
|
124
|
+
if isinstance(a, str):
|
125
|
+
if not isinstance(b, str):
|
126
|
+
odxraise()
|
127
|
+
|
128
|
+
if a < b:
|
129
|
+
return -1
|
130
|
+
elif b < a:
|
131
|
+
return 1
|
132
|
+
else:
|
133
|
+
return 0
|
134
|
+
|
135
|
+
# bytefields are treated like long integers: to pad the shorter
|
136
|
+
# object with zeros and treat the results like strings.
|
137
|
+
if isinstance(a, (bytes, bytearray)):
|
138
|
+
if not isinstance(b, (bytes, bytearray)):
|
139
|
+
odxraise()
|
140
|
+
|
141
|
+
obj_len = max(len(a), len(b))
|
142
|
+
|
143
|
+
tmp_a = a.ljust(obj_len, b'\x00')
|
144
|
+
tmp_b = b.ljust(obj_len, b'\x00')
|
145
|
+
|
146
|
+
if tmp_a > tmp_b:
|
147
|
+
return 1
|
148
|
+
elif tmp_a < tmp_b:
|
149
|
+
return -1
|
150
|
+
else:
|
151
|
+
return 0
|
152
|
+
|
153
|
+
odxraise(f"Unhandled comparsion between objects of type {type(a).__name__} "
|
154
|
+
f"and {type(b).__name__}")
|
155
|
+
|
156
|
+
|
105
157
|
class DataType(Enum):
|
106
158
|
"""Types for the physical and internal value.
|
107
159
|
|
@@ -125,7 +177,8 @@ class DataType(Enum):
|
|
125
177
|
A_ASCIISTRING = "A_ASCIISTRING"
|
126
178
|
A_UTF8STRING = "A_UTF8STRING"
|
127
179
|
|
128
|
-
|
180
|
+
@property
|
181
|
+
def python_type(self) -> Type[AtomicOdxType]:
|
129
182
|
return _ODX_TYPE_TO_PYTHON_TYPE[self.value]
|
130
183
|
|
131
184
|
def from_string(self, value: str) -> AtomicOdxType:
|
@@ -158,10 +211,10 @@ class DataType(Enum):
|
|
158
211
|
return self.from_string(value)
|
159
212
|
else:
|
160
213
|
# regular type cast of python objects
|
161
|
-
return self.
|
214
|
+
return self.python_type(value)
|
162
215
|
|
163
216
|
def isinstance(self, value: Any) -> bool:
|
164
|
-
expected_type = self.
|
217
|
+
expected_type = self.python_type
|
165
218
|
if isinstance(value, expected_type):
|
166
219
|
return True
|
167
220
|
elif expected_type == float and isinstance(value, (int, float)):
|
odxtools/outputparam.py
CHANGED
@@ -35,7 +35,7 @@ class OutputParam(IdentifiableElement):
|
|
35
35
|
return {}
|
36
36
|
|
37
37
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
38
|
-
self._dop_base = odxlinks.resolve(self.dop_base_ref)
|
38
|
+
self._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
|
39
39
|
|
40
40
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
41
41
|
pass
|
@@ -46,6 +46,6 @@ class OutputParam(IdentifiableElement):
|
|
46
46
|
return self._dop_base
|
47
47
|
|
48
48
|
@property
|
49
|
-
@deprecated(details="use .dop_base")
|
49
|
+
@deprecated(details="use .dop_base") # type: ignore[misc]
|
50
50
|
def dop(self) -> DopBase:
|
51
51
|
return self._dop_base
|