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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. odxtools/__init__.py +7 -5
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,80 +1,140 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from typing import List, cast
4
+ from xml.etree import ElementTree
4
5
 
5
- from ..exceptions import DecodeError, EncodeError, odxassert
6
+ from ..exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
7
+ from ..odxlink import OdxDocFragment
6
8
  from ..odxtypes import AtomicOdxType, DataType
7
- from .compumethod import CompuMethod, CompuMethodCategory
9
+ from ..utils import dataclass_fields_asdict
10
+ from .compumethod import CompuCategory, CompuMethod
8
11
  from .compuscale import CompuScale
9
12
 
10
13
 
11
14
  @dataclass
12
15
  class TexttableCompuMethod(CompuMethod):
16
+ """Text table compute methods translate numbers to human readable
17
+ textual descriptions.
13
18
 
14
- internal_to_phys: List[CompuScale]
15
- # For compu_default_value, the compu_const is always defined
16
- # the compu_inverse_value is optional
17
- compu_default_value: Optional[CompuScale]
19
+ For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.7.
20
+
21
+ """
22
+
23
+ @staticmethod
24
+ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
25
+ internal_type: DataType,
26
+ physical_type: DataType) -> "TexttableCompuMethod":
27
+ cm = CompuMethod.compu_method_from_et(
28
+ et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
29
+ kwargs = dataclass_fields_asdict(cm)
30
+
31
+ return TexttableCompuMethod(**kwargs)
18
32
 
19
33
  def __post_init__(self) -> None:
20
- odxassert(self.physical_type == DataType.A_UNICODE2STRING,
21
- "TEXTTABLE must have A_UNICODE2STRING as its physical datatype.")
34
+ odxassert(self.category == CompuCategory.TEXTTABLE,
35
+ "TexttableCompuMethod must exhibit TEXTTABLE category")
36
+
37
+ # the spec says that the physical data type shall be
38
+ # A_UNICODE2STRING, but we are a bit more lenient and allow
39
+ # any kind of string...
40
+ odxassert(
41
+ self.physical_type
42
+ in [DataType.A_UNICODE2STRING, DataType.A_UTF8STRING, DataType.A_ASCIISTRING],
43
+ "TEXTTABLE must have string type as its physical datatype.")
44
+
45
+ if self.compu_internal_to_phys is None:
46
+ odxraise("TEXTTABLE compu methods must exhibit a COMPU-INTERNAL-TO-PHYS subtag.")
47
+ scales = []
48
+ else:
49
+ scales = self.compu_internal_to_phys.compu_scales
50
+
22
51
  odxassert(
23
- all(scale.lower_limit is not None or scale.upper_limit is not None
24
- for scale in self.internal_to_phys),
25
- "Text table compu method doesn't have expected format!")
52
+ all(scale.lower_limit is not None or scale.upper_limit is not None for scale in scales),
53
+ "All scales of TEXTTABLE compu methods must provide limits!")
26
54
 
27
- @property
28
- def category(self) -> CompuMethodCategory:
29
- return "TEXTTABLE"
55
+ self._compu_physical_default_value = None
56
+ citp = odxrequire(self.compu_internal_to_phys)
57
+ if (cdv := citp.compu_default_value) is not None:
58
+ self._compu_physical_default_value = self.physical_type.from_string(odxrequire(cdv.vt))
30
59
 
31
- def get_scales(self) -> List[CompuScale]:
32
- scales = list(self.internal_to_phys)
33
- if self.compu_default_value:
34
- # Default is last, since it's a fallback
35
- scales.append(self.compu_default_value)
36
- return scales
60
+ self._compu_internal_default_value = None
61
+ cpti = self.compu_phys_to_internal
62
+ if cpti is not None and (cdv := cpti.compu_default_value) is not None:
63
+ self._compu_internal_default_value = self.internal_type.from_string(odxrequire(cdv.v))
37
64
 
38
65
  def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
