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/__init__.py
CHANGED
@@ -62,11 +62,11 @@ References
|
|
62
62
|
- _`[ISO22901]` The ISO 22901 Standard: https://www.iso.org/standard/41207.html
|
63
63
|
|
64
64
|
"""
|
65
|
-
from .load_file import load_file
|
66
|
-
from .load_odx_d_file import load_odx_d_file
|
67
|
-
from .load_pdx_file import load_pdx_file
|
68
|
-
from .version import __version__
|
69
|
-
from .write_pdx_file import write_pdx_file
|
65
|
+
from .load_file import load_file as load_file # noqa: F401
|
66
|
+
from .load_odx_d_file import load_odx_d_file as load_odx_d_file # noqa: F401
|
67
|
+
from .load_pdx_file import load_pdx_file as load_pdx_file # noqa: F401
|
68
|
+
from .version import __version__ as __version__ # noqa: F401
|
69
|
+
from .write_pdx_file import write_pdx_file as write_pdx_file # noqa: F401
|
70
70
|
|
71
71
|
__author__ = "Katrin Bauer"
|
72
72
|
|
odxtools/basicstructure.py
CHANGED
@@ -68,7 +68,8 @@ class BasicStructure(ComplexDop):
|
|
68
68
|
cursor += param_bit_length
|
69
69
|
length = max(length, cursor)
|
70
70
|
|
71
|
-
# Round up to account for padding bits
|
71
|
+
# Round up to account for padding bits (all structures are
|
72
|
+
# byte aligned)
|
72
73
|
return ((length + 7) // 8) * 8
|
73
74
|
|
74
75
|
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
|
@@ -210,18 +211,18 @@ class BasicStructure(ComplexDop):
|
|
210
211
|
stacklevel=1)
|
211
212
|
|
212
213
|
def convert_physical_to_bytes(self,
|
213
|
-
|
214
|
+
physical_value: ParameterValue,
|
214
215
|
encode_state: EncodeState,
|
215
216
|
bit_position: int = 0) -> bytes:
|
216
|
-
if not isinstance(
|
217
|
+
if not isinstance(physical_value, dict):
|
217
218
|
raise EncodeError(
|
218
219
|
f"Expected a dictionary for the values of structure {self.short_name}, "
|
219
|
-
f"got {type(
|
220
|
+
f"got {type(physical_value)}")
|
220
221
|
if bit_position != 0:
|
221
222
|
raise EncodeError("Structures must be aligned, i.e. bit_position=0, but "
|
222
223
|
f"{self.short_name} was passed the bit position {bit_position}")
|
223
224
|
return self.convert_physical_to_internal(
|
224
|
-
|
225
|
+
physical_value,
|
225
226
|
triggering_coded_request=encode_state.triggering_request,
|
226
227
|
is_end_of_pdu=encode_state.is_end_of_pdu,
|
227
228
|
)
|
@@ -254,9 +255,7 @@ class BasicStructure(ComplexDop):
|
|
254
255
|
Parameters of the RPC as mapping from SHORT-NAME of the parameter to the value
|
255
256
|
"""
|
256
257
|
return self.convert_physical_to_internal(
|
257
|
-
params,
|
258
|
-
triggering_coded_request=coded_request,
|
259
|
-
is_end_of_pdu=True)
|
258
|
+
params, triggering_coded_request=coded_request, is_end_of_pdu=True)
|
260
259
|
|
261
260
|
def decode(self, message: bytes) -> ParameterValueDict:
|
262
261
|
decode_state = DecodeState(coded_message=message)
|
odxtools/cli/_parser_utils.py
CHANGED
@@ -2,11 +2,26 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: MIT
|
4
4
|
import argparse
|
5
|
+
from typing import Any, Protocol, runtime_checkable
|
5
6
|
|
6
7
|
from ..database import Database
|
7
8
|
from ..load_file import load_file as _load_file
|
8
9
|
|
9
10
|
|
11
|
+
@runtime_checkable
|
12
|
+
class SubparsersList(Protocol):
|
13
|
+
"""This protocol reproduces the parts of `argparse._SubparsersAction` which are needed by odxtools
|
14
|
+
|
15
|
+
Unfortunately this is necessary because
|
16
|
+
`argparse._SubparsersAction` is a private class of the `argparse`
|
17
|
+
module that is used by some of its public API and thus cannot be
|
18
|
+
used outside of the `argparse` module.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def add_parser(self, name: str, **kwargs: Any) -> "argparse.ArgumentParser":
|
22
|
+
...
|
23
|
+
|
24
|
+
|
10
25
|
def add_pdx_argument(parser: argparse.ArgumentParser, is_optional: bool = False) -> None:
|
11
26
|
parser.add_argument(
|
12
27
|
"pdx_file",
|
odxtools/cli/_print_utils.py
CHANGED
@@ -36,7 +36,7 @@ def print_diagnostic_service(service: DiagService,
|
|
36
36
|
print_state_transitions: bool = False,
|
37
37
|
print_audiences: bool = False,
|
38
38
|
allow_unknown_bit_lengths: bool = False,
|
39
|
-
print_fn: Callable = print) -> None:
|
39
|
+
print_fn: Callable[..., Any] = print) -> None:
|
40
40
|
|
41
41
|
print_fn(f" Service '{service.short_name}':")
|
42
42
|
|
@@ -70,7 +70,7 @@ def print_diagnostic_service(service: DiagService,
|
|
70
70
|
|
71
71
|
def print_service_parameters(service: DiagService,
|
72
72
|
allow_unknown_bit_lengths: bool = False,
|
73
|
-
print_fn: Callable = print) -> None:
|
73
|
+
print_fn: Callable[..., Any] = print) -> None:
|
74
74
|
# prints parameter details of request, positive response and negative response of diagnostic service
|
75
75
|
|
76
76
|
# Request
|
@@ -156,6 +156,7 @@ def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Dict[str,
|
|
156
156
|
byte.append(param.byte_position)
|
157
157
|
semantic.append(param.semantic)
|
158
158
|
param_type.append(param.parameter_type)
|
159
|
+
length = 0
|
159
160
|
if param.get_static_bit_length() is not None:
|
160
161
|
bit_length.append(param.get_static_bit_length())
|
161
162
|
length = (param.get_static_bit_length() or 0) // 4
|
@@ -227,7 +228,7 @@ def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Dict[str,
|
|
227
228
|
}
|
228
229
|
|
229
230
|
|
230
|
-
def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable = print) -> None:
|
231
|
+
def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = print) -> None:
|
231
232
|
|
232
233
|
name = []
|
233
234
|
type = []
|
odxtools/cli/browse.py
CHANGED
@@ -4,7 +4,7 @@ import logging
|
|
4
4
|
import sys
|
5
5
|
from typing import List, Optional, Union, cast
|
6
6
|
|
7
|
-
|
7
|
+
import InquirerPy.prompt as IP_prompt
|
8
8
|
from tabulate import tabulate # TODO: switch to rich tables
|
9
9
|
|
10
10
|
from ..database import Database
|
@@ -20,6 +20,7 @@ from ..parameters.valueparameter import ValueParameter
|
|
20
20
|
from ..request import Request
|
21
21
|
from ..response import Response
|
22
22
|
from . import _parser_utils
|
23
|
+
from ._parser_utils import SubparsersList
|
23
24
|
from ._print_utils import extract_parameter_tabulation_data
|
24
25
|
|
25
26
|
# name of the tool
|
@@ -88,13 +89,15 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
|
|
88
89
|
# else _convert_string_to_odx_type(x, p.physical_type.base_data_type, param=p) # This does not work because the next parameter to be promted is used (for some reason?)
|
89
90
|
}]
|
90
91
|
|
91
|
-
if
|
92
|
-
|
93
|
-
scales =
|
92
|
+
if (dop := getattr(parameter, "dop", None)) and \
|
93
|
+
(compu_method := getattr(dop, "compu_method", None)):
|
94
|
+
scales = compu_method.internal_to_phys
|
94
95
|
choices = [scale.compu_const for scale in scales if scale is not None]
|
96
|
+
if (cdv := compu_method.compu_default_value) is not None:
|
97
|
+
choices.append(cdv.compu_const)
|
95
98
|
param_prompt[0]["choices"] = choices
|
96
99
|
|
97
|
-
answer =
|
100
|
+
answer = IP_prompt(param_prompt)
|
98
101
|
if answer.get(parameter.short_name) == "" and not parameter.is_required:
|
99
102
|
return None
|
100
103
|
elif parameter.physical_type.base_data_type is not None:
|
@@ -104,7 +107,7 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
|
|
104
107
|
logging.warning(
|
105
108
|
f"Parameter {parameter.short_name} does not have a physical data type. Param details: {parameter}"
|
106
109
|
)
|
107
|
-
return answer.get(parameter.short_name)
|
110
|
+
return cast(str, answer.get(parameter.short_name))
|
108
111
|
|
109
112
|
|
110
113
|
def encode_message_interactively(sub_service: Union[Request, Response],
|
@@ -117,7 +120,7 @@ def encode_message_interactively(sub_service: Union[Request, Response],
|
|
117
120
|
for param_or_dict in param_dict.values():
|
118
121
|
if isinstance(param_or_dict, dict):
|
119
122
|
for param in param_or_dict.values():
|
120
|
-
if
|
123
|
+
if isinstance(param, Parameter) and param.is_settable:
|
121
124
|
exists_definable_param = True
|
122
125
|
elif param_or_dict.is_settable:
|
123
126
|
exists_definable_param = True
|
@@ -132,10 +135,11 @@ def encode_message_interactively(sub_service: Union[Request, Response],
|
|
132
135
|
"message": f"Do you want to encode a message? [y/n]",
|
133
136
|
"choices": ["yes", "no"],
|
134
137
|
}]
|
135
|
-
answer =
|
138
|
+
answer = IP_prompt(encode_message_prompt)
|
136
139
|
if answer.get("yes_no_prompt") == "no":
|
137
140
|
return
|
138
141
|
|
142
|
+
answered_request = b''
|
139
143
|
if isinstance(sub_service, Response):
|
140
144
|
answered_request_prompt = [{
|
141
145
|
"type":
|
@@ -147,7 +151,7 @@ def encode_message_interactively(sub_service: Union[Request, Response],
|
|
147
151
|
"filter":
|
148
152
|
lambda input: _convert_string_to_bytes(input),
|
149
153
|
}]
|
150
|
-
answer =
|
154
|
+
answer = IP_prompt(answered_request_prompt)
|
151
155
|
answered_request = cast(bytes, answer.get("request"))
|
152
156
|
print(f"Input interpretation as list: {list(answered_request)}")
|
153
157
|
|
@@ -250,7 +254,8 @@ def encode_message_from_string_values(
|
|
250
254
|
parameter_values[parameter_sn] = _convert_string_to_odx_type(
|
251
255
|
parameter_value, DataType.A_BYTEFIELD)
|
252
256
|
|
253
|
-
payload = sub_service.encode(**parameter_values)
|
257
|
+
payload = sub_service.encode(coded_request=b'\xff' * 100, **parameter_values)
|
258
|
+
|
254
259
|
print(f"Message payload: 0x{bytes(payload).hex()}")
|
255
260
|
|
256
261
|
|
@@ -266,7 +271,7 @@ def browse(odxdb: Database) -> None:
|
|
266
271
|
"message": "Select a Variant.",
|
267
272
|
"choices": list(dl_names) + ["[exit]"],
|
268
273
|
}]
|
269
|
-
answer =
|
274
|
+
answer = IP_prompt(selection)
|
270
275
|
if answer.get("variant") == "[exit]":
|
271
276
|
return
|
272
277
|
|
@@ -304,7 +309,7 @@ def browse(odxdb: Database) -> None:
|
|
304
309
|
f"The variant {variant.short_name} offers the following services. Select one!",
|
305
310
|
"choices": [s.short_name for s in services] + ["[back]"],
|
306
311
|
}]
|
307
|
-
answer =
|
312
|
+
answer = IP_prompt(selection)
|
308
313
|
if answer.get("service") == "[back]":
|
309
314
|
break
|
310
315
|
|
@@ -339,7 +344,7 @@ def browse(odxdb: Database) -> None:
|
|
339
344
|
"short": f"Negative response: {nr.short_name}",
|
340
345
|
} for nr in service.negative_responses] + ["[back]"], # type: ignore
|
341
346
|
}]
|
342
|
-
answer =
|
347
|
+
answer = IP_prompt(selection)
|
343
348
|
if answer.get("message_type") == "[back]":
|
344
349
|
continue
|
345
350
|
|
@@ -353,7 +358,7 @@ def browse(odxdb: Database) -> None:
|
|
353
358
|
encode_message_interactively(codec, ask_user_confirmation=True)
|
354
359
|
|
355
360
|
|
356
|
-
def add_subparser(subparsers:
|
361
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
357
362
|
# Browse interactively to avoid spamming the console.
|
358
363
|
parser = subparsers.add_parser(
|
359
364
|
"browse",
|
odxtools/cli/compare.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
import argparse
|
5
5
|
import os
|
6
|
-
from typing import Any, Dict, List, Optional, Set, Union
|
6
|
+
from typing import Any, Dict, List, Optional, Set, Union, cast
|
7
7
|
|
8
8
|
from rich import print
|
9
9
|
from tabulate import tabulate # TODO: switch to rich tables
|
@@ -19,6 +19,7 @@ from ..parameters.parameter import Parameter
|
|
19
19
|
from ..parameters.physicalconstantparameter import PhysicalConstantParameter
|
20
20
|
from ..parameters.valueparameter import ValueParameter
|
21
21
|
from . import _parser_utils
|
22
|
+
from ._parser_utils import SubparsersList
|
22
23
|
from ._print_utils import (extract_service_tabulation_data, print_dl_metrics,
|
23
24
|
print_service_parameters)
|
24
25
|
|
@@ -104,8 +105,10 @@ class Display:
|
|
104
105
|
f" Detailed changes of diagnostic service [u cyan]{service.short_name}[/u cyan]"
|
105
106
|
)
|
106
107
|
# detailed_info in [infotext1, dict1, infotext2, dict2, ...]
|
107
|
-
|
108
|
-
|
108
|
+
info_list = cast(
|
109
|
+
list, # type: ignore[type-arg]
|
110
|
+
service_dict["changed_parameters_of_service"])[2][service_idx]
|
111
|
+
for detailed_info in info_list:
|
109
112
|
if isinstance(detailed_info, str):
|
110
113
|
print()
|
111
114
|
print(detailed_info)
|
@@ -256,7 +259,8 @@ class Comparison(Display):
|
|
256
259
|
|
257
260
|
return {"Property": property, "Old Value": old, "New Value": new}
|
258
261
|
|
259
|
-
def compare_services(self, service1: DiagService,
|
262
|
+
def compare_services(self, service1: DiagService,
|
263
|
+
service2: DiagService) -> List[SpecsServiceDict]:
|
260
264
|
# compares request, positive response and negative response parameters of two diagnostic services
|
261
265
|
|
262
266
|
information: List[Union[str, Dict[str, Any]]] = [
|
@@ -382,9 +386,10 @@ class Comparison(Display):
|
|
382
386
|
str(service2.negative_responses)]
|
383
387
|
})
|
384
388
|
|
385
|
-
return [information, changed_params]
|
389
|
+
return [information, changed_params] # type: ignore[list-item]
|
386
390
|
|
387
|
-
def compare_diagnostic_layers(self, dl1: DiagLayer,
|
391
|
+
def compare_diagnostic_layers(self, dl1: DiagLayer,
|
392
|
+
dl2: DiagLayer) -> dict: # type: ignore[type-arg]
|
388
393
|
# compares diagnostic services of two diagnostic layers with each other
|
389
394
|
# save changes in dictionary (service_dict)
|
390
395
|
# TODO: add comparison of SingleECUJobs
|
@@ -467,21 +472,23 @@ class Comparison(Display):
|
|
467
472
|
# add parameters which have been changed (type: String)
|
468
473
|
service_dict["changed_parameters_of_service"][
|
469
474
|
1].append( # type: ignore[union-attr]
|
470
|
-
detailed_information[1])
|
475
|
+
detailed_information[1]) # type: ignore[arg-type]
|
471
476
|
# add detailed information about changed service parameters (type: list) [infotext1, table1, infotext2, table2, ...]
|
472
477
|
service_dict["changed_parameters_of_service"][
|
473
478
|
2].append( # type: ignore[union-attr]
|
474
|
-
detailed_information[0])
|
479
|
+
detailed_information[0]) # type: ignore[arg-type]
|
475
480
|
|
476
481
|
for service2_idx, service2 in enumerate(dl2.services):
|
477
482
|
|
478
483
|
# check for deleted diagnostic services
|
479
484
|
if service2.short_name not in dl1_service_names and dl2_request_prefixes[
|
480
|
-
service2_idx] not in dl1_request_prefixes
|
481
|
-
"deleted_services"]:
|
485
|
+
service2_idx] not in dl1_request_prefixes:
|
482
486
|
|
483
|
-
service_dict["deleted_services"]
|
484
|
-
|
487
|
+
deleted_list = service_dict["deleted_services"]
|
488
|
+
assert isinstance(deleted_list, list)
|
489
|
+
if service2 not in deleted_list:
|
490
|
+
service_dict["deleted_services"].append( # type: ignore[union-attr]
|
491
|
+
service2) # type: ignore[arg-type]
|
485
492
|
|
486
493
|
if service1.short_name == service2.short_name:
|
487
494
|
# compare request, pos. response and neg. response parameters of both diagnostic services
|
@@ -496,13 +503,14 @@ class Comparison(Display):
|
|
496
503
|
service1)
|
497
504
|
# add parameters which have been changed (type: String)
|
498
505
|
service_dict["changed_parameters_of_service"][ # type: ignore[union-attr]
|
499
|
-
1].append(detailed_information[1])
|
506
|
+
1].append(detailed_information[1]) # type: ignore[arg-type]
|
500
507
|
# add detailed information about changed service parameters (type: list) [infotext1, table1, infotext2, table2, ...]
|
501
508
|
service_dict["changed_parameters_of_service"][ # type: ignore[union-attr]
|
502
|
-
2].append(detailed_information[0])
|
509
|
+
2].append(detailed_information[0]) # type: ignore[arg-type]
|
503
510
|
return service_dict
|
504
511
|
|
505
|
-
def compare_databases(self, database_new: Database,
|
512
|
+
def compare_databases(self, database_new: Database,
|
513
|
+
database_old: Database) -> dict: # type: ignore[type-arg]
|
506
514
|
# compares two PDX-files with each other
|
507
515
|
|
508
516
|
new_variants: NewVariants = []
|
@@ -539,7 +547,7 @@ class Comparison(Display):
|
|
539
547
|
return changes_variants
|
540
548
|
|
541
549
|
|
542
|
-
def add_subparser(subparsers:
|
550
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
543
551
|
parser = subparsers.add_parser(
|
544
552
|
"compare",
|
545
553
|
description="\n".join([
|
odxtools/cli/decode.py
CHANGED
@@ -9,6 +9,7 @@ from ..exceptions import odxraise
|
|
9
9
|
from ..odxtypes import ParameterValue
|
10
10
|
from ..singleecujob import SingleEcuJob
|
11
11
|
from . import _parser_utils
|
12
|
+
from ._parser_utils import SubparsersList
|
12
13
|
|
13
14
|
# name of the tool
|
14
15
|
_odxtools_tool_name_ = "decode"
|
@@ -70,7 +71,7 @@ def print_summary(
|
|
70
71
|
print(f" {param_name}={get_display_value(param_value)}")
|
71
72
|
|
72
73
|
|
73
|
-
def add_subparser(subparsers:
|
74
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
74
75
|
parser = subparsers.add_parser(
|
75
76
|
"decode",
|
76
77
|
description="\n".join([
|
odxtools/cli/dummy_sub_parser.py
CHANGED
@@ -4,6 +4,8 @@ import sys
|
|
4
4
|
import traceback
|
5
5
|
from io import StringIO
|
6
6
|
|
7
|
+
from ._parser_utils import SubparsersList
|
8
|
+
|
7
9
|
|
8
10
|
class DummyTool:
|
9
11
|
"""A tool which acts as a placeholder for a "real" tool that
|
@@ -20,7 +22,7 @@ class DummyTool:
|
|
20
22
|
self._odxtools_tool_name_ = tool_name
|
21
23
|
self._error = error
|
22
24
|
|
23
|
-
def add_subparser(self, subparser_list:
|
25
|
+
def add_subparser(self, subparser_list: SubparsersList) -> None:
|
24
26
|
desc = StringIO()
|
25
27
|
|
26
28
|
print(f"Tool '{self._odxtools_tool_name_}' is unavailable: {self._error}", file=desc)
|
odxtools/cli/find.py
CHANGED
@@ -7,6 +7,7 @@ from ..diagservice import DiagService
|
|
7
7
|
from ..odxtypes import ParameterValue
|
8
8
|
from ..singleecujob import SingleEcuJob
|
9
9
|
from . import _parser_utils
|
10
|
+
from ._parser_utils import SubparsersList
|
10
11
|
from ._print_utils import print_diagnostic_service
|
11
12
|
|
12
13
|
# name of the tool
|
@@ -66,7 +67,7 @@ def print_summary(odxdb: Database,
|
|
66
67
|
print(f"Unknown service: {service}")
|
67
68
|
|
68
69
|
|
69
|
-
def add_subparser(subparsers:
|
70
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
70
71
|
parser = subparsers.add_parser(
|
71
72
|
"find",
|
72
73
|
description="\n".join([
|
odxtools/cli/list.py
CHANGED
@@ -10,6 +10,7 @@ from ..diaglayer import DiagLayer
|
|
10
10
|
from ..diagservice import DiagService
|
11
11
|
from ..singleecujob import SingleEcuJob
|
12
12
|
from . import _parser_utils
|
13
|
+
from ._parser_utils import SubparsersList
|
13
14
|
from ._print_utils import format_desc, print_diagnostic_service, print_dl_metrics
|
14
15
|
|
15
16
|
# name of the tool
|
@@ -111,7 +112,7 @@ def print_summary(odxdb: Database,
|
|
111
112
|
rich.print(f" {com_param.short_name}: {com_param.value}")
|
112
113
|
|
113
114
|
|
114
|
-
def add_subparser(subparsers:
|
115
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
115
116
|
parser = subparsers.add_parser(
|
116
117
|
"list",
|
117
118
|
description="\n".join([
|
odxtools/cli/main.py
CHANGED
odxtools/cli/snoop.py
CHANGED
@@ -15,6 +15,7 @@ from odxtools.isotp_state_machine import IsoTpStateMachine
|
|
15
15
|
from odxtools.response import Response, ResponseType
|
16
16
|
|
17
17
|
from . import _parser_utils
|
18
|
+
from ._parser_utils import SubparsersList
|
18
19
|
|
19
20
|
# name of the tool
|
20
21
|
_odxtools_tool_name_ = "snoop"
|
@@ -30,6 +31,8 @@ def handle_telegram(telegram_id: int, payload: bytes) -> None:
|
|
30
31
|
global odx_diag_layer
|
31
32
|
global last_request
|
32
33
|
|
34
|
+
assert odx_diag_layer is not None
|
35
|
+
|
33
36
|
if telegram_id == ecu_tx_id:
|
34
37
|
if uds.is_response_pending(payload):
|
35
38
|
print(f" ... (response pending)")
|
@@ -209,7 +212,7 @@ def add_cli_arguments(parser: argparse.ArgumentParser) -> None:
|
|
209
212
|
_parser_utils.add_pdx_argument(parser)
|
210
213
|
|
211
214
|
|
212
|
-
def add_subparser(subparsers:
|
215
|
+
def add_subparser(subparsers: SubparsersList) -> None:
|
213
216
|
parser = subparsers.add_parser(
|
214
217
|
"snoop",
|
215
218
|
description="Live decoding of a diagnostic session.",
|
odxtools/comparaminstance.py
CHANGED
@@ -7,7 +7,7 @@ from xml.etree import ElementTree
|
|
7
7
|
from .basecomparam import BaseComparam
|
8
8
|
from .comparam import Comparam
|
9
9
|
from .complexcomparam import ComplexComparam, ComplexValue, create_complex_value_from_et
|
10
|
-
from .exceptions import OdxWarning,
|
10
|
+
from .exceptions import OdxWarning, odxraise, odxrequire
|
11
11
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
12
12
|
from .utils import create_description_from_et
|
13
13
|
|
@@ -75,7 +75,7 @@ class ComparamInstance:
|
|
75
75
|
def spec(self) -> BaseComparam:
|
76
76
|
return self._spec
|
77
77
|
|
78
|
-
def get_value(self) ->
|
78
|
+
def get_value(self) -> str:
|
79
79
|
"""Retrieve the value of a simple communication parameter
|
80
80
|
|
81
81
|
This takes the default value of the comparam (if any) into
|
@@ -91,7 +91,8 @@ class ComparamInstance:
|
|
91
91
|
else:
|
92
92
|
result = self.spec.physical_default_value
|
93
93
|
|
94
|
-
|
94
|
+
if not isinstance(result, str):
|
95
|
+
odxraise()
|
95
96
|
|
96
97
|
return result
|
97
98
|
|
@@ -119,6 +120,7 @@ class ComparamInstance:
|
|
119
120
|
name_list = [cp.short_name for cp in comparam_spec.subparams]
|
120
121
|
try:
|
121
122
|
idx = name_list.index(subparam_name)
|
123
|
+
subparam = comparam_spec.subparams[idx]
|
122
124
|
except ValueError:
|
123
125
|
warnings.warn(
|
124
126
|
f"Communication parameter '{self.short_name}' "
|
@@ -129,8 +131,8 @@ class ComparamInstance:
|
|
129
131
|
return None
|
130
132
|
|
131
133
|
result = value_list[idx]
|
132
|
-
if result is None:
|
133
|
-
result =
|
134
|
+
if result is None and isinstance(subparam, (Comparam, ComplexComparam)):
|
135
|
+
result = subparam.physical_default_value
|
134
136
|
if not isinstance(result, str):
|
135
137
|
odxraise()
|
136
138
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
|
-
import abc
|
3
2
|
from dataclasses import dataclass
|
4
3
|
from typing import Literal
|
5
4
|
|
@@ -15,14 +14,13 @@ CompuMethodCategory = Literal[
|
|
15
14
|
|
16
15
|
|
17
16
|
@dataclass
|
18
|
-
class CompuMethod
|
17
|
+
class CompuMethod:
|
19
18
|
internal_type: DataType
|
20
19
|
physical_type: DataType
|
21
20
|
|
22
21
|
@property
|
23
|
-
@abc.abstractmethod
|
24
22
|
def category(self) -> CompuMethodCategory:
|
25
|
-
|
23
|
+
raise NotImplementedError()
|
26
24
|
|
27
25
|
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
|
28
26
|
raise NotImplementedError()
|
@@ -42,13 +42,22 @@ class CompuScale:
|
|
42
42
|
compu_const: Optional[AtomicOdxType]
|
43
43
|
compu_rational_coeffs: Optional[CompuRationalCoeffs]
|
44
44
|
|
45
|
+
# the following two attributes are not specified for COMPU-SCALE
|
46
|
+
# tags in the XML, but they are required to do anything useful
|
47
|
+
# with it.
|
48
|
+
internal_type: DataType
|
49
|
+
physical_type: DataType
|
50
|
+
|
45
51
|
@staticmethod
|
46
|
-
def
|
47
|
-
|
52
|
+
def compuscale_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
53
|
+
internal_type: DataType, physical_type: DataType) -> "CompuScale":
|
48
54
|
short_label = et_element.findtext("SHORT-LABEL")
|
49
55
|
description = create_description_from_et(et_element.find("DESC"))
|
50
|
-
|
51
|
-
|
56
|
+
|
57
|
+
lower_limit = Limit.limit_from_et(
|
58
|
+
et_element.find("LOWER-LIMIT"), doc_frags, value_type=internal_type)
|
59
|
+
upper_limit = Limit.limit_from_et(
|
60
|
+
et_element.find("UPPER-LIMIT"), doc_frags, value_type=internal_type)
|
52
61
|
|
53
62
|
compu_inverse_value = internal_type.create_from_et(et_element.find("COMPU-INVERSE-VALUE"))
|
54
63
|
compu_const = physical_type.create_from_et(et_element.find("COMPU-CONST"))
|
@@ -64,4 +73,35 @@ class CompuScale:
|
|
64
73
|
upper_limit=upper_limit,
|
65
74
|
compu_inverse_value=compu_inverse_value,
|
66
75
|
compu_const=compu_const,
|
67
|
-
compu_rational_coeffs=compu_rational_coeffs
|
76
|
+
compu_rational_coeffs=compu_rational_coeffs,
|
77
|
+
internal_type=internal_type,
|
78
|
+
physical_type=physical_type)
|
79
|
+
|
80
|
+
def applies(self, internal_value: AtomicOdxType) -> bool:
|
81
|
+
|
82
|
+
if self.lower_limit is None and self.upper_limit is None:
|
83
|
+
# Everything is allowed: No limits have been specified
|
84
|
+
return True
|
85
|
+
elif self.upper_limit is None:
|
86
|
+
# no upper limit has been specified. the spec says that
|
87
|
+
# the value specified by the lower limit is the only one
|
88
|
+
# which is allowed (cf section 7.3.6.6.1)
|
89
|
+
assert self.lower_limit is not None
|
90
|
+
|
91
|
+
return internal_value == self.lower_limit.value
|
92
|
+
elif self.lower_limit is None:
|
93
|
+
# only the upper limit has been specified. the spec is
|
94
|
+
# ambiguous: it only says that if no upper limit is
|
95
|
+
# defined, the lower limit shall also be used as the upper
|
96
|
+
# limit and a closed interval type ought to be assumed,
|
97
|
+
# but it does not say what happens if the lower limit is
|
98
|
+
# not defined (which is allowed by the XSD). We thus
|
99
|
+
# assume that if only the upper limit is defined, is
|
100
|
+
# treated the same way as if only the lower limit is
|
101
|
+
# specified.
|
102
|
+
assert self.upper_limit is not None
|
103
|
+
|
104
|
+
return internal_value == self.upper_limit.value
|
105
|
+
|
106
|
+
return self.lower_limit.complies_to_lower(internal_value) and \
|
107
|
+
self.upper_limit.complies_to_upper(internal_value)
|