odxtools 8.3.4__py3-none-any.whl → 9.1.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.
Files changed (40) hide show
  1. odxtools/basicstructure.py +36 -207
  2. odxtools/cli/_print_utils.py +163 -131
  3. odxtools/cli/browse.py +94 -79
  4. odxtools/cli/compare.py +88 -69
  5. odxtools/cli/list.py +2 -3
  6. odxtools/codec.py +211 -0
  7. odxtools/comparamspec.py +16 -54
  8. odxtools/comparamsubset.py +20 -61
  9. odxtools/database.py +18 -2
  10. odxtools/diaglayercontainer.py +14 -40
  11. odxtools/diaglayers/hierarchyelement.py +0 -10
  12. odxtools/dopbase.py +5 -3
  13. odxtools/dtcdop.py +101 -14
  14. odxtools/inputparam.py +0 -7
  15. odxtools/leadinglengthinfotype.py +1 -8
  16. odxtools/message.py +0 -7
  17. odxtools/minmaxlengthtype.py +4 -4
  18. odxtools/odxcategory.py +83 -0
  19. odxtools/outputparam.py +0 -7
  20. odxtools/parameterinfo.py +12 -12
  21. odxtools/parameters/lengthkeyparameter.py +1 -2
  22. odxtools/parameters/parameter.py +6 -4
  23. odxtools/parameters/tablekeyparameter.py +9 -8
  24. odxtools/paramlengthinfotype.py +8 -9
  25. odxtools/request.py +109 -16
  26. odxtools/response.py +115 -15
  27. odxtools/specialdatagroup.py +1 -1
  28. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -21
  29. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +4 -22
  30. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -25
  31. odxtools/templates/macros/printDOP.xml.jinja2 +16 -0
  32. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  33. odxtools/uds.py +0 -8
  34. odxtools/version.py +2 -2
  35. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/METADATA +7 -8
  36. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/RECORD +40 -37
  37. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/WHEEL +1 -1
  38. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/LICENSE +0 -0
  39. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/entry_points.txt +0 -0
  40. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,83 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .admindata import AdminData
7
+ from .companydata import CompanyData
8
+ from .element import IdentifiableElement
9
+ from .exceptions import odxrequire
10
+ from .nameditemlist import NamedItemList
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
+ from .snrefcontext import SnRefContext
13
+ from .specialdatagroup import SpecialDataGroup
14
+ from .utils import dataclass_fields_asdict
15
+
16
+ if TYPE_CHECKING:
17
+ from .database import Database
18
+
19
+
20
+ @dataclass
21
+ class OdxCategory(IdentifiableElement):
22
+ """This is the base class for all top-level container classes in ODX"""
23
+
24
+ admin_data: Optional[AdminData]
25
+ company_datas: NamedItemList[CompanyData]
26
+ sdgs: List[SpecialDataGroup]
27
+
28
+ @staticmethod
29
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "OdxCategory":
30
+ raise Exception("Calling `._from_et()` is not allowed for OdxCategory. "
31
+ "Use `OdxCategory.category_from_et()`!")
32
+
33
+ @staticmethod
34
+ def category_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
35
+ doc_type: str) -> "OdxCategory":
36
+
37
+ short_name = odxrequire(et_element.findtext("SHORT-NAME"))
38
+ # create the current ODX "document fragment" (description of the
39
+ # current document for references and IDs)
40
+ doc_frags = [OdxDocFragment(short_name, doc_type)]
41
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
42
+
43
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
44
+ company_datas = NamedItemList([
45
+ CompanyData.from_et(cde, doc_frags)
46
+ for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
47
+ ])
48
+ sdgs = [
49
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
50
+ ]
51
+
52
+ return OdxCategory(admin_data=admin_data, company_datas=company_datas, sdgs=sdgs, **kwargs)
53
+
54
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
55
+ result = {self.odx_id: self}
56
+
57
+ if self.admin_data is not None:
58
+ result.update(self.admin_data._build_odxlinks())
59
+ for cd in self.company_datas:
60
+ result.update(cd._build_odxlinks())
61
+ for sdg in self.sdgs:
62
+ result.update(sdg._build_odxlinks())
63
+
64
+ return result
65
+
66
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
67
+ if self.admin_data is not None:
68
+ self.admin_data._resolve_odxlinks(odxlinks)
69
+ for cd in self.company_datas:
70
+ cd._resolve_odxlinks(odxlinks)
71
+ for sdg in self.sdgs:
72
+ sdg._resolve_odxlinks(odxlinks)
73
+
74
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
75
+ pass
76
+
77
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
78
+ if self.admin_data is not None:
79
+ self.admin_data._resolve_snrefs(context)
80
+ for cd in self.company_datas:
81
+ cd._resolve_snrefs(context)
82
+ for sdg in self.sdgs:
83
+ sdg._resolve_snrefs(context)
odxtools/outputparam.py CHANGED
@@ -3,8 +3,6 @@ from dataclasses import dataclass
3
3
  from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from deprecation import deprecated