39
- matching_scales = [x for x in self.get_scales() if x.compu_const == physical_value]
40
- for scale in matching_scales:
41
- if scale.compu_inverse_value is not None:
42
- return scale.compu_inverse_value
43
- elif scale.lower_limit is not None:
44
- return scale.lower_limit.value
45
- elif scale.upper_limit is not None:
46
- return scale.upper_limit.value
47
-
48
- raise EncodeError(f"Texttable compu method could not encode '{physical_value!r}'.")
49
-
50
- def __is_internal_in_scale(self, internal_value: AtomicOdxType, scale: CompuScale) -> bool:
51
- if scale == self.compu_default_value:
52
- return True
53
- if scale.lower_limit is not None and not scale.lower_limit.complies_to_lower(
54
- internal_value):
55
- return False
56
- # If no UPPER-LIMIT is defined
57
- # the COMPU-SCALE will be applied only for the value defined in LOWER-LIMIT
58
- upper_limit = scale.upper_limit or scale.lower_limit
59
- if upper_limit is not None and not upper_limit.complies_to_upper(internal_value):
60
- return False
61
- # value complies to the defined limits
62
- return True
66
+ scales = []
67
+ if (citp := self.compu_internal_to_phys) is not None:
68
+ scales = citp.compu_scales
69
+ matching_scales = [
70
+ x for x in scales if x.compu_const is not None and x.compu_const.value == physical_value
71
+ ]
72
+
73
+ if len(matching_scales) == 0:
74
+ if self._compu_internal_default_value is None:
75
+ odxraise(f"Texttable could not encode {physical_value!r}.", EncodeError)
76
+ return cast(None, AtomicOdxType)
77
+
78
+ return self._compu_internal_default_value
79
+ elif len(matching_scales) > 1:
80
+ odxraise(f"Texttable could not uniquely encode {physical_value!r}.", EncodeError)
81
+
82
+ scale = matching_scales[0]
83
+ if scale.compu_inverse_value is not None and (civ :=
84
+ scale.compu_inverse_value.value) is not None:
85
+ return civ
86
+ elif scale.lower_limit is not None and scale.lower_limit._value is not None:
87
+ return scale.lower_limit._value
88
+ elif scale.upper_limit is not None and scale.upper_limit._value is not None:
89
+ return scale.upper_limit._value
90
+
91
+ odxraise(f"Texttable compu method could not encode '{physical_value!r}'.", EncodeError)
63
92
 
64
93
  def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
65
- scale = next(
66
- filter(
67
- lambda scale: self.__is_internal_in_scale(internal_value, scale),
68
- self.get_scales(),
69
- ), None)
70
- if scale is None or scale.compu_const is None:
71
- raise DecodeError(
72
- f"Texttable compu method could not decode {internal_value!r} to string.")
73
- return scale.compu_const
94
+ scales = []
95
+ if (citp := self.compu_internal_to_phys) is not None:
96
+ scales = citp.compu_scales
97
+ matching_scales: List[CompuScale] = [x for x in scales if x.applies(internal_value)]
98
+
99
+ if len(matching_scales) == 0:
100
+ if self._compu_physical_default_value is None:
101
+ odxraise(f"Texttable could not decode {internal_value!r}.", DecodeError)
102
+ return cast(None, AtomicOdxType)
103
+
104
+ return self._compu_physical_default_value
105
+
106
+ if len(matching_scales) > 1:
107
+ odxraise(f"Texttable could not uniquely decode {internal_value!r}.", DecodeError)
108
+
109
+ scale = matching_scales[0]
110
+
111
+ if scale.compu_const is None:
112
+ odxraise(f"Encountered a COMPU-SCALE with no COMPU-CONST.")
113
+ return cast(None, AtomicOdxType)
114
+
115
+ if scale.compu_const.value is not None:
116
+ return scale.compu_const.value
117
+
118
+ odxraise(f"Texttable compu method could not decode '{internal_value!r}'.", EncodeError)
74
119
 
