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,36 +1,60 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
3
+ from typing import Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .complexdop import ComplexDop
9
+ from .dataobjectproperty import DataObjectProperty
7
10
  from .decodestate import DecodeState
11
+ from .dtcdop import DtcDop
8
12
  from .encodestate import EncodeState
9
13
  from .environmentdata import EnvironmentData
10
- from .exceptions import DecodeError, EncodeError, odxrequire
14
+ from .exceptions import odxraise, odxrequire
15
+ from .nameditemlist import NamedItemList
11
16
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
- from .odxtypes import ParameterValue
17
+ from .odxtypes import DataType, ParameterValue, ParameterValueDict
18
+ from .parameters.codedconstparameter import CodedConstParameter
19
+ from .parameters.parameter import Parameter
20
+ from .parameters.parameterwithdop import ParameterWithDOP
21
+ from .parameters.physicalconstantparameter import PhysicalConstantParameter
22
+ from .parameters.valueparameter import ValueParameter
23
+ from .snrefcontext import SnRefContext
13
24
  from .utils import dataclass_fields_asdict
14
25
 
15
- if TYPE_CHECKING:
16
- from .diaglayer import DiagLayer
17
-
18
26
 
19
27
  @dataclass
20
28
  class EnvironmentDataDescription(ComplexDop):
21
- """This class represents Environment Data Description, which is a complex DOP
22
- that is used to define the interpretation of environment data."""
29
+ """This class represents environment data descriptions
30
+
31
+ An environment data description provides a list of all environment
32
+ data objects that are potentially applicable to decode a given
33
+ response. (If a given environment data object is applicable
34
+ depends on the value of the DtcDOP that is associated with it.)
35
+
36
+ """
37
+
38
+ param_snref: Optional[str]
39
+ param_snpathref: Optional[str]
23
40
 
24
41
  # in ODX 2.0.0, ENV-DATAS seems to be a mandatory
25
42
  # sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
26
43
  # present
27
- env_datas: List[EnvironmentData]
44
+ env_datas: NamedItemList[EnvironmentData]
28
45
  env_data_refs: List[OdxLinkRef]
29
- param_snref: Optional[str]
30
- param_snpathref: Optional[str]
31
46
 
32
- def __post_init__(self) -> None:
33
- self.bit_length = None
47
+ @property
48
+ def param(self) -> Parameter:
49
+ # the parameter referenced via SNREF cannot be resolved here
50
+ # because the relevant list of parameters depends on the
51
+ # concrete codec object processed, whilst an environment data
52
+ # description object can be featured in an arbitrary number of
53
+ # responses. Instead, lookup of the appropriate parameter is
54
+ # done within the encode and decode methods.
55
+ odxraise("The parameter of ENV-DATA-DESC objects cannot be resolved "
56
+ "because it depends on the context")
57
+ return cast(None, Parameter)
34
58
 
35
59
  @staticmethod
