odxtools 10.2.0__py3-none-any.whl → 10.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 (68) hide show
  1. odxtools/cli/browse.py +4 -2
  2. odxtools/cli/compare.py +3 -3
  3. odxtools/compositecodec.py +1 -1
  4. odxtools/configdata.py +70 -0
  5. odxtools/configdatadictionaryspec.py +57 -0
  6. odxtools/configiditem.py +18 -0
  7. odxtools/configitem.py +85 -0
  8. odxtools/configrecord.py +146 -0
  9. odxtools/database.py +21 -0
  10. odxtools/dataiditem.py +18 -0
  11. odxtools/datarecord.py +132 -0
  12. odxtools/decodestate.py +1 -1
  13. odxtools/diagcommdataconnector.py +61 -0
  14. odxtools/diaglayers/diaglayer.py +39 -17
  15. odxtools/diaglayers/ecuvariant.py +12 -19
  16. odxtools/diaglayers/hierarchyelement.py +5 -5
  17. odxtools/diaglayers/protocol.py +14 -0
  18. odxtools/diagservice.py +10 -10
  19. odxtools/ecuconfig.py +89 -0
  20. odxtools/encryptcompressmethod.py +16 -3
  21. odxtools/externflashdata.py +21 -2
  22. odxtools/flashdata.py +40 -2
  23. odxtools/identvalue.py +16 -3
  24. odxtools/internflashdata.py +4 -0
  25. odxtools/isotp_state_machine.py +11 -11
  26. odxtools/itemvalue.py +77 -0
  27. odxtools/nameditemlist.py +2 -2
  28. odxtools/odxlink.py +4 -2
  29. odxtools/optionitem.py +79 -0
  30. odxtools/parameters/codedconstparameter.py +2 -3
  31. odxtools/readdiagcommconnector.py +79 -0
  32. odxtools/readparamvalue.py +52 -0
  33. odxtools/request.py +3 -3
  34. odxtools/response.py +8 -4
  35. odxtools/statemachine.py +3 -2
  36. odxtools/subcomponentparamconnector.py +1 -1
  37. odxtools/systemitem.py +23 -0
  38. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -0
  39. odxtools/templates/ecu_config.odx-e.xml.jinja2 +38 -0
  40. odxtools/templates/flash.odx-f.xml.jinja2 +2 -2
  41. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  42. odxtools/templates/macros/printChecksum.xml.jinja2 +1 -1
  43. odxtools/templates/macros/printCompuMethod.xml.jinja2 +2 -2
  44. odxtools/templates/macros/printConfigData.xml.jinja2 +39 -0
  45. odxtools/templates/macros/printConfigDataDictionarySpec.xml.jinja2 +22 -0
  46. odxtools/templates/macros/printConfigItems.xml.jinja2 +66 -0
  47. odxtools/templates/macros/printConfigRecord.xml.jinja2 +73 -0
  48. odxtools/templates/macros/printDOP.xml.jinja2 +5 -6
  49. odxtools/templates/macros/printDataRecord.xml.jinja2 +35 -0
  50. odxtools/templates/macros/printDiagCommDataConnector.xml.jinja2 +66 -0
  51. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +1 -1
  52. odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
  53. odxtools/templates/macros/printItemValue.xml.jinja2 +31 -0
  54. odxtools/templates/macros/printOwnIdent.xml.jinja2 +1 -1
  55. odxtools/templates/macros/printSecurity.xml.jinja2 +4 -4
  56. odxtools/templates/macros/printSegment.xml.jinja2 +1 -1
  57. odxtools/validbasevariant.py +62 -0
  58. odxtools/validityfor.py +16 -3
  59. odxtools/variantmatcher.py +4 -4
  60. odxtools/version.py +2 -2
  61. odxtools/writediagcommconnector.py +77 -0
  62. odxtools/writepdxfile.py +15 -0
  63. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/METADATA +2 -1
  64. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/RECORD +68 -44
  65. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/WHEEL +1 -1
  66. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/entry_points.txt +0 -0
  67. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/licenses/LICENSE +0 -0
  68. {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxrequire
7
+ from .odxdoccontext import OdxDocContext
8
+ from .odxlink import OdxLinkDatabase, OdxLinkId
9
+ from .readdiagcommconnector import ReadDiagCommConnector
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import read_hex_binary
12
+ from .writediagcommconnector import WriteDiagCommConnector
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class DiagCommDataConnector:
17
+ uncompressed_size: int
18
+ source_start_address: int
19
+ read_diag_comm_connector: ReadDiagCommConnector | None = None
20
+ write_diag_comm_connector: WriteDiagCommConnector | None = None
21
+
22
+ @staticmethod
23
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DiagCommDataConnector":
24
+ uncompressed_size = int(odxrequire(et_element.findtext("UNCOMPRESSED-SIZE")))
25
+ source_start_address = odxrequire(read_hex_binary(et_element.find("SOURCE-START-ADDRESS")))
26
+
27
+ read_diag_comm_connector = None
28
+ if (rdcc_elem := et_element.find("READ-DIAG-COMM-CONNECTOR")) is not None:
29
+ read_diag_comm_connector = ReadDiagCommConnector.from_et(rdcc_elem, context)
30
+
31
+ write_diag_comm_connector = None
32
+ if (wdcc_elem := et_element.find("WRITE-DIAG-COMM-CONNECTOR")) is not None:
33
+ write_diag_comm_connector = WriteDiagCommConnector.from_et(wdcc_elem, context)
34
+
35
+ return DiagCommDataConnector(
36
+ uncompressed_size=uncompressed_size,
37
+ source_start_address=source_start_address,
38
+ read_diag_comm_connector=read_diag_comm_connector,
39
+ write_diag_comm_connector=write_diag_comm_connector)
40
+
41
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
42
+ odxlinks = {}
43
+
44
+ if self.read_diag_comm_connector is not None:
45
+ odxlinks.update(self.read_diag_comm_connector._build_odxlinks())
46
+ if self.write_diag_comm_connector is not None:
47
+ odxlinks.update(self.write_diag_comm_connector._build_odxlinks())
48
+
49
+ return odxlinks
50
+
51
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
52
+ if self.read_diag_comm_connector is not None:
53
+ self.read_diag_comm_connector._resolve_odxlinks(odxlinks)
54
+ if self.write_diag_comm_connector is not None:
55
+ self.write_diag_comm_connector._resolve_odxlinks(odxlinks)
56
+
57
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
58
+ if self.read_diag_comm_connector is not None:
59
+ self.read_diag_comm_connector._resolve_snrefs(context)
60
+ if self.write_diag_comm_connector is not None:
61
+ self.write_diag_comm_connector._resolve_snrefs(context)
@@ -7,6 +7,7 @@ from itertools import chain
7
7
  from typing import Any, Union, cast
8
8
  from xml.etree import ElementTree
9
9
 
10
+ from ..additionalaudience import AdditionalAudience
10
11
  from ..admindata import AdminData
11
12
  from ..companydata import CompanyData
12
13
  from ..description import Description
@@ -14,6 +15,7 @@ from ..diagcomm import DiagComm
14
15
  from ..diagdatadictionaryspec import DiagDataDictionarySpec
15
16
  from ..diagservice import DiagService
16
17
  from ..exceptions import DecodeError, odxassert, odxraise
18
+ from ..functionalclass import FunctionalClass
17
19
  from ..library import Library
18
20
  from ..message import Message
19
21
  from ..nameditemlist import NamedItemList, TNamed
@@ -26,6 +28,7 @@ from ..servicebinner import ServiceBinner
26
28
  from ..singleecujob import SingleEcuJob
27
29
  from ..snrefcontext import SnRefContext
28
30
  from ..specialdatagroup import SpecialDataGroup
31
+ from ..statechart import StateChart
29
32
  from ..subcomponent import SubComponent
30
33
  from ..unitgroup import UnitGroup
31
34
  from .diaglayerraw import DiagLayerRaw
@@ -223,12 +226,30 @@ class DiagLayer:
223
226
  def admin_data(self) -> AdminData | None:
224
227
  return self.diag_layer_raw.admin_data
225
228
 
229
+ @property
230
+ def company_datas(self) -> NamedItemList[CompanyData]:
231
+ return self.diag_layer_raw.company_datas
232
+
233
+ @property
234
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
235
+ return self.diag_layer_raw.functional_classes
236
+
237
+ @property
238
+ def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
239
+ """The DiagDataDictionarySpec applicable to this DiagLayer"""
240
+ return self._diag_data_dictionary_spec
241
+
242
+ @property
243
+ def diag_comms_raw(self) -> list[OdxLinkRef | DiagComm]:
244
+ return self.diag_layer_raw.diag_comms_raw
245
+
226
246
  @property
227
247
  def diag_comms(self) -> NamedItemList[DiagComm]:
228
248
  return self.diag_layer_raw.diag_comms
229
249
 
230
250
  @property
231
251
  def services(self) -> NamedItemList[DiagService]:
252
+ """This is an alias for `.diag_services`"""
232
253
  return self.diag_layer_raw.services
233
254
 
234
255
  @property
@@ -239,10 +260,6 @@ class DiagLayer:
239
260
  def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
240
261
  return self.diag_layer_raw.single_ecu_jobs
241
262
 
242
- @property
243
- def company_datas(self) -> NamedItemList[CompanyData]:
244
- return self.diag_layer_raw.company_datas
245
-
246
263
  @property
247
264
  def requests(self) -> NamedItemList[Request]:
248
265
  return self.diag_layer_raw.requests
@@ -264,21 +281,24 @@ class DiagLayer:
264
281
  return self.diag_layer_raw.import_refs
265
282
 
266
283
  @property
267
- def libraries(self) -> NamedItemList[Library]:
268
- return self.diag_layer_raw.libraries
284
+ def state_charts(self) -> NamedItemList[StateChart]:
285
+ return self.diag_layer_raw.state_charts
286
+
287
+ @property
288
+ def additional_audiences(self) -> NamedItemList[AdditionalAudience]:
289
+ return self.diag_layer_raw.additional_audiences
269
290
 
270
291
  @property
271
292
  def sub_components(self) -> NamedItemList[SubComponent]:
272
293
  return self.diag_layer_raw.sub_components
273
294
 
274
295
  @property
275
- def sdgs(self) -> list[SpecialDataGroup]:
276
- return self.diag_layer_raw.sdgs
296
+ def libraries(self) -> NamedItemList[Library]:
297
+ return self.diag_layer_raw.libraries
277
298
 
278
299
  @property
279
- def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
280
- """The DiagDataDictionarySpec applicable to this DiagLayer"""
281
- return self._diag_data_dictionary_spec
300
+ def sdgs(self) -> list[SpecialDataGroup]:
301
+ return self.diag_layer_raw.sdgs
282
302
 
283
303
  #####
284
304
  # </properties forwarded to the "raw" diag layer>
@@ -332,7 +352,7 @@ class DiagLayer:
332
352
  # corresponding request for `decode_response()`.)
333
353
  request_prefix = b''
334
354
  if s.request is not None:
335
- request_prefix = s.request.coded_const_prefix()
355
+ request_prefix = bytes(s.request.coded_const_prefix())
336
356
  prefixes = [request_prefix]
337
357
  gnrs = getattr(self, "global_negative_responses", [])
338
358
  prefixes += [
@@ -363,7 +383,7 @@ class DiagLayer:
363
383
  else:
364
384
  cast(list[DiagService], sub_tree[-1]).append(service)
365
385
 
366
- def _find_services_for_uds(self, message: bytes) -> list[DiagService]:
386
+ def _find_services_for_uds(self, message: bytes | bytearray) -> list[DiagService]:
367
387
  prefix_tree = self._prefix_tree
368
388
 
369
389
  # Find matching service(s) in prefix tree
@@ -378,7 +398,8 @@ class DiagLayer:
378
398
  possible_services += cast(list[DiagService], prefix_tree[-1])
379
399
  return possible_services
380
400
 
381
- def _decode(self, message: bytes, candidate_services: Iterable[DiagService]) -> list[Message]:
401
+ def _decode(self, message: bytes | bytearray,
402
+ candidate_services: Iterable[DiagService]) -> list[Message]:
382
403
  decoded_messages: list[Message] = []
383
404
 
384
405
  for service in candidate_services:
@@ -400,7 +421,7 @@ class DiagLayer:
400
421
 
401
422
  decoded_messages.append(
402
423
  Message(
403
- coded_message=message,
424
+ coded_message=bytes(message),
404
425
  service=service,
405
426
  coding_object=gnr,
406
427
  param_dict=decoded_gnr))
@@ -417,12 +438,13 @@ class DiagLayer:
417
438
 
418
439
  return decoded_messages
419
440
 
420
- def decode(self, message: bytes) -> list[Message]:
441
+ def decode(self, message: bytes | bytearray) -> list[Message]:
421
442
  candidate_services = self._find_services_for_uds(message)
422
443
 
423
444
  return self._decode(message, candidate_services)
424
445
 
425
- def decode_response(self, response: bytes, request: bytes) -> list[Message]:
446
+ def decode_response(self, response: bytes | bytearray,
447
+ request: bytes | bytearray) -> list[Message]:
426
448
  candidate_services = self._find_services_for_uds(request)
427
449
  if candidate_services is None:
428
450
  raise DecodeError(f"Couldn't find corresponding service for request {request.hex()}.")
@@ -33,6 +33,18 @@ class EcuVariant(HierarchyElement):
33
33
  def diag_variables_raw(self) -> list[DiagVariable | OdxLinkRef]:
34
34
  return self.ecu_variant_raw.diag_variables_raw
35
35
 
36
+ @property
37
+ def diag_variables(self) -> NamedItemList[DiagVariable]:
38
+ return self._diag_variables
39
+
40
+ @property
41
+ def variable_groups(self) -> NamedItemList[VariableGroup]:
42
+ return self._variable_groups
43
+
44
+ @property
45
+ def ecu_variant_patterns(self) -> list[EcuVariantPattern]:
46
+ return self.ecu_variant_raw.ecu_variant_patterns
47
+
36
48
  @property
37
49
  def dyn_defined_spec(self) -> DynDefinedSpec | None:
38
50
  return self.ecu_variant_raw.dyn_defined_spec
@@ -56,25 +68,6 @@ class EcuVariant(HierarchyElement):
56
68
 
57
69
  return None
58
70
 
59
- @property
60
- def ecu_variant_patterns(self) -> list[EcuVariantPattern]:
61
- return self.ecu_variant_raw.ecu_variant_patterns
62
-
63
- #######
64
- # <properties subject to value inheritance>
65
- #######
66
- @property
67
- def diag_variables(self) -> NamedItemList[DiagVariable]:
68
- return self._diag_variables
69
-
70
- @property
71
- def variable_groups(self) -> NamedItemList[VariableGroup]:
72
- return self._variable_groups
73
-
74
- #######
75
- # </properties subject to value inheritance>
76
- #######
77
-
78
71
  @staticmethod
79
72
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "EcuVariant":
80
73
  ecu_variant_raw = EcuVariantRaw.from_et(et_element, context)
@@ -438,6 +438,11 @@ class HierarchyElement(DiagLayer):
438
438
  #######
439
439
  # <properties subject to value inheritance>
440
440
  #######
441
+ @property
442
+ def functional_classes(self) -> NamedItemList[FunctionalClass]:
443
+ """All functional classes applicable to this DiagLayer"""
444
+ return self._functional_classes
445
+
441
446
  @property
442
447
  def diag_data_dictionary_spec(self) -> DiagDataDictionarySpec:
443
448
  return self._diag_data_dictionary_spec
@@ -480,11 +485,6 @@ class HierarchyElement(DiagLayer):
480
485
  """All global negative responses applicable to this DiagLayer"""
481
486
  return self._global_negative_responses
482
487
 
483
- @property
484
- def functional_classes(self) -> NamedItemList[FunctionalClass]:
485
- """All functional classes applicable to this DiagLayer"""
486
- return self._functional_classes
487
-
488
488
  @property
489
489
  def state_charts(self) -> NamedItemList[StateChart]:
490
490
  """All state charts applicable to this DiagLayer"""
@@ -7,6 +7,8 @@ from xml.etree import ElementTree
7
7
  from ..comparamspec import ComparamSpec
8
8
  from ..exceptions import odxassert
9
9
  from ..odxdoccontext import OdxDocContext
10
+ from ..odxlink import OdxLinkRef
11
+ from ..parentref import ParentRef
10
12
  from ..protstack import ProtStack
11
13
  from .hierarchyelement import HierarchyElement
12
14
  from .protocolraw import ProtocolRaw
@@ -24,14 +26,26 @@ class Protocol(HierarchyElement):
24
26
  def protocol_raw(self) -> ProtocolRaw:
25
27
  return cast(ProtocolRaw, self.diag_layer_raw)
26
28
 
29
+ @property
30
+ def comparam_spec_ref(self) -> OdxLinkRef:
31
+ return self.protocol_raw.comparam_spec_ref
32
+
27
33
  @property
28
34
  def comparam_spec(self) -> ComparamSpec:
29
35
  return self.protocol_raw.comparam_spec
30
36
 
37
+ @property
38
+ def prot_stack_snref(self) -> str | None:
39
+ return self.protocol_raw.prot_stack_snref
40
+
31
41
  @property
32
42
  def prot_stack(self) -> ProtStack | None:
33
43
  return self.protocol_raw.prot_stack
34
44
 
45
+ @property
46
+ def parent_refs(self) -> list[ParentRef]:
47
+ return self.protocol_raw.parent_refs
48
+
35
49
  @staticmethod
36
50
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Protocol":
37
51
  protocol_raw = ProtocolRaw.from_et(et_element, context)
odxtools/diagservice.py CHANGED
@@ -179,13 +179,13 @@ class DiagService(DiagComm):
179
179
 
180
180
  self.request.print_free_parameters_info()
181
181
 
182
- def decode_message(self, raw_message: bytes) -> Message:
182
+ def decode_message(self, raw_message: bytes | bytearray) -> Message:
183
183
  request_prefix = b''
184
184
  candidate_coding_objects: list[Request | Response] = [
185
185
  *self.positive_responses, *self.negative_responses
186
186
  ]
187
187
  if self.request is not None:
188
- request_prefix = self.request.coded_const_prefix()
188
+ request_prefix = bytes(self.request.coded_const_prefix())
189
189
  candidate_coding_objects.append(self.request)
190
190
 
191
191
  coding_objects: list[Request | Response] = []
@@ -199,7 +199,7 @@ class DiagService(DiagComm):
199
199
  try:
200
200
  result_list.append(
201
201
  Message(
202
- coded_message=raw_message,
202
+ coded_message=bytes(raw_message),
203
203
  service=self,
204
204
  coding_object=coding_object,
205
205
  param_dict=coding_object.decode(raw_message)))
@@ -221,7 +221,7 @@ class DiagService(DiagComm):
221
221
 
222
222
  return result_list[0]
223
223
 
224
- def encode_request(self, **kwargs: ParameterValue) -> bytes:
224
+ def encode_request(self, **kwargs: ParameterValue) -> bytearray:
225
225
  """Prepare an array of bytes ready to be send over the wire
226
226
  for the request of this service.
227
227
  """
@@ -229,7 +229,7 @@ class DiagService(DiagComm):
229
229
  # encoding are specified (parameters which have a default are
230
230
  # optional)
231
231
  if self.request is None:
232
- return b''
232
+ return bytearray()
233
233
 
234
234
  missing_params = {x.short_name
235
235
  for x in self.request.required_parameters}.difference(kwargs.keys())
@@ -245,18 +245,18 @@ class DiagService(DiagComm):
245
245
  return self.request.encode(**kwargs)
246
246
 
247
247
  def encode_positive_response(self,
248
- coded_request: bytes,
248
+ coded_request: bytes | bytearray,
249
249
  response_index: int = 0,
250
- **kwargs: ParameterValue) -> bytes:
250
+ **kwargs: ParameterValue) -> bytearray:
251
251
  # TODO: Should the user decide the positive response or what are the differences?
252
252
  return self.positive_responses[response_index].encode(coded_request, **kwargs)
253
253
 
254
254
  def encode_negative_response(self,
255
- coded_request: bytes,
255
+ coded_request: bytes | bytearray,
256
256
  response_index: int = 0,
257
- **kwargs: ParameterValue) -> bytes:
257
+ **kwargs: ParameterValue) -> bytearray:
258
258
  return self.negative_responses[response_index].encode(coded_request, **kwargs)
