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
@@ -0,0 +1,177 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional, Union
4
+ from xml.etree import ElementTree
5
+
6
+ from .diagcomm import DiagClassType, DiagComm
7
+ from .exceptions import odxraise, odxrequire
8
+ from .nameditemlist import NamedItemList
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
+ from .snrefcontext import SnRefContext
11
+ from .table import Table
12
+
13
+
14
+ @dataclass
15
+ class DynIdDefModeInfo:
16
+ def_mode: str
17
+
18
+ clear_dyn_def_message_ref: Optional[OdxLinkRef]
19
+ clear_dyn_def_message_snref: Optional[str]
20
+
21
+ read_dyn_def_message_ref: Optional[OdxLinkRef]
22
+ read_dyn_def_message_snref: Optional[str]
23
+
24
+ dyn_def_message_ref: Optional[OdxLinkRef]
25
+ dyn_def_message_snref: Optional[str]
26
+
27
+ supported_dyn_ids: List[bytes]
28
+ selection_table_refs: List[Union[OdxLinkRef, str]]
29
+
30
+ @property
31
+ def clear_dyn_def_message(self) -> DiagComm:
32
+ return self._clear_dyn_def_message
33
+
34
+ @property
35
+ def read_dyn_def_message(self) -> DiagComm:
36
+ return self._read_dyn_def_message
37
+
38
+ @property
39
+ def dyn_def_message(self) -> DiagComm:
40
+ return self._dyn_def_message
41
+
42
+ @property
43
+ def selection_tables(self) -> NamedItemList[Table]:
44
+ return self._selection_tables
45
+
46
+ @staticmethod
47
+ def from_et(et_element: ElementTree.Element,
48
+ doc_frags: List[OdxDocFragment]) -> "DynIdDefModeInfo":
49
+ def_mode = odxrequire(et_element.findtext("DEF-MODE"))
50
+
51
+ clear_dyn_def_message_ref = OdxLinkRef.from_et(
52
+ et_element.find("CLEAR-DYN-DEF-MESSAGE-REF"), doc_frags)
53
+ if (snref_elem := et_element.find("CLEAR-DYN-DEF-MESSAGE-SNREF")) is not None:
54
+ clear_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
55
+
56
+ read_dyn_def_message_ref = OdxLinkRef.from_et(
57
+ et_element.find("READ-DYN-DEF-MESSAGE-REF"), doc_frags)
58
+ if (snref_elem := et_element.find("READ-DYN-DEF-MESSAGE-SNREF")) is not None:
59
+ read_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
60
+
61
+ dyn_def_message_ref = OdxLinkRef.from_et(et_element.find("DYN-DEF-MESSAGE-REF"), doc_frags)
62
+ if (snref_elem := et_element.find("DYN-DEF-MESSAGE-SNREF")) is not None:
63
+ dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
64
+
65
+ supported_dyn_ids = [
66
+ bytes.fromhex(odxrequire(x.text))
67
+ for x in et_element.iterfind("SUPPORTED-DYN-IDS/SUPPORTED-DYN-ID")
68
+ ]
69
+
70
+ selection_table_refs: List[Union[OdxLinkRef, str]] = []
71
+ if (st_elems := et_element.find("SELECTION-TABLE-REFS")) is not None:
72
+ for st_elem in st_elems:
73
+ if st_elem.tag == "SELECTION-TABLE-REF":
74
+ selection_table_refs.append(OdxLinkRef.from_et(st_elem, doc_frags))
75
+ elif st_elem.tag == "SELECTION-TABLE-SNREF":
76
+ selection_table_refs.append(odxrequire(st_elem.get("SHORT-NAME")))
77
+ else:
78
+ odxraise()
79
+
80
+ return DynIdDefModeInfo(
81
+ def_mode=def_mode,
82
+ clear_dyn_def_message_ref=clear_dyn_def_message_ref,
83
+ clear_dyn_def_message_snref=clear_dyn_def_message_snref,
84
+ read_dyn_def_message_ref=read_dyn_def_message_ref,
85
+ read_dyn_def_message_snref=read_dyn_def_message_snref,
86
+ dyn_def_message_ref=dyn_def_message_ref,
87
+ dyn_def_message_snref=dyn_def_message_snref,
88
+ supported_dyn_ids=supported_dyn_ids,
89
+ selection_table_refs=selection_table_refs,
90
+ )
91
+
92
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
93
+ result: Dict[OdxLinkId, Any] = {}
94
+
95
+ return result
96
+
97
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
98
+ self._selection_tables = NamedItemList[Table]()
99
+
100
+ if self.clear_dyn_def_message_ref is not None:
101
+ self._clear_dyn_def_message = odxlinks.resolve(self.clear_dyn_def_message_ref, DiagComm)
102
+
103
+ if self.read_dyn_def_message_ref is not None:
104
+ self._read_dyn_def_message = odxlinks.resolve(self.read_dyn_def_message_ref, DiagComm)
105
+
106
+ if self.dyn_def_message_ref is not None:
107
+ self._dyn_def_message = odxlinks.resolve(self.dyn_def_message_ref, DiagComm)
108
+
109
+ # resolve the selection tables referenced using ODXLINK
110
+ for x in self.selection_table_refs:
111
+ if isinstance(x, OdxLinkRef):
112
+ self._selection_tables.append(odxlinks.resolve(x, Table))
113
+
114
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
115
+ diag_layer = odxrequire(context.diag_layer)
116
+
117
+ if self.clear_dyn_def_message_snref is not None:
118
+ self._clear_dyn_def_message = resolve_snref(self.clear_dyn_def_message_snref,
119
+ diag_layer.diag_comms, DiagComm)
120
+
121
+ if self.read_dyn_def_message_snref is not None:
122
+ self._read_dyn_def_message = resolve_snref(self.read_dyn_def_message_snref,
123
+ diag_layer.diag_comms, DiagComm)
124
+
125
+ if self.dyn_def_message_snref is not None:
126
+ self._dyn_def_message = resolve_snref(self.dyn_def_message_snref, diag_layer.diag_comms,
127
+ DiagComm)
128
+
129
+ if self._clear_dyn_def_message.diagnostic_class != DiagClassType.CLEAR_DYN_DEF_MESSAGE:
130
+ odxraise(
131
+ f"Diagnostic communication object of wrong type referenced: "
132
+ f"({odxrequire(self._clear_dyn_def_message.diagnostic_class).value} instead of "
133
+ f"CLEAR-DYN-DEF-MESSAGE)")
134
+ if self._read_dyn_def_message.diagnostic_class != DiagClassType.READ_DYN_DEFINED_MESSAGE:
135
+ odxraise(f"Diagnostic communication object of wrong type referenced: "
136
+ f"({odxrequire(self._read_dyn_def_message.diagnostic_class).value} instead of "
137
+ f"READ-DYN-DEFINED-MESSAGE)")
138
+ if self._dyn_def_message.diagnostic_class != DiagClassType.DYN_DEF_MESSAGE:
139
+ odxraise(f"Diagnostic communication object of wrong type referenced: "
140
+ f"({odxrequire(self._dyn_def_message.diagnostic_class).value} instead of "
141
+ f"DYN-DEF-MESSAGE)")
142
+
143
+ # resolve the remaining selection tables that are referenced via SNREF
144
+ for i, x in enumerate(self.selection_table_refs):
145
+ if isinstance(x, str):
146
+ ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
147
+ self._selection_tables.insert(i, resolve_snref(x, ddd_spec.tables, Table))
148
+
149
+
150
+ @dataclass
151
+ class DynDefinedSpec:
152
+ dyn_id_def_mode_infos: List[DynIdDefModeInfo]
153
+
154
+ @staticmethod
155
+ def from_et(et_element: ElementTree.Element,
156
+ doc_frags: List[OdxDocFragment]) -> "DynDefinedSpec":
157
+ dyn_id_def_mode_infos = [
158
+ DynIdDefModeInfo.from_et(x, doc_frags)
159
+ for x in et_element.iterfind("DYN-ID-DEF-MODE-INFOS/DYN-ID-DEF-MODE-INFO")
160
+ ]
161
+ return DynDefinedSpec(dyn_id_def_mode_infos=dyn_id_def_mode_infos)
162
+
163
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
164
+ result: Dict[OdxLinkId, Any] = {}
165
+
166
+ for didmi in self.dyn_id_def_mode_infos:
167
+ result.update(didmi._build_odxlinks())
168
+
169
+ return result
170
+
171
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
172
+ for didmi in self.dyn_id_def_mode_infos:
173
+ didmi._resolve_odxlinks(odxlinks)
174
+
175
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
176
+ for didmi in self.dyn_id_def_mode_infos:
177
+ didmi._resolve_snrefs(context)
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional, overload
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxraise, odxrequire
7
+ from .odxlink import OdxDocFragment, OdxLinkRef
8
+ from .utils import dataclass_fields_asdict
9
+
10
+
11
+ @dataclass
12
+ class DynEndDopRef(OdxLinkRef):
13
+ termination_value_raw: str
14
+
15
+ @staticmethod
16
+ @overload
17
+ def from_et(et_element: None, source_doc_frags: List[OdxDocFragment]) -> None:
18
+ ...
19
+
20
+ @staticmethod
21
+ @overload
22
+ def from_et(et_element: ElementTree.Element,
23
+ source_doc_frags: List[OdxDocFragment]) -> "DynEndDopRef":
24
+ ...
25
+
26
+ @staticmethod
27
+ def from_et(et_element: Optional[ElementTree.Element],
28
+ source_doc_frags: List[OdxDocFragment]) -> Optional["DynEndDopRef"]:
29
+
30
+ if et_element is None:
31
+ odxraise("Mandatory DYN-END-DOP-REF tag is missing")
32
+ return None
33
+
34
+ kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, source_doc_frags))
35
+
36
+ termination_value_raw = odxrequire(et_element.findtext("TERMINATION-VALUE"))
37
+
38
+ return DynEndDopRef(termination_value_raw=termination_value_raw, **kwargs)
@@ -2,10 +2,11 @@
2
2
  from enum import Enum
3
3
  from typing import Dict, Generator, List, Optional
4
4
 
5
- from .diaglayer import DiagLayer
6
- from .diaglayertype import DiagLayerType
5
+ from .diaglayers.diaglayer import DiagLayer
6
+ from .diaglayers.diaglayertype import DiagLayerType
7
+ from .diaglayers.ecuvariant import EcuVariant
7
8
  from .diagservice import DiagService
8
- from .exceptions import OdxError, odxassert
9
+ from .exceptions import OdxError, odxassert, odxrequire
9
10
  from .matchingparameter import MatchingParameter
10
11
  from .odxtypes import ParameterValue
11
12
  from .response import Response
@@ -42,9 +43,7 @@ class EcuVariantMatcher:
42
43
  @staticmethod
43
44
  def get_ident_service(diag_layer: DiagLayer, matching_param: MatchingParameter) -> DiagService:
44
45
  service_name = matching_param.diag_comm_snref
45
- odxassert(service_name in [x.short_name for x in diag_layer.services])
46
- service = diag_layer.services[service_name]
47
- odxassert(isinstance(service, DiagService))
46
+ service = odxrequire(diag_layer.services.get(service_name))
48
47
  return service
49
48
 
50
49
  @staticmethod
@@ -89,7 +88,7 @@ class EcuVariantMatcher:
89
88
  raise OdxError(f"The snref or snpathref '{matching_param.out_param_if}' is cannot be \
90
89
  resolved for any positive or negative response.")
91
90
 
92
- def __init__(self, ecu_variant_candidates: List[DiagLayer], use_cache: bool = True):
91
+ def __init__(self, ecu_variant_candidates: List[EcuVariant], use_cache: bool = True):
93
92
 
94
93
  self.ecus = ecu_variant_candidates
95
94
  for ecu in self.ecus:
odxtools/element.py CHANGED
@@ -2,42 +2,40 @@ from dataclasses import dataclass
2
2
  from typing import List, Optional
3
3
  from xml.etree import ElementTree
4
4
 
5
+ from .description import Description
5
6
  from .exceptions import odxrequire
6
7
  from .odxlink import OdxDocFragment, OdxLinkId
7
- from .utils import create_description_from_et, dataclass_fields_asdict
8
+ from .utils import dataclass_fields_asdict
8
9
 
9
10
 
10
11
  @dataclass
11
12
  class NamedElement:
12
13
  short_name: str
13
14
  long_name: Optional[str]
14
- description: Optional[str]
15
+ description: Optional[Description]
15
16
 
16
17
  @staticmethod
17
- def from_et(
18
- et_element: ElementTree.Element,
19
- doc_frags: List[OdxDocFragment],
20
- ) -> "NamedElement":
18
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "NamedElement":
21
19
 
22
20
  return NamedElement(
23
21
  short_name=odxrequire(et_element.findtext("SHORT-NAME")),
24
22
  long_name=et_element.findtext("LONG-NAME"),
25
- description=create_description_from_et(et_element.find("DESC")),
23
+ description=Description.from_et(et_element.find("DESC"), doc_frags),
26
24
  )
27
25
 
28
26
 
29
27
  @dataclass
30
28
  class IdentifiableElement(NamedElement):
31
29
  odx_id: OdxLinkId
30
+ oid: Optional[str]
32
31
 
33
32
  @staticmethod
34
- def from_et(
35
- et_element: ElementTree.Element,
36
- doc_frags: List[OdxDocFragment],
37
- ) -> "IdentifiableElement":
33
+ def from_et(et_element: ElementTree.Element,
34
+ doc_frags: List[OdxDocFragment]) -> "IdentifiableElement":
38
35
 
39
36
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
40
- return IdentifiableElement(
41
- **kwargs,
42
- odx_id=odxrequire(OdxLinkId.from_et(et_element, doc_frags)),
43
- )
37
+
38
+ odx_id = odxrequire(OdxLinkId.from_et(et_element, doc_frags))
39
+ oid = et_element.get("OID")
40
+
41
+ return IdentifiableElement(**kwargs, odx_id=odx_id, oid=oid)
odxtools/encodestate.py CHANGED
@@ -1,12 +1,18 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Dict, Optional
4
+ from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
5
5
 
6
- from .exceptions import OdxWarning
6
+ from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
7
+ from .odxtypes import AtomicOdxType, DataType, ParameterValue
8
+
9
+ try:
10
+ import bitstruct.c as bitstruct
11
+ except ImportError:
12
+ import bitstruct
7
13
 
8
14
  if TYPE_CHECKING:
9
- from .tablerow import TableRow
15
+ from .parameters.parameter import Parameter
10
16
 
11
17
 
12
18
  @dataclass
@@ -15,10 +21,23 @@ class EncodeState:
15
21
  """
16
22
 
17
23
  #: payload that has been constructed so far
18
- coded_message: bytearray
24
+ coded_message: bytearray = field(default_factory=bytearray)
25
+
26
+ #: the bits of the payload that are used
27
+ used_mask: bytearray = field(default_factory=bytearray)
28
+
29
+ #: The absolute position in bytes from the beginning of the PDU to
30
+ #: which relative positions refer to, e.g., the beginning of the
31
+ #: structure.
32
+ origin_byte_position: int = 0
33
+
34
+ #: The absolute position in bytes from the beginning of the PDU
35
+ #: where the next object ought to be placed into the PDU
36
+ cursor_byte_position: int = 0
19
37
 
20
- #: a mapping from short name to value for each parameter
21
- parameter_values: Dict[str, Any]
38
+ #: The bit position [0-7] where the next object ought to be
39
+ #: placed into the PDU
40
+ cursor_bit_position: int = 0
22
41
 
23
42
  #: If encoding a response: request that triggered the response
24
43
  triggering_request: Optional[bytes] = None
@@ -28,31 +47,189 @@ class EncodeState:
28
47
  length_keys: Dict[str, int] = field(default_factory=dict)
29
48
 
30
49
  #: Mapping from the short name of a table-key parameter to the
31
- #: corresponding row of the table (specified by TableKeyParameter)
32
- table_keys: Dict[str, "TableRow"] = field(default_factory=dict)
50
+ #: short name of the corresponding row of the table (specified by
51
+ #: TableKeyParameter)
52
+ table_keys: Dict[str, str] = field(default_factory=dict)
53
+
54
+ #: The cursor position where a given length- or table key is located
55
+ #: in the PDU
56
+ key_pos: Dict[str, int] = field(default_factory=dict)
33
57
 
34
58
  #: Flag whether we are currently the last parameter of the PDU
35
- #: (needed for MinMaxLengthType)
36
- is_end_of_pdu: bool = False
59
+ #: (needed for MinMaxLengthType, EndOfPduField, etc.)
60
+ is_end_of_pdu: bool = True
61
+
62
+ #: list of parameters that have been encoded so far. The journal
63
+ #: is used by some types of parameters which depend on the values of
64
+ #: other parameters; e.g., environment data description parameters
65
+ journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
66
+
67
+ #: If this is True, specifying unknown parameters for encoding
68
+ #: will raise an OdxError exception in strict mode.
69
+ allow_unknown_parameters = False
70
+
71
+ def __post_init__(self) -> None:
72
+ # if a coded message has been specified, but no used_mask, we
73
+ # assume that all of the bits of the coded message are
74
+ # currently used.
75
+ if len(self.coded_message) > len(self.used_mask):
76
+ self.used_mask += b'\xff' * (len(self.coded_message) - len(self.used_mask))
77
+ if len(self.coded_message) < len(self.used_mask):
78
+ odxraise(f"The specified bit mask 0x{self.used_mask.hex()} for used bits "
79
+ f"is not suitable for representing the coded_message "
80
+ f"0x{self.coded_message.hex()}")
81
+ self.used_mask = self.used_mask[:len(self.coded_message)]
82
+
83
+ def emplace_atomic_value(
84
+ self,
85
+ *,
86
+ internal_value: AtomicOdxType,
87
+ bit_length: int,
88
+ base_data_type: DataType,
89
+ is_highlow_byte_order: bool,
90
+ used_mask: Optional[bytes],
91
+ ) -> None:
92
+ """Convert the internal_value to bytes and emplace this into the PDU"""
93
+
94
+ raw_value: AtomicOdxType
95
+
96
+ # Check that bytes and strings actually fit into the bit length
97
+ if base_data_type == DataType.A_BYTEFIELD:
98
+ if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
99
+ odxraise()
100
+ if 8 * len(internal_value) > bit_length:
101
+ raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
102
+ f"({len(internal_value)} bytes)."
103
+ f" The maximum length is {bit_length//8}.")
104
+ raw_value = bytes(internal_value)
105
+ elif base_data_type == DataType.A_ASCIISTRING:
106
+ if not isinstance(internal_value, str):
107
+ odxraise()
108
+
109
+ # The spec says ASCII, meaning only byte values 0-127.
110
+ # But in practice, vendors use iso-8859-1, aka latin-1
111
+ # reason being iso-8859-1 never fails since it has a valid
112
+ # character mapping for every possible byte sequence.
113
+ raw_value = internal_value.encode("iso-8859-1")
114
+
115
+ if 8 * len(raw_value) > bit_length:
116
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
117
+ f" The maximum number of characters is {bit_length//8}.")
118
+ elif base_data_type == DataType.A_UTF8STRING:
119
+ if not isinstance(internal_value, str):
120
+ odxraise()
121
+
122
+ raw_value = internal_value.encode("utf-8")
123
+
124
+ if 8 * len(raw_value) > bit_length:
125
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
126
+ f" The maximum number of bytes is {bit_length//8}.")
37
127
 
38
- def emplace_atomic_value(self,
39
- new_data: bytes,
40
- param_name: str,
41
- pos: Optional[int] = None) -> None:
42
- if pos is None:
43
- pos = len(self.coded_message)
128
+ elif base_data_type == DataType.A_UNICODE2STRING:
129
+ if not isinstance(internal_value, str):
130
+ odxraise()
131
+
132
+ text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
133
+ raw_value = internal_value.encode(text_encoding)
134
+
135
+ if 8 * len(raw_value) > bit_length:
136
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
137
+ f" The maximum number of characters is {bit_length//16}.")
138
+ else:
139
+ raw_value = internal_value
140
+
141
+ # If the bit length is zero, return empty bytes
142
+ if bit_length == 0:
143
+ if (base_data_type.value in [
144
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
145
+ ] and base_data_type.value != 0):
146
+ odxraise(
147
+ f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
148
+ EncodeError)
149
+ self.emplace_bytes(b'')
150
+ return
151
+
152
+ char = base_data_type.bitstruct_format_letter
153
+ padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
154
+ odxassert((0 <= padding and padding < 8 and
155
+ (padding + bit_length + self.cursor_bit_position) % 8 == 0),
156
+ f"Incorrect padding {padding}")
157
+ left_pad = f"p{padding}" if padding > 0 else ""
158
+
159
+ # actually encode the value
160
+ coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
161
+
162
+ # create the raw mask of used bits for numeric objects
163
+ used_mask_raw = used_mask
164
+ if base_data_type in [DataType.A_INT32, DataType.A_UINT32
165
+ ] and (self.cursor_bit_position != 0 or
166
+ (self.cursor_bit_position + bit_length) % 8 != 0):
167
+ if used_mask is None:
168
+ tmp = (1 << bit_length) - 1
169
+ else:
170
+ tmp = int.from_bytes(used_mask, "big")
171
+ tmp <<= self.cursor_bit_position
172
+
173
+ used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
174
+
175
+ # apply byte order to numeric objects
176
+ if not is_highlow_byte_order and base_data_type in [
177
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
178
+ ]:
179
+ coded = coded[::-1]
180
+
181
+ if used_mask_raw is not None:
182
+ used_mask_raw = used_mask_raw[::-1]
183
+
184
+ self.cursor_bit_position = 0
185
+ self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
186
+
187
+ def emplace_bytes(self,
188
+ new_data: bytes,
189
+ obj_name: Optional[str] = None,
190
+ obj_used_mask: Optional[bytes] = None) -> None:
191
+ if self.cursor_bit_position != 0:
192
+ odxraise("EncodeState.emplace_bytes can only be called "
193
+ "for a bit position of 0!", RuntimeError)
194
+
195
+ pos = self.cursor_byte_position
44
196
 
45
197
  # Make blob longer if necessary
46
198
  min_length = pos + len(new_data)
47
199
  if len(self.coded_message) < min_length:
48
- self.coded_message.extend([0] * (min_length - len(self.coded_message)))
200
+ pad = b'\x00' * (min_length - len(self.coded_message))
201
+ self.coded_message += pad
202
+ self.used_mask += pad
203
+
204
+ if obj_used_mask is None:
205
+ # Happy path for when no obj_used_mask has been
206
+ # specified. In this case we assume that all bits of the
207
+ # new data to be emplaced are used.
208
+ n = len(new_data)
49
209
 
50
- for byte_idx_val, byte_idx_rpc in enumerate(range(pos, pos + len(new_data))):
51
- # insert byte value
52
- if self.coded_message[byte_idx_rpc] & new_data[byte_idx_val] != 0:
210
+ if self.used_mask[pos:pos + n] != b'\x00' * n:
53
211
  warnings.warn(
54
- f"Object '{param_name}' overlaps with another parameter (bytes are already set)",
212
+ f"Overlapping objects detected in between bytes {pos} and "
213
+ f"{pos+n}",
55
214
  OdxWarning,
56
215
  stacklevel=1,
57
216
  )
58
- self.coded_message[byte_idx_rpc] |= new_data[byte_idx_val]
217
+ self.coded_message[pos:pos + n] = new_data
218
+ self.used_mask[pos:pos + n] = b'\xff' * n
219
+ else:
220
+ # insert data the hard way, i.e. we have to look at each
221
+ # individual byte to determine if it has already been used
222
+ # somewhere else (it would be nice if bytearrays supported
223
+ # bitwise operations!)
224
+ for i in range(len(new_data)):
225
+ if self.used_mask[pos + i] & obj_used_mask[i] != 0:
226
+ warnings.warn(
227
+ f"Overlapping objects detected at position {pos + i}",
228
+ OdxWarning,
229
+ stacklevel=1,
230
+ )
231
+ self.coded_message[pos + i] &= ~obj_used_mask[i]
232
+ self.coded_message[pos + i] |= new_data[i] & obj_used_mask[i]
233
+ self.used_mask[pos + i] |= obj_used_mask[i]
234
+
235
+ self.cursor_byte_position += len(new_data)
odxtools/endofpdufield.py CHANGED
@@ -1,8 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from typing import List, Optional, 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 .encodestate import EncodeState
8
10
  from .exceptions import EncodeError, odxassert, odxraise
@@ -39,30 +41,39 @@ class EndOfPduField(Field):
39
41
 
40
42
  return eopf
41
43
 
42
- def convert_physical_to_bytes(
43
- self,
44
- physical_values: ParameterValue,
45
- encode_state: EncodeState,
46
- bit_position: int = 0,
47
- ) -> bytes:
48
-
49
- odxassert(
50
- bit_position == 0, "End of PDU field must be byte aligned. "
51
- "Is there an error in reading the .odx?", EncodeError)
52
- if not isinstance(physical_values, list):
44
+ @override
45
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
46
+ encode_state: EncodeState) -> None:
47
+ odxassert(not encode_state.cursor_bit_position,
48
+ "No bit position can be specified for end-of-pdu fields!")
49
+ odxassert(encode_state.is_end_of_pdu,
50
+ "End-of-pdu fields can only be located at the end of PDUs!")
51
+
52
+ if not isinstance(physical_value, Sequence):
53
53
  odxraise(
54
- f"Expected a list of values for end-of-pdu field {self.short_name}, "
55
- f"got {type(physical_values)}", EncodeError)
54
+ f"Invalid type {type(physical_value).__name__} of physical "
55
+ f"value for end-of-pdu field, expected a list", EncodeError)
56
+ return
57
+
58
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
59
+ encode_state.is_end_of_pdu = False
56
60
 
57
- coded_message = b''
58
- for value in physical_values:
59
- coded_message += self.structure.convert_physical_to_bytes(value, encode_state)
60
- return coded_message
61
+ for i, value in enumerate(physical_value):
62
+ if i == len(physical_value) - 1:
63
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
61
64
 
65
+ self.structure.encode_into_pdu(value, encode_state)
66
+
67
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
68
+
69
+ @override
62
70
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
63
71
  odxassert(not decode_state.cursor_bit_position,
64
72
  "No bit position can be specified for end-of-pdu fields!")
65
73
 
74
+ orig_origin = decode_state.origin_byte_position
75
+ decode_state.origin_byte_position = decode_state.cursor_byte_position
76
+
66
77
  result: List[ParameterValue] = []
67
78
  while decode_state.cursor_byte_position < len(decode_state.coded_message):
68
79
  # ATTENTION: the ODX specification is very misleading
@@ -71,4 +82,6 @@ class EndOfPduField(Field):
71
82
  # repeated are identical, not their values
72
83
  result.append(self.structure.decode_from_pdu(decode_state))
73
84
 
85
+ decode_state.origin_byte_position = orig_origin
86
+
74
87
  return result
@@ -11,7 +11,14 @@ from .utils import dataclass_fields_asdict
11
11
 
12
12
  @dataclass
13
13
  class EnvironmentData(BasicStructure):
14
- """This class represents Environment Data that describes the circumstances in which the error occurred."""
14
+ """This class represents Environment Data that describes the
15
+ circumstances in which the error occurred.
16
+
17
+ This is one of the many multiplexer mechanisms specified by the
18
+ ODX standard, because an environment data parameter must only be
19
+ used if a DTC parameter has a certain set of values. (In this
20
+ sense, it is quite similar to NRC-CONST parameters.)
21
+ """
15
22
 
16
23
  all_value: Optional[bool]
17
24
  dtc_values: List[int]