odxtools 8.3.4__py3-none-any.whl → 9.1.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 +36 -207
- odxtools/cli/_print_utils.py +163 -131
- odxtools/cli/browse.py +94 -79
- odxtools/cli/compare.py +88 -69
- odxtools/cli/list.py +2 -3
- odxtools/codec.py +211 -0
- odxtools/comparamspec.py +16 -54
- odxtools/comparamsubset.py +20 -61
- odxtools/database.py +18 -2
- odxtools/diaglayercontainer.py +14 -40
- odxtools/diaglayers/hierarchyelement.py +0 -10
- odxtools/dopbase.py +5 -3
- odxtools/dtcdop.py +101 -14
- odxtools/inputparam.py +0 -7
- odxtools/leadinglengthinfotype.py +1 -8
- odxtools/message.py +0 -7
- odxtools/minmaxlengthtype.py +4 -4
- odxtools/odxcategory.py +83 -0
- odxtools/outputparam.py +0 -7
- odxtools/parameterinfo.py +12 -12
- odxtools/parameters/lengthkeyparameter.py +1 -2
- odxtools/parameters/parameter.py +6 -4
- odxtools/parameters/tablekeyparameter.py +9 -8
- odxtools/paramlengthinfotype.py +8 -9
- odxtools/request.py +109 -16
- odxtools/response.py +115 -15
- odxtools/specialdatagroup.py +1 -1
- odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -21
- odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +4 -22
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -25
- odxtools/templates/macros/printDOP.xml.jinja2 +16 -0
- odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
- odxtools/uds.py +0 -8
- odxtools/version.py +2 -2
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/METADATA +7 -8
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/RECORD +40 -37
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/WHEEL +1 -1
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/LICENSE +0 -0
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/entry_points.txt +0 -0
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/top_level.txt +0 -0
odxtools/cli/_print_utils.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import re
|
3
3
|
import textwrap
|
4
|
-
from typing import
|
4
|
+
from typing import List, Optional, Tuple, Union
|
5
5
|
|
6
6
|
import markdownify
|
7
|
-
from
|
7
|
+
from rich import print as rich_print
|
8
|
+
from rich.padding import Padding as RichPadding
|
9
|
+
from rich.table import Table as RichTable
|
8
10
|
|
9
11
|
from ..description import Description
|
10
12
|
from ..diaglayers.diaglayer import DiagLayer
|
@@ -36,148 +38,179 @@ def print_diagnostic_service(service: DiagService,
|
|
36
38
|
print_pre_condition_states: bool = False,
|
37
39
|
print_state_transitions: bool = False,
|
38
40
|
print_audiences: bool = False,
|
39
|
-
allow_unknown_bit_lengths: bool = False
|
40
|
-
print_fn: Callable[..., Any] = print) -> None:
|
41
|
+
allow_unknown_bit_lengths: bool = False) -> None:
|
41
42
|
|
42
|
-
|
43
|
+
rich_print(f" Service '{service.short_name}':")
|
43
44
|
|
44
45
|
if service.description:
|
45
46
|
desc = format_desc(service.description, indent=3)
|
46
|
-
|
47
|
+
rich_print(f" Description: " + desc)
|
47
48
|
|
48
49
|
if print_pre_condition_states and len(service.pre_condition_states) > 0:
|
49
50
|
pre_condition_states_short_names = [
|
50
51
|
pre_condition_state.short_name for pre_condition_state in service.pre_condition_states
|
51
52
|
]
|
52
|
-
|
53
|
+
rich_print(f" Pre-Condition States: {', '.join(pre_condition_states_short_names)}")
|
53
54
|
|
54
55
|
if print_state_transitions and len(service.state_transitions) > 0:
|
55
56
|
state_transitions = [
|
56
57
|
f"{state_transition.source_snref} -> {state_transition.target_snref}"
|
57
58
|
for state_transition in service.state_transitions
|
58
59
|
]
|
59
|
-
|
60
|
+
rich_print(f" State Transitions: {', '.join(state_transitions)}")
|
60
61
|
|
61
62
|
if print_audiences and service.audience:
|
62
63
|
enabled_audiences_short_names = [
|
63
64
|
enabled_audience.short_name for enabled_audience in service.audience.enabled_audiences
|
64
65
|
]
|
65
|
-
|
66
|
+
rich_print(f" Enabled Audiences: {', '.join(enabled_audiences_short_names)}")
|
66
67
|
|
67
68
|
if print_params:
|
68
|
-
print_service_parameters(
|
69
|
-
service, allow_unknown_bit_lengths=allow_unknown_bit_lengths, print_fn=print_fn)
|
69
|
+
print_service_parameters(service, allow_unknown_bit_lengths=allow_unknown_bit_lengths)
|
70
70
|
|
71
71
|
|
72
72
|
def print_service_parameters(service: DiagService,
|
73
|
-
|
74
|
-
|
75
|
-
# prints parameter details of request, positive response and
|
73
|
+
*,
|
74
|
+
allow_unknown_bit_lengths: bool = False) -> None:
|
75
|
+
# prints parameter details of request, positive response and
|
76
|
+
# negative response of diagnostic service
|
76
77
|
|
77
78
|
# Request
|
78
79
|
if service.request:
|
79
|
-
|
80
|
+
rich_print(f" Request '{service.request.short_name}':")
|
80
81
|
const_prefix = service.request.coded_const_prefix()
|
81
|
-
|
82
|
+
rich_print(
|
82
83
|
f" Identifying Prefix: 0x{const_prefix.hex().upper()} ({bytes(const_prefix)!r})")
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
print_fn(table_str)
|
88
|
-
print_fn()
|
84
|
+
rich_print(f" Parameters:")
|
85
|
+
param_table = extract_parameter_tabulation_data(service.request.parameters)
|
86
|
+
rich_print(RichPadding(param_table, pad=(0, 0, 0, 4)))
|
87
|
+
rich_print()
|
89
88
|
else:
|
90
|
-
|
89
|
+
rich_print(f" No Request!")
|
91
90
|
|
92
91
|
# Positive Responses
|
93
92
|
if not service.positive_responses:
|
94
|
-
|
93
|
+
rich_print(f" No positive responses")
|
95
94
|
|
96
95
|
for resp in service.positive_responses:
|
97
|
-
|
98
|
-
|
96
|
+
rich_print(f" Positive Response '{resp.short_name}':")
|
97
|
+
rich_print(f" Parameters:\n")
|
99
98
|
table = extract_parameter_tabulation_data(list(resp.parameters))
|
100
|
-
|
101
|
-
|
102
|
-
print_fn()
|
99
|
+
rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
|
100
|
+
rich_print()
|
103
101
|
|
104
102
|
# Negative Response
|
105
103
|
if not service.negative_responses:
|
106
|
-
|
104
|
+
rich_print(f" No negative responses")
|
107
105
|
|
108
106
|
for resp in service.negative_responses:
|
109
|
-
|
110
|
-
|
107
|
+
rich_print(f" Negative Response '{resp.short_name}':")
|
108
|
+
rich_print(f" Parameters:\n")
|
111
109
|
table = extract_parameter_tabulation_data(list(resp.parameters))
|
112
|
-
|
113
|
-
|
114
|
-
print_fn()
|
110
|
+
rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
|
111
|
+
rich_print()
|
115
112
|
|
116
|
-
|
113
|
+
rich_print("\n")
|
117
114
|
|
118
115
|
|
119
|
-
def extract_service_tabulation_data(services: List[DiagService]
|
120
|
-
|
121
|
-
|
116
|
+
def extract_service_tabulation_data(services: List[DiagService],
|
117
|
+
*,
|
118
|
+
additional_columns: Optional[List[Tuple[str, List[str]]]] = None
|
119
|
+
) -> RichTable:
|
120
|
+
"""Extracts data of diagnostic services into Dictionary which can
|
121
|
+
be printed by tabulate module
|
122
|
+
"""
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
# Create Rich table
|
125
|
+
table = RichTable(
|
126
|
+
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)
|
127
|
+
|
128
|
+
name_column: List[str] = []
|
129
|
+
semantic_column: List[str] = []
|
130
|
+
request_column: List[str] = []
|
126
131
|
|
127
132
|
for service in services:
|
128
|
-
|
129
|
-
|
133
|
+
name_column.append(service.short_name)
|
134
|
+
semantic_column.append(service.semantic or "")
|
130
135
|
|
131
136
|
if service.request:
|
132
137
|
prefix = service.request.coded_const_prefix()
|
133
|
-
|
134
|
-
prefix) > 32 else
|
138
|
+
request_column.append(f"0x{str(prefix.hex().upper())[:32]}...") if len(
|
139
|
+
prefix) > 32 else request_column.append(f"0x{str(prefix.hex().upper())}")
|
135
140
|
else:
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
141
|
+
request_column.append("")
|
142
|
+
|
143
|
+
table.add_column("Name", style="green")
|
144
|
+
table.add_column("Semantic", justify="left", style="white")
|
145
|
+
table.add_column("Request", justify="left", style="white")
|
146
|
+
if additional_columns is not None:
|
147
|
+
for ac_title, _ in additional_columns:
|
148
|
+
table.add_column(ac_title, justify="left", style="white")
|
149
|
+
|
150
|
+
rows = zip(name_column, semantic_column, request_column,
|
151
|
+
*[ac[1] for ac in additional_columns])
|
152
|
+
for row in rows:
|
153
|
+
table.add_row(*map(str, row))
|
154
|
+
|
155
|
+
return table
|
156
|
+
|
157
|
+
|
158
|
+
def extract_parameter_tabulation_data(parameters: List[Parameter]) -> RichTable:
|
159
|
+
# extracts data of parameters of diagnostic services into
|
160
|
+
# a RichTable object that can be printed
|
161
|
+
|
162
|
+
# Create Rich table
|
163
|
+
table = RichTable(
|
164
|
+
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)
|
165
|
+
|
166
|
+
# Add columns with appropriate styling
|
167
|
+
table.add_column("Name", style="green")
|
168
|
+
table.add_column("Byte Position", justify="right", style="yellow")
|
169
|
+
table.add_column("Bit Length", justify="right", style="yellow")
|
170
|
+
table.add_column("Semantic", justify="left", style="white")
|
171
|
+
table.add_column("Parameter Type", justify="left", style="white")
|
172
|
+
table.add_column("Data Type", justify="left", style="white")
|
173
|
+
table.add_column("Value", justify="left", style="yellow")
|
174
|
+
table.add_column("Value Type", justify="left", style="white")
|
175
|
+
table.add_column("Linked DOP", justify="left", style="white")
|
176
|
+
|
177
|
+
name_column: List[str] = []
|
178
|
+
byte_column: List[str] = []
|
179
|
+
bit_length_column: List[str] = []
|
180
|
+
semantic_column: List[str] = []
|
181
|
+
param_type_column: List[str] = []
|
182
|
+
value_column: List[str] = []
|
183
|
+
value_type_column: List[str] = []
|
184
|
+
data_type_column: List[str] = []
|
185
|
+
dop_column: List[str] = []
|
154
186
|
|
155
187
|
for param in parameters:
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
188
|
+
name_column.append(param.short_name)
|
189
|
+
byte_column.append("" if param.byte_position is None else str(param.byte_position))
|
190
|
+
semantic_column.append(param.semantic or "")
|
191
|
+
param_type_column.append(param.parameter_type)
|
160
192
|
length = 0
|
161
193
|
if param.get_static_bit_length() is not None:
|
162
|
-
|
163
|
-
|
194
|
+
n = param.get_static_bit_length()
|
195
|
+
bit_length_column.append("" if n is None else str(n))
|
196
|
+
length = (n or 0) // 4
|
164
197
|
else:
|
165
|
-
|
198
|
+
bit_length_column.append("")
|
166
199
|
if isinstance(param, CodedConstParameter):
|
167
200
|
if isinstance(param.coded_value, int):
|
168
|
-
|
201
|
+
value_column.append(f"0x{param.coded_value:0{length}X}")
|
169
202
|
elif isinstance(param.coded_value, bytes) or isinstance(param.coded_value, bytearray):
|
170
|
-
|
203
|
+
value_column.append(f"0x{param.coded_value.hex().upper()}")
|
171
204
|
else:
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
205
|
+
value_column.append(f"{param.coded_value!r}")
|
206
|
+
data_type_column.append(param.diag_coded_type.base_data_type.name)
|
207
|
+
value_type_column.append('coded value')
|
208
|
+
dop_column.append("")
|
176
209
|
elif isinstance(param, NrcConstParameter):
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
210
|
+
data_type_column.append(param.diag_coded_type.base_data_type.name)
|
211
|
+
value_column.append(str(param.coded_values))
|
212
|
+
value_type_column.append('coded values')
|
213
|
+
dop_column.append("")
|
181
214
|
elif isinstance(param, (PhysicalConstantParameter, SystemParameter, ValueParameter)):
|
182
215
|
# this is a hack to make this routine work for parameters
|
183
216
|
# which reference DOPs of a type that a is not yet
|
@@ -186,72 +219,71 @@ def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Dict[str,
|
|
186
219
|
param_dop = getattr(param, "_dop", None)
|
187
220
|
|
188
221
|
if param_dop is not None:
|
189
|
-
|
222
|
+
dop_column.append(param_dop.short_name)
|
190
223
|
|
191
224
|
if param_dop is not None and (phys_type := getattr(param, "physical_type",
|
192
225
|
None)) is not None:
|
193
|
-
|
226
|
+
data_type_column.append(phys_type.base_data_type.name)
|
194
227
|
else:
|
195
|
-
|
228
|
+
data_type_column.append("")
|
196
229
|
if isinstance(param, PhysicalConstantParameter):
|
197
230
|
if isinstance(param.physical_constant_value, bytes) or isinstance(
|
198
231
|
param.physical_constant_value, bytearray):
|
199
|
-
|
232
|
+
value_column.append(f"0x{param.physical_constant_value.hex().upper()}")
|
200
233
|
else:
|
201
|
-
|
202
|
-
|
234
|
+
value_column.append(f"{param.physical_constant_value!r}")
|
235
|
+
value_type_column.append('constant value')
|
203
236
|
elif isinstance(param, ValueParameter) and param.physical_default_value is not None:
|
204
237
|
if isinstance(param.physical_default_value, bytes) or isinstance(
|
205
238
|
param.physical_default_value, bytearray):
|
206
|
-
|
239
|
+
value_column.append(f"0x{param.physical_default_value.hex().upper()}")
|
207
240
|
else:
|
208
|
-
|
209
|
-
|
241
|
+
value_column.append(f"{param.physical_default_value!r}")
|
242
|
+
value_type_column.append('default value')
|
210
243
|
else:
|
211
|
-
|
212
|
-
|
244
|
+
value_column.append("")
|
245
|
+
value_type_column.append("")
|
213
246
|
else:
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
247
|
+
value_column.append("")
|
248
|
+
data_type_column.append("")
|
249
|
+
value_type_column.append("")
|
250
|
+
dop_column.append("")
|
251
|
+
|
252
|
+
# Add all rows at once by zipping dictionary values
|
253
|
+
rows = zip(name_column, byte_column, bit_length_column, semantic_column, param_type_column,
|
254
|
+
data_type_column, value_column, value_type_column, dop_column)
|
255
|
+
for row in rows:
|
256
|
+
table.add_row(*map(str, row))
|
257
|
+
|
258
|
+
return table
|
259
|
+
|
260
|
+
|
261
|
+
def print_dl_metrics(variants: List[DiagLayer]) -> None:
|
262
|
+
"""
|
263
|
+
Print diagnostic layer metrics using Rich tables.
|
264
|
+
Args:
|
265
|
+
variants: List of diagnostic layer variants to analyze
|
266
|
+
"""
|
267
|
+
# Create Rich table
|
268
|
+
table = RichTable(
|
269
|
+
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)
|
270
|
+
|
271
|
+
# Add columns with appropriate styling
|
272
|
+
table.add_column("Name", style="green")
|
273
|
+
table.add_column("Variant Type", style="magenta")
|
274
|
+
table.add_column("Number of Services", justify="right", style="yellow")
|
275
|
+
table.add_column("Number of DOPs", justify="right", style="yellow")
|
276
|
+
table.add_column("Number of communication parameters", justify="right", style="yellow")
|
277
|
+
|
278
|
+
# Process each variant
|
239
279
|
for variant in variants:
|
240
280
|
assert isinstance(variant, DiagLayer)
|
241
281
|
all_services: List[Union[DiagService, SingleEcuJob]] = sorted(
|
242
282
|
variant.services, key=lambda x: x.short_name)
|
243
|
-
name.append(variant.short_name)
|
244
|
-
type.append(variant.variant_type.value)
|
245
|
-
num_services.append(len(all_services))
|
246
283
|
ddds = variant.diag_data_dictionary_spec
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
'Number of Services': num_services,
|
254
|
-
'Number of DOPs': num_dops,
|
255
|
-
'Number of communication parameters': num_comparam_refs
|
256
|
-
}
|
257
|
-
print_fn(tabulate(table, headers='keys', tablefmt='presto'))
|
284
|
+
|
285
|
+
# Add row to table
|
286
|
+
table.add_row(variant.short_name, variant.variant_type.value, str(len(all_services)),
|
287
|
+
str(len(ddds.data_object_props)),
|
288
|
+
str(len(getattr(variant, "comparams_refs", []))))
|
289
|
+
rich_print(table)
|
odxtools/cli/browse.py
CHANGED
@@ -5,14 +5,16 @@ import sys
|
|
5
5
|
from typing import List, Optional, Union, cast
|
6
6
|
|
7
7
|
import InquirerPy.prompt as IP_prompt
|
8
|
-
from tabulate import tabulate # TODO: switch to rich tables
|
9
8
|
|
9
|
+
from ..complexdop import ComplexDop
|
10
10
|
from ..database import Database
|
11
11
|
from ..dataobjectproperty import DataObjectProperty
|
12
12
|
from ..diaglayer import DiagLayer
|
13
13
|
from ..diagservice import DiagService
|
14
|
+
from ..dopbase import DopBase
|
14
15
|
from ..exceptions import odxraise, odxrequire
|
15
16
|
from ..hierarchyelement import HierarchyElement
|
17
|
+
from ..odxlink import resolve_snref
|
16
18
|
from ..odxtypes import AtomicOdxType, DataType, ParameterValueDict
|
17
19
|
from ..parameters.matchingrequestparameter import MatchingRequestParameter
|
18
20
|
from ..parameters.parameter import Parameter
|
@@ -111,24 +113,44 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
|
|
111
113
|
return cast(str, answer.get(parameter.short_name))
|
112
114
|
|
113
115
|
|
114
|
-
def encode_message_interactively(
|
116
|
+
def encode_message_interactively(codec: Union[Request, Response],
|
115
117
|
ask_user_confirmation: bool = False) -> None:
|
116
118
|
if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
|
117
119
|
) or not sys.__stdout__.isatty():
|
118
120
|
raise SystemError("This command can only be used in an interactive shell!")
|
119
|
-
param_dict = sub_service.parameter_dict()
|
120
121
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
122
|
+
answered_request = b''
|
123
|
+
if isinstance(codec, Response):
|
124
|
+
answered_request_prompt = [{
|
125
|
+
"type":
|
126
|
+
"input",
|
127
|
+
"name":
|
128
|
+
"request",
|
129
|
+
"message":
|
130
|
+
f"What is the request you want to answer? (Enter the coded request as integer in hexadecimal format (e.g. 12 3B 5)",
|
131
|
+
"filter":
|
132
|
+
lambda input: _convert_string_to_bytes(input),
|
133
|
+
}]
|
134
|
+
answer = IP_prompt(answered_request_prompt)
|
135
|
+
answered_request = cast(bytes, answer.get("request"))
|
136
|
+
print(f"Input interpretation as list: {list(answered_request)}")
|
137
|
+
|
138
|
+
has_settable_param = False
|
139
|
+
for param in codec.parameters:
|
140
|
+
if not param.is_settable:
|
141
|
+
continue
|
142
|
+
|
143
|
+
# TODO: Specifying complex parameters with nesting depth > 1
|
144
|
+
# is not possible yet
|
145
|
+
if (inner_params := getattr(getattr(param, "dop", None), "parameters", None)) is not None:
|
146
|
+
for inner_param in inner_params:
|
147
|
+
if inner_param.is_settable:
|
148
|
+
has_settable_param = True
|
149
|
+
elif param.is_settable:
|
150
|
+
has_settable_param = True
|
129
151
|
|
130
152
|
param_values: ParameterValueDict = {}
|
131
|
-
if
|
153
|
+
if has_settable_param:
|
132
154
|
# Ask whether user wants to encode a message
|
133
155
|
if ask_user_confirmation:
|
134
156
|
encode_message_prompt = [{
|
@@ -141,48 +163,40 @@ def encode_message_interactively(sub_service: Union[Request, Response],
|
|
141
163
|
if answer.get("yes_no_prompt") == "no":
|
142
164
|
return
|
143
165
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
f"What is the request you want to answer? (Enter the coded request as integer in hexadecimal format (e.g. 12 3B 5)",
|
153
|
-
"filter":
|
154
|
-
lambda input: _convert_string_to_bytes(input),
|
155
|
-
}]
|
156
|
-
answer = IP_prompt(answered_request_prompt)
|
157
|
-
answered_request = cast(bytes, answer.get("request"))
|
158
|
-
print(f"Input interpretation as list: {list(answered_request)}")
|
159
|
-
|
160
|
-
# Request values for parameters
|
161
|
-
for key, param_or_structure in param_dict.items():
|
162
|
-
if isinstance(param_or_structure, dict):
|
163
|
-
# param_or_structure refers to a structure (represented as dict of params)
|
166
|
+
# Query user for the values of all settable parameters
|
167
|
+
for param in codec.parameters:
|
168
|
+
if (inner_params := getattr(dop := getattr(param, "dop", None), "parameters",
|
169
|
+
None)) is not None:
|
170
|
+
assert isinstance(dop, DopBase)
|
171
|
+
inner_params = cast(List[Parameter], inner_params)
|
172
|
+
# param refers to a complex DOP, i.e., the required
|
173
|
+
# value is a key-value dict
|
164
174
|
print(
|
165
|
-
f"The next {len(
|
175
|
+
f"The next {len(inner_params)} parameters belong to the structure '{dop.short_name}'"
|
166
176
|
)
|
167
177
|
structure_param_values: ParameterValueDict = {}
|
168
|
-
for
|
169
|
-
if
|
170
|
-
val = prompt_single_parameter_value(
|
178
|
+
for inner_param in inner_params:
|
179
|
+
if inner_param.is_settable:
|
180
|
+
val = prompt_single_parameter_value(inner_param)
|
171
181
|
if val is not None:
|
172
|
-
structure_param_values[
|
173
|
-
param_values[
|
174
|
-
elif
|
175
|
-
|
176
|
-
val = prompt_single_parameter_value(param_or_structure)
|
182
|
+
structure_param_values[inner_param.short_name] = val
|
183
|
+
param_values[param.short_name] = structure_param_values
|
184
|
+
elif param.is_settable:
|
185
|
+
val = prompt_single_parameter_value(param)
|
177
186
|
if val is not None:
|
178
|
-
param_values[
|
179
|
-
|
180
|
-
|
187
|
+
param_values[param.short_name] = val
|
188
|
+
|
189
|
+
if isinstance(codec, Response):
|
190
|
+
payload = codec.encode(coded_request=answered_request, **param_values)
|
181
191
|
else:
|
182
|
-
payload =
|
192
|
+
payload = codec.encode(coded_request=b'', **param_values)
|
183
193
|
else:
|
184
|
-
# There are no
|
185
|
-
|
194
|
+
# There are no settable parameters -> Just print message
|
195
|
+
if isinstance(codec, Response):
|
196
|
+
payload = codec.encode(coded_request=answered_request)
|
197
|
+
else:
|
198
|
+
payload = codec.encode()
|
199
|
+
|
186
200
|
print(f"Message payload: 0x{bytes(payload).hex()}")
|
187
201
|
|
188
202
|
|
@@ -193,56 +207,58 @@ def encode_message_from_string_values(
|
|
193
207
|
if parameter_values is None:
|
194
208
|
parameter_values = {}
|
195
209
|
parameter_values = parameter_values.copy()
|
196
|
-
param_dict = sub_service.parameter_dict()
|
197
210
|
|
198
|
-
# Check if all needed parameters
|
211
|
+
# Check if all needed parameters have been specified
|
199
212
|
missing_parameter_names = []
|
200
|
-
for
|
201
|
-
if
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
213
|
+
for param in sub_service.parameters:
|
214
|
+
if (inner_params := getattr(dop := getattr(param, "dop", None), "parameters",
|
215
|
+
None)) is not None:
|
216
|
+
inner_param_values = parameter_values.get(param.short_name, {})
|
217
|
+
if not isinstance(inner_param_values, dict):
|
218
|
+
print(f"Value for composite parameter {param.short_name} must be "
|
219
|
+
f"a dictionary, got {type(inner_param_values).__name__}")
|
220
|
+
continue
|
221
|
+
for inner_param in inner_params:
|
222
|
+
if inner_param.is_required and inner_param.short_name not in inner_param_values:
|
223
|
+
missing_parameter_names.append(f"{param.short_name}.{inner_param.short_name}")
|
210
224
|
else:
|
211
|
-
if param.is_required and parameter_values.get(
|
212
|
-
missing_parameter_names.append(
|
225
|
+
if param.is_required and parameter_values.get(param.short_name) is None:
|
226
|
+
missing_parameter_names.append(param.short_name)
|
213
227
|
|
214
228
|
if len(missing_parameter_names) > 0:
|
215
|
-
print("The following parameters are required but missing
|
216
|
-
print(" - " + "\n - ".join(missing_parameter_names))
|
229
|
+
print("The following parameters are required but missing:")
|
230
|
+
print(" - " + "\n - ".join(sorted(missing_parameter_names)))
|
217
231
|
return
|
218
232
|
|
219
233
|
# Request values for parameters
|
220
234
|
for parameter_sn, parameter_value in parameter_values.items():
|
221
|
-
parameter =
|
235
|
+
parameter = resolve_snref(parameter_sn, sub_service.parameters, Parameter)
|
222
236
|
if parameter is None:
|
223
237
|
print(f"I don't know the parameter {parameter_sn}")
|
224
238
|
continue
|
225
239
|
|
226
240
|
if isinstance(parameter_value, dict):
|
227
241
|
# parameter_value refers to a structure (represented as dict of params)
|
242
|
+
dop = getattr(parameter, "dop", None)
|
243
|
+
inner_params = getattr(dop, "parameters", None)
|
244
|
+
assert isinstance(dop, ComplexDop)
|
245
|
+
assert isinstance(inner_params, list)
|
246
|
+
inner_params = cast(List[Parameter], inner_params)
|
247
|
+
|
228
248
|
typed_dict = parameter_value.copy()
|
229
|
-
for
|
230
|
-
|
231
|
-
if
|
232
|
-
print(f"Unknown sub-parameter {
|
249
|
+
for inner_param_sn, inner_param_value in parameter_value.items():
|
250
|
+
inner_param = resolve_snref(inner_param_sn, inner_params, Parameter)
|
251
|
+
if inner_param is None:
|
252
|
+
print(f"Unknown sub-parameter {inner_param_sn}")
|
233
253
|
continue
|
234
|
-
if not isinstance(
|
235
|
-
print(f"The value specified for parameter {
|
254
|
+
if not isinstance(inner_param_value, str):
|
255
|
+
print(f"The value specified for parameter {inner_param_sn} is not a string")
|
236
256
|
continue
|
237
257
|
|
238
|
-
typed_dict[
|
239
|
-
|
240
|
-
|
241
|
-
)
|
242
|
-
parameter_values[parameter_sn] = typed_dict
|
258
|
+
typed_dict[inner_param_sn] = _convert_string_to_odx_type(
|
259
|
+
inner_param_value, inner_param.physical_type.base_data_type)
|
260
|
+
parameter_values[parameter.short_name] = typed_dict
|
243
261
|
else:
|
244
|
-
assert isinstance(parameter, Parameter)
|
245
|
-
|
246
262
|
if not isinstance(parameter_value, str):
|
247
263
|
print(f"Value for parameter {parameter_sn} is not a string")
|
248
264
|
continue
|
@@ -356,8 +372,7 @@ def browse(odxdb: Database) -> None:
|
|
356
372
|
if codec is not None:
|
357
373
|
assert isinstance(codec, (Request, Response))
|
358
374
|
table = extract_parameter_tabulation_data(codec.parameters)
|
359
|
-
|
360
|
-
print(table_str)
|
375
|
+
print(table)
|
361
376
|
|
362
377
|
encode_message_interactively(codec, ask_user_confirmation=True)
|
363
378
|
|