259
259
 
260
- def __call__(self, **kwargs: ParameterValue) -> bytes:
260
+ def __call__(self, **kwargs: ParameterValue) -> bytearray:
261
261
  """Encode a request."""
262
262
  return self.encode_request(**kwargs)
odxtools/ecuconfig.py ADDED
@@ -0,0 +1,89 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .additionalaudience import AdditionalAudience
7
+ from .configdata import ConfigData
8
+ from .configdatadictionaryspec import ConfigDataDictionarySpec
9
+ from .nameditemlist import NamedItemList
10
+ from .odxcategory import OdxCategory
11
+ from .odxdoccontext import OdxDocContext
12
+ from .odxlink import OdxLinkDatabase, OdxLinkId
13
+ from .snrefcontext import SnRefContext
14
+ from .utils import dataclass_fields_asdict
15
+
16
+ if TYPE_CHECKING:
17
+ from .database import Database
18
+
19
+
20
+ @dataclass(kw_only=True)
21
+ class EcuConfig(OdxCategory):
22
+ config_datas: NamedItemList[ConfigData]
23
+ additional_audiences: NamedItemList[AdditionalAudience]
24
+ config_data_dictionary_spec: ConfigDataDictionarySpec | None
25
+
26
+ @staticmethod
27
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "EcuConfig":
28
+
29
+ base_obj = OdxCategory.from_et(et_element, context)
30
+ kwargs = dataclass_fields_asdict(base_obj)
31
+
32
+ config_datas = NamedItemList([
33
+ ConfigData.from_et(el, context)
34
+ for el in et_element.iterfind("CONFIG-DATAS/CONFIG-DATA")
35
+ ])
36
+ additional_audiences = NamedItemList([
37
+ AdditionalAudience.from_et(el, context)
38
+ for el in et_element.iterfind("ADDITIONAL-AUDIENCES/ADDITIONAL-AUDIENCE")
39
+ ])
40
+ config_data_dictionary_spec = None
41
+ if (cdd_elem := et_element.find("CONFIG-DATA-DICTIONARY-SPEC")) is not None:
42
+ config_data_dictionary_spec = ConfigDataDictionarySpec.from_et(cdd_elem, context)
43
+
44
+ return EcuConfig(
45
+ config_datas=config_datas,
46
+ additional_audiences=additional_audiences,
47
+ config_data_dictionary_spec=config_data_dictionary_spec,
48
+ **kwargs)
49
+
50
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
51
+ odxlinks = super()._build_odxlinks()
52
+
53
+ for config_data in self.config_datas:
54
+ odxlinks.update(config_data._build_odxlinks())
55
+
56
+ for additional_audience in self.additional_audiences:
57
+ odxlinks.update(additional_audience._build_odxlinks())
58
+
59
+ if self.config_data_dictionary_spec is not None:
60
+ odxlinks.update(self.config_data_dictionary_spec._build_odxlinks())
61
+
62
+ return odxlinks
63
+
64
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
65
+ super()._resolve_odxlinks(odxlinks)
66
+
67
+ for config_data in self.config_datas:
68
+ config_data._resolve_odxlinks(odxlinks)
69
+
70
+ for additional_audiences in self.additional_audiences:
71
+ additional_audiences._resolve_odxlinks(odxlinks)
72
+
73
+ if self.config_data_dictionary_spec is not None:
74
+ self.config_data_dictionary_spec._resolve_odxlinks(odxlinks)
75
+
76
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
77
+ super()._finalize_init(database, odxlinks)
78
+
79
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
80
+ super()._resolve_snrefs(context)
81
+
82
+ for config_data in self.config_datas:
83
+ config_data._resolve_snrefs(context)
84
+
85
+ for additional_audiences in self.additional_audiences:
86
+ additional_audiences._resolve_snrefs(context)
87
+
88
+ if self.config_data_dictionary_spec is not None:
89
+ self.config_data_dictionary_spec._resolve_snrefs(context)
@@ -6,17 +6,26 @@ from .encryptcompressmethodtype import EncryptCompressMethodType
6
6
  from .exceptions import odxraise, odxrequire
