odxtools 5.3.1__py3-none-any.whl → 6.0.1__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 (88) hide show
  1. odxtools/__init__.py +1 -1
  2. odxtools/basicstructure.py +76 -91
  3. odxtools/cli/_parser_utils.py +12 -9
  4. odxtools/cli/_print_utils.py +7 -7
  5. odxtools/cli/browse.py +94 -73
  6. odxtools/cli/find.py +42 -59
  7. odxtools/cli/list.py +21 -17
  8. odxtools/cli/snoop.py +19 -18
  9. odxtools/communicationparameterref.py +6 -3
  10. odxtools/companydocinfo.py +2 -2
  11. odxtools/companyrevisioninfo.py +1 -1
  12. odxtools/comparamsubset.py +6 -6
  13. odxtools/complexcomparam.py +1 -1
  14. odxtools/compumethods/compumethod.py +6 -9
  15. odxtools/compumethods/createanycompumethod.py +11 -9
  16. odxtools/compumethods/identicalcompumethod.py +5 -4
  17. odxtools/compumethods/limit.py +9 -9
  18. odxtools/compumethods/linearcompumethod.py +25 -17
  19. odxtools/compumethods/scalelinearcompumethod.py +6 -5
  20. odxtools/compumethods/tabintpcompumethod.py +30 -9
  21. odxtools/compumethods/texttablecompumethod.py +22 -24
  22. odxtools/database.py +5 -5
  23. odxtools/dataobjectproperty.py +10 -23
  24. odxtools/decodestate.py +1 -1
  25. odxtools/determinenumberofitems.py +37 -8
  26. odxtools/diagcodedtype.py +14 -9
  27. odxtools/diagdatadictionaryspec.py +60 -37
  28. odxtools/diaglayer.py +30 -21
  29. odxtools/diaglayercontainer.py +40 -40
  30. odxtools/diaglayerraw.py +92 -63
  31. odxtools/diagnostictroublecode.py +2 -2
  32. odxtools/diagservice.py +53 -35
  33. odxtools/docrevision.py +1 -1
  34. odxtools/dopbase.py +14 -3
  35. odxtools/dtcdop.py +15 -9
  36. odxtools/dynamiclengthfield.py +6 -4
  37. odxtools/endofpdufield.py +22 -23
  38. odxtools/environmentdata.py +2 -5
  39. odxtools/environmentdatadescription.py +6 -4
  40. odxtools/field.py +3 -8
  41. odxtools/isotp_state_machine.py +52 -38
  42. odxtools/leadinglengthinfotype.py +9 -7
  43. odxtools/load_file.py +2 -1
  44. odxtools/load_odx_d_file.py +2 -5
  45. odxtools/load_pdx_file.py +2 -6
  46. odxtools/message.py +11 -3
  47. odxtools/minmaxlengthtype.py +107 -78
  48. odxtools/modification.py +2 -2
  49. odxtools/multiplexer.py +23 -21
  50. odxtools/multiplexerswitchkey.py +37 -8
  51. odxtools/nameditemlist.py +59 -58
  52. odxtools/odxlink.py +4 -2
  53. odxtools/odxtypes.py +4 -3
  54. odxtools/parameterinfo.py +6 -6
  55. odxtools/parameters/codedconstparameter.py +15 -25
  56. odxtools/parameters/createanyparameter.py +1 -1
  57. odxtools/parameters/dynamicparameter.py +6 -5
  58. odxtools/parameters/lengthkeyparameter.py +2 -1
  59. odxtools/parameters/matchingrequestparameter.py +8 -11
  60. odxtools/parameters/nrcconstparameter.py +11 -21
  61. odxtools/parameters/parameter.py +4 -18
  62. odxtools/parameters/parameterwithdop.py +14 -29
  63. odxtools/parameters/physicalconstantparameter.py +7 -9
  64. odxtools/parameters/reservedparameter.py +17 -38
  65. odxtools/parameters/systemparameter.py +6 -5
  66. odxtools/parameters/tableentryparameter.py +6 -5
  67. odxtools/parameters/tablekeyparameter.py +8 -15
  68. odxtools/parameters/tablestructparameter.py +11 -12
  69. odxtools/parameters/valueparameter.py +9 -24
  70. odxtools/paramlengthinfotype.py +11 -9
  71. odxtools/physicaldimension.py +1 -1
  72. odxtools/physicaltype.py +2 -2
  73. odxtools/response.py +7 -3
  74. odxtools/singleecujob.py +48 -22
  75. odxtools/standardlengthtype.py +11 -6
  76. odxtools/uds.py +1 -1
  77. odxtools/unit.py +5 -5
  78. odxtools/unitgroup.py +1 -1
  79. odxtools/unitspec.py +2 -2
  80. odxtools/version.py +13 -3
  81. odxtools/write_pdx_file.py +7 -4
  82. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
  83. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
  84. odxtools/positioneddataobjectproperty.py +0 -74
  85. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
  86. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
  87. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
  88. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/top_level.txt +0 -0
