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
odxtools/dtcdop.py CHANGED
@@ -1,9 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
- # from dataclasses import dataclass, field
3
2
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
3
+ from typing import Any, Dict, List, Optional, Union, cast
5
4
  from xml.etree import ElementTree
6
5
 
6
+ from typing_extensions import override
7
+
7
8
  from .compumethods.compumethod import CompuMethod
8
9
  from .compumethods.createanycompumethod import create_any_compu_method_from_et
9
10
  from .createanydiagcodedtype import create_any_diag_coded_type_from_et
@@ -12,15 +13,59 @@ from .diagcodedtype import DiagCodedType
12
13
  from .diagnostictroublecode import DiagnosticTroubleCode
13
14
  from .dopbase import DopBase
14
15
  from .encodestate import EncodeState
15
- from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
16
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
16
17
  from .nameditemlist import NamedItemList
17
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
18
19
  from .odxtypes import ParameterValue, odxstr_to_bool
19
20
  from .physicaltype import PhysicalType
21
+ from .snrefcontext import SnRefContext
20
22
  from .utils import dataclass_fields_asdict
21
23
 
22
- if TYPE_CHECKING:
23
- from .diaglayer import DiagLayer
24
+
25
+ @dataclass
26
+ class LinkedDtcDop:
27
+ not_inherited_dtc_snrefs: List[str]
28
+ dtc_dop_ref: OdxLinkRef
29
+
30
+ @property
31
+ def dtc_dop(self) -> "DtcDop":
32
+ return self._dtc_dop
33
+
34
+ @property
35
+ def short_name(self) -> str:
36
+ return self._dtc_dop.short_name
37
+
38
+ @property
39
+ def not_inherited_dtcs(self) -> NamedItemList[DiagnosticTroubleCode]:
40
+ return self._not_inherited_dtcs
41
+
42
+ @staticmethod
43
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "LinkedDtcDop":
44
+ not_inherited_dtc_snrefs = [
45
+ odxrequire(el.get("SHORT-NAME"))
46
+ for el in et_element.iterfind("NOT-INHERITED-DTC-SNREFS/"
47
+ "NOT-INHERITED-DTC-SNREF")
48
+ ]
49
+
50
+ dtc_dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DTC-DOP-REF"), doc_frags))
51
+
52
+ return LinkedDtcDop(
53
+ not_inherited_dtc_snrefs=not_inherited_dtc_snrefs, dtc_dop_ref=dtc_dop_ref)
54
+
55
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
56
+ return {}
57
+
58
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
59
+ self._dtc_dop = odxlinks.resolve(self.dtc_dop_ref, DtcDop)
60
+
61
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
62
+ dtc_dop = self._dtc_dop
63
+ not_inherited_dtcs = [
64
+ resolve_snref(ni_snref, dtc_dop.dtcs, DiagnosticTroubleCode)
65
+ for ni_snref in self.not_inherited_dtc_snrefs
66
+ ]
67
+
68
+ self._not_inherited_dtcs = NamedItemList(not_inherited_dtcs)
24
69
 
25
70
 
26
71
  @dataclass
@@ -31,9 +76,12 @@ class DtcDop(DopBase):
31
76
  physical_type: PhysicalType
32
77
  compu_method: CompuMethod
33
78
  dtcs_raw: List[Union[DiagnosticTroubleCode, OdxLinkRef]]
34
- linked_dtc_dop_refs: List[OdxLinkRef]
79
+ linked_dtc_dops_raw: List[LinkedDtcDop]
35
80
  is_visible_raw: Optional[bool]
36
81
 
82
+ def __post_init__(self) -> None:
83
+ self._init_finished = False
84
+
37
85
  @staticmethod
38
86
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DtcDop":
39
87
  """Reads a DTC-DOP."""
@@ -57,12 +105,10 @@ class DtcDop(DopBase):
57
105
  elif dtc_proxy_elem.tag == "DTC-REF":
58
106
  dtcs_raw.append(OdxLinkRef.from_et(dtc_proxy_elem, doc_frags))
59
107
 
