odxtools 6.7.0__py3-none-any.whl → 9.3.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 (213) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ from .utils import is_short_name, is_short_name_path
12
12
  class MatchingParameter:
13
13
  """According to ISO 22901, a MatchingParameter contains a string value identifying
14
14
  the active ECU variant. Moreover, it references a DIAG-COMM via snref and one of its
15
- positiv response's OUT-PARAM-IF via snref or snpathref.
15
+ positive response's OUT-PARAM-IF via snref or snpathref.
16
16
 
17
17
  Unlike other parameters defined in the `parameters` package, a MatchingParameter is
18
18
  not transferred over the network.
@@ -32,8 +32,8 @@ class MatchingParameter:
32
32
  doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
33
33
 
34
34
  expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
35
- diag_com_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
- diag_comm_snref = odxrequire(diag_com_snref_el.get("SHORT-NAME"))
35
+ diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
+ diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME"))
37
37
  out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF")
38
38
  out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF")
39
39
  out_param_if = None
odxtools/message.py CHANGED
@@ -2,8 +2,6 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Union
4
4
 
5
- from deprecation import deprecated
6
-
7
5
  from .odxtypes import ParameterValue, ParameterValueDict
8
6
 
9
7
  if TYPE_CHECKING:
@@ -29,8 +27,3 @@ class Message:
29
27
 
30
28
  def __getitem__(self, key: str) -> ParameterValue:
31
29
  return self.param_dict[key]
32
-
33
- @property
34
- @deprecated("use .coding_object") # type: ignore[misc]
35
- def structure(self) -> Union["Request", "Response"]:
36
- return self.coding_object
@@ -1,12 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Optional
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
4
7
 
5
8
  from .decodestate import DecodeState
6
9
  from .diagcodedtype import DctType, DiagCodedType
7
10
  from .encodestate import EncodeState
8
- from .exceptions import DecodeError, EncodeError, odxassert, odxraise
11
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment
9
13
  from .odxtypes import AtomicOdxType, DataType
14
+ from .utils import dataclass_fields_asdict
10
15
 
11
16
 
12
17
  @dataclass
@@ -15,6 +20,25 @@ class MinMaxLengthType(DiagCodedType):
15
20
  max_length: Optional[int]
16
21
  termination: str
17
22
 
23
+ @property
24
+ def dct_type(self) -> DctType:
25
+ return "MIN-MAX-LENGTH-TYPE"
26
+
27
+ @staticmethod
28
+ @override
29
+ def from_et(et_element: ElementTree.Element,
30
+ doc_frags: List[OdxDocFragment]) -> "MinMaxLengthType":
31
+ kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
32
+
33
+ min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
34
+ max_length = None
35
+ if et_element.find("MAX-LENGTH") is not None:
36
+ max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
37
+ termination = odxrequire(et_element.get("TERMINATION"))
38
+
39
+ return MinMaxLengthType(
40
+ min_length=min_length, max_length=max_length, termination=termination, **kwargs)
41
+
18
42
  def __post_init__(self) -> None:
19
43
  odxassert(self.max_length is None or self.min_length <= self.max_length)
20
44
  odxassert(
@@ -30,10 +54,6 @@ class MinMaxLengthType(DiagCodedType):
30
54
  "END-OF-PDU",
31
55
  ], f"A min-max length type cannot have the termination {self.termination}")
32
56
 
33
- @property
34
- def dct_type(self) -> DctType:
35
- return "MIN-MAX-LENGTH-TYPE"
36
-
37
57
  def __termination_sequence(self) -> bytes:
38
58
  """Returns the termination byte sequence if it isn't defined."""
39
59
  # The termination sequence is actually not specified by ASAM
@@ -51,9 +71,10 @@ class MinMaxLengthType(DiagCodedType):
51
71
  termination_sequence = bytes([0xFF, 0xFF])
52
72
  return termination_sequence
53
73
 
54
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
55
- bit_position: int) -> bytes:
56
- if not isinstance(internal_value, (bytes, str)):
74
+ @override
75
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
76
+
77
+ if not isinstance(internal_value, (bytes, str, bytearray)):
57
78
  odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
58
79
  EncodeError)
59
80
 
