odxtools 6.4.2__py3-none-any.whl → 6.5.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/basicstructure.py +7 -3
- odxtools/cli/_print_utils.py +210 -25
- odxtools/cli/compare.py +730 -0
- odxtools/cli/decode.py +1 -1
- odxtools/cli/find.py +13 -4
- odxtools/cli/list.py +74 -40
- odxtools/cli/main.py +1 -1
- odxtools/database.py +1 -0
- odxtools/dataobjectproperty.py +15 -2
- odxtools/diagcodedtype.py +85 -67
- odxtools/internalconstr.py +34 -0
- odxtools/leadinglengthinfotype.py +5 -5
- odxtools/minmaxlengthtype.py +3 -3
- odxtools/paramlengthinfotype.py +2 -2
- odxtools/scaleconstr.py +50 -0
- odxtools/standardlengthtype.py +2 -2
- odxtools/templates/macros/printDOP.xml.jinja2 +52 -5
- odxtools/version.py +2 -2
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/METADATA +388 -47
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/RECORD +24 -21
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/LICENSE +0 -0
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/WHEEL +0 -0
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/entry_points.txt +0 -0
- {odxtools-6.4.2.dist-info → odxtools-6.5.0.dist-info}/top_level.txt +0 -0
odxtools/cli/decode.py
CHANGED
@@ -84,7 +84,7 @@ def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
|
84
84
|
" For more information use:",
|
85
85
|
" odxtools decode -h",
|
86
86
|
]),
|
87
|
-
help="Find & print service by hex-data. Can also decode the hex-data to
|
87
|
+
help="Find & print service by hex-data. Can also decode the hex-data to its named parameters.",
|
88
88
|
formatter_class=argparse.RawTextHelpFormatter,
|
89
89
|
)
|
90
90
|
_parser_utils.add_pdx_argument(parser)
|
odxtools/cli/find.py
CHANGED
@@ -26,7 +26,8 @@ def print_summary(odxdb: Database,
|
|
26
26
|
service_names: List[str],
|
27
27
|
ecu_variants: Optional[List[str]] = None,
|
28
28
|
allow_unknown_bit_lengths: bool = False,
|
29
|
-
print_params: bool = False
|
29
|
+
print_params: bool = False,
|
30
|
+
plumbing_output: bool = False) -> None:
|
30
31
|
ecu_names = ecu_variants if ecu_variants else [ecu.short_name for ecu in odxdb.ecus]
|
31
32
|
service_db: Dict[str, DiagService] = {}
|
32
33
|
service_ecus: Dict[str, List[str]] = {}
|
@@ -60,7 +61,7 @@ def print_summary(odxdb: Database,
|
|
60
61
|
print_pre_condition_states=True,
|
61
62
|
print_state_transitions=True,
|
62
63
|
print_audiences=True,
|
63
|
-
|
64
|
+
plumbing_output=plumbing_output)
|
64
65
|
elif isinstance(service, SingleEcuJob):
|
65
66
|
print(f"SingleEcuJob: {service.odx_id}")
|
66
67
|
else:
|
@@ -79,7 +80,7 @@ def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
|
79
80
|
" For more information use:",
|
80
81
|
" odxtools find -h",
|
81
82
|
]),
|
82
|
-
help="Find &
|
83
|
+
help="Find & display services by their name",
|
83
84
|
formatter_class=argparse.RawTextHelpFormatter,
|
84
85
|
)
|
85
86
|
_parser_utils.add_pdx_argument(parser)
|
@@ -120,6 +121,14 @@ def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
|
120
121
|
help="Relax output formatting rules (allow unknown bitlengths for ascii representation)",
|
121
122
|
)
|
122
123
|
|
124
|
+
parser.add_argument(
|
125
|
+
"-po",
|
126
|
+
"--plumbing-output",
|
127
|
+
action="store_true",
|
128
|
+
required=False,
|
129
|
+
help="Print full objects instead of selected and formatted attributes",
|
130
|
+
)
|
131
|
+
|
123
132
|
|
124
133
|
def run(args: argparse.Namespace) -> None:
|
125
134
|
odxdb = _parser_utils.load_file(args)
|
@@ -131,4 +140,4 @@ def run(args: argparse.Namespace) -> None:
|
|
131
140
|
service_names=args.service_names,
|
132
141
|
print_params=not args.no_details,
|
133
142
|
allow_unknown_bit_lengths=args.relaxed_output,
|
134
|
-
|
143
|
+
plumbing_output=args.plumbing_output)
|
odxtools/cli/list.py
CHANGED
@@ -2,40 +2,57 @@
|
|
2
2
|
import argparse
|
3
3
|
from typing import Callable, List, Optional
|
4
4
|
|
5
|
+
from rich import print
|
6
|
+
|
5
7
|
from ..database import Database
|
6
8
|
from ..diagcomm import DiagComm
|
7
9
|
from ..diaglayer import DiagLayer
|
8
10
|
from ..diagservice import DiagService
|
9
11
|
from ..singleecujob import SingleEcuJob
|
10
12
|
from . import _parser_utils
|
11
|
-
from ._print_utils import format_desc, print_diagnostic_service
|
13
|
+
from ._print_utils import format_desc, print_diagnostic_service, print_dl_metrics
|
12
14
|
|
13
15
|
# name of the tool
|
14
16
|
_odxtools_tool_name_ = "list"
|
15
17
|
|
16
18
|
|
17
|
-
def print_summary(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
19
|
+
def print_summary(odxdb: Database,
|
20
|
+
print_global_negative_responses: bool = False,
|
21
|
+
print_services: bool = False,
|
22
|
+
print_dops: bool = False,
|
23
|
+
print_params: bool = False,
|
24
|
+
print_comparams: bool = False,
|
25
|
+
print_pre_condition_states: bool = False,
|
26
|
+
print_state_transitions: bool = False,
|
27
|
+
print_audiences: bool = False,
|
28
|
+
allow_unknown_bit_lengths: bool = False,
|
29
|
+
variants: Optional[List[str]] = None,
|
30
|
+
service_filter: Callable[[DiagComm], bool] = lambda x: True,
|
31
|
+
plumbing_output: bool = False) -> None:
|
32
|
+
|
33
|
+
diag_layer_names = [dl.short_name for dl in odxdb.diag_layers]
|
34
|
+
diag_layers: List[DiagLayer] = []
|
35
|
+
|
36
|
+
if variants is None:
|
37
|
+
variants = diag_layer_names
|
38
|
+
|
39
|
+
for name in variants:
|
40
|
+
if name in diag_layer_names:
|
41
|
+
diag_layers.append([x for x in odxdb.diag_layers if x.short_name == name][0])
|
42
|
+
|
43
|
+
else:
|
44
|
+
print(f"The variant '{name}' could not be found!")
|
45
|
+
return
|
46
|
+
|
47
|
+
if diag_layers:
|
48
|
+
print("\n")
|
49
|
+
print(f"Overview of diagnostic layers: ")
|
50
|
+
print_dl_metrics(diag_layers)
|
51
|
+
|
52
|
+
for dl in diag_layers:
|
53
|
+
print("\n")
|
54
|
+
print(f"[green]Diagnostic layer:[/green] '[bold white]{dl.short_name}[/bold white]'")
|
55
|
+
print(f" [blue]Variant Type[/blue]: {dl.variant_type.value}")
|
39
56
|
|
40
57
|
assert isinstance(dl, DiagLayer)
|
41
58
|
all_services: List[DiagComm] = sorted(dl.services, key=lambda x: x.short_name)
|
@@ -43,31 +60,35 @@ def print_summary(
|
|
43
60
|
data_object_properties = dl.diag_data_dictionary_spec.data_object_props
|
44
61
|
comparams = dl.comparams
|
45
62
|
|
46
|
-
print(f"{dl.variant_type} '{dl.short_name}'")
|
47
|
-
print(
|
48
|
-
f" num services: {len(all_services)}, num DOPs: {len(data_object_properties)}, num communication parameters: {len(comparams)}."
|
49
|
-
)
|
50
|
-
|
51
63
|
for proto in dl.protocols:
|
52
64
|
if (can_rx_id := dl.get_can_receive_id(proto.short_name)) is not None:
|
53
|
-
print(
|
65
|
+
print(
|
66
|
+
f" [blue]CAN receive ID[/blue] for protocol '{proto.short_name}': 0x{can_rx_id:x}"
|
67
|
+
)
|
54
68
|
|
55
69
|
if (can_tx_id := dl.get_can_send_id(proto.short_name)) is not None:
|
56
|
-
print(
|
70
|
+
print(
|
71
|
+
f" [blue]CAN send ID[/blue] for protocol '{proto.short_name}': 0x{can_tx_id:x}"
|
72
|
+
)
|
57
73
|
|
58
74
|
if dl.description:
|
59
75
|
desc = format_desc(dl.description, ident=2)
|
60
|
-
print(f" Description: " + desc)
|
76
|
+
print(f" [blue]Description[/blue]: " + desc)
|
61
77
|
|
62
78
|
if print_global_negative_responses and dl.global_negative_responses:
|
63
|
-
print(
|
79
|
+
print("\n")
|
80
|
+
print(f"The [blue]global negative responses[/blue] of '{dl.short_name}' are: ")
|
64
81
|
for gnr in dl.global_negative_responses:
|
65
|
-
|
82
|
+
if plumbing_output:
|
83
|
+
print(f" {gnr}")
|
84
|
+
else:
|
85
|
+
print(f" {gnr.short_name}")
|
66
86
|
|
67
87
|
if print_services and len(all_services) > 0:
|
68
88
|
services = [s for s in all_services if service_filter(s)]
|
69
89
|
if len(services) > 0:
|
70
|
-
print(
|
90
|
+
print("\n")
|
91
|
+
print(f"The [blue]services[/blue] of '{dl.short_name}' are: ")
|
71
92
|
for service in services:
|
72
93
|
if isinstance(service, DiagService):
|
73
94
|
print_diagnostic_service(
|
@@ -77,21 +98,26 @@ def print_summary(
|
|
77
98
|
print_state_transitions=print_state_transitions,
|
78
99
|
print_audiences=print_audiences,
|
79
100
|
allow_unknown_bit_lengths=allow_unknown_bit_lengths,
|
80
|
-
|
101
|
+
plumbing_output=plumbing_output)
|
81
102
|
elif isinstance(service, SingleEcuJob):
|
82
|
-
print(f" Single ECU job: {service.odx_id}")
|
103
|
+
print(f" [blue]Single ECU job[/blue]: {service.odx_id}")
|
83
104
|
else:
|
84
105
|
print(f" Unidentifiable service: {service}")
|
85
106
|
|
86
107
|
if print_dops and len(data_object_properties) > 0:
|
87
|
-
print(
|
108
|
+
print("\n")
|
109
|
+
print(f"The [blue]DOPs[/blue] of the {dl.variant_type.value} '{dl.short_name}' are: ")
|
88
110
|
for dop in sorted(
|
89
111
|
data_object_properties, key=lambda x: (type(x).__name__, x.short_name)):
|
90
|
-
|
112
|
+
if plumbing_output:
|
113
|
+
print(" " + str(dop).replace("\n", "\n "))
|
114
|
+
else:
|
115
|
+
print(" " + str(dop.short_name).replace("\n", "\n "))
|
91
116
|
|
92
117
|
if print_comparams and len(comparams) > 0:
|
118
|
+
print("\n")
|
93
119
|
print(
|
94
|
-
f"The communication parameters of the {dl.variant_type.value} '{dl.short_name}' are: "
|
120
|
+
f"The [blue]communication parameters[/blue] of the {dl.variant_type.value} '{dl.short_name}' are: "
|
95
121
|
)
|
96
122
|
for com_param in comparams:
|
97
123
|
print(f" {com_param.short_name}: {com_param.value}")
|
@@ -178,6 +204,14 @@ def add_subparser(subparsers: "argparse._SubParsersAction") -> None:
|
|
178
204
|
help="Print a list of all diagnostic services and DOPs specified in the pdx",
|
179
205
|
)
|
180
206
|
|
207
|
+
parser.add_argument(
|
208
|
+
"-po",
|
209
|
+
"--plumbing-output",
|
210
|
+
action="store_true",
|
211
|
+
required=False,
|
212
|
+
help="Print full objects instead of selected and formatted attributes",
|
213
|
+
)
|
214
|
+
|
181
215
|
|
182
216
|
def run(args: argparse.Namespace) -> None:
|
183
217
|
odxdb = _parser_utils.load_file(args)
|
@@ -201,4 +235,4 @@ def run(args: argparse.Namespace) -> None:
|
|
201
235
|
print_state_transitions=args.all,
|
202
236
|
print_audiences=args.all,
|
203
237
|
allow_unknown_bit_lengths=args.all,
|
204
|
-
|
238
|
+
plumbing_output=args.plumbing_output)
|
odxtools/cli/main.py
CHANGED
@@ -9,7 +9,7 @@ from .dummy_sub_parser import DummyTool
|
|
9
9
|
# import the tool modules which can be loaded. if a tool
|
10
10
|
# can't be loaded, add a dummy one
|
11
11
|
tool_modules: List[Any] = []
|
12
|
-
for tool_name in ["list", "browse", "snoop", "find", "decode"]:
|
12
|
+
for tool_name in ["list", "browse", "snoop", "find", "decode", "compare"]:
|
13
13
|
try:
|
14
14
|
tool_modules.append(importlib.import_module(f".{tool_name}", package="odxtools.cli"))
|
15
15
|
except Exception as e:
|
odxtools/database.py
CHANGED
odxtools/dataobjectproperty.py
CHANGED
@@ -11,6 +11,7 @@ from .diagcodedtype import DiagCodedType
|
|
11
11
|
from .dopbase import DopBase
|
12
12
|
from .encodestate import EncodeState
|
13
13
|
from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
|
14
|
+
from .internalconstr import InternalConstr
|
14
15
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
15
16
|
from .odxtypes import AtomicOdxType, ParameterValue
|
16
17
|
from .physicaltype import PhysicalType
|
@@ -41,8 +42,8 @@ class DataObjectProperty(DopBase):
|
|
41
42
|
#: The unit associated with physical values (e.g. 'm/s^2')
|
42
43
|
unit_ref: Optional[OdxLinkRef]
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
internal_constr: Optional[InternalConstr]
|
46
|
+
physical_constr: Optional[InternalConstr]
|
46
47
|
|
47
48
|
@staticmethod
|
48
49
|
def from_et(et_element: ElementTree.Element,
|
@@ -63,11 +64,23 @@ class DataObjectProperty(DopBase):
|
|
63
64
|
)
|
64
65
|
unit_ref = OdxLinkRef.from_et(et_element.find("UNIT-REF"), doc_frags)
|
65
66
|
|
67
|
+
internal_constr = None
|
68
|
+
if (internal_constr_elem := et_element.find("INTERNAL-CONSTR")) is not None:
|
69
|
+
internal_constr = InternalConstr.from_et(
|
70
|
+
internal_constr_elem, internal_type=diag_coded_type.base_data_type)
|
71
|
+
|
72
|
+
physical_constr = None
|
73
|
+
if (physical_constr_elem := et_element.find("PHYS-CONSTR")) is not None:
|
74
|
+
physical_constr = InternalConstr.from_et(
|
75
|
+
physical_constr_elem, internal_type=diag_coded_type.base_data_type)
|
76
|
+
|
66
77
|
return DataObjectProperty(
|
67
78
|
diag_coded_type=diag_coded_type,
|
68
79
|
physical_type=physical_type,
|
69
80
|
compu_method=compu_method,
|
70
81
|
unit_ref=unit_ref,
|
82
|
+
internal_constr=internal_constr,
|
83
|
+
physical_constr=physical_constr,
|
71
84
|
**kwargs)
|
72
85
|
|
73
86
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
odxtools/diagcodedtype.py
CHANGED
@@ -3,7 +3,10 @@ import abc
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Union
|
5
5
|
|
6
|
-
|
6
|
+
try:
|
7
|
+
import bitstruct.c as bitstruct
|
8
|
+
except ImportError:
|
9
|
+
import bitstruct
|
7
10
|
|
8
11
|
from . import exceptions
|
9
12
|
from .decodestate import DecodeState
|
@@ -23,8 +26,8 @@ ODX_TYPE_TO_FORMAT_LETTER = {
|
|
23
26
|
DataType.A_FLOAT64: "f",
|
24
27
|
DataType.A_BYTEFIELD: "r",
|
25
28
|
DataType.A_UNICODE2STRING: "r", # UTF-16 strings must be converted explicitly
|
26
|
-
DataType.A_ASCIISTRING: "
|
27
|
-
DataType.A_UTF8STRING: "
|
29
|
+
DataType.A_ASCIISTRING: "r",
|
30
|
+
DataType.A_UTF8STRING: "r",
|
28
31
|
}
|
29
32
|
|
30
33
|
# Allowed diag-coded types
|
@@ -66,8 +69,8 @@ class DiagCodedType(abc.ABC):
|
|
66
69
|
def is_highlow_byte_order(self) -> bool:
|
67
70
|
return self.is_highlow_byte_order_raw in [None, True]
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
@staticmethod
|
73
|
+
def _extract_internal_value(
|
71
74
|
coded_message: bytes,
|
72
75
|
byte_position: int,
|
73
76
|
bit_position: int,
|
@@ -75,9 +78,11 @@ class DiagCodedType(abc.ABC):
|
|
75
78
|
base_data_type: DataType,
|
76
79
|
is_highlow_byte_order: bool,
|
77
80
|
) -> Tuple[AtomicOdxType, int]:
|
78
|
-
"""Extract
|
81
|
+
"""Extract an internal value from a blob of raw bytes.
|
79
82
|
|
80
|
-
|
83
|
+
:return: Tuple with the internal value of the object and the
|
84
|
+
byte position of the first undecoded byte after the
|
85
|
+
extracted object.
|
81
86
|
"""
|
82
87
|
# If the bit length is zero, return "empty" values of each type
|
83
88
|
if bit_length == 0:
|
@@ -89,46 +94,45 @@ class DiagCodedType(abc.ABC):
|
|
89
94
|
cursor_position = byte_position + byte_length
|
90
95
|
extracted_bytes = coded_message[byte_position:cursor_position]
|
91
96
|
|
92
|
-
# Apply byteorder
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
DataType.
|
97
|
+
# Apply byteorder for numerical objects. Note that doing this
|
98
|
+
# here might lead to garbage data being included in the result
|
99
|
+
# if the data to be extracted is not byte aligned and crosses
|
100
|
+
# byte boundaries, but it is what the specification says.
|
101
|
+
if not is_highlow_byte_order and base_data_type in [
|
102
|
+
DataType.A_INT32,
|
103
|
+
DataType.A_UINT32,
|
104
|
+
DataType.A_FLOAT32,
|
105
|
+
DataType.A_FLOAT64,
|
98
106
|
]:
|
99
107
|
extracted_bytes = extracted_bytes[::-1]
|
100
108
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
109
|
+
padding = (8 - (bit_length + bit_position) % 8) % 8
|
110
|
+
internal_value, = bitstruct.unpack_from(
|
111
|
+
f"{ODX_TYPE_TO_FORMAT_LETTER[base_data_type]}{bit_length}",
|
112
|
+
extracted_bytes,
|
113
|
+
offset=padding)
|
114
|
+
|
115
|
+
text_errors = 'strict' if exceptions.strict_mode else 'replace'
|
107
116
|
if base_data_type == DataType.A_ASCIISTRING:
|
108
|
-
# The spec says ASCII, meaning only byte values 0-127
|
117
|
+
# The spec says ASCII, meaning only byte values 0-127.
|
109
118
|
# But in practice, vendors use iso-8859-1, aka latin-1
|
110
|
-
# reason being iso-8859-1 never fails
|
111
|
-
#
|
119
|
+
# reason being iso-8859-1 never fails since it has a valid
|
120
|
+
# character mapping for every possible byte sequence.
|
112
121
|
text_encoding = 'iso-8859-1'
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
# Convert bytes to string with utf-16 decoding
|
123
|
-
if is_highlow_byte_order:
|
124
|
-
internal_value = internal_value.decode("utf-16-be")
|
125
|
-
else:
|
126
|
-
internal_value = internal_value.decode("utf-16-le")
|
122
|
+
internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
|
123
|
+
elif base_data_type == DataType.A_UTF8STRING:
|
124
|
+
text_encoding = "utf-8"
|
125
|
+
internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
|
126
|
+
elif base_data_type == DataType.A_UNICODE2STRING:
|
127
|
+
# For UTF-16, we need to manually decode the extracted
|
128
|
+
# bytes to a string
|
129
|
+
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
130
|
+
internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
|
127
131
|
|
128
132
|
return internal_value, cursor_position
|
129
133
|
|
130
|
-
|
131
|
-
|
134
|
+
@staticmethod
|
135
|
+
def _encode_internal_value(
|
132
136
|
internal_value: AtomicOdxType,
|
133
137
|
bit_position: int,
|
134
138
|
bit_length: int,
|
@@ -137,23 +141,46 @@ class DiagCodedType(abc.ABC):
|
|
137
141
|
) -> bytes:
|
138
142
|
"""Convert the internal_value to bytes."""
|
139
143
|
# Check that bytes and strings actually fit into the bit length
|
140
|
-
if base_data_type
|
144
|
+
if base_data_type == DataType.A_BYTEFIELD:
|
145
|
+
if isinstance(internal_value, bytearray):
|
146
|
+
internal_value = bytes(internal_value)
|
141
147
|
if not isinstance(internal_value, bytes):
|
142
148
|
odxraise()
|
143
149
|
if 8 * len(internal_value) > bit_length:
|
144
150
|
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
|
145
151
|
f"({len(internal_value)} bytes)."
|
146
152
|
f" The maximum length is {bit_length//8}.")
|
147
|
-
if base_data_type
|
153
|
+
if base_data_type == DataType.A_ASCIISTRING:
|
148
154
|
if not isinstance(internal_value, str):
|
149
155
|
odxraise()
|
156
|
+
|
157
|
+
# The spec says ASCII, meaning only byte values 0-127.
|
158
|
+
# But in practice, vendors use iso-8859-1, aka latin-1
|
159
|
+
# reason being iso-8859-1 never fails since it has a valid
|
160
|
+
# character mapping for every possible byte sequence.
|
161
|
+
internal_value = internal_value.encode("iso-8859-1")
|
162
|
+
|
150
163
|
if 8 * len(internal_value) > bit_length:
|
151
164
|
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
152
165
|
f" The maximum number of characters is {bit_length//8}.")
|
153
|
-
|
166
|
+
elif base_data_type == DataType.A_UTF8STRING:
|
154
167
|
if not isinstance(internal_value, str):
|
155
168
|
odxraise()
|
156
|
-
|
169
|
+
|
170
|
+
internal_value = internal_value.encode("utf-8")
|
171
|
+
|
172
|
+
if 8 * len(internal_value) > bit_length:
|
173
|
+
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
174
|
+
f" The maximum number of bytes is {bit_length//8}.")
|
175
|
+
|
176
|
+
elif base_data_type == DataType.A_UNICODE2STRING:
|
177
|
+
if not isinstance(internal_value, str):
|
178
|
+
odxraise()
|
179
|
+
|
180
|
+
text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
|
181
|
+
internal_value = internal_value.encode(text_encoding)
|
182
|
+
|
183
|
+
if 8 * len(internal_value) > bit_length:
|
157
184
|
raise EncodeError(f"The string {repr(internal_value)} is too large."
|
158
185
|
f" The maximum number of characters is {bit_length//16}.")
|
159
186
|
|
@@ -167,33 +194,24 @@ class DiagCodedType(abc.ABC):
|
|
167
194
|
return b''
|
168
195
|
|
169
196
|
char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
internal_value = internal_value.encode("utf-16-le")
|
185
|
-
|
186
|
-
code = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)
|
187
|
-
|
188
|
-
if not is_highlow_byte_order and base_data_type not in [
|
189
|
-
DataType.A_UNICODE2STRING,
|
190
|
-
DataType.A_BYTEFIELD,
|
191
|
-
DataType.A_ASCIISTRING,
|
192
|
-
DataType.A_UTF8STRING,
|
197
|
+
padding = (8 - ((bit_length + bit_position) % 8)) % 8
|
198
|
+
odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0),
|
199
|
+
f"Incorrect padding {padding}")
|
200
|
+
left_pad = f"p{padding}" if padding > 0 else ""
|
201
|
+
|
202
|
+
# actually encode the value
|
203
|
+
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)
|
204
|
+
|
205
|
+
# apply byte order for numeric objects
|
206
|
+
if not is_highlow_byte_order and base_data_type in [
|
207
|
+
DataType.A_INT32,
|
208
|
+
DataType.A_UINT32,
|
209
|
+
DataType.A_FLOAT32,
|
210
|
+
DataType.A_FLOAT64,
|
193
211
|
]:
|
194
|
-
|
212
|
+
coded = coded[::-1]
|
195
213
|
|
196
|
-
return
|
214
|
+
return coded
|
197
215
|
|
198
216
|
def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
|
199
217
|
"""Helper method to get the minimal byte length.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import List, Optional
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .compumethods.limit import Limit
|
7
|
+
from .odxtypes import DataType
|
8
|
+
from .scaleconstr import ScaleConstr
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class InternalConstr:
|
13
|
+
"""This class represents INTERNAL-CONSTR objects.
|
14
|
+
"""
|
15
|
+
|
16
|
+
# TODO: Enforce the internal and physical constraints.
|
17
|
+
|
18
|
+
lower_limit: Optional[Limit]
|
19
|
+
upper_limit: Optional[Limit]
|
20
|
+
scale_constrs: List[ScaleConstr]
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def from_et(et_element: ElementTree.Element, internal_type: DataType) -> "InternalConstr":
|
24
|
+
|
25
|
+
lower_limit = Limit.from_et(et_element.find("LOWER-LIMIT"), internal_type=internal_type)
|
26
|
+
upper_limit = Limit.from_et(et_element.find("UPPER-LIMIT"), internal_type=internal_type)
|
27
|
+
|
28
|
+
scale_constrs = [
|
29
|
+
ScaleConstr.from_et(sc_el, internal_type)
|
30
|
+
for sc_el in et_element.iterfind("SCALE-CONSTRS/SCALE-CONSTR")
|
31
|
+
]
|
32
|
+
|
33
|
+
return InternalConstr(
|
34
|
+
lower_limit=lower_limit, upper_limit=upper_limit, scale_constrs=scale_constrs)
|
@@ -38,7 +38,7 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
38
38
|
|
39
39
|
byte_length = self._minimal_byte_length_of(internal_value)
|
40
40
|
|
41
|
-
|
41
|
+
length_bytes = self._encode_internal_value(
|
42
42
|
byte_length,
|
43
43
|
bit_position=bit_position,
|
44
44
|
bit_length=self.bit_length,
|
@@ -46,7 +46,7 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
46
46
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
47
47
|
)
|
48
48
|
|
49
|
-
|
49
|
+
value_bytes = self._encode_internal_value(
|
50
50
|
internal_value,
|
51
51
|
bit_position=0,
|
52
52
|
bit_length=8 * byte_length,
|
@@ -54,7 +54,7 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
54
54
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
55
55
|
)
|
56
56
|
|
57
|
-
return
|
57
|
+
return length_bytes + value_bytes
|
58
58
|
|
59
59
|
def convert_bytes_to_internal(self,
|
60
60
|
decode_state: DecodeState,
|
@@ -62,7 +62,7 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
62
62
|
coded_message = decode_state.coded_message
|
63
63
|
|
64
64
|
# Extract length of the parameter value
|
65
|
-
byte_length, byte_position = self.
|
65
|
+
byte_length, byte_position = self._extract_internal_value(
|
66
66
|
coded_message=coded_message,
|
67
67
|
byte_position=decode_state.cursor_position,
|
68
68
|
bit_position=bit_position,
|
@@ -77,7 +77,7 @@ class LeadingLengthInfoType(DiagCodedType):
|
|
77
77
|
# Extract actual value
|
78
78
|
# TODO: The returned value is None if the byte_length is 0. Maybe change it
|
79
79
|
# to some default value like an empty bytearray() or 0?
|
80
|
-
value, cursor_position = self.
|
80
|
+
value, cursor_position = self._extract_internal_value(
|
81
81
|
coded_message=coded_message,
|
82
82
|
byte_position=byte_position,
|
83
83
|
bit_position=0,
|
odxtools/minmaxlengthtype.py
CHANGED
@@ -63,7 +63,7 @@ class MinMaxLengthType(DiagCodedType):
|
|
63
63
|
data_length = len(internal_value)
|
64
64
|
|
65
65
|
value_bytes = bytearray(
|
66
|
-
self.
|
66
|
+
self._encode_internal_value(
|
67
67
|
internal_value,
|
68
68
|
bit_position=0,
|
69
69
|
bit_length=8 * data_length,
|
@@ -145,7 +145,7 @@ class MinMaxLengthType(DiagCodedType):
|
|
145
145
|
terminator_pos += 1
|
146
146
|
|
147
147
|
# Extract the value
|
148
|
-
value, byte_pos = self.
|
148
|
+
value, byte_pos = self._extract_internal_value(
|
149
149
|
decode_state.coded_message,
|
150
150
|
byte_position=cursor_pos,
|
151
151
|
bit_position=bit_position,
|
@@ -164,7 +164,7 @@ class MinMaxLengthType(DiagCodedType):
|
|
164
164
|
# or at the end of the PDU.
|
165
165
|
byte_length = max_terminator_pos - cursor_pos
|
166
166
|
|
167
|
-
value, byte_pos = self.
|
167
|
+
value, byte_pos = self._extract_internal_value(
|
168
168
|
decode_state.coded_message,
|
169
169
|
byte_position=cursor_pos,
|
170
170
|
bit_position=bit_position,
|
odxtools/paramlengthinfotype.py
CHANGED
@@ -66,7 +66,7 @@ class ParamLengthInfoType(DiagCodedType):
|
|
66
66
|
if bit_length is None:
|
67
67
|
odxraise()
|
68
68
|
|
69
|
-
return self.
|
69
|
+
return self._encode_internal_value(
|
70
70
|
internal_value,
|
71
71
|
bit_position=bit_position,
|
72
72
|
bit_length=bit_length,
|
@@ -91,7 +91,7 @@ class ParamLengthInfoType(DiagCodedType):
|
|
91
91
|
odxraise(f"Did not find any length key with short name {self.length_key.short_name}")
|
92
92
|
|
93
93
|
# Extract the internal value and return.
|
94
|
-
return self.
|
94
|
+
return self._extract_internal_value(
|
95
95
|
decode_state.coded_message,
|
96
96
|
decode_state.cursor_position,
|
97
97
|
bit_position,
|