60
- # TODO: NOT-INHERITED-DTC-SNREFS
61
- linked_dtc_dop_refs = [
62
- OdxLinkRef.from_et(dtc_ref_elem, doc_frags)
108
+ linked_dtc_dops_raw = [
109
+ LinkedDtcDop.from_et(dtc_ref_elem, doc_frags)
63
110
  for dtc_ref_elem in et_element.iterfind("LINKED-DTC-DOPS/"
64
- "LINKED-DTC-DOP/"
65
- "DTC-DOP-REF")
111
+ "LINKED-DTC-DOP")
66
112
  ]
67
113
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
68
114
 
@@ -71,7 +117,7 @@ class DtcDop(DopBase):
71
117
  physical_type=physical_type,
72
118
  compu_method=compu_method,
73
119
  dtcs_raw=dtcs_raw,
74
- linked_dtc_dop_refs=linked_dtc_dop_refs,
120
+ linked_dtc_dops_raw=linked_dtc_dops_raw,
75
121
  is_visible_raw=is_visible_raw,
76
122
  **kwargs)
77
123
 
@@ -80,24 +126,27 @@ class DtcDop(DopBase):
80
126
  return self._dtcs
81
127
 
82
128
  @property
83
- def is_visible(self) -> bool:
84
- return self.is_visible_raw is True
129
+ def linked_dtc_dops(self) -> NamedItemList[LinkedDtcDop]:
130
+ return self._linked_dtc_dops
85
131
 
86
132
  @property
87
- def linked_dtc_dops(self) -> NamedItemList["DtcDop"]:
88
- return self._linked_dtc_dops
133
+ def is_visible(self) -> bool:
134
+ return self.is_visible_raw is True
89
135
 
136
+ @override
90
137
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
91
138
 
92
139
  int_trouble_code = self.diag_coded_type.decode_from_pdu(decode_state)
93
140
 
94
- if self.compu_method.is_valid_internal_value(int_trouble_code):
95
- trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
96
- else:
141
+ if not self.compu_method.is_valid_internal_value(int_trouble_code):
97
142
  # TODO: How to prevent this?
98
- raise DecodeError(
143
+ odxraise(
99
144
  f"DTC-DOP {self.short_name} could not convert the coded value "
100
- f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}.")
145
+ f" {repr(int_trouble_code)} to physical type {self.physical_type.base_data_type}.",
146
+ DecodeError)
147
+ return
148
+
149
+ trouble_code = self.compu_method.convert_internal_to_physical(int_trouble_code)
101
150
 
102
151
  assert isinstance(trouble_code, int)
103
152
 
@@ -110,12 +159,15 @@ class DtcDop(DopBase):
110
159
  return dtcs[0]
111
160
 
112
161
  # the DTC was not specified. This probably means that the