75
120
  def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
76
- return any(x.compu_const == physical_value for x in self.get_scales())
121
+ if self._compu_physical_default_value is not None:
122
+ return True
123
+
124
+ scales = []
125
+ if (cpti := self.compu_internal_to_phys) is not None:
126
+ scales = cpti.compu_scales
127
+
128
+ return any(scale.compu_const.value == physical_value
129
+ for scale in scales
130
+ if scale.compu_const is not None)
77
131
 
78
132
  def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
79
- return any(
80
- self.__is_internal_in_scale(internal_value, scale) for scale in self.get_scales())
133
+ if self._compu_internal_default_value is not None:
134
+ return True
135
+
136
+ scales = []
137
+ if (citp := self.compu_internal_to_phys) is not None:
138
+ scales = citp.compu_scales
139
+
140
+ return any(scale.applies(internal_value) for scale in scales)
@@ -1,85 +1,28 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from typing import List, cast
2
+ from typing import List
3
3
  from xml.etree import ElementTree
4
4
 
5
5
  from .diagcodedtype import DiagCodedType
6
- from .exceptions import odxraise, odxrequire
6
+ from .exceptions import odxraise
7
7
  from .globals import xsi
8
8
  from .leadinglengthinfotype import LeadingLengthInfoType
9
9
  from .minmaxlengthtype import MinMaxLengthType
10
- from .odxlink import OdxDocFragment, OdxLinkRef
11
- from .odxtypes import DataType, odxstr_to_bool
10
+ from .odxlink import OdxDocFragment
12
11
  from .paramlengthinfotype import ParamLengthInfoType
13
12
  from .standardlengthtype import StandardLengthType
14
13
 
15
14
 
16
15
  def create_any_diag_coded_type_from_et(et_element: ElementTree.Element,
17
16
  doc_frags: List[OdxDocFragment]) -> DiagCodedType:
18
- base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
19
-
20
- base_data_type_str = odxrequire(et_element.get("BASE-DATA-TYPE"))
21
- try:
22
- base_data_type = DataType(base_data_type_str)
23
- except ValueError:
24
- base_data_type = cast(DataType, None)
25
- odxraise(f"Unknown base data type {base_data_type_str}")
26
-
27
- is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
28
-
29
17
  dct_type = et_element.get(f"{xsi}type")
30
- bit_length = None
31
18
  if dct_type == "LEADING-LENGTH-INFO-TYPE":
32
- bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
33
- return LeadingLengthInfoType(
34
- base_data_type=base_data_type,
35
- bit_length=bit_length,
36
- base_type_encoding=base_type_encoding,
37
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
38
- )
19
+ return LeadingLengthInfoType.from_et(et_element, doc_frags)
39
20
  elif dct_type == "MIN-MAX-LENGTH-TYPE":
40
- min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
41
- max_length = None
42
- if et_element.find("MAX-LENGTH") is not None:
43
- max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
44
- termination = odxrequire(et_element.get("TERMINATION"))
45
-
46
- return MinMaxLengthType(
47
- base_data_type=base_data_type,
48
- min_length=min_length,
49
- max_length=max_length,
50
- termination=termination,
51
- base_type_encoding=base_type_encoding,
52
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
53
- )
21
+ return MinMaxLengthType.from_et(et_element, doc_frags)
54
22
  elif dct_type == "PARAM-LENGTH-INFO-TYPE":
55
- length_key_ref = odxrequire(
56
- OdxLinkRef.from_et(et_element.find("LENGTH-KEY-REF"), doc_frags))
57
-
58
- return ParamLengthInfoType(
59
- base_data_type=base_data_type,
60
- length_key_ref=length_key_ref,
61
- base_type_encoding=base_type_encoding,
62
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
63
- )
23
+ return ParamLengthInfoType.from_et(et_element, doc_frags)
64
24
  elif dct_type == "STANDARD-LENGTH-TYPE":
