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
odxtools/cli/browse.py
CHANGED
@@ -255,8 +255,10 @@ def encode_message_from_string_values(
|
|
255
255
|
print(f"The value specified for parameter {inner_param_sn} is not a string")
|
256
256
|
continue
|
257
257
|
|
258
|
-
|
259
|
-
|
258
|
+
if isinstance(inner_param,
|
259
|
+
ParameterWithDOP) and inner_param.physical_type is not None:
|
260
|
+
typed_dict[inner_param_sn] = _convert_string_to_odx_type(
|
261
|
+
inner_param_value, inner_param.physical_type.base_data_type)
|
260
262
|
parameter_values[parameter.short_name] = typed_dict
|
261
263
|
else:
|
262
264
|
if not isinstance(parameter_value, str):
|
odxtools/cli/compare.py
CHANGED
@@ -425,10 +425,10 @@ class Comparison(Display):
|
|
425
425
|
changed_parameters_of_service=services_with_param_changes)
|
426
426
|
dl1_service_names = [service.short_name for service in dl1.services]
|
427
427
|
|
428
|
-
dl1_request_prefixes: list[bytes | None] = [
|
428
|
+
dl1_request_prefixes: list[bytes | bytearray | None] = [
|
429
429
|
None if s.request is None else s.request.coded_const_prefix() for s in dl1.services
|
430
430
|
]
|
431
|
-
dl2_request_prefixes: list[bytes | None] = [
|
431
|
+
dl2_request_prefixes: list[bytes | bytearray | None] = [
|
432
432
|
None if s.request is None else s.request.coded_const_prefix() for s in dl2.services
|
433
433
|
]
|
434
434
|
|
@@ -436,7 +436,7 @@ class Comparison(Display):
|
|
436
436
|
for service1 in dl1.services:
|
437
437
|
|
438
438
|
# check for added diagnostic services
|
439
|
-
rq_prefix: bytes
|
439
|
+
rq_prefix: bytes | bytearray
|
440
440
|
if service1.request is not None:
|
441
441
|
rq_prefix = service1.request.coded_const_prefix()
|
442
442
|
|
odxtools/compositecodec.py
CHANGED
@@ -79,7 +79,7 @@ def composite_codec_get_free_parameters(codec: CompositeCodec) -> list[Parameter
|
|
79
79
|
|
80
80
|
|
81
81
|
def composite_codec_get_coded_const_prefix(codec: CompositeCodec,
|
82
|
-
request_prefix: bytes = b'') ->
|
82
|
+
request_prefix: bytes = b'') -> bytearray:
|
83
83
|
encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
|
84
84
|
|
85
85
|
for param in codec.parameters:
|
odxtools/configdata.py
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .configrecord import ConfigRecord
|
7
|
+
from .element import NamedElement
|
8
|
+
from .nameditemlist import NamedItemList
|
9
|
+
from .odxdoccontext import OdxDocContext
|
10
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
11
|
+
from .snrefcontext import SnRefContext
|
12
|
+
from .specialdatagroup import SpecialDataGroup
|
13
|
+
from .utils import dataclass_fields_asdict
|
14
|
+
from .validbasevariant import ValidBaseVariant
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass(kw_only=True)
|
18
|
+
class ConfigData(NamedElement):
|
19
|
+
"""This class represents a CONFIG-DATA."""
|
20
|
+
valid_base_variants: list[ValidBaseVariant]
|
21
|
+
config_records: NamedItemList[ConfigRecord]
|
22
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigData":
|
26
|
+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
|
27
|
+
|
28
|
+
valid_base_variants = [
|
29
|
+
ValidBaseVariant.from_et(vbv_elem, context)
|
30
|
+
for vbv_elem in et_element.iterfind("VALID-BASE-VARIANTS/VALID-BASE-VARIANT")
|
31
|
+
]
|
32
|
+
config_records = NamedItemList([
|
33
|
+
ConfigRecord.from_et(cr_elem, context)
|
34
|
+
for cr_elem in et_element.iterfind("CONFIG-RECORDS/CONFIG-RECORD")
|
35
|
+
])
|
36
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
37
|
+
|
38
|
+
return ConfigData(
|
39
|
+
valid_base_variants=valid_base_variants,
|
40
|
+
config_records=config_records,
|
41
|
+
sdgs=sdgs,
|
42
|
+
**kwargs)
|
43
|
+
|
44
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
45
|
+
result = {}
|
46
|
+
|
47
|
+
for valid_base_variant in self.valid_base_variants:
|
48
|
+
result.update(valid_base_variant._build_odxlinks())
|
49
|
+
for config_record in self.config_records:
|
50
|
+
result.update(config_record._build_odxlinks())
|
51
|
+
for sdg in self.sdgs:
|
52
|
+
result.update(sdg._build_odxlinks())
|
53
|
+
|
54
|
+
return result
|
55
|
+
|
56
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
57
|
+
for valid_base_variant in self.valid_base_variants:
|
58
|
+
valid_base_variant._resolve_odxlinks(odxlinks)
|
59
|
+
for config_record in self.config_records:
|
60
|
+
config_record._resolve_odxlinks(odxlinks)
|
61
|
+
for sdg in self.sdgs:
|
62
|
+
sdg._resolve_odxlinks(odxlinks)
|
63
|
+
|
64
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
65
|
+
for valid_base_variant in self.valid_base_variants:
|
66
|
+
valid_base_variant._resolve_snrefs(context)
|
67
|
+
for config_record in self.config_records:
|
68
|
+
config_record._resolve_snrefs(context)
|
69
|
+
for sdg in self.sdgs:
|
70
|
+
sdg._resolve_snrefs(context)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .dataobjectproperty import DataObjectProperty
|
7
|
+
from .nameditemlist import NamedItemList
|
8
|
+
from .odxdoccontext import OdxDocContext
|
9
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
10
|
+
from .snrefcontext import SnRefContext
|
11
|
+
from .unitspec import UnitSpec
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(kw_only=True)
|
15
|
+
class ConfigDataDictionarySpec:
|
16
|
+
data_object_props: NamedItemList[DataObjectProperty] = field(default_factory=NamedItemList)
|
17
|
+
unit_spec: UnitSpec | None = None
|
18
|
+
|
19
|
+
@staticmethod
|
20
|
+
def from_et(et_element: ElementTree.Element,
|
21
|
+
context: OdxDocContext) -> "ConfigDataDictionarySpec":
|
22
|
+
data_object_props = NamedItemList([
|
23
|
+
DataObjectProperty.from_et(dop_element, context)
|
24
|
+
for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
|
25
|
+
])
|
26
|
+
|
27
|
+
if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
|
28
|
+
unit_spec = UnitSpec.from_et(spec_elem, context)
|
29
|
+
else:
|
30
|
+
unit_spec = None
|
31
|
+
|
32
|
+
return ConfigDataDictionarySpec(
|
33
|
+
data_object_props=data_object_props,
|
34
|
+
unit_spec=unit_spec,
|
35
|
+
)
|
36
|
+
|
37
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
38
|
+
odxlinks = {}
|
39
|
+
|
40
|
+
for data_object_prop in self.data_object_props:
|
41
|
+
odxlinks.update(data_object_prop._build_odxlinks())
|
42
|
+
if self.unit_spec is not None:
|
43
|
+
odxlinks.update(self.unit_spec._build_odxlinks())
|
44
|
+
|
45
|
+
return odxlinks
|
46
|
+
|
47
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
48
|
+
for data_object_prop in self.data_object_props:
|
49
|
+
data_object_prop._resolve_odxlinks(odxlinks)
|
50
|
+
if self.unit_spec is not None:
|
51
|
+
self.unit_spec._resolve_odxlinks(odxlinks)
|
52
|
+
|
53
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
54
|
+
for data_object_prop in self.data_object_props:
|
55
|
+
data_object_prop._resolve_snrefs(context)
|
56
|
+
if self.unit_spec is not None:
|
57
|
+
self.unit_spec._resolve_snrefs(context)
|
odxtools/configiditem.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from .configitem import ConfigItem
|
6
|
+
from .odxdoccontext import OdxDocContext
|
7
|
+
from .utils import dataclass_fields_asdict
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass(kw_only=True)
|
11
|
+
class ConfigIdItem(ConfigItem):
|
12
|
+
"""This class represents a CONFIG-ID-ITEM."""
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigIdItem":
|
16
|
+
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
|
17
|
+
|
18
|
+
return ConfigIdItem(**kwargs)
|
odxtools/configitem.py
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .dopbase import DopBase
|
7
|
+
from .element import NamedElement
|
8
|
+
from .exceptions import odxrequire
|
9
|
+
from .odxdoccontext import OdxDocContext
|
10
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
11
|
+
from .snrefcontext import SnRefContext
|
12
|
+
from .specialdatagroup import SpecialDataGroup
|
13
|
+
from .utils import dataclass_fields_asdict
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass(kw_only=True)
|
17
|
+
class ConfigItem(NamedElement):
|
18
|
+
"""This class represents a CONFIG-ITEM.
|
19
|
+
|
20
|
+
CONFIG-ITEM is the base class for CONFIG-ID-ITEM, DATA-ID-ITEM,
|
21
|
+
OPTION-ITEM, and SYSTEM-ITEM.
|
22
|
+
"""
|
23
|
+
byte_position: int | None = None
|
24
|
+
bit_position: int | None = None
|
25
|
+
|
26
|
+
# according to the spec exactly one of the following two
|
27
|
+
# attributes is not None...x
|
28
|
+
data_object_prop_ref: OdxLinkRef | None = None
|
29
|
+
data_object_prop_snref: str | None = None
|
30
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
31
|
+
|
32
|
+
@property
|
33
|
+
def data_object_prop(self) -> DopBase:
|
34
|
+
return self._data_object_prop
|
35
|
+
|
36
|
+
@staticmethod
|
37
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigItem":
|
38
|
+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
|
39
|
+
|
40
|
+
byte_position = None
|
41
|
+
if (byte_pos_elem := et_element.findtext("BYTE-POSITION")) is not None:
|
42
|
+
byte_position = int(byte_pos_elem)
|
43
|
+
|
44
|
+
bit_position = None
|
45
|
+
if (bit_pos_elem := et_element.findtext("BIT-POSITION")) is not None:
|
46
|
+
bit_position = int(bit_pos_elem)
|
47
|
+
|
48
|
+
data_object_prop_ref = OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), context)
|
49
|
+
data_object_prop_snref = None
|
50
|
+
if (data_object_prop_snref_elem := et_element.find("DATA-OBJECT-PROP-SNREF")) is not None:
|
51
|
+
data_object_prop_snref = odxrequire(
|
52
|
+
data_object_prop_snref_elem.attrib.get("SHORT-NAME"))
|
53
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
54
|
+
|
55
|
+
return ConfigItem(
|
56
|
+
byte_position=byte_position,
|
57
|
+
bit_position=bit_position,
|
58
|
+
data_object_prop_ref=data_object_prop_ref,
|
59
|
+
data_object_prop_snref=data_object_prop_snref,
|
60
|
+
sdgs=sdgs,
|
61
|
+
**kwargs)
|
62
|
+
|
63
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
64
|
+
result = {}
|
65
|
+
|
66
|
+
for sdg in self.sdgs:
|
67
|
+
result.update(sdg._build_odxlinks())
|
68
|
+
|
69
|
+
return result
|
70
|
+
|
71
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
72
|
+
if self.data_object_prop_ref is not None:
|
73
|
+
self._data_object_prop = odxlinks.resolve(self.data_object_prop_ref, DopBase)
|
74
|
+
|
75
|
+
for sdg in self.sdgs:
|
76
|
+
sdg._resolve_odxlinks(odxlinks)
|
77
|
+
|
78
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
79
|
+
if self.data_object_prop_snref is not None:
|
80
|
+
ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
|
81
|
+
self._data_object_prop = resolve_snref(self.data_object_prop_snref,
|
82
|
+
ddds.all_data_object_properties, DopBase)
|
83
|
+
|
84
|
+
for sdg in self.sdgs:
|
85
|
+
sdg._resolve_snrefs(context)
|
odxtools/configrecord.py
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .audience import Audience
|
7
|
+
from .configiditem import ConfigIdItem
|
8
|
+
from .dataiditem import DataIdItem
|
9
|
+
from .datarecord import DataRecord
|
10
|
+
from .diagcommdataconnector import DiagCommDataConnector
|
11
|
+
from .element import NamedElement
|
12
|
+
from .exceptions import odxrequire
|
13
|
+
from .identvalue import IdentValue
|
14
|
+
from .nameditemlist import NamedItemList
|
15
|
+
from .odxdoccontext import OdxDocContext
|
16
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
17
|
+
from .optionitem import OptionItem
|
18
|
+
from .snrefcontext import SnRefContext
|
19
|
+
from .specialdatagroup import SpecialDataGroup
|
20
|
+
from .systemitem import SystemItem
|
21
|
+
from .utils import dataclass_fields_asdict
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass(kw_only=True)
|
25
|
+
class ConfigRecord(NamedElement):
|
26
|
+
"""This class represents a CONFIG-RECORD."""
|
27
|
+
config_id_item: ConfigIdItem | None = None
|
28
|
+
diag_comm_data_connectors: list[DiagCommDataConnector] = field(default_factory=list)
|
29
|
+
config_id: IdentValue | None = None
|
30
|
+
data_records: NamedItemList[DataRecord] = field(default_factory=NamedItemList)
|
31
|
+
audience: Audience | None = None
|
32
|
+
system_items: NamedItemList[SystemItem] = field(default_factory=NamedItemList)
|
33
|
+
data_id_item: DataIdItem | None = None
|
34
|
+
option_items: NamedItemList[OptionItem] = field(default_factory=NamedItemList)
|
35
|
+
default_data_record_snref: str | None = None
|
36
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigRecord":
|
40
|
+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
|
41
|
+
|
42
|
+
config_id_item = None
|
43
|
+
if (cid_elem := et_element.find("CONFIG-ID-ITEM")) is not None:
|
44
|
+
config_id_item = ConfigIdItem.from_et(cid_elem, context)
|
45
|
+
diag_comm_data_connectors = [
|
46
|
+
DiagCommDataConnector.from_et(dcdc_elem, context) for dcdc_elem in et_element.iterfind(
|
47
|
+
"DIAG-COMM-DATA-CONNECTORS/DIAG-COMM-DATA-CONNECTOR")
|
48
|
+
]
|
49
|
+
config_id = None
|
50
|
+
if (cid_elem := et_element.find("CONFIG-ID")) is not None:
|
51
|
+
config_id = IdentValue.from_et(cid_elem, context)
|
52
|
+
data_records = NamedItemList([
|
53
|
+
DataRecord.from_et(dr_elem, context)
|
54
|
+
for dr_elem in et_element.iterfind("DATA-RECORDS/DATA-RECORD")
|
55
|
+
])
|
56
|
+
audience = None
|
57
|
+
if (aud_elem := et_element.find("AUDIENCE")) is not None:
|
58
|
+
audience = Audience.from_et(aud_elem, context)
|
59
|
+
system_items = NamedItemList([
|
60
|
+
SystemItem.from_et(si_elem, context)
|
61
|
+
for si_elem in et_element.iterfind("SYSTEM-ITEMS/SYSTEM-ITEM")
|
62
|
+
])
|
63
|
+
data_id_item = None
|
64
|
+
if (dii_elem := et_element.find("DATA-ID-ITEM")) is not None:
|
65
|
+
data_id_item = DataIdItem.from_et(dii_elem, context)
|
66
|
+
option_items = NamedItemList([
|
67
|
+
OptionItem.from_et(si_elem, context)
|
68
|
+
for si_elem in et_element.iterfind("OPTION-ITEMS/OPTION-ITEM")
|
69
|
+
])
|
70
|
+
default_data_record_snref = None
|
71
|
+
if (default_data_record_snref_elem :=
|
72
|
+
et_element.find("DEFAULT-DATA-RECORD-SNREF")) is not None:
|
73
|
+
default_data_record_snref = odxrequire(
|
74
|
+
default_data_record_snref_elem.attrib.get("SHORT-NAME"))
|
75
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
76
|
+
|
77
|
+
return ConfigRecord(
|
78
|
+
config_id_item=config_id_item,
|
79
|
+
diag_comm_data_connectors=diag_comm_data_connectors,
|
80
|
+
config_id=config_id,
|
81
|
+
data_records=data_records,
|
82
|
+
audience=audience,
|
83
|
+
system_items=system_items,
|
84
|
+
data_id_item=data_id_item,
|
85
|
+
option_items=option_items,
|
86
|
+
default_data_record_snref=default_data_record_snref,
|
87
|
+
sdgs=sdgs,
|
88
|
+
**kwargs)
|
89
|
+
|
90
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
91
|
+
result = {}
|
92
|
+
|
93
|
+
if self.config_id_item is not None:
|
94
|
+
result.update(self.config_id_item._build_odxlinks())
|
95
|
+
for diag_comm_data_connector in self.diag_comm_data_connectors:
|
96
|
+
result.update(diag_comm_data_connector._build_odxlinks())
|
97
|
+
for data_record in self.data_records:
|
98
|
+
result.update(data_record._build_odxlinks())
|
99
|
+
if self.audience is not None:
|
100
|
+
result.update(self.audience._build_odxlinks())
|
101
|
+
for system_item in self.system_items:
|
102
|
+
result.update(system_item._build_odxlinks())
|
103
|
+
if self.data_id_item is not None:
|
104
|
+
result.update(self.data_id_item._build_odxlinks())
|
105
|
+
for option_item in self.option_items:
|
106
|
+
result.update(option_item._build_odxlinks())
|
107
|
+
for sdg in self.sdgs:
|
108
|
+
result.update(sdg._build_odxlinks())
|
109
|
+
|
110
|
+
return result
|
111
|
+
|
112
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
113
|
+
if self.config_id_item is not None:
|
114
|
+
self.config_id_item._resolve_odxlinks(odxlinks)
|
115
|
+
for diag_comm_data_connector in self.diag_comm_data_connectors:
|
116
|
+
diag_comm_data_connector._resolve_odxlinks(odxlinks)
|
117
|
+
for data_record in self.data_records:
|
118
|
+
data_record._resolve_odxlinks(odxlinks)
|
119
|
+
if self.audience is not None:
|
120
|
+
self.audience._resolve_odxlinks(odxlinks)
|
121
|
+
for system_item in self.system_items:
|
122
|
+
system_item._resolve_odxlinks(odxlinks)
|
123
|
+
if self.data_id_item is not None:
|
124
|
+
self.data_id_item._resolve_odxlinks(odxlinks)
|
125
|
+
for option_item in self.option_items:
|
126
|
+
option_item._resolve_odxlinks(odxlinks)
|
127
|
+
for sdg in self.sdgs:
|
128
|
+
sdg._resolve_odxlinks(odxlinks)
|
129
|
+
|
130
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
131
|
+
if self.config_id_item is not None:
|
132
|
+
self.config_id_item._resolve_snrefs(context)
|
133
|
+
for diag_comm_data_connector in self.diag_comm_data_connectors:
|
134
|
+
diag_comm_data_connector._resolve_snrefs(context)
|
135
|
+
for data_record in self.data_records:
|
136
|
+
data_record._resolve_snrefs(context)
|
137
|
+
if self.audience is not None:
|
138
|
+
self.audience._resolve_snrefs(context)
|
139
|
+
for system_item in self.system_items:
|
140
|
+
system_item._resolve_snrefs(context)
|
141
|
+
if self.data_id_item is not None:
|
142
|
+
self.data_id_item._resolve_snrefs(context)
|
143
|
+
for option_item in self.option_items:
|
144
|
+
option_item._resolve_snrefs(context)
|
145
|
+
for sdg in self.sdgs:
|
146
|
+
sdg._resolve_snrefs(context)
|
odxtools/database.py
CHANGED
@@ -18,6 +18,7 @@ from .diaglayers.ecushareddata import EcuSharedData
|
|
18
18
|
from .diaglayers.ecuvariant import EcuVariant
|
19
19
|
from .diaglayers.functionalgroup import FunctionalGroup
|
20
20
|
from .diaglayers.protocol import Protocol
|
21
|
+
from .ecuconfig import EcuConfig
|
21
22
|
from .exceptions import odxraise, odxrequire
|
22
23
|
from .flash import Flash
|
23
24
|
from .nameditemlist import NamedItemList
|
@@ -40,6 +41,7 @@ class Database:
|
|
40
41
|
self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
|
41
42
|
self._comparam_subsets = NamedItemList[ComparamSubset]()
|
42
43
|
self._comparam_specs = NamedItemList[ComparamSpec]()
|
44
|
+
self._ecu_configs = NamedItemList[EcuConfig]()
|
43
45
|
self._flashs = NamedItemList[Flash]()
|
44
46
|
self._short_name = "odx_database"
|
45
47
|
|
@@ -114,6 +116,10 @@ class Database:
|
|
114
116
|
self._comparam_subsets.append(ComparamSubset.from_et(category_et, context))
|
115
117
|
else:
|
116
118
|
self._comparam_specs.append(ComparamSpec.from_et(category_et, context))
|
119
|
+
elif category_tag == "ECU-CONFIG":
|
120
|
+
context = OdxDocContext(model_version,
|
121
|
+
(OdxDocFragment(category_sn, DocType.ECU_CONFIG),))
|
122
|
+
self._ecu_configs.append(EcuConfig.from_et(category_et, context))
|
117
123
|
elif category_tag == "FLASH":
|
118
124
|
context = OdxDocContext(model_version, (OdxDocFragment(category_sn, DocType.FLASH),))
|
119
125
|
self._flashs.append(Flash.from_et(category_et, context))
|
@@ -148,6 +154,9 @@ class Database:
|
|
148
154
|
for dlc in self.diag_layer_containers:
|
149
155
|
dlc._resolve_odxlinks(self._odxlinks)
|
150
156
|
|
157
|
+
for ecu_config in self.ecu_configs:
|
158
|
+
ecu_config._resolve_odxlinks(self._odxlinks)
|
159
|
+
|
151
160
|
for flash in self.flashs:
|
152
161
|
flash._resolve_odxlinks(self._odxlinks)
|
153
162
|
|
@@ -163,6 +172,8 @@ class Database:
|
|
163
172
|
spec._finalize_init(self, self._odxlinks)
|
164
173
|
for dlc in self.diag_layer_containers:
|
165
174
|
dlc._finalize_init(self, self._odxlinks)
|
175
|
+
for ecu_config in self.ecu_configs:
|
176
|
+
ecu_config._finalize_init(self, self._odxlinks)
|
166
177
|
for flash in self.flashs:
|
167
178
|
flash._finalize_init(self, self._odxlinks)
|
168
179
|
|
@@ -172,6 +183,8 @@ class Database:
|
|
172
183
|
spec._resolve_snrefs(context)
|
173
184
|
for dlc in self.diag_layer_containers:
|
174
185
|
dlc._resolve_snrefs(context)
|
186
|
+
for ecu_config in self.ecu_configs:
|
187
|
+
ecu_config._resolve_snrefs(context)
|
175
188
|
for flash in self.flashs:
|
176
189
|
flash._resolve_snrefs(context)
|
177
190
|
|
@@ -187,6 +200,9 @@ class Database:
|
|
187
200
|
for dlc in self.diag_layer_containers:
|
188
201
|
result.update(dlc._build_odxlinks())
|
189
202
|
|
203
|
+
for ecu_config in self.ecu_configs:
|
204
|
+
result.update(ecu_config._build_odxlinks())
|
205
|
+
|
190
206
|
for flash in self.flashs:
|
191
207
|
result.update(flash._build_odxlinks())
|
192
208
|
|
@@ -263,6 +279,10 @@ class Database:
|
|
263
279
|
def comparam_specs(self) -> NamedItemList[ComparamSpec]:
|
264
280
|
return self._comparam_specs
|
265
281
|
|
282
|
+
@property
|
283
|
+
def ecu_configs(self) -> NamedItemList[EcuConfig]:
|
284
|
+
return self._ecu_configs
|
285
|
+
|
266
286
|
@property
|
267
287
|
def flashs(self) -> NamedItemList[Flash]:
|
268
288
|
return self._flashs
|
@@ -274,4 +294,5 @@ class Database:
|
|
274
294
|
f"diag_layer_containers={repr(self.diag_layer_containers)}, " \
|
275
295
|
f"comparam_subsets={repr(self.comparam_subsets)}, " \
|
276
296
|
f"comparam_specs={repr(self.comparam_specs)}, " \
|
297
|
+
f"ecu_configs={repr(self.ecu_configs)}, " \
|
277
298
|
f"flashs={repr(self.flashs)})"
|
odxtools/dataiditem.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from .configitem import ConfigItem
|
6
|
+
from .odxdoccontext import OdxDocContext
|
7
|
+
from .utils import dataclass_fields_asdict
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass(kw_only=True)
|
11
|
+
class DataIdItem(ConfigItem):
|
12
|
+
"""This class represents a DATA-ID-ITEM."""
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DataIdItem":
|
16
|
+
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
|
17
|
+
|
18
|
+
return DataIdItem(**kwargs)
|
odxtools/datarecord.py
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
import re
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import Any, cast
|
5
|
+
from xml.etree import ElementTree
|
6
|
+
|
7
|
+
from bincopy import BinFile
|
8
|
+
|
9
|
+
from .datafile import Datafile
|
10
|
+
from .dataformatselection import DataformatSelection
|
11
|
+
from .element import NamedElement
|
12
|
+
from .exceptions import odxraise, odxrequire
|
13
|
+
from .identvalue import IdentValue
|
14
|
+
from .odxdoccontext import OdxDocContext
|
15
|
+
from .odxlink import OdxLinkDatabase, OdxLinkId
|
16
|
+
from .snrefcontext import SnRefContext
|
17
|
+
from .specialdatagroup import SpecialDataGroup
|
18
|
+
from .utils import dataclass_fields_asdict
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass(kw_only=True)
|
22
|
+
class DataRecord(NamedElement):
|
23
|
+
rule: str | None = None
|
24
|
+
key: str | None = None
|
25
|
+
data_id: IdentValue | None = None
|
26
|
+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
|
27
|
+
|
28
|
+
# at most one of the following two attributes is not None
|
29
|
+
datafile: Datafile | None = None
|
30
|
+
data: str | None = None
|
31
|
+
|
32
|
+
dataformat: DataformatSelection
|
33
|
+
|
34
|
+
@property
|
35
|
+
def dataset(self) -> BinFile | bytearray:
|
36
|
+
if self.datafile is not None:
|
37
|
+
db = odxrequire(self._database)
|
38
|
+
if db is None:
|
39
|
+
return bytearray()
|
40
|
+
|
41
|
+
datafile = odxrequire(self.datafile)
|
42
|
+
if datafile is None:
|
43
|
+
return bytearray()
|
44
|
+
|
45
|
+
aux_file = odxrequire(db.auxiliary_files.get(datafile.value))
|
46
|
+
if aux_file is None:
|
47
|
+
return bytearray()
|
48
|
+
|
49
|
+
data_str = aux_file.read().decode()
|
50
|
+
aux_file.seek(0)
|
51
|
+
elif self.data is not None:
|
52
|
+
data_str = self.data
|
53
|
+
else:
|
54
|
+
odxraise("No data specified for DATA-RECORD")
|
55
|
+
return bytearray()
|
56
|
+
|
57
|
+
if self.dataformat in (DataformatSelection.INTEL_HEX, DataformatSelection.MOTOROLA_S):
|
58
|
+
bf = BinFile()
|
59
|
+
|
60
|
+
# remove white space and empty lines
|
61
|
+
bf.add("\n".join([re.sub(r"\s", "", x) for x in data_str.splitlines() if x.strip()]))
|
62
|
+
|
63
|
+
return bf
|
64
|
+
elif self.dataformat == DataformatSelection.BINARY:
|
65
|
+
return bytearray.fromhex(re.sub(r"\s", "", data_str, flags=re.MULTILINE))
|
66
|
+
|
67
|
+
# user defined formats are not possible here
|
68
|
+
odxraise(f"Unsupported data format {self.dataformat.value}")
|
69
|
+
return bytearray()
|
70
|
+
|
71
|
+
@property
|
72
|
+
def blob(self) -> bytearray:
|
73
|
+
"""Computes the binary data blob that ought to be send to the ECU.
|
74
|
+
|
75
|
+
i.e., this property stitches together the data of all
|
76
|
+
segments.
|
77
|
+
|
78
|
+
Note that, in order to reduce memory usage, this property is
|
79
|
+
not computed when instanting the data record object, but at
|
80
|
+
run time when it is accessed.
|
81
|
+
"""
|
82
|
+
|
83
|
+
if isinstance(self.dataset, BinFile):
|
84
|
+
return cast(bytearray, self.dataset.as_binary())
|
85
|
+
|
86
|
+
return self.dataset
|
87
|
+
|
88
|
+
@staticmethod
|
89
|
+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DataRecord":
|
90
|
+
|
91
|
+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
|
92
|
+
|
93
|
+
rule = et_element.findtext("RULE")
|
94
|
+
key = et_element.findtext("KEY")
|
95
|
+
data_id = None
|
96
|
+
if (did_elem := et_element.find("DATA-ID")) is not None:
|
97
|
+
data_id = IdentValue.from_et(did_elem, context)
|
98
|
+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
|
99
|
+
datafile = None
|
100
|
+
if (df_elem := et_element.find("DATA-FILE")) is not None:
|
101
|
+
datafile = Datafile.from_et(df_elem, context)
|
102
|
+
data = et_element.findtext("DATA")
|
103
|
+
|
104
|
+
dataformat_str = odxrequire(et_element.attrib.get("DATAFORMAT"))
|
105
|
+
try:
|
106
|
+
dataformat = DataformatSelection(dataformat_str)
|
107
|
+
except ValueError:
|
108
|
+
dataformat = cast(DataformatSelection, None)
|
109
|
+
odxraise(f"Encountered unknown data format selection '{dataformat_str}'")
|
110
|
+
|
111
|
+
return DataRecord(
|
112
|
+
rule=rule,
|
113
|
+
key=key,
|
114
|
+
data_id=data_id,
|
115
|
+
sdgs=sdgs,
|
116
|
+
datafile=datafile,
|
117
|
+
data=data,
|
118
|
+
dataformat=dataformat,
|
119
|
+
**kwargs)
|
120
|
+
|
121
|
+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
|
122
|
+
odxlinks: dict[OdxLinkId, Any] = {}
|
123
|
+
|
124
|
+
return odxlinks
|
125
|
+
|
126
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
127
|
+
pass
|
128
|
+
|
129
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
130
|
+
# this is slightly hacky because we only remember the
|
131
|
+
# applicable ODX database and do not resolve any SNREFs here
|
132
|
+
self._database = odxrequire(context.database)
|