odxtools/diaglayerraw.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from copy import copy
3
3
  from dataclasses import dataclass
4
- from itertools import chain
5
4
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
6
5
  from xml.etree import ElementTree
7
6
 
@@ -202,76 +201,106 @@ class DiagLayerRaw(IdentifiableElement):
202
201
  """Construct a mapping from IDs to all objects that are contained in this diagnostic layer."""
203
202
  odxlinks = {self.odx_id: self}
204
203
 
205
- for obj in chain(
206
- [self.admin_data],
207
- self.company_datas,
208
- self.functional_classes,
209
- [self.diag_data_dictionary_spec],
210
- self.diag_comms,
211
- self.requests,
212
- self.positive_responses,
213
- self.negative_responses,
214
- self.global_negative_responses,
215
- self.state_charts,
216
- self.additional_audiences,
217
- self.sdgs,
218
- self.parent_refs,
219
- self.communication_parameters,
220
- ):
221
- # the diag_comms may contain references.
222
- if obj is None or isinstance(obj, OdxLinkRef):
204
+ if self.admin_data is not None:
205
+ odxlinks.update(self.admin_data._build_odxlinks())
206
+ if self.diag_data_dictionary_spec is not None:
207
+ odxlinks.update(self.diag_data_dictionary_spec._build_odxlinks())
208
+
209
+ for company_data in self.company_datas:
210
+ odxlinks.update(company_data._build_odxlinks())
211
+ for functional_class in self.functional_classes:
212
+ odxlinks.update(functional_class._build_odxlinks())
213
+ for diag_comm in self.diag_comms:
214
+ if isinstance(diag_comm, OdxLinkRef):
223
215
  continue
224
-
225
- odxlinks.update(obj._build_odxlinks())
216
+ odxlinks.update(diag_comm._build_odxlinks())
217
+ for request in self.requests:
218
+ odxlinks.update(request._build_odxlinks())
219
+ for positive_response in self.positive_responses:
220
+ odxlinks.update(positive_response._build_odxlinks())
221
+ for negative_response in self.negative_responses:
222
+ odxlinks.update(negative_response._build_odxlinks())
223
+ for global_negative_response in self.global_negative_responses:
224
+ odxlinks.update(global_negative_response._build_odxlinks())
225
+ for state_chart in self.state_charts:
226
+ odxlinks.update(state_chart._build_odxlinks())
227
+ for additional_audience in self.additional_audiences:
228
+ odxlinks.update(additional_audience._build_odxlinks())
229
+ for sdg in self.sdgs:
230
+ odxlinks.update(sdg._build_odxlinks())
231
+ for parent_ref in self.parent_refs:
232
+ odxlinks.update(parent_ref._build_odxlinks())
233
+ for communication_parameter in self.communication_parameters:
234
+ odxlinks.update(communication_parameter._build_odxlinks())
226
235
 
227
236
  return odxlinks
228
237
 
229
238
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
230
239
  """Recursively resolve all references."""
231
240
 
