odxtools 6.6.1__py3-none-any.whl → 9.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. odxtools/__init__.py +7 -5
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
odxtools/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."""
@@ -46,8 +94,8 @@ class DtcDop(DopBase):
46
94
  compu_method = create_any_compu_method_from_et(
47
95
  odxrequire(et_element.find("COMPU-METHOD")),
48
96
  doc_frags,
49
- diag_coded_type.base_data_type,
50
- physical_type.base_data_type,
97
+ internal_type=diag_coded_type.base_data_type,
98
+ physical_type=physical_type.base_data_type,
51
99
  )
52
100
  dtcs_raw: List[Union[DiagnosticTroubleCode, OdxLinkRef]] = []
53
101
  if (dtcs_elem := et_element.find("DTCS")) is not None:
@@ -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
- cast(OdxLinkRef, 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,41 +178,70 @@ class DtcDop(DopBase):
126
178
  sdgs=[],
127
179
  )
128
180
 
129
- return dtc
130
-
131
- def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
132
- bit_position: int) -> bytes:
133
- if isinstance(physical_value, DiagnosticTroubleCode):
134
- trouble_code = physical_value.trouble_code
135
- 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):
136
185
  # assume that physical value is the trouble_code
137
- trouble_code = physical_value
138
- elif isinstance(physical_value, str):
186
+ return dtc_value
187
+ elif isinstance(dtc_value, str):
139
188
  # assume that physical value is the short_name
140
- dtcs = [dtc for dtc in self.dtcs if dtc.short_name == physical_value]
141
- odxassert(len(dtcs) == 1)
142
- 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
143
196
  else:
144
- raise EncodeError(f"The DTC-DOP {self.short_name} expected a"
145
- 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)
146
210
 
147
- 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))
148
212
 
149
- return self.diag_coded_type.convert_internal_to_bytes(
150
- 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)
151
225
 
152
226
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
153
227
  odxlinks = super()._build_odxlinks()
154
228
 
229
+ odxlinks.update(self.compu_method._build_odxlinks())
230
+
155
231
  for dtc_proxy in self.dtcs_raw:
156
232
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
157
233
  odxlinks.update(dtc_proxy._build_odxlinks())
158
234
 
235
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
236
+ odxlinks.update(linked_dtc_dop._build_odxlinks())
237
+
159
238
  return odxlinks
160
239
 
161
240
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
162
241
  super()._resolve_odxlinks(odxlinks)
163
242
 
243
+ self.compu_method._resolve_odxlinks(odxlinks)
244
+
164
245
  self._dtcs = NamedItemList[DiagnosticTroubleCode]()
165
246
  for dtc_proxy in self.dtcs_raw:
166
247
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
@@ -170,12 +251,51 @@ class DtcDop(DopBase):
170
251
  dtc = odxlinks.resolve(dtc_proxy, DiagnosticTroubleCode)
171
252
  self._dtcs.append(dtc)
172
253
 
173
- linked_dtc_dops = [odxlinks.resolve(x, DtcDop) for x in self.linked_dtc_dop_refs]
174
- 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
175
264
 
176
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
177
- super()._resolve_snrefs(diag_layer)
265
+ self._init_finished = True
266
+
267
+ super()._resolve_snrefs(context)
268
+
269
+ self.compu_method._resolve_snrefs(context)
178
270
 
179
271
  for dtc_proxy in self.dtcs_raw:
180
272
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
181
- 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
+
50
+ @override
51
+ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
49
52
 
50
- def convert_physical_to_bytes(
51
- self,
52
- physical_value: ParameterValue,
53
- encode_state: EncodeState,
54
- bit_position: int = 0,
55
- ) -> bytes:
53
+ odxassert(encode_state.cursor_bit_position == 0,
54
+ "No bit position can be specified for dynamic length fields!")
56
55
 
57
- odxassert(bit_position == 0, "No bit position can be specified for dynamic length fields!")
58
- if not isinstance(physical_value, list):
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"")
87
89
 
90
+ # move cursor and origin positions
91
+ encode_state.origin_byte_position = orig_origin
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
@@ -99,6 +104,7 @@ class DynamicLengthField(Field):
99
104
  decode_state.cursor_bit_position = det_num_items.bit_position or 0
100
105
 
101
106
  n = det_num_items.dop.decode_from_pdu(decode_state)
107
+ result: List[ParameterValue] = []
102
108
 
103
109
  if not isinstance(n, int):
104
110
  odxraise(f"Number of items specified by a dynamic length field {self.short_name} "
@@ -107,13 +113,12 @@ class DynamicLengthField(Field):
107
113
  odxraise(
108
114
  f"Number of items specified by a dynamic length field {self.short_name} "
109
115
  f"must be positive (is: {n})", DecodeError)
110
- else:
111
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.offset
112
- result: List[ParameterValue] = []
113
- for _ in range(n):
114
- result.append(self.structure.decode_from_pdu(decode_state))
116
+ n = 0
117
+
118
+ decode_state.cursor_byte_position = decode_state.origin_byte_position + self.offset
119
+ for _ in range(n):
120
+ result.append(self.structure.decode_from_pdu(decode_state))
115
121
 
116
122
  decode_state.origin_byte_position = orig_origin
117
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
118
123
 
119
124
  return result