@@ -62,20 +83,24 @@ class MinMaxLengthType(DiagCodedType):
62
83
  else:
63
84
  data_length = len(internal_value)
64
85
 
65
- value_bytes = bytearray(
66
- self._encode_internal_value(
67
- internal_value,
68
- bit_position=0,
69
- bit_length=8 * data_length,
70
- base_data_type=self.base_data_type,
71
- is_highlow_byte_order=self.is_highlow_byte_order,
72
- ))
86
+ orig_cursor = encode_state.cursor_byte_position
87
+ encode_state.emplace_atomic_value(
88
+ internal_value=internal_value,
89
+ used_mask=None,
90
+ bit_length=8 * data_length,
91
+ base_data_type=self.base_data_type,
92
+ is_highlow_byte_order=self.is_highlow_byte_order,
93
+ )
94
+ value_len = encode_state.cursor_byte_position - orig_cursor
73
95
 
74
96
  # TODO: ensure that the termination delimiter is not
75
97
  # encountered within the encoded value.
76
98
 
77
- odxassert(self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu)
78
- if encode_state.is_end_of_pdu or len(value_bytes) == self.max_length:
99
+ odxassert(
100
+ self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
101
+ "Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
102
+ "which is not located at the end of the PDU")
103
+ if encode_state.is_end_of_pdu or value_len == self.max_length:
79
104
  # All termination types may be ended by the end of the PDU
80
105
  # or once reaching the maximum length. In this case, we
81
106
  # must not add the termination sequence
@@ -85,20 +110,23 @@ class MinMaxLengthType(DiagCodedType):
85
110
 
86
111
  # ensure that we don't try to encode an odd-length
87
112
  # value when using a two-byte terminator
88
- odxassert(len(value_bytes) % len(termination_sequence) == 0)
89
-
90
- value_bytes.extend(termination_sequence)
91
-
92
- if len(value_bytes) < self.min_length:
93
- raise EncodeError(f"Encoded value for MinMaxLengthType "
94
- f"must be at least {self.min_length} bytes long. "
95
- f"(Is: {len(value_bytes)} bytes.)")
96
- elif self.max_length is not None and len(value_bytes) > self.max_length:
97
- raise EncodeError(f"Encoded value for MinMaxLengthType "
98
- f"must not be longer than {self.max_length} bytes. "
99
- f"(Is: {len(value_bytes)} bytes.)")
100
-
101
- return value_bytes
113
+ odxassert(value_len % len(termination_sequence) == 0)
114
+
115
+ value_len += len(termination_sequence)
116
+ encode_state.emplace_bytes(termination_sequence)
117
+
118
+ if value_len < self.min_length:
119
+ odxraise(
120
+ f"Encoded value for MinMaxLengthType "
121
+ f"must be at least {self.min_length} bytes long. "
122
+ f"(Is: {value_len} bytes.)", EncodeError)
123
+ return
124
+ elif self.max_length is not None and value_len > self.max_length:
125
+ odxraise(
126
+ f"Encoded value for MinMaxLengthType "
127
+ f"must not be longer than {self.max_length} bytes. "
128
+ f"(Is: {value_len} bytes.)", EncodeError)
129
+ return
102
130
 