232
- # do reference resolution for the objects that do not use
233
- # short name references
234
- for obj in chain(
235
- [self.admin_data],
236
- self.company_datas,
237
- self.functional_classes,
238
- [self.diag_data_dictionary_spec],
239
- self.diag_comms,
240
- self.requests,
241
- self.positive_responses,
242
- self.negative_responses,
243
- self.global_negative_responses,
244
- self.state_charts,
245
- self.additional_audiences,
246
- self.sdgs,
247
- self.parent_refs,
248
- self.communication_parameters,
249
- ):
250
- if obj is None or isinstance(obj, OdxLinkRef):
241
+ # do ODXLINK reference resolution
242
+ if self.admin_data is not None:
243
+ self.admin_data._resolve_odxlinks(odxlinks)
244
+ if self.diag_data_dictionary_spec is not None:
245
+ self.diag_data_dictionary_spec._resolve_odxlinks(odxlinks)
246
+
247
+ for company_data in self.company_datas:
248
+ company_data._resolve_odxlinks(odxlinks)
249
+ for functional_class in self.functional_classes:
250
+ functional_class._resolve_odxlinks(odxlinks)
251
+ for diag_comm in self.diag_comms:
252
+ if isinstance(diag_comm, OdxLinkRef):
251
253
  continue
252
-
253
- obj._resolve_odxlinks(odxlinks)
254
+ diag_comm._resolve_odxlinks(odxlinks)
255
+ for request in self.requests:
256
+ request._resolve_odxlinks(odxlinks)
257
+ for positive_response in self.positive_responses:
258
+ positive_response._resolve_odxlinks(odxlinks)
259
+ for negative_response in self.negative_responses:
260
+ negative_response._resolve_odxlinks(odxlinks)
261
+ for global_negative_response in self.global_negative_responses:
262
+ global_negative_response._resolve_odxlinks(odxlinks)
263
+ for state_chart in self.state_charts:
264
+ state_chart._resolve_odxlinks(odxlinks)
265
+ for additional_audience in self.additional_audiences:
266
+ additional_audience._resolve_odxlinks(odxlinks)
267
+ for sdg in self.sdgs:
268
+ sdg._resolve_odxlinks(odxlinks)
269
+ for parent_ref in self.parent_refs:
270
+ parent_ref._resolve_odxlinks(odxlinks)
271
+ for communication_parameter in self.communication_parameters:
272
+ communication_parameter._resolve_odxlinks(odxlinks)
254
273
 
255
274
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
256
- # do reference resolution for the objects that may use short name
257
- # references
258
- for obj in chain(
259
- [self.admin_data],
260
- self.company_datas,
261
- self.functional_classes,
262
- [self.diag_data_dictionary_spec],
263
- self.diag_comms,
264
- self.requests,
265
- self.positive_responses,
266
- self.negative_responses,
267
- self.global_negative_responses,
268
- self.state_charts,
269
- self.additional_audiences,
270
- self.sdgs,
271
- self.parent_refs,
272
- self.communication_parameters,
273
- ):
274
- if obj is None or isinstance(obj, OdxLinkRef):
275
+ # do short-name reference resolution
276
+ if self.admin_data is not None:
277
+ self.admin_data._resolve_snrefs(diag_layer)
278
+ if self.diag_data_dictionary_spec is not None:
279
+ self.diag_data_dictionary_spec._resolve_snrefs(diag_layer)
280
+
281
+ for company_data in self.company_datas:
282
+ company_data._resolve_snrefs(diag_layer)
283
+ for functional_classe in self.functional_classes:
284
+ functional_classe._resolve_snrefs(diag_layer)
285
+ for diag_comm in self.diag_comms:
286
+ if isinstance(diag_comm, OdxLinkRef):
275
287
  continue
276
-
277
- obj._resolve_snrefs(diag_layer)
288
+ diag_comm._resolve_snrefs(diag_layer)
289
+ for request in self.requests:
290
+ request._resolve_snrefs(diag_layer)
291
+ for positive_response in self.positive_responses:
292
+ positive_response._resolve_snrefs(diag_layer)
293
+ for negative_response in self.negative_responses:
294
+ negative_response._resolve_snrefs(diag_layer)
295
+ for global_negative_response in self.global_negative_responses:
296
+ global_negative_response._resolve_snrefs(diag_layer)
297
+ for state_chart in self.state_charts:
298
+ state_chart._resolve_snrefs(diag_layer)
299
+ for additional_audience in self.additional_audiences:
300
+ additional_audience._resolve_snrefs(diag_layer)
301
+ for sdg in self.sdgs:
302
+ sdg._resolve_snrefs(diag_layer)
303
+ for parent_ref in self.parent_refs:
304
+ parent_ref._resolve_snrefs(diag_layer)
305
+ for communication_parameter in self.communication_parameters:
306
+ communication_parameter._resolve_snrefs(diag_layer)
@@ -26,7 +26,7 @@ class DiagnosticTroubleCode(IdentifiableElement):
26
26
 
