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
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
3
+ from typing import Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .compumethods.compumethod import CompuMethod
@@ -10,17 +10,15 @@ from .decodestate import DecodeState
10
10
  from .diagcodedtype import DiagCodedType
11
11
  from .dopbase import DopBase
12
12
  from .encodestate import EncodeState
13
- from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
13
+ from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
14
14
  from .internalconstr import InternalConstr
15
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
16
16
  from .odxtypes import AtomicOdxType, ParameterValue
17
17
  from .physicaltype import PhysicalType
18
+ from .snrefcontext import SnRefContext
18
19
  from .unit import Unit
19
20
  from .utils import dataclass_fields_asdict
20
21
 
21
- if TYPE_CHECKING:
22
- from .diaglayer import DiagLayer
23
-
24
22
 
25
23
  @dataclass
26
24
  class DataObjectProperty(DopBase):
@@ -86,6 +84,7 @@ class DataObjectProperty(DopBase):
86
84
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
87
85
  result = super()._build_odxlinks()
88
86
  result.update(self.diag_coded_type._build_odxlinks())
87
+ result.update(self.compu_method._build_odxlinks())
89
88
  return result
90
89
 
91
90
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -93,15 +92,17 @@ class DataObjectProperty(DopBase):
93
92
  super()._resolve_odxlinks(odxlinks)
94
93
 
95
94
  self.diag_coded_type._resolve_odxlinks(odxlinks)
95
+ self.compu_method._resolve_odxlinks(odxlinks)
96
96
 
97
97
  self._unit: Optional[Unit] = None
98
98
  if self.unit_ref:
99
99
  self._unit = odxlinks.resolve(self.unit_ref, Unit)
100
100
 
101
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
102
- super()._resolve_snrefs(diag_layer)
101
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
102
+ super()._resolve_snrefs(context)
103
103
 
104
- self.diag_coded_type._resolve_snrefs(diag_layer)
104
+ self.diag_coded_type._resolve_snrefs(context)
105
+ self.compu_method._resolve_snrefs(context)
105
106
 
106
107
  @property
107
108
  def unit(self) -> Optional[Unit]:
@@ -110,20 +111,7 @@ class DataObjectProperty(DopBase):
110
111
  def get_static_bit_length(self) -> Optional[int]:
111
112
  return self.diag_coded_type.get_static_bit_length()
112
113
 
113
- def convert_physical_to_internal(self, physical_value: Any) -> Any:
114
- """
115
- Convert a physical representation of a parameter to its internal counterpart
116
- """
117
- odxassert(
118
- self.physical_type.base_data_type.isinstance(physical_value),
119
- f"Expected {self.physical_type.base_data_type.value}, got {type(physical_value)}")
120
-
121
- return self.compu_method.convert_physical_to_internal(physical_value)
122
-
123
- def convert_physical_to_bytes(self,
124
- physical_value: Any,
125
- encode_state: EncodeState,
126
- bit_position: int = 0) -> bytes:
114
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
127
115
  """
128
116
  Convert a physical representation of a parameter to a string bytes that can be send over the wire
129
117
  """
@@ -132,9 +120,11 @@ class DataObjectProperty(DopBase):
132
120
  f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
133
121
  f" is not a valid.")
134
122
 
135
- internal_val = self.convert_physical_to_internal(physical_value)
136
- return self.diag_coded_type.convert_internal_to_bytes(
137
- internal_val, encode_state, bit_position=bit_position)
123
+ if not isinstance(physical_value, (int, float, str, bytes, bytearray)):
124
+ odxraise(f"Invalid type '{type(physical_value).__name__}' for physical value. "
125
+ f"(Expect atomic type!)")
126
+ internal_value = self.compu_method.convert_physical_to_internal(physical_value)
127
+ self.diag_coded_type.encode_into_pdu(internal_value, encode_state)
138
128
 
139
129
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
140
130
  """
odxtools/decodestate.py CHANGED
@@ -1,11 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass, field
3
- from typing import TYPE_CHECKING, Dict, cast
3
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, cast
4
4
 
5
5
  import odxtools.exceptions as exceptions
6
6
 
7
7
  from .exceptions import DecodeError
8
- from .odxtypes import AtomicOdxType, DataType
8
+ from .odxtypes import AtomicOdxType, DataType, ParameterValue
9
9
 
10
10
  try:
11
11
  import bitstruct.c as bitstruct
@@ -13,20 +13,9 @@ except ImportError:
13
13
  import bitstruct
14
14
 
15
15
  if TYPE_CHECKING:
16
+ from .parameters.parameter import Parameter
16
17
  from .tablerow import TableRow
17
18
 
18
- # format specifiers for the data type using the bitstruct module
19
- ODX_TYPE_TO_FORMAT_LETTER = {
20
- DataType.A_INT32: "s",
21
- DataType.A_UINT32: "u",
22
- DataType.A_FLOAT32: "f",
23
- DataType.A_FLOAT64: "f",
24
- DataType.A_BYTEFIELD: "r",
25
- DataType.A_UNICODE2STRING: "r", # UTF-16 strings must be converted explicitly
26
- DataType.A_ASCIISTRING: "r",
27
- DataType.A_UTF8STRING: "r",
28
- }
29
-
30
19
 
31
20
  @dataclass
32
21
  class DecodeState:
@@ -58,6 +47,11 @@ class DecodeState:
58
47
  #: values of the table key parameters decoded so far
59
48
  table_keys: Dict[str, "TableRow"] = field(default_factory=dict)
60
49
 
50
+ #: List of parameters that have been decoded so far. The journal
51
+ #: is used by some types of parameters which depend on the values of
52
+ #: other parameters; i.e., environment data description parameters
53
+ journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
54
+
61
55
  def extract_atomic_value(
62
56
  self,
63
57
  bit_length: int,
@@ -94,7 +88,7 @@ class DecodeState:
94
88
 
95
89
  padding = (8 - (bit_length + self.cursor_bit_position) % 8) % 8
96
90
  internal_value, = bitstruct.unpack_from(
97
- f"{ODX_TYPE_TO_FORMAT_LETTER[base_data_type]}{bit_length}",
91
+ f"{base_data_type.bitstruct_format_letter}{bit_length}",
98
92
  extracted_bytes,
99
93
  offset=padding)
100
94
 
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+ from xml.etree import ElementTree
4
+
5
+ from .exceptions import odxrequire
6
+ from .odxlink import OdxDocFragment
7
+
8
+
9
+ @dataclass
10
+ class Description:
11
+ text: str
12
+ external_docs: List[str]
13
+ text_identifier: Optional[str]
14
+
15
+ @staticmethod
16
+ def from_et(et_element: Optional[ElementTree.Element],
17
+ doc_frags: List[OdxDocFragment]) -> Optional["Description"]:
18
+ if et_element is None:
19
+ return None
20
+
21
+ # Extract the contents of the tag as a XHTML string.
22
+ raw_string = et_element.text or ""
23
+ for e in et_element:
24
+ if e.tag == "EXTERNAL-DOCS":
25
+ break
26
+ raw_string += ElementTree.tostring(e, encoding="unicode")
27
+
28
+ # remove white spaces at the beginning and at the end of all
29
+ # extracted lines
30
+ stripped_lines = [x.strip() for x in raw_string.split("\n")]
31
+
32
+ text = "\n".join(stripped_lines).strip()
33
+
34
+ text_identifier = et_element.get("TI")
35
+
36
+ external_docs = \
37
+ [
38
+ odxrequire(ed.get("HREF")) for ed in et_element.iterfind("EXTERNAL-DOCS/EXTERNAL-DOC")
39
+ ]
40
+ return Description(text=text, text_identifier=text_identifier, external_docs=external_docs)
41
+
42
+ @staticmethod
43
+ def from_string(text: str) -> "Description":
44
+ return Description(text=text, external_docs=[], text_identifier=None)
45
+
46
+ def __str__(self) -> str:
47
+ return self.text
@@ -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 DetermineNumberOfItems:
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
odxtools/diagcodedtype.py CHANGED
@@ -1,21 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import abc
3
2
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast
3
+ from typing import Any, Dict, List, Literal, Optional, Union, cast
4
+ from xml.etree import ElementTree
5
5
 
6
- from .decodestate import ODX_TYPE_TO_FORMAT_LETTER, DecodeState
6
+ from .decodestate import DecodeState
7
7
  from .encodestate import EncodeState
8
- from .exceptions import EncodeError, odxassert, odxraise
9
- from .odxlink import OdxLinkDatabase, OdxLinkId
10
- from .odxtypes import AtomicOdxType, DataType
11
-
12
- try:
13
- import bitstruct.c as bitstruct
14
- except ImportError:
15
- import bitstruct
16
-
17
- if TYPE_CHECKING:
18
- from .diaglayer import DiagLayer
8
+ from .exceptions import odxassert, odxraise, odxrequire
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
10
+ from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
11
+ from .snrefcontext import SnRefContext
19
12
 
20
13
  # Allowed diag-coded types
21
14
  DctType = Literal[
@@ -27,12 +20,30 @@ DctType = Literal[
27
20
 
28
21
 
29
22
  @dataclass
30
- class DiagCodedType(abc.ABC):
23
+ class DiagCodedType:
31
24
 
32
25
  base_data_type: DataType
33
26
  base_type_encoding: Optional[str]
34
27
  is_highlow_byte_order_raw: Optional[bool]
35
28
 
29
+ @staticmethod
30
+ def from_et(et_element: ElementTree.Element,
31
+ doc_frags: List[OdxDocFragment]) -> "DiagCodedType":
32
+ base_data_type_str = odxrequire(et_element.get("BASE-DATA-TYPE"))
33
+ try:
34
+ base_data_type = DataType(base_data_type_str)
35
+ except ValueError:
36
+ odxraise(f"Unknown base data type {base_data_type_str}")
37
+ base_data_type = cast(DataType, None)
38
+
39
+ base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
40
+ is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
41
+
42
+ return DiagCodedType(
43
+ base_data_type=base_data_type,
44
+ base_type_encoding=base_type_encoding,
45
+ is_highlow_byte_order_raw=is_highlow_byte_order_raw)
46
+
36
47
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: # noqa: B027
37
48
  return {}
38
49
 
@@ -40,7 +51,7 @@ class DiagCodedType(abc.ABC):
40
51
  """Recursively resolve any odxlinks references"""
41
52
  pass
42
53
 
43
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: # noqa: B027
54
+ def _resolve_snrefs(self, context: SnRefContext) -> None: # noqa: B027
44
55
  """Recursively resolve any short-name references"""
45
56
  pass
46
57
 
@@ -48,96 +59,15 @@ class DiagCodedType(abc.ABC):
48
59
  return None
49
60
 
50
61
  @property
51
- @abc.abstractmethod
52
62
  def dct_type(self) -> DctType:
53
- pass
63
+ odxraise(f"Class {type(self).__name__} does not override required method "
64
+ f"dct_type()", NotImplementedError)
65
+ return cast(DctType, None)
54
66
 
55
67
  @property
56
68
  def is_highlow_byte_order(self) -> bool:
57
69
  return self.is_highlow_byte_order_raw in [None, True]
58
70
 
59
- @staticmethod
60
- def _encode_internal_value(
61
- internal_value: AtomicOdxType,
62
- bit_position: int,
63
- bit_length: int,
64
- base_data_type: DataType,
65
- is_highlow_byte_order: bool,
66
- ) -> bytes:
67
- """Convert the internal_value to bytes."""
68
- # Check that bytes and strings actually fit into the bit length
69
- if base_data_type == DataType.A_BYTEFIELD:
70
- if isinstance(internal_value, bytearray):
71
- internal_value = bytes(internal_value)
72
- if not isinstance(internal_value, bytes):
73
- odxraise()
74
- if 8 * len(internal_value) > bit_length:
75
- raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
76
- f"({len(internal_value)} bytes)."
77
- f" The maximum length is {bit_length//8}.")
78
- if base_data_type == DataType.A_ASCIISTRING:
79
- if not isinstance(internal_value, str):
80
- odxraise()
81
-
82
- # The spec says ASCII, meaning only byte values 0-127.
83
- # But in practice, vendors use iso-8859-1, aka latin-1
84
- # reason being iso-8859-1 never fails since it has a valid
85
- # character mapping for every possible byte sequence.
86
- internal_value = internal_value.encode("iso-8859-1")
87
-
88
- if 8 * len(internal_value) > bit_length:
89
- raise EncodeError(f"The string {repr(internal_value)} is too large."
90
- f" The maximum number of characters is {bit_length//8}.")
91
- elif base_data_type == DataType.A_UTF8STRING:
92
- if not isinstance(internal_value, str):
93
- odxraise()
94
-
95
- internal_value = internal_value.encode("utf-8")
96
-
97
- if 8 * len(internal_value) > bit_length:
98
- raise EncodeError(f"The string {repr(internal_value)} is too large."
99
- f" The maximum number of bytes is {bit_length//8}.")
100
-
101
- elif base_data_type == DataType.A_UNICODE2STRING:
102
- if not isinstance(internal_value, str):
103
- odxraise()
104
-
105
- text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
106
- internal_value = internal_value.encode(text_encoding)
107
-
108
- if 8 * len(internal_value) > bit_length:
109
- raise EncodeError(f"The string {repr(internal_value)} is too large."
110
- f" The maximum number of characters is {bit_length//16}.")
111
-
112
- # If the bit length is zero, return empty bytes
113
- if bit_length == 0:
114
- if (base_data_type.value in [
115
- DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
116
- ] and base_data_type.value != 0):
117
- raise EncodeError(
118
- f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
119
- return b''
120
-
121
- char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
122
- padding = (8 - ((bit_length + bit_position) % 8)) % 8
123
- odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0),
124
- f"Incorrect padding {padding}")
125
- left_pad = f"p{padding}" if padding > 0 else ""
126
-
127
- # actually encode the value
128
- coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)
129
-
130
- # apply byte order for numeric objects
131
- if not is_highlow_byte_order and base_data_type in [
132
- DataType.A_INT32,
133
- DataType.A_UINT32,
134
- DataType.A_FLOAT32,
135
- DataType.A_FLOAT64,
136
- ]:
137
- coded = coded[::-1]
138
-
139
- return cast(bytes, coded)
140
-
141
71
  def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
142
72
  """Helper method to get the minimal byte length.
143
73
  (needed for LeadingLength- and MinMaxLengthType)
@@ -160,11 +90,10 @@ class DiagCodedType(abc.ABC):
160
90
  odxassert(
161
91
  byte_length % 2 == 0, f"The bit length of A_UNICODE2STRING must"
162
92
  f" be a multiple of 16 but is {8*byte_length}")
93
+
163
94
  return byte_length
164
95
 
165
- @abc.abstractmethod
166
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
167
- bit_position: int) -> bytes:
96
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
168
97
  """Encode the internal value.
169
98
 
170
99
  Parameters
@@ -177,9 +106,9 @@ class DiagCodedType(abc.ABC):
177
106
  mapping from ID (of the length key) to bit length
178
107
  (only needed for ParamLengthInfoType)
179
108
  """
180
- pass
109
+ raise NotImplementedError(
110
+ f".encode_into_pdu() is not implemented by the class {type(self).__name__}")
181
111
 
182
- @abc.abstractmethod
183
112
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
184
113
  """Decode the parameter value from the coded message.
185
114
 
@@ -195,4 +124,5 @@ class DiagCodedType(abc.ABC):
195
124
  int
196
125
  the next byte position after the extracted parameter
197
126
  """
198
- pass
127
+ raise NotImplementedError(
128
+ f".decode_from_pdu() is not implemented by the class {type(self).__name__}")
odxtools/diagcomm.py CHANGED
@@ -6,20 +6,20 @@ from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
8
8
  from .audience import Audience
9
- from .createsdgs import create_sdgs_from_et
10
9
  from .element import IdentifiableElement
11
10
  from .exceptions import odxraise, odxrequire
12
11
  from .functionalclass import FunctionalClass
13
12
  from .nameditemlist import NamedItemList
14
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
13
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
15
14
  from .odxtypes import odxstr_to_bool
15
+ from .snrefcontext import SnRefContext
16
16
  from .specialdatagroup import SpecialDataGroup
17
17
  from .state import State
18
18
  from .statetransition import StateTransition
19
19
  from .utils import dataclass_fields_asdict
20
20
 
21
21
  if TYPE_CHECKING:
22
- from .diaglayer import DiagLayer
22
+ from .diaglayers.protocol import Protocol
23
23
 
24
24
 
25
25
  class DiagClassType(Enum):
@@ -76,7 +76,9 @@ class DiagComm(IdentifiableElement):
76
76
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
77
77
 
78
78
  admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
79
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
79
+ sdgs = [
80
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
81
+ ]
80
82
 
81
83
  functional_class_refs = [
82
84
  odxrequire(OdxLinkRef.from_et(el, doc_frags))
@@ -141,7 +143,7 @@ class DiagComm(IdentifiableElement):
141
143
  return self._functional_classes
142
144
 
143
145
  @property
144
- def protocols(self) -> NamedItemList["DiagLayer"]:
146
+ def protocols(self) -> NamedItemList["Protocol"]:
145
147
  return self._protocols
146
148
 
147
149
  @property
@@ -201,15 +203,25 @@ class DiagComm(IdentifiableElement):
201
203
  self._state_transitions = NamedItemList(
202
204
  [odxlinks.resolve(stt_ref, StateTransition) for stt_ref in self.state_transition_refs])
203
205
 
204
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
206
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
205
207
  if self.admin_data:
206
- self.admin_data._resolve_snrefs(diag_layer)
208
+ self.admin_data._resolve_snrefs(context)
207
209
 
208
210
  if self.audience:
209
- self.audience._resolve_snrefs(diag_layer)
211
+ self.audience._resolve_snrefs(context)
210
212
 
211
213
  for sdg in self.sdgs:
212
- sdg._resolve_snrefs(diag_layer)
213
-
214
- self._protocols = NamedItemList(
215
- [diag_layer.protocols[prot_snref] for prot_snref in self.protocol_snrefs])
214
+ sdg._resolve_snrefs(context)
215
+
216
+ if TYPE_CHECKING:
217
+ diag_layer = odxrequire(context.diag_layer)
218
+ self._protocols = NamedItemList([
219
+ resolve_snref(prot_snref, getattr(diag_layer, "protocols", []), Protocol)
220
+ for prot_snref in self.protocol_snrefs
221
+ ])
222
+ else:
223
+ diag_layer = odxrequire(context.diag_layer)
224
+ self._protocols = NamedItemList([
225
+ resolve_snref(prot_snref, getattr(diag_layer, "protocols", []))
226
+ for prot_snref in self.protocol_snrefs
227
+ ])
@@ -1,15 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from itertools import chain
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from typing import Any, Dict, List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
8
8
  from .basicstructure import BasicStructure
9
- from .createsdgs import create_sdgs_from_et
10
9
  from .dataobjectproperty import DataObjectProperty
11
10
  from .dopbase import DopBase
12
11
  from .dtcdop import DtcDop
12
+ from .dynamicendmarkerfield import DynamicEndmarkerField
13
13
  from .dynamiclengthfield import DynamicLengthField
14
14
  from .endofpdufield import EndOfPduField
15
15
  from .environmentdata import EnvironmentData
@@ -18,15 +18,13 @@ from .exceptions import odxraise
18
18
  from .multiplexer import Multiplexer
19
19
  from .nameditemlist import NamedItemList
20
20
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
21
+ from .snrefcontext import SnRefContext
21
22
  from .specialdatagroup import SpecialDataGroup
22
23
  from .staticfield import StaticField
23
24
  from .structure import Structure
24
25
  from .table import Table
25
26
  from .unitspec import UnitSpec
26
27
 
27
- if TYPE_CHECKING:
28
- from .diaglayer import DiagLayer
29
-
30
28
 
31
29
  @dataclass
32
30
  class DiagDataDictionarySpec:
@@ -37,7 +35,7 @@ class DiagDataDictionarySpec:
37
35
  structures: NamedItemList[BasicStructure]
38
36
  static_fields: NamedItemList[StaticField]
39
37
  dynamic_length_fields: NamedItemList[DynamicLengthField]
40
- #dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
38
+ dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
41
39
  end_of_pdu_fields: NamedItemList[EndOfPduField]
42
40
  muxs: NamedItemList[Multiplexer]
43
41
  env_datas: NamedItemList[EnvironmentData]
@@ -54,7 +52,7 @@ class DiagDataDictionarySpec:
54
52
  self.structures,
55
53
  self.static_fields,
56
54
  self.dynamic_length_fields,
57
- #self.dynamic_endmarker_fields,
55
+ self.dynamic_endmarker_fields,
58
56
  self.end_of_pdu_fields,
59
57
  self.muxs,
60
58
  self.env_datas,
@@ -99,11 +97,10 @@ class DiagDataDictionarySpec:
99
97
  for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
100
98
  ]
101
99
 
102
- # TODO: dynamic endmarker fields
103
- #dynamic_endmarker_fields = [
104
- # DynamicEndmarkerField.from_et(dl_element, doc_frags)
105
- # for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
106
- #]
100
+ dynamic_endmarker_fields = [
101
+ DynamicEndmarkerField.from_et(dl_element, doc_frags) for dl_element in
102
+ et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
103
+ ]
107
104
 
108
105
  end_of_pdu_fields = [
109
106
  EndOfPduField.from_et(eofp_element, doc_frags)
@@ -135,7 +132,9 @@ class DiagDataDictionarySpec:
135
132
  for table_element in et_element.iterfind("TABLES/TABLE")
136
133
  ]
137
134
 
138
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
135
+ sdgs = [
136
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
137
+ ]
139
138
 
140
139
  return DiagDataDictionarySpec(
141
140
  admin_data=admin_data,
@@ -145,7 +144,7 @@ class DiagDataDictionarySpec:
145
144
  structures=NamedItemList(structures),
146
145
  static_fields=NamedItemList(static_fields),
147
146
  dynamic_length_fields=NamedItemList(dynamic_length_fields),
148
- #dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
147
+ dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
149
148
  end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
150
149
  muxs=NamedItemList(muxs),
151
150
  env_datas=NamedItemList(env_datas),
@@ -172,8 +171,8 @@ class DiagDataDictionarySpec:
172
171
  odxlinks.update(static_field._build_odxlinks())
173
172
  for dynamic_length_field in self.dynamic_length_fields:
174
173
  odxlinks.update(dynamic_length_field._build_odxlinks())
175
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
176
- # odxlinks.update(dynamic_endmarker_field._build_odxlinks())
174
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
175
+ odxlinks.update(dynamic_endmarker_field._build_odxlinks())
177
176
  for end_of_pdu_field in self.end_of_pdu_fields:
178
177
  odxlinks.update(end_of_pdu_field._build_odxlinks())
179
178
  for mux in self.muxs:
@@ -204,8 +203,8 @@ class DiagDataDictionarySpec:
204
203
  static_field._resolve_odxlinks(odxlinks)
205
204
  for dynamic_length_field in self.dynamic_length_fields:
206
205
  dynamic_length_field._resolve_odxlinks(odxlinks)
207
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
208
- # dynamic_endmarker_field._resolve_odxlinks(odxlinks)
206
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
207
+ dynamic_endmarker_field._resolve_odxlinks(odxlinks)
209
208
  for end_of_pdu_field in self.end_of_pdu_fields:
210
209
  end_of_pdu_field._resolve_odxlinks(odxlinks)
211
210
  for mux in self.muxs:
@@ -219,35 +218,35 @@ class DiagDataDictionarySpec:
219
218
  for sdg in self.sdgs:
220
219
  sdg._resolve_odxlinks(odxlinks)
221
220
 
222
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
221
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
223
222
  if self.admin_data is not None:
224
- self.admin_data._resolve_snrefs(diag_layer)
223
+ self.admin_data._resolve_snrefs(context)
225
224
  for dtc_dop in self.dtc_dops:
226
- dtc_dop._resolve_snrefs(diag_layer)
225
+ dtc_dop._resolve_snrefs(context)
227
226
  for env_data_desc in self.env_data_descs:
228
- env_data_desc._resolve_snrefs(diag_layer)
227
+ env_data_desc._resolve_snrefs(context)
229
228
  for data_object_prop in self.data_object_props:
230
- data_object_prop._resolve_snrefs(diag_layer)
229
+ data_object_prop._resolve_snrefs(context)
231
230
  for structure in self.structures:
232
- structure._resolve_snrefs(diag_layer)
231
+ structure._resolve_snrefs(context)
233
232
  for static_field in self.static_fields:
234
- static_field._resolve_snrefs(diag_layer)
233
+ static_field._resolve_snrefs(context)
235
234
  for dynamic_length_field in self.dynamic_length_fields:
236
- dynamic_length_field._resolve_snrefs(diag_layer)
237
- #for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
- # dynamic_endmarker_field._resolve_snrefs(diag_layer)
235
+ dynamic_length_field._resolve_snrefs(context)
236
+ for dynamic_endmarker_field in self.dynamic_endmarker_fields:
237
+ dynamic_endmarker_field._resolve_snrefs(context)
239
238
  for end_of_pdu_field in self.end_of_pdu_fields:
240
- end_of_pdu_field._resolve_snrefs(diag_layer)
239
+ end_of_pdu_field._resolve_snrefs(context)
241
240
  for mux in self.muxs:
242
- mux._resolve_snrefs(diag_layer)
241
+ mux._resolve_snrefs(context)
243
242
  for env_data in self.env_datas:
244
- env_data._resolve_snrefs(diag_layer)
243
+ env_data._resolve_snrefs(context)
245
244
  if self.unit_spec is not None:
246
- self.unit_spec._resolve_snrefs(diag_layer)
245
+ self.unit_spec._resolve_snrefs(context)
247
246
  for table in self.tables:
248
- table._resolve_snrefs(diag_layer)
247
+ table._resolve_snrefs(context)
249
248
  for sdg in self.sdgs:
250
- sdg._resolve_snrefs(diag_layer)
249
+ sdg._resolve_snrefs(context)
251
250
 
252
251
  @property
253
252
  def all_data_object_properties(self) -> NamedItemList[DopBase]: