odxtools 6.6.1__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 (222) hide show
  1. odxtools/__init__.py +7 -5
  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 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
odxtools/library.py ADDED
@@ -0,0 +1,66 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional, cast
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import IdentifiableElement
7
+ from .exceptions import odxraise, odxrequire
8
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
+ from .snrefcontext import SnRefContext
10
+ from .utils import dataclass_fields_asdict
11
+
12
+
13
+ @dataclass
14
+ class Library(IdentifiableElement):
15
+ """
16
+ A library defines a shared library used for single ECU jobs etc.
17
+
18
+ It this is basically equivalent to ProgCode.
19
+ """
20
+
21
+ code_file: str
22
+ encryption: Optional[str]
23
+ syntax: str
24
+ revision: str
25
+ entrypoint: Optional[str]
26
+
27
+ @property
28
+ def code(self) -> bytes:
29
+ return self._code
30
+
31
+ @staticmethod
32
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Library":
33
+
34
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
35
+
36
+ code_file = odxrequire(et_element.findtext("CODE-FILE"))
37
+ encryption = et_element.findtext("ENCRYPTION")
38
+ syntax = odxrequire(et_element.findtext("SYNTAX"))
39
+ revision = odxrequire(et_element.findtext("REVISION"))
40
+ entrypoint = et_element.findtext("ENTRYPOINT")
41
+
42
+ return Library(
43
+ code_file=code_file,
44
+ encryption=encryption,
45
+ syntax=syntax,
46
+ revision=revision,
47
+ entrypoint=entrypoint,
48
+ **kwargs)
49
+
50
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
51
+ return {self.odx_id: self}
52
+
53
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
54
+ pass
55
+
56
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
57
+ aux_file = odxrequire(context.database).auxiliary_files.get(self.code_file)
58
+
59
+ if aux_file is None:
60
+ odxraise(f"Reference to auxiliary file '{self.code_file}' "
61
+ f"could not be resolved")
62
+ self._code: bytes = cast(bytes, None)
63
+ return
64
+
65
+ self._code = aux_file.read()
66
+ aux_file.seek(0)
odxtools/loadfile.py ADDED
@@ -0,0 +1,64 @@
1
+ # SPDX-License-Identifier: MIT
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Union
5
+
6
+ from .database import Database
7
+
8
+
9
+ def load_pdx_file(pdx_file: Union[str, Path]) -> Database:
10
+ db = Database()
11
+ db.add_pdx_file(str(pdx_file))
12
+ db.refresh()
13
+ return db
14
+
15
+
16
+ def load_odx_d_file(odx_d_file_name: Union[str, Path]) -> Database:
17
+ db = Database()
18
+ db.add_odx_file(str(odx_d_file_name))
19
+ db.refresh()
20
+
21
+ return db
22
+
23
+
24
+ def load_file(file_name: Union[str, Path]) -> Database:
25
+ if str(file_name).lower().endswith(".pdx"):
26
+ return load_pdx_file(str(file_name))
27
+ elif str(file_name).lower().endswith(".odx-d"):
28
+ return load_odx_d_file(str(file_name))
29
+ else:
30
+ raise RuntimeError(f"Could not guess the file format of file '{file_name}'!")
31
+
32
+
33
+ def load_files(*file_names: Union[str, Path]) -> Database:
34
+ db = Database()
35
+ for file_name in file_names:
36
+ p = Path(file_name)
37
+ if p.suffix.lower() == ".pdx":
38
+ db.add_pdx_file(str(file_name))
39
+ elif p.suffix.lower().startswith(".odx"):
40
+ db.add_odx_file(str(file_name))
41
+ elif p.name.lower() != "index.xml":
42
+ db.add_auxiliary_file(str(file_name))
43
+
44
+ db.refresh()
45
+ return db
46
+
47
+
48
+ def load_directory(dir_name: Union[str, Path]) -> Database:
49
+ db = Database()
50
+ for file_name in os.listdir(str(dir_name)):
51
+ p = Path(dir_name) / file_name
52
+
53
+ if not p.is_file():
54
+ continue
55
+
56
+ if p.suffix.lower() == ".pdx":
57
+ db.add_pdx_file(str(p))
58
+ elif p.suffix.lower().startswith(".odx"):
59
+ db.add_odx_file(str(p))
60
+ elif p.name.lower() != "index.xml":
61
+ db.add_auxiliary_file(p.name, open(str(p), "rb"))
62
+
63
+ db.refresh()
64
+ return db
@@ -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")
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,13 +31,15 @@ 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
- kwargs = dataclass_fields_asdict(ComplexDop.from_et(et_element, doc_frags))
41
+ base_obj = ComplexDop.from_et(et_element, doc_frags)
42
+ kwargs = dataclass_fields_asdict(base_obj)
40
43
 
41
44
  byte_position = int(et_element.findtext("BYTE-POSITION", "0"))
