odxtools 9.4.1__py3-none-any.whl → 9.6.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/additionalaudience.py +2 -2
- odxtools/admindata.py +3 -0
- odxtools/audience.py +9 -13
- odxtools/basecomparam.py +1 -2
- odxtools/basevariantpattern.py +38 -0
- odxtools/basicstructure.py +34 -35
- odxtools/commrelation.py +2 -1
- odxtools/companydata.py +1 -2
- odxtools/companyspecificinfo.py +3 -0
- odxtools/comparam.py +16 -8
- odxtools/comparaminstance.py +12 -12
- odxtools/comparamspec.py +4 -3
- odxtools/comparamsubset.py +26 -24
- odxtools/compumethods/compuconst.py +4 -4
- odxtools/compumethods/limit.py +9 -9
- odxtools/compumethods/linearsegment.py +8 -8
- odxtools/dataobjectproperty.py +16 -18
- odxtools/description.py +4 -2
- odxtools/determinenumberofitems.py +4 -4
- odxtools/diagcodedtype.py +20 -20
- odxtools/diagcomm.py +61 -41
- odxtools/diagdatadictionaryspec.py +51 -55
- odxtools/diaglayercontainer.py +25 -25
- odxtools/diaglayers/basevariant.py +5 -0
- odxtools/diaglayers/basevariantraw.py +7 -1
- odxtools/diaglayers/diaglayerraw.py +26 -27
- odxtools/diaglayers/ecuvariant.py +16 -0
- odxtools/diagnostictroublecode.py +13 -10
- odxtools/diagservice.py +48 -50
- odxtools/diagvariable.py +10 -8
- odxtools/docrevision.py +5 -5
- odxtools/dtcdop.py +17 -17
- odxtools/dynamicendmarkerfield.py +8 -8
- odxtools/dynamiclengthfield.py +2 -0
- odxtools/dyndefinedspec.py +21 -8
- odxtools/ecuvariantpattern.py +20 -9
- odxtools/encodestate.py +3 -3
- odxtools/endofpdufield.py +7 -9
- odxtools/environmentdatadescription.py +9 -20
- odxtools/field.py +21 -21
- odxtools/inputparam.py +15 -14
- odxtools/leadinglengthinfotype.py +4 -4
- odxtools/matchingbasevariantparameter.py +38 -0
- odxtools/matchingparameter.py +108 -28
- odxtools/minmaxlengthtype.py +6 -6
- odxtools/multiplexer.py +38 -39
- odxtools/multiplexercase.py +3 -6
- odxtools/multiplexerdefaultcase.py +3 -6
- odxtools/multiplexerswitchkey.py +4 -4
- odxtools/negoutputparam.py +6 -9
- odxtools/odxlink.py +21 -5
- odxtools/odxtypes.py +7 -6
- odxtools/outputparam.py +9 -8
- odxtools/parameterinfo.py +1 -1
- odxtools/parameters/codedconstparameter.py +28 -27
- odxtools/parameters/dynamicparameter.py +9 -9
- odxtools/parameters/lengthkeyparameter.py +18 -18
- odxtools/parameters/matchingrequestparameter.py +15 -15
- odxtools/parameters/nrcconstparameter.py +32 -24
- odxtools/parameters/parameter.py +35 -37
- odxtools/parameters/parameterwithdop.py +6 -6
- odxtools/parameters/physicalconstantparameter.py +19 -20
- odxtools/parameters/reservedparameter.py +10 -11
- odxtools/parameters/systemparameter.py +10 -11
- odxtools/parameters/tableentryparameter.py +19 -20
- odxtools/parameters/tablekeyparameter.py +0 -2
- odxtools/parameters/tablestructparameter.py +27 -21
- odxtools/parameters/valueparameter.py +20 -20
- odxtools/parentref.py +6 -7
- odxtools/physicaldimension.py +11 -11
- odxtools/physicaltype.py +9 -14
- odxtools/preconditionstateref.py +85 -0
- odxtools/progcode.py +1 -2
- odxtools/protstack.py +4 -4
- odxtools/relateddoc.py +3 -4
- odxtools/scaleconstr.py +0 -1
- odxtools/singleecujob.py +8 -4
- odxtools/specialdata.py +10 -9
- odxtools/specialdatagroup.py +1 -0
- odxtools/standardlengthtype.py +18 -18
- odxtools/statechart.py +10 -6
- odxtools/statemachine.py +186 -0
- odxtools/statetransitionref.py +231 -0
- odxtools/structure.py +4 -4
- odxtools/subcomponent.py +78 -11
- odxtools/table.py +23 -13
- odxtools/tablerow.py +86 -69
- odxtools/teammember.py +4 -4
- odxtools/templates/macros/printBaseVariant.xml.jinja2 +4 -9
- odxtools/templates/macros/printBaseVariantPattern.xml.jinja2 +32 -0
- odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
- odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
- odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
- odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
- odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +7 -6
- odxtools/templates/macros/printParam.xml.jinja2 +5 -5
- odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
- odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
- odxtools/templates/macros/printTable.xml.jinja2 +13 -9
- odxtools/text.py +35 -0
- odxtools/unit.py +1 -3
- odxtools/unitgroup.py +6 -8
- odxtools/variantmatcher.py +209 -0
- odxtools/variantpattern.py +38 -0
- odxtools/version.py +2 -2
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/RECORD +111 -102
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
- odxtools/createecuvariantpatterns.py +0 -18
- odxtools/ecuvariantmatcher.py +0 -171
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
- {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .exceptions import odxassert, odxrequire
|
7
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
8
|
+
from .odxtypes import ParameterValueDict
|
9
|
+
from .parameters.parameter import Parameter
|
10
|
+
from .snrefcontext import SnRefContext
|
11
|
+
from .state import State
|
12
|
+
from .statetransitionref import _check_applies
|
13
|
+
from .utils import dataclass_fields_asdict
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from .statemachine import StateMachine
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class PreConditionStateRef(OdxLinkRef):
|
21
|
+
"""
|
22
|
+
This class represents the PRE-CONDITION-STATE-REF XML tag.
|
23
|
+
"""
|
24
|
+
value: Optional[str]
|
25
|
+
|
26
|
+
in_param_if_snref: Optional[str]
|
27
|
+
in_param_if_snpathref: Optional[str]
|
28
|
+
|
29
|
+
@property
|
30
|
+
def state(self) -> "State":
|
31
|
+
return self._state
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def from_et( # type: ignore[override]
|
35
|
+
et_element: ElementTree.Element,
|
36
|
+
doc_frags: List[OdxDocFragment]) -> "PreConditionStateRef":
|
37
|
+
kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, doc_frags))
|
38
|
+
|
39
|
+
value = et_element.findtext("VALUE")
|
40
|
+
|
41
|
+
in_param_if_snref = None
|
42
|
+
if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
|
43
|
+
in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
|
44
|
+
|
45
|
+
in_param_if_snpathref = None
|
46
|
+
if (in_param_if_snpathref_elem := et_element.find("IN-PARAM-IF-SNPATHREF")) is not None:
|
47
|
+
in_param_if_snpathref = odxrequire(in_param_if_snpathref_elem.get("SHORT-NAME-PATH"))
|
48
|
+
|
49
|
+
return PreConditionStateRef(
|
50
|
+
value=value,
|
51
|
+
in_param_if_snref=in_param_if_snref,
|
52
|
+
in_param_if_snpathref=in_param_if_snpathref,
|
53
|
+
**kwargs)
|
54
|
+
|
55
|
+
def __post_init__(self) -> None:
|
56
|
+
if self.value is not None:
|
57
|
+
odxassert(self.in_param_if_snref is not None or self.in_param_if_snref is not None,
|
58
|
+
"If VALUE is specified, a parameter must be referenced")
|
59
|
+
|
60
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
61
|
+
return {}
|
62
|
+
|
63
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
64
|
+
self._state = odxlinks.resolve(self, State)
|
65
|
+
|
66
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
67
|
+
pass
|
68
|
+
|
69
|
+
def applies(self, state_machine: "StateMachine", params: List[Parameter],
|
70
|
+
param_value_dict: ParameterValueDict) -> bool:
|
71
|
+
"""Given a state machine, evaluate whether the precondition is fulfilled or not
|
72
|
+
|
73
|
+
Note that the specification is unclear about what the
|
74
|
+
parameters are: It says "The optional VALUE together with the
|
75
|
+
also optional IN-PARAM-IF snref at STATE-TRANSITION-REF and
|
76
|
+
PRE-CONDITION-STATE-REF can be used if the STATE-TRANSITIONs
|
77
|
+
and pre-condition STATEs are dependent on the values of the
|
78
|
+
referenced PARAMs.", but it does not specify what the
|
79
|
+
"referenced PARAMs" are. For the state transition refs, let's
|
80
|
+
assume that they are the parameters of a positive response
|
81
|
+
received from the ECU, whilst we assume that they are the
|
82
|
+
parameters of the request for PRE-CONDITION-STATE-REFs.
|
83
|
+
"""
|
84
|
+
|
85
|
+
return _check_applies(self, state_machine, params, param_value_dict)
|
odxtools/progcode.py
CHANGED
@@ -31,7 +31,6 @@ class ProgCode:
|
|
31
31
|
@staticmethod
|
32
32
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ProgCode":
|
33
33
|
code_file = odxrequire(et_element.findtext("CODE-FILE"))
|
34
|
-
|
35
34
|
encryption = et_element.findtext("ENCRYPTION")
|
36
35
|
syntax = odxrequire(et_element.findtext("SYNTAX"))
|
37
36
|
revision = odxrequire(et_element.findtext("REVISION"))
|
@@ -44,9 +43,9 @@ class ProgCode:
|
|
44
43
|
|
45
44
|
return ProgCode(
|
46
45
|
code_file=code_file,
|
46
|
+
encryption=encryption,
|
47
47
|
syntax=syntax,
|
48
48
|
revision=revision,
|
49
|
-
encryption=encryption,
|
50
49
|
entrypoint=entrypoint,
|
51
50
|
library_refs=library_refs,
|
52
51
|
)
|
odxtools/protstack.py
CHANGED
@@ -19,6 +19,10 @@ class ProtStack(IdentifiableElement):
|
|
19
19
|
physical_link_type: str
|
20
20
|
comparam_subset_refs: List[OdxLinkRef]
|
21
21
|
|
22
|
+
@property
|
23
|
+
def comparam_subsets(self) -> NamedItemList[ComparamSubset]:
|
24
|
+
return self._comparam_subsets
|
25
|
+
|
22
26
|
@staticmethod
|
23
27
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ProtStack":
|
24
28
|
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
@@ -47,7 +51,3 @@ class ProtStack(IdentifiableElement):
|
|
47
51
|
|
48
52
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
49
53
|
pass
|
50
|
-
|
51
|
-
@property
|
52
|
-
def comparam_subsets(self) -> NamedItemList[ComparamSubset]:
|
53
|
-
return self._comparam_subsets
|
odxtools/relateddoc.py
CHANGED
@@ -11,20 +11,19 @@ from .xdoc import XDoc
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class RelatedDoc:
|
14
|
-
description: Optional[Description]
|
15
14
|
xdoc: Optional[XDoc]
|
15
|
+
description: Optional[Description]
|
16
16
|
|
17
17
|
@staticmethod
|
18
18
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "RelatedDoc":
|
19
|
-
description = Description.from_et(et_element.find("DESC"), doc_frags)
|
20
|
-
|
21
19
|
xdoc: Optional[XDoc] = None
|
22
20
|
if (xdoc_elem := et_element.find("XDOC")) is not None:
|
23
21
|
xdoc = XDoc.from_et(xdoc_elem, doc_frags)
|
22
|
+
description = Description.from_et(et_element.find("DESC"), doc_frags)
|
24
23
|
|
25
24
|
return RelatedDoc(
|
26
|
-
description=description,
|
27
25
|
xdoc=xdoc,
|
26
|
+
description=description,
|
28
27
|
)
|
29
28
|
|
30
29
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
odxtools/scaleconstr.py
CHANGED
@@ -35,7 +35,6 @@ class ScaleConstr:
|
|
35
35
|
value_type: DataType) -> "ScaleConstr":
|
36
36
|
short_label = et_element.findtext("SHORT-LABEL")
|
37
37
|
description = Description.from_et(et_element.find("DESC"), doc_frags)
|
38
|
-
|
39
38
|
lower_limit = Limit.limit_from_et(
|
40
39
|
odxrequire(et_element.find("LOWER-LIMIT")), doc_frags, value_type=value_type)
|
41
40
|
upper_limit = Limit.limit_from_et(
|
odxtools/singleecujob.py
CHANGED
@@ -18,12 +18,16 @@ from .utils import dataclass_fields_asdict
|
|
18
18
|
class SingleEcuJob(DiagComm):
|
19
19
|
"""A single ECU job is a diagnostic communication primitive.
|
20
20
|
|
21
|
-
A single ECU job is more complex than a diagnostic service and is
|
22
|
-
In particular, the job is
|
21
|
+
A single ECU job is more complex than a diagnostic service and is
|
22
|
+
not provided natively by the ECU. In particular, the job is
|
23
|
+
defined in external programs which are referenced by the attribute
|
24
|
+
`.prog_codes`.
|
23
25
|
|
24
|
-
In contrast to "multiple ECU jobs", a single ECU job only
|
26
|
+
In contrast to "multiple ECU jobs", a single ECU job only involves
|
27
|
+
calls to services provided by a single ECU.
|
25
28
|
|
26
|
-
Single ECU jobs are defined in section 7.3.5.7 of the ASAM MCD-2
|
29
|
+
Single ECU jobs are defined in section 7.3.5.7 of the ASAM MCD-2
|
30
|
+
standard.
|
27
31
|
"""
|
28
32
|
|
29
33
|
prog_codes: List[ProgCode]
|
odxtools/specialdata.py
CHANGED
@@ -9,19 +9,11 @@ from .snrefcontext import SnRefContext
|
|
9
9
|
|
10
10
|
@dataclass
|
11
11
|
class SpecialData:
|
12
|
+
"""This corresponds to the SD XML tag"""
|
12
13
|
semantic_info: Optional[str] # the "SI" attribute
|
13
14
|
text_identifier: Optional[str] # the "TI" attribute, specifies the language used
|
14
15
|
value: str
|
15
16
|
|
16
|
-
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
17
|
-
return {}
|
18
|
-
|
19
|
-
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
20
|
-
pass
|
21
|
-
|
22
|
-
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
23
|
-
pass
|
24
|
-
|
25
17
|
@staticmethod
|
26
18
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "SpecialData":
|
27
19
|
semantic_info = et_element.get("SI")
|
@@ -30,3 +22,12 @@ class SpecialData:
|
|
30
22
|
|
31
23
|
return SpecialData(
|
32
24
|
semantic_info=semantic_info, text_identifier=text_identifier, value=value)
|
25
|
+
|
26
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
27
|
+
return {}
|
28
|
+
|
29
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
30
|
+
pass
|
31
|
+
|
32
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
33
|
+
pass
|
odxtools/specialdatagroup.py
CHANGED
@@ -11,6 +11,7 @@ from .specialdatagroupcaption import SpecialDataGroupCaption
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class SpecialDataGroup:
|
14
|
+
"""This corresponds to the SDG XML tag"""
|
14
15
|
sdg_caption: Optional[SpecialDataGroupCaption]
|
15
16
|
sdg_caption_ref: Optional[OdxLinkRef]
|
16
17
|
values: List[Union["SpecialDataGroup", SpecialData]]
|
odxtools/standardlengthtype.py
CHANGED
@@ -3,14 +3,14 @@ from dataclasses import dataclass
|
|
3
3
|
from typing import List, Literal, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
-
from typing_extensions import
|
6
|
+
from typing_extensions import override
|
7
7
|
|
8
8
|
from .decodestate import DecodeState
|
9
9
|
from .diagcodedtype import DctType, DiagCodedType
|
10
10
|
from .encodestate import EncodeState
|
11
11
|
from .exceptions import odxassert, odxraise, odxrequire
|
12
12
|
from .odxlink import OdxDocFragment
|
13
|
-
from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
|
13
|
+
from .odxtypes import AtomicOdxType, BytesTypes, DataType, odxstr_to_bool
|
14
14
|
from .utils import dataclass_fields_asdict
|
15
15
|
|
16
16
|
|
@@ -21,6 +21,14 @@ class StandardLengthType(DiagCodedType):
|
|
21
21
|
bit_mask: Optional[int]
|
22
22
|
is_condensed_raw: Optional[bool]
|
23
23
|
|
24
|
+
@property
|
25
|
+
def dct_type(self) -> DctType:
|
26
|
+
return "STANDARD-LENGTH-TYPE"
|
27
|
+
|
28
|
+
@property
|
29
|
+
def is_condensed(self) -> bool:
|
30
|
+
return self.is_condensed_raw is True
|
31
|
+
|
24
32
|
@staticmethod
|
25
33
|
@override
|
26
34
|
def from_et(et_element: ElementTree.Element,
|
@@ -43,14 +51,6 @@ class StandardLengthType(DiagCodedType):
|
|
43
51
|
return StandardLengthType(
|
44
52
|
bit_length=bit_length, bit_mask=bit_mask, is_condensed_raw=is_condensed_raw, **kwargs)
|
45
53
|
|
46
|
-
@property
|
47
|
-
def dct_type(self) -> DctType:
|
48
|
-
return "STANDARD-LENGTH-TYPE"
|
49
|
-
|
50
|
-
@property
|
51
|
-
def is_condensed(self) -> bool:
|
52
|
-
return self.is_condensed_raw is True
|
53
|
-
|
54
54
|
def __post_init__(self) -> None:
|
55
55
|
if self.bit_mask is not None:
|
56
56
|
maskable_types = (DataType.A_UINT32, DataType.A_INT32, DataType.A_BYTEFIELD)
|
@@ -91,7 +91,7 @@ class StandardLengthType(DiagCodedType):
|
|
91
91
|
return used_mask.to_bytes((bit_sz + 7) // 8, endianness)
|
92
92
|
|
93
93
|
sz: int
|
94
|
-
if isinstance(internal_value,
|
94
|
+
if isinstance(internal_value, BytesTypes):
|
95
95
|
sz = len(bytes(internal_value))
|
96
96
|
else:
|
97
97
|
sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
|
@@ -107,7 +107,7 @@ class StandardLengthType(DiagCodedType):
|
|
107
107
|
|
108
108
|
if self.is_condensed:
|
109
109
|
int_value: int
|
110
|
-
if isinstance(internal_value,
|
110
|
+
if isinstance(internal_value, BytesTypes):
|
111
111
|
int_value = int.from_bytes(internal_value, 'big')
|
112
112
|
elif isinstance(internal_value, int):
|
113
113
|
int_value = internal_value
|
@@ -126,14 +126,14 @@ class StandardLengthType(DiagCodedType):
|
|
126
126
|
|
127
127
|
mask_bit += 1
|
128
128
|
|
129
|
-
if isinstance(internal_value,
|
129
|
+
if isinstance(internal_value, BytesTypes):
|
130
130
|
return result.to_bytes(len(internal_value), 'big')
|
131
131
|
|
132
132
|
return result
|
133
133
|
|
134
134
|
if isinstance(internal_value, int):
|
135
135
|
return internal_value & self.bit_mask
|
136
|
-
if isinstance(internal_value,
|
136
|
+
if isinstance(internal_value, BytesTypes):
|
137
137
|
int_value = int.from_bytes(internal_value, 'big')
|
138
138
|
int_value &= self.bit_mask
|
139
139
|
return int_value.to_bytes(len(bytes(internal_value)), 'big')
|
@@ -146,7 +146,7 @@ class StandardLengthType(DiagCodedType):
|
|
146
146
|
return raw_value
|
147
147
|
if self.is_condensed:
|
148
148
|
int_value: int
|
149
|
-
if isinstance(raw_value,
|
149
|
+
if isinstance(raw_value, BytesTypes):
|
150
150
|
int_value = int.from_bytes(raw_value, 'big')
|
151
151
|
elif isinstance(raw_value, int):
|
152
152
|
int_value = raw_value
|
@@ -164,16 +164,16 @@ class StandardLengthType(DiagCodedType):
|
|
164
164
|
|
165
165
|
mask_bit += 1
|
166
166
|
|
167
|
-
if isinstance(raw_value,
|
167
|
+
if isinstance(raw_value, BytesTypes):
|
168
168
|
return result.to_bytes(len(raw_value), 'big')
|
169
169
|
|
170
170
|
return result
|
171
171
|
if isinstance(raw_value, int):
|
172
172
|
return raw_value & self.bit_mask
|
173
|
-
if isinstance(raw_value,
|
173
|
+
if isinstance(raw_value, BytesTypes):
|
174
174
|
int_value = int.from_bytes(raw_value, 'big')
|
175
175
|
int_value &= self.bit_mask
|
176
|
-
return int_value
|
176
|
+
return int_value.to_bytes(len(raw_value), 'big')
|
177
177
|
|
178
178
|
odxraise(f'Can not apply a bit_mask on a value of type {type(raw_value)}')
|
179
179
|
return raw_value
|
odxtools/statechart.py
CHANGED
@@ -30,6 +30,7 @@ class StateChart(IdentifiableElement):
|
|
30
30
|
@staticmethod
|
31
31
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "StateChart":
|
32
32
|
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
33
|
+
|
33
34
|
semantic: str = odxrequire(et_element.findtext("SEMANTIC"))
|
34
35
|
|
35
36
|
state_transitions = [
|
@@ -54,15 +55,18 @@ class StateChart(IdentifiableElement):
|
|
54
55
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
55
56
|
odxlinks = {self.odx_id: self}
|
56
57
|
|
57
|
-
for st in self.states:
|
58
|
-
odxlinks.update(st._build_odxlinks())
|
59
|
-
|
60
58
|
for strans in self.state_transitions:
|
61
59
|
odxlinks.update(strans._build_odxlinks())
|
62
60
|
|
61
|
+
for st in self.states:
|
62
|
+
odxlinks.update(st._build_odxlinks())
|
63
|
+
|
63
64
|
return odxlinks
|
64
65
|
|
65
66
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
67
|
+
for strans in self.state_transitions:
|
68
|
+
strans._resolve_odxlinks(odxlinks)
|
69
|
+
|
66
70
|
for st in self.states:
|
67
71
|
st._resolve_odxlinks(odxlinks)
|
68
72
|
|
@@ -77,10 +81,10 @@ class StateChart(IdentifiableElement):
|
|
77
81
|
# does that mean?
|
78
82
|
self._start_state = resolve_snref(self.start_state_snref, self.states, State)
|
79
83
|
|
80
|
-
for st in self.states:
|
81
|
-
st._resolve_snrefs(context)
|
82
|
-
|
83
84
|
for strans in self.state_transitions:
|
84
85
|
strans._resolve_snrefs(context)
|
85
86
|
|
87
|
+
for st in self.states:
|
88
|
+
st._resolve_snrefs(context)
|
89
|
+
|
86
90
|
context.state_chart = None
|
odxtools/statemachine.py
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import TYPE_CHECKING, Any, Generator, Union
|
4
|
+
|
5
|
+
from .exceptions import odxraise
|
6
|
+
from .odxtypes import ParameterValueDict
|
7
|
+
from .state import State
|
8
|
+
from .statechart import StateChart
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from .diaglayers.diaglayer import DiagLayer
|
12
|
+
from .diagservice import DiagService
|
13
|
+
|
14
|
+
|
15
|
+
@dataclass
|
16
|
+
class StateMachine:
|
17
|
+
"""Objects of this class represent the runtime state of a state chart
|
18
|
+
|
19
|
+
It is used to track which services can be executed at a given time.
|
20
|
+
|
21
|
+
Usage example (using asynchronous communication routines):
|
22
|
+
|
23
|
+
```python
|
24
|
+
ecu = db.ecu_variants.my_ecu_variant
|
25
|
+
fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
|
26
|
+
|
27
|
+
for raw_request in (executor := fsm.execute(ecu.services.my_service,
|
28
|
+
param1="hello",
|
29
|
+
param2=123)):
|
30
|
+
# send raw request to the ECU (usually using ISO-TP or DoIP), and
|
31
|
+
# receive the response
|
32
|
+
await send_to_ecu(raw_request)
|
33
|
+
while odxtools.is_response_pending(raw_response := await receive_from_ecu()):
|
34
|
+
pass
|
35
|
+
|
36
|
+
executor.send(raw_response)
|
37
|
+
|
38
|
+
print(f"Request send: {fsm.succeeded}")
|
39
|
+
```
|
40
|
+
|
41
|
+
Alternatively, more of the glue functionality can be handled by
|
42
|
+
the calling code:
|
43
|
+
|
44
|
+
```python
|
45
|
+
|
46
|
+
ecu = db.ecu_variants.my_ecu_variant
|
47
|
+
fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
|
48
|
+
|
49
|
+
service = ecu.services.my_service
|
50
|
+
request_param_values = { "param1": "hello", "param2":123 }
|
51
|
+
if any(pcs.applies(fsm, service.request.parameters, **request_param_values)
|
52
|
+
for pcs in service.pre_condition_states):
|
53
|
+
|
54
|
+
# send raw request to the ECU (usually using ISO-TP or DoIP), and
|
55
|
+
# receive the response
|
56
|
+
raw_req = service.request.encode(**request_param_values)
|
57
|
+
await send_data_to_ecu(raw_req)
|
58
|
+
while odxtools.is_response_pending(raw_resp := await receive_from_ecu()):
|
59
|
+
pass
|
60
|
+
|
61
|
+
done = False
|
62
|
+
for decoded_resp_msg in ecu.decode_response(raw_resp, raw_req):
|
63
|
+
for stransref in service.state_transition_refs:
|
64
|
+
if stransref.execute(decoded_resp.parameters, decoded_resp_msg.param_dict):
|
65
|
+
done = True
|
66
|
+
break
|
67
|
+
if done:
|
68
|
+
break
|
69
|
+
else:
|
70
|
+
raise RuntimeException(f"Cannot execute request for service {service.short_name}")
|
71
|
+
```
|
72
|
+
|
73
|
+
"""
|
74
|
+
|
75
|
+
succeeded: bool
|
76
|
+
|
77
|
+
@property
|
78
|
+
def diag_layer(self) -> "DiagLayer":
|
79
|
+
return self._diag_layer
|
80
|
+
|
81
|
+
@property
|
82
|
+
def state_chart(self) -> StateChart:
|
83
|
+
return self._state_chart
|
84
|
+
|
85
|
+
@property
|
86
|
+
def active_state(self) -> State:
|
87
|
+
return self._active_state
|
88
|
+
|
89
|
+
@active_state.setter
|
90
|
+
def active_state(self, value: State) -> None:
|
91
|
+
self._active_state = value
|
92
|
+
|
93
|
+
def __init__(self, diag_layer: "DiagLayer", state_chart: StateChart) -> None:
|
94
|
+
self.succeeded = True
|
95
|
+
self._diag_layer = diag_layer
|
96
|
+
self._state_chart = state_chart
|
97
|
+
self._active_state = state_chart.start_state
|
98
|
+
|
99
|
+
def execute(self, service: "DiagService", **service_params: Any
|
100
|
+
) -> Generator[bytes, Union[bytes, bytearray, ParameterValueDict], None]:
|
101
|
+
"""Run a diagnostic service and update the state machine
|
102
|
+
depending on the outcome.
|
103
|
+
|
104
|
+
This simplifies error handling, en- and decoding etc.
|
105
|
+
|
106
|
+
Usage example (using asynchronous communication routines):
|
107
|
+
|
108
|
+
```python
|
109
|
+
ecu = db.ecu_variants.my_ecu_variant
|
110
|
+
fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
|
111
|
+
|
112
|
+
for raw_request in (executor := fsm.execute(ecu.services.my_service,
|
113
|
+
param1="hello",
|
114
|
+
param2=123)):
|
115
|
+
# send raw request to the ECU (usually using ISO-TP or DoIP), and
|
116
|
+
# receive the response
|
117
|
+
await send_to_ecu(raw_request)
|
118
|
+
while odxtools.is_response_pending(raw_response := await receive_from_ecu()):
|
119
|
+
pass
|
120
|
+
|
121
|
+
executor.send(raw_response)
|
122
|
+
|
123
|
+
print(f"Request send: {fsm.succeeded}")
|
124
|
+
```
|
125
|
+
|
126
|
+
"""
|
127
|
+
if service.request is None:
|
128
|
+
odxraise("Services without requests are not allowed in this context")
|
129
|
+
self.succeeded = False
|
130
|
+
return
|
131
|
+
|
132
|
+
# the service can be executed if any of the specified
|
133
|
+
# precondition states is fulfilled. (TODO: correct?)
|
134
|
+
if service.pre_condition_state_refs is not None:
|
135
|
+
if not any(
|
136
|
+
x.applies(self, service.request.parameters, service_params)
|
137
|
+
for x in service.pre_condition_state_refs):
|
138
|
+
# if all preconditions which are applicable are
|
139
|
+
# invalid (i.e., they evaluate to False), we must not
|
140
|
+
# execute the service.
|
141
|
+
self.succeeded = False
|
142
|
+
return
|
143
|
+
|
144
|
+
raw_req = service.request.encode(**service_params)
|
145
|
+
# ask the calling code to send the request to the ECU and
|
146
|
+
# report back the reply
|
147
|
+
raw_resp = yield raw_req
|
148
|
+
|
149
|
+
decoded_req_params = service.request.decode(raw_req)
|
150
|
+
for stransref in service.state_transition_refs:
|
151
|
+
# check if the state transition applies for the
|
152
|
+
# request. Note that the ODX specification is unclear
|
153
|
+
# about which kind of parameters are relevant for
|
154
|
+
# STATE-TRANSITION-REF
|
155
|
+
if stransref.execute(self, service.request.parameters, decoded_req_params):
|
156
|
+
self.succeeded = True
|
157
|
+
yield b''
|
158
|
+
return
|
159
|
+
|
160
|
+
if raw_resp is None:
|
161
|
+
raise RuntimeError("The calling code must send back a reply")
|
162
|
+
elif isinstance(raw_resp, (bytes, bytearray)):
|
163
|
+
for decoded_resp_msg in self.diag_layer.decode_response(raw_resp, raw_req):
|
164
|
+
for stransref in service.state_transition_refs:
|
165
|
+
# we only execute the first applicable state
|
166
|
+
# transition: The spec seems to imply a
|
167
|
+
# deterministic state machine and chaining
|
168
|
+
# transistions most likely is not what the user
|
169
|
+
# expects. (The spec seems to be a bit loose on
|
170
|
+
# this front...)
|
171
|
+
if stransref.execute(self, decoded_resp_msg.coding_object.parameters,
|
172
|
+
decoded_resp_msg.param_dict):
|
173
|
+
self.succeeded = True
|
174
|
+
yield b''
|
175
|
+
return
|
176
|
+
else:
|
177
|
+
resp_object = service.positive_responses[0]
|
178
|
+
for stransref in service.state_transition_refs:
|
179
|
+
if stransref.execute(self, resp_object.parameters, raw_resp):
|
180
|
+
self.succeeded = True
|
181
|
+
yield b''
|
182
|
+
return
|
183
|
+
|
184
|
+
self.succeeded = True
|
185
|
+
yield b''
|
186
|
+
return
|