Skip to content

Commit

Permalink
Introduce OdxCategory
Browse files Browse the repository at this point in the history
The spec defines `OdxCategory` as the base for any top-level content
tag in an ODX XML file. odxtools currently implements
`DIAG-LAYER-CONTAINER`, `COMPARAM-SPEC` and `COMPARAM-SUBSET` (all of
them are relevant for diagnostics based on UDS). Currently missing are
the categories `ECU-CONFIG` (variant coding), `FLASH` (firmware blobs
for flashing), `FUNCTION-DICTIONARY` (functionality distributed over
multiple ECUs) and `MULTIPLE-ECU-JOB-SPEC` (multiple-ecu jobs).

Signed-off-by: Andreas Lauser <andreas.lauser@mercedes-benz.com>
Signed-off-by: Christian Hackenbeck <christian.hackenbeck@mercedes-benz.com>
  • Loading branch information
andlaus committed Dec 5, 2024
1 parent 6ab7b31 commit 1440d5e
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 223 deletions.
64 changes: 10 additions & 54 deletions odxtools/comparamspec.py
Original file line number Diff line number Diff line change
@@ -1,94 +1,50 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List
from xml.etree import ElementTree

from .admindata import AdminData
from .companydata import CompanyData
from .element import IdentifiableElement
from .exceptions import odxrequire
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .protstack import ProtStack
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .utils import dataclass_fields_asdict


@dataclass
class ComparamSpec(IdentifiableElement):
admin_data: Optional[AdminData]
company_datas: NamedItemList[CompanyData]
sdgs: List[SpecialDataGroup]
class ComparamSpec(OdxCategory):
prot_stacks: NamedItemList[ProtStack]

@staticmethod
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ComparamSpec":

short_name = odxrequire(et_element.findtext("SHORT-NAME"))
doc_frags = [OdxDocFragment(short_name, str(et_element.tag))]
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="COMPARAM-SPEC")
doc_frags = cat.odx_id.doc_fragments
kwargs = dataclass_fields_asdict(cat)

admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
company_datas = NamedItemList([
CompanyData.from_et(cde, doc_frags)
for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
])
sdgs = [
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
]
prot_stacks = NamedItemList([
ProtStack.from_et(dl_element, doc_frags)
for dl_element in et_element.iterfind("PROT-STACKS/PROT-STACK")
])

return ComparamSpec(
admin_data=admin_data,
company_datas=company_datas,
sdgs=sdgs,
prot_stacks=prot_stacks,
**kwargs)
return ComparamSpec(prot_stacks=prot_stacks, **kwargs)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxlinks: Dict[OdxLinkId, Any] = {}
odxlinks[self.odx_id] = self

if self.admin_data is not None:
odxlinks.update(self.admin_data._build_odxlinks())

for cd in self.company_datas:
odxlinks.update(cd._build_odxlinks())

for sdg in self.sdgs:
odxlinks.update(sdg._build_odxlinks())
odxlinks = super()._build_odxlinks()

for ps in self.prot_stacks:
odxlinks.update(ps._build_odxlinks())

return odxlinks

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.admin_data is not None:
self.admin_data._resolve_odxlinks(odxlinks)

for cd in self.company_datas:
cd._resolve_odxlinks(odxlinks)

for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)
super()._resolve_odxlinks(odxlinks)

for ps in self.prot_stacks:
ps._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.admin_data is not None:
self.admin_data._resolve_snrefs(context)

for cd in self.company_datas:
cd._resolve_snrefs(context)

for sdg in self.sdgs:
sdg._resolve_snrefs(context)
super()._resolve_snrefs(context)

for ps in self.prot_stacks:
ps._resolve_snrefs(context)
75 changes: 14 additions & 61 deletions odxtools/comparamsubset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,36 @@
from typing import Any, Dict, List, Optional
from xml.etree import ElementTree

from .admindata import AdminData
from .companydata import CompanyData
from .comparam import Comparam
from .complexcomparam import ComplexComparam
from .dataobjectproperty import DataObjectProperty
from .element import IdentifiableElement
from .exceptions import odxrequire
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .unitspec import UnitSpec
from .utils import dataclass_fields_asdict