7
-
8
6
  from .dopbase import DopBase
9
7
  from .element import IdentifiableElement
10
8
  from .exceptions import odxrequire
@@ -40,8 +38,3 @@ class OutputParam(IdentifiableElement):
40
38
  def dop_base(self) -> DopBase:
41
39
  """The data object property describing this parameter."""
42
40
  return self._dop_base
43
-
44
- @property
45
- @deprecated(details="use .dop_base") # type: ignore[misc]
46
- def dop(self) -> DopBase:
47
- return self._dop_base
odxtools/parameterinfo.py CHANGED
@@ -34,36 +34,36 @@ from .staticfield import StaticField
34
34
 
35
35
  def _get_linear_segment_info(segment: LinearSegment) -> str:
36
36
  ll = segment.physical_lower_limit
37
- ul = segment.physical_upper_limit
38
37
  if ll is None or ll.interval_type == IntervalType.INFINITE:
39
38
  ll_str = "(-inf"
40
39
  else:
41
40
  ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
42
41
  ll_str = f"{ll_delim}{ll._value!r}"
43
42
 
44
- if ul is None or ul.interval_type == IntervalType.INFINITE:
45
- ul_str = "inf)"
46
- else:
47
- ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
48
- ul_str = f"{ul._value!r}{ul_delim}"
43
+ ul = segment.physical_upper_limit
44
+ if ul is None or ul.interval_type == IntervalType.INFINITE:
45
+ ul_str = "inf)"
46
+ else:
47
+ ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
48
+ ul_str = f"{ul._value!r}{ul_delim}"
49
49
 
50
50
  return f"{ll_str}, {ul_str}"
51
51
 
52
52
 
53
53
  def _get_rat_func_segment_info(segment: RatFuncSegment) -> str:
54
54
  ll = segment.lower_limit
55
- ul = segment.upper_limit
56
55
  if ll is None or ll.interval_type == IntervalType.INFINITE:
57
56
  ll_str = "(-inf"
58
57
  else:
59
58
  ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
60
59
  ll_str = f"{ll_delim}{ll._value!r}"
61
60
 
62
- if ul is None or ul.interval_type == IntervalType.INFINITE:
63
- ul_str = "inf)"
64
- else:
65
- ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
66
- ul_str = f"{ul._value!r}{ul_delim}"
61
+ ul = segment.upper_limit
62
+ if ul is None or ul.interval_type == IntervalType.INFINITE:
63
+ ul_str = "inf)"
64
+ else:
65
+ ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
66
+ ul_str = f"{ul._value!r}{ul_delim}"
67
67
 
68
68
  return f"{ll_str}, {ul_str}"
69
69
 
@@ -105,9 +105,8 @@ class LengthKeyParameter(ParameterWithDOP):
105
105
  # emplace a value of zero into the encode state, but pretend the bits not to be used
106
106
  n = odxrequire(self.dop.get_static_bit_length()) + encode_state.cursor_bit_position