42
45
  switch_key = MultiplexerSwitchKey.from_et(
@@ -46,9 +49,8 @@ class Multiplexer(ComplexDop):
46
49
  if (dc_elem := et_element.find("DEFAULT-CASE")) is not None:
47
50
  default_case = MultiplexerDefaultCase.from_et(dc_elem, doc_frags)
48
51
 
49
- cases = []
50
- if (cases_elem := et_element.find("CASES")) is not None:
51
- 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")])
52
54
 
53
55
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
54
56
 
@@ -66,88 +68,143 @@ class Multiplexer(ComplexDop):
66
68
 
67
69
  def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
68
70
  key_type = self.switch_key.dop.physical_type.base_data_type
69
- lower_limit = key_type.make_from(case.lower_limit)
70
- 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)
71
73
  if not isinstance(lower_limit, type(upper_limit)) and not isinstance(
72
74
  upper_limit, type(lower_limit)):
73
75
  odxraise("Upper and lower bounds of limits must compareable")
74
76
  return lower_limit, upper_limit
75
77
 
76
- def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
77
- bit_position: int) -> bytes:
78
-
79
- if bit_position != 0:
80
- raise EncodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
81
- f"{self.short_name} was passed the bit position {bit_position}")
82
-
83
- if not isinstance(physical_value, dict) or len(physical_value) != 1:
84
- raise EncodeError("""Multiplexer should be defined as a dict
85
- with only one key equal to the desired case""")
86
-
87
- case_name, case_value = next(iter(physical_value.items()))
88
- case_pos = self.byte_position
89
-
90
- for mux_case in self.cases or []:
91
- if mux_case.short_name == case_name:
92
- if mux_case._structure:
93
- case_bytes = mux_case._structure.convert_physical_to_bytes(
94
- case_value, encode_state, 0)
95
- else:
96
- case_bytes = b''
97
-
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):
98
112
  key_value, _ = self._get_case_limits(mux_case)
99
- key_bytes = self.switch_key.dop.convert_physical_to_bytes(
100
- key_value, encode_state, bit_position=self.switch_key.bit_position or 0)
101
-
102
- mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
103
- mux_bytes = bytearray(mux_len)
104
- mux_bytes[:len(key_bytes)] = key_bytes
105
- mux_bytes[case_pos:case_pos + len(case_bytes)] = case_bytes
106
-
107
- return bytes(mux_bytes)
108
-
109
- raise EncodeError(f"The case {case_name} is not found in Multiplexer {self.short_name}")
110
-
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
111
158
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
112
-
113
- # multiplexers are structures and thus the origin position
114
- # must be moved to the start of the multiplexer
115
159
  orig_origin = decode_state.origin_byte_position
116
- orig_cursor = decode_state.cursor_byte_position
117
- if self.byte_position is not None:
118
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
119
160
  decode_state.origin_byte_position = decode_state.cursor_byte_position
120
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
121
167
  key_value = self.switch_key.dop.decode_from_pdu(decode_state)
168
+ decode_state.cursor_bit_position = 0
122
169
 
123
170
  if not isinstance(key_value, int):
124
171
  odxraise(f"Multiplexer keys must be integers (is '{type(key_value).__name__}'"
125
172
  f" for multiplexer '{self.short_name}')")
126
173
 
127
- case_value: Optional[ParameterValue] = None
128
- for case in self.cases or []:
129
- lower, upper = self._get_case_limits(case)
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:
181
+ lower, upper = self._get_case_limits(mux_case)
130
182
  if lower <= key_value and key_value <= upper: # type: ignore[operator]
131
- if case._structure:
132
- case_value = case._structure.decode_from_pdu(decode_state)
183
+ applicable_case = mux_case
133
184
  break
134
185
 
135
- if case_value is None and self.default_case is not None:
136
- if self.default_case._structure:
137
- 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)
138
195
 
139
- if case_value is None:
140
- odxraise(f"Failed to find a matching case in {self.short_name} for value {key_value!r}",
141
- 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 = {}
142
200
 
143
- mux_value = (case.short_name, case_value)
201
+ result = (applicable_case.short_name, case_value)
144
202
 
145
- # go back to the original origin
146
203
  decode_state.origin_byte_position = orig_origin
147
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
148
204
 
149
- return mux_value
205
+ return result
150
206
 
207
+ @override
151
208
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
152
209
  odxlinks = super()._build_odxlinks()
153
210
 
@@ -157,6 +214,7 @@ class Multiplexer(ComplexDop):
157
214
 
158
215
  return odxlinks
159
216
 
217
+ @override
160
218
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
161
219
  super()._resolve_odxlinks(odxlinks)
162
220
 
@@ -164,15 +222,17 @@ class Multiplexer(ComplexDop):
164
222
  if self.default_case is not None:
165
223
  self.default_case._resolve_odxlinks(odxlinks)
166
224
 
167
- for case in self.cases:
168
- case._resolve_odxlinks(odxlinks)
225
+ for mux_case in self.cases:
226
+ mux_case._mux_case_resolve_odxlinks(
227
+ odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
169
228
 
170
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
171
- super()._resolve_snrefs(diag_layer)
229
+ @override
230
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
231
+ super()._resolve_snrefs(context)
172
232
 
173
- self.switch_key._resolve_snrefs(diag_layer)
233
+ self.switch_key._resolve_snrefs(context)
174
234
  if self.default_case is not None:
175
- self.default_case._resolve_snrefs(diag_layer)
235
+ self.default_case._resolve_snrefs(context)
176
236
 
177
- for case in self.cases:
178
- case._resolve_snrefs(diag_layer)
237
+ for mux_case in self.cases:
238
+ mux_case._resolve_snrefs(context)