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,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, Literal, 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 odxassert, odxraise
9
- from .odxtypes import AtomicOdxType, DataType
11
+ from .exceptions import odxassert, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment
13
+ from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
14
+ from .utils import dataclass_fields_asdict
10
15
 
11
16
 
12
17
  @dataclass
@@ -16,10 +21,36 @@ class StandardLengthType(DiagCodedType):
16
21
  bit_mask: Optional[int]
17
22
  is_condensed_raw: Optional[bool]
18
23
 
24
+ @staticmethod
25
+ @override
26
+ def from_et(et_element: ElementTree.Element,
27
+ doc_frags: List[OdxDocFragment]) -> "StandardLengthType":
28
+ kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
29
+
30
+ bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
31
+ bit_mask = None
32
+ if (bit_mask_str := et_element.findtext("BIT-MASK")) is not None:
33
+ # The XSD uses the type xsd:hexBinary
34
+ # xsd:hexBinary allows for leading/trailing whitespace, empty strings, and it only allows an even
35
+ # number of hex digits, while some of the examples shown in the ODX specification exhibit an
36
+ # odd number of hex digits.
37
+ # This causes a validation paradox, so we try to be flexible
38
+ bit_mask_str = bit_mask_str.strip()
39
+ if len(bit_mask_str):
40
+ bit_mask = int(bit_mask_str, 16)
41
+ is_condensed_raw = odxstr_to_bool(et_element.get("IS-CONDENSED"))
42
+
43
+ return StandardLengthType(
44
+ bit_length=bit_length, bit_mask=bit_mask, is_condensed_raw=is_condensed_raw, **kwargs)
45
+
19
46
  @property
20
47
  def dct_type(self) -> DctType:
21
48
  return "STANDARD-LENGTH-TYPE"
22
49
 
50
+ @property
51
+ def is_condensed(self) -> bool:
52
+ return self.is_condensed_raw is True
53
+
23
54
  def __post_init__(self) -> None:
24
55
  if self.bit_mask is not None:
25
56
  maskable_types = (DataType.A_UINT32, DataType.A_INT32, DataType.A_BYTEFIELD)
@@ -28,11 +59,46 @@ class StandardLengthType(DiagCodedType):
28
59
  'Can not apply a bit_mask on a value of type {self.base_data_type}',
29
60
  )
30
61
 
62
+ def __get_raw_mask(self, internal_value: AtomicOdxType) -> Optional[bytes]:
63
+ """Returns a byte field where all bits that are used by the
64
+ DiagCoded type are set and all unused ones are not set.
65
+
66
+ If `None` is returned, all bits are used.
67
+ """
68
+ if self.bit_mask is None:
69
+ return None
70
+
71
+ if self.is_condensed:
72
+ odxraise("Condensed bit masks are not yet supported", NotImplementedError)
73
+ return
74
+
75
+ endianness: Literal["little", "big"] = "big"
76
+ if not self.is_highlow_byte_order and self.base_data_type in [
77
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
78
+ ]:
79
+ # TODO (?): Technically, little endian A_UNICODE2STRING
80
+ # objects require a byte swap for each 16 bit letter, and
81
+ # thus also for the mask. I somehow doubt that this has
82
+ # been anticipated by the standard, though...
83
+ endianness = "little"
84
+
85
+ sz: int
86
+ if isinstance(internal_value, (bytes, bytearray)):
87
+ sz = len(internal_value)
88
+ else:
89
+ sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
90
+
91
+ max_value = (1 << (sz * 8)) - 1
92
+ bit_mask = self.bit_mask & max_value
93
+
94
+ return bit_mask.to_bytes(sz, endianness)
95
+
31
96
  def __apply_mask(self, internal_value: AtomicOdxType) -> AtomicOdxType:
32
97
  if self.bit_mask is None:
33
98
  return internal_value
34
- if self.is_condensed_raw is True:
35
- raise NotImplementedError("Serialization of condensed bit mask is not supported")
99
+ if self.is_condensed:
100
+ odxraise("Serialization of condensed bit mask is not supported", NotImplementedError)
101
+ return
36
102
  if isinstance(internal_value, int):
37
103
  return internal_value & self.bit_mask