7
7
  from .odxdoccontext import OdxDocContext
8
8
  from .odxlink import OdxLinkDatabase, OdxLinkId
9
+ from .odxtypes import AtomicOdxType, DataType
9
10
  from .snrefcontext import SnRefContext
10
11
 
11
12
 
12
13
  @dataclass(kw_only=True)
13
14
  class EncryptCompressMethod:
14
- value: str
15
+ value_raw: str
15
16
  value_type: EncryptCompressMethodType
16
17
 
18
+ @property
19
+ def value(self) -> AtomicOdxType:
20
+ return self._value
21
+
22
+ @property
23
+ def data_type(self) -> DataType:
24
+ return self._data_type
25
+
17
26
  @staticmethod
18
27
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "EncryptCompressMethod":
19
- value = et_element.text or ""
28
+ value_raw = et_element.text or ""
20
29
 
21
30
  value_type_str = odxrequire(et_element.attrib.get("TYPE"))
22
31
  try:
@@ -25,7 +34,11 @@ class EncryptCompressMethod:
25
34
  value_type = cast(EncryptCompressMethodType, None)
26
35
  odxraise(f"Encountered unknown addressing type '{value_type_str}'")
27
36
 
28
- return EncryptCompressMethod(value=value, value_type=value_type)
37
+ return EncryptCompressMethod(value_raw=value_raw, value_type=value_type)
38
+
39
+ def __post_init__(self) -> None:
40
+ self._data_type = DataType(self.value_type.value)
41
+ self._value = self._data_type.from_string(self.value_raw.strip())
29
42
 