36
60
  def from_et(et_element: ElementTree.Element,
@@ -44,16 +68,20 @@ class EnvironmentDataDescription(ComplexDop):
44
68
  param_snpathref = None
45
69
  if (param_snpathref_elem := et_element.find("PARAM-SNPATHREF")) is not None:
46
70
  param_snpathref = odxrequire(param_snpathref_elem.get("SHORT-NAME-PATH"))
71
+
72
+ # ODX 2.0 mandates ENV-DATA-DESC to contain a list of
73
+ # ENV-DATAS and no ENV-DATA-REFS while for ODX 2.2 the
74
+ # situation is reversed. This means that we will create one
75
+ # empty and one non-empty list here. (Which is which depends
76
+ # on the version of the standard used by the file.)
47
77
  env_data_refs = [
48
78
  odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
49
79
  for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
50
80
  ]
51
-
52
- # ODX 2.0.0 says ENV-DATA-DESC could contain a list of ENV-DATAS
53
- env_datas = [
81
+ env_datas = NamedItemList([
54
82
  EnvironmentData.from_et(env_data_elem, doc_frags)
55
83
  for env_data_elem in et_element.iterfind("ENV-DATAS/ENV-DATA")
56
- ]
84
+ ])
57
85
 
58
86
  return EnvironmentDataDescription(
59
87
  param_snref=param_snref,
@@ -65,8 +93,9 @@ class EnvironmentDataDescription(ComplexDop):
65
93
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
66
94
  odxlinks = {self.odx_id: self}
67
95
 
68
- for ed in self.env_datas:
69
- odxlinks.update(ed._build_odxlinks())
96
+ if not self.env_data_refs:
97
+ for ed in self.env_datas:
98
+ odxlinks.update(ed._build_odxlinks())
70
99
 
71
100
  return odxlinks
72
101
 
@@ -74,33 +103,166 @@ class EnvironmentDataDescription(ComplexDop):
74
103
  # ODX 2.0 specifies environment data objects here, ODX 2.2
75
104
  # uses references
76
105
  if self.env_data_refs:
77
- self.env_datas = [odxlinks.resolve(x) for x in self.env_data_refs]
106
+ self.env_datas = NamedItemList([odxlinks.resolve(x) for x in self.env_data_refs])
78
107
  else:
79
108
  for ed in self.env_datas:
80
109
  ed._resolve_odxlinks(odxlinks)
81
110
 
82
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
111
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
83
112
  # ODX 2.0 specifies environment data objects here, ODX 2.2
84
113
  # uses references
85
114
  if self.env_data_refs:
86
115
  for ed in self.env_datas:
87
- ed._resolve_snrefs(diag_layer)
88
-
89
- def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
90
- bit_position: int) -> bytes:
91
- """Convert the physical value into bytes.
116
+ ed._resolve_snrefs(context)
92
117
 
93
- Since environmental data is supposed to never appear on the
94
- wire, this method just raises an EncodeError exception.
118
+ @override
119
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
120
+ encode_state: EncodeState) -> None:
121
+ """Convert a physical value into bytes and emplace them into a PDU.
95
122
  """
96
- raise EncodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
97
123
 
98
- def convert_bytes_to_physical(self,
99
- decode_state: DecodeState,
100
- bit_position: int = 0) -> Tuple[ParameterValue, int]:
101
- """Extract the bytes from the PDU and convert them to the physical value.
124
+ # retrieve the DTC as a numerical value from the referenced
125
+ # parameter (which must be located somewhere before the
126
+ # parameter using the environment data description)
127
+ if self.param_snref is None:
128
+ odxraise("Specifying the DTC parameter for environment data "
129
+ "descriptions via SNPATHREF is not supported yet")
130
+ return None
131
+
132
+ numerical_dtc_value: Optional[ParameterValue] = None
133
+ for prev_param, prev_param_value in reversed(encode_state.journal):
134
+ if prev_param.short_name == self.param_snref:
135
+ numerical_dtc_value = self._get_numerical_dtc_from_parameter(
136
+ prev_param, prev_param_value)
137
+ break
138
+
139
+ if numerical_dtc_value is None:
140
+ odxraise("Environment data description parameters are only allowed following "
141
+ "the referenced parameter.")
142
+ return
143
+
144
+ # deal with the "all value" environment data. This holds
145
+ # parameters that are common to all DTCs. Be aware that the
146
+ # specification mandates that there is at most one such
147
+ # environment data object
148
+ for env_data in self.env_datas:
149
+ if env_data.all_value:
150
+ tmp = encode_state.allow_unknown_parameters
151
+ encode_state.allow_unknown_parameters = True
152
+ env_data.encode_into_pdu(physical_value, encode_state)
153
+ encode_state.allow_unknown_parameters = tmp
154
+ break
155
+
156
+ # find the environment data corresponding to the given trouble
157
+ # code
158
+ for env_data in self.env_datas:
159
+ if numerical_dtc_value in env_data.dtc_values:
160
+ tmp = encode_state.allow_unknown_parameters
161
+ encode_state.allow_unknown_parameters = True
162
+ env_data.encode_into_pdu(physical_value, encode_state)
163
+ encode_state.allow_unknown_parameters = tmp
164
+ break
102
165
 
103
- Since environmental data is supposed to never appear on the
104
- wire, this method just raises an DecodeError exception.
166
+ @override
167
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
168
+ """Extract the bytes from a PDU and convert them to a physical value.
105
169
  """
106
- raise DecodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
170
+
171
+ # retrieve the DTC as a numerical value from the referenced
172
+ # parameter (which must be located somewhere before the
173
+ # parameter using the environment data description)
174
+ if self.param_snref is None:
175
+ odxraise("Specifying the DTC parameter for environment data "
176
+ "descriptions via SNPATHREF is not supported yet")
177
+ return None
178
+
179
+ numerical_dtc_value: Optional[ParameterValue] = None
180
+ for prev_param, prev_param_value in reversed(decode_state.journal):
181
+ if prev_param.short_name == self.param_snref:
182
+ numerical_dtc_value = self._get_numerical_dtc_from_parameter(
183
+ prev_param, prev_param_value)
184
+ break
185
+
186
+ if numerical_dtc_value is None:
187
+ odxraise("Environment data description parameters are only allowed following "
188
+ "the referenced parameter.")
189
+ return
190
+
191
+ result: ParameterValueDict = {}
192
+
193
+ # deal with the "all value" environment data. This holds
194
+ # parameters that are common to all DTCs. Be aware that the
195
+ # specification mandates that there is at most one such
196
+ # environment data object
197
+ for env_data in self.env_datas:
198
+ if env_data.all_value:
199
+ tmp = env_data.decode_from_pdu(decode_state)
200
+ if not isinstance(tmp, dict):
201
+ odxraise()
202
+ result.update(tmp)
203
+ break
204
+
205
+ # find the environment data corresponding to the given trouble
206
+ # code
207
+ for env_data in self.env_datas:
208
+ if numerical_dtc_value in env_data.dtc_values:
209
+ tmp = env_data.decode_from_pdu(decode_state)
210
+ if not isinstance(tmp, dict):
211
+ odxraise()
212
+ result.update(tmp)
213
+ break
214
+
215
+ return result
216
+
217
+ def _get_numerical_dtc_from_parameter(self, param: Parameter,
218
+ param_value: Optional[ParameterValue]) -> int:
219
+ if isinstance(param, ParameterWithDOP):
220
+ dop = param.dop
221
+ if not isinstance(dop, (DataObjectProperty, DtcDop)):
222
+ odxraise(f"The DOP of the parameter referenced by environment data descriptions "
223
+ f"must use either be DataObjectProperty or a DtcDop (encountered "
224
+ f"{type(param).__name__} for parameter '{self.param.short_name}' "
225
+ f"of ENV-DATA-DESC '{self.short_name}')")
226
+ return 0
227
+
228
+ if dop.diag_coded_type.base_data_type != DataType.A_UINT32:
229
+ odxraise(f"The data type used by the DOP of the parameter referenced "
230
+ f"by environment data descriptions must be A_UINT32 "
231
+ f"(encountered '{dop.diag_coded_type.base_data_type.value}')")
232
+
233
+ if param_value is None:
234
+ if isinstance(param, ValueParameter):
235
+ param_value = param.physical_default_value
236
+ elif isinstance(param, PhysicalConstantParameter):
237
+ param_value = param.physical_constant_value
238
+ else:
239
+ odxraise() # make mypy happy...
240
+ return
241
+
242
+ if isinstance(dop, DtcDop):
243
+ return dop.convert_to_numerical_trouble_code(odxrequire(param_value))
244
+ elif isinstance(dop, DataObjectProperty):
245
+ return int(dop.compu_method.convert_physical_to_internal(
246
+ param_value)) # type: ignore[arg-type]
247
+
248
+ odxraise() # not reachable...
249
+
250
+ elif isinstance(param, CodedConstParameter):
251
+ if param.diag_coded_type.base_data_type != DataType.A_UINT32:
252
+ odxraise(f"The data type used by the parameter referenced "
253
+ f"by environment data descriptions must be A_UINT32 "
254
+ f"(encountered '{param.diag_coded_type.base_data_type.value}')")
255
+
256
+ return param.coded_value
257
+
258
+ if not isinstance(param.coded_value, int):
259
+ odxraise()
260
+
261
+ return param.coded_value
262
+
263
+ else:
264
+ odxraise(f"The parameter referenced by environment data descriptions "
265
+ f"must be a parameter that either specifies a DOP or a constant "
266
+ f"(encountered {type(param).__name__} for reference '{self.param_snref}' of "
267
+ f"ENV-DATA-DESC '{self.short_name}')")
268
+ return 0
odxtools/exceptions.py CHANGED
@@ -9,13 +9,22 @@ class OdxError(Exception):
9
9
 
10
10
 
11
11
  class EncodeError(Warning, OdxError):
12
- """Encoding of a message to raw data failed."""
12
+ """Encoding of a message to raw data failed"""
13
13
 
14
14
 
15
15
  class DecodeError(Warning, OdxError):
16
16
  """Decoding raw data failed."""
17
17
 
18
18
 
19
+ class DecodeMismatch(DecodeError):
20
+ """Decoding failed because some parameters exhibit an incorrect value
21
+
22
+ This is can happen if NRC-CONST or environment data descriptions
23
+ are present.
24
+
25
+ """
26
+
27
+
19
28
  class OdxWarning(Warning):
20
29
  """Any warning that happens during interacting with diagnostic objects."""
21
30
 
@@ -40,7 +49,7 @@ def odxraise(message: Optional[str] = None, error_type: Type[Exception] = OdxErr
40
49
  else:
41
50
  raise error_type(message)
42
51
  elif message is not None:
43
- logger.warn(message)
52
+ logger.warning(message)
44
53
 
45
54
 
46
55
  def odxassert(condition: bool,
odxtools/field.py CHANGED
@@ -1,18 +1,17 @@
1
+ # SPDX-License-Identifier: MIT
1
2
  from dataclasses import dataclass
2
- from typing import TYPE_CHECKING, List, Optional
3
+ from typing import List, Optional
3
4
  from xml.etree import ElementTree
4
5
 
5
6
  from .basicstructure import BasicStructure
6
7
  from .complexdop import ComplexDop
7
8
  from .environmentdatadescription import EnvironmentDataDescription
8
9
  from .exceptions import odxassert, odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
10
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef, resolve_snref
10
11
  from .odxtypes import odxstr_to_bool
12
+ from .snrefcontext import SnRefContext
11
13
  from .utils import dataclass_fields_asdict
12
14
 
13
- if TYPE_CHECKING:
14
- from .diaglayer import DiagLayer
15
-
16
15
 
17
16
  @dataclass
18
17
  class Field(ComplexDop):
@@ -80,12 +79,13 @@ class Field(ComplexDop):
80
79
  self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref,
81
80
  EnvironmentDataDescription)
82
81
 
83
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
82
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
84
83
  """Recursively resolve any short-name references"""
84
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
85
+
85
86
  if self.structure_snref is not None:
86
- structures = diag_layer.diag_data_dictionary_spec.structures
87
- self._structure = odxrequire(structures.get(self.structure_snref))
87
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure)
88
88
 
89
89
  if self.env_data_desc_snref is not None:
90
- env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
91
- self._env_data_desc = odxrequire(env_data_descs.get(self.env_data_desc_snref))
90
+ self._env_data_desc = resolve_snref(self.env_data_desc_snref, ddds.env_data_descs,
91
+ EnvironmentDataDescription)
@@ -1,15 +1,13 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
7
7
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
8
+ from .snrefcontext import SnRefContext
8
9
  from .utils import dataclass_fields_asdict
9
10
 
10
- if TYPE_CHECKING:
11
- from .diaglayer import DiagLayer
12
-
13
11
 
14
12
  @dataclass
15
13
  class FunctionalClass(IdentifiableElement):
@@ -31,5 +29,5 @@ class FunctionalClass(IdentifiableElement):
31
29
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
32
30
  pass
33
31
 
34
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
35
33
  pass
odxtools/inputparam.py CHANGED
@@ -1,19 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from deprecation import deprecated
7
-
8
6
  from .dopbase import DopBase
9
7
  from .element import NamedElement
10
8
  from .exceptions import odxrequire
11
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from .snrefcontext import SnRefContext
12
11
  from .utils import dataclass_fields_asdict
13
12
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
13
 
18
14
  @dataclass
19
15
  class InputParam(NamedElement):
@@ -44,15 +40,10 @@ class InputParam(NamedElement):
44
40
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
45
41
  self._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
46
42
 
47
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
43
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
48
44
  pass
49
45
 
50
46
  @property
51
47
  def dop_base(self) -> DopBase:
52
48
  """The data object property describing this parameter."""
53
49
  return self._dop_base
54
-
55
- @property
56
- @deprecated(details="use .dop_base")
57
- def dop(self) -> DopBase:
58
- return self._dop_base
@@ -4,6 +4,7 @@ from typing import List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .compumethods.limit import Limit
7
+ from .odxlink import OdxDocFragment
7
8
  from .odxtypes import DataType
8
9
  from .scaleconstr import ScaleConstr
9
10
 
@@ -19,16 +20,24 @@ class InternalConstr:
19
20
  upper_limit: Optional[Limit]
20
21
  scale_constrs: List[ScaleConstr]
21
22
 
23
+ value_type: DataType
24
+
22
25
  @staticmethod
23
- def from_et(et_element: ElementTree.Element, internal_type: DataType) -> "InternalConstr":
26
+ def constr_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
27
+ value_type: DataType) -> "InternalConstr":
24
28
 