107
107
  tmp_val = b'\x00' * ((n + 7) // 8)
108
- encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
109
-
110
108
  encode_state.cursor_bit_position = 0
109
+ encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
111
110
 
112
111
  def encode_value_into_pdu(self, encode_state: EncodeState) -> None:
113
112
 
@@ -33,11 +33,13 @@ ParameterType = Literal[
33
33
  @dataclass
34
34
  class Parameter(NamedElement):
35
35
  """This class corresponds to POSITIONABLE-PARAM in the ODX
36
- specification.
36
+ specification
37
37
 
38
- Be aware that, even though the ODX specification seems to make the
39
- distinction of "positionable" and "normal" parameters, it does not
40
- define any non-positionable parameter types.
38
+ All parameter classes must adhere to the `Codec` type protocol, so
39
+ `isinstance(param, Codec)` ought to be true. Be aware that, even
40
+ though the ODX specification seems to make the distinction of
41
+ "positionable" and "normal" parameters, it does not define any
42
+ non-positionable parameter types.
41
43
 
42
44
  """
43
45
  oid: Optional[str]
@@ -80,18 +80,23 @@ class TableKeyParameter(Parameter):
80
80
  super()._resolve_odxlinks(odxlinks)
81
81
 
82
82
  # Either table_ref or table_row_ref will be defined
83
- if self.table_ref:
83
+ if self.table_ref is not None:
84
84
  if TYPE_CHECKING:
85
85
  self._table = odxlinks.resolve(self.table_ref, Table)
86
86
  else:
87
87
  self._table = odxlinks.resolve(self.table_ref)
88
88
 
89
- if self.table_row_ref:
89
+ if self.table_row_ref is not None:
90
90
  if TYPE_CHECKING:
91
91
  self._table_row = odxlinks.resolve(self.table_row_ref, TableRow)
92
92
  else:
93
93
  self._table_row = odxlinks.resolve(self.table_row_ref)
94
- self._table = self._table_row.table
94
+
95
+ if self.table_ref is None and self.table_snref is None:
96
+ if TYPE_CHECKING:
97
+ self._table = odxlinks.resolve(self._table_row.table_ref, Table)
98
+ else:
99
+ self._table = odxlinks.resolve(self._table_row.table_ref)
95
100
 
96
101
  @override
97
102
  def _resolve_snrefs(self, context: SnRefContext) -> None:
@@ -116,11 +121,7 @@ class TableKeyParameter(Parameter):
116
121
 
117
122
  @property
118
123
  def table(self) -> "Table":
119
- if self._table is not None:
120
- return self._table
121
- if self._table_row is not None:
122
- return self._table_row.table
123
- odxraise(f'Could not resolve the table of {self.short_name}')
124
+ return self._table
124
125
 
125
126
  @property
126
127
  def table_row(self) -> Optional["TableRow"]:
@@ -20,9 +20,16 @@ if TYPE_CHECKING:
20
20
 
21
21
  @dataclass
22
22
  class ParamLengthInfoType(DiagCodedType):
23
-
24
23
  length_key_ref: OdxLinkRef
25
24
 
25
+ @property
26
+ def dct_type(self) -> DctType:
27
+ return "PARAM-LENGTH-INFO-TYPE"
28
+
29
+ @property
30
+ def length_key(self) -> "LengthKeyParameter":
31
+ return self._length_key
32
+
26
33
  @staticmethod
27
34
  @override
28
35
  def from_et(et_element: ElementTree.Element,
@@ -34,10 +41,6 @@ class ParamLengthInfoType(DiagCodedType):
34
41
 
35
42
  return ParamLengthInfoType(length_key_ref=length_key_ref, **kwargs)
36
43
 
37
- @property
38
- def dct_type(self) -> DctType:
39
- return "PARAM-LENGTH-INFO-TYPE"
40
-
41
44
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
42
45
  return super()._build_odxlinks()
43
46
 
@@ -54,10 +57,6 @@ class ParamLengthInfoType(DiagCodedType):
54
57
  """Recursively resolve any short-name references"""
55
58
  super()._resolve_snrefs(context)
56
59
 
57
- @property
58
- def length_key(self) -> "LengthKeyParameter":
59
- return self._length_key
60
-
61
60
  @override
62
61
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
63
62
  bit_length = encode_state.length_keys.get(self.length_key.short_name)
odxtools/request.py CHANGED
@@ -1,39 +1,132 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
6
+ from .admindata import AdminData
7
+ from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
8
+ composite_codec_get_coded_const_prefix, composite_codec_get_free_parameters,
9
+ composite_codec_get_required_parameters, composite_codec_get_static_bit_length)
10
+ from .decodestate import DecodeState
11
+ from .element import IdentifiableElement
7
12
  from .encodestate import EncodeState
8
- from .odxlink import OdxDocFragment
9
- from .odxtypes import ParameterValue
13
+ from .exceptions import odxraise
14
+ from .nameditemlist import NamedItemList
15
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
+ from .odxtypes import ParameterValue, ParameterValueDict
17
+ from .parameters.createanyparameter import create_any_parameter_from_et
18
+ from .parameters.parameter import Parameter
10
19
  from .snrefcontext import SnRefContext
20
+ from .specialdatagroup import SpecialDataGroup
11
21
  from .utils import dataclass_fields_asdict
12
22
 
13
23
 
14
- # TODO: The spec does not say that requests are basic structures. For
15
- # now, we derive from it anyway because it simplifies the en- and
16
- # decoding machinery...
17
24
  @dataclass
18
- class Request(BasicStructure):
25
+ class Request(IdentifiableElement):
26
+ """Represents all information related to an UDS request
27
+
28
+ This class implements the `CompositeCodec` interface.
29
+ """
30
+ admin_data: Optional[AdminData]
31
+ parameters: NamedItemList[Parameter]
32
+ sdgs: List[SpecialDataGroup]
33
+
34
+ @property
35
+ def required_parameters(self) -> List[Parameter]:
36
+ return composite_codec_get_required_parameters(self)
37
+
38
+ @property
39
+ def free_parameters(self) -> List[Parameter]:
40
+ return composite_codec_get_free_parameters(self)
19
41
 
20
42
  @staticmethod
21
43
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Request":
22
- """Reads a response."""
23
- kwargs = dataclass_fields_asdict(BasicStructure.from_et(et_element, doc_frags))
44
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
24
45
 
25
- return Request(**kwargs)
46
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
47
+ parameters = NamedItemList([
48
+ create_any_parameter_from_et(et_parameter, doc_frags)
49
+ for et_parameter in et_element.iterfind("PARAMS/PARAM")
50
+ ])
51
+ sdgs = [
52
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
53
+ ]
26
54
 
27
- def encode(self, **kwargs: ParameterValue) -> bytes:
28
- encode_state = EncodeState(is_end_of_pdu=True)
55
+ return Request(admin_data=admin_data, parameters=parameters, sdgs=sdgs, **kwargs)
29
56
 
30
- self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
57
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
58
+ result = {self.odx_id: self}
31
59
 
32
- return encode_state.coded_message
60
+ if self.admin_data is not None:
61
+ result.update(self.admin_data._build_odxlinks())
62
+
63
+ for param in self.parameters:
64
+ result.update(param._build_odxlinks())
65
+
66
+ for sdg in self.sdgs:
67
+ result.update(sdg._build_odxlinks())
68
+
69
+ return result
70
+
71
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
72
+ if self.admin_data is not None:
73
+ self.admin_data._resolve_odxlinks(odxlinks)
74
+
75
+ for param in self.parameters:
76
+ param._resolve_odxlinks(odxlinks)
77
+
78
+ for sdg in self.sdgs:
79
+ sdg._resolve_odxlinks(odxlinks)
33
80
 
34
81
  def _resolve_snrefs(self, context: SnRefContext) -> None:
35
82
  context.request = self
83
+ context.parameters = self.parameters
36
84
 
37
- super()._resolve_snrefs(context)
85
+ if self.admin_data is not None:
86
+ self.admin_data._resolve_snrefs(context)
87
+
88
+ for param in self.parameters:
89
+ param._resolve_snrefs(context)
90
+
91
+ for sdg in self.sdgs:
92
+ sdg._resolve_snrefs(context)
38
93
 
39
94
  context.request = None
95
+ context.parameters = None
96
+
97
+ def get_static_bit_length(self) -> Optional[int]:
98
+ return composite_codec_get_static_bit_length(self)
99
+
100
+ def print_free_parameters_info(self) -> None:
101
+ """Print a human readable description of the composite codec's
102
+ free parameters to `stdout`
103
+ """
104
+ from .parameterinfo import parameter_info
105
+
106
+ print(parameter_info(self.free_parameters), end="")
107
+
108
+ def encode(self, **kwargs: ParameterValue) -> bytearray:
109
+ encode_state = EncodeState(is_end_of_pdu=True)
110
+
111
+ self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
112
+
113
+ return encode_state.coded_message
114
+
115
+ def decode(self, message: bytes) -> ParameterValueDict:
116
+ decode_state = DecodeState(coded_message=message)
117
+ param_values = self.decode_from_pdu(decode_state)
118
+
119
+ if not isinstance(param_values, dict):
120
+ odxraise("Decoding a request must result in a dictionary")
121
+
122
+ return cast(ParameterValueDict, param_values)
123
+
124
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
125
+ encode_state: EncodeState) -> None:
126
+ composite_codec_encode_into_pdu(self, physical_value, encode_state)
127
+
128
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
129
+ return composite_codec_decode_from_pdu(self, decode_state)
130
+
131
+ def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
132
+ return composite_codec_get_coded_const_prefix(self, request_prefix)
odxtools/response.py CHANGED
@@ -1,15 +1,24 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import List, Optional, cast
4
+ from typing import Any, Dict, List, Optional, cast
5
5
  from xml.etree import ElementTree
6
6
 
7
- from .basicstructure import BasicStructure
7
+ from .admindata import AdminData
8
+ from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
9
+ composite_codec_get_coded_const_prefix, composite_codec_get_free_parameters,
10
+ composite_codec_get_required_parameters, composite_codec_get_static_bit_length)
11
+ from .decodestate import DecodeState
12
+ from .element import IdentifiableElement
8
13
  from .encodestate import EncodeState
9
14
  from .exceptions import odxraise
10
- from .odxlink import OdxDocFragment
11
- from .odxtypes import ParameterValue
15
+ from .nameditemlist import NamedItemList
16
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
17
+ from .odxtypes import ParameterValue, ParameterValueDict
18
+ from .parameters.createanyparameter import create_any_parameter_from_et
19
+ from .parameters.parameter import Parameter
12
20
  from .snrefcontext import SnRefContext
21
+ from .specialdatagroup import SpecialDataGroup
13
22
  from .utils import dataclass_fields_asdict
14
23
 
15
24
 
@@ -19,17 +28,23 @@ class ResponseType(Enum):
19
28
  GLOBAL_NEGATIVE = "GLOBAL-NEG-RESPONSE"
20
29
 
21
30
 
22
- # TODO: The spec does not say that responses are basic structures. For
23
- # now, we derive from it anyway because it simplifies the en- and
24
- # decoding machinery...
25
31
  @dataclass
26
- class Response(BasicStructure):
32
+ class Response(IdentifiableElement):
33
+ """Represents all information related to an UDS response
34
+
35
+ This class implements the `CompositeCodec` interface.
36
+ """
37
+
27
38
  response_type: ResponseType
28
39
 
40
+ admin_data: Optional[AdminData]
41
+ parameters: NamedItemList[Parameter]
42
+ sdgs: List[SpecialDataGroup]
43
+
29
44
  @staticmethod
30
45
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Response":
31
46
  """Reads a response."""
32
- kwargs = dataclass_fields_asdict(BasicStructure.from_et(et_element, doc_frags))
47
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
33
48
 
34
49
  try:
35
50
  response_type = ResponseType(et_element.tag)
@@ -37,18 +52,103 @@ class Response(BasicStructure):
37
52
  response_type = cast(ResponseType, None)
38
53
  odxraise(f"Encountered unknown response type '{et_element.tag}'")
39
54
 
40
- return Response(response_type=response_type, **kwargs)
55
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
56
+ parameters = NamedItemList([
57
+ create_any_parameter_from_et(et_parameter, doc_frags)
58
+ for et_parameter in et_element.iterfind("PARAMS/PARAM")
59
+ ])
60
+ sdgs = [
61
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
62
+ ]
41
63
 
42
- def encode(self, coded_request: Optional[bytes] = None, **kwargs: ParameterValue) -> bytes:
43
- encode_state = EncodeState(triggering_request=coded_request, is_end_of_pdu=True)
64
+ return Response(
65
+ response_type=response_type,
66
+ admin_data=admin_data,
67
+ parameters=parameters,
68
+ sdgs=sdgs,
69
+ **kwargs)
44
70
 
45
- self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
71
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
72
+ result = {self.odx_id: self}
46
73
 
47
- return encode_state.coded_message
74
+ if self.admin_data is not None:
75
+ result.update(self.admin_data._build_odxlinks())
76
+
77
+ for param in self.parameters:
78
+ result.update(param._build_odxlinks())
79
+
80
+ for sdg in self.sdgs:
81
+ result.update(sdg._build_odxlinks())
82
+
83
+ return result
84
+
85
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
86
+ if self.admin_data is not None:
87
+ self.admin_data._resolve_odxlinks(odxlinks)
88
+
89
+ for param in self.parameters:
90
+ param._resolve_odxlinks(odxlinks)
91
+
92
+ for sdg in self.sdgs:
93
+ sdg._resolve_odxlinks(odxlinks)
48
94
 
49
95
  def _resolve_snrefs(self, context: SnRefContext) -> None:
50
96
  context.response = self
97
+ context.parameters = self.parameters
51
98
 
52
- super()._resolve_snrefs(context)
99
+ if self.admin_data is not None:
100
+ self.admin_data._resolve_snrefs(context)
101
+
102
+ for param in self.parameters:
103
+ param._resolve_snrefs(context)
104
+
105
+ for sdg in self.sdgs:
106
+ sdg._resolve_snrefs(context)
53
107
 
54
108
  context.response = None
109
+ context.parameters = None
110
+
111
+ def encode(self, coded_request: Optional[bytes] = None, **kwargs: ParameterValue) -> bytearray:
112
+ encode_state = EncodeState(triggering_request=coded_request, is_end_of_pdu=True)
113
+
114
+ self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
115
+
116
+ return encode_state.coded_message
117
+
118
+ def decode(self, message: bytes) -> ParameterValueDict:
119
+ decode_state = DecodeState(coded_message=message)
120
+ param_values = self.decode_from_pdu(decode_state)
121
+
122
+ if not isinstance(param_values, dict):
123
+ odxraise("Decoding a response must result in a dictionary")
124
+
125
+ return cast(ParameterValueDict, param_values)
126
+
127
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
128
+ encode_state: EncodeState) -> None:
129
+ composite_codec_encode_into_pdu(self, physical_value, encode_state)
130
+
131
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
132
+ return composite_codec_decode_from_pdu(self, decode_state)
133
+
134
+ def get_static_bit_length(self) -> Optional[int]:
135
+ return composite_codec_get_static_bit_length(self)
136
+
137
+ @property
138
+ def required_parameters(self) -> List[Parameter]:
139
+ return composite_codec_get_required_parameters(self)
140
+
141
+ @property
142
+ def free_parameters(self) -> List[Parameter]:
143
+ return composite_codec_get_free_parameters(self)
144
+
145
+ def print_free_parameters_info(self) -> None:
146
+ """Return a human readable description of the structure's
147
+ free parameters.
148
+ """
149
+ from .parameterinfo import parameter_info
150
+
151
+ print(parameter_info(self.free_parameters), end="")
152
+
153
+ def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
154
+ return composite_codec_get_coded_const_prefix(self, request_prefix)
@@ -13,8 +13,8 @@ from .specialdatagroupcaption import SpecialDataGroupCaption
13
13
  class SpecialDataGroup:
14
14
  sdg_caption: Optional[SpecialDataGroupCaption]
15
15
  sdg_caption_ref: Optional[OdxLinkRef]
16
- semantic_info: Optional[str] # the "SI" attribute
17
16
  values: List[Union["SpecialDataGroup", SpecialData]]
17
+ semantic_info: Optional[str] # the "SI" attribute
18
18
 
19
19
  @staticmethod
20
20
  def from_et(et_element: ElementTree.Element,
@@ -5,32 +5,15 @@
5
5
  # This template writes an .odx-c file for a communication
6
6
  # parameter specification.
7
7
  -#}
8
- {%- import('macros/printAdminData.xml.jinja2') as pad -%}
9
- {%- import('macros/printCompanyData.xml.jinja2') as pcd -%}
8
+ {%- import('macros/printOdxCategory.xml.jinja2') as poc %}
10
9
  {%- import('macros/printProtStack.xml.jinja2') as pps %}
11
- {%- import('macros/printDescription.xml.jinja2') as pd %}
12
10
  {#- -#}
13
11
 
14
12
  <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
15
- <ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
16
13
  <!-- Written using odxtools {{odxtools_version}} -->
17
- <COMPARAM-SPEC ID="{{comparam_spec.odx_id.local_id}}">
18
- <SHORT-NAME>{{comparam_spec.short_name}}</SHORT-NAME>
19
- {%- if comparam_spec.long_name is not none %}
20
- <LONG-NAME>{{comparam_spec.long_name|e}}</LONG-NAME>
21
- {%- endif %}
22
- {{pd.printDescription(comparam_spec.description)}}
23
- {%- if comparam_spec.admin_data is not none %}
24
- {{- pad.printAdminData(comparam_spec.admin_data) | indent(3) }}
25
- {%- endif %}
26
- {%- if comparam_spec.company_datas %}
27
- <COMPANY-DATAS>
28
- {%- for cd in comparam_spec.company_datas %}
29
- {{- pcd.printCompanyData(cd) | indent(5) -}}
30
- {%- endfor %}
31
- </COMPANY-DATAS>
32
- {%- endif %}
33
-
14
+ <ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
15
+ <COMPARAM-SPEC {{- poc.printOdxCategoryAttribs(comparam_spec) }}>
16
+ {{- poc.printOdxCategorySubtags(comparam_spec)|indent(3) }}
34
17
  {%- if comparam_spec.prot_stacks %}
35
18
  <PROT-STACKS>
36
19
  {%- for ps in comparam_spec.prot_stacks %}
@@ -5,36 +5,18 @@
5
5
  # This template writes an .odx-cs file for a communication
6
6
  # parameter subset.
7
7
  -#}
8
+ {%- import('macros/printOdxCategory.xml.jinja2') as poc %}
8
9
  {%- import('macros/printComparam.xml.jinja2') as pcp -%}
9
- {%- import('macros/printAdminData.xml.jinja2') as pad -%}
10
- {%- import('macros/printCompanyData.xml.jinja2') as pcd -%}
11
10
  {%- import('macros/printDOP.xml.jinja2') as pdop %}
12
11
  {%- import('macros/printUnitSpec.xml.jinja2') as pus %}
13
- {%- import('macros/printSpecialData.xml.jinja2') as psd %}
14
12
  {%- import('macros/printDescription.xml.jinja2') as pd %}
15
13
  {#- -#}
16
14
 
17
15
  <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
18
- <ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
19
16
  <!-- Written using odxtools {{odxtools_version}} -->
20
- <COMPARAM-SUBSET ID="{{comparam_subset.odx_id.local_id}}"
21
- CATEGORY="{{comparam_subset.category}}" >
22
- <SHORT-NAME>{{comparam_subset.short_name}}</SHORT-NAME>
23
- {%- if comparam_subset.long_name is not none %}
24
- <LONG-NAME>{{comparam_subset.long_name|e}}</LONG-NAME>
25
- {%- endif %}
26
- {{pd.printDescription(comparam_subset.description)}}
27
- {%- if comparam_subset.admin_data is not none %}
28
- {{- pad.printAdminData(comparam_subset.admin_data) | indent(3) }}
29
- {%- endif %}
30
- {%- if comparam_subset.company_datas %}
31
- <COMPANY-DATAS>
32
- {%- for cd in comparam_subset.company_datas %}
33
- {{- pcd.printCompanyData(cd) | indent(5, first=True) }}
34
- {%- endfor %}
35
- </COMPANY-DATAS>
36
- {%- endif %}
37
- {{- psd.printSpecialDataGroups(comparam_subset.sdgs)|indent(3, first=True) }}
17
+ <ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
18
+ <COMPARAM-SUBSET {{- poc.printOdxCategoryAttribs(comparam_subset) }} {{make_xml_attrib("CATEGORY", comparam_subset.category)}}>
19
+ {{- poc.printOdxCategorySubtags(comparam_subset)|indent(3) }}
38
20
  {%- if comparam_subset.comparams %}
39
21
  <COMPARAMS>
40
22
  {%- for cp in comparam_subset.comparams %}