odxtools 7.3.0__py3-none-any.whl → 7.4.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 (34) hide show
  1. odxtools/cli/_print_utils.py +3 -3
  2. odxtools/cli/browse.py +4 -2
  3. odxtools/cli/list.py +3 -3
  4. odxtools/commrelation.py +122 -0
  5. odxtools/comparaminstance.py +1 -1
  6. odxtools/comparamspec.py +1 -2
  7. odxtools/diaglayer.py +13 -8
  8. odxtools/diaglayerraw.py +116 -23
  9. odxtools/diagvariable.py +104 -0
  10. odxtools/dyndefinedspec.py +179 -0
  11. odxtools/environmentdatadescription.py +3 -2
  12. odxtools/odxlink.py +7 -21
  13. odxtools/parameterinfo.py +10 -1
  14. odxtools/parameters/tablekeyparameter.py +11 -4
  15. odxtools/servicebinner.py +1 -1
  16. odxtools/specialdatagroup.py +1 -1
  17. odxtools/swvariable.py +21 -0
  18. odxtools/templates/macros/printComparam.xml.jinja2 +4 -2
  19. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -8
  20. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  21. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  22. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  23. odxtools/templates/macros/printParam.xml.jinja2 +7 -8
  24. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  25. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +2 -2
  26. odxtools/templates/macros/printVariant.xml.jinja2 +30 -13
  27. odxtools/variablegroup.py +22 -0
  28. odxtools/version.py +2 -2
  29. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/METADATA +18 -18
  30. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/RECORD +34 -27
  31. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/WHEEL +1 -1
  32. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/LICENSE +0 -0
  33. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/entry_points.txt +0 -0
  34. {odxtools-7.3.0.dist-info → odxtools-7.4.1.dist-info}/top_level.txt +0 -0
@@ -235,7 +235,7 @@ def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = p
235
235
  type = []
236
236
  num_services = []
237
237
  num_dops = []
238
- num_comparams = []
238
+ num_comparam_refs = []
239
239
  for variant in variants:
240
240
  assert isinstance(variant, DiagLayer)