25
- lower_limit = Limit.from_et(et_element.find("LOWER-LIMIT"), internal_type=internal_type)
26
- upper_limit = Limit.from_et(et_element.find("UPPER-LIMIT"), internal_type=internal_type)
29
+ lower_limit = Limit.limit_from_et(
30
+ et_element.find("LOWER-LIMIT"), doc_frags, value_type=value_type)
31
+ upper_limit = Limit.limit_from_et(
32
+ et_element.find("UPPER-LIMIT"), doc_frags, value_type=value_type)
27
33
 
28
34
  scale_constrs = [
29
- ScaleConstr.from_et(sc_el, internal_type)
35
+ ScaleConstr.scale_constr_from_et(sc_el, doc_frags, value_type=value_type)
30
36
  for sc_el in et_element.iterfind("SCALE-CONSTRS/SCALE-CONSTR")
31
37
  ]
32
38
 
33
39
  return InternalConstr(
34
- lower_limit=lower_limit, upper_limit=upper_limit, scale_constrs=scale_constrs)
40
+ lower_limit=lower_limit,
41
+ upper_limit=upper_limit,
42
+ scale_constrs=scale_constrs,
43
+ value_type=value_type)
@@ -55,10 +55,13 @@ class IsoTpStateMachine:
55
55
  return # unknown CAN ID