65
- bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
66
- bit_mask = None
67
- if (bit_mask_str := et_element.findtext("BIT-MASK")) is not None:
68
- # The XSD uses the type xsd:hexBinary
69
- # xsd:hexBinary allows for leading/trailing whitespace, empty strings, and it only allows an even
70
- # number of hex digits, while some of the examples shown in the ODX specification exhibit an
71
- # odd number of hex digits.
72
- # This causes a validation paradox, so we try to be flexible
73
- bit_mask_str = bit_mask_str.strip()
74
- if len(bit_mask_str):
75
- bit_mask = int(bit_mask_str, 16)
76
- is_condensed_raw = odxstr_to_bool(et_element.get("CONDENSED"))
77
- return StandardLengthType(
78
- base_data_type=base_data_type,
79
- bit_length=bit_length,
80
- bit_mask=bit_mask,
81
- is_condensed_raw=is_condensed_raw,
82
- base_type_encoding=base_type_encoding,
83
- is_highlow_byte_order_raw=is_highlow_byte_order_raw,
84
- )
85
- raise NotImplementedError(f"I do not know the diag-coded-type {dct_type}")
25
+ return StandardLengthType.from_et(et_element, doc_frags)
26
+
27
+ odxraise(f"Unknown DIAG-CODED-TYPE {dct_type}", NotImplementedError)
28
+ return DiagCodedType.from_et(et_element, doc_frags)
odxtools/database.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from itertools import chain
3
+ from os import PathLike
3
4
  from pathlib import Path
4
- from typing import List, Optional
5
+ from typing import IO, Any, Dict, List, Optional, OrderedDict, Union
5
6
  from xml.etree import ElementTree
6
7
  from zipfile import ZipFile
7
8
 
@@ -9,11 +10,17 @@ from packaging.version import Version
9
10
 
10
11
  from .comparamspec import ComparamSpec
11
12
  from .comparamsubset import ComparamSubset
12
- from .diaglayer import DiagLayer
13
13
  from .diaglayercontainer import DiagLayerContainer
14
- from .exceptions import odxraise
14
+ from .diaglayers.basevariant import BaseVariant
15
+ from .diaglayers.diaglayer import DiagLayer
16
+ from .diaglayers.ecushareddata import EcuSharedData
17
+ from .diaglayers.ecuvariant import EcuVariant
18
+ from .diaglayers.functionalgroup import FunctionalGroup
19
+ from .diaglayers.protocol import Protocol
20
+ from .exceptions import odxraise, odxrequire
15
21
  from .nameditemlist import NamedItemList
16
- from .odxlink import OdxLinkDatabase
22
+ from .odxlink import OdxLinkDatabase, OdxLinkId
23
+ from .snrefcontext import SnRefContext
17
24
 
18
25
 
19
26
  class Database:
@@ -22,110 +29,150 @@ class Database:
22
29
  into a single PDX file.
