Skip to content

Commit abf7fad

Browse files
authored
Merge pull request #323 from andlaus/diag_variables
Implement diagnostic variables
2 parents 9d74fa2 + 2a4375f commit abf7fad

16 files changed

+718
-13
lines changed

examples/somersaultecu.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,9 @@ class SomersaultSID(IntEnum):
20372037
ecu_variant_patterns=[],
20382038
comparam_spec_ref=None,
20392039
prot_stack_snref=None,
2040+
diag_variables_raw=[],
2041+
variable_groups=NamedItemList(),
2042+
dyn_defined_spec=None,
20402043
)
20412044
somersault_diaglayer = DiagLayer(diag_layer_raw=somersault_diaglayer_raw)
20422045

@@ -2082,6 +2085,9 @@ class SomersaultSID(IntEnum):
20822085
ecu_variant_patterns=[],
20832086
comparam_spec_ref=None,
20842087
prot_stack_snref=None,
2088+
diag_variables_raw=[],
2089+
variable_groups=NamedItemList(),
2090+
dyn_defined_spec=None,
20852091
)
20862092
somersault_lazy_diaglayer = DiagLayer(diag_layer_raw=somersault_lazy_diaglayer_raw)
20872093

@@ -2306,6 +2312,9 @@ class SomersaultSID(IntEnum):
23062312
ecu_variant_patterns=[],
23072313
comparam_spec_ref=None,
23082314
prot_stack_snref=None,
2315+
diag_variables_raw=[],
2316+
variable_groups=NamedItemList(),
2317+
dyn_defined_spec=None,
23092318
)
23102319
somersault_assiduous_diaglayer = DiagLayer(diag_layer_raw=somersault_assiduous_diaglayer_raw)
23112320