103
131
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
104
132
  odxassert(decode_state.cursor_bit_position == 0,
odxtools/modification.py CHANGED
@@ -1,12 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
7
-
8
- if TYPE_CHECKING:
9
- from .diaglayer import DiagLayer
7
+ from .snrefcontext import SnRefContext
10
8
 
11
9
 
12
10
  @dataclass
@@ -27,5 +25,5 @@ class Modification:
27
25
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
28
26
  pass
29
27
 
30
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
28
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
31
29
  pass
odxtools/multiplexer.py CHANGED
@@ -1,22 +1,23 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
3
+ from typing import Any, Dict, List, Optional, Tuple, Union, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .complexdop import ComplexDop
7
9
  from .decodestate import DecodeState
8
10
  from .encodestate import EncodeState
9
- from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
11
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
10
12
  from .multiplexercase import MultiplexerCase
11
13
  from .multiplexerdefaultcase import MultiplexerDefaultCase
12
14
  from .multiplexerswitchkey import MultiplexerSwitchKey
15
+ from .nameditemlist import NamedItemList
13
16
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
14
17
  from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
18
+ from .snrefcontext import SnRefContext
15
19
  from .utils import dataclass_fields_asdict
16
20
 
17
- if TYPE_CHECKING:
18
- from .diaglayer import DiagLayer
19
-
20
21
 
21
22
  @dataclass
22
23
  class Multiplexer(ComplexDop):
@@ -30,10 +31,11 @@ class Multiplexer(ComplexDop):
30
31
  byte_position: int
31
32
  switch_key: MultiplexerSwitchKey
32
33
  default_case: Optional[MultiplexerDefaultCase]
33
- cases: List[MultiplexerCase]
34
+ cases: NamedItemList[MultiplexerCase]
34
35
  is_visible_raw: Optional[bool]
35
36
 
36
37
  @staticmethod
38
+ @override
37
39
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
38
40
  """Reads a Multiplexer from Diag Layer."""
39
41
  base_obj = ComplexDop.from_et(et_element, doc_frags)
@@ -47,9 +49,8 @@ class Multiplexer(ComplexDop):
47
49
  if (dc_elem := et_element.find("DEFAULT-CASE")) is not None:
48
50
  default_case = MultiplexerDefaultCase.from_et(dc_elem, doc_frags)
49
51
 
50
- cases = []
51
- if (cases_elem := et_element.find("CASES")) is not None:
52
- cases = [MultiplexerCase.from_et(el, doc_frags) for el in cases_elem.iterfind("CASE")]
52
+ cases = NamedItemList(
53
+ [MultiplexerCase.from_et(el, doc_frags) for el in et_element.iterfind("CASES/CASE")])
53
54
 
54
55
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
55
56
 
@@ -67,91 +68,143 @@ class Multiplexer(ComplexDop):
67
68
 
68
69
  def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
69
70
  key_type = self.switch_key.dop.physical_type.base_data_type
70
- lower_limit = key_type.make_from(case.lower_limit)
71
- upper_limit = key_type.make_from(case.upper_limit)
71
+ lower_limit = key_type.make_from(case.lower_limit.value)
72
+ upper_limit = key_type.make_from(case.upper_limit.value)
72
73
  if not isinstance(lower_limit, type(upper_limit)) and not isinstance(
73
74
  upper_limit, type(lower_limit)):
74
75
  odxraise("Upper and lower bounds of limits must compareable")
75
76
  return lower_limit, upper_limit
76
77
 
77
- def convert_physical_to_bytes(self,
78
- physical_value: ParameterValue,
79
- encode_state: EncodeState,
80
- bit_position: int = 0) -> bytes:
81
-
82
- if bit_position != 0:
83
- raise EncodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
84
- f"{self.short_name} was passed the bit position {bit_position}")
85
-
86
- if not isinstance(physical_value, dict) or len(physical_value) != 1:
87
- raise EncodeError("""Multiplexer should be defined as a dict
88
- with only one key equal to the desired case""")
89
-
90
- case_name, case_value = next(iter(physical_value.items()))
91
- case_pos = self.byte_position
92
-
93
- for mux_case in self.cases or []:
94
- if mux_case.short_name == case_name:
95
- if mux_case._structure:
96
- case_bytes = mux_case._structure.convert_physical_to_bytes(
97
- case_value, encode_state, 0)
98
- else:
99
- case_bytes = b''
100
-
78
+ @override
79
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
80
+
81
+ if encode_state.cursor_bit_position != 0:
82
+ raise EncodeError(f"Multiplexer parameters must be aligned, i.e. bit_position=0, but "
83
+ f"{self.short_name} was passed the bit position "
84
+ f"{encode_state.cursor_bit_position}")
85
+
86
+ orig_origin = encode_state.origin_byte_position
87
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
88
+
89
+ if isinstance(physical_value, (list, tuple)) and len(physical_value) == 2:
90
+ case_spec, case_value = physical_value
91
+ elif isinstance(physical_value, dict) and len(physical_value) == 1:
92
+ case_spec, case_value = next(iter(physical_value.items()))
93
+ else:
94
+ raise EncodeError(
95
+ f"Values of multiplexer parameters must be defined as a "
96
+ f"(case_name, content_value) tuple instead of as '{physical_value!r}'")
97
+
98
+ mux_case: Union[MultiplexerCase, MultiplexerDefaultCase]
99
+ applicable_cases: List[Union[MultiplexerCase, MultiplexerDefaultCase]]
100
+
101
+ if isinstance(case_spec, str):
102
+ applicable_cases = [x for x in self.cases if x.short_name == case_spec]
103
+ if not applicable_cases and self.default_case:
104
+ applicable_cases.append(self.default_case)
105
+ if len(applicable_cases) == 0:
106
+ raise EncodeError(
107
+ f"Multiplexer {self.short_name} does not know any case called {case_spec}")
108
+
109
+ odxassert(len(applicable_cases) == 1)
110
+ mux_case = applicable_cases[0]
111
+ if isinstance(mux_case, MultiplexerCase):
101
112
  key_value, _ = self._get_case_limits(mux_case)