23
30
  """
24
31
 
25
- def __init__(self,
26
- *,
27
- pdx_zip: Optional[ZipFile] = None,
28
- odx_d_file_name: Optional[str] = None) -> None:
29
- self.model_version = None
30
-
31
- if pdx_zip is None and odx_d_file_name is None:
32
- # create an empty database object
33
- self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
34
- self._comparam_subsets = NamedItemList[ComparamSubset]()
35
- self._comparam_specs = NamedItemList[ComparamSpec]()
36
- return
37
-
38
- if pdx_zip is not None and odx_d_file_name is not None:
39
- raise TypeError("The 'pdx_zip' and 'odx_d_file_name' parameters are mutually exclusive")
40
-
41
- documents: List[ElementTree.Element] = []
42
- if pdx_zip is not None:
43
- names = list(pdx_zip.namelist())
44
- names.sort()
45
- for zip_member in names:
46
- # The name of ODX files can end with .odx, .odx-d,
47
- # .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
48
- # .odx-v . We could test for all that, or just make
49
- # sure that the file's suffix starts with .odx
50
- if Path(zip_member).suffix.startswith(".odx"):
51
- d = pdx_zip.read(zip_member)
52
- root = ElementTree.fromstring(d)
53
- documents.append(root)
54
-
55
- elif odx_d_file_name is not None:
56
- documents.append(ElementTree.parse(odx_d_file_name).getroot())
32
+ def __init__(self) -> None:
33
+ self.model_version: Optional[Version] = None
34
+ self.auxiliary_files: OrderedDict[str, IO[bytes]] = OrderedDict()
57
35
 
36
+ # create an empty database object
37
+ self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
38
+ self._comparam_subsets = NamedItemList[ComparamSubset]()
39
+ self._comparam_specs = NamedItemList[ComparamSpec]()
40
+ self._short_name = "odx_database"
41
+
42
+ def add_pdx_file(self, pdx_file: Union[str, "PathLike[Any]", IO[bytes], ZipFile]) -> None:
43
+ """Add PDX file to database.
44
+ Either pass the path to the file, an IO with the file content or a ZipFile object.
45
+ """
46
+ if isinstance(pdx_file, ZipFile):
47
+ pdx_zip = pdx_file
48
+ else:
49
+ pdx_zip = ZipFile(pdx_file)
50
+ for zip_member in pdx_zip.namelist():
51
+ # The name of ODX files can end with .odx, .odx-d,
52
+ # .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
53
+ # .odx-v . We could test for all that, or just make
54
+ # sure that the file's suffix starts with .odx
55
+ p = Path(zip_member)
56
+ if p.suffix.lower().startswith(".odx"):
57
+ root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
58
+ self._process_xml_tree(root)
59
+ elif p.name.lower() == "index.xml":
60
+ root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
61
+ db_short_name = odxrequire(root.findtext("SHORT-NAME"))
62
+ self.short_name = db_short_name
63
+ else:
64
+ self.add_auxiliary_file(zip_member, pdx_zip.open(zip_member))
65
+
66
+ def add_odx_file(self, odx_file_name: Union[str, "PathLike[Any]"]) -> None:
67
+ self._process_xml_tree(ElementTree.parse(odx_file_name).getroot())
68
+
69
+ def add_auxiliary_file(self,
70
+ aux_file_name: Union[str, "PathLike[Any]"],
71
+ aux_file_obj: Optional[IO[bytes]] = None) -> None:
72
+ if aux_file_obj is None:
73
+ aux_file_obj = open(aux_file_name, "rb")
74
+
75
+ self.auxiliary_files[str(aux_file_name)] = aux_file_obj
76
+
77
+ def _process_xml_tree(self, root: ElementTree.Element) -> None:
58
78
  dlcs: List[DiagLayerContainer] = []
59
79
  comparam_subsets: List[ComparamSubset] = []
60
80
  comparam_specs: List[ComparamSpec] = []
61
- for root in documents:
62
- # ODX spec version
63
- model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
64
- if self.model_version is not None and self.model_version != model_version:
65
- odxraise(f"Different ODX versions used in the same file (ODX {model_version} "
66
- f"and ODX {self.model_version}")
67
- self.model_version = model_version
68
- dlc = root.find("DIAG-LAYER-CONTAINER")
69
- if dlc is not None:
70
- dlcs.append(DiagLayerContainer.from_et(dlc, []))
71
-
72
- # In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
73
- # content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
74
- # and COMPARAM-SPEC became a container for PROT-STACKS and
75
- # a PROT-STACK references a list of COMPARAM-SUBSET
76
- cp_subset = root.find("COMPARAM-SUBSET")
77
- if cp_subset is not None:
78
- comparam_subsets.append(ComparamSubset.from_et(cp_subset, []))
79
-
80
- cp_spec = root.find("COMPARAM-SPEC")
81
- if cp_spec is not None:
82
- if model_version < Version("2.2"):
83
- comparam_subsets.append(ComparamSubset.from_et(cp_spec, []))
84
- else: # odx >= 2.2
85
- comparam_specs.append(ComparamSpec.from_et(cp_spec, []))
86
-
87
- self._diag_layer_containers = NamedItemList(dlcs)
88
- self._comparam_subsets = NamedItemList(comparam_subsets)
89
- self._comparam_specs = NamedItemList(comparam_specs)
90
-
91
- self.refresh()
81
+
82
+ # ODX spec version
83
+ model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
84
+ if self.model_version is not None and self.model_version != model_version:
85
+ odxraise(f"Different ODX versions used for the same database (ODX {model_version} "
86
+ f"and ODX {self.model_version}")
87
+
88
+ self.model_version = model_version
89
+
90
+ dlc = root.find("DIAG-LAYER-CONTAINER")
91
+ if dlc is not None:
92
+ dlcs.append(DiagLayerContainer.from_et(dlc, []))
93
+
94
+ # In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
95
+ # content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
96
+ # and COMPARAM-SPEC became a container for PROT-STACKS and
97
+ # a PROT-STACK references a list of COMPARAM-SUBSET
98
+ cp_subset = root.find("COMPARAM-SUBSET")
99
+ if cp_subset is not None:
100
+ comparam_subsets.append(ComparamSubset.from_et(cp_subset, []))
101
+
102
+ cp_spec = root.find("COMPARAM-SPEC")
103
+ if cp_spec is not None:
104
+ if model_version < Version("2.2"):
105
+ comparam_subsets.append(ComparamSubset.from_et(cp_spec, []))
106
+ else: # odx >= 2.2
107
+ comparam_specs.append(ComparamSpec.from_et(cp_spec, []))
108
+
109
+ self._diag_layer_containers.extend(dlcs)
110
+ self._comparam_subsets.extend(comparam_subsets)
111
+ self._comparam_specs.extend(comparam_specs)
92
112
 
93
113
  def refresh(self) -> None:
94
114
  # Create wrapper objects
95
115
  self._diag_layers = NamedItemList(
96
116
  chain(*[dlc.diag_layers for dlc in self.diag_layer_containers]))
97
117
 
118
+ self._ecu_shared_datas = NamedItemList(
119
+ chain(*[dlc.ecu_shared_datas for dlc in self.diag_layer_containers]))
98
120
  self._protocols = NamedItemList(
99
121
  chain(*[dlc.protocols for dlc in self.diag_layer_containers]))
100
-
101
- self._ecus = NamedItemList(chain(*[dlc.ecu_variants for dlc in self.diag_layer_containers]))
122
+ self._functional_groups = NamedItemList(
123
+ chain(*[dlc.functional_groups for dlc in self.diag_layer_containers]))
124
+ self._base_variants = NamedItemList(
125
+ chain(*[dlc.base_variants for dlc in self.diag_layer_containers]))
126
+ self._ecu_variants = NamedItemList(
127
+ chain(*[dlc.ecu_variants for dlc in self.diag_layer_containers]))
102
128
 
103
129
  # Build odxlinks
104
130
  self._odxlinks = OdxLinkDatabase()
131
+ self._odxlinks.update(self._build_odxlinks())
105
132
 
133
+ # Resolve ODXLINK references
106
134
  for subset in self.comparam_subsets:
107
- self._odxlinks.update(subset._build_odxlinks())
135
+ subset._resolve_odxlinks(self._odxlinks)
108
136
 
109
137
  for spec in self.comparam_specs:
110
- self._odxlinks.update(spec._build_odxlinks())
138
+ spec._resolve_odxlinks(self._odxlinks)
111
139
 
112
140
  for dlc in self.diag_layer_containers:
113
- self._odxlinks.update(dlc._build_odxlinks())
141
+ dlc._resolve_odxlinks(self._odxlinks)
114
142
 
115
- # Resolve ODXLINK references
116
- for subset in self.comparam_subsets:
117
- subset._resolve_odxlinks(self._odxlinks)
143
+ # resolve short name references for containers which do not do
144
+ # inheritance (we can call directly call _resolve_snrefs())
145
+ context = SnRefContext()
146
+ context.database = self
118
147
 
148
+ # let the diaglayers sort out the inherited objects
149
+ for subset in self.comparam_subsets:
150
+ subset._finalize_init(self, self._odxlinks)
119
151
  for spec in self.comparam_specs:
120
- spec._resolve_odxlinks(self._odxlinks)
152
+ spec._finalize_init(self, self._odxlinks)
153
+ for dlc in self.diag_layer_containers:
154
+ dlc._finalize_init(self, self._odxlinks)
121
155
 
156
+ for subset in self.comparam_subsets:
157
+ subset._resolve_snrefs(context)
158
+ for spec in self.comparam_specs:
159
+ spec._resolve_snrefs(context)
122
160
  for dlc in self.diag_layer_containers:
123
- dlc._resolve_odxlinks(self._odxlinks)
161
+ dlc._resolve_snrefs(context)
162
+
163
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
164
+ result: Dict[OdxLinkId, Any] = {}
165
+
166
+ for subset in self.comparam_subsets:
167
+ result.update(subset._build_odxlinks())
168
+
169
+ for spec in self.comparam_specs:
170
+ result.update(spec._build_odxlinks())
124
171
 
125
- # let the diaglayers sort out the inherited objects and the
126
- # short name references
127
172
  for dlc in self.diag_layer_containers:
128
- dlc._finalize_init(self._odxlinks)
173
+ result.update(dlc._build_odxlinks())
174
+
175
+ return result
129
176
 
130
177
  @property
131
178
  def odxlinks(self) -> OdxLinkDatabase:
@@ -133,19 +180,52 @@ class Database:
133
180
  return self._odxlinks
134
181
 
135
182
  @property
136
- def protocols(self) -> NamedItemList[DiagLayer]:
137
- """
138
- All protocols defined by this database
183
+ def short_name(self) -> str:
184
+ return self._short_name
185
+
186
+ @short_name.setter
187
+ def short_name(self, value: str) -> None:
188
+ self._short_name = value
189
+
190
+ @property
191
+ def ecu_shared_datas(self) -> NamedItemList[EcuSharedData]:
192
+ """All ECU shared data layers defined by this database
193
+
194
+ ECU shared data layers act as a kind of shared library for
195
+ data that is common to multiple otherwise unrelated ECUs.
196
+
139
197
  """
