odxtools 9.5.0__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 +5 -5
- 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/diaglayerraw.py +26 -27
- 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/encodestate.py +1 -2
- 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/matchingparameter.py +2 -3
- odxtools/minmaxlengthtype.py +7 -7
- 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 +4 -4
- 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 +10 -10
- odxtools/statechart.py +10 -6
- odxtools/statemachine.py +186 -0
- odxtools/statetransitionref.py +231 -0
- odxtools/structure.py +4 -4
- odxtools/subcomponent.py +72 -8
- odxtools/table.py +23 -13
- odxtools/tablerow.py +86 -69
- odxtools/teammember.py +4 -4
- 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/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/utils.py +0 -4
- odxtools/version.py +2 -2
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/RECORD +102 -96
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
- {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
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
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .basicstructure import BasicStructure
|
7
|
+
from .dataobjectproperty import DataObjectProperty
|
8
|
+
from .exceptions import odxassert, odxraise, odxrequire
|
9
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
10
|
+
from .odxtypes import ParameterValue, ParameterValueDict
|
11
|
+
from .parameters.codedconstparameter import CodedConstParameter
|
12
|
+
from .parameters.parameter import Parameter
|
13
|
+
from .parameters.physicalconstantparameter import PhysicalConstantParameter
|
14
|
+
from .parameters.tablekeyparameter import TableKeyParameter
|
15
|
+
from .parameters.tablestructparameter import TableStructParameter
|
16
|
+
from .parameters.valueparameter import ValueParameter
|
17
|
+
from .snrefcontext import SnRefContext
|
18
|
+
from .state import State
|
19
|
+
from .statemachine import StateMachine
|
20
|
+
from .statetransition import StateTransition
|
21
|
+
from .utils import dataclass_fields_asdict
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from .preconditionstateref import PreConditionStateRef
|
25
|
+
from .statemachine import StateMachine
|
26
|
+
from .tablerow import TableRow
|
27
|
+
|
28
|
+
|
29
|
+
def _resolve_in_param(
|
30
|
+
in_param_if_snref: Optional[str],
|
31
|
+
in_param_if_snpathref: Optional[str],
|
32
|
+
params: List[Parameter],
|
33
|
+
param_dict: ParameterValueDict,
|
34
|
+
) -> Tuple[Optional[Parameter], Optional[ParameterValue]]:
|
35
|
+
|
36
|
+
if in_param_if_snref is not None:
|
37
|
+
path_chunks = [in_param_if_snref]
|
38
|
+
elif in_param_if_snpathref is not None:
|
39
|
+
path_chunks = in_param_if_snpathref.split(".")
|
40
|
+
else:
|
41
|
+
return None, None
|
42
|
+
|
43
|
+
return _resolve_in_param_helper(params, param_dict, path_chunks)
|
44
|
+
|
45
|
+
|
46
|
+
def _resolve_in_param_helper(
|
47
|
+
params: List[Parameter],
|
48
|
+
param_dict: ParameterValueDict,
|
49
|
+
path_chunks: List[str],
|
50
|
+
) -> Tuple[Optional[Parameter], Optional[ParameterValue]]:
|
51
|
+
|
52
|
+
inner_param = resolve_snref(path_chunks[0], params, Parameter, lenient=True)
|
53
|
+
if inner_param is None:
|
54
|
+
return None, None
|
55
|
+
inner_param_value = param_dict.get(path_chunks[0])
|
56
|
+
|
57
|
+
if len(path_chunks) == 1:
|
58
|
+
return inner_param, inner_param_value
|
59
|
+
|
60
|
+
# deal with table parameters
|
61
|
+
if isinstance(inner_param, TableStructParameter):
|
62
|
+
if not isinstance(inner_param_value, tuple) or len(inner_param_value) == 2:
|
63
|
+
odxraise("Invalid value for table struct parameter")
|
64
|
+
return None, None
|
65
|
+
|
66
|
+
if TYPE_CHECKING:
|
67
|
+
tr = resolve_snref(inner_param_value[0], inner_param.table.table_rows, TableRow)
|
68
|
+
else:
|
69
|
+
tr = resolve_snref(inner_param_value[0], inner_param.table.table_rows)
|
70
|
+
|
71
|
+
if tr.structure is None:
|
72
|
+
odxraise("Table row must reference a structure")
|
73
|
+
return None, None
|
74
|
+
|
75
|
+
inner_param_list = tr.structure.parameters
|
76
|
+
inner_param_value = inner_param_value[1]
|
77
|
+
else:
|
78
|
+
dop = getattr(inner_param, "dop", None)
|
79
|
+
if isinstance(dop, BasicStructure):
|
80
|
+
inner_param_list = dop.parameters
|
81
|
+
else:
|
82
|
+
odxraise(f"Expected the referenced parameter to be composite")
|
83
|
+
|
84
|
+
if not isinstance(inner_param_value, dict):
|
85
|
+
odxraise(f"Expected the referenced parameter to be composite")
|
86
|
+
return None, None
|
87
|
+
|
88
|
+
return _resolve_in_param_helper(inner_param_list, inner_param_value, path_chunks[1:])
|
89
|
+
|
90
|
+
|
91
|
+
def _check_applies(ref: Union["StateTransitionRef",
|
92
|
+
"PreConditionStateRef"], state_machine: "StateMachine",
|
93
|
+
params: List[Parameter], param_value_dict: ParameterValueDict) -> bool:
|
94
|
+
if state_machine.active_state != ref.state:
|
95
|
+
# if the active state of the state machine is not the
|
96
|
+
# specified one, the precondition does not apply
|
97
|
+
return False
|
98
|
+
|
99
|
+
if ref.in_param_if_snref is None and ref.in_param_if_snpathref is None:
|
100
|
+
# no parameter given -> only the specified state is relevant
|
101
|
+
return True
|
102
|
+
|
103
|
+
param, param_value = \
|
104
|
+
_resolve_in_param(ref.in_param_if_snref, ref.in_param_if_snpathref, params, param_value_dict)
|
105
|
+
|
106
|
+
if param is None:
|
107
|
+
# The referenced parameter does not exist.
|
108
|
+
return False
|
109
|
+
elif not isinstance(
|
110
|
+
param,
|
111
|
+
(CodedConstParameter, PhysicalConstantParameter, TableKeyParameter, ValueParameter)):
|
112
|
+
# see checker rule 194 in section B.2 of the spec
|
113
|
+
odxraise(f"Parameter referenced by state transition ref is of "
|
114
|
+
f"invalid type {type(param).__name__}")
|
115
|
+
return False
|
116
|
+
elif isinstance(param, (CodedConstParameter, PhysicalConstantParameter,
|
117
|
+
TableKeyParameter)) and ref.value is not None:
|
118
|
+
# see checker rule 193 in section B.2 of the spec. Why can
|
119
|
+
# no values for constant parameters be specified? (This
|
120
|
+
# seems to be rather inconvenient...)
|
121
|
+
odxraise(f"No value may be specified for state transitions referencing "
|
122
|
+
f"parameters of type {type(param).__name__}")
|
123
|
+
return False
|
124
|
+
elif isinstance(param, ValueParameter):
|
125
|
+
# see checker rule 193 in section B.2 of the spec
|
126
|
+
if ref.value is None:
|
127
|
+
odxraise(f"An expected VALUE must be specified for preconditions "
|
128
|
+
f"referencing VALUE parameters")
|
129
|
+
return False
|
130
|
+
|
131
|
+
if param_value is None:
|
132
|
+
param_value = param.physical_default_value
|
133
|
+
if param_value is None:
|
134
|
+
odxraise(f"No value can be determined for parameter {param.short_name}")
|
135
|
+
return False
|
136
|
+
|
137
|
+
if param.dop is None or not isinstance(param.dop, DataObjectProperty):
|
138
|
+
odxraise(f"Parameter {param.short_name} uses a non-simple DOP")
|
139
|
+
return False
|
140
|
+
|
141
|
+
expected_value = param.dop.physical_type.base_data_type.from_string(ref.value)
|
142
|
+
|
143
|
+
if expected_value != param_value:
|
144
|
+
return False
|
145
|
+
|
146
|
+
return True
|
147
|
+
|
148
|
+
|
149
|
+
@dataclass
|
150
|
+
class StateTransitionRef(OdxLinkRef):
|
151
|
+
"""Describes a state transition that is to be potentially taken if
|
152
|
+
a diagnostic communication is executed
|
153
|
+
|
154
|
+
Besides the "raw" state transistion, state transition references
|
155
|
+
may also be conditional on the observed response of the ECU.
|
156
|
+
|
157
|
+
"""
|
158
|
+
value: Optional[str]
|
159
|
+
|
160
|
+
in_param_if_snref: Optional[str]
|
161
|
+
in_param_if_snpathref: Optional[str]
|
162
|
+
|
163
|
+
@property
|
164
|
+
def state_transition(self) -> StateTransition:
|
165
|
+
return self._state_transition
|
166
|
+
|
167
|
+
@property
|
168
|
+
def state(self) -> State:
|
169
|
+
return self.state_transition.source_state
|
170
|
+
|
171
|
+
@staticmethod
|
172
|
+
def from_et( # type: ignore[override]
|
173
|
+
et_element: ElementTree.Element,
|
174
|
+
doc_frags: List[OdxDocFragment]) -> "StateTransitionRef":
|
175
|
+
kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, doc_frags))
|
176
|
+
|
177
|
+
value = et_element.findtext("VALUE")
|
178
|
+
|
179
|
+
in_param_if_snref = None
|
180
|
+
if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
|
181
|
+
in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
|
182
|
+
|
183
|
+
in_param_if_snpathref = None
|
184
|
+
if (in_param_if_snpathref_elem := et_element.find("IN-PARAM-IF-SNPATHREF")) is not None:
|
185
|
+
in_param_if_snpathref = odxrequire(in_param_if_snpathref_elem.get("SHORT-NAME-PATH"))
|
186
|
+
|
187
|
+
return StateTransitionRef(
|
188
|
+
value=value,
|
189
|
+
in_param_if_snref=in_param_if_snref,
|
190
|
+
in_param_if_snpathref=in_param_if_snpathref,
|
191
|
+
**kwargs)
|
192
|
+
|
193
|
+
def __post_init__(self) -> None:
|
194
|
+
if self.value is not None:
|
195
|
+
odxassert(self.in_param_if_snref is not None or self.in_param_if_snref is not None,
|
196
|
+
"If VALUE is specified, a parameter must be referenced")
|
197
|
+
|
198
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
199
|
+
return {}
|
200
|
+
|
201
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
202
|
+
self._state_transition = odxlinks.resolve(self, StateTransition)
|
203
|
+
|
204
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
205
|
+
pass
|
206
|
+
|
207
|
+
def execute(self, state_machine: StateMachine, params: List[Parameter],
|
208
|
+
param_value_dict: ParameterValueDict) -> bool:
|
209
|
+
"""Update a StateMachine object if the state transition ought
|
210
|
+
to be executed based on the response received after executing a
|
211
|
+
diagnostic communication
|
212
|
+
|
213
|
+
Note that the specification is unclear about what the
|
214
|
+
parameters are: It says "The optional VALUE together with the
|
215
|
+
also optional IN-PARAM-IF snref at STATE-TRANSITION-REF and
|
216
|
+
PRE-CONDITION-STATE-REF can be used if the STATE-TRANSITIONs
|
217
|
+
and pre-condition STATEs are dependent on the values of the
|
218
|
+
referenced PARAMs.", but it does not specify what the
|
219
|
+
"referenced PARAMs" are. For the state transition refs, let's
|
220
|
+
assume that they are the parameters of a response received
|
221
|
+
from the ECU, whilst we assume that they are the parameters of
|
222
|
+
the request for PRE-CONDITION-STATE-REFs.
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
``True´` if the state transition was taken, else ``False``
|
226
|
+
"""
|
227
|
+
if _check_applies(self, state_machine, params, param_value_dict):
|
228
|
+
state_machine.active_state = self.state_transition.target_state
|
229
|
+
return True
|
230
|
+
|
231
|
+
return False
|
odxtools/structure.py
CHANGED
@@ -13,6 +13,10 @@ from .utils import dataclass_fields_asdict
|
|
13
13
|
class Structure(BasicStructure):
|
14
14
|
is_visible_raw: Optional[bool]
|
15
15
|
|
16
|
+
@property
|
17
|
+
def is_visible(self) -> bool:
|
18
|
+
return self.is_visible_raw in (True, None)
|
19
|
+
|
16
20
|
@staticmethod
|
17
21
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Structure":
|
18
22
|
"""Read a STRUCTURE element from XML."""
|
@@ -21,7 +25,3 @@ class Structure(BasicStructure):
|
|
21
25
|
is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
|
22
26
|
|
23
27
|
return Structure(is_visible_raw=is_visible_raw, **kwargs)
|
24
|
-
|
25
|
-
@property
|
26
|
-
def is_visible(self) -> bool:
|
27
|
-
return self.is_visible_raw in (True, None)
|
odxtools/subcomponent.py
CHANGED
@@ -9,7 +9,7 @@ from .dtcdop import DtcDop
|
|
9
9
|
from .element import IdentifiableElement, NamedElement
|
10
10
|
from .environmentdata import EnvironmentData
|
11
11
|
from .environmentdatadescription import EnvironmentDataDescription
|
12
|
-
from .exceptions import odxraise, odxrequire
|
12
|
+
from .exceptions import odxassert, odxraise, odxrequire
|
13
13
|
from .nameditemlist import NamedItemList
|
14
14
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
|
15
15
|
from .parameters.parameter import Parameter
|
@@ -38,6 +38,21 @@ class SubComponentPattern:
|
|
38
38
|
|
39
39
|
return SubComponentPattern(matching_parameters=matching_parameters)
|
40
40
|
|
41
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
42
|
+
result = {}
|
43
|
+
for mp in self.matching_parameters:
|
44
|
+
result.update(mp._build_odxlinks())
|
45
|
+
|
46
|
+
return result
|
47
|
+
|
48
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
49
|
+
for mp in self.matching_parameters:
|
50
|
+
mp._resolve_odxlinks(odxlinks)
|
51
|
+
|
52
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
53
|
+
for mp in self.matching_parameters:
|
54
|
+
mp._resolve_snrefs(context)
|
55
|
+
|
41
56
|
|
42
57
|
@dataclass
|
43
58
|
class SubComponentParamConnector(IdentifiableElement):
|
@@ -72,16 +87,18 @@ class SubComponentParamConnector(IdentifiableElement):
|
|
72
87
|
if elem.tag != "OUT-PARAM-IF-SNREF":
|
73
88
|
odxraise("Currently, only SNREFS are supported for OUT-PARAM-IF-REFS")
|
74
89
|
continue
|
75
|
-
|
76
|
-
|
90
|
+
else:
|
91
|
+
odxassert(elem.tag == "OUT-PARAM-IF-SNREF")
|
92
|
+
out_param_if_refs.append(odxrequire(elem.attrib.get("SHORT-NAME")))
|
77
93
|
|
78
94
|
in_param_if_refs = []
|
79
95
|
for elem in et_element.find("IN-PARAM-IF-REFS") or []:
|
80
96
|
if elem.tag != "IN-PARAM-IF-SNREF":
|
81
97
|
odxraise("Currently, only SNREFS are supported for IN-PARAM-IF-REFS")
|
82
98
|
continue
|
83
|
-
|
84
|
-
|
99
|
+
else:
|
100
|
+
odxassert(elem.tag == "IN-PARAM-IF-SNREF")
|
101
|
+
in_param_if_refs.append(odxrequire(elem.attrib.get("SHORT-NAME")))
|
85
102
|
|
86
103
|
return SubComponentParamConnector(
|
87
104
|
diag_comm_snref=diag_comm_snref,
|
@@ -106,13 +123,19 @@ class SubComponentParamConnector(IdentifiableElement):
|
|
106
123
|
if not self._service.positive_responses:
|
107
124
|
odxraise()
|
108
125
|
return
|
109
|
-
request = odxrequire(service.request)
|
110
|
-
response = service.positive_responses[0]
|
111
126
|
|
127
|
+
request = odxrequire(service.request)
|
112
128
|
in_param_ifs = []
|
113
129
|
for x in self.in_param_if_refs:
|
114
130
|
in_param_ifs.append(resolve_snref(x, request.parameters, Parameter))
|
115
131
|
|
132
|
+
# TODO: The output parameters are probably part of a response
|
133
|
+
# (?). If so, they cannot be resolved ahead of time because
|
134
|
+
# the service in question can have multiple responses
|
135
|
+
# associated with it and each of these has its own set of
|
136
|
+
# parameters. In the meantime, we simply use the first
|
137
|
+
# positive response specified.
|
138
|
+
response = service.positive_responses[0]
|
116
139
|
out_param_ifs = []
|
117
140
|
for x in self.out_param_if_refs:
|
118
141
|
out_param_ifs.append(resolve_snref(x, response.parameters, Parameter))
|
@@ -235,7 +258,7 @@ class SubComponent(IdentifiableElement):
|
|
235
258
|
|
236
259
|
"""
|
237
260
|
|
238
|
-
|
261
|
+
sub_component_patterns: List[SubComponentPattern]
|
239
262
|
sub_component_param_connectors: NamedItemList[SubComponentParamConnector]
|
240
263
|
table_row_connectors: NamedItemList[TableRowConnector]
|
241
264
|
env_data_connectors: NamedItemList[EnvDataConnector]
|
@@ -249,6 +272,10 @@ class SubComponent(IdentifiableElement):
|
|
249
272
|
|
250
273
|
semantic = et_element.get("SEMANTIC")
|
251
274
|
|
275
|
+
sub_component_patterns = [
|
276
|
+
SubComponentPattern.from_et(el, doc_frags)
|
277
|
+
for el in et_element.iterfind("SUB-COMPONENT-PATTERNS/SUB-COMPONENT-PATTERN")
|
278
|
+
]
|
252
279
|
sub_component_param_connectors = [
|
253
280
|
SubComponentParamConnector.from_et(el, doc_frags) for el in et_element.iterfind(
|
254
281
|
"SUB-COMPONENT-PARAM-CONNECTORS/SUB-COMPONENT-PARAM-CONNECTOR")
|
@@ -268,6 +295,7 @@ class SubComponent(IdentifiableElement):
|
|
268
295
|
|
269
296
|
return SubComponent(
|
270
297
|
semantic=semantic,
|
298
|
+
sub_component_patterns=sub_component_patterns,
|
271
299
|
sub_component_param_connectors=NamedItemList(sub_component_param_connectors),
|
272
300
|
table_row_connectors=NamedItemList(table_row_connectors),
|
273
301
|
env_data_connectors=NamedItemList(env_data_connectors),
|
@@ -277,15 +305,51 @@ class SubComponent(IdentifiableElement):
|
|
277
305
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
278
306
|
result = {}
|
279
307
|
|
308
|
+
for scp in self.sub_component_patterns:
|
309
|
+
result.update(scp._build_odxlinks())
|
310
|
+
|
311
|
+
for scpc in self.sub_component_param_connectors:
|
312
|
+
result.update(scpc._build_odxlinks())
|
313
|
+
|
314
|
+
for trc in self.table_row_connectors:
|
315
|
+
result.update(trc._build_odxlinks())
|
316
|
+
|
317
|
+
for edc in self.env_data_connectors:
|
318
|
+
result.update(edc._build_odxlinks())
|
319
|
+
|
280
320
|
for dtc_conn in self.dtc_connectors:
|
281
321
|
result.update(dtc_conn._build_odxlinks())
|
282
322
|
|
283
323
|
return result
|
284
324
|
|
285
325
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
326
|
+
for scp in self.sub_component_patterns:
|
327
|
+
scp._resolve_odxlinks(odxlinks)
|
328
|
+
|
329
|
+
for scpc in self.sub_component_param_connectors:
|
330
|
+
scpc._resolve_odxlinks(odxlinks)
|
331
|
+
|
332
|
+
for trc in self.table_row_connectors:
|
333
|
+
trc._resolve_odxlinks(odxlinks)
|
334
|
+
|
335
|
+
for edc in self.env_data_connectors:
|
336
|
+
edc._resolve_odxlinks(odxlinks)
|
337
|
+
|
286
338
|
for dtc_conn in self.dtc_connectors:
|
287
339
|
dtc_conn._resolve_odxlinks(odxlinks)
|
288
340
|
|
289
341
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
342
|
+
for scp in self.sub_component_patterns:
|
343
|
+
scp._resolve_snrefs(context)
|
344
|
+
|
345
|
+
for scpc in self.sub_component_param_connectors:
|
346
|
+
scpc._resolve_snrefs(context)
|
347
|
+
|
348
|
+
for trc in self.table_row_connectors:
|
349
|
+
trc._resolve_snrefs(context)
|
350
|
+
|
351
|
+
for edc in self.env_data_connectors:
|
352
|
+
edc._resolve_snrefs(context)
|
353
|
+
|
290
354
|
for dtc_conn in self.dtc_connectors:
|
291
355
|
dtc_conn._resolve_snrefs(context)
|
odxtools/table.py
CHANGED
@@ -32,6 +32,7 @@ class TableDiagCommConnector:
|
|
32
32
|
doc_frags: List[OdxDocFragment]) -> "TableDiagCommConnector":
|
33
33
|
|
34
34
|
semantic = odxrequire(et_element.findtext("SEMANTIC"))
|
35
|
+
|
35
36
|
diag_comm_ref = OdxLinkRef.from_et(et_element.find("DIAG-COMM-REF"), doc_frags)
|
36
37
|
diag_comm_snref = None
|
37
38
|
if (dc_snref_elem := et_element.find("DIAG-COMM-SNREF")) is not None:
|
@@ -56,7 +57,6 @@ class TableDiagCommConnector:
|
|
56
57
|
@dataclass
|
57
58
|
class Table(IdentifiableElement):
|
58
59
|
"""This class represents a TABLE."""
|
59
|
-
semantic: Optional[str]
|
60
60
|
key_label: Optional[str]
|
61
61
|
struct_label: Optional[str]
|
62
62
|
admin_data: Optional[AdminData]
|
@@ -64,13 +64,23 @@ class Table(IdentifiableElement):
|
|
64
64
|
table_rows_raw: List[Union[TableRow, OdxLinkRef]]
|
65
65
|
table_diag_comm_connectors: List[TableDiagCommConnector]
|
66
66
|
sdgs: List[SpecialDataGroup]
|
67
|
+
semantic: Optional[str]
|
68
|
+
|
69
|
+
@property
|
70
|
+
def key_dop(self) -> Optional[DataObjectProperty]:
|
71
|
+
"""The key data object property associated with this table."""
|
72
|
+
return self._key_dop
|
73
|
+
|
74
|
+
@property
|
75
|
+
def table_rows(self) -> NamedItemList[TableRow]:
|
76
|
+
"""The table rows (both local and referenced) in this table."""
|
77
|
+
return self._table_rows
|
67
78
|
|
68
79
|
@staticmethod
|
69
80
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Table":
|
70
81
|
"""Reads a TABLE."""
|
71
82
|
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
72
83
|
odx_id = kwargs["odx_id"]
|
73
|
-
semantic = et_element.get("SEMANTIC")
|
74
84
|
key_label = et_element.findtext("KEY-LABEL")
|
75
85
|
struct_label = et_element.findtext("STRUCT-LABEL")
|
76
86
|
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
@@ -92,9 +102,9 @@ class Table(IdentifiableElement):
|
|
92
102
|
sdgs = [
|
93
103
|
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
94
104
|
]
|
105
|
+
semantic = et_element.get("SEMANTIC")
|
95
106
|
|
96
107
|
return Table(
|
97
|
-
semantic=semantic,
|
98
108
|
key_label=key_label,
|
99
109
|
struct_label=struct_label,
|
100
110
|
admin_data=admin_data,
|
@@ -102,18 +112,9 @@ class Table(IdentifiableElement):
|
|
102
112
|
table_rows_raw=table_rows_raw,
|
103
113
|
table_diag_comm_connectors=table_diag_comm_connectors,
|
104
114
|
sdgs=sdgs,
|
115
|
+
semantic=semantic,
|
105
116
|
**kwargs)
|
106
117
|
|
107
|
-
@property
|
108
|
-
def key_dop(self) -> Optional[DataObjectProperty]:
|
109
|
-
"""The key data object property associated with this table."""
|
110
|
-
return self._key_dop
|
111
|
-
|
112
|
-
@property
|
113
|
-
def table_rows(self) -> NamedItemList[TableRow]:
|
114
|
-
"""The table rows (both local and referenced) in this table."""
|
115
|
-
return self._table_rows
|
116
|
-
|
117
118
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
118
119
|
result = {self.odx_id: self}
|
119
120
|
|
@@ -124,6 +125,9 @@ class Table(IdentifiableElement):
|
|
124
125
|
for dcc in self.table_diag_comm_connectors:
|
125
126
|
result.update(dcc._build_odxlinks())
|
126
127
|
|
128
|
+
for sdg in self.sdgs:
|
129
|
+
result.update(sdg._build_odxlinks())
|
130
|
+
|
127
131
|
return result
|
128
132
|
|
129
133
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
@@ -147,6 +151,9 @@ class Table(IdentifiableElement):
|
|
147
151
|
for dcc in self.table_diag_comm_connectors:
|
148
152
|
dcc._resolve_odxlinks(odxlinks)
|
149
153
|
|
154
|
+
for sdg in self.sdgs:
|
155
|
+
sdg._resolve_odxlinks(odxlinks)
|
156
|
+
|
150
157
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
151
158
|
for table_row_wrapper in self.table_rows_raw:
|
152
159
|
if isinstance(table_row_wrapper, TableRow):
|
@@ -154,3 +161,6 @@ class Table(IdentifiableElement):
|
|
154
161
|
|
155
162
|
for dcc in self.table_diag_comm_connectors:
|
156
163
|
dcc._resolve_snrefs(context)
|
164
|
+
|
165
|
+
for sdg in self.sdgs:
|
166
|
+
sdg._resolve_snrefs(context)
|