113
- # diagnostic description file is incomplete. We do not bail
114
- # out but we cannot provide an interpretation for it out of the
115
- # box...
116
- dtc = DiagnosticTroubleCode(
162
+ # diagnostic description file is incomplete.
163
+ odxraise(
164
+ f"Encountered DTC 0x{trouble_code:06x} which has not been defined "
165
+ f"by the database", DecodeError)
166
+
167
+ return DiagnosticTroubleCode(
117
168
  trouble_code=trouble_code,
118
169
  odx_id=cast(OdxLinkId, None),
170
+ oid=None,
119
171
  short_name=f'DTC_{trouble_code:06x}',
120
172
  long_name=None,
121
173
  description=None,
@@ -126,43 +178,70 @@ class DtcDop(DopBase):
126
178
  sdgs=[],
127
179
  )
128
180
 
129
- return dtc
130
-
131
- def convert_physical_to_bytes(self,
132
- physical_value: ParameterValue,
133
- encode_state: EncodeState,
134
- bit_position: int = 0) -> bytes:
135
- if isinstance(physical_value, DiagnosticTroubleCode):
136
- trouble_code = physical_value.trouble_code
137
- elif isinstance(physical_value, int):
181
+ def convert_to_numerical_trouble_code(self, dtc_value: ParameterValue) -> int:
182
+ if isinstance(dtc_value, DiagnosticTroubleCode):
183
+ return dtc_value.trouble_code
184
+ elif isinstance(dtc_value, int):
138
185
  # assume that physical value is the trouble_code
139
- trouble_code = physical_value
140
- elif isinstance(physical_value, str):
186
+ return dtc_value
187
+ elif isinstance(dtc_value, str):
141
188
  # assume that physical value is the short_name
142
- dtcs = [dtc for dtc in self.dtcs if dtc.short_name == physical_value]
143
- odxassert(len(dtcs) == 1)
144
- trouble_code = dtcs[0].trouble_code
189
+ dtcs = [dtc for dtc in self.dtcs if dtc.short_name == dtc_value]
190
+ if len(dtcs) != 1:
191
+ odxraise(f"No DTC named {dtc_value} found for DTC-DOP "
192
+ f"{self.short_name}.", EncodeError)
193
+ return cast(int, None)
194
+
195
+ return dtcs[0].trouble_code
145
196
  else:
146
- raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
147
- f" DiagnosticTroubleCode but got {physical_value!r}.")
197
+ odxraise(
198
+ f"The DTC-DOP {self.short_name} expected a"
199
+ f" diagnostic trouble code but got {type(dtc_value).__name__}", EncodeError)
200
+ return cast(int, None)
201
+
202
+ @override
203
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
204
+ encode_state: EncodeState) -> None:
205
+ if physical_value is None:
206
+ odxraise(f"No DTC specified", EncodeError)
207
+ return
208
+
209
+ trouble_code = self.convert_to_numerical_trouble_code(physical_value)
148
210
 
149
- internal_trouble_code = self.compu_method.convert_physical_to_internal(trouble_code)
211
+ internal_trouble_code = int(self.compu_method.convert_physical_to_internal(trouble_code))
150
212
 
151
- return self.diag_coded_type.convert_internal_to_bytes(
152
- internal_trouble_code, encode_state=encode_state, bit_position=bit_position)
213
+ found = False
214
+ for dtc in self.dtcs:
215
+ if internal_trouble_code == dtc.trouble_code:
216
+ found = True
217
+ break
218
+
219
+ if not found:
220
+ odxraise(
221
+ f"Unknown diagnostic trouble code {physical_value!r} "
222
+ f"(0x{internal_trouble_code: 06x}) specified", EncodeError)
223
+
224
+ self.diag_coded_type.encode_into_pdu(internal_trouble_code, encode_state)
153
225
 
154
226
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
155
227
  odxlinks = super()._build_odxlinks()
156
228
 
229
+ odxlinks.update(self.compu_method._build_odxlinks())
230
+
157
231
  for dtc_proxy in self.dtcs_raw:
158
232
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
159
233
  odxlinks.update(dtc_proxy._build_odxlinks())
160
234
 
235
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
236
+ odxlinks.update(linked_dtc_dop._build_odxlinks())
237
+
161
238
  return odxlinks
162
239
 
163
240
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
164
241
  super()._resolve_odxlinks(odxlinks)
165
242
 
243
+ self.compu_method._resolve_odxlinks(odxlinks)
244
+
166
245
  self._dtcs = NamedItemList[DiagnosticTroubleCode]()
167
246
  for dtc_proxy in self.dtcs_raw:
168
247
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
@@ -172,12 +251,51 @@ class DtcDop(DopBase):
172
251
  dtc = odxlinks.resolve(dtc_proxy, DiagnosticTroubleCode)
173
252
  self._dtcs.append(dtc)
174
253
 
175
- linked_dtc_dops = [odxlinks.resolve(x, DtcDop) for x in self.linked_dtc_dop_refs]
176
- self._linked_dtc_dops = NamedItemList(linked_dtc_dops)
254
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
255
+ linked_dtc_dop._resolve_odxlinks(odxlinks)
256
+
257
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
258
+ # hack to avoid initializing the DtcDop object multiple
259
+ # times. This is required, because the linked DTC DOP feature
260
+ # requires the "parent" DTC DOPs to be fully initialized but
261
+ # the standard does not define a formal ordering of DTC DOPs.
262
+ if self._init_finished:
263
+ return
177
264
 