102
- key_bytes = self.switch_key.dop.convert_physical_to_bytes(
103
- key_value, encode_state, bit_position=self.switch_key.bit_position or 0)
104
-
105
- mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
106
- mux_bytes = bytearray(mux_len)
107
- mux_bytes[:len(key_bytes)] = key_bytes
108
- mux_bytes[case_pos:case_pos + len(case_bytes)] = case_bytes
109
-
110
- return bytes(mux_bytes)
111
-
112
- raise EncodeError(f"The case {case_name} is not found in Multiplexer {self.short_name}")
113
-
113
+ else:
114
+ key_value = 0
115
+ elif isinstance(case_spec, int):
116
+ applicable_cases = []
117
+ for x in self.cases:
118
+ lower, upper = cast(Tuple[int, int], self._get_case_limits(x))
119
+ if lower <= case_spec and case_spec <= upper:
120
+ applicable_cases.append(x)
121
+
122
+ if len(applicable_cases) == 0:
123
+ if self.default_case is None:
124
+ raise EncodeError(
125
+ f"Multiplexer {self.short_name} does not know any case called {case_spec}")
126
+ mux_case = self.default_case
127
+ key_value = case_spec
128
+ else:
129
+ mux_case = applicable_cases[0]
130
+ key_value = case_spec
131
+ elif isinstance(case_spec, MultiplexerCase):
132
+ mux_case = case_spec
133
+ key_value, _ = self._get_case_limits(mux_case)
134
+ elif case_spec is None:
135
+ if self.default_case is None:
136
+ raise EncodeError(f"Multiplexer {self.short_name} does not define a default case")
137
+ key_value = 0
138
+ else:
139
+ raise EncodeError(f"Illegal case specification '{case_spec}' for "
140
+ f"multiplexer {self.short_name}")
141
+
142
+ # the byte position of the switch key is relative to
143
+ # the multiplexer's position
144
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
145
+ encode_state.cursor_bit_position = self.switch_key.bit_position or 0
146
+ self.switch_key.dop.encode_into_pdu(physical_value=key_value, encode_state=encode_state)
147
+ encode_state.cursor_bit_position = 0
148
+
149
+ if mux_case.structure is not None:
150
+ # the byte position of the content is specified by the
151
+ # BYTE-POSITION attribute of the multiplexer
152
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
153
+ mux_case.structure.encode_into_pdu(physical_value=case_value, encode_state=encode_state)
154
+
155
+ encode_state.origin_byte_position = orig_origin
156
+
157
+ @override
114
158
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
115
-
116
- # multiplexers are structures and thus the origin position
117
- # must be moved to the start of the multiplexer
118
159
  orig_origin = decode_state.origin_byte_position
119
- orig_cursor = decode_state.cursor_byte_position
120
- if self.byte_position is not None:
121
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
122
160
  decode_state.origin_byte_position = decode_state.cursor_byte_position
123
161
 
162
+ # Decode the switch key. Its BYTE-POSITION is relative to the
163
+ # that of the multiplexer.
164
+ if self.switch_key.byte_position is not None:
165
+ decode_state.cursor_byte_position = decode_state.origin_byte_position + self.switch_key.byte_position
166
+ decode_state.cursor_bit_position = self.switch_key.bit_position or 0
124
167
  key_value = self.switch_key.dop.decode_from_pdu(decode_state)