odxtools/commrelation.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# SPDX-License-Identifier: MIT
2+
import warnings
3+
from dataclasses import dataclass
4+
from enum import Enum
5+
from typing import Any, Dict, List, Optional
6+
from xml.etree import ElementTree
7+
8+
from .description import Description
9+
from .diagcomm import DiagComm
10+
from .diagservice import DiagService
11+
from .exceptions import OdxWarning, odxraise, odxrequire
12+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
13+
from .parameters.parameter import Parameter
14+
from .snrefcontext import SnRefContext
15+
16+
17+
class CommRelationValueType(Enum):
18+
CURRENT = "CURRENT"
19+
STORED = "STORED"
20+
STATIC = "STATIC"
21+
SUBSTITUTED = "SUBSTITUTED"
22+
23+
24+
@dataclass
25+
class CommRelation:
26+
description: Optional[Description]
27+
relation_type: str
28+
diag_comm_ref: Optional[OdxLinkRef]
29+
diag_comm_snref: Optional[str]
30+
in_param_if_snref: Optional[str]
31+
#in_param_if_snpathref: Optional[str] # TODO
32+
out_param_if_snref: Optional[str]
33+
#out_param_if_snpathref: Optional[str] # TODO
34+
value_type_raw: Optional[CommRelationValueType]
35+
36+
@property
37+
def diag_comm(self) -> DiagComm:
38+
return self._diag_comm
39+
40+
@property
41+
def in_param_if(self) -> Optional[Parameter]:
42+
return self._in_param_if
43+
44+
@property
45+
def out_param_if(self) -> Optional[Parameter]:
46+
return self._out_param_if
47+
48+
@property
49+
def value_type(self) -> CommRelationValueType:
50+
if self.value_type_raw is None:
51+
return CommRelationValueType.CURRENT
52+
53+
return self.value_type_raw
54+
55+
@staticmethod
56+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "CommRelation":
57+
description = Description.from_et(et_element.find("DESC"), doc_frags)
58+
relation_type = odxrequire(et_element.findtext("RELATION-TYPE"))
59+
60+
diag_comm_ref = OdxLinkRef.from_et(et_element.find("DIAG-COMM-REF"), doc_frags)
61+
diag_comm_snref = None
62+
if (diag_comm_snref_elem := et_element.find("DIAG-COMM-SNREF")) is not None:
63+
diag_comm_snref = odxrequire(diag_comm_snref_elem.get("SHORT-NAME"))
64+
65+
in_param_if_snref = None
66+
if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
67+
in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
68+
69+
if et_element.find("IN-PARAM-IF-SNPATHREF") is not None:
70+
warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
71+
72+
out_param_if_snref = None
73+
if (out_param_if_snref_elem := et_element.find("OUT-PARAM-IF-SNREF")) is not None:
74+
out_param_if_snref = odxrequire(out_param_if_snref_elem.get("SHORT-NAME"))
75+
76+
if et_element.find("OUT-PARAM-IF-SNPATHREF") is not None:
77+
warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
78+
79+
value_type_raw = None
80+
if (value_type_str := et_element.get("VALUE-TYPE")) is not None:
81+
try:
82+
value_type_raw = CommRelationValueType(value_type_str)
83+
except ValueError:
84+
odxraise(f"Encountered unknown comm relation value type '{value_type_str}'")
85+
86+
return CommRelation(
87+
description=description,
88+
relation_type=relation_type,
89+
diag_comm_ref=diag_comm_ref,
90+
diag_comm_snref=diag_comm_snref,
91+
in_param_if_snref=in_param_if_snref,
92+
out_param_if_snref=out_param_if_snref,
93+
value_type_raw=value_type_raw)
94+
95+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
96+
return {}
97+
98+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
99+
if self.diag_comm_ref is not None:
100+
self._diag_comm = odxlinks.resolve(self.diag_comm_ref, DiagComm)
101+
102+
def _resolve_snrefs(self, context: SnRefContext) -> None:
103+
diag_layer = odxrequire(context.diag_layer)
104+
105+
if self.diag_comm_snref is not None:
106+
self._diag_comm = resolve_snref(self.diag_comm_snref, diag_layer.diag_comms, DiagComm)
107+
108+
service = self.diag_comm
109+
if not isinstance(service, DiagService):
110+
odxraise(f"DIAG-VARIABLE references non-service {type(service).__name__} "
111+
f"diagnostic communication")
112+
113+
self._in_param_if = None
114+
if self.in_param_if_snref is not None:
115+
self._in_param_if = resolve_snref(self.in_param_if_snref,
116+
odxrequire(service.request).parameters, Parameter)
117+
118+
self._out_param_if = None
119+
if self.out_param_if_snref is not None:
120+
self._out_param_if = resolve_snref(self.out_param_if_snref,
121+
odxrequire(service.positive_responses[0]).parameters,
122+
Parameter)

odxtools/diaglayerraw.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from .diagdatadictionaryspec import DiagDataDictionarySpec
1515
from .diaglayertype import DiagLayerType
1616
from .diagservice import DiagService
17+
from .diagvariable import DiagVariable
18+
from .dyndefinedspec import DynDefinedSpec
1719
from .ecuvariantpattern import EcuVariantPattern
1820
from .element import IdentifiableElement
1921
from .exceptions import odxassert, odxraise, odxrequire
@@ -29,6 +31,7 @@
2931
from .specialdatagroup import SpecialDataGroup
3032
from .statechart import StateChart
3133
from .utils import dataclass_fields_asdict
34+
from .variablegroup import VariableGroup
3235

3336

3437
@dataclass
@@ -63,11 +66,15 @@ class DiagLayerRaw(IdentifiableElement):
6366
ecu_variant_patterns: List[EcuVariantPattern]
6467
comparam_spec_ref: Optional[OdxLinkRef]
6568
prot_stack_snref: Optional[str]
66-
# diag_variables: List[DiagVariable] # TODO
67-
# diag_variable_groups: List[DiagVariableGroup] # TODO
68-
# dyn_defined_spec: Optional[DynDefinedSpec] # TODO
69+
diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]]
70+
variable_groups: NamedItemList[VariableGroup]
71+
dyn_defined_spec: Optional[DynDefinedSpec]
6972
# base_variant_patterns: List[EcuVariantPattern] # TODO
7073

74+
@property
75+
def diag_variables(self) -> NamedItemList[DiagVariable]:
76+
return self._diag_variables
77+
7178
@staticmethod
7279
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayerRaw":
7380
try:
@@ -178,6 +185,29 @@ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) ->
178185
if (prot_stack_snref_elem := et_element.find("PROT-STACK-SNREF")) is not None:
179186
prot_stack_snref = odxrequire(prot_stack_snref_elem.get("SHORT-NAME"))
180187

188+
diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
189+
if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
190+
for dv_proxy_elem in dv_elems:
191+
dv_proxy: Union[OdxLinkRef, DiagVariable]
192+
if dv_proxy_elem.tag == "DIAG-VARIABLE-REF":
193+
dv_proxy = OdxLinkRef.from_et(dv_proxy_elem, doc_frags)
194+
elif dv_proxy_elem.tag == "DIAG-VARIABLE":
195+
dv_proxy = DiagVariable.from_et(dv_proxy_elem, doc_frags)
196+
else:
197+
odxraise("DIAG-VARIABLES tags may only contain "
198+
"DIAG-VARIABLE and DIAG-VARIABLE-REF subtags")
199+
200+
diag_variables_raw.append(dv_proxy)
201+
202+
variable_groups = NamedItemList([
203+
VariableGroup.from_et(vg_elem, doc_frags)
204+
for vg_elem in et_element.iterfind("VARIABLE-GROUPS/VARIABLE-GROUP")
205+
])
206+
207+
dyn_defined_spec = None
208+
if (dds_elem := et_element.find("DYN-DEFINED-SPEC")) is not None:
209+
dyn_defined_spec = DynDefinedSpec.from_et(dds_elem, doc_frags)
210+
181211
# Create DiagLayer
182212
return DiagLayerRaw(
183213
variant_type=variant_type,
@@ -199,6 +229,9 @@ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) ->
199229
ecu_variant_patterns=ecu_variant_patterns,
200230
comparam_spec_ref=comparam_spec_ref,
201231
prot_stack_snref=prot_stack_snref,
232+
diag_variables_raw=diag_variables_raw,
233+
variable_groups=variable_groups,
234+
dyn_defined_spec=dyn_defined_spec,
202235
**kwargs)
203236

204237
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -236,6 +269,11 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
236269
odxlinks.update(parent_ref._build_odxlinks())
237270
for comparam in self.comparams:
238271
odxlinks.update(comparam._build_odxlinks())
272+
for dv_proxy in self.diag_variables_raw:
273+
if not isinstance(dv_proxy, OdxLinkRef):
274+
odxlinks.update(dv_proxy._build_odxlinks())
275+
if self.dyn_defined_spec is not None:
276+
odxlinks.update(self.dyn_defined_spec._build_odxlinks())
239277

240278
return odxlinks
241279

@@ -281,6 +319,19 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
281319
for comparam in self.comparams:
282320
comparam._resolve_odxlinks(odxlinks)
283321

322+
self._diag_variables: NamedItemList[DiagVariable] = NamedItemList()
323+
for dv_proxy in self.diag_variables_raw:
324+
if isinstance(dv_proxy, OdxLinkRef):
325+
dv = odxlinks.resolve(dv_proxy, DiagVariable)
326+
else:
327+
dv_proxy._resolve_odxlinks(odxlinks)
328+
dv = dv_proxy
329+
330+
self._diag_variables.append(dv)
331+
332+
if self.dyn_defined_spec is not None:
333+
self.dyn_defined_spec._resolve_odxlinks(odxlinks)
334+
284335
def _resolve_snrefs(self, context: SnRefContext) -> None:
285336
self._prot_stack: Optional[ProtStack] = None
286337
if self.prot_stack_snref is not None:
@@ -322,6 +373,13 @@ def _resolve_snrefs(self, context: SnRefContext) -> None:
322373
for comparam in self.comparams:
323374
comparam._resolve_snrefs(context)
324375