27
27
  @property
28
28
  def is_temporary(self) -> bool:
29
- return self.is_temporary_raw == True
29
+ return self.is_temporary_raw is True
30
30
 
31
31
  @staticmethod
32
32
  def from_et(et_element: ElementTree.Element,
@@ -69,6 +69,6 @@ class DiagnosticTroubleCode(IdentifiableElement):
69
69
  for sdg in self.sdgs:
70
70
  sdg._resolve_odxlinks(odxlinks)
71
71
 
72
- def _resolve_snrefs(self, diag_layer: "DiagLayer"):
72
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
73
73
  for sdg in self.sdgs:
74
74
  sdg._resolve_snrefs(diag_layer)
odxtools/diagservice.py CHANGED
@@ -8,18 +8,21 @@ from .audience import Audience
8
8
  from .createsdgs import create_sdgs_from_et
9
9
  from .element import IdentifiableElement
10
10
  from .exceptions import DecodeError, odxassert, odxrequire
11
+ from .functionalclass import FunctionalClass
11
12
  from .message import Message
12
13
  from .nameditemlist import NamedItemList
13
14
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
15
+ from .odxtypes import ParameterValue
14
16
  from .parameters.parameter import Parameter
15
17
  from .request import Request
16
18
  from .response import Response
17
19
  from .specialdatagroup import SpecialDataGroup
20
+ from .state import State
21
+ from .statetransition import StateTransition
18
22
  from .utils import dataclass_fields_asdict
19
23
 
20
24
  if TYPE_CHECKING:
21
25
  from .diaglayer import DiagLayer
22
- from .endofpdufield import EndOfPduField
23
26
 
24
27
 
25
28
  @dataclass
@@ -96,7 +99,7 @@ class DiagService(IdentifiableElement):
96
99
  return self._request
97
100
 
98
101
  @property
99
- def free_parameters(self) -> List[Union[Parameter, "EndOfPduField"]]: # type: ignore
102
+ def free_parameters(self) -> List[Parameter]:
100
103
  """Return the list of parameters which can be freely specified by
101
104
  the user when encoding the service's request.
102
105
  """
@@ -112,23 +115,23 @@ class DiagService(IdentifiableElement):
112
115
  self.request.print_free_parameters_info()
113
116
 
114
117
  @property
115
- def positive_responses(self) -> Optional[NamedItemList[Response]]:
118
+ def positive_responses(self) -> NamedItemList[Response]:
116
119
  return self._positive_responses
117
120
 
118
121
  @property
119
- def negative_responses(self) -> Optional[NamedItemList[Response]]:
122
+ def negative_responses(self) -> NamedItemList[Response]:
120
123
  return self._negative_responses
121
124
 
122
125
  @property
123
- def functional_classes(self):
126
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
124
127
  return self._functional_classes
125
128
 
126
129
  @property
127
- def pre_condition_states(self):
130
+ def pre_condition_states(self) -> NamedItemList[State]:
128
131
  return self._pre_condition_states
129
132
 
130
133
  @property
131
- def state_transitions(self):
134
+ def state_transitions(self) -> NamedItemList[StateTransition]:
132
135
  return self._state_transitions
133
136
 
134
137
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -149,11 +152,11 @@ class DiagService(IdentifiableElement):
149
152
  [odxlinks.resolve(x, Response) for x in self.neg_response_refs])
150
153
 
151
154
  self._functional_classes = NamedItemList(
152
- [odxlinks.resolve(fc_id) for fc_id in self.functional_class_refs])
155
+ [odxlinks.resolve(fc_ref, FunctionalClass) for fc_ref in self.functional_class_refs])
153
156
  self._pre_condition_states = NamedItemList(
154
- [odxlinks.resolve(st_id) for st_id in self.pre_condition_state_refs])
157
+ [odxlinks.resolve(st_ref, State) for st_ref in self.pre_condition_state_refs])
155
158
  self._state_transitions = NamedItemList(
156
- [odxlinks.resolve(stt_id) for stt_id in self.state_transition_refs])
159
+ [odxlinks.resolve(stt_ref, StateTransition) for stt_ref in self.state_transition_refs])
157
160
 