30
43
  def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
31
44
  odxlinks: dict[OdxLinkId, Any] = {}
@@ -1,10 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any
3
+ from typing import IO, Any
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .datafile import Datafile
7
- from .exceptions import odxrequire
7
+ from .exceptions import odxraise, odxrequire
8
8
  from .flashdata import Flashdata
9
9
  from .odxdoccontext import OdxDocContext
10
10
  from .odxlink import OdxLinkDatabase, OdxLinkId
@@ -16,6 +16,21 @@ from .utils import dataclass_fields_asdict
16
16
  class ExternFlashdata(Flashdata):
17
17
  datafile: Datafile
18
18
 
19
+ @property
20
+ def data_str(self) -> str:
21
+ if self._database is None:
22
+ odxraise("No database object specified")
23
+ return ""
24
+
25
+ aux_file: IO[bytes] = odxrequire(self._database.auxiliary_files.get(self.datafile.value))
26
+ if aux_file is None:
27
+ return ""
28
+
29
+ result = aux_file.read().decode()
30
+ aux_file.seek(0)
31
+
32
+ return result
33
+
19
34
  @staticmethod
20
35
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ExternFlashdata":
21
36
  kwargs = dataclass_fields_asdict(Flashdata.from_et(et_element, context))
@@ -32,3 +47,7 @@ class ExternFlashdata(Flashdata):
32
47
 
