odxtools 5.2.6__py3-none-any.whl → 5.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odxtools/basicstructure.py +2 -2
- odxtools/cli/browse.py +18 -20
- odxtools/cli/dummy_sub_parser.py +3 -3
- odxtools/cli/main.py +1 -1
- odxtools/compumethods/compuscale.py +1 -2
- odxtools/compumethods/createanycompumethod.py +0 -1
- odxtools/compumethods/identicalcompumethod.py +0 -2
- odxtools/compumethods/limit.py +1 -1
- odxtools/compumethods/linearcompumethod.py +1 -1
- odxtools/compumethods/scalelinearcompumethod.py +0 -1
- odxtools/compumethods/tabintpcompumethod.py +0 -1
- odxtools/determinenumberofitems.py +18 -0
- odxtools/diagdatadictionaryspec.py +14 -4
- odxtools/diaglayer.py +54 -59
- odxtools/dynamiclengthfield.py +59 -0
- odxtools/element.py +2 -4
- odxtools/endofpdufield.py +6 -77
- odxtools/field.py +94 -0
- odxtools/isotp_state_machine.py +20 -24
- odxtools/matchingparameter.py +1 -1
- odxtools/multiplexer.py +6 -18
- odxtools/multiplexercase.py +22 -6
- odxtools/multiplexerdefaultcase.py +10 -2
- odxtools/multiplexerswitchkey.py +8 -43
- odxtools/odxlink.py +8 -4
- odxtools/odxtypes.py +1 -1
- odxtools/parameters/codedconstparameter.py +4 -2
- odxtools/parameters/dynamicparameter.py +6 -4
- odxtools/parameters/lengthkeyparameter.py +8 -3
- odxtools/parameters/matchingrequestparameter.py +5 -3
- odxtools/parameters/nrcconstparameter.py +4 -2
- odxtools/parameters/parameter.py +21 -6
- odxtools/parameters/parameterwithdop.py +6 -1
- odxtools/parameters/physicalconstantparameter.py +4 -2
- odxtools/parameters/reservedparameter.py +4 -2
- odxtools/parameters/systemparameter.py +5 -3
- odxtools/parameters/tableentryparameter.py +5 -3
- odxtools/parameters/tablekeyparameter.py +8 -4
- odxtools/parameters/tablestructparameter.py +4 -2
- odxtools/parameters/valueparameter.py +5 -3
- odxtools/positioneddataobjectproperty.py +74 -0
- odxtools/progcode.py +2 -3
- odxtools/tablerow.py +1 -2
- odxtools/templates/macros/printAudience.xml.jinja2 +3 -9
- odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -27
- odxtools/templates/macros/printComparam.xml.jinja2 +4 -18
- odxtools/templates/macros/printDOP.xml.jinja2 +3 -9
- odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +22 -0
- odxtools/templates/macros/printElementID.xml.jinja2 +6 -6
- odxtools/templates/macros/printEndOfPdu.xml.jinja2 +3 -2
- odxtools/templates/macros/printEnvData.xml.jinja2 +2 -2
- odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +3 -2
- odxtools/templates/macros/printFunctionalClass.xml.jinja2 +3 -9
- odxtools/templates/macros/printMux.xml.jinja2 +13 -6
- odxtools/templates/macros/printParam.xml.jinja2 +2 -7
- odxtools/templates/macros/printRequest.xml.jinja2 +2 -9
- odxtools/templates/macros/printResponse.xml.jinja2 +2 -9
- odxtools/templates/macros/printService.xml.jinja2 +2 -9
- odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
- odxtools/templates/macros/printState.xml.jinja2 +3 -9
- odxtools/templates/macros/printStateChart.xml.jinja2 +2 -9
- odxtools/templates/macros/printStateTransition.xml.jinja2 +3 -9
- odxtools/templates/macros/printStructure.xml.jinja2 +2 -4
- odxtools/templates/macros/printTable.xml.jinja2 +2 -7
- odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -3
- odxtools/templates/macros/printVariant.xml.jinja2 +10 -9
- odxtools/version.py +4 -2
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/METADATA +70 -13
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/RECORD +73 -68
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/LICENSE +0 -0
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/WHEEL +0 -0
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/entry_points.txt +0 -0
- {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/top_level.txt +0 -0
odxtools/endofpdufield.py
CHANGED
@@ -1,62 +1,28 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from copy import copy
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import
|
4
|
+
from typing import List, Optional
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
-
from .basicstructure import BasicStructure
|
8
|
-
from .createsdgs import create_sdgs_from_et
|
9
7
|
from .decodestate import DecodeState
|
10
|
-
from .dopbase import DopBase
|
11
|
-
from .element import IdentifiableElement
|
12
8
|
from .encodestate import EncodeState
|
13
9
|
from .exceptions import odxassert
|
14
|
-
from .
|
15
|
-
from .
|
10
|
+
from .field import Field
|
11
|
+
from .odxlink import OdxDocFragment
|
12
|
+
from .odxtypes import ParameterValueDict
|
16
13
|
from .utils import dataclass_fields_asdict
|
17
14
|
|
18
|
-
if TYPE_CHECKING:
|
19
|
-
from .diaglayer import DiagLayer
|
20
|
-
|
21
15
|
|
22
16
|
@dataclass
|
23
|
-
class EndOfPduField(
|
17
|
+
class EndOfPduField(Field):
|
24
18
|
"""End of PDU fields are structures that are repeated until the end of the PDU"""
|
25
|
-
|
26
|
-
structure_ref: Optional[OdxLinkRef]
|
27
|
-
structure_snref: Optional[str]
|
28
|
-
env_data_desc_ref: Optional[OdxLinkRef]
|
29
|
-
env_data_desc_snref: Optional[str]
|
30
19
|
min_number_of_items: Optional[int]
|
31
20
|
max_number_of_items: Optional[int]
|
32
21
|
|
33
|
-
def __post_init__(self) -> None:
|
34
|
-
num_struct_refs = 0 if self.structure_ref is None else 1
|
35
|
-
num_struct_refs += 0 if self.structure_snref is None else 1
|
36
|
-
|
37
|
-
num_edd_refs = 0 if self.env_data_desc_ref is None else 1
|
38
|
-
num_edd_refs += 0 if self.env_data_desc_snref is None else 1
|
39
|
-
|
40
|
-
odxassert(
|
41
|
-
num_struct_refs + num_edd_refs == 1,
|
42
|
-
"END-OF-PDU-FIELDs need to specify exactly one reference to a "
|
43
|
-
"structure or an environment data description")
|
44
|
-
|
45
22
|
@staticmethod
|
46
23
|
def from_et(et_element: ElementTree.Element,
|
47
24
|
doc_frags: List[OdxDocFragment]) -> "EndOfPduField":
|
48
|
-
kwargs = dataclass_fields_asdict(
|
49
|
-
sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
|
50
|
-
|
51
|
-
structure_ref = OdxLinkRef.from_et(et_element.find("BASIC-STRUCTURE-REF"), doc_frags)
|
52
|
-
structure_snref = None
|
53
|
-
if (edsnr_elem := et_element.find("BASIC-STRUCTURE-SNREF")) is not None:
|
54
|
-
structure_snref = edsnr_elem.get("SHORT-NAME")
|
55
|
-
|
56
|
-
env_data_desc_ref = OdxLinkRef.from_et(et_element.find("ENV-DATA-DESC-REF"), doc_frags)
|
57
|
-
env_data_desc_snref = None
|
58
|
-
if (edsnr_elem := et_element.find("ENV-DATA-DESC-SNREF")) is not None:
|
59
|
-
env_data_desc_snref = edsnr_elem.get("SHORT-NAME")
|
25
|
+
kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
|
60
26
|
|
61
27
|
if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
|
62
28
|
min_number_of_items = int(min_n_str)
|
@@ -67,32 +33,13 @@ class EndOfPduField(DopBase):
|
|
67
33
|
else:
|
68
34
|
max_number_of_items = None
|
69
35
|
|
70
|
-
is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
|
71
36
|
eopf = EndOfPduField(
|
72
|
-
sdgs=sdgs,
|
73
|
-
structure_ref=structure_ref,
|
74
|
-
structure_snref=structure_snref,
|
75
|
-
env_data_desc_ref=env_data_desc_ref,
|
76
|
-
env_data_desc_snref=env_data_desc_snref,
|
77
37
|
min_number_of_items=min_number_of_items,
|
78
38
|
max_number_of_items=max_number_of_items,
|
79
|
-
is_visible_raw=is_visible_raw,
|
80
39
|
**kwargs)
|
81
40
|
|
82
41
|
return eopf
|
83
42
|
|
84
|
-
@property
|
85
|
-
def structure(self) -> "BasicStructure":
|
86
|
-
"""may be a Structure or a env-data-desc"""
|
87
|
-
return self._structure
|
88
|
-
|
89
|
-
@property
|
90
|
-
def bit_length(self):
|
91
|
-
return self.structure.bit_length
|
92
|
-
|
93
|
-
def convert_physical_to_internal(self, physical_value):
|
94
|
-
return self.structure.convert_physical_to_internal(physical_value)
|
95
|
-
|
96
43
|
def convert_physical_to_bytes(
|
97
44
|
self,
|
98
45
|
physical_value: ParameterValueDict,
|
@@ -133,21 +80,3 @@ class EndOfPduField(DopBase):
|
|
133
80
|
value.append(new_value)
|
134
81
|
|
135
82
|
return value, next_byte_position
|
136
|
-
|
137
|
-
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
138
|
-
"""Recursively resolve any odxlinks references"""
|
139
|
-
if self.structure_ref is not None:
|
140
|
-
self._structure = odxlinks.resolve(self.structure_ref)
|
141
|
-
|
142
|
-
if self.env_data_desc_ref is not None:
|
143
|
-
self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref)
|
144
|
-
|
145
|
-
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
146
|
-
"""Recursively resolve any short-name references"""
|
147
|
-
if self.structure_snref is not None:
|
148
|
-
structures = diag_layer.diag_data_dictionary_spec.structures
|
149
|
-
self._structure = structures[self.structure_snref]
|
150
|
-
|
151
|
-
if self.env_data_desc_snref is not None:
|
152
|
-
env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
|
153
|
-
self._env_data_desc = env_data_descs[self.env_data_desc_snref]
|
odxtools/field.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import TYPE_CHECKING, List, Optional
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from .environmentdatadescription import EnvironmentDataDescription
|
6
|
+
from .basicstructure import BasicStructure
|
7
|
+
from .createsdgs import create_sdgs_from_et
|
8
|
+
from .dopbase import DopBase
|
9
|
+
from .element import IdentifiableElement
|
10
|
+
from .exceptions import odxassert, odxrequire
|
11
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
|
12
|
+
from .odxtypes import odxstr_to_bool
|
13
|
+
from .utils import dataclass_fields_asdict
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from .diaglayer import DiagLayer
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class Field(DopBase):
|
21
|
+
structure_ref: Optional[OdxLinkRef]
|
22
|
+
structure_snref: Optional[str]
|
23
|
+
env_data_desc_ref: Optional[OdxLinkRef]
|
24
|
+
env_data_desc_snref: Optional[str]
|
25
|
+
|
26
|
+
def __post_init__(self) -> None:
|
27
|
+
self._structure: Optional[BasicStructure] = None
|
28
|
+
self._env_data_desc: Optional[EnvironmentDataDescription] = None
|
29
|
+
num_struct_refs = 0 if self.structure_ref is None else 1
|
30
|
+
num_struct_refs += 0 if self.structure_snref is None else 1
|
31
|
+
|
32
|
+
num_edd_refs = 0 if self.env_data_desc_ref is None else 1
|
33
|
+
num_edd_refs += 0 if self.env_data_desc_snref is None else 1
|
34
|
+
|
35
|
+
odxassert(
|
36
|
+
num_struct_refs + num_edd_refs == 1,
|
37
|
+
"FIELDs need to specify exactly one reference to a "
|
38
|
+
"structure or an environment data description")
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Field":
|
42
|
+
kwargs = dataclass_fields_asdict(DopBase.from_et(et_element, doc_frags))
|
43
|
+
sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
|
44
|
+
|
45
|
+
structure_ref = OdxLinkRef.from_et(et_element.find("BASIC-STRUCTURE-REF"), doc_frags)
|
46
|
+
structure_snref = None
|
47
|
+
if (edsnr_elem := et_element.find("BASIC-STRUCTURE-SNREF")) is not None:
|
48
|
+
structure_snref = edsnr_elem.get("SHORT-NAME")
|
49
|
+
|
50
|
+
env_data_desc_ref = OdxLinkRef.from_et(et_element.find("ENV-DATA-DESC-REF"), doc_frags)
|
51
|
+
env_data_desc_snref = None
|
52
|
+
if (edsnr_elem := et_element.find("ENV-DATA-DESC-SNREF")) is not None:
|
53
|
+
env_data_desc_snref = edsnr_elem.get("SHORT-NAME")
|
54
|
+
|
55
|
+
is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
|
56
|
+
return Field(
|
57
|
+
sdgs=sdgs,
|
58
|
+
structure_ref=structure_ref,
|
59
|
+
structure_snref=structure_snref,
|
60
|
+
env_data_desc_ref=env_data_desc_ref,
|
61
|
+
env_data_desc_snref=env_data_desc_snref,
|
62
|
+
is_visible_raw=is_visible_raw,
|
63
|
+
**kwargs)
|
64
|
+
|
65
|
+
@property
|
66
|
+
def structure(self) -> BasicStructure:
|
67
|
+
"""may be a Structure or a env-data-desc"""
|
68
|
+
return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
|
69
|
+
|
70
|
+
@property
|
71
|
+
def bit_length(self):
|
72
|
+
return self.structure.bit_length
|
73
|
+
|
74
|
+
def convert_physical_to_internal(self, physical_value):
|
75
|
+
return self.structure.convert_physical_to_internal(physical_value)
|
76
|
+
|
77
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
78
|
+
"""Recursively resolve any odxlinks references"""
|
79
|
+
if self.structure_ref is not None:
|
80
|
+
self._structure = odxlinks.resolve(self.structure_ref, BasicStructure)
|
81
|
+
|
82
|
+
if self.env_data_desc_ref is not None:
|
83
|
+
self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref,
|
84
|
+
EnvironmentDataDescription)
|
85
|
+
|
86
|
+
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
87
|
+
"""Recursively resolve any short-name references"""
|
88
|
+
if self.structure_snref is not None:
|
89
|
+
structures = diag_layer.diag_data_dictionary_spec.structures
|
90
|
+
self._structure = odxrequire(structures.get(self.structure_snref))
|
91
|
+
|
92
|
+
if self.env_data_desc_snref is not None:
|
93
|
+
env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
|
94
|
+
self._env_data_desc = odxrequire(env_data_descs.get(self.env_data_desc_snref))
|
odxtools/isotp_state_machine.py
CHANGED
@@ -5,6 +5,7 @@ import asyncio
|
|
5
5
|
import re
|
6
6
|
import sys
|
7
7
|
from enum import IntEnum
|
8
|
+
from typing import Iterable, List, Optional, Tuple, Union
|
8
9
|
|
9
10
|
import bitstruct
|
10
11
|
import can
|
@@ -28,7 +29,7 @@ class IsoTpStateMachine:
|
|
28
29
|
"([a-zA-Z0-9_-]*) *([0-9A-Fa-f ]*) *\[[0-9]*\] *([ 0-9A-Fa-f]*)")
|
29
30
|
can_log_frame_re = re.compile("\([0-9.]*\) *([a-zA-Z0-9_-]*) ([0-9A-Fa-f]*)#([0-9A-Fa-f]*)")
|
30
31
|
|
31
|
-
def __init__(self, can_rx_ids):
|
32
|
+
def __init__(self, can_rx_ids: Union[int, List[int]]):
|
32
33
|
if isinstance(can_rx_ids, int):
|
33
34
|
can_rx_ids = [can_rx_ids]
|
34
35
|
|
@@ -36,21 +37,21 @@ class IsoTpStateMachine:
|
|
36
37
|
assert isinstance(self._can_rx_ids, list)
|
37
38
|
|
38
39
|
self._telegram_specified_len = [0] * len(can_rx_ids)
|
39
|
-
self._telegram_data = [None] * len(can_rx_ids)
|
40
|
+
self._telegram_data: List[Optional[bytearray]] = [None] * len(can_rx_ids)
|
40
41
|
self._telegram_last_rx_fragment_idx = [0] * len(can_rx_ids)
|
41
42
|
|
42
|
-
def decode_rx_frame(self, rx_id, data,
|
43
|
+
def decode_rx_frame(self, rx_id: int, data: bytes) -> Iterable[Tuple[int, bytes]]:
|
43
44
|
"""Handle the ISO-TP state transitions caused by a CAN frame.
|
44
45
|
|
45
|
-
E.g., add some data to a telegram, etc.
|
46
|
+
E.g., add some data to a telegram, etc. Returns a generator of
|
47
|
+
(receive_id, payload_data) tuples.
|
46
48
|
"""
|
47
49
|
try:
|
48
50
|
telegram_idx = self._can_rx_ids.index(rx_id)
|
49
51
|
except ValueError:
|
50
|
-
return
|
52
|
+
return # unknown CAN ID
|
51
53
|
|
52
54
|
# decode the isotp segment
|
53
|
-
result = True
|
54
55
|
(frame_type,) = bitstruct.unpack("u4", data)
|
55
56
|
telegram_len = None
|
56
57
|
if frame_type == IsoTp.FRAME_TYPE_SINGLE:
|
@@ -59,14 +60,13 @@ class IsoTpStateMachine:
|
|
59
60
|
self.on_single_frame(telegram_idx, data[1:1 + telegram_len])
|
60
61
|
self.on_telegram_complete(telegram_idx, data[1:1 + telegram_len])
|
61
62
|
|
62
|
-
|
63
|
-
yield (rx_id, data[1:1 + telegram_len])
|
63
|
+
yield (rx_id, data[1:1 + telegram_len])
|
64
64
|
|
65
65
|
elif frame_type == IsoTp.FRAME_TYPE_FIRST:
|
66
66
|
frame_type, telegram_len = bitstruct.unpack("u4u12", data)
|
67
67
|
|
68
68
|
self._telegram_specified_len[telegram_idx] = telegram_len
|
69
|
-
self._telegram_data[telegram_idx] = data[2:]
|
69
|
+
self._telegram_data[telegram_idx] = bytearray(data[2:])
|
70
70
|
self._telegram_last_rx_fragment_idx[telegram_idx] = 0
|
71
71
|
|
72
72
|
self.on_first_frame(telegram_idx, data)
|
@@ -75,38 +75,34 @@ class IsoTpStateMachine:
|
|
75
75
|
frame_type, rx_segment_idx = bitstruct.unpack("u4u4", data)
|
76
76
|
|
77
77
|
expected_segment_idx = (self._telegram_last_rx_fragment_idx[telegram_idx] + 1) % 16
|
78
|
+
telegram_data = self._telegram_data[telegram_idx]
|
79
|
+
assert isinstance(telegram_data, bytearray)
|
78
80
|
|
79
81
|
if expected_segment_idx == rx_segment_idx:
|
80
82
|
self._telegram_last_rx_fragment_idx[telegram_idx] = rx_segment_idx
|
81
|
-
|
83
|
+
telegram_data += data[1:]
|
82
84
|
|
83
85
|
n = self._telegram_specified_len[telegram_idx]
|
84
|
-
if len(
|
86
|
+
if len(telegram_data) > n:
|
85
87
|
# can frames can include padding, i.e. the length
|
86
88
|
# of the telegram payload is not necessarily a
|
87
89
|
# multiple of the segment payloads
|
88
|
-
|
89
|
-
else:
|
90
|
-
result = False
|
90
|
+
telegram_data = telegram_data[:n]
|
91
91
|
|
92
92
|
self.on_consecutive_frame(telegram_idx, rx_segment_idx, data[1:])
|
93
93
|
|
94
94
|
if expected_segment_idx != rx_segment_idx:
|
95
95
|
self.on_sequence_error(telegram_idx, expected_segment_idx, rx_segment_idx)
|
96
|
-
elif len(
|
97
|
-
self.on_telegram_complete(telegram_idx,
|
98
|
-
|
99
|
-
yield (rx_id, self._telegram_data[telegram_idx])
|
96
|
+
elif len(telegram_data) == n:
|
97
|
+
self.on_telegram_complete(telegram_idx, telegram_data)
|
98
|
+
yield (rx_id, telegram_data)
|
100
99
|
|
101
100
|
elif frame_type == IsoTp.FRAME_TYPE_FLOW_CONTROL:
|
102
101
|
frame_type, flow_control_flag = bitstruct.unpack("u4u4", data)
|
103
102
|
self.on_flow_control_frame(telegram_idx, flow_control_flag)
|
104
103
|
else:
|
105
|
-
result = False
|
106
104
|
self.on_frame_type_error(telegram_idx, frame_type)
|
107
105
|
|
108
|
-
return result
|
109
|
-
|
110
106
|
async def read_telegrams(self, bus):
|
111
107
|
"""This is equivalent to the :py:meth:`file.readlines()` method, but
|
112
108
|
it yields ISO-TP telegrams instead of lines.
|
@@ -130,7 +126,7 @@ class IsoTpStateMachine:
|
|
130
126
|
await rx_event.wait()
|
131
127
|
|
132
128
|
msg = bus.recv()
|
133
|
-
for tmp in self.decode_rx_frame(msg.arbitration_id, msg.data
|
129
|
+
for tmp in self.decode_rx_frame(msg.arbitration_id, msg.data):
|
134
130
|
yield tmp
|
135
131
|
else:
|
136
132
|
# input is a file
|
@@ -148,7 +144,7 @@ class IsoTpStateMachine:
|
|
148
144
|
frame_data = bytearray(
|
149
145
|
map(lambda x: int(x, 16), frame_data_formatted.split(" ")))
|
150
146
|
|
151
|
-
for tmp in self.decode_rx_frame(frame_id, frame_data
|
147
|
+
for tmp in self.decode_rx_frame(frame_id, frame_data):
|
152
148
|
yield tmp
|
153
149
|
|
154
150
|
elif m := self.can_log_frame_re.match(cur_line.strip()):
|
@@ -162,7 +158,7 @@ class IsoTpStateMachine:
|
|
162
158
|
]
|
163
159
|
frame_data = bytearray(map(lambda x: int(x, 16), frame_data_list))
|
164
160
|
|
165
|
-
for tmp in self.decode_rx_frame(frame_id, frame_data
|
161
|
+
for tmp in self.decode_rx_frame(frame_id, frame_data):
|
166
162
|
yield tmp
|
167
163
|
|
168
164
|
else:
|
odxtools/matchingparameter.py
CHANGED
odxtools/multiplexer.py
CHANGED
@@ -63,7 +63,7 @@ class Multiplexer(DopBase):
|
|
63
63
|
return None
|
64
64
|
|
65
65
|
def _get_case_limits(self, case: MultiplexerCase):
|
66
|
-
key_type = self.switch_key.
|
66
|
+
key_type = self.switch_key.dop.physical_type.base_data_type
|
67
67
|
lower_limit = key_type.make_from(case.lower_limit)
|
68
68
|
upper_limit = key_type.make_from(case.upper_limit)
|
69
69
|
return lower_limit, upper_limit
|
@@ -80,7 +80,6 @@ class Multiplexer(DopBase):
|
|
80
80
|
with only one key equal to the desired case""")
|
81
81
|
|
82
82
|
case_name, case_value = next(iter(physical_value.items()))
|
83
|
-
key_pos = self.switch_key.byte_position
|
84
83
|
case_pos = self.byte_position
|
85
84
|
|
86
85
|
for case in self.cases or []:
|
@@ -92,14 +91,11 @@ class Multiplexer(DopBase):
|
|
92
91
|
case_bytes = bytes()
|
93
92
|
|
94
93
|
key_value, _ = self._get_case_limits(case)
|
95
|
-
|
96
|
-
sk_bit_position = sk_bit_position if sk_bit_position is not None else 0
|
97
|
-
key_bytes = self.switch_key._dop.convert_physical_to_bytes(
|
98
|
-
key_value, encode_state, sk_bit_position)
|
94
|
+
key_bytes = self.switch_key.convert_physical_to_bytes(key_value, encode_state)
|
99
95
|
|
100
|
-
mux_len = max(len(key_bytes)
|
96
|
+
mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
|
101
97
|
mux_bytes = bytearray(mux_len)
|
102
|
-
mux_bytes[
|
98
|
+
mux_bytes[:len(key_bytes)] = key_bytes
|
103
99
|
mux_bytes[case_pos:case_pos + len(case_bytes)] = case_bytes
|
104
100
|
|
105
101
|
return bytes(mux_bytes)
|
@@ -111,17 +107,9 @@ class Multiplexer(DopBase):
|
|
111
107
|
if bit_position != 0:
|
112
108
|
raise DecodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
|
113
109
|
f"{self.short_name} was passed the bit position {bit_position}")
|
114
|
-
|
115
|
-
key_decode_state = DecodeState(
|
116
|
-
coded_message=byte_code[self.switch_key.byte_position:],
|
117
|
-
parameter_values=dict(),
|
118
|
-
next_byte_position=0,
|
119
|
-
)
|
120
|
-
bit_position_int = (
|
121
|
-
self.switch_key.bit_position if self.switch_key.bit_position is not None else 0)
|
122
|
-
key_value, key_next_byte = self.switch_key._dop.convert_bytes_to_physical(
|
123
|
-
key_decode_state, bit_position=bit_position_int)
|
110
|
+
key_value, key_next_byte = self.switch_key.convert_bytes_to_physical(decode_state)
|
124
111
|
|
112
|
+
byte_code = decode_state.coded_message[decode_state.next_byte_position:]
|
125
113
|
case_decode_state = DecodeState(
|
126
114
|
coded_message=byte_code[self.byte_position:],
|
127
115
|
parameter_values=dict(),
|
odxtools/multiplexercase.py
CHANGED
@@ -17,30 +17,46 @@ if TYPE_CHECKING:
|
|
17
17
|
class MultiplexerCase(NamedElement):
|
18
18
|
"""This class represents a Case which represents multiple options in a Multiplexer."""
|
19
19
|
|
20
|
-
structure_ref: OdxLinkRef
|
20
|
+
structure_ref: Optional[OdxLinkRef]
|
21
|
+
structure_snref: Optional[str]
|
21
22
|
lower_limit: str
|
22
23
|
upper_limit: str
|
23
24
|
|
24
25
|
def __post_init__(self) -> None:
|
25
|
-
self._structure:
|
26
|
+
self._structure: BasicStructure
|
26
27
|
|
27
28
|
@staticmethod
|
28
29
|
def from_et(et_element: ElementTree.Element,
|
29
30
|
doc_frags: List[OdxDocFragment]) -> "MultiplexerCase":
|
30
31
|
"""Reads a Case for a Multiplexer."""
|
31
32
|
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
|
32
|
-
structure_ref =
|
33
|
+
structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
|
34
|
+
structure_snref = None
|
35
|
+
if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
|
36
|
+
structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
|
37
|
+
|
33
38
|
lower_limit = odxrequire(et_element.findtext("LOWER-LIMIT"))
|
34
39
|
upper_limit = odxrequire(et_element.findtext("UPPER-LIMIT"))
|
35
40
|
|
36
41
|
return MultiplexerCase(
|
37
|
-
structure_ref=structure_ref,
|
42
|
+
structure_ref=structure_ref,
|
43
|
+
structure_snref=structure_snref,
|
44
|
+
lower_limit=lower_limit,
|
45
|
+
upper_limit=upper_limit,
|
46
|
+
**kwargs)
|
38
47
|
|
39
48
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
40
49
|
return {}
|
41
50
|
|
42
51
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
43
|
-
|
52
|
+
if self.structure_ref:
|
53
|
+
self._structure = odxlinks.resolve(self.structure_ref)
|
44
54
|
|
45
55
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
46
|
-
|
56
|
+
if self.structure_snref:
|
57
|
+
ddds = diag_layer.diag_data_dictionary_spec
|
58
|
+
self._structure = odxrequire(ddds.structures.get(self.structure_snref))
|
59
|
+
|
60
|
+
@property
|
61
|
+
def structure(self) -> BasicStructure:
|
62
|
+
return self._structure
|
@@ -5,6 +5,7 @@ from xml.etree import ElementTree
|
|
5
5
|
|
6
6
|
from .basicstructure import BasicStructure
|
7
7
|
from .element import NamedElement
|
8
|
+
from .exceptions import odxrequire
|
8
9
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
9
10
|
from .utils import dataclass_fields_asdict
|
10
11
|
|
@@ -16,6 +17,7 @@ if TYPE_CHECKING:
|
|
16
17
|
class MultiplexerDefaultCase(NamedElement):
|
17
18
|
"""This class represents a Default Case, which is selected when there are no cases defined in the Multiplexer."""
|
18
19
|
structure_ref: Optional[OdxLinkRef]
|
20
|
+
structure_snref: Optional[str]
|
19
21
|
|
20
22
|
def __post_init__(self) -> None:
|
21
23
|
self._structure: Optional[BasicStructure] = None
|
@@ -27,8 +29,12 @@ class MultiplexerDefaultCase(NamedElement):
|
|
27
29
|
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
|
28
30
|
|
29
31
|
structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
|
32
|
+
structure_snref = None
|
33
|
+
if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
|
34
|
+
structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
|
30
35
|
|
31
|
-
return MultiplexerDefaultCase(
|
36
|
+
return MultiplexerDefaultCase(
|
37
|
+
structure_ref=structure_ref, structure_snref=structure_snref, **kwargs)
|
32
38
|
|
33
39
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
34
40
|
return {}
|
@@ -38,4 +44,6 @@ class MultiplexerDefaultCase(NamedElement):
|
|
38
44
|
self._structure = odxlinks.resolve(self.structure_ref)
|
39
45
|
|
40
46
|
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
41
|
-
|
47
|
+
if self.structure_snref:
|
48
|
+
ddds = diag_layer.diag_data_dictionary_spec
|
49
|
+
self._structure = odxrequire(ddds.structures.get(self.structure_snref))
|
odxtools/multiplexerswitchkey.py
CHANGED
@@ -1,53 +1,18 @@
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
2
1
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
2
|
+
from typing import List
|
4
3
|
from xml.etree import ElementTree
|
5
4
|
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
from .
|
9
|
-
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
10
|
-
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from .diaglayer import DiagLayer
|
5
|
+
from .odxlink import OdxDocFragment
|
6
|
+
from .positioneddataobjectproperty import PositionedDataObjectProperty
|
7
|
+
from .utils import dataclass_fields_asdict
|
13
8
|
|
14
9
|
|
15
10
|
@dataclass
|
16
|
-
class MultiplexerSwitchKey:
|
17
|
-
"""This class represents a Switch Key, which is used to select one of the cases defined in the Multiplexer."""
|
18
|
-
|
19
|
-
byte_position: int
|
20
|
-
bit_position: Optional[int]
|
21
|
-
dop_ref: OdxLinkRef
|
22
|
-
|
23
|
-
def __post_init__(self):
|
24
|
-
self._dop: DataObjectProperty = None # type: ignore
|
11
|
+
class MultiplexerSwitchKey(PositionedDataObjectProperty):
|
25
12
|
|
26
13
|
@staticmethod
|
27
14
|
def from_et(et_element: ElementTree.Element,
|
28
15
|
doc_frags: List[OdxDocFragment]) -> "MultiplexerSwitchKey":
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
bit_position = int(bit_position_str) if bit_position_str is not None else None
|
33
|
-
dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), doc_frags))
|
34
|
-
|
35
|
-
return MultiplexerSwitchKey(
|
36
|
-
byte_position=byte_position,
|
37
|
-
bit_position=bit_position,
|
38
|
-
dop_ref=dop_ref,
|
39
|
-
)
|
40
|
-
|
41
|
-
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
42
|
-
return {}
|
43
|
-
|
44
|
-
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
45
|
-
dop = odxlinks.resolve(self.dop_ref)
|
46
|
-
if isinstance(dop, DataObjectProperty):
|
47
|
-
self._dop = dop
|
48
|
-
else:
|
49
|
-
logger.warning(
|
50
|
-
f"DATA-OBJECT-PROP-REF '{self.dop_ref}' could not be resolved in SWITCH-KEY.")
|
51
|
-
|
52
|
-
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
53
|
-
pass
|
16
|
+
kwargs = dataclass_fields_asdict(
|
17
|
+
PositionedDataObjectProperty.from_et(et_element, doc_frags))
|
18
|
+
return MultiplexerSwitchKey(**kwargs)
|
odxtools/odxlink.py
CHANGED
@@ -12,11 +12,15 @@ class OdxDocFragment:
|
|
12
12
|
doc_name: str
|
13
13
|
doc_type: Optional[str]
|
14
14
|
|
15
|
-
def __eq__(self, other) -> bool:
|
15
|
+
def __eq__(self, other: Any) -> bool:
|
16
16
|
if other is None:
|
17
|
-
# if the other document fragment is not specified, we
|
17
|
+
# if the other document fragment is not specified, we
|
18
|
+
# treat it as a wildcard...
|
18
19
|
return True
|
19
20
|
|
21
|
+
if not isinstance(other, OdxDocFragment):
|
22
|
+
return False
|
23
|
+
|
20
24
|
# the ODX spec says that the doctype can be ignored...
|
21
25
|
return self.doc_name == other.doc_name
|
22
26
|
|
@@ -59,7 +63,7 @@ class OdxLinkId:
|
|
59
63
|
# i.e. the same OdxId object can be put into all of them.
|
60
64
|
return self.local_id == other.local_id
|
61
65
|
|
62
|
-
def __str__(self):
|
66
|
+
def __str__(self) -> str:
|
63
67
|
return f"OdxLinkId('{self.local_id}')"
|
64
68
|
|
65
69
|
@staticmethod
|
@@ -139,7 +143,7 @@ class OdxLinkRef:
|
|
139
143
|
"""Construct an OdxLinkRef for a given OdxLinkId."""
|
140
144
|
return OdxLinkRef(odxid.local_id, odxid.doc_fragments)
|
141
145
|
|
142
|
-
def __str__(self):
|
146
|
+
def __str__(self) -> str:
|
143
147
|
return f"OdxLinkRef('{self.ref_id}')"
|
144
148
|
|
145
149
|
def __contains__(self, odx_id: OdxLinkId) -> bool:
|
odxtools/odxtypes.py
CHANGED
@@ -6,7 +6,7 @@ from xml.etree import ElementTree
|
|
6
6
|
from .exceptions import odxassert, odxraise, odxrequire
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from
|
9
|
+
from .parameters.parameter import Parameter
|
10
10
|
|
11
11
|
|
12
12
|
def bytefield_to_bytearray(bytefield: str) -> bytearray:
|
@@ -47,10 +47,12 @@ class CodedConstParameter(Parameter):
|
|
47
47
|
def internal_data_type(self) -> DataType:
|
48
48
|
return self.diag_coded_type.base_data_type
|
49
49
|
|
50
|
-
|
50
|
+
@property
|
51
|
+
def is_required(self) -> bool:
|
51
52
|
return False
|
52
53
|
|
53
|
-
|
54
|
+
@property
|
55
|
+
def is_settable(self) -> bool:
|
54
56
|
return False
|
55
57
|
|
56
58
|
def get_coded_value(self):
|
@@ -11,11 +11,13 @@ class DynamicParameter(Parameter):
|
|
11
11
|
def parameter_type(self) -> ParameterType:
|
12
12
|
return "DYNAMIC"
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
@property
|
15
|
+
def is_required(self) -> bool:
|
16
|
+
raise NotImplementedError(".is_required for a DynamicParameter")
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
@property
|
19
|
+
def is_settable(self) -> bool:
|
20
|
+
raise NotImplementedError(".is_settable for a DynamicParameter")
|
19
21
|
|
20
22
|
def get_coded_value(self):
|
21
23
|
raise NotImplementedError("Encoding a DynamicParameter is not implemented yet.")
|