158
161
  if self.admin_data:
159
162
  self.admin_data._resolve_odxlinks(odxlinks)
@@ -174,31 +177,37 @@ class DiagService(IdentifiableElement):
174
177
  for sdg in self.sdgs:
175
178
  sdg._resolve_snrefs(diag_layer)
176
179
 
177
- def decode_message(self, message: bytes) -> Message:
178
-
179
- # Check if message is a request or positive or negative response
180
- interpretable_message_types = []
181
-
182
- if (self.request is None or self.positive_responses is None or
183
- self.negative_responses is None):
184
- raise RuntimeError("References couldn't be resolved or have not been resolved yet."
185
- " Try calling `database.resolve_odxlinks()`.")
180
+ def decode_message(self, raw_message: bytes) -> Message:
181
+ request_prefix = b''
182
+ candidate_coding_objects: List[Union[Request, Response]] = [
183
+ *self.positive_responses, *self.negative_responses
184
+ ]
185
+ if self.request is not None:
186
+ request_prefix = self.request.coded_const_prefix()
187
+ candidate_coding_objects.append(self.request)
186
188
 
187
- for message_type in [self.request, *self.positive_responses, *self.negative_responses]:
188
- prefix = message_type.coded_const_prefix(
189
- request_prefix=self.request.coded_const_prefix())
190
- if all(b == message[i] for (i, b) in enumerate(prefix)):
191
- interpretable_message_types.append(message_type)
189
+ coding_objects: List[Union[Request, Response]] = []
190
+ for candidate_coding_object in candidate_coding_objects:
191
+ prefix = candidate_coding_object.coded_const_prefix(request_prefix=request_prefix)
192
+ if len(raw_message) >= len(prefix) and prefix == raw_message[:len(prefix)]:
193
+ coding_objects.append(candidate_coding_object)
192
194
 
193
- if len(interpretable_message_types) != 1:
195
+ if len(coding_objects) != 1:
194
196
  raise DecodeError(
195
- f"The service {self.short_name} cannot decode the message {message.hex()}")
196
- message_type = interpretable_message_types[0]
197
- param_dict = message_type.decode(message)
197
+ f"The service {self.short_name} cannot decode the message {raw_message.hex()}")
198
+ coding_object = coding_objects[0]
199
+ param_dict = coding_object.decode(raw_message)
200
+ if not isinstance(param_dict, dict):
201
+ # if this happens, this is probably due to a bug in
202
+ # coding_object.decode()
203
+ raise RuntimeError(f"Expected a set of decoded parameters, got {type(param_dict)}")
198
204
  return Message(
199
- coded_message=message, service=self, structure=message_type, param_dict=param_dict)
205
+ coded_message=raw_message,
206
+ service=self,
207
+ coding_object=coding_object,
208
+ param_dict=param_dict)
200
209
 
