Skip to content

Commit bac838f

Browse files
authored
Merge pull request #438 from andlaus/function_dictionary
Implement support for the `FUNCTION-DICT` category
2 parents 98736ce + 40045d7 commit bac838f

26 files changed

+1265
-17
lines changed

examples/somersaultecu.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -845,8 +845,6 @@ class SomersaultSID(IntEnum):
845845
}
846846

847847
# tables
848-
flip_quality_table_id = OdxLinkId("somersault.table.flip_quality", doc_frags)
849-
flip_quality_table_ref = OdxLinkRef.from_id(flip_quality_table_id)
850848
somersault_tables = {
851849
"last_flip_details":
852850
Table(

odxtools/basefunctionnode.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 .admindata import AdminData
7+
from .audience import Audience
8+
from .componentconnector import ComponentConnector
9+
from .element import IdentifiableElement
10+
from .functioninparam import FunctionInParam
11+
from .functionoutparam import FunctionOutParam
12+
from .multipleecujob import MultipleEcuJob
13+
from .nameditemlist import NamedItemList
14+
from .odxdoccontext import OdxDocContext
15+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
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 BaseFunctionNode(IdentifiableElement):
23+
audience: Audience | None = None
24+
function_in_params: NamedItemList[FunctionInParam] = field(default_factory=NamedItemList)
25+
function_out_params: NamedItemList[FunctionOutParam] = field(default_factory=NamedItemList)
26+
component_connectors: list[ComponentConnector] = field(default_factory=list)
27+
multiple_ecu_job_refs: list[OdxLinkRef] = field(default_factory=list)
28+
admin_data: AdminData | None = None
29+
sdg: SpecialDataGroup | None = None
30+
31+
@property
32+
def multiple_ecu_jobs(self) -> NamedItemList[MultipleEcuJob]:
33+
return self._multiple_ecu_jobs
34+
35+
@staticmethod
36+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "BaseFunctionNode":
37+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
38+
39+
audience = None
40+
if (audience_elem := et_element.find("AUDIENCE")) is not None:
41+
audience = Audience.from_et(audience_elem, context)
42+
43+
function_in_params = NamedItemList([
44+
FunctionInParam.from_et(fip_el, context)
45+
for fip_el in et_element.iterfind("FUNCTION-IN-PARAMS/FUNCTION-IN-PARAM")
46+
])
47+
48+
function_out_params = NamedItemList([
49+
FunctionOutParam.from_et(fop_el, context)
50+
for fop_el in et_element.iterfind("FUNCTION-OUT-PARAMS/FUNCTION-OUT-PARAM")
51+
])
52+
53+
component_connectors = [
54+
ComponentConnector.from_et(cc_el, context)
55+
for cc_el in et_element.iterfind("COMPONENT-CONNECTORS/COMPONENT-CONNECTOR")
56+
]
57+
58+
multiple_ecu_job_refs = [
59+
OdxLinkRef.from_et(el, context)
60+
for el in et_element.iterfind("MULTIPLE-ECU-JOB-REFS/MULTIPLE-ECU-JOB-REF")
61+
]
62+
63+
admin_data = None
64+
if (admin_data_elem := et_element.find("ADMIN-DATA")) is not None:
65+
admin_data = AdminData.from_et(admin_data_elem, context)
66+
67+
# be a aware that for whatever reason, function nodes only
68+
# exhibit at most one SDG. (quirk of the ODX standard?)
69+
sdg = None
70+
if (sdge := et_element.find("SDG")) is not None:
71+
sdg = SpecialDataGroup.from_et(sdge, context)
72+
73+
return BaseFunctionNode(
74+
audience=audience,
75+
function_in_params=function_in_params,
76+
function_out_params=function_out_params,
77+
component_connectors=component_connectors,
78+
multiple_ecu_job_refs=multiple_ecu_job_refs,
79+
admin_data=admin_data,
80+
sdg=sdg,
81+
**kwargs)
82+
83+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
84+
result = {self.odx_id: self}
85+
86+
if self.audience is not None:
87+
result.update(self.audience._build_odxlinks())
88+
89+
for function_in_param in self.function_in_params:
90+
result.update(function_in_param._build_odxlinks())
91+
92+
for function_out_param in self.function_out_params:
93+
result.update(function_out_param._build_odxlinks())
94+
95+
for component_connector in self.component_connectors:
96+
result.update(component_connector._build_odxlinks())
97+
98+
if self.admin_data is not None:
99+
result.update(self.admin_data._build_odxlinks())
100+
101+
if self.sdg is not None:
102+
result.update(self.sdg._build_odxlinks())
103+
104+
return result
105+
106+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
107+
self._multiple_ecu_jobs = NamedItemList(
108+
[odxlinks.resolve(ref, MultipleEcuJob) for ref in self.multiple_ecu_job_refs])
109+
110+
if self.audience:
111+
self.audience._resolve_odxlinks(odxlinks)
112+
113+
for function_in_param in self.function_in_params:
114+
function_in_param._resolve_odxlinks(odxlinks)
115+
116+
for function_out_param in self.function_out_params:
117+
function_out_param._resolve_odxlinks(odxlinks)
118+
119+
for component_connector in self.component_connectors:
120+
component_connector._resolve_odxlinks(odxlinks)
121+
122+
if self.admin_data:
123+
self.admin_data._resolve_odxlinks(odxlinks)
124+
125+
if self.sdg is not None:
126+
self.sdg._resolve_odxlinks(odxlinks)
127+
128+
def _resolve_snrefs(self, context: SnRefContext) -> None:
129+
if self.audience:
130+
self.audience._resolve_snrefs(context)
131+
132+
for function_in_param in self.function_in_params:
133+
function_in_param._resolve_snrefs(context)
134+
135+
for function_out_param in self.function_out_params:
136+
function_out_param._resolve_snrefs(context)
137+
138+
for component_connector in self.component_connectors:
139+
component_connector._resolve_snrefs(context)
140+
141+
if self.admin_data:
142+
self.admin_data._resolve_snrefs(context)
143+
144+
if self.sdg is not None:
145+
self.sdg._resolve_snrefs(context)

odxtools/componentconnector.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 .diaglayers.basevariant import BaseVariant
7+
from .diaglayers.ecuvariant import EcuVariant
8+
from .diagobjectconnector import DiagObjectConnector
9+
from .exceptions import odxraise
10+
from .nameditemlist import NamedItemList
11+
from .odxdoccontext import OdxDocContext
12+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
13+
from .snrefcontext import SnRefContext
14+
15+
16+
@dataclass(kw_only=True)
17+
class ComponentConnector:
18+
ecu_variant_refs: list[OdxLinkRef] = field(default_factory=list)
19+
base_variant_ref: OdxLinkRef | None = None
20+
21+
diag_object_connector: DiagObjectConnector | None = None
22+
diag_object_connector_ref: OdxLinkRef | None = None
23+
24+
@property
25+
def ecu_variants(self) -> NamedItemList[EcuVariant]:
26+
return self._ecu_variants
27+
28+
@property
29+
def base_variant(self) -> BaseVariant | None:
30+
return self._base_variant
31+
32+
@staticmethod
33+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ComponentConnector":
34+
ecu_variant_refs = [
35+
OdxLinkRef.from_et(el, context)
36+
for el in et_element.iterfind("ECU-VARIANT-REFS/ECU-VARIANT-REF")
37+
]
38+
base_variant_ref = OdxLinkRef.from_et(et_element.find("BASE-VARIANT-REF"), context)
39+
diag_object_connector = None
40+
if (doc_elem := et_element.find("DIAG-OBJECT-CONNECTOR")) is not None:
41+
diag_object_connector = DiagObjectConnector.from_et(doc_elem, context)
42+
diag_object_connector_ref = OdxLinkRef.from_et(
43+
et_element.find("DIAG-OBJECT-CONNECTOR-REF"), context)
44+
45+
return ComponentConnector(
46+
ecu_variant_refs=ecu_variant_refs,
47+
base_variant_ref=base_variant_ref,
48+
diag_object_connector=diag_object_connector,
49+
diag_object_connector_ref=diag_object_connector_ref)
50+
51+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
52+
result = {}
53+
54+
if self.diag_object_connector_ref is None:
55+
if self.diag_object_connector is None:
56+
odxraise()
57+
else:
58+
result.update(self.diag_object_connector._build_odxlinks())
59+
60+
return result
61+
62+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
63+
self._ecu_variants = NamedItemList(
64+
[odxlinks.resolve(ev_ref, EcuVariant) for ev_ref in self.ecu_variant_refs])
65+
66+
self._base_variant = None
67+
if self.base_variant_ref is not None:
68+
self._base_variant = odxlinks.resolve(self.base_variant_ref, BaseVariant)
69+
70+
if self.diag_object_connector_ref is None:
71+
if self.diag_object_connector is None:
72+
odxraise()
73+
self.diag_object_connector._resolve_odxlinks(odxlinks)
74+
else:
75+
self.diag_object_connector = odxlinks.resolve(self.diag_object_connector_ref,
76+
DiagObjectConnector)
77+
78+
def _resolve_snrefs(self, context: SnRefContext) -> None:
79+
if self.diag_object_connector_ref is None:
80+
if self.diag_object_connector is None:
81+
odxraise()
82+
return
83+
84+
self.diag_object_connector._resolve_snrefs(context)

odxtools/database.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .ecuconfig import EcuConfig
2222
from .exceptions import odxraise, odxrequire
2323
from .flash import Flash
24+
from .functiondictionary import FunctionDictionary
2425
from .multipleecujobspec import MultipleEcuJobSpec
2526
from .nameditemlist import NamedItemList
2627
from .odxdoccontext import OdxDocContext
@@ -47,6 +48,7 @@ def __init__(self) -> None:
4748
self._vehicle_info_specs = NamedItemList[VehicleInfoSpec]()
4849
self._flashs = NamedItemList[Flash]()
4950
self._multiple_ecu_job_specs = NamedItemList[MultipleEcuJobSpec]()
51+
self._function_dictionaries = NamedItemList[FunctionDictionary]()
5052
self._short_name = "odx_database"
5153

5254
def add_pdx_file(self, pdx_file: Union[str, "PathLike[Any]", IO[bytes], ZipFile]) -> None:
@@ -135,6 +137,12 @@ def add_xml_tree(self, root: ElementTree.Element) -> None:
135137
context = OdxDocContext(model_version,
136138
(OdxDocFragment(category_sn, DocType.MULTIPLE_ECU_JOB_SPEC),))
137139
self._multiple_ecu_job_specs.append(MultipleEcuJobSpec.from_et(category_et, context))
140+
elif category_tag == "FUNCTION-DICTIONARY":
141+
context = OdxDocContext(model_version, (OdxDocFragment(
142+
category_sn, DocType.FUNCTION_DICTIONARY_SPEC),))
143+
self._function_dictionaries.append(FunctionDictionary.from_et(category_et, context))
144+
else:
145+
odxraise(f"Encountered unknown ODX category '{category_tag}' (non-conforming dataset?)")
138146

139147
def refresh(self) -> None:
140148
# Create wrapper objects
@@ -178,6 +186,9 @@ def refresh(self) -> None:
178186
for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
179187
multiple_ecu_job_spec._resolve_odxlinks(self._odxlinks)
180188

189+
for function_dictionary in self.function_dictionaries:
190+
function_dictionary._resolve_odxlinks(self._odxlinks)
191+
181192
# resolve short name references for containers which do not do
182193
# inheritance (we can call directly call _resolve_snrefs())
183194
context = SnRefContext()
@@ -198,6 +209,8 @@ def refresh(self) -> None:
198209
flash._finalize_init(self, self._odxlinks)
199210
for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
200211
multiple_ecu_job_spec._finalize_init(self, self._odxlinks)
212+
for function_dictionary in self.function_dictionaries:
213+
function_dictionary._finalize_init(self, self._odxlinks)
201214

202215
for subset in self.comparam_subsets:
203216
subset._resolve_snrefs(context)
@@ -213,6 +226,8 @@ def refresh(self) -> None:
213226
flash._resolve_snrefs(context)
214227
for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
215228
multiple_ecu_job_spec._resolve_snrefs(context)
229+
for function_dictionary in self.function_dictionaries:
230+
function_dictionary._resolve_snrefs(context)
216231

217232
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
218233
result: dict[OdxLinkId, Any] = {}
@@ -234,9 +249,13 @@ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
234249

235250
for flash in self.flashs:
236251
result.update(flash._build_odxlinks())
252+
237253
for multiple_ecu_job_spec in self.multiple_ecu_job_specs:
238254
result.update(multiple_ecu_job_spec._build_odxlinks())
239255

256+
for function_dictionary in self.function_dictionaries:
257+
result.update(function_dictionary._build_odxlinks())
258+
240259
return result
241260

242261
@property
@@ -326,6 +345,10 @@ def flashs(self) -> NamedItemList[Flash]:
326345
def multiple_ecu_job_specs(self) -> NamedItemList[MultipleEcuJobSpec]:
327346
return self._multiple_ecu_job_specs
328347

348+
@property
349+
def function_dictionaries(self) -> NamedItemList[FunctionDictionary]:
350+
return self._function_dictionaries
351+
329352
def __repr__(self) -> str:
330353
return f"Database(model_version={self.model_version}, " \
331354
f"protocols={[x.short_name for x in self.protocols]}, " \
@@ -336,4 +359,5 @@ def __repr__(self) -> str:
336359
f"ecu_configs={repr(self.ecu_configs)}, " \
337360
f"vehicle_info_specs={repr(self.vehicle_info_specs)}, " \
338361
f"multiple_ecu_job_specs={repr(self.multiple_ecu_job_specs)}, " \
362+
f"function_dictionaries={repr(self.function_dictionaries)}, " \
339363
f"flashs={repr(self.flashs)})"

0 commit comments

Comments
 (0)