168
+ decode_state.cursor_bit_position = 0
125
169
 
126
170
  if not isinstance(key_value, int):
127
171
  odxraise(f"Multiplexer keys must be integers (is '{type(key_value).__name__}'"
128
172
  f" for multiplexer '{self.short_name}')")
129
173
 
130
- case_value: Optional[ParameterValue] = None
131
- mux_case = None
132
- for mux_case in self.cases or []:
174
+ # "If a matching CASE is found, the referenced STRUCTURE is
175
+ # analyzed at the BYTE-POSITION (child element of MUX)
176
+ # relatively to the byte position of the MUX."
177
+ decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
178
+
179
+ applicable_case: Optional[Union[MultiplexerCase, MultiplexerDefaultCase]] = None
180
+ for mux_case in self.cases:
133
181
  lower, upper = self._get_case_limits(mux_case)
134
182
  if lower <= key_value and key_value <= upper: # type: ignore[operator]
135
- if mux_case._structure:
136
- case_value = mux_case._structure.decode_from_pdu(decode_state)
183
+ applicable_case = mux_case
137
184
  break
138
185
 
139
- if case_value is None and self.default_case is not None:
140
- if self.default_case._structure:
141
- case_value = self.default_case._structure.decode_from_pdu(decode_state)
186
+ if applicable_case is None:
187
+ applicable_case = self.default_case
188
+
189
+ if applicable_case is None:
190
+ odxraise(
191
+ f"Cannot find an applicable case for value {key_value} in "
192
+ f"multiplexer {self.short_name}", DecodeError)
193
+ decode_state.origin_byte_position = orig_origin
194
+ return (None, None)
142
195
 
143
- if mux_case is None or case_value is None:
144
- odxraise(f"Failed to find a matching case in {self.short_name} for value {key_value!r}",
145
- DecodeError)
196
+ if applicable_case.structure is not None:
197
+ case_value = applicable_case.structure.decode_from_pdu(decode_state)
198
+ else:
199
+ case_value = {}
146
200
 
147
- mux_value = (mux_case.short_name, case_value)
201
+ result = (applicable_case.short_name, case_value)
148
202
 
149
- # go back to the original origin
150
203
  decode_state.origin_byte_position = orig_origin
151
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
152
204
 
153
- return mux_value
205
+ return result
154
206
 
207
+ @override
155
208
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
156
209
  odxlinks = super()._build_odxlinks()
157
210
 
@@ -161,6 +214,7 @@ class Multiplexer(ComplexDop):
161
214
 
162
215
  return odxlinks
163
216
 
217
+ @override
164
218
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
165
219
  super()._resolve_odxlinks(odxlinks)
166
220
 
@@ -172,12 +226,13 @@ class Multiplexer(ComplexDop):
172
226
  mux_case._mux_case_resolve_odxlinks(
173
227
  odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
174
228
 
175
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
176
- super()._resolve_snrefs(diag_layer)
229
+ @override
230
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
231
+ super()._resolve_snrefs(context)
177
232
 
178
- self.switch_key._resolve_snrefs(diag_layer)
233
+ self.switch_key._resolve_snrefs(context)
179
234
  if self.default_case is not None:
180
- self.default_case._resolve_snrefs(diag_layer)
235
+ self.default_case._resolve_snrefs(context)
181
236
 
182
237
  for mux_case in self.cases:
183
- mux_case._resolve_snrefs(diag_layer)
238
+ mux_case._resolve_snrefs(context)
@@ -1,23 +1,21 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
7
6
  from .compumethods.limit import Limit
8
7
  from .element import NamedElement
9
8
  from .exceptions import odxrequire
10
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
11
10
  from .odxtypes import AtomicOdxType, DataType
11
+ from .snrefcontext import SnRefContext
12
+ from .structure import Structure
12
13
  from .utils import dataclass_fields_asdict
13
14
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
15
 
18
16
  @dataclass
19
17
  class MultiplexerCase(NamedElement):
20
- """This class represents a Case which represents multiple options in a Multiplexer."""
18
+ """This class represents a case which represents a range of keys of a multiplexer."""
21
19
 
22
20
  structure_ref: Optional[OdxLinkRef]
23
21
  structure_snref: Optional[str]
@@ -25,7 +23,7 @@ class MultiplexerCase(NamedElement):
25
23
  upper_limit: Limit
26
24
 
27
25
  def __post_init__(self) -> None:
28
- self._structure: BasicStructure
26
+ self._structure: Optional[Structure]
29
27
 
30
28
  @staticmethod
31
29
  def from_et(et_element: ElementTree.Element,
@@ -60,25 +58,26 @@ class MultiplexerCase(NamedElement):
60
58
 
61
59
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
62
60
  raise RuntimeError("Calling MultiplexerCase._resolve_odxlinks() is not allowed. "
63
- "Use ._mux_case_resolve_odxlinks()().")
61
+ "Use ._mux_case_resolve_odxlinks().")
64
62
 
65
63
  def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *,
66
64
  key_physical_type: DataType) -> None:
65
+ self._structure = None
67
66
  if self.structure_ref:
68
- self._structure = odxlinks.resolve(self.structure_ref)
67
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
69
68
 
70
69
  self.lower_limit.set_value_type(key_physical_type)
71
70
  self.upper_limit.set_value_type(key_physical_type)
72
71
 
73
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
72
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
74
73
  if self.structure_snref:
75
- ddds = diag_layer.diag_data_dictionary_spec
76
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
74
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
75
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
77
76
 
78
77
  def applies(self, value: AtomicOdxType) -> bool:
79
78
  return self.lower_limit.complies_to_lower(value) \
80
79
  and self.upper_limit.complies_to_upper(value)
81
80
 
82
81
  @property
83
- def structure(self) -> BasicStructure:
82
+ def structure(self) -> Optional[Structure]:
84
83
  return self._structure
@@ -1,17 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
7
6
  from .element import NamedElement
8
7
  from .exceptions import odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
9
+ from .snrefcontext import SnRefContext
10
+ from .structure import Structure
10
11
  from .utils import dataclass_fields_asdict
11
12
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
13
 
16
14
  @dataclass
17
15
  class MultiplexerDefaultCase(NamedElement):
@@ -20,12 +18,12 @@ class MultiplexerDefaultCase(NamedElement):
20
18
  structure_snref: Optional[str]
21
19
 
22
20
  def __post_init__(self) -> None:
23
- self._structure: Optional[BasicStructure] = None
21
+ self._structure: Optional[Structure]
24
22
 
25
23
  @staticmethod
26
24
  def from_et(et_element: ElementTree.Element,
27
25
  doc_frags: List[OdxDocFragment]) -> "MultiplexerDefaultCase":
28
- """Reads a Default Case for a Multiplexer."""
26
+ """Reads a default case for a multiplexer."""
29
27
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
30
28
 
31
29
  structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
@@ -40,10 +38,15 @@ class MultiplexerDefaultCase(NamedElement):
40
38
  return {}
41
39
 
42
40
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
41
+ self._structure = None
43
42
  if self.structure_ref is not None:
44
- self._structure = odxlinks.resolve(self.structure_ref)
43
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
45
44
 
46
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
45
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
47
46
  if self.structure_snref:
48
- ddds = diag_layer.diag_data_dictionary_spec
49
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
47
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
48
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
49
+
50
+ @property
51
+ def structure(self) -> Optional[Structure]:
52
+ return self._structure
@@ -1,13 +1,12 @@
1
+ # SPDX-License-Identifier: MIT
1
2
  from dataclasses import dataclass
2
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
3
4
  from xml.etree import ElementTree
4
5
 
5
6
  from .dataobjectproperty import DataObjectProperty
6
7
  from .exceptions import odxrequire
7
8
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
-
9
- if TYPE_CHECKING:
10
- from .diaglayer import DiagLayer
9
+ from .snrefcontext import SnRefContext
11
10
 
12
11
 
13
12
  @dataclass
@@ -39,7 +38,7 @@ class MultiplexerSwitchKey:
39
38
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
40
39
  self._dop = odxlinks.resolve(self.dop_ref, DataObjectProperty)
41
40
 
42
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
41
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
43
42
  pass
44
43
 
45
44
  @property