198
+ return self._ecu_shared_datas
199
+
200
+ @property
201
+ def protocols(self) -> NamedItemList[Protocol]:
202
+ """All protocol layers defined by this database"""
140
203
  return self._protocols
141
204
 
142
205
  @property
143
- def ecus(self) -> NamedItemList[DiagLayer]:
144
- """ECU-variants defined in the database"""
145
- return self._ecus
206
+ def functional_groups(self) -> NamedItemList["FunctionalGroup"]:
207
+ """Functional group layers defined in the database"""
208
+ return self._functional_groups
209
+
210
+ @property
211
+ def base_variants(self) -> NamedItemList["BaseVariant"]:
212
+ """Base variants defined in the database"""
213
+ return self._base_variants
214
+
215
+ @property
216
+ def ecus(self) -> NamedItemList["EcuVariant"]:
217
+ """ECU variants defined in the database
218
+
219
+ This property is an alias for `.ecu_variants`"""
220
+ return self._ecu_variants
221
+
222
+ @property
223
+ def ecu_variants(self) -> NamedItemList["EcuVariant"]:
224
+ """ECU variants defined in the database"""
225
+ return self._ecu_variants
146
226
 
147
227
  @property
148
- def diag_layers(self) -> NamedItemList[DiagLayer]:
228
+ def diag_layers(self) -> NamedItemList["DiagLayer"]:
149
229
  """All diagnostic layers defined in the database"""
150
230
  return self._diag_layers
151
231