33
48
  def _resolve_snrefs(self, context: SnRefContext) -> None:
34
49
  super()._resolve_snrefs(context)
50
+
51
+ # this is slightly hacky because we only remember the
52
+ # applicable ODX database and do not resolve any SNREFs here
53
+ self._database = context.database
odxtools/flashdata.py CHANGED
@@ -1,12 +1,16 @@
1
1
  # SPDX-License-Identifier: MIT
2
+ import re
2
3
  from dataclasses import dataclass
3
- from typing import Any
4
+ from typing import Any, cast
4
5
  from xml.etree import ElementTree
5
6
 
7
+ from bincopy import BinFile
8
+
6
9
  from .dataformat import Dataformat
10
+ from .dataformatselection import DataformatSelection
7
11
  from .element import IdentifiableElement
8
12
  from .encryptcompressmethod import EncryptCompressMethod
9
- from .exceptions import odxrequire
13
+ from .exceptions import odxassert, odxraise, odxrequire
10
14
  from .odxdoccontext import OdxDocContext
11
15
  from .odxlink import OdxLinkDatabase, OdxLinkId
12
16
  from .snrefcontext import SnRefContext
@@ -20,6 +24,40 @@ class Flashdata(IdentifiableElement):
20
24
  dataformat: Dataformat