376+
for dv_proxy in self.diag_variables_raw:
377+
if not isinstance(dv_proxy, OdxLinkRef):
378+
dv_proxy._resolve_snrefs(context)
379+
380+
if self.dyn_defined_spec is not None:
381+
self.dyn_defined_spec._resolve_snrefs(context)
382+
325383
@property
326384
def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
327385
return self._comparam_spec

odxtools/diagvariable.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass
3+
from typing import Any, Dict, List, Optional
4+
from xml.etree import ElementTree
5+
6+
from .admindata import AdminData
7+
from .commrelation import CommRelation
8+
from .element import IdentifiableElement
9+
from .exceptions import odxrequire
10+
from .nameditemlist import NamedItemList
11+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12+
from .odxtypes import odxstr_to_bool
13+
from .snrefcontext import SnRefContext
14+
from .specialdatagroup import SpecialDataGroup
15+
from .swvariable import SwVariable
16+
from .utils import dataclass_fields_asdict
17+
from .variablegroup import VariableGroup
18+
19+
20+
@dataclass
21+
class DiagVariable(IdentifiableElement):
22+
"""Representation of a diagnostic variable
23+
"""
24+
25+
admin_data: Optional[AdminData]
26+
variable_group_ref: OdxLinkRef
27+
sw_variables: List[SwVariable]
28+
comm_relations: List[CommRelation]
29+
#snref_to_tablerow: Optional[SnrefToTableRow] # TODO
30+
sdgs: List[SpecialDataGroup]
31+
is_read_before_write_raw: Optional[bool]
32+
33+
@property
34+
def variable_group(self) -> VariableGroup:
35+
return self._variable_group
36+
37+
@property
38+
def is_read_before_write(self) -> bool:
39+
return self.is_read_before_write_raw is True
40+
41+
@staticmethod
42+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagVariable":
43+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
44+
45+
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
46+
variable_group_ref = odxrequire(
47+
OdxLinkRef.from_et(et_element.find("VARIABLE-GROUP-REF"), doc_frags))
48+
sw_variables = NamedItemList([
49+
SwVariable.from_et(swv_elem, doc_frags)
50+
for swv_elem in et_element.iterfind("SW-VARIABLES/SW-VARIABLE")
51+
])
52+
comm_relations = [
53+
CommRelation.from_et(cr_elem, doc_frags)
54+
for cr_elem in et_element.iterfind("COMM-RELATIONS/COMM-RELATION")
55+
]
56+
sdgs = [
57+
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
58+
]
59+
is_read_before_write_raw = odxstr_to_bool(et_element.get("IS-READ-BEFORE-WRITE"))
60+
61+
return DiagVariable(
62+
admin_data=admin_data,
63+
variable_group_ref=variable_group_ref,
64+
sw_variables=sw_variables,
65+
comm_relations=comm_relations,
66+
sdgs=sdgs,
67+
is_read_before_write_raw=is_read_before_write_raw,
68+
**kwargs)
69+
70+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
71+
result = {self.odx_id: self}
72+
73+
if self.admin_data is not None:
74+
result.update(self.admin_data._build_odxlinks())
75+
76+
for sdg in self.sdgs:
77+
result.update(sdg._build_odxlinks())
78+
79+
for cr in self.comm_relations:
80+
result.update(cr._build_odxlinks())
81+
82+
return result
83+
84+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
85+
self._variable_group = odxlinks.resolve(self.variable_group_ref, VariableGroup)
86+
87+
if self.admin_data is not None:
88+
self.admin_data._resolve_odxlinks(odxlinks)
89+
90+
for sdg in self.sdgs:
91+
sdg._resolve_odxlinks(odxlinks)
92+
93+
for cr in self.comm_relations:
94+
cr._resolve_odxlinks(odxlinks)
95+
96+
def _resolve_snrefs(self, context: SnRefContext) -> None:
97+
if self.admin_data is not None:
98+
self.admin_data._resolve_snrefs(context)
99+
100+
for sdg in self.sdgs:
101+
sdg._resolve_snrefs(context)
102+
103+
for cr in self.comm_relations:
104+
cr._resolve_snrefs(context)

0 commit comments

Comments
 (0)