56
56
 
57
57
  # decode the isotp segment
58
- (frame_type,) = bitstruct.unpack("u4", data)
58
+ frame_type, _ = bitstruct.unpack("u4u4", data)
59
+ assert isinstance(frame_type, int)
60
+
59
61
  telegram_len = None
60
62
  if frame_type == IsoTp.FRAME_TYPE_SINGLE:
61
63
  frame_type, telegram_len = bitstruct.unpack("u4u4", data)
64
+ assert isinstance(telegram_len, int)
62
65
 
63
66
  self.on_single_frame(telegram_idx, data[1:1 + telegram_len])
64
67
  self.on_telegram_complete(telegram_idx, data[1:1 + telegram_len])
@@ -67,6 +70,7 @@ class IsoTpStateMachine:
67
70
 
68
71
  elif frame_type == IsoTp.FRAME_TYPE_FIRST:
69
72
  frame_type, telegram_len = bitstruct.unpack("u4u12", data)
73
+ assert isinstance(telegram_len, int)
70
74
 
71
75
  self._telegram_specified_len[telegram_idx] = telegram_len
72
76
  self._telegram_data[telegram_idx] = bytearray(data[2:])
@@ -76,11 +80,13 @@ class IsoTpStateMachine:
76
80
 
77
81
  elif frame_type == IsoTp.FRAME_TYPE_CONSECUTIVE:
78
82
  frame_type, rx_segment_idx = bitstruct.unpack("u4u4", data)
83
+ assert isinstance(rx_segment_idx, int)
79
84
 
80
85
  expected_segment_idx = (self._telegram_last_rx_fragment_idx[telegram_idx] + 1) % 16
81
86
  telegram_data = self._telegram_data[telegram_idx]
82
87
  assert isinstance(telegram_data, bytearray)
83
88
 
89
+ n = -1
84
90
  if expected_segment_idx == rx_segment_idx:
85
91
  self._telegram_last_rx_fragment_idx[telegram_idx] = rx_segment_idx
86
92
  telegram_data += data[1:]
@@ -102,6 +108,8 @@ class IsoTpStateMachine:
102
108
 
103
109
  elif frame_type == IsoTp.FRAME_TYPE_FLOW_CONTROL:
104
110
  frame_type, flow_control_flag = bitstruct.unpack("u4u4", data)
111
+ assert isinstance(flow_control_flag, int)
112
+
105
113
  self.on_flow_control_frame(telegram_idx, flow_control_flag)
106
114
  else:
107
115
  self.on_frame_type_error(telegram_idx, frame_type)
@@ -139,7 +147,7 @@ class IsoTpStateMachine:
139
147
  return
140
148
 
141
149
  if m := self.can_normal_frame_re.match(cur_line.strip()):