38
104
  if isinstance(internal_value, bytes):
@@ -46,16 +112,16 @@ class StandardLengthType(DiagCodedType):
46
112
  def get_static_bit_length(self) -> Optional[int]:
47
113
  return self.bit_length
48
114
 
49
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
50
- bit_position: int) -> bytes:
51
- return self._encode_internal_value(
52
- self.__apply_mask(internal_value),
53
- bit_position,
54
- self.bit_length,
55
- self.base_data_type,
56
- is_highlow_byte_order=self.is_highlow_byte_order,
57
- )
115
+ @override
116
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
117
+ encode_state.emplace_atomic_value(
118
+ internal_value=self.__apply_mask(internal_value),
119
+ used_mask=self.__get_raw_mask(internal_value),
120
+ bit_length=self.bit_length,
121
+ base_data_type=self.base_data_type,
122
+ is_highlow_byte_order=self.is_highlow_byte_order)
58
123
 
124
+ @override
59
125
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
60
126
  internal_value = decode_state.extract_atomic_value(
61
127
  self.bit_length,
odxtools/state.py CHANGED
@@ -1,15 +1,13 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
7
7
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
8
+ from .snrefcontext import SnRefContext
8
9
  from .utils import dataclass_fields_asdict
9
10
 
10
- if TYPE_CHECKING:
11
- from .diaglayer import DiagLayer
12
-
13
11
 
14
12
  @dataclass
15
13
  class State(IdentifiableElement):
@@ -29,5 +27,5 @@ class State(IdentifiableElement):
29
27
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
28
  pass
31
29
 
32
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
30
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
31
  pass
odxtools/statechart.py CHANGED
@@ -1,19 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
7
7
  from .exceptions import odxrequire
8
8
  from .nameditemlist import NamedItemList
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
10
+ from .snrefcontext import SnRefContext
10
11
  from .state import State
11
12
  from .statetransition import StateTransition
12
13
  from .utils import dataclass_fields_asdict
13
14
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
15
 
18
16
  @dataclass
19
17
  class StateChart(IdentifiableElement):
@@ -68,25 +66,21 @@ class StateChart(IdentifiableElement):
68
66
  for st in self.states:
69
67
  st._resolve_odxlinks(odxlinks)
70
68
 
71
- # For now, we assume that the start state short name ref
72
- # points to a state local to the state chart. TODO: The XML
69
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
70
+ context.state_chart = self
71
+
72
+ # For now, we assume that the start state short name reference
73
+ # points to a local state of the state chart. TODO: The XSD
73
74
  # allows to define state charts without any states, yet the
74
75
  # start state SNREF is mandatory. Is this a gap in the spec or
75
76
  # does it allow "foreign" start states? If the latter, what
76
77
  # does that mean?
77
- self._start_state: State
78
- for st in self.states:
79
- if st.short_name == self.start_state_snref:
80
- self._start_state = st
81
- break
78
+ self._start_state = resolve_snref(self.start_state_snref, self.states, State)
82
79
 
83
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
84
80
  for st in self.states:
85
- st._resolve_snrefs(diag_layer)
81
+ st._resolve_snrefs(context)
86
82
 
87
83
  for strans in self.state_transitions:
88
- # note that the signature of the state transition's
89
- # _resolve_snrefs() method is non-standard as the
90
- # namespace of these SNREFs is the state chart, not the
91
- # whole diag layer...
92
- strans._resolve_snrefs(diag_layer, states=self.states)
84
+ strans._resolve_snrefs(context)
85
+
86
+ context.state_chart = None
@@ -1,17 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, Iterable, List
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
7
7
  from .exceptions import odxrequire
8
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
8
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
9
+ from .snrefcontext import SnRefContext
9
10
  from .state import State
10
11
  from .utils import dataclass_fields_asdict
11
12
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
13
 
16
14
  @dataclass
17
15
  class StateTransition(IdentifiableElement):
@@ -20,7 +18,7 @@ class StateTransition(IdentifiableElement):
20
18
  """
21
19
  source_snref: str
22
20
  target_snref: str
23
- #external_access_method: Optional[ExternalAccessMethod] # TODO
21
+ # external_access_method: Optional[ExternalAccessMethod] # TODO
24
22
 
25
23
  @property
26
24
  def source_state(self) -> State:
@@ -50,15 +48,7 @@ class StateTransition(IdentifiableElement):
50
48
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
51
49
  pass
52
50
 
53
- # note that the signature of this method is non-standard because
54
- # the namespace of these SNREFs is the corresponding state
55
- # chart. To mitigate this a bit, the non-standard parameters are
56
- # keyword-only...
57
- def _resolve_snrefs(self, diag_layer: "DiagLayer", *, states: Iterable[State]) -> None:
58
- self._source_state: State
59
- self._target_state: State
60
- for st in states:
61
- if st.short_name == self.source_snref:
62
- self._source_state = st
63
- if st.short_name == self.target_snref:
64
- self._target_state = st
51
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
52
+ states = odxrequire(context.state_chart).states
53
+ self._source_state = resolve_snref(self.source_snref, states, State)
54
+ self._target_state = resolve_snref(self.target_snref, states, State)
@@ -0,0 +1,107 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Sequence
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
7
+
8
+ from .decodestate import DecodeState
9
+ from .encodestate import EncodeState
10
+ from .exceptions import odxassert, odxraise, odxrequire
11
+ from .field import Field
12
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
13
+ from .odxtypes import ParameterValue
14
+ from .snrefcontext import SnRefContext
15
+ from .utils import dataclass_fields_asdict
16
+
17
+
18
+ @dataclass
19
+ class StaticField(Field):
20
+ """Array of a fixed number of structure objects"""
21
+ fixed_number_of_items: int
22
+ item_byte_size: int
23
+
24
+ @staticmethod
25
+ @override
26
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "StaticField":
27
+ kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
28
+
29
+ fixed_number_of_items = int(odxrequire(et_element.findtext('FIXED-NUMBER-OF-ITEMS')))
30
+ item_byte_size = int(odxrequire(et_element.findtext('ITEM-BYTE-SIZE')))
31
+
32
+ return StaticField(
33
+ fixed_number_of_items=fixed_number_of_items, item_byte_size=item_byte_size, **kwargs)
34
+
35
+ @override
36
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
37
+ odxlinks = super()._build_odxlinks()
38
+ return odxlinks
39
+
40
+ @override
41
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
42
+ super()._resolve_odxlinks(odxlinks)
43
+
44
+ @override
45
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
46
+ super()._resolve_snrefs(context)
47
+
48
+ @override
49
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
50
+
51
+ if not isinstance(physical_value,
52
+ Sequence) or len(physical_value) != self.fixed_number_of_items:
53
+ odxraise(f"Value for static field '{self.short_name}' "
54
+ f"must be a list of size {self.fixed_number_of_items}")
55
+
56
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
57
+ encode_state.is_end_of_pdu = False
58
+ for i, val in enumerate(physical_value):
59
+ if not isinstance(val, dict):
60
+ odxraise(f"The individual parameter values for static field '{self.short_name}' "
61
+ f"must be dictionaries for structure '{self.structure.short_name}'")
62
+
63
+ if i == len(physical_value) - 1:
64
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
65
+
66
+ pos_before = encode_state.cursor_byte_position
67
+ self.structure.encode_into_pdu(val, encode_state)
68
+ pos_after = encode_state.cursor_byte_position
69
+
70
+ if pos_after - pos_before > self.item_byte_size:
71
+ odxraise(
72
+ f"Insufficient item byte size for static field {self.short_name}: "
73
+ f"Is {self.item_byte_size} bytes, but need at least {pos_after - pos_before} bytes"
74
+ )
75
+ encode_state.cursor_byte_position = pos_before + self.item_byte_size
76
+ elif pos_after - pos_before < self.item_byte_size:
77
+ # add some padding bytes
78
+ encode_state.emplace_bytes(b'\x00' * (self.item_byte_size -
79
+ (pos_after - pos_before)))
80
+
81
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
82
+
83
+ @override
84
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
85
+
86
+ odxassert(decode_state.cursor_bit_position == 0,
87
+ "No bit position can be specified for static length fields!")
88
+
89
+ orig_origin = decode_state.origin_byte_position
90
+ decode_state.origin_byte_position = decode_state.cursor_byte_position
91
+
92
+ result: List[ParameterValue] = []
93
+ for _ in range(self.fixed_number_of_items):
94
+ orig_cursor = decode_state.cursor_byte_position
95
+
96
+ if decode_state.cursor_byte_position - orig_cursor > self.item_byte_size:
97
+ odxraise(f"Insufficient item byte size for static field {self.short_name}: "
98
+ f"Is {self.item_byte_size} bytes, but need at least "
99
+ f"{decode_state.cursor_byte_position - orig_cursor} bytes")
100
+
101
+ result.append(self.structure.decode_from_pdu(decode_state))
102
+
103
+ decode_state.cursor_byte_position = orig_cursor + self.item_byte_size
104
+
105
+ decode_state.origin_byte_position = orig_origin
106
+
107
+ return result
@@ -0,0 +1,288 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .diagnostictroublecode import DiagnosticTroubleCode
7
+ from .diagservice import DiagService
8
+ from .dtcdop import DtcDop
9
+ from .element import IdentifiableElement, NamedElement
10
+ from .environmentdata import EnvironmentData
11
+ from .environmentdatadescription import EnvironmentDataDescription
12
+ from .exceptions import odxraise, odxrequire
13
+ from .matchingparameter import MatchingParameter
14
+ from .nameditemlist import NamedItemList
15
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
16
+ from .parameters.parameter import Parameter
17
+ from .snrefcontext import SnRefContext
18
+ from .table import Table
19
+ from .tablerow import TableRow
20
+ from .utils import dataclass_fields_asdict
21
+
22
+
23
+ @dataclass
24
+ class SubComponentPattern:
25
+ matching_parameters: List[MatchingParameter]
26
+
27
+ @staticmethod
28
+ def from_et(et_element: ElementTree.Element,
29
+ doc_frags: List[OdxDocFragment]) -> "SubComponentPattern":
30
+
31
+ matching_parameters = [
32
+ MatchingParameter.from_et(el, doc_frags)
33
+ for el in et_element.iterfind("MATCHING-PARAMETERS/MATCHING-PARAMETER")
34
+ ]
35
+
36
+ return SubComponentPattern(matching_parameters=matching_parameters)
37
+
38
+
39
+ @dataclass
40
+ class SubComponentParamConnector(IdentifiableElement):
41
+ diag_comm_snref: str
42
+
43
+ # TODO: we currently only support SNREFs, not SNPATHREFs
44
+ out_param_if_refs: List[str]
45
+ in_param_if_refs: List[str]
46
+
47
+ @property
48
+ def service(self) -> DiagService:
49
+ return self._service
50
+
51
+ @property
52
+ def out_param_ifs(self) -> NamedItemList[Parameter]:
53
+ return self._out_param_ifs
54
+
55
+ @property
56
+ def in_param_ifs(self) -> NamedItemList[Parameter]:
57
+ return self._in_param_ifs
58
+
59
+ @staticmethod
60
+ def from_et(et_element: ElementTree.Element,
61
+ doc_frags: List[OdxDocFragment]) -> "SubComponentParamConnector":
62
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
63
+
64
+ diag_comm_snref = odxrequire(
65
+ odxrequire(et_element.find("DIAG-COMM-SNREF")).get("SHORT-NAME"))
66
+
67
+ out_param_if_refs = []
68
+ for elem in et_element.find("OUT-PARAM-IF-REFS") or []:
69
+ if elem.tag != "OUT-PARAM-IF-SNREF":
70
+ odxraise("Currently, only SNREFS are supported for OUT-PARAM-IF-REFS")
71
+ continue
72
+
73
+ out_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
74
+
75
+ in_param_if_refs = []
76
+ for elem in et_element.find("IN-PARAM-IF-REFS") or []:
77
+ if elem.tag != "IN-PARAM-IF-SNREF":
78
+ odxraise("Currently, only SNREFS are supported for IN-PARAM-IF-REFS")
79
+ continue
80
+
81
+ in_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
82
+
83
+ return SubComponentParamConnector(
84
+ diag_comm_snref=diag_comm_snref,
85
+ out_param_if_refs=out_param_if_refs,
86
+ in_param_if_refs=in_param_if_refs,
87
+ **kwargs)
88
+
89
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
90
+ return {}
91
+
92
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
93
+ pass
94
+
95
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
96
+ service = resolve_snref(self.diag_comm_snref,
97
+ odxrequire(context.diag_layer).diag_comms, DiagService)
98
+ self._service = service
99
+
100
+ if self._service.request is not None:
101
+ odxraise()
102
+ return
103
+ if not self._service.positive_responses:
104
+ odxraise()
105
+ return
106
+ request = odxrequire(service.request)
107
+ response = service.positive_responses[0]
108
+
109
+ in_param_ifs = []
110
+ for x in self.in_param_if_refs:
111
+ in_param_ifs.append(resolve_snref(x, request.parameters, Parameter))
112
+
113
+ out_param_ifs = []
114
+ for x in self.out_param_if_refs:
115
+ out_param_ifs.append(resolve_snref(x, response.parameters, Parameter))
116
+
117
+ self._in_param_ifs = NamedItemList(in_param_ifs)
118
+ self._out_param_ifs = NamedItemList(out_param_ifs)
119
+
120
+
121
+ @dataclass
122
+ class TableRowConnector(NamedElement):
123
+ table_ref: OdxLinkRef
124
+ table_row_snref: str
125
+
126
+ @property
127
+ def table(self) -> Table:
128
+ return self._table
129
+
130
+ @property
131
+ def table_row(self) -> TableRow:
132
+ return self._table_row
133
+
134
+ @staticmethod
135
+ def from_et(et_element: ElementTree.Element,
136
+ doc_frags: List[OdxDocFragment]) -> "TableRowConnector":
137
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
138
+
139
+ table_ref = odxrequire(OdxLinkRef.from_et(et_element.find("TABLE-REF"), doc_frags))
140
+ table_row_snref_el = odxrequire(et_element.find("TABLE-ROW-SNREF"))
141
+ table_row_snref = odxrequire(table_row_snref_el.get("SHORT-NAME"))
142
+
143
+ return TableRowConnector(table_ref=table_ref, table_row_snref=table_row_snref, **kwargs)
144
+
145
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
146
+ return {}
147
+
148
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
149
+ self._table = odxlinks.resolve(self.table_ref, Table)
150
+
151
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
152
+ self._table_row = resolve_snref(self.table_row_snref, self._table.table_rows, TableRow)
153
+
154
+
155
+ @dataclass
156
+ class DtcConnector(NamedElement):
157
+ dtc_dop_ref: OdxLinkRef
158
+ dtc_snref: str
159
+
160
+ @property
161
+ def dtc_dop(self) -> DtcDop:
162
+ return self._dtc_dop
163
+
164
+ @property
165
+ def dtc(self) -> DiagnosticTroubleCode:
166
+ return self._dtc
167
+
168
+ @staticmethod
169
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DtcConnector":
170
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
171
+
172
+ dtc_dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DTC-DOP-REF"), doc_frags))
173
+ dtc_snref_el = odxrequire(et_element.find("DTC-SNREF"))
174
+ dtc_snref = odxrequire(dtc_snref_el.get("SHORT-NAME"))
175
+
176
+ return DtcConnector(dtc_dop_ref=dtc_dop_ref, dtc_snref=dtc_snref, **kwargs)
177
+
178
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
179
+ return {}
180
+
181
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
182
+ self._dtc_dop = odxlinks.resolve(self.dtc_dop_ref, DtcDop)
183
+
184
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
185
+ self._dtc = resolve_snref(self.dtc_snref, self._dtc_dop.dtcs, DiagnosticTroubleCode)
186
+
187
+
188
+ @dataclass
189
+ class EnvDataConnector(NamedElement):
190
+ env_data_desc_ref: OdxLinkRef
191
+ env_data_snref: str
192
+
193
+ @property
194
+ def env_data_desc(self) -> EnvironmentDataDescription:
195
+ return self._env_data_desc
196
+
197
+ @property
198
+ def env_data(self) -> EnvironmentData:
199
+ return self._env_data
200
+
201
+ @staticmethod
202
+ def from_et(et_element: ElementTree.Element,
203
+ doc_frags: List[OdxDocFragment]) -> "EnvDataConnector":
204
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
205
+
206
+ env_data_desc_ref = odxrequire(
207
+ OdxLinkRef.from_et(et_element.find("ENV-DATA-DESC-REF"), doc_frags))
208
+ env_data_snref_el = odxrequire(et_element.find("ENV-DATA-SNREF"))
209
+ env_data_snref = odxrequire(env_data_snref_el.get("SHORT-NAME"))
210
+
211
+ return EnvDataConnector(
212
+ env_data_desc_ref=env_data_desc_ref, env_data_snref=env_data_snref, **kwargs)
213
+
214
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
215
+ return {}
216
+
217
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
218
+ self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref, EnvironmentDataDescription)
219
+
220
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
221
+ self._env_data = resolve_snref(self.env_data_snref, self._env_data_desc.env_datas,
222
+ EnvironmentData)
223
+
224
+
225
+ @dataclass
226
+ class SubComponent(IdentifiableElement):
227
+ """Sub-components describe collections of related diagnostic variables
228
+
229
+ Note that the communication paradigm via diagnostic variables is
230
+ somewhat uncommon. If your ECU does not define any, there's no
231
+ need for it to define sub-components.
232
+
233
+ """
234
+
235
+ #sub_component_patterns: NamedItemList[SubComponentPattern]
236
+ sub_component_param_connectors: NamedItemList[SubComponentParamConnector]
237
+ table_row_connectors: NamedItemList[TableRowConnector]
238
+ env_data_connectors: NamedItemList[EnvDataConnector]
239
+ dtc_connectors: NamedItemList[DtcConnector]
240
+
241
+ semantic: Optional[str]
242
+
243
+ @staticmethod
244
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "SubComponent":
245
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
246
+
247
+ semantic = et_element.get("SEMANTIC")
248
+
249
+ sub_component_param_connectors = [
250
+ SubComponentParamConnector.from_et(el, doc_frags) for el in et_element.iterfind(
251
+ "SUB-COMPONENT-PARAM-CONNECTORS/SUB-COMPONENT-PARAM-CONNECTOR")
252
+ ]
253
+ table_row_connectors = [
254
+ TableRowConnector.from_et(el, doc_frags)
255
+ for el in et_element.iterfind("TABLE-ROW-CONNECTORS/TABLE-ROW-CONNECTOR")
256
+ ]
257
+ env_data_connectors = [
258
+ EnvDataConnector.from_et(el, doc_frags)
259
+ for el in et_element.iterfind("ENV-DATA-CONNECTORS/ENV-DATA-CONNECTOR")
260
+ ]
261
+ dtc_connectors = [
262
+ DtcConnector.from_et(el, doc_frags)
263
+ for el in et_element.iterfind("DTC-CONNECTORS/DTC-CONNECTOR")
264
+ ]
265
+
266
+ return SubComponent(
267
+ semantic=semantic,
268
+ sub_component_param_connectors=NamedItemList(sub_component_param_connectors),
269
+ table_row_connectors=NamedItemList(table_row_connectors),
270
+ env_data_connectors=NamedItemList(env_data_connectors),
271
+ dtc_connectors=NamedItemList(dtc_connectors),
272
+ **kwargs)
273
+
274
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
275
+ result = {}
276
+
277
+ for dtc_conn in self.dtc_connectors:
278
+ result.update(dtc_conn._build_odxlinks())
279
+
280
+ return result
281
+
282
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
283
+ for dtc_conn in self.dtc_connectors:
284
+ dtc_conn._resolve_odxlinks(odxlinks)
285
+
286
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
287
+ for dtc_conn in self.dtc_connectors:
288
+ dtc_conn._resolve_snrefs(context)
odxtools/swvariable.py ADDED
@@ -0,0 +1,21 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import NamedElement
7
+ from .odxlink import OdxDocFragment
8
+ from .utils import dataclass_fields_asdict
9
+
10
+
11
+ @dataclass
12
+ class SwVariable(NamedElement):
13
+ origin: Optional[str]
14
+
15
+ @staticmethod
16
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "SwVariable":
17
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
18
+
19
+ origin = et_element.findtext("ORIGIN")
20
+
21
+ return SwVariable(origin=origin, **kwargs)