201
- def encode_request(self, **params):
210
+ def encode_request(self, **params: ParameterValue) -> bytes:
202
211
  """
203
212
  Composes an UDS request as list of bytes for this service.
204
213
  Parameters:
@@ -209,6 +218,9 @@ class DiagService(IdentifiableElement):
209
218
  # make sure that all parameters which are required for
210
219
  # encoding are specified (parameters which have a default are
211
220
  # optional)
221
+ if self.request is None:
222
+ return b''
223
+
212
224
  missing_params = {x.short_name
213
225
  for x in self.request.required_parameters}.difference(params.keys())
214
226
  odxassert(not missing_params, f"The parameters {missing_params} are required but missing!")
@@ -219,21 +231,27 @@ class DiagService(IdentifiableElement):
219
231
  set(params.keys()).issubset(rq_all_param_names),
220
232
  f"Unknown parameters specified for encoding: {params.keys()}, "
221
233
  f"known parameters are: {rq_all_param_names}")
222
- return self.request.encode(**params)
234
+ return self.request.encode(coded_request=None, **params)
223
235
 
224
- def encode_positive_response(self, coded_request, response_index=0, **params):
236
+ def encode_positive_response(self,
237
+ coded_request: bytes,
238
+ response_index: int = 0,
239
+ **params: ParameterValue) -> bytes:
225
240
  # TODO: Should the user decide the positive response or what are the differences?
226
241
  return self.positive_responses[response_index].encode(coded_request, **params)
227
242
 
228
- def encode_negative_response(self, coded_request, response_index=0, **params):
243
+ def encode_negative_response(self,
244
+ coded_request: bytes,
245
+ response_index: int = 0,
246
+ **params: ParameterValue) -> bytes:
229
247
  return self.negative_responses[response_index].encode(coded_request, **params)
230
248
 
231
- def __call__(self, **params) -> bytes:
249
+ def __call__(self, **params: ParameterValue) -> bytes:
232
250
  """Encode a request."""
233
251
  return self.encode_request(**params)
234
252
 
235
253
  def __hash__(self) -> int:
236
254
  return hash(self.odx_id)
237
255
 
238
- def __eq__(self, o: object) -> bool:
256
+ def __eq__(self, o: Any) -> bool:
239
257
  return isinstance(o, DiagService) and self.odx_id == o.odx_id
odxtools/docrevision.py CHANGED
@@ -64,7 +64,7 @@ class DocRevision:
64
64
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
65
65
  return {}
66
66
 
67
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
67
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
68
68
  self._team_member: Optional[TeamMember] = None
69
69
  if self.team_member_ref is not None:
70
70
  self._team_member = odxlinks.resolve(self.team_member_ref, TeamMember)
odxtools/dopbase.py CHANGED
@@ -1,11 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
4
4
 
5
5
  from .decodestate import DecodeState
6
6
  from .element import IdentifiableElement
7
7
  from .encodestate import EncodeState
8
8
  from .odxlink import OdxLinkDatabase, OdxLinkId
9
+ from .odxtypes import ParameterValue
9
10
  from .specialdatagroup import SpecialDataGroup
10
11
 
11
12
  if TYPE_CHECKING:
@@ -48,15 +49,25 @@ class DopBase(IdentifiableElement):
48
49
  for sdg in self.sdgs:
49
50
  sdg._resolve_snrefs(diag_layer)
50
51
 
52
+ def get_static_bit_length(self) -> Optional[int]:
53
+ return None
54
+
55
+ def is_valid_physical_value(self, physical_value: ParameterValue) -> bool:
56
+ """Determine if a phyical value can be handled by the DOP
57
+ """
58
+ raise NotImplementedError
59
+
51
60
  @property
52
61
  def is_visible(self) -> bool:
53
62
  return self.is_visible_raw in (None, True)
54
63
 
55
- def convert_physical_to_bytes(self, physical_value, encode_state: EncodeState,
64
+ def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
56
65
  bit_position: int) -> bytes:
57
66
  """Convert the physical value into bytes."""
58
67
  raise NotImplementedError
59
68
 
60
- def convert_bytes_to_physical(self, decode_state: DecodeState, bit_position: int = 0):
69
+ def convert_bytes_to_physical(self,
70
+ decode_state: DecodeState,
71
+ bit_position: int = 0) -> Tuple[ParameterValue, int]:
61
72
  """Extract the bytes from the PDU and convert them to the physical value."""
62
73
  raise NotImplementedError
odxtools/dtcdop.py CHANGED
@@ -1,13 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # from dataclasses import dataclass, field
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Union, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union, cast
5
5
 
6
6
  from .dataobjectproperty import DataObjectProperty
7
+ from .decodestate import DecodeState
7
8
  from .diagnostictroublecode import DiagnosticTroubleCode
9
+ from .encodestate import EncodeState
8
10
  from .exceptions import EncodeError, odxassert
9
11
  from .nameditemlist import NamedItemList
10
12
  from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
13
+ from .odxtypes import ParameterValue
11
14
 
12
15
  if TYPE_CHECKING:
13
16
  from .diaglayer import DiagLayer
@@ -28,8 +31,10 @@ class DtcDop(DataObjectProperty):
28
31
  def linked_dtc_dops(self) -> NamedItemList["DtcDop"]:
29
32
  return self._linked_dtc_dops
30
33
 
31
- def convert_bytes_to_physical(self, decode_state, bit_position: int = 0):
32
- trouble_code, next_byte = super().convert_bytes_to_physical(
34
+ def convert_bytes_to_physical(self,
35
+ decode_state: DecodeState,
36
+ bit_position: int = 0) -> Tuple[ParameterValue, int]:
37
+ trouble_code, cursor_position = super().convert_bytes_to_physical(
33
38
  decode_state, bit_position=bit_position)
34
39
 
35
40
  dtcs = [x for x in self.dtcs if x.trouble_code == trouble_code]
@@ -38,7 +43,7 @@ class DtcDop(DataObjectProperty):
38
43
 
39
44
  if len(dtcs) == 1:
40
45
  # we found exactly one described DTC
41
- return dtcs[0], next_byte
46
+ return dtcs[0], cursor_position
42
47
 
43
48
  # the DTC was not specified. This probably means that the
44
49
  # diagnostic description file is incomplete. We do not bail
@@ -57,9 +62,10 @@ class DtcDop(DataObjectProperty):
57
62
  sdgs=[],
58
63
  )
59
64
 
60
- return dtc, next_byte
65
+ return dtc, cursor_position
61
66
 
62
- def convert_physical_to_bytes(self, physical_value, encode_state, bit_position):
67
+ def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
68
+ bit_position: int) -> bytes:
63
69
  if isinstance(physical_value, DiagnosticTroubleCode):
64
70
  trouble_code = physical_value.trouble_code
65
71
  elif isinstance(physical_value, int):
@@ -71,7 +77,7 @@ class DtcDop(DataObjectProperty):
71
77
  trouble_code = dtc.trouble_code
72
78
  else:
73
79
  raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
74
- f" DiagnosticTroubleCode but got {physical_value}.")
80
+ f" DiagnosticTroubleCode but got {physical_value!r}.")
75
81
 
76
82
  return super().convert_physical_to_bytes(trouble_code, encode_state, bit_position)
77
83
 
@@ -84,7 +90,7 @@ class DtcDop(DataObjectProperty):
84
90
 
85
91
  return odxlinks
86
92
 
87
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
93
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
88
94
  super()._resolve_odxlinks(odxlinks)
89
95
 
90
96
  self._dtcs = NamedItemList[DiagnosticTroubleCode]()
@@ -99,7 +105,7 @@ class DtcDop(DataObjectProperty):
99
105
  linked_dtc_dops = [odxlinks.resolve(x, DtcDop) for x in self.linked_dtc_dop_refs]
100
106
  self._linked_dtc_dops = NamedItemList(linked_dtc_dops)
101
107
 
102
- def _resolve_snrefs(self, diag_layer: "DiagLayer"):
108
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
103
109
  super()._resolve_snrefs(diag_layer)
104
110
 
105
111
  for dtc_proxy in self.dtcs_raw:
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Tuple
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .decodestate import DecodeState
@@ -9,7 +9,7 @@ from .encodestate import EncodeState
9
9
  from .exceptions import odxrequire
10
10
  from .field import Field
11
11
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
- from .odxtypes import ParameterValueDict
12
+ from .odxtypes import ParameterValue
13
13
  from .utils import dataclass_fields_asdict
14
14
 
15
15
  if TYPE_CHECKING:
@@ -49,11 +49,13 @@ class DynamicLengthField(Field):
49
49
 
50
50
  def convert_physical_to_bytes(
51
51
  self,
52
- physical_value: List[ParameterValueDict],
52
+ physical_value: ParameterValue,
53
53
  encode_state: EncodeState,
54
54
  bit_position: int = 0,
55
55
  ) -> bytes:
56
56
  raise NotImplementedError()
57
57
 
58
- def convert_bytes_to_physical(self, decode_state: DecodeState, bit_position: int = 0):
58
+ def convert_bytes_to_physical(self,
59
+ decode_state: DecodeState,
60
+ bit_position: int = 0) -> Tuple[ParameterValue, int]:
59
61
  raise NotImplementedError()
odxtools/endofpdufield.py CHANGED
@@ -1,15 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from copy import copy
3
3
  from dataclasses import dataclass
4
- from typing import List, Optional
4
+ from typing import List, Optional, Tuple
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .decodestate import DecodeState
8
8
  from .encodestate import EncodeState
9
- from .exceptions import odxassert
9
+ from .exceptions import EncodeError, odxassert, odxraise
10
10
  from .field import Field
11
11
  from .odxlink import OdxDocFragment
12
- from .odxtypes import ParameterValueDict
12
+ from .odxtypes import ParameterValue
13
13
  from .utils import dataclass_fields_asdict
14
14
 
15
15
 
@@ -42,41 +42,40 @@ class EndOfPduField(Field):
42
42
 
43
43
  def convert_physical_to_bytes(
44
44
  self,
45
- physical_value: ParameterValueDict,
45
+ physical_values: ParameterValue,
46
46
  encode_state: EncodeState,
47
47
  bit_position: int = 0,
48
48
  ) -> bytes:
49
49
  odxassert(
50
50
  bit_position == 0, "End of PDU field must be byte aligned. "
51
- "Is there an error in reading the .odx?")
52
- if isinstance(physical_value, dict):
53
- # If the value is given as a dict, the End of PDU field behaves like the underlying structure.
54
- return self.structure.convert_physical_to_bytes(physical_value, encode_state)
55
- else:
56
- odxassert(
57
- isinstance(physical_value, list),
58
- "The value of an End-of-PDU-field must be a list or a dict.")
59
- # If the value is given as a list, each list element is a encoded seperately using the structure.
60
- coded_rpc = bytes()
61
- for value in physical_value:
62
- coded_rpc += self.structure.convert_physical_to_bytes(value, encode_state)
63
- return coded_rpc
51
+ "Is there an error in reading the .odx?", EncodeError)
52
+ if not isinstance(physical_values, list):
53
+ odxraise(
54
+ f"Expected a list of values for structure {self.short_name}, "
55
+ f"got {type(physical_values)}", EncodeError)
56
+
57
+ coded_message = b''
58
+ for value in physical_values:
59
+ coded_message += self.structure.convert_physical_to_bytes(value, encode_state)
60
+ return coded_message
64
61
 
65
- def convert_bytes_to_physical(self, decode_state: DecodeState, bit_position: int = 0):
62
+ def convert_bytes_to_physical(self,
63
+ decode_state: DecodeState,
64
+ bit_position: int = 0) -> Tuple[ParameterValue, int]:
66
65
  decode_state = copy(decode_state)
67
- next_byte_position = decode_state.next_byte_position
66
+ cursor_position = decode_state.cursor_position
68
67
  byte_code = decode_state.coded_message
69
68
 
70
69
  value = []
71
- while len(byte_code) > next_byte_position:
70
+ while len(byte_code) > cursor_position:
72
71
  # ATTENTION: the ODX specification is very misleading
73
72
  # here: it says that the item is repeated until the end of
74
73
  # the PDU, but it means that DOP of the items that are
75
74
  # repeated are identical, not their values
76
- new_value, next_byte_position = self.structure.convert_bytes_to_physical(
75
+ new_value, cursor_position = self.structure.convert_bytes_to_physical(
77
76
  decode_state, bit_position=bit_position)
78
77
  # Update next byte_position
79
- decode_state.next_byte_position = next_byte_position
78
+ decode_state.cursor_position = cursor_position
80
79
  value.append(new_value)
81
80
 
82
- return value, next_byte_position
81
+ return value, cursor_position
@@ -7,6 +7,7 @@ from .basicstructure import BasicStructure
7
7
  from .createsdgs import create_sdgs_from_et
8
8
  from .element import IdentifiableElement
9
9
  from .exceptions import odxrequire
10
+ from .nameditemlist import NamedItemList
10
11
  from .odxlink import OdxDocFragment
11
12
  from .odxtypes import odxstr_to_bool
12
13
  from .parameters.createanyparameter import create_any_parameter_from_et
@@ -19,10 +20,6 @@ class EnvironmentData(BasicStructure):
19
20
 
20
21
  dtc_values: List[int]
21
22
 
22
- def __init__(self, *, dtc_values: List[int], **kwargs):
23
- super().__init__(**kwargs)
24
- self.dtc_values = dtc_values
25
-
26
23
  @staticmethod
27
24
  def from_et(et_element: ElementTree.Element,
28
25
  doc_frags: List[OdxDocFragment]) -> "EnvironmentData":
@@ -44,7 +41,7 @@ class EnvironmentData(BasicStructure):
44
41
  return EnvironmentData(
45
42
  sdgs=sdgs,
46
43
  is_visible_raw=is_visible_raw,
47
- parameters=parameters,
44
+ parameters=NamedItemList(parameters),
48
45
  byte_size=byte_size,
49
46
  dtc_values=dtc_values,
50
47
  **kwargs)