odxtools 9.5.0__py3-none-any.whl → 9.6.1__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.
Files changed (102) hide show
  1. odxtools/additionalaudience.py +2 -2
  2. odxtools/admindata.py +3 -0
  3. odxtools/audience.py +9 -13
  4. odxtools/basecomparam.py +1 -2
  5. odxtools/basevariantpattern.py +5 -5
  6. odxtools/basicstructure.py +34 -35
  7. odxtools/commrelation.py +2 -1
  8. odxtools/companydata.py +1 -2
  9. odxtools/companyspecificinfo.py +3 -0
  10. odxtools/comparam.py +16 -8
  11. odxtools/comparaminstance.py +12 -12
  12. odxtools/comparamspec.py +4 -3
  13. odxtools/comparamsubset.py +26 -24
  14. odxtools/compumethods/compuconst.py +4 -4
  15. odxtools/compumethods/limit.py +9 -9
  16. odxtools/compumethods/linearsegment.py +8 -8
  17. odxtools/dataobjectproperty.py +16 -18
  18. odxtools/description.py +4 -2
  19. odxtools/determinenumberofitems.py +4 -4
  20. odxtools/diagcodedtype.py +20 -20
  21. odxtools/diagcomm.py +61 -41
  22. odxtools/diagdatadictionaryspec.py +51 -55
  23. odxtools/diaglayercontainer.py +25 -25
  24. odxtools/diaglayers/diaglayerraw.py +26 -27
  25. odxtools/diagnostictroublecode.py +13 -10
  26. odxtools/diagservice.py +49 -51
  27. odxtools/diagvariable.py +10 -8
  28. odxtools/docrevision.py +5 -5
  29. odxtools/dtcdop.py +17 -17
  30. odxtools/dynamicendmarkerfield.py +8 -8
  31. odxtools/dynamiclengthfield.py +2 -0
  32. odxtools/dyndefinedspec.py +21 -8
  33. odxtools/encodestate.py +1 -2
  34. odxtools/endofpdufield.py +7 -9
  35. odxtools/environmentdatadescription.py +9 -20
  36. odxtools/field.py +21 -21
  37. odxtools/inputparam.py +15 -14
  38. odxtools/leadinglengthinfotype.py +4 -4
  39. odxtools/matchingparameter.py +2 -3
  40. odxtools/minmaxlengthtype.py +7 -7
  41. odxtools/multiplexer.py +38 -39
  42. odxtools/multiplexercase.py +3 -6
  43. odxtools/multiplexerdefaultcase.py +3 -6
  44. odxtools/multiplexerswitchkey.py +4 -4
  45. odxtools/negoutputparam.py +6 -9
  46. odxtools/odxlink.py +21 -5
  47. odxtools/odxtypes.py +4 -4
  48. odxtools/outputparam.py +9 -8
  49. odxtools/parameterinfo.py +1 -1
  50. odxtools/parameters/codedconstparameter.py +28 -27
  51. odxtools/parameters/dynamicparameter.py +9 -9
  52. odxtools/parameters/lengthkeyparameter.py +18 -18
  53. odxtools/parameters/matchingrequestparameter.py +15 -15
  54. odxtools/parameters/nrcconstparameter.py +32 -24
  55. odxtools/parameters/parameter.py +35 -37
  56. odxtools/parameters/parameterwithdop.py +6 -6
  57. odxtools/parameters/physicalconstantparameter.py +19 -20
  58. odxtools/parameters/reservedparameter.py +10 -11
  59. odxtools/parameters/systemparameter.py +10 -11
  60. odxtools/parameters/tableentryparameter.py +19 -20
  61. odxtools/parameters/tablekeyparameter.py +0 -2
  62. odxtools/parameters/tablestructparameter.py +27 -21
  63. odxtools/parameters/valueparameter.py +20 -20
  64. odxtools/parentref.py +6 -7
  65. odxtools/physicaldimension.py +11 -11
  66. odxtools/physicaltype.py +9 -14
  67. odxtools/preconditionstateref.py +85 -0
  68. odxtools/progcode.py +1 -2
  69. odxtools/protstack.py +4 -4
  70. odxtools/relateddoc.py +3 -4
  71. odxtools/scaleconstr.py +0 -1
  72. odxtools/singleecujob.py +8 -4
  73. odxtools/specialdata.py +10 -9
  74. odxtools/specialdatagroup.py +1 -0
  75. odxtools/standardlengthtype.py +10 -10
  76. odxtools/statechart.py +10 -6
  77. odxtools/statemachine.py +186 -0
  78. odxtools/statetransitionref.py +231 -0
  79. odxtools/structure.py +4 -4
  80. odxtools/subcomponent.py +72 -8
  81. odxtools/table.py +23 -13
  82. odxtools/tablerow.py +86 -69
  83. odxtools/teammember.py +4 -4
  84. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  85. odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
  86. odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
  87. odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
  88. odxtools/templates/macros/printParam.xml.jinja2 +5 -5
  89. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
  90. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
  91. odxtools/templates/macros/printTable.xml.jinja2 +13 -9
  92. odxtools/text.py +35 -0
  93. odxtools/unit.py +1 -3
  94. odxtools/unitgroup.py +6 -8
  95. odxtools/utils.py +0 -4
  96. odxtools/version.py +2 -2
  97. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info}/METADATA +3 -2
  98. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info}/RECORD +102 -96
  99. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info}/WHEEL +1 -1
  100. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info}/entry_points.txt +0 -0
  101. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info/licenses}/LICENSE +0 -0
  102. {odxtools-9.5.0.dist-info → odxtools-9.6.1.dist-info}/top_level.txt +0 -0
@@ -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
- out_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
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
- in_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
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
- #sub_component_patterns: NamedItemList[SubComponentPattern]
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)