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/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 into their named parameters.",
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) -> None:
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 & print services by hex-data, or name. Can also decode the request.",
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
- odxdb: Database,
19
- print_global_negative_responses: bool = False,
20
- print_services: bool = False,
21
- print_dops: bool = False,
22
- print_params: bool = False,
23
- print_comparams: bool = False,
24
- print_pre_condition_states: bool = False,
25
- print_state_transitions: bool = False,
26
- print_audiences: bool = False,
27
- allow_unknown_bit_lengths: bool = False,
28
- variants: Optional[str] = None,
29
- service_filter: Callable[[DiagComm], bool] = lambda x: True,
30
- ) -> None:
31
-
32
- diag_layer_names = variants if variants else [dl.short_name for dl in odxdb.diag_layers]
33
-
34
- for dl_sn in diag_layer_names:
35
- dl = odxdb.diag_layers[dl_sn]
36
- if not dl:
37
- print(f"The variant '{dl_sn}' could not be found!")
38
- continue
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(f" CAN receive ID for protocol '{proto.short_name}': 0x{can_rx_id:x}")
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(f" CAN send ID for protocol '{proto.short_name}': 0x{can_tx_id:x}")
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(f"The global negative responses of '{dl.short_name}' are: ")
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
- print(f" {gnr}")
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(f"The services of '{dl.short_name}' are: ")
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(f"The DOPs of the {dl.variant_type} '{dl.short_name}' are: ")
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
- print(" " + str(dop).replace("\n", "\n "))
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
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
  from typing import List, Optional
5
5
  from xml.etree import ElementTree
6
6
  from zipfile import ZipFile
7
+
7
8
  from packaging.version import Version
8
9
 
9
10
  from .comparamspec import ComparamSpec
@@ -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
- # TODO: physical_const: Optional[InternalConstr]
45
- # TODO: internal_const: Optional[InternalConstr]
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
- import bitstruct
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: "t",
27
- DataType.A_UTF8STRING: "t",
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
- def _extract_internal(
70
- self,
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 the internal value.
81
+ """Extract an internal value from a blob of raw bytes.
79
82
 
80
- Helper method for `DiagCodedType.convert_bytes_to_internal`.
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
- if not is_highlow_byte_order and base_data_type not in [
94
- DataType.A_UNICODE2STRING,
95
- DataType.A_BYTEFIELD,
96
- DataType.A_ASCIISTRING,
97
- DataType.A_UTF8STRING,
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
- format_letter = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
102
- padding = 8 * byte_length - (bit_length + bit_position)
103
- text_encoding = 'utf-8'
104
- text_errors = 'strict'
105
- if not exceptions.strict_mode:
106
- text_errors = 'replace'
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
- # since it has a valid character mapping for every possible byte sequence.
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
- internal_value = bitstruct.unpack_from(
114
- f"{format_letter}{bit_length}",
115
- extracted_bytes,
116
- offset=padding,
117
- text_encoding=text_encoding,
118
- text_errors=text_errors,
119
- )[0]
120
-
121
- if base_data_type == DataType.A_UNICODE2STRING:
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
- def _to_bytes(
131
- self,
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 in [DataType.A_BYTEFIELD]:
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 in [DataType.A_ASCIISTRING, DataType.A_UTF8STRING]:
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
- if base_data_type in [DataType.A_UNICODE2STRING]:
166
+ elif base_data_type == DataType.A_UTF8STRING:
154
167
  if not isinstance(internal_value, str):
155
168
  odxraise()
156
- if 16 * len(internal_value) > bit_length:
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
- # The coded byte is divided into (0..0)(value)(0..0) with bit lengths (left_pad)(bit_length)(bit_position)
172
- offset = (8 - ((bit_length + bit_position) % 8)) % 8
173
- odxassert((0 <= offset and offset < 8 and (offset + bit_length + bit_position) % 8 == 0),
174
- f"Computational mistake, offset={offset}")
175
- left_pad = f"p{offset}" if offset > 0 else ""
176
-
177
- # Convert string to bytes with utf-16 encoding
178
- if base_data_type == DataType.A_UNICODE2STRING:
179
- if not isinstance(internal_value, str):
180
- odxraise()
181
- if is_highlow_byte_order:
182
- internal_value = internal_value.encode("utf-16-be")
183
- else:
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
- code = code[::-1]
212
+ coded = coded[::-1]
195
213
 
196
- return code
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
- length_byte = self._to_bytes(
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
- value_byte = self._to_bytes(
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 length_byte + value_byte
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._extract_internal(
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._extract_internal(
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,
@@ -63,7 +63,7 @@ class MinMaxLengthType(DiagCodedType):
63
63
  data_length = len(internal_value)
64
64
 
65
65
  value_bytes = bytearray(
66
- self._to_bytes(
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._extract_internal(
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._extract_internal(
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,
@@ -66,7 +66,7 @@ class ParamLengthInfoType(DiagCodedType):
66
66
  if bit_length is None:
67
67
  odxraise()
68
68
 
69
- return self._to_bytes(
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._extract_internal(
94
+ return self._extract_internal_value(
95
95
  decode_state.coded_message,
96
96
  decode_state.cursor_position,
97
97
  bit_position,