178
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
179
- super()._resolve_snrefs(diag_layer)
265
+ self._init_finished = True
266
+
267
+ super()._resolve_snrefs(context)
268
+
269
+ self.compu_method._resolve_snrefs(context)
180
270
 
181
271
  for dtc_proxy in self.dtcs_raw:
182
272
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
183
- dtc_proxy._resolve_snrefs(diag_layer)
273
+ dtc_proxy._resolve_snrefs(context)
274
+
275
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
276
+ linked_dtc_dop._resolve_snrefs(context)
277
+
278
+ # add the inherited DTCs from linked DTC DOPs. Note that this
279
+ # requires that there are no cycles in the "link-hierarchy"
280
+ dtc_short_names = {dtc.short_name for dtc in self._dtcs}
281
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
282
+ linked_dtc_dop.dtc_dop._resolve_snrefs(context)
283
+
284
+ for dtc in linked_dtc_dop.dtc_dop.dtcs:
285
+ if dtc.short_name in dtc_short_names:
286
+ # we already have a DTC with that name. Since we
287
+ # are not supposed to overwrite the local DTCs, we
288
+ # skip processing this one. TODO: Are inheritance
289
+ # conflicts for DTCs allowed?
290
+ continue
291
+
292
+ if dtc.short_name in linked_dtc_dop.not_inherited_dtc_snrefs:
293
+ # DTC is explicitly not inherited
294
+ continue
295
+
296
+ self._dtcs.append(dtc)
297
+ dtc_short_names.add(dtc.short_name)
298
+
299
+ # at this place, the linked DTC DOPs exhibit .short_name, so
300
+ # we can create a NamedItemList...
301
+ self._linked_dtc_dops = NamedItemList(self.linked_dtc_dops_raw)
@@ -0,0 +1,134 @@
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 .dataobjectproperty import DataObjectProperty
9
+ from .decodestate import DecodeState
10
+ from .dynenddopref import DynEndDopRef
11
+ from .encodestate import EncodeState
12
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
13
+ from .field import Field
14
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
+ from .odxtypes import AtomicOdxType, ParameterValue
16
+ from .snrefcontext import SnRefContext
17
+ from .utils import dataclass_fields_asdict
18
+
19
+
20
+ @dataclass
21
+ class DynamicEndmarkerField(Field):
22
+ """Array of a structure with variable length determined by a termination sequence"""
23
+
24
+ dyn_end_dop_ref: DynEndDopRef
25
+
26
+ @staticmethod
27
+ def from_et(et_element: ElementTree.Element,
28
+ doc_frags: List[OdxDocFragment]) -> "DynamicEndmarkerField":
29
+ kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
30
+
31
+ # ODX 2.0 uses DATA-OBJECT-PROP-REF
32
+ # ODX 2.2 uses DYN-END-DOP-REF
33
+ dop_ref = et_element.find("DYN-END-DOP-REF") or et_element.find("DATA-OBJECT-PROP-REF")
34
+ dyn_end_dop_ref = DynEndDopRef.from_et(odxrequire(dop_ref), doc_frags)
35
+
36
+ return DynamicEndmarkerField(dyn_end_dop_ref=dyn_end_dop_ref, **kwargs)
37
+
38
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
39
+ odxlinks = super()._build_odxlinks()
40
+ return odxlinks
41
+
42
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
43
+ super()._resolve_odxlinks(odxlinks)
44
+
45
+ self._dyn_end_dop = odxlinks.resolve(self.dyn_end_dop_ref, DataObjectProperty)
46
+
47
+ tv_string = self.dyn_end_dop_ref.termination_value_raw
48
+ tv_physical = self._dyn_end_dop.diag_coded_type.base_data_type.from_string(tv_string)
49
+
50
+ self._termination_value = tv_physical
51
+
52
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
53
+ super()._resolve_snrefs(context)
54
+
55
+ @property
56
+ def dyn_end_dop(self) -> DataObjectProperty:
57
+ return self._dyn_end_dop
58
+
59
+ @property
60
+ def termination_value(self) -> AtomicOdxType:
61
+ return self._termination_value
62
+
63
+ @override
64
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
65
+
66
+ odxassert(encode_state.cursor_bit_position == 0,
67
+ "No bit position can be specified for dynamic endmarker fields!")
68
+ if not isinstance(physical_value, Sequence):
69
+ odxraise(
70
+ f"Expected a sequence of values for dynamic endmarker field {self.short_name}, "
71
+ f"got {type(physical_value).__name__}", EncodeError)
72
+ return
73
+
74
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
75
+ encode_state.is_end_of_pdu = False
76
+ for i, item in enumerate(physical_value):
77
+ if i == len(physical_value) - 1:
78
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
79
+
80
+ self.structure.encode_into_pdu(item, encode_state)
81
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
82
+
83
+ if not encode_state.is_end_of_pdu:
84
+ # only add an endmarker if we are not at the end of the
85
+ # PDU. note that since section 7.3.6.10.5 of the MCD-2
86
+ # specification states that the data used by the endmarker
87
+ # ought to be considered to be not consumed (why?!), we
88
+ # need to keep the cursor where it is before adding the
89
+ # endmarker. (we still consider its bits to be used
90
+ # "used", in order to produce a warning if it is attempted
91
+ # to be overridden.)
92
+ tmp_cursor = encode_state.cursor_byte_position
93
+ self.dyn_end_dop.encode_into_pdu(self.termination_value, encode_state)
94
+ encode_state.cursor_byte_position = tmp_cursor
95
+
96
+ @override
97
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
98
+
99
+ odxassert(decode_state.cursor_bit_position == 0,
100
+ "No bit position can be specified for dynamic endmarker fields!")
101
+
102
+ orig_origin = decode_state.origin_byte_position
103
+ decode_state.origin_byte_position = decode_state.cursor_byte_position
104
+
105
+ result: List[ParameterValue] = []
106
+ while True:
107
+ # check if we're at the end of the PDU
108
+ if decode_state.cursor_byte_position == len(decode_state.coded_message):
109
+ break
110
+
111
+ # check if the cursor currently points to a termination
112
+ # value
113
+ tmp_cursor = decode_state.cursor_byte_position
114
+ try:
115
+ tv_candidate = self.dyn_end_dop.decode_from_pdu(decode_state)
116
+ if tv_candidate == self.termination_value:
117
+ # note that section 7.3.6.10.5 of the MCD-2
118
+ # specification states that the bytes occupied by
119
+ # the endmarker ought to be considered to be not
120
+ # consumed (why?!), i.e., we need to keep the
121
+ # cursor where it is before adding the
122
+ # endmarker. (we still consider its to be used
123
+ # "used", though.)
124
+ decode_state.cursor_byte_position = tmp_cursor
125
+ break
126
+ except DecodeError:
127
+ pass
128
+ decode_state.cursor_byte_position = tmp_cursor
129
+
130
+ result.append(self.structure.decode_from_pdu(decode_state))
131
+
132
+ decode_state.origin_byte_position = orig_origin
133
+
134
+ return result
@@ -1,8 +1,10 @@
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, Sequence
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .decodestate import DecodeState
7
9
  from .determinenumberofitems import DetermineNumberOfItems