142
- #frame_interface = m.group(1)
150
+ # frame_interface = m.group(1)
143
151
  frame_id = int(m.group(2), 16)
144
152
 
145
153
  frame_data_formatted = m.group(3).strip()
@@ -151,7 +159,7 @@ class IsoTpStateMachine:
151
159
  elif (m := self.can_log_frame_re.match(
152
160
  cur_line.strip())) or (m := self.can_fd_log_frame_re.match(
153
161
  cur_line.strip())):
154
- #frame_interface = m.group(2)
162
+ # frame_interface = m.group(2)
155
163
  frame_id = int(m.group(2), 16)
156
164
 
157
165
  frame_data_formatted = m.group(3).strip()
@@ -257,7 +265,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
257
265
 
258
266
  def on_single_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
259
267
  # send ACK
260
- #rx_id = self.can_rx_id(telegram_idx)
268
+ # rx_id = self.can_rx_id(telegram_idx)
261
269
  tx_id = self.can_tx_id(telegram_idx)
262
270
  block_size = 0xFF
263
271
  min_separation_time = 0 # ms
@@ -276,7 +284,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
276
284
 
277
285
  def on_first_frame(self, telegram_idx: int, frame_payload: bytes) -> None:
278
286
  # send ACK
279
- #rx_id = self.can_rx_id(telegram_idx)
287
+ # rx_id = self.can_rx_id(telegram_idx)
280
288
  tx_id = self.can_tx_id(telegram_idx)
281
289
  block_size = 0xFF # default value, can be overwritten later
282
290
  min_separation_time = 0 # ms
@@ -308,7 +316,7 @@ class IsoTpActiveDecoder(IsoTpStateMachine):
308
316
  # send new ACK if necessary
309
317
  block_size = self._block_size[telegram_idx]
310
318
  if block_size is not None and num_received >= block_size:
311
- #rx_id = self.can_rx_id(telegram_idx)
319
+ # rx_id = self.can_rx_id(telegram_idx)
312
320
  tx_id = self.can_tx_id(telegram_idx)
313
321
  min_separation_time = 0 # ms
314
322
  fc_payload = bitstruct.pack(
@@ -1,12 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Optional
3
+ from typing import List
4
+ from xml.etree import ElementTree
5
+
6
+ from typing_extensions import override
4
7
 
5
8
  from .decodestate import DecodeState
6
9
  from .diagcodedtype import DctType, DiagCodedType
7
10
  from .encodestate import EncodeState
8
- from .exceptions import odxassert, odxraise
11
+ from .exceptions import EncodeError, odxassert, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment
9
13
  from .odxtypes import AtomicOdxType, DataType
14
+ from .utils import dataclass_fields_asdict
10
15
 
11
16
 
12
17
  @dataclass
@@ -18,6 +23,16 @@ class LeadingLengthInfoType(DiagCodedType):
18
23
  #: object.
19
24
  bit_length: int
20
25
 
26
+ @staticmethod
27
+ @override
28
+ def from_et(et_element: ElementTree.Element,
29
+ doc_frags: List[OdxDocFragment]) -> "LeadingLengthInfoType":
30
+ kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
31
+
32
+ bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
33
+
34
+ return LeadingLengthInfoType(bit_length=bit_length, **kwargs)
35
+
21
36
  def __post_init__(self) -> None:
22
37
  odxassert(self.bit_length > 0,
23
38
  "A Leading length info type with bit length == 0 does not make sense.")
@@ -35,36 +50,40 @@ class LeadingLengthInfoType(DiagCodedType):
35
50
  def dct_type(self) -> DctType:
36
51
  return "LEADING-LENGTH-INFO-TYPE"
37
52
 
38
- def get_static_bit_length(self) -> Optional[int]:
39
- # note that self.bit_length is just the length of the length
40
- # specifier field. This is then followed by the same number of
41
- # bytes as the value of this field, i.e., the length of this
42
- # DCT is dynamic!
43
- return None
53
+ @override
54
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
44
55
 
45
- def convert_internal_to_bytes(self, internal_value: Any, encode_state: EncodeState,
46
- bit_position: int) -> bytes:
56
+ if not isinstance(internal_value, (str, bytes)):
57
+ odxraise(
58
+ f"LEADING-LENGTH-INFO types can only be used for strings and byte fields, "
59
+ f"not {type(internal_value).__name__}", EncodeError)
60
+ return
47
61
 
48
62
  byte_length = self._minimal_byte_length_of(internal_value)
49
63
 
50
- length_bytes = self._encode_internal_value(
51
- byte_length,
52
- bit_position=bit_position,
64
+ used_mask = None
65
+ bit_pos = encode_state.cursor_bit_position
66
+ if encode_state.cursor_bit_position != 0 or (bit_pos + self.bit_length) % 8 != 0:
67
+ used_mask = (1 << self.bit_length) - 1
68
+ used_mask <<= bit_pos
69
+
70
+ encode_state.emplace_atomic_value(
71
+ internal_value=byte_length,
72
+ used_mask=None,
53
73
  bit_length=self.bit_length,
54
74
  base_data_type=DataType.A_UINT32,
55
75
  is_highlow_byte_order=self.is_highlow_byte_order,
56
76
  )
57
77
 
58
- value_bytes = self._encode_internal_value(
59
- internal_value,
60
- bit_position=0,
78
+ encode_state.emplace_atomic_value(
79
+ internal_value=internal_value,
80
+ used_mask=None,
61
81
  bit_length=8 * byte_length,
62
82
  base_data_type=self.base_data_type,
63
83
  is_highlow_byte_order=self.is_highlow_byte_order,
64
84
  )
65
85
 
66
- return length_bytes + value_bytes
67
-
86
+ @override
68
87
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
69
88
 
70
89
  # Extract length of the parameter value