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.
- odxtools/cli/browse.py +4 -2
- odxtools/cli/compare.py +3 -3
- odxtools/compositecodec.py +1 -1
- odxtools/configdata.py +70 -0
- odxtools/configdatadictionaryspec.py +57 -0
- odxtools/configiditem.py +18 -0
- odxtools/configitem.py +85 -0
- odxtools/configrecord.py +146 -0
- odxtools/database.py +21 -0
- odxtools/dataiditem.py +18 -0
- odxtools/datarecord.py +132 -0
- odxtools/decodestate.py +1 -1
- odxtools/diagcommdataconnector.py +61 -0
- odxtools/diaglayers/diaglayer.py +39 -17
- odxtools/diaglayers/ecuvariant.py +12 -19
- odxtools/diaglayers/hierarchyelement.py +5 -5
- odxtools/diaglayers/protocol.py +14 -0
- odxtools/diagservice.py +10 -10
- odxtools/ecuconfig.py +89 -0
- odxtools/encryptcompressmethod.py +16 -3
- odxtools/externflashdata.py +21 -2
- odxtools/flashdata.py +40 -2
- odxtools/identvalue.py +16 -3
- odxtools/internflashdata.py +4 -0
- odxtools/isotp_state_machine.py +11 -11
- odxtools/itemvalue.py +77 -0
- odxtools/nameditemlist.py +2 -2
- odxtools/odxlink.py +4 -2
- odxtools/optionitem.py +79 -0
- odxtools/parameters/codedconstparameter.py +2 -3
- odxtools/readdiagcommconnector.py +79 -0
- odxtools/readparamvalue.py +52 -0
- odxtools/request.py +3 -3
- odxtools/response.py +8 -4
- odxtools/statemachine.py +3 -2
- odxtools/subcomponentparamconnector.py +1 -1
- odxtools/systemitem.py +23 -0
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -0
- odxtools/templates/ecu_config.odx-e.xml.jinja2 +38 -0
- odxtools/templates/flash.odx-f.xml.jinja2 +2 -2
- odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
- odxtools/templates/macros/printChecksum.xml.jinja2 +1 -1
- odxtools/templates/macros/printCompuMethod.xml.jinja2 +2 -2
- odxtools/templates/macros/printConfigData.xml.jinja2 +39 -0
- odxtools/templates/macros/printConfigDataDictionarySpec.xml.jinja2 +22 -0
- odxtools/templates/macros/printConfigItems.xml.jinja2 +66 -0
- odxtools/templates/macros/printConfigRecord.xml.jinja2 +73 -0
- odxtools/templates/macros/printDOP.xml.jinja2 +5 -6
- odxtools/templates/macros/printDataRecord.xml.jinja2 +35 -0
- odxtools/templates/macros/printDiagCommDataConnector.xml.jinja2 +66 -0
- odxtools/templates/macros/printExpectedIdent.xml.jinja2 +1 -1
- odxtools/templates/macros/printFlashdata.xml.jinja2 +2 -2
- odxtools/templates/macros/printItemValue.xml.jinja2 +31 -0
- odxtools/templates/macros/printOwnIdent.xml.jinja2 +1 -1
- odxtools/templates/macros/printSecurity.xml.jinja2 +4 -4
- odxtools/templates/macros/printSegment.xml.jinja2 +1 -1
- odxtools/validbasevariant.py +62 -0
- odxtools/validityfor.py +16 -3
- odxtools/variantmatcher.py +4 -4
- odxtools/version.py +2 -2
- odxtools/writediagcommconnector.py +77 -0
- odxtools/writepdxfile.py +15 -0
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/METADATA +2 -1
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/RECORD +68 -44
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/WHEEL +1 -1
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/entry_points.txt +0 -0
- {odxtools-10.2.0.dist-info → odxtools-10.3.0.dist-info}/licenses/LICENSE +0 -0
- {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)
|
odxtools/diaglayers/diaglayer.py
CHANGED
@@ -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
|
268
|
-
return self.diag_layer_raw.
|
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
|
276
|
-
return self.diag_layer_raw.
|
296
|
+
def libraries(self) -> NamedItemList[Library]:
|
297
|
+
return self.diag_layer_raw.libraries
|
277
298
|
|
278
299
|
@property
|
279
|
-
def
|
280
|
-
|
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
|
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
|
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"""
|
odxtools/diaglayers/protocol.py
CHANGED
@@ -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) ->
|
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
|
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) ->
|
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) ->
|
257
|
+
**kwargs: ParameterValue) -> bytearray:
|
258
258
|
return self.negative_responses[response_index].encode(coded_request, **kwargs)
|
259
259
|
|
260
|
-
def __call__(self, **kwargs: ParameterValue) ->
|
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
|
-
|
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
|
-
|
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(
|
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] = {}
|
odxtools/externflashdata.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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(
|
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())
|
odxtools/internflashdata.py
CHANGED
@@ -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))
|