odxtools 6.7.0__py3-none-any.whl → 9.3.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.
Files changed (213) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
odxtools/cli/browse.py CHANGED
@@ -5,13 +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
16
+ from ..hierarchyelement import HierarchyElement
17
+ from ..odxlink import resolve_snref
15
18
  from ..odxtypes import AtomicOdxType, DataType, ParameterValueDict
16
19
  from ..parameters.matchingrequestparameter import MatchingRequestParameter
17
20
  from ..parameters.parameter import Parameter
@@ -110,23 +113,44 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
110
113
  return cast(str, answer.get(parameter.short_name))
111
114
 
112
115
 
113
- def encode_message_interactively(sub_service: Union[Request, Response],
116
+ def encode_message_interactively(codec: Union[Request, Response],
114
117
  ask_user_confirmation: bool = False) -> None:
115
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
118
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
119
+ ) or not sys.__stdout__.isatty():
116
120
  raise SystemError("This command can only be used in an interactive shell!")
117
- param_dict = sub_service.parameter_dict()
118
121
 
119
- exists_definable_param = False
120
- for param_or_dict in param_dict.values():
121
- if isinstance(param_or_dict, dict):
122
- for param in param_or_dict.values():
123
- if isinstance(param, Parameter) and param.is_settable:
124
- exists_definable_param = True
125
- elif param_or_dict.is_settable:
126
- exists_definable_param = True
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
127
151
 
128
152
  param_values: ParameterValueDict = {}
129
- if exists_definable_param > 0:
153
+ if has_settable_param:
130
154
  # Ask whether user wants to encode a message
131
155
  if ask_user_confirmation:
132
156
  encode_message_prompt = [{
@@ -139,48 +163,40 @@ def encode_message_interactively(sub_service: Union[Request, Response],
139
163
  if answer.get("yes_no_prompt") == "no":
140
164
  return
141
165
 
142
- answered_request = b''
143
- if isinstance(sub_service, Response):
144
- answered_request_prompt = [{
145
- "type":
146
- "input",
147
- "name":
148
- "request",
149
- "message":
150
- f"What is the request you want to answer? (Enter the coded request as integer in hexadecimal format (e.g. 12 3B 5)",
151
- "filter":
152
- lambda input: _convert_string_to_bytes(input),
153
- }]
154
- answer = IP_prompt(answered_request_prompt)
155
- answered_request = cast(bytes, answer.get("request"))
156
- print(f"Input interpretation as list: {list(answered_request)}")
157
-
158
- # Request values for parameters
159
- for key, param_or_structure in param_dict.items():
160
- if isinstance(param_or_structure, dict):
161
- # 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
162
174
  print(
163
- f"The next {len(param_or_structure)} parameters belong to the structure '{key}'"
175
+ f"The next {len(inner_params)} parameters belong to the structure '{dop.short_name}'"
164
176
  )
165
177
  structure_param_values: ParameterValueDict = {}
166
- for param_sn, param in param_or_structure.items():
167
- if not isinstance(param, dict) and param.is_settable:
168
- val = prompt_single_parameter_value(param)
178
+ for inner_param in inner_params:
179
+ if inner_param.is_settable:
180
+ val = prompt_single_parameter_value(inner_param)
169
181
  if val is not None:
170
- structure_param_values[param_sn] = val
171
- param_values[key] = structure_param_values
172
- elif param_or_structure.is_settable:
173
- # param_or_structure is a parameter
174
- 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)
175
186
  if val is not None:
176
- param_values[key] = val
177
- if isinstance(sub_service, Response):
178
- payload = sub_service.encode(coded_request=answered_request, **param_values)
187
+ param_values[param.short_name] = val
188
+
189
+ if isinstance(codec, Response):
190
+ payload = codec.encode(coded_request=answered_request, **param_values)
179
191
  else:
180
- payload = sub_service.encode(coded_request=b'', **param_values)
192
+ payload = codec.encode(coded_request=b'', **param_values)
181
193
  else:
182
- # There are no optional parameters that need to be defined by the user -> Just print message
183
- payload = sub_service.encode()
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
+
184
200
  print(f"Message payload: 0x{bytes(payload).hex()}")
185
201
 
186
202
 
@@ -191,56 +207,58 @@ def encode_message_from_string_values(
191
207
  if parameter_values is None:
192
208
  parameter_values = {}
193
209
  parameter_values = parameter_values.copy()
194
- param_dict = sub_service.parameter_dict()
195
210
 
196
- # Check if all needed parameters are given
211
+ # Check if all needed parameters have been specified
197
212
  missing_parameter_names = []
198
- for param_sn, param in param_dict.items():
199
- if isinstance(param, dict):
200
- # param_value refers to a structure (represented as dict of params)
201
- for simple_param_sn, simple_param in param.items():
202
- structured_value = parameter_values.get(param_sn)
203
- if not isinstance(simple_param, Parameter):
204
- continue
205
- if simple_param.is_required and (not isinstance(structured_value, dict) or
206
- structured_value.get(simple_param_sn) is None):
207
- missing_parameter_names.append(f"{param_sn} :: {simple_param_sn}")
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}")
208
224
  else:
209
- if param.is_required and parameter_values.get(param_sn) is None:
210
- missing_parameter_names.append(param_sn)
225
+ if param.is_required and parameter_values.get(param.short_name) is None:
226
+ missing_parameter_names.append(param.short_name)
211
227
 
212
228
  if len(missing_parameter_names) > 0:
213
- print("The following parameters are required but missing!")
214
- print(" - " + "\n - ".join(missing_parameter_names))
229
+ print("The following parameters are required but missing:")
230
+ print(" - " + "\n - ".join(sorted(missing_parameter_names)))
215
231
  return
216
232
 
217
233
  # Request values for parameters
218
234
  for parameter_sn, parameter_value in parameter_values.items():
219
- parameter = param_dict.get(parameter_sn)
235
+ parameter = resolve_snref(parameter_sn, sub_service.parameters, Parameter)
220
236
  if parameter is None:
221
237
  print(f"I don't know the parameter {parameter_sn}")
222
238
  continue
223
239
 
224
240
  if isinstance(parameter_value, dict):
225
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
+
226
248
  typed_dict = parameter_value.copy()
227
- for simple_param_sn, simple_param_val in parameter_value.items():
228
- simple_parameter = param_dict.get(simple_param_sn)
229
- if simple_parameter is None:
230
- print(f"Unknown sub-parameter {simple_param_sn}")
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}")
231
253
  continue
232
- if not isinstance(simple_param_val, str):
233
- print(f"The value specified for parameter {simple_param_sn} is not a string")
254
+ if not isinstance(inner_param_value, str):
255
+ print(f"The value specified for parameter {inner_param_sn} is not a string")
234
256
  continue
235
257
 
236
- typed_dict[simple_param_sn] = _convert_string_to_odx_type(
237
- simple_param_val,
238
- simple_parameter.physical_type.base_data_type # type: ignore[union-attr]
239
- )
240
- 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
241
261
  else:
242
- assert isinstance(parameter, Parameter)
243
-
244
262
  if not isinstance(parameter_value, str):
245
263
  print(f"Value for parameter {parameter_sn} is not a string")
246
264
  continue
@@ -260,7 +278,8 @@ def encode_message_from_string_values(
260
278
 
261
279
 
262
280
  def browse(odxdb: Database) -> None:
263
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
281
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
282
+ ) or not sys.__stdout__.isatty():
264
283
  raise SystemError("This command can only be used in an interactive shell!")
265
284
  dl_names = [dl.short_name for dl in odxdb.diag_layers]
266
285
  while True:
@@ -281,19 +300,20 @@ def browse(odxdb: Database) -> None:
281
300
  print(f"{type(answer.get('variant'))=}")
282
301
  assert isinstance(variant, DiagLayer)
283
302
 
284
- if (rx_id := variant.get_receive_id()) is not None:
285
- recv_id = hex(rx_id)
286
- else:
287
- recv_id = "None"
303
+ if isinstance(variant, HierarchyElement):
304
+ if (rx_id := variant.get_receive_id()) is not None:
305
+ recv_id = hex(rx_id)
306
+ else:
307
+ recv_id = "None"
288
308
 
289
- if (tx_id := variant.get_send_id()) is not None:
290
- send_id = hex(tx_id)
291
- else:
292
- send_id = "None"
309
+ if (tx_id := variant.get_send_id()) is not None:
310
+ send_id = hex(tx_id)
311
+ else:
312
+ send_id = "None"
293
313
 
294
- print(
295
- f"{variant.variant_type.value} '{variant.short_name}' (Receive ID: {recv_id}, Send ID: {send_id})"
296
- )
314
+ print(
315
+ f"{variant.variant_type.value} '{variant.short_name}' (Receive ID: {recv_id}, Send ID: {send_id})"
316
+ )
297
317
 
298
318
  while True:
299
319
  services: List[DiagService] = [
@@ -352,8 +372,7 @@ def browse(odxdb: Database) -> None:
352
372
  if codec is not None:
353
373
  assert isinstance(codec, (Request, Response))
354
374
  table = extract_parameter_tabulation_data(codec.parameters)
355
- table_str = tabulate(table, headers='keys', tablefmt='presto')
356
- print(table_str)
375
+ print(table)
357
376
 
358
377
  encode_message_interactively(codec, ask_user_confirmation=True)
359
378
 
odxtools/cli/compare.py CHANGED
@@ -5,13 +5,14 @@ import argparse
5
5
  import os
6
6
  from typing import Any, Dict, List, Optional, Set, Union, cast
7
7
 
8
- from rich import print
9
- from tabulate import tabulate # TODO: switch to rich tables
8
+ from rich import print as rich_print
9
+ from rich.padding import Padding as RichPadding
10
+ from rich.table import Table as RichTable
10
11
 
11
12
  from ..database import Database
12
- from ..diaglayer import DiagLayer
13
+ from ..diaglayers.diaglayer import DiagLayer
13
14
  from ..diagservice import DiagService
14
- from ..load_file import load_file
15
+ from ..loadfile import load_file
15
16
  from ..odxtypes import AtomicOdxType
16
17
  from ..parameters.codedconstparameter import CodedConstParameter
17
18
  from ..parameters.nrcconstparameter import NrcConstParameter
@@ -46,11 +47,14 @@ class Display:
46
47
  # class with variables and functions to display the result of the comparison
47
48
 
48
49
  # TODO
49
- # Idea: results as json export
50
- # - write results of comparison in json structure
51
- # - use odxlinks to refer to dignostic services / objects if changes have already been detected (e.g. in another ecu variant / diagnostic layer)
52
-
53
- # print all information about parameter properties (request, pos. response & neg. response parameters) for changed diagnostic services
50
+ # - Idea: results as json export
51
+ # - write results of comparison in json structure
52
+ # - use odxlinks to refer to dignostic services / objects if
53
+ # changes have already been detected (e.g. in another ecu
54
+ # variant / diagnostic layer)
55
+ # - print all information about parameter properties (request,
56
+ # pos. response & neg. response parameters) for changed diagnostic
57
+ # services
54
58
  param_detailed: bool
55
59
  obj_detailed: bool
56
60
 
@@ -62,46 +66,45 @@ class Display:
62
66
  if service_dict["new_services"] or service_dict["deleted_services"] or service_dict[
63
67
  "changed_name_of_service"][0] or service_dict["changed_parameters_of_service"][0]:
64
68
  assert isinstance(service_dict["diag_layer"], str)
65
- print()
66
- print(
69
+ rich_print()
70
+ rich_print(
67
71
  f"Changed diagnostic services for diagnostic layer '{service_dict['diag_layer']}' ({service_dict['diag_layer_type']}):"
68
72
  )
69
73
  if service_dict["new_services"]:
70
74
  assert isinstance(service_dict["new_services"], List)
71
- print()
72
- print(" [blue]New services[/blue]")
73
- table = extract_service_tabulation_data(
74
- service_dict["new_services"]) # type: ignore[arg-type]
75
- print(tabulate(table, headers="keys", tablefmt="presto"))
75
+ rich_print()
76
+ rich_print(" [blue]New services[/blue]")
77
+ rich_print(extract_service_tabulation_data(
78
+ service_dict["new_services"])) # type: ignore[arg-type]
76
79
  if service_dict["deleted_services"]:
77
80
  assert isinstance(service_dict["deleted_services"], List)
78
- print()
79
- print(" [blue]Deleted services[/blue]")
80
- table = extract_service_tabulation_data(
81
- service_dict["deleted_services"]) # type: ignore[arg-type]
82
- print(tabulate(table, headers="keys", tablefmt="presto"))
81
+ rich_print()
82
+ rich_print(" [blue]Deleted services[/blue]")
83
+ rich_print(extract_service_tabulation_data(
84
+ service_dict["deleted_services"])) # type: ignore[arg-type]
83
85
  if service_dict["changed_name_of_service"][0]:
84
- print()
85
- print(" [blue]Renamed services[/blue]")
86
- table = extract_service_tabulation_data(
87
- service_dict["changed_name_of_service"][0]) # type: ignore[arg-type]
88
- table["Old service name"] = service_dict["changed_name_of_service"][1]
89
- print(tabulate(table, headers="keys", tablefmt="presto"))
86
+ rich_print()
87
+ rich_print(" [blue]Renamed services[/blue]")
88
+ rich_print(extract_service_tabulation_data(
89
+ service_dict["changed_name_of_service"][0])) # type: ignore[arg-type]
90
90
  if service_dict["changed_parameters_of_service"][0]:
91
- print()
92
- print(" [blue]Services with parameter changes[/blue]")
91
+ rich_print()
92
+ rich_print(" [blue]Services with parameter changes[/blue]")
93
93
  # create table with information about services with parameter changes
94
+ changed_param_column = [
95
+ str(x) for x in service_dict["changed_parameters_of_service"][
96
+ 1] # type: ignore[union-attr]
97
+ ]
94
98
  table = extract_service_tabulation_data(
95
- service_dict["changed_parameters_of_service"][0]) # type: ignore[arg-type]
96
- # add information about changed parameters
97
- table["Changed parameters"] = service_dict["changed_parameters_of_service"][1]
98
- print(tabulate(table, headers="keys", tablefmt="presto"))
99
+ service_dict["changed_parameters_of_service"][0], # type: ignore[arg-type]
100
+ additional_columns=[("Changed Parameters", changed_param_column)])
101
+ rich_print(table)
99
102
 
100
103
  for service_idx, service in enumerate(
101
104
  service_dict["changed_parameters_of_service"][0]): # type: ignore[arg-type]
102
105
  assert isinstance(service, DiagService)
103
- print()
104
- print(
106
+ rich_print()
107
+ rich_print(
105
108
  f" Detailed changes of diagnostic service [u cyan]{service.short_name}[/u cyan]"
106
109
  )
107
110
  # detailed_info in [infotext1, dict1, infotext2, dict2, ...]
@@ -110,10 +113,22 @@ class Display:
110
113
  service_dict["changed_parameters_of_service"])[2][service_idx]
111
114
  for detailed_info in info_list:
112
115
  if isinstance(detailed_info, str):
113
- print()
114
- print(detailed_info)
116
+ rich_print()
117
+ rich_print(detailed_info)
115
118
  elif isinstance(detailed_info, dict):
116
- print(tabulate(detailed_info, headers="keys", tablefmt="presto"))
119
+ table = RichTable(
120
+ show_header=True,
121
+ header_style="bold cyan",
122
+ border_style="blue",
123
+ show_lines=True)
124
+ for header in detailed_info:
125
+ table.add_column(header)
126
+ rows = zip(*detailed_info.values())
127
+ for row in rows:
128
+ table.add_row(*map(str, row))
129
+
130
+ rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
131
+ rich_print()
117
132
  if self.param_detailed:
118
133
  # print all parameter details of diagnostic service
119
134
  print_service_parameters(service, allow_unknown_bit_lengths=True)
@@ -124,16 +139,18 @@ class Display:
124
139
  # diagnostic layers
125
140
  if changes_variants["new_diagnostic_layers"] or changes_variants[
126
141
  "deleted_diagnostic_layers"]:
127
- print()
128
- print("[bright_blue]Changed diagnostic layers[/bright_blue]: ")
129
- print(" New diagnostic layers: ")
142
+ rich_print()
143
+ rich_print("[bright_blue]Changed diagnostic layers[/bright_blue]: ")
144
+ rich_print(" New diagnostic layers: ")
130
145
  for variant in changes_variants["new_diagnostic_layers"]:
131
146
  assert isinstance(variant, DiagLayer)
132
- print(f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
133
- print(" Deleted diagnostic layers: ")
147
+ rich_print(
148
+ f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
149
+ rich_print(" Deleted diagnostic layers: ")
134
150
  for variant in changes_variants["deleted_diagnostic_layers"]:
135
151
  assert isinstance(variant, DiagLayer)
136
- print(f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
152
+ rich_print(
153
+ f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
137
154
 
138
155
  # diagnostic services
139
156
  for _, value in changes_variants.items():
@@ -275,8 +292,8 @@ class Comparison(Display):
275
292
  if res1_idx == res2_idx:
276
293
  # find changed request parameter properties
277
294
  table = self.compare_parameters(param1, param2)
278
- infotext = (f" Properties of request parameter '{param2.short_name}'"
279
- f" have changed\n")
295
+ infotext = (f" Properties of request parameter '{param2.short_name}' "
296
+ f"that have changed:\n")
280
297
  # array index starts with 0 -> param[0] is 1. service parameter
281
298
 
282
299
  if table["Property"]:
@@ -311,8 +328,8 @@ class Comparison(Display):
311
328
  # find changed positive response parameter properties
312
329
  table = self.compare_parameters(param1, param2)
313
330
  infotext = (
314
- f" Properties of positive response parameter '{param2.short_name}'"
315
- f"have changed\n")
331
+ f" Properties of positive response parameter '{param2.short_name}' that "
332
+ f"have changed:\n")
316
333
  # array index starts with 0 -> param[0] is first service parameter
317
334
 
318
335
  if table["Property"]:
@@ -354,7 +371,7 @@ class Comparison(Display):
354
371
  if param1_idx == param2_idx:
355
372
  # find changed negative response parameter properties
356
373
  table = self.compare_parameters(param1, param2)
357
- infotext = f" Properties of response parameter '{param2.short_name}' have changed\n"
374
+ infotext = f" Properties of response parameter '{param2.short_name}' that have changed:\n"
358
375
  # array index starts with 0 -> param[0] is 1. service parameter
359
376
 
360
377
  if table["Property"]:
@@ -622,7 +639,7 @@ def run(args: argparse.Namespace) -> None:
622
639
 
623
640
  for name in args.variants:
624
641
  if name not in task.diagnostic_layer_names:
625
- print(f"The variant '{name}' could not be found!")
642
+ rich_print(f"The variant '{name}' could not be found!")
626
643
  return
627
644
 
628
645
  task.db_indicator_1 = 0
@@ -632,19 +649,20 @@ def run(args: argparse.Namespace) -> None:
632
649
  break
633
650
  task.db_indicator_2 = db_idx + 1
634
651
 
635
- print()
636
- print(f"Changes in file '{os.path.basename(db_names[0])}'")
637
- print(f" (compared to '{os.path.basename(db_names[db_idx + 1])}')")
652
+ rich_print()
653
+ rich_print(f"Changes in file '{os.path.basename(db_names[0])}'")
654
+ rich_print(f" (compared to '{os.path.basename(db_names[db_idx + 1])}')")
638
655
 
639
- print()
640
- print(f"Overview of diagnostic layers (for {os.path.basename(db_names[0])})")
656
+ rich_print()
657
+ rich_print(f"Overview of diagnostic layers (for {os.path.basename(db_names[0])})")
641
658
  print_dl_metrics([
642
659
  variant for variant in task.databases[0].diag_layers
643
660
  if variant.short_name in task.diagnostic_layer_names
644
661
  ])
645
662
 
646
- print()
647
- print(f"Overview of diagnostic layers (for {os.path.basename(db_names[db_idx+1])})")
663
+ rich_print()
664
+ rich_print(
665
+ f"Overview of diagnostic layers (for {os.path.basename(db_names[db_idx+1])})")
648
666
  print_dl_metrics([
649
667
  variant for variant in task.databases[db_idx + 1].diag_layers
650
668
  if variant.short_name in task.diagnostic_layer_names
@@ -673,16 +691,17 @@ def run(args: argparse.Namespace) -> None:
673
691
  break
674
692
  task.db_indicator_2 = db_idx + 1
675
693
 
676
- print()
677
- print(f"Changes in file '{os.path.basename(db_names[0])}")
678
- print(f" (compared to '{os.path.basename(db_names[db_idx + 1])}')")
694
+ rich_print()
695
+ rich_print(f"Changes in file '{os.path.basename(db_names[0])}")
696
+ rich_print(f" (compared to '{os.path.basename(db_names[db_idx + 1])}')")
679
697
 
680
- print()
681
- print(f"Overview of diagnostic layers (for {os.path.basename(db_names[0])})")
698
+ rich_print()
699
+ rich_print(f"Overview of diagnostic layers (for {os.path.basename(db_names[0])})")
682
700
  print_dl_metrics(list(task.databases[0].diag_layers))
683
701
 
684
- print()
685
- print(f"Overview of diagnostic layers (for {os.path.basename(db_names[db_idx+1])})")
702
+ rich_print()
703
+ rich_print(
704
+ f"Overview of diagnostic layers (for {os.path.basename(db_names[db_idx+1])})")
686
705
  print_dl_metrics(list(task.databases[db_idx + 1].diag_layers))
687
706
 
688
707
  task.print_database_changes(
@@ -704,20 +723,20 @@ def run(args: argparse.Namespace) -> None:
704
723
 
705
724
  for name in args.variants:
706
725
  if name not in task.diagnostic_layer_names:
707
- print(f"The variant '{name}' could not be found!")
726
+ rich_print(f"The variant '{name}' could not be found!")
708
727
  return
709
728
 
710
- print()
711
- print(f"Overview of diagnostic layers: ")
729
+ rich_print()
730
+ rich_print(f"Overview of diagnostic layers: ")
712
731
  print_dl_metrics(task.diagnostic_layers)
713
732
 
714
733
  for db_idx, dl in enumerate(task.diagnostic_layers):
715
734
  if db_idx + 1 >= len(task.diagnostic_layers):
716
735
  break
717
736
 
718
- print()
719
- print(f"Changes in diagnostic layer '{dl.short_name}' ({dl.variant_type.value})")
720
- print(
737
+ rich_print()
738
+ rich_print(f"Changes in diagnostic layer '{dl.short_name}' ({dl.variant_type.value})")
739
+ rich_print(
721
740
  f" (compared to '{task.diagnostic_layers[db_idx + 1].short_name}' ({task.diagnostic_layers[db_idx + 1].variant_type.value}))"
722
741
  )
723
742
  task.print_dl_changes(
@@ -725,4 +744,4 @@ def run(args: argparse.Namespace) -> None:
725
744
 
726
745
  else:
727
746
  # no databases & no variants specified
728
- print("Please specify either a database or variant for a comparison")
747
+ rich_print("Please specify either a database or variant for a comparison")