@dataclass
class ComparamSubset(IdentifiableElement):
# mandatory in ODX 2.2, but non existent in ODX 2.0
class ComparamSubset(OdxCategory):
# mandatory in ODX 2.2, but non-existent in ODX 2.0
category: Optional[str]
data_object_props: NamedItemList[DataObjectProperty]

comparams: NamedItemList[Comparam]
complex_comparams: NamedItemList[ComplexComparam]
data_object_props: NamedItemList[DataObjectProperty]
unit_spec: Optional[UnitSpec]
admin_data: Optional[AdminData]
company_datas: NamedItemList[CompanyData]
sdgs: List[SpecialDataGroup]

@staticmethod
def from_et(et_element: ElementTree.Element,
doc_frags: List[OdxDocFragment]) -> "ComparamSubset":

category = et_element.get("CATEGORY")

short_name = odxrequire(et_element.findtext("SHORT-NAME"))
doc_frags = [OdxDocFragment(short_name, str(et_element.tag))]
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="COMPARAM-SUBSET")
doc_frags = cat.odx_id.doc_fragments
kwargs = dataclass_fields_asdict(cat)

admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
company_datas = NamedItemList([
CompanyData.from_et(cde, doc_frags)
for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
])
category = et_element.get("CATEGORY")

data_object_props = NamedItemList([
DataObjectProperty.from_et(el, doc_frags)
Expand All @@ -61,25 +49,16 @@ def from_et(et_element: ElementTree.Element,
else:
unit_spec = None

sdgs = [
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
]

return ComparamSubset(
category=category,
admin_data=admin_data,
company_datas=company_datas,
data_object_props=data_object_props,
comparams=comparams,
complex_comparams=complex_comparams,
unit_spec=unit_spec,
sdgs=sdgs,
**kwargs)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxlinks: Dict[OdxLinkId, Any] = {}
if self.odx_id is not None:
odxlinks[self.odx_id] = self
odxlinks = super()._build_odxlinks()

for dop in self.data_object_props:
odxlinks[dop.odx_id] = dop
Expand All @@ -93,19 +72,11 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
if self.unit_spec:
odxlinks.update(self.unit_spec._build_odxlinks())

if self.admin_data is not None:
odxlinks.update(self.admin_data._build_odxlinks())

if self.company_datas is not None:
for cd in self.company_datas:
odxlinks.update(cd._build_odxlinks())

for sdg in self.sdgs:
odxlinks.update(sdg._build_odxlinks())

return odxlinks

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
super()._resolve_odxlinks(odxlinks)

for dop in self.data_object_props:
dop._resolve_odxlinks(odxlinks)

Expand All @@ -118,17 +89,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.unit_spec:
self.unit_spec._resolve_odxlinks(odxlinks)

if self.admin_data is not None:
self.admin_data._resolve_odxlinks(odxlinks)

if self.company_datas is not None:
for cd in self.company_datas:
cd._resolve_odxlinks(odxlinks)

for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
super()._resolve_snrefs(context)

for dop in self.data_object_props:
dop._resolve_snrefs(context)

Expand All @@ -140,13 +103,3 @@ def _resolve_snrefs(self, context: SnRefContext) -> None:

if self.unit_spec:
self.unit_spec._resolve_snrefs(context)

if self.admin_data is not None:
self.admin_data._resolve_snrefs(context)

if self.company_datas is not None:
for cd in self.company_datas:
cd._resolve_snrefs(context)

for sdg in self.sdgs:
sdg._resolve_snrefs(context)
11 changes: 11 additions & 0 deletions odxtools/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .exceptions import odxraise, odxrequire
from .nameditemlist import NamedItemList
from .odxlink import OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext


class Database:
Expand Down Expand Up @@ -139,6 +140,16 @@ def refresh(self) -> None:
for dlc in self.diag_layer_containers:
dlc._resolve_odxlinks(self._odxlinks)

# resolve short name references for containers which do not do
# inheritance (we can call directly call _resolve_snrefs())
context = SnRefContext()
context.database = self

for subset in self.comparam_subsets:
subset._resolve_snrefs(context)
for spec in self.comparam_specs:
spec._resolve_snrefs(context)

# let the diaglayers sort out the inherited objects and the
# short name references
for dlc in self.diag_layer_containers:
Expand Down
48 changes: 8 additions & 40 deletions odxtools/diaglayercontainer.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from itertools import chain
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Dict, List, Union
from xml.etree import ElementTree

from .admindata import AdminData
from .companydata import CompanyData
from .diaglayers.basevariant import BaseVariant
from .diaglayers.diaglayer import DiagLayer
from .diaglayers.ecushareddata import EcuSharedData
from .diaglayers.ecuvariant import EcuVariant
from .diaglayers.functionalgroup import FunctionalGroup
from .diaglayers.protocol import Protocol
from .element import IdentifiableElement
from .exceptions import odxrequire
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .specialdatagroup import SpecialDataGroup
from .utils import dataclass_fields_asdict

if TYPE_CHECKING:
from .database import Database


@dataclass
class DiagLayerContainer(IdentifiableElement):
admin_data: Optional[AdminData]
company_datas: NamedItemList[CompanyData]
class DiagLayerContainer(OdxCategory):
ecu_shared_datas: NamedItemList[EcuSharedData]
protocols: NamedItemList[Protocol]
functional_groups: NamedItemList[FunctionalGroup]
base_variants: NamedItemList[BaseVariant]
ecu_variants: NamedItemList[EcuVariant]
sdgs: List[SpecialDataGroup]

@property
def ecus(self) -> NamedItemList[EcuVariant]:
Expand All @@ -54,17 +47,10 @@ def __post_init__(self) -> None:
def from_et(et_element: ElementTree.Element,
doc_frags: List[OdxDocFragment]) -> "DiagLayerContainer":

short_name = odxrequire(et_element.findtext("SHORT-NAME"))
# create the current ODX "document fragment" (description of the
# current document for references and IDs)
doc_frags = [OdxDocFragment(short_name, "CONTAINER")]
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="CONTAINER")
doc_frags = cat.odx_id.doc_fragments
kwargs = dataclass_fields_asdict(cat)

admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
company_datas = NamedItemList([
CompanyData.from_et(cde, doc_frags)
for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
])
ecu_shared_datas = NamedItemList([
EcuSharedData.from_et(dl_element, doc_frags)
for dl_element in et_element.iterfind("ECU-SHARED-DATAS/ECU-SHARED-DATA")
Expand All @@ -85,30 +71,17 @@ def from_et(et_element: ElementTree.Element,
EcuVariant.from_et(dl_element, doc_frags)
for dl_element in et_element.iterfind("ECU-VARIANTS/ECU-VARIANT")
])
sdgs = [
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
]

return DiagLayerContainer(
admin_data=admin_data,
company_datas=company_datas,
ecu_shared_datas=ecu_shared_datas,
protocols=protocols,
functional_groups=functional_groups,
base_variants=base_variants,
ecu_variants=ecu_variants,
sdgs=sdgs,
**kwargs)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
result = {self.odx_id: self}

if self.admin_data is not None:
result.update(self.admin_data._build_odxlinks())
for cd in self.company_datas:
result.update(cd._build_odxlinks())
for sdg in self.sdgs:
result.update(sdg._build_odxlinks())
result = super()._build_odxlinks()

for ecu_shared_data in self.ecu_shared_datas:
result.update(ecu_shared_data._build_odxlinks())
Expand All @@ -124,12 +97,7 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.admin_data is not None:
self.admin_data._resolve_odxlinks(odxlinks)
for cd in self.company_datas:
cd._resolve_odxlinks(odxlinks)
for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)
super()._resolve_odxlinks(odxlinks)

for ecu_shared_data in self.ecu_shared_datas:
ecu_shared_data._resolve_odxlinks(odxlinks)
Expand Down
Loading

0 comments on commit 1440d5e

Please sign in to comment.