8
10
  from .encodestate import EncodeState
@@ -10,11 +12,9 @@ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequir
10
12
  from .field import Field
11
13
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
14
  from .odxtypes import ParameterValue
15
+ from .snrefcontext import SnRefContext
13
16
  from .utils import dataclass_fields_asdict
14
17
 
15
- if TYPE_CHECKING:
16
- from .diaglayer import DiagLayer
17
-
18
18
 
19
19
  @dataclass
20
20
  class DynamicLengthField(Field):
@@ -43,55 +43,60 @@ class DynamicLengthField(Field):
43
43
  super()._resolve_odxlinks(odxlinks)
44
44
  self.determine_number_of_items._resolve_odxlinks(odxlinks)
45
45
 
46
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
47
- super()._resolve_snrefs(diag_layer)
48
- self.determine_number_of_items._resolve_snrefs(diag_layer)
46
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
47
+ super()._resolve_snrefs(context)
48
+ self.determine_number_of_items._resolve_snrefs(context)
49
49
 
50
- def convert_physical_to_bytes(
51
- self,
52
- physical_value: ParameterValue,
53
- encode_state: EncodeState,
54
- bit_position: int = 0,
55
- ) -> bytes:
50
+ @override
51
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
56
52
 
57
- odxassert(bit_position == 0, "No bit position can be specified for dynamic length fields!")
58
- if not isinstance(physical_value, list):
53
+ odxassert(encode_state.cursor_bit_position == 0,
54
+ "No bit position can be specified for dynamic length fields!")
55
+
56
+ if not isinstance(physical_value, Sequence):
59
57
  odxraise(
60
58
  f"Expected a list of values for dynamic length field {self.short_name}, "
61
59
  f"got {type(physical_value)}", EncodeError)