21
25
  encrypt_compress_method: EncryptCompressMethod | None = None
22
26
 
27
+ @property
28
+ def data_str(self) -> str:
29
+ raise NotImplementedError(f"The .data_str property has not been implemented "
30
+ f"by the {type(self).__name__} class")
31
+
32
+ @property
33
+ def dataset(self) -> BinFile | bytearray | None:
34
+ data_str = self.data_str
35
+ if self.dataformat.selection in (DataformatSelection.INTEL_HEX,
36
+ DataformatSelection.MOTOROLA_S):
37
+ bf = BinFile()
38
+
39
+ # remove white space and empty lines
40
+ bf.add("\n".join([re.sub(r"\s", "", x) for x in data_str.splitlines() if x.strip()]))
41
+
42
+ return bf
43
+ elif self.dataformat.selection == DataformatSelection.BINARY:
44
+ return bytearray.fromhex(re.sub(r"\s", "", data_str, flags=re.MULTILINE))
45
+ else:
46
+ odxassert(self.dataformat.selection == DataformatSelection.USER_DEFINED,
47
+ f"Unsupported data format {self.dataformat.selection}")
48
+ return None
49
+
50
+ @property
51
+ def blob(self) -> bytearray:
52
+ ds = self.dataset
53
+ if isinstance(ds, BinFile):
54
+ return cast(bytearray, ds.as_binary())
55
+ elif isinstance(ds, bytearray):
56
+ return ds
57
+
58
+ odxraise("USER-DEFINED flash data cannot be interpreted on the odxtools level")
59
+ return bytearray()
60
+
23
61
  @staticmethod