241
241
  all_services: List[Union[DiagService, SingleEcuJob]] = sorted(
@@ -244,13 +244,13 @@ def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = p
244
244
  type.append(variant.variant_type.value)
245
245
  num_services.append(len(all_services))
246
246
  num_dops.append(len(variant.diag_data_dictionary_spec.data_object_props))
247
- num_comparams.append(len(variant.comparams))
247
+ num_comparam_refs.append(len(variant.comparam_refs))
248
248
 
249
249
  table = {
250
250
  'Name': name,
251
251
  'Variant Type': type,
252
252
  'Number of Services': num_services,
253
253
  'Number of DOPs': num_dops,
254
- 'Number of communication parameters': num_comparams
254
+ 'Number of communication parameters': num_comparam_refs
255
255
  }
256
256
  print_fn(tabulate(table, headers='keys', tablefmt='presto'))
odxtools/cli/browse.py CHANGED
@@ -112,7 +112,8 @@ def prompt_single_parameter_value(parameter: Parameter) -> Optional[AtomicOdxTyp
112
112
 
113
113
  def encode_message_interactively(sub_service: Union[Request, Response],
114
114
  ask_user_confirmation: bool = False) -> None:
115
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
115
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
116
+ ) or not sys.__stdout__.isatty():
116
117
  raise SystemError("This command can only be used in an interactive shell!")
117
118
  param_dict = sub_service.parameter_dict()
118
119
 
@@ -260,7 +261,8 @@ def encode_message_from_string_values(
260
261
 
261
262
 
262
263
  def browse(odxdb: Database) -> None:
263
- if not sys.__stdin__.isatty() or not sys.__stdout__.isatty():
264
+ if sys.__stdin__ is None or sys.__stdout__ is None or not sys.__stdin__.isatty(
265
+ ) or not sys.__stdout__.isatty():
264
266
  raise SystemError("This command can only be used in an interactive shell!")
265
267
  dl_names = [dl.short_name for dl in odxdb.diag_layers]
266
268
  while True:
odxtools/cli/list.py CHANGED
@@ -57,7 +57,7 @@ def print_summary(odxdb: Database,
57
57
  all_services: List[DiagComm] = sorted(dl.services, key=lambda x: x.short_name)
58
58
 
59
59
  data_object_properties = dl.diag_data_dictionary_spec.data_object_props
60
- comparams = dl.comparams
60
+ comparam_refs = dl.comparam_refs
61
61
 
62
62
  for proto in dl.protocols:
63
63
  if (can_rx_id := dl.get_can_receive_id(proto.short_name)) is not None:
@@ -103,12 +103,12 @@ def print_summary(odxdb: Database,
103
103
  data_object_properties, key=lambda x: (type(x).__name__, x.short_name)):
104
104
  rich.print(" " + str(dop.short_name).replace("\n", "\n "))
105
105
 
106
- if print_comparams and len(comparams) > 0:
106
+ if print_comparams and len(comparam_refs) > 0:
107
107
  rich.print("\n")
108
108
  rich.print(
109
109
  f"The communication parameters of the {dl.variant_type.value} '{dl.short_name}' are: "
110
110
  )
111
- for com_param in comparams:
111
+ for com_param in comparam_refs:
112
112
  rich.print(f" {com_param.short_name}: {com_param.value}")
113
113
 
114
114
 
@@ -0,0 +1,122 @@
1
+ # SPDX-License-Identifier: MIT
2
+ import warnings
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional
6
+ from xml.etree import ElementTree
7
+
8
+ from .description import Description
9
+ from .diagcomm import DiagComm
10
+ from .diagservice import DiagService
11
+ from .exceptions import OdxWarning, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
13
+ from .parameters.parameter import Parameter
14
+ from .snrefcontext import SnRefContext
15
+
16
+
17
+ class CommRelationValueType(Enum):
18
+ CURRENT = "CURRENT"
19
+ STORED = "STORED"
20
+ STATIC = "STATIC"
21
+ SUBSTITUTED = "SUBSTITUTED"
22
+
23
+
24
+ @dataclass
25
+ class CommRelation:
26
+ description: Optional[Description]
27
+ relation_type: str
28
+ diag_comm_ref: Optional[OdxLinkRef]
29
+ diag_comm_snref: Optional[str]
30
+ in_param_if_snref: Optional[str]
31
+ #in_param_if_snpathref: Optional[str] # TODO
32
+ out_param_if_snref: Optional[str]
33
+ #out_param_if_snpathref: Optional[str] # TODO
34
+ value_type_raw: Optional[CommRelationValueType]
35
+
36
+ @property
37
+ def diag_comm(self) -> DiagComm:
38
+ return self._diag_comm
39
+
40
+ @property
41
+ def in_param_if(self) -> Optional[Parameter]:
42
+ return self._in_param_if
43
+
44
+ @property
45
+ def out_param_if(self) -> Optional[Parameter]:
46
+ return self._out_param_if
47
+
48
+ @property
49
+ def value_type(self) -> CommRelationValueType:
50
+ if self.value_type_raw is None:
51
+ return CommRelationValueType.CURRENT
52
+
53
+ return self.value_type_raw
54
+
55
+ @staticmethod
56
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "CommRelation":
57
+ description = Description.from_et(et_element.find("DESC"), doc_frags)
58
+ relation_type = odxrequire(et_element.findtext("RELATION-TYPE"))
59
+
60
+ diag_comm_ref = OdxLinkRef.from_et(et_element.find("DIAG-COMM-REF"), doc_frags)
61
+ diag_comm_snref = None
62
+ if (diag_comm_snref_elem := et_element.find("DIAG-COMM-SNREF")) is not None:
63
+ diag_comm_snref = odxrequire(diag_comm_snref_elem.get("SHORT-NAME"))
64
+
65
+ in_param_if_snref = None
66
+ if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
67
+ in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
68
+
69
+ if et_element.find("IN-PARAM-IF-SNPATHREF") is not None:
70
+ warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
71
+
72
+ out_param_if_snref = None
73
+ if (out_param_if_snref_elem := et_element.find("OUT-PARAM-IF-SNREF")) is not None:
74
+ out_param_if_snref = odxrequire(out_param_if_snref_elem.get("SHORT-NAME"))
75
+
76
+ if et_element.find("OUT-PARAM-IF-SNPATHREF") is not None:
77
+ warnings.warn("SNPATHREFs are not supported by odxtools yet", OdxWarning, stacklevel=1)
78
+
79
+ value_type_raw = None
80
+ if (value_type_str := et_element.get("VALUE-TYPE")) is not None:
81
+ try:
82
+ value_type_raw = CommRelationValueType(value_type_str)
83
+ except ValueError:
84
+ odxraise(f"Encountered unknown comm relation value type '{value_type_str}'")
85
+
86
+ return CommRelation(
87
+ description=description,
88
+ relation_type=relation_type,
89
+ diag_comm_ref=diag_comm_ref,
90
+ diag_comm_snref=diag_comm_snref,
91
+ in_param_if_snref=in_param_if_snref,
92
+ out_param_if_snref=out_param_if_snref,
93
+ value_type_raw=value_type_raw)
94
+
95
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
96
+ return {}
97
+
98
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
99
+ if self.diag_comm_ref is not None:
100
+ self._diag_comm = odxlinks.resolve(self.diag_comm_ref, DiagComm)
101
+
102
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
103
+ diag_layer = odxrequire(context.diag_layer)
104
+
105
+ if self.diag_comm_snref is not None:
106
+ self._diag_comm = resolve_snref(self.diag_comm_snref, diag_layer.diag_comms, DiagComm)
107
+
108
+ service = self.diag_comm
109
+ if not isinstance(service, DiagService):
110
+ odxraise(f"DIAG-VARIABLE references non-service {type(service).__name__} "
111
+ f"diagnostic communication")
112
+
113
+ self._in_param_if = None
114
+ if self.in_param_if_snref is not None:
115
+ self._in_param_if = resolve_snref(self.in_param_if_snref,
116
+ odxrequire(service.request).parameters, Parameter)
117
+
118
+ self._out_param_if = None
119
+ if self.out_param_if_snref is not None:
120
+ self._out_param_if = resolve_snref(self.out_param_if_snref,
121
+ odxrequire(service.positive_responses[0]).parameters,
122
+ Parameter)
@@ -32,7 +32,7 @@ class ComparamInstance:
32
32
  spec_ref = odxrequire(OdxLinkRef.from_et(et_element, doc_frags))
33
33
 
34
34
  # ODX standard v2.0.0 defined only VALUE. ODX v2.0.1 decided
35
- # to break things and change it to choice between SIMPLE-VALUE
35
+ # to break things and change it to a choice between SIMPLE-VALUE
36
36
  # and COMPLEX-VALUE
37
37
  value: Union[str, List[Union[str, ComplexValue]]]
38
38
  if et_element.find("VALUE") is not None:
odxtools/comparamspec.py CHANGED
@@ -51,8 +51,7 @@ class ComparamSpec(IdentifiableElement):
51
51
 
52
52
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
53
53
  odxlinks: Dict[OdxLinkId, Any] = {}
54
- if self.odx_id is not None:
55
- odxlinks[self.odx_id] = self
54
+ odxlinks[self.odx_id] = self
56
55
 
57
56
  if self.admin_data is not None:
58
57
  odxlinks.update(self.admin_data._build_odxlinks())
odxtools/diaglayer.py CHANGED
@@ -170,9 +170,9 @@ class DiagLayer:
170
170
  self._diag_comms = NamedItemList(diag_comms)
171
171
 
172
172
  # filter the diag comms for services and single-ECU jobs
173
- services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
173
+ diag_services = [dc for dc in diag_comms if isinstance(dc, DiagService)]
174
174
  single_ecu_jobs = [dc for dc in diag_comms if isinstance(dc, SingleEcuJob)]
175
- self._services = NamedItemList(services)
175
+ self._diag_services = NamedItemList(diag_services)
176
176
  self._single_ecu_jobs = NamedItemList(single_ecu_jobs)
177
177
 
178
178
  global_negative_responses = self._compute_available_global_neg_responses(odxlinks)
@@ -289,7 +289,7 @@ class DiagLayer:
289
289
  # scheme, cf the docstring of
290
290
  # _compute_available_commmunication_parameters().
291
291
  #####
292
- self._comparams = NamedItemList(self._compute_available_commmunication_parameters())
292
+ self._comparam_refs = NamedItemList(self._compute_available_commmunication_parameters())
293
293
 
294
294
  #####
295
295
  # resolve all SNREFs. TODO: We allow SNREFS to objects that
@@ -407,12 +407,17 @@ class DiagLayer:
407
407
 
408
408
  @property
409
409
  def services(self) -> NamedItemList[DiagService]:
410
+ """This property is an alias for `.diag_services`"""
411
+ return self._diag_services
412
+
413
+ @property
414
+ def diag_services(self) -> NamedItemList[DiagService]:
410
415
  """All diagnostic services applicable to this DiagLayer
411
416
 
412
417
  This is a subset of all diagnostic communication
413
418
  primitives. All references are resolved in the list returned.
414
419
  """
415
- return self._services
420
+ return self._diag_services
416
421
 
417
422
  @property
418
423
  def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
@@ -707,20 +712,20 @@ class DiagLayer:
707
712
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
708
713
 
709
714
  # finally, handle the locally defined communication parameters
710
- for cp in self.diag_layer_raw.comparams:
715
+ for cp in self.diag_layer_raw.comparam_refs:
711
716
  com_params_dict[(cp.spec_ref.ref_id, cp.protocol_snref)] = cp
712
717
 
713
718
  return list(com_params_dict.values())
714
719
 
715
720
  @property
716
- def comparams(self) -> NamedItemList[ComparamInstance]:
721
+ def comparam_refs(self) -> NamedItemList[ComparamInstance]:
717
722
  """All communication parameters applicable to this DiagLayer
718
723
 
719
724
  Note that, although communication parameters use inheritance,
720
725
  it is *not* the "value inheritance" scheme used by e.g. DOPs,
721
726
  tables, state charts, ...
722
727
  """
723
- return self._comparams
728
+ return self._comparam_refs
724
729
 
725
730
  @cached_property
726
731
  def protocols(self) -> NamedItemList["DiagLayer"]:
@@ -757,7 +762,7 @@ class DiagLayer:
757
762
  protocol_name = protocol
758
763
 
759
764
  # determine the set of applicable communication parameters
760
- cps = [cp for cp in self.comparams if cp.short_name == cp_short_name]
765
+ cps = [cp for cp in self.comparam_refs if cp.short_name == cp_short_name]
761
766
  if protocol_name is not None:
762
767
  cps = [cp for cp in cps if cp.protocol_snref in (None, protocol_name)]
763
768
 
odxtools/diaglayerraw.py CHANGED
@@ -14,6 +14,8 @@ from .diagcomm import DiagComm
14
14
  from .diagdatadictionaryspec import DiagDataDictionarySpec
15
15
  from .diaglayertype import DiagLayerType
16
16
  from .diagservice import DiagService
17
+ from .diagvariable import DiagVariable
18
+ from .dyndefinedspec import DynDefinedSpec
17
19
  from .ecuvariantpattern import EcuVariantPattern
18
20
  from .element import IdentifiableElement
19
21
  from .exceptions import odxassert, odxraise, odxrequire
@@ -29,6 +31,7 @@ from .snrefcontext import SnRefContext
29
31
  from .specialdatagroup import SpecialDataGroup
30
32
  from .statechart import StateChart
31
33
  from .utils import dataclass_fields_asdict
34
+ from .variablegroup import VariableGroup
32
35
 
33
36
 
34
37
  @dataclass
@@ -44,7 +47,7 @@ class DiagLayerRaw(IdentifiableElement):
44
47
  company_datas: NamedItemList[CompanyData]
45
48
  functional_classes: NamedItemList[FunctionalClass]
46
49
  diag_data_dictionary_spec: Optional[DiagDataDictionarySpec]
47
- diag_comms: List[Union[OdxLinkRef, DiagComm]]
50
+ diag_comms_raw: List[Union[OdxLinkRef, DiagComm]]
48
51
  requests: NamedItemList[Request]
49
52
  positive_responses: NamedItemList[Response]
50
53
  negative_responses: NamedItemList[Response]
@@ -59,15 +62,36 @@ class DiagLayerRaw(IdentifiableElement):
59
62
  # these attributes are only defined for some kinds of diag layers!
60
63
  # TODO: make a proper class hierarchy!
61
64
  parent_refs: List[ParentRef]
62
- comparams: List[ComparamInstance]
65
+ comparam_refs: List[ComparamInstance]
63
66
  ecu_variant_patterns: List[EcuVariantPattern]
64
67
  comparam_spec_ref: Optional[OdxLinkRef]
65
68
  prot_stack_snref: Optional[str]
66
- # diag_variables: List[DiagVariable] # TODO
67
- # diag_variable_groups: List[DiagVariableGroup] # TODO
68
- # dyn_defined_spec: Optional[DynDefinedSpec] # TODO
69
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]]
70
+ variable_groups: NamedItemList[VariableGroup]
71
+ dyn_defined_spec: Optional[DynDefinedSpec]
69
72
  # base_variant_patterns: List[EcuVariantPattern] # TODO
70
73
 
74
+ @property
75
+ def diag_comms(self) -> NamedItemList[DiagComm]:
76
+ return self._diag_comms
77
+
78
+ @property
79
+ def diag_services(self) -> NamedItemList[DiagService]:
80
+ return self._diag_services
81
+
82
+ @property
83
+ def services(self) -> NamedItemList[DiagService]:
84
+ """This property is an alias for `.diag_services`"""
85
+ return self._diag_services
86
+
87
+ @property
88
+ def single_ecu_jobs(self) -> NamedItemList[SingleEcuJob]:
89
+ return self._single_ecu_jobs
90
+
91
+ @property
92
+ def diag_variables(self) -> NamedItemList[DiagVariable]:
93
+ return self._diag_variables
94
+
71
95
  @staticmethod
72
96
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagLayerRaw":
73
97
  try:
@@ -101,7 +125,7 @@ class DiagLayerRaw(IdentifiableElement):
101
125
  if (ddds_elem := et_element.find("DIAG-DATA-DICTIONARY-SPEC")) is not None:
102
126
  diag_data_dictionary_spec = DiagDataDictionarySpec.from_et(ddds_elem, doc_frags)
103
127
 
104
- diag_comms: List[Union[OdxLinkRef, DiagComm]] = []
128
+ diag_comms_raw: List[Union[OdxLinkRef, DiagComm]] = []
105
129
  if (dc_elems := et_element.find("DIAG-COMMS")) is not None:
106
130
  for dc_proxy_elem in dc_elems:
107
131
  dc: Union[OdxLinkRef, DiagComm]
@@ -113,7 +137,7 @@ class DiagLayerRaw(IdentifiableElement):
113
137
  odxassert(dc_proxy_elem.tag == "SINGLE-ECU-JOB")
114
138
  dc = SingleEcuJob.from_et(dc_proxy_elem, doc_frags)
115
139
 
116
- diag_comms.append(dc)
140
+ diag_comms_raw.append(dc)
117
141
 
118
142
  requests = NamedItemList([
119
143
  Request.from_et(rq_elem, doc_frags)
@@ -159,7 +183,7 @@ class DiagLayerRaw(IdentifiableElement):
159
183
  for pr_el in et_element.iterfind("PARENT-REFS/PARENT-REF")
160
184
  ]
161
185
 
162
- comparams = [
186
+ comparam_refs = [
163
187
  ComparamInstance.from_et(el, doc_frags)
164
188
  for el in et_element.iterfind("COMPARAM-REFS/COMPARAM-REF")
165
189
  ]
@@ -178,6 +202,29 @@ class DiagLayerRaw(IdentifiableElement):
178
202
  if (prot_stack_snref_elem := et_element.find("PROT-STACK-SNREF")) is not None:
179
203
  prot_stack_snref = odxrequire(prot_stack_snref_elem.get("SHORT-NAME"))
180
204
 
205
+ diag_variables_raw: List[Union[DiagVariable, OdxLinkRef]] = []
206
+ if (dv_elems := et_element.find("DIAG-VARIABLES")) is not None:
207
+ for dv_proxy_elem in dv_elems:
208
+ dv_proxy: Union[OdxLinkRef, DiagVariable]
209
+ if dv_proxy_elem.tag == "DIAG-VARIABLE-REF":
210
+ dv_proxy = OdxLinkRef.from_et(dv_proxy_elem, doc_frags)
211
+ elif dv_proxy_elem.tag == "DIAG-VARIABLE":
212
+ dv_proxy = DiagVariable.from_et(dv_proxy_elem, doc_frags)
213
+ else:
214
+ odxraise("DIAG-VARIABLES tags may only contain "
215
+ "DIAG-VARIABLE and DIAG-VARIABLE-REF subtags")
216
+
217
+ diag_variables_raw.append(dv_proxy)
218
+
219
+ variable_groups = NamedItemList([
220
+ VariableGroup.from_et(vg_elem, doc_frags)
221
+ for vg_elem in et_element.iterfind("VARIABLE-GROUPS/VARIABLE-GROUP")
222
+ ])
223
+
224
+ dyn_defined_spec = None
225
+ if (dds_elem := et_element.find("DYN-DEFINED-SPEC")) is not None:
226
+ dyn_defined_spec = DynDefinedSpec.from_et(dds_elem, doc_frags)
227
+
181
228
  # Create DiagLayer
182
229
  return DiagLayerRaw(
183
230
  variant_type=variant_type,
@@ -185,7 +232,7 @@ class DiagLayerRaw(IdentifiableElement):
185
232
  company_datas=NamedItemList(company_datas),
186
233
  functional_classes=NamedItemList(functional_classes),
187
234
  diag_data_dictionary_spec=diag_data_dictionary_spec,
188
- diag_comms=diag_comms,
235
+ diag_comms_raw=diag_comms_raw,
189
236
  requests=requests,
190
237
  positive_responses=positive_responses,
191
238
  negative_responses=negative_responses,
@@ -195,10 +242,13 @@ class DiagLayerRaw(IdentifiableElement):
195
242
  additional_audiences=NamedItemList(additional_audiences),
196
243
  sdgs=sdgs,
197
244
  parent_refs=parent_refs,
198
- comparams=comparams,
245
+ comparam_refs=comparam_refs,
199
246
  ecu_variant_patterns=ecu_variant_patterns,
200
247
  comparam_spec_ref=comparam_spec_ref,
201
248
  prot_stack_snref=prot_stack_snref,
249
+ diag_variables_raw=diag_variables_raw,
250
+ variable_groups=variable_groups,
251
+ dyn_defined_spec=dyn_defined_spec,
202
252
  **kwargs)
203
253
 
204
254
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -214,10 +264,10 @@ class DiagLayerRaw(IdentifiableElement):
214
264
  odxlinks.update(company_data._build_odxlinks())
215
265
  for functional_class in self.functional_classes:
216
266
  odxlinks.update(functional_class._build_odxlinks())
217
- for diag_comm in self.diag_comms:
218
- if isinstance(diag_comm, OdxLinkRef):
267
+ for dc_proxy in self.diag_comms_raw:
268
+ if isinstance(dc_proxy, OdxLinkRef):
219
269
  continue
220
- odxlinks.update(diag_comm._build_odxlinks())
270
+ odxlinks.update(dc_proxy._build_odxlinks())
221
271
  for request in self.requests:
222
272
  odxlinks.update(request._build_odxlinks())
223
273
  for positive_response in self.positive_responses:
@@ -234,8 +284,13 @@ class DiagLayerRaw(IdentifiableElement):
234
284
  odxlinks.update(sdg._build_odxlinks())
235
285
  for parent_ref in self.parent_refs:
236
286
  odxlinks.update(parent_ref._build_odxlinks())
237
- for comparam in self.comparams:
287
+ for comparam in self.comparam_refs:
238
288
  odxlinks.update(comparam._build_odxlinks())
289
+ for dv_proxy in self.diag_variables_raw:
290
+ if not isinstance(dv_proxy, OdxLinkRef):
291
+ odxlinks.update(dv_proxy._build_odxlinks())
292
+ if self.dyn_defined_spec is not None:
293
+ odxlinks.update(self.dyn_defined_spec._build_odxlinks())
239
294
 
240
295
  return odxlinks
241
296
 
@@ -258,10 +313,28 @@ class DiagLayerRaw(IdentifiableElement):
258
313
  company_data._resolve_odxlinks(odxlinks)
259
314
  for functional_class in self.functional_classes:
260
315
  functional_class._resolve_odxlinks(odxlinks)
261
- for diag_comm in self.diag_comms:
262
- if isinstance(diag_comm, OdxLinkRef):
263
- continue
264
- diag_comm._resolve_odxlinks(odxlinks)
316
+
317
+ # resolve references to diagnostic communication objects and
318
+ # separate them into services and single-ecu jobs
319
+ self._diag_comms = NamedItemList[DiagComm]()
320
+ self._diag_services = NamedItemList[DiagService]()
321
+ self._single_ecu_jobs = NamedItemList[SingleEcuJob]()
322
+ for dc_proxy in self.diag_comms_raw:
323
+ if isinstance(dc_proxy, OdxLinkRef):
324
+ dc = odxlinks.resolve(dc_proxy, DiagComm)
325
+ else:
326
+ dc = dc_proxy
327
+ dc._resolve_odxlinks(odxlinks)
328
+
329
+ self._diag_comms.append(dc)
330
+
331
+ if isinstance(dc, DiagService):
332
+ self._diag_services.append(dc)
333
+ elif isinstance(dc, SingleEcuJob):
334
+ self._single_ecu_jobs.append(dc)
335
+ else:
336
+ odxraise()
337
+
265
338
  for request in self.requests:
266
339
  request._resolve_odxlinks(odxlinks)
267
340
  for positive_response in self.positive_responses:
@@ -278,9 +351,22 @@ class DiagLayerRaw(IdentifiableElement):
278
351
  sdg._resolve_odxlinks(odxlinks)
279
352
  for parent_ref in self.parent_refs:
280
353
  parent_ref._resolve_odxlinks(odxlinks)
281
- for comparam in self.comparams:
354
+ for comparam in self.comparam_refs:
282
355
  comparam._resolve_odxlinks(odxlinks)
283
356
 
357
+ self._diag_variables: NamedItemList[DiagVariable] = NamedItemList()
358
+ for dv_proxy in self.diag_variables_raw:
359
+ if isinstance(dv_proxy, OdxLinkRef):
360
+ dv = odxlinks.resolve(dv_proxy, DiagVariable)
361
+ else:
362
+ dv_proxy._resolve_odxlinks(odxlinks)
363
+ dv = dv_proxy
364
+
365
+ self._diag_variables.append(dv)
366
+
367
+ if self.dyn_defined_spec is not None:
368
+ self.dyn_defined_spec._resolve_odxlinks(odxlinks)
369
+
284
370
  def _resolve_snrefs(self, context: SnRefContext) -> None:
285
371
  self._prot_stack: Optional[ProtStack] = None
286
372
  if self.prot_stack_snref is not None:
@@ -299,10 +385,10 @@ class DiagLayerRaw(IdentifiableElement):
299
385
  company_data._resolve_snrefs(context)
300
386
  for functional_class in self.functional_classes:
301
387
  functional_class._resolve_snrefs(context)
302
- for diag_comm in self.diag_comms:
303
- if isinstance(diag_comm, OdxLinkRef):
388
+ for dc_proxy in self.diag_comms_raw:
389
+ if isinstance(dc_proxy, OdxLinkRef):
304
390
  continue
305
- diag_comm._resolve_snrefs(context)
391
+ dc_proxy._resolve_snrefs(context)
306
392
  for request in self.requests:
307
393
  request._resolve_snrefs(context)
308
394
  for positive_response in self.positive_responses:
@@ -319,9 +405,16 @@ class DiagLayerRaw(IdentifiableElement):
319
405
  sdg._resolve_snrefs(context)
320
406
  for parent_ref in self.parent_refs:
321
407
  parent_ref._resolve_snrefs(context)
322
- for comparam in self.comparams:
408
+ for comparam in self.comparam_refs:
323
409
  comparam._resolve_snrefs(context)
324
410
 
411
+ for dv_proxy in self.diag_variables_raw:
412
+ if not isinstance(dv_proxy, OdxLinkRef):
413
+ dv_proxy._resolve_snrefs(context)
414
+
415
+ if self.dyn_defined_spec is not None:
416
+ self.dyn_defined_spec._resolve_snrefs(context)
417
+
325
418
  @property
326
419
  def comparam_spec(self) -> Optional[Union[ComparamSpec, ComparamSubset]]:
327
420
  return self._comparam_spec
@@ -0,0 +1,104 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .admindata import AdminData
7
+ from .commrelation import CommRelation
8
+ from .element import IdentifiableElement
9
+ from .exceptions import odxrequire
10
+ from .nameditemlist import NamedItemList
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
+ from .odxtypes import odxstr_to_bool
13
+ from .snrefcontext import SnRefContext
14
+ from .specialdatagroup import SpecialDataGroup
15
+ from .swvariable import SwVariable
16
+ from .utils import dataclass_fields_asdict
17
+ from .variablegroup import VariableGroup
18
+
19
+
20
+ @dataclass
21
+ class DiagVariable(IdentifiableElement):
22
+ """Representation of a diagnostic variable
23
+ """
24
+
25
+ admin_data: Optional[AdminData]
26
+ variable_group_ref: OdxLinkRef
27
+ sw_variables: List[SwVariable]
28
+ comm_relations: List[CommRelation]
29
+ #snref_to_tablerow: Optional[SnrefToTableRow] # TODO
30
+ sdgs: List[SpecialDataGroup]
31
+ is_read_before_write_raw: Optional[bool]
32
+
33
+ @property
34
+ def variable_group(self) -> VariableGroup:
35
+ return self._variable_group
36
+
37
+ @property
38
+ def is_read_before_write(self) -> bool:
39
+ return self.is_read_before_write_raw is True
40
+
41
+ @staticmethod
42
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DiagVariable":
43
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
44
+
45
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
46
+ variable_group_ref = odxrequire(
47
+ OdxLinkRef.from_et(et_element.find("VARIABLE-GROUP-REF"), doc_frags))
48
+ sw_variables = NamedItemList([
49
+ SwVariable.from_et(swv_elem, doc_frags)
50
+ for swv_elem in et_element.iterfind("SW-VARIABLES/SW-VARIABLE")
51
+ ])
52
+ comm_relations = [
53
+ CommRelation.from_et(cr_elem, doc_frags)
54
+ for cr_elem in et_element.iterfind("COMM-RELATIONS/COMM-RELATION")
55
+ ]
56
+ sdgs = [
57
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
58
+ ]
59
+ is_read_before_write_raw = odxstr_to_bool(et_element.get("IS-READ-BEFORE-WRITE"))
60
+
61
+ return DiagVariable(
62
+ admin_data=admin_data,
63
+ variable_group_ref=variable_group_ref,
64
+ sw_variables=sw_variables,
65
+ comm_relations=comm_relations,
66
+ sdgs=sdgs,
67
+ is_read_before_write_raw=is_read_before_write_raw,
68
+ **kwargs)
69
+
70
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
71
+ result = {self.odx_id: self}
72
+
73
+ if self.admin_data is not None:
74
+ result.update(self.admin_data._build_odxlinks())
75
+
76
+ for sdg in self.sdgs:
77
+ result.update(sdg._build_odxlinks())
78
+
79
+ for cr in self.comm_relations:
80
+ result.update(cr._build_odxlinks())
81
+
82
+ return result
83
+
84
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
85
+ self._variable_group = odxlinks.resolve(self.variable_group_ref, VariableGroup)
86
+
87
+ if self.admin_data is not None:
88
+ self.admin_data._resolve_odxlinks(odxlinks)
89
+
90
+ for sdg in self.sdgs:
91
+ sdg._resolve_odxlinks(odxlinks)
92
+
93
+ for cr in self.comm_relations:
94
+ cr._resolve_odxlinks(odxlinks)
95
+
96
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
97
+ if self.admin_data is not None:
98
+ self.admin_data._resolve_snrefs(context)
99
+
100
+ for sdg in self.sdgs:
101
+ sdg._resolve_snrefs(context)
102
+
103
+ for cr in self.comm_relations:
104
+ cr._resolve_snrefs(context)