62
60
 
61
+ # move the origin to the cursor position
62
+ orig_origin = encode_state.origin_byte_position
63
+ encode_state.origin_byte_position = encode_state.cursor_byte_position
64
+
63
65
  det_num_items = self.determine_number_of_items
64
- field_len = det_num_items.dop.convert_physical_to_bytes(
65
- len(physical_value), encode_state, det_num_items.bit_position or 0)
66
-
67
- # hack to emplace the length specifier at the correct location
68
- tmp = encode_state.coded_message
69
- encode_state.coded_message = bytearray()
70
- encode_state.emplace_atomic_value(field_len, self.short_name + ".num_items",
71
- det_num_items.byte_position)
72
- result = encode_state.coded_message
73
- encode_state.coded_message = tmp
74
-
75
- # if required, add padding between the length specifier and
76
- # the first item
77
- if len(result) < self.offset:
78
- result.extend([0] * (self.offset - len(result)))
79
- elif len(result) > self.offset:
66
+ encode_state.cursor_bit_position = self.determine_number_of_items.bit_position or 0
67
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + det_num_items.byte_position
68
+ det_num_items.dop.encode_into_pdu(len(physical_value), encode_state)
69
+
70
+ if encode_state.cursor_byte_position - encode_state.origin_byte_position > self.offset:
80
71
  odxraise(f"The length specifier of field {self.short_name} overlaps "
81
- f"with the first item!")
72
+ f"with its first item!")
82
73
 
83
- for value in physical_value:
84
- result += self.structure.convert_physical_to_bytes(value, encode_state)
74
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.offset
75
+ encode_state.cursor_bit_position = 0
85
76
 
86
- return result
77
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
78
+ encode_state.is_end_of_pdu = False
79
+ for i, value in enumerate(physical_value):
80
+ if i == len(physical_value) - 1:
81
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
82
+
83
+ self.structure.encode_into_pdu(value, encode_state)
84
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
85
+
86
+ # ensure the correct message size if the field is empty
87
+ if len(physical_value) == 0:
88
+ encode_state.emplace_bytes(b"")
89
+
90
+ # move cursor and origin positions
91
+ encode_state.origin_byte_position = orig_origin
87
92
 
93
+ @override
88
94
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
89
95
 
90
96
  odxassert(decode_state.cursor_bit_position == 0,
91
97
  "No bit position can be specified for dynamic length fields!")
92
98
 
93
99
  orig_origin = decode_state.origin_byte_position
94
- orig_cursor = decode_state.cursor_byte_position
95
100
 
96
101
  det_num_items = self.determine_number_of_items
97
102
  decode_state.origin_byte_position = decode_state.cursor_byte_position
@@ -115,6 +120,5 @@ class DynamicLengthField(Field):
115
120
  result.append(self.structure.decode_from_pdu(decode_state))
116
121
 
117
122
  decode_state.origin_byte_position = orig_origin
118
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
119
123
 
120
124
  return result