24
62
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Flashdata":
25
63
 
odxtools/identvalue.py CHANGED
@@ -5,6 +5,7 @@ from xml.etree import ElementTree
5
5
  from .exceptions import odxraise
6
6
  from .identvaluetype import IdentValueType
7
7
  from .odxdoccontext import OdxDocContext
8
+ from .odxtypes import AtomicOdxType, DataType
8
9
 
9
10
 
10
11
  @dataclass(kw_only=True)
@@ -13,15 +14,23 @@ class IdentValue:
13
14
  Corresponds to IDENT-VALUE.
14
15
  """
15
16
 
16
- value: str
17
+ value_raw: str
17
18
 
18
19
  # note that the spec says this attribute is named "TYPE", but in
19
20
  # python, "type" is a build-in function...
20
21
  value_type: IdentValueType
21
22
 
23
+ @property
24
+ def value(self) -> AtomicOdxType:
25
+ return self._value
26
+
27
+ @property
28
+ def data_type(self) -> DataType:
29
+ return self._data_type
30
+
22
31
  @staticmethod
23
32
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "IdentValue":
24
- value = et_element.text or ""
33
+ value_raw = et_element.text or ""
25
34
 
26
35
  try:
27
36
  value_type = IdentValueType(et_element.attrib["TYPE"])
@@ -29,4 +38,8 @@ class IdentValue:
29
38
  odxraise(f"Cannot parse IDENT-VALUE-TYPE: {e}")
30
39
  value_type = None
31
40
 
32
- return IdentValue(value=value, value_type=value_type)
41
+ return IdentValue(value_raw=value_raw, value_type=value_type)
42
+
43
+ def __post_init__(self) -> None:
44
+ self._data_type = DataType(self.value_type.value)
45
+ self._value = self._data_type.from_string(self.value_raw.strip())
@@ -15,6 +15,10 @@ from .utils import dataclass_fields_asdict
15
15
  class InternFlashdata(Flashdata):
16
16
  data: str
17
17
 
18
+ @property
19
+ def data_str(self) -> str:
20
+ return self.data
21
+
18
22
  @staticmethod
19
23
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "InternFlashdata":
20
24
  kwargs = dataclass_fields_asdict(Flashdata.from_et(et_element, context))