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
@@ -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):
@@ -59,20 +57,20 @@ class DataObjectProperty(DopBase):
59
57
  compu_method = create_any_compu_method_from_et(
60
58
  odxrequire(et_element.find("COMPU-METHOD")),
61
59
  doc_frags,
62
- diag_coded_type.base_data_type,
63
- physical_type.base_data_type,
60
+ internal_type=diag_coded_type.base_data_type,
61
+ physical_type=physical_type.base_data_type,
64
62
  )
65
63
  unit_ref = OdxLinkRef.from_et(et_element.find("UNIT-REF"), doc_frags)
66
64
 
67
65
  internal_constr = None
68
66
  if (internal_constr_elem := et_element.find("INTERNAL-CONSTR")) is not None:
69
- internal_constr = InternalConstr.from_et(
70
- internal_constr_elem, internal_type=diag_coded_type.base_data_type)
67
+ internal_constr = InternalConstr.constr_from_et(
68
+ internal_constr_elem, doc_frags, value_type=diag_coded_type.base_data_type)
71
69
 
72
70
  physical_constr = None
73
71
  if (physical_constr_elem := et_element.find("PHYS-CONSTR")) is not None:
74
- physical_constr = InternalConstr.from_et(
75
- physical_constr_elem, internal_type=physical_type.base_data_type)
72
+ physical_constr = InternalConstr.constr_from_et(
73
+ physical_constr_elem, doc_frags, value_type=physical_type.base_data_type)
76
74
 
77
75
  return DataObjectProperty(
78
76
  diag_coded_type=diag_coded_type,
@@ -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,28 +111,20 @@ 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, physical_value: Any, encode_state: EncodeState,
124
- bit_position: int) -> bytes:
114
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
125
115
  """
126
116
  Convert a physical representation of a parameter to a string bytes that can be send over the wire
127
117
  """
128
118
  if not self.is_valid_physical_value(physical_value):
129
- raise EncodeError(f"The value {repr(physical_value)} of type {type(physical_value)}"
130
- f" is not a valid.")
131
-
132
- internal_val = self.convert_physical_to_internal(physical_value)
133
- return self.diag_coded_type.convert_internal_to_bytes(
134
- internal_val, encode_state, bit_position=bit_position)
119
+ raise EncodeError(
120
+ f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
121
+ f" is not a valid.")
122
+
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)
135
128
 
136
129
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
137
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
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,
@@ -72,7 +66,7 @@ class DecodeState:
72
66
  """
73
67
  # If the bit length is zero, return "empty" values of each type
74
68
  if bit_length == 0:
75
- return base_data_type.as_python_type()()
69
+ return base_data_type.python_type()
76
70
 
77
71
  byte_length = (bit_length + self.cursor_bit_position + 7) // 8
78
72
  if self.cursor_byte_position + byte_length > len(self.coded_message):
@@ -94,12 +88,13 @@ 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
 
101
95
  text_errors = 'strict' if exceptions.strict_mode else 'replace'
102
96
  if base_data_type == DataType.A_ASCIISTRING:
97
+ assert isinstance(internal_value, (bytes, bytearray))
103
98
  # The spec says ASCII, meaning only byte values 0-127.
104
99
  # But in practice, vendors use iso-8859-1, aka latin-1
105
100
  # reason being iso-8859-1 never fails since it has a valid
@@ -107,9 +102,11 @@ class DecodeState:
107
102
  text_encoding = 'iso-8859-1'
108
103
  internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
109
104
  elif base_data_type == DataType.A_UTF8STRING:
105
+ assert isinstance(internal_value, (bytes, bytearray))
110
106
  text_encoding = "utf-8"
111
107
  internal_value = internal_value.decode(encoding=text_encoding, errors=text_errors)
112
108
  elif base_data_type == DataType.A_UNICODE2STRING:
109
+ assert isinstance(internal_value, (bytes, bytearray))
113
110
  # For UTF-16, we need to manually decode the extracted
114
111
  # bytes to a string
115
112
  text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
@@ -118,4 +115,4 @@ class DecodeState:
118
115
  self.cursor_byte_position += byte_length
119
116
  self.cursor_bit_position = 0
120
117
 
121
- return internal_value
118
+ return cast(AtomicOdxType, internal_value)
@@ -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
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,100 +59,20 @@ 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 in [
115
- DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
116
- ] and base_data_type != 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 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)
144
74
  """
75
+ byte_length: int = -1
145
76
  # A_BYTEFIELD, A_ASCIISTRING, A_UNICODE2STRING, A_UTF8STRING
146
77
  if self.base_data_type == DataType.A_BYTEFIELD:
147
78
  byte_length = len(internal_value)
@@ -159,11 +90,10 @@ class DiagCodedType(abc.ABC):
159
90
  odxassert(
160
91
  byte_length % 2 == 0, f"The bit length of A_UNICODE2STRING must"
161
92
  f" be a multiple of 16 but is {8*byte_length}")
93
+
162
94
  return byte_length
163
95
 
164
- @abc.abstractmethod
165
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
166
- bit_position: int) -> bytes:
96
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
167
97
  """Encode the internal value.
168
98
 
169
99
  Parameters
@@ -176,9 +106,9 @@ class DiagCodedType(abc.ABC):
176
106
  mapping from ID (of the length key) to bit length
177
107
  (only needed for ParamLengthInfoType)
178
108
  """
179
- pass
109
+ raise NotImplementedError(
110
+ f".encode_into_pdu() is not implemented by the class {type(self).__name__}")
180
111
 
181
- @abc.abstractmethod
182
112
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
183
113
  """Decode the parameter value from the coded message.
184
114
 
@@ -194,4 +124,5 @@ class DiagCodedType(abc.ABC):
194
124
  int
195
125
  the next byte position after the extracted parameter
196
126
  """
197
- 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
+ ])