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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import warnings
3
2
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
5
4
  from xml.etree import ElementTree
6
5
 
7
6
  from typing_extensions import override
@@ -10,25 +9,29 @@ from ..createanydiagcodedtype import create_any_diag_coded_type_from_et
10
9
  from ..decodestate import DecodeState
11
10
  from ..diagcodedtype import DiagCodedType
12
11
  from ..encodestate import EncodeState
13
- from ..exceptions import DecodeError, EncodeError, odxrequire
14
- from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
- from ..odxtypes import AtomicOdxType, DataType
12
+ from ..exceptions import DecodeMismatch, EncodeError, odxraise, odxrequire
13
+ from ..odxlink import OdxDocFragment, OdxLinkId
14
+ from ..odxtypes import AtomicOdxType, DataType, ParameterValue
16
15
  from ..utils import dataclass_fields_asdict
17
16
  from .parameter import Parameter, ParameterType
18
17
 
19
- if TYPE_CHECKING:
20
- from ..diaglayer import DiagLayer
21
-
22
18
 
23
19
  @dataclass
24
20
  class NrcConstParameter(Parameter):
25
- """A param of type NRC-CONST defines a set of values to be matched.
26
-
27
- An NRC-CONST can only be used in a negative response.
28
- Its encoding behaviour is similar to a VALUE parameter with a TEXTTABLE.
29
- However, an NRC-CONST is used for matching a response (similar to a CODED-CONST).
21
+ """A parameter of type NRC-CONST defines a set of values to be
22
+ matched for a negative response object to apply
23
+
24
+ The behaviour of NRC-CONST parameters is similar to CODED-CONST
25
+ parameters in that they allow to specify which coding objects
26
+ apply to a binary string, but in contrast to CODED-CONST
27
+ parameters they allow to specify multiple values. Thus, the value
28
+ of a CODED-CONST parameter is usually set using an overlapping
29
+ VALUE parameter. Since NRC-CONST parameters can only be specified
30
+ for negative responses, they can thus be regarded as a multiplexer
31
+ mechanism that is specific to negative responses.
30
32
 
31
33
  See ASAM MCD-2 D (ODX), p. 77-79.
34
+
32
35
  """
33
36
 
34
37
  diag_coded_type: DiagCodedType
@@ -64,14 +67,6 @@ class NrcConstParameter(Parameter):
64
67
 
65
68
  return result
66
69
 
67
- @override
68
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
69
- super()._resolve_odxlinks(odxlinks)
70
-
71
- @override
72
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
73
- super()._resolve_snrefs(diag_layer)
74
-
75
70
  @override
76
71
  def get_static_bit_length(self) -> Optional[int]:
77
72
  return self.diag_coded_type.get_static_bit_length()
@@ -91,38 +86,42 @@ class NrcConstParameter(Parameter):
91
86
  return False
92
87
 
93
88
  @override
94
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
95
- if self.short_name in encode_state.parameter_values:
96
- if encode_state.parameter_values[self.short_name] not in self.coded_values:
97
- raise EncodeError(f"The parameter '{self.short_name}' must have"
98
- f" one of the constant values {self.coded_values}")
99
- else:
100
- coded_value = encode_state.parameter_values[self.short_name]
101
- else:
102
- # If the user does not select one, just select any.
103
- # I think it does not matter ...
104
- coded_value = self.coded_values[0]
105
-
106
- bit_position_int = self.bit_position if self.bit_position is not None else 0
107
- return self.diag_coded_type.convert_internal_to_bytes(
108
- coded_value, encode_state, bit_position=bit_position_int)
89
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
90
+ encode_state: EncodeState) -> None:
91
+ # NRC-CONST parameters are not encoding any value on its
92
+ # own. instead, it is supposed to overlap with a value
93
+ # parameter.
94
+ if physical_value is not None:
95
+ odxraise("The value of NRC-CONST parameters cannot be set directly!", EncodeError)
96
+
97
+ # TODO (?): extract the parameter and check if it is one of
98
+ # the values of self.coded_values. if not, throw an
99
+ # EncodeMismatch exception! This is probably a bad idea
100
+ # because the parameter which determines the value of the
101
+ # NRC-CONST might possibly be specified after the NRC-CONST.
102
+
103
+ # move the cursor forward by the size of the parameter
104
+ bit_pos = encode_state.cursor_bit_position
105
+ bit_len = self.diag_coded_type.get_static_bit_length()
106
+
107
+ if bit_len is None:
108
+ odxraise("The diag coded type of NRC-CONST parameters must "
109
+ "exhibit a static size")
110
+ return
111
+
112
+ encode_state.cursor_byte_position += (bit_pos + bit_len + 7) // 8
113
+ encode_state.cursor_bit_position = 0
114
+
115
+ encode_state.emplace_bytes(b'', self.short_name)
109
116
 
110
117
  @override
111
118
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
112
- # Extract coded values
119
+ # Extract coded value
113
120
  coded_value = self.diag_coded_type.decode_from_pdu(decode_state)
114
121
 
115
122
  # Check if the coded value in the message is correct.
116
123
  if coded_value not in self.coded_values:
117
- warnings.warn(
118
- f"Coded constant parameter does not match! "
119
- f"The parameter {self.short_name} expected a coded "
120
- f"value in {str(self.coded_values)} but got {str(coded_value)} "
121
- f"at byte position {decode_state.cursor_byte_position} "
122
- f"in coded message {decode_state.coded_message.hex()}.",
123
- DecodeError,
124
- stacklevel=1,
125
- )
124
+ raise DecodeMismatch(f"NRC-CONST parameter {self.short_name} does not apply")
126
125
 
127
126
  return coded_value
128
127
 
@@ -1,22 +1,19 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional
3
+ from typing import Any, Dict, List, Literal, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import final, override
7
7
 
8
- from ..createsdgs import create_sdgs_from_et
9
8
  from ..decodestate import DecodeState
10
9
  from ..element import NamedElement
11
10
  from ..encodestate import EncodeState
12
11
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
13
12
  from ..odxtypes import ParameterValue
13
+ from ..snrefcontext import SnRefContext
14
14
  from ..specialdatagroup import SpecialDataGroup
15
15
  from ..utils import dataclass_fields_asdict
16
16
 
17
- if TYPE_CHECKING:
18
- from ..diaglayer import DiagLayer
19
-
20
17
  ParameterType = Literal[
21
18
  "CODED-CONST",
22
19
  "DYNAMIC",
@@ -36,13 +33,16 @@ ParameterType = Literal[
36
33
  @dataclass
37
34
  class Parameter(NamedElement):
38
35
  """This class corresponds to POSITIONABLE-PARAM in the ODX
39
- specification.
36
+ specification
40
37
 
41
- Be aware that, even though the ODX specification seems to make the
42
- distinction of "positionable" and "normal" parameters, it does not
43
- define any non-positionable parameter types.
38
+ All parameter classes must adhere to the `Codec` type protocol, so
39
+ `isinstance(param, Codec)` ought to be true. Be aware that, even
40
+ though the ODX specification seems to make the distinction of
41
+ "positionable" and "normal" parameters, it does not define any
42
+ non-positionable parameter types.
44
43
 
45
44
  """
45
+ oid: Optional[str]
46
46
  byte_position: Optional[int]
47
47
  bit_position: Optional[int]
48
48
  semantic: Optional[str]
@@ -54,8 +54,11 @@ class Parameter(NamedElement):
54
54
 
55
55
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
56
56
 
57
+ oid = et_element.get("OID")
57
58
  semantic = et_element.get("SEMANTIC")
58
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
59
+ sdgs = [
60
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
61
+ ]
59
62
 
60
63
  byte_position_str = et_element.findtext("BYTE-POSITION")
61
64
  bit_position_str = et_element.findtext("BIT-POSITION")
@@ -64,6 +67,7 @@ class Parameter(NamedElement):
64
67
  bit_position = int(bit_position_str) if bit_position_str is not None else None
65
68
 
66
69
  return Parameter(
70
+ oid=oid,
67
71
  byte_position=byte_position,
68
72
  bit_position=bit_position,
69
73
  semantic=semantic,
@@ -82,9 +86,9 @@ class Parameter(NamedElement):
82
86
  for sdg in self.sdgs:
83
87
  sdg._resolve_odxlinks(odxlinks)
84
88
 
85
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
89
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
86
90
  for sdg in self.sdgs:
87
- sdg._resolve_snrefs(diag_layer)
91
+ sdg._resolve_snrefs(context)
88
92
 
89
93
  @property
90
94
  def parameter_type(self) -> ParameterType:
@@ -117,16 +121,47 @@ class Parameter(NamedElement):
117
121
  """
118
122
  raise NotImplementedError(".is_settable is not implemented by the concrete parameter class")
119
123
 
120
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
121
- """Get the coded value of the parameter given the encode state.
122
- Note that this method is called by `encode_into_pdu`.
124
+ @final
125
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
126
+ encode_state: EncodeState) -> None:
127
+ """Convert a physical value into its encoded form and place it
128
+ into the PDU
129
+
130
+ Also, adapt the `encode_state` so that it points to where the
131
+ next parameter is located (if the next parameter does not
132
+ explicitly specify a position)
133
+
123
134
  """
135
+
136
+ if self.byte_position is not None:
137
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
138
+
139
+ encode_state.cursor_bit_position = self.bit_position or 0
140
+
141
+ self._encode_positioned_into_pdu(physical_value, encode_state)
142
+
143
+ encode_state.cursor_bit_position = 0
144
+
145
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
146
+ encode_state: EncodeState) -> None:
147
+ """Method which actually encodes the parameter
148
+
149
+ Its location is managed by `Parameter`."""
124
150
  raise NotImplementedError(
125
- ".get_coded_value_as_bytes() is not implemented by the concrete parameter class")
151
+ f"Required method '_encode_positioned_into_pdu()' not implemented by "
152
+ f"child class {type(self).__name__}")
126
153
 
127
154
  @final
128
155
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
129
- orig_cursor = decode_state.cursor_byte_position
156
+ """Retrieve the raw data for the parameter from the PDU and
157
+ convert it to its physical interpretation
158
+
159
+ Also, adapt the `encode_state` so that it points to where the
160
+ next parameter is located (if the next parameter does not
161
+ explicitly specify a position)
162
+
163
+ """
164
+
130
165
  if self.byte_position is not None:
131
166
  decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
132
167
 
@@ -134,7 +169,6 @@ class Parameter(NamedElement):
134
169
 
135
170
  result = self._decode_positioned_from_pdu(decode_state)
136
171
 
137
- decode_state.cursor_byte_position = max(decode_state.cursor_byte_position, orig_cursor)
138
172
  decode_state.cursor_bit_position = 0
139
173
 
140
174
  return result
@@ -144,41 +178,5 @@ class Parameter(NamedElement):
144
178
 
145
179
  Its location is managed by `Parameter`."""
146
180
  raise NotImplementedError(
147
- "Required method '_decode_positioned_from_pdu()' not implemented by child class")
148
-
149
- def encode_into_pdu(self, encode_state: EncodeState) -> bytes:
150
- """Encode the value of a parameter into a binary blob and return it
151
-
152
- If the byte position of the parameter is not defined,
153
- the byte code is appended to the blob.
154
-
155
- Technical note for subclasses: The default implementation
156
- tries to compute the coded value via
157
- `self.get_coded_value_as_bytes(encoded_state)` and inserts it
158
- into the PDU. Thus it usually suffices to overwrite
159
- `get_coded_value_as_bytes()` instead of `encode_into_pdu()`.
160
-
161
- Parameters:
162
- ----------
163
- encode_state: EncodeState, i.e. a named tuple with attributes
164
- * coded_message: bytes, the message encoded so far
165
- * parameter_values: List[ParameterValuePairs]
166
- * triggering_coded_request: bytes
167
-
168
- Returns:
169
- -------
170
- bytes
171
- the message's blob after adding the encoded parameter into it
172
-
173
- """
174
- msg_blob = encode_state.coded_message
175
- param_blob = self.get_coded_value_as_bytes(encode_state)
176
-
177
- if self.byte_position is not None:
178
- byte_position = self.byte_position
179
- else:
180
- byte_position = len(msg_blob)
181
-
182
- encode_state.emplace_atomic_value(param_blob, self.short_name, byte_position)
183
-
184
- return encode_state.coded_message
181
+ f"Required method '_decode_positioned_from_pdu()' not implemented by "
182
+ f"child class {type(self).__name__}")
@@ -1,6 +1,6 @@
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, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -11,15 +11,13 @@ from ..dopbase import DopBase
11
11
  from ..dtcdop import DtcDop
12
12
  from ..encodestate import EncodeState
13
13
  from ..exceptions import odxassert, odxrequire
14
- from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
15
- from ..odxtypes import ParameterValue
14
+ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
15
+ from ..odxtypes import AtomicOdxType, ParameterValue
16
16
  from ..physicaltype import PhysicalType
17
+ from ..snrefcontext import SnRefContext
17
18
  from ..utils import dataclass_fields_asdict
18
19
  from .parameter import Parameter
19
20
 
20
- if TYPE_CHECKING:
21
- from ..diaglayer import DiagLayer
22
-
23
21
 
24
22
  @dataclass
25
23
  class ParameterWithDOP(Parameter):
@@ -43,7 +41,7 @@ class ParameterWithDOP(Parameter):
43
41
  def __post_init__(self) -> None:
44
42
  odxassert(self.dop_snref is not None or self.dop_ref is not None,
45
43
  f"Param {self.short_name} without a DOP-(SN)REF should not exist!")
46
- self._dop: Optional[DopBase] = None
44
+ self._dop: DopBase
47
45
 
48
46
  @override
49
47
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -55,26 +53,21 @@ class ParameterWithDOP(Parameter):
55
53
 
56
54
  if self.dop_ref is not None:
57
55
  odxassert(self.dop_snref is None)
58
- # TODO: do not do lenient resolves here. The problem is
59
- # that currently not all kinds of DOPs are internalized
60
- # (e.g., static and dynamic fields)
61
- self._dop = odxlinks.resolve_lenient(self.dop_ref)
56
+ self._dop = odxlinks.resolve(self.dop_ref)
62
57
 
63
58
  @override
64
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
65
- super()._resolve_snrefs(diag_layer)
59
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
60
+ super()._resolve_snrefs(context)
66
61
 
67
62
  if self.dop_snref:
68
- ddds = diag_layer.diag_data_dictionary_spec
69
- self._dop = odxrequire(ddds.all_data_object_properties.get(self.dop_snref))
63
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
64
+ self._dop = resolve_snref(self.dop_snref, ddds.all_data_object_properties, DopBase)
70
65
 
71
66
  @property
72
67
  def dop(self) -> DopBase:
73
- """may be a DataObjectProperty, a Structure or None"""
68
+ """This is usually a DataObjectProperty or a Structure object"""
74
69
 
75
- return odxrequire(
76
- self._dop, "Specifying a data object property is mandatory but it "
77
- "could not be resolved")
70
+ return self._dop
78
71
 
79
72
  @override
80
73
  def get_static_bit_length(self) -> Optional[int]:
@@ -91,12 +84,9 @@ class ParameterWithDOP(Parameter):
91
84
  return None
92
85
 
93
86
  @override
94
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
95
- dop = odxrequire(self.dop, "Reference to DOP is not resolved")
96
- physical_value = encode_state.parameter_values[self.short_name]
97
- bit_position_int = self.bit_position if self.bit_position is not None else 0
98
- return dop.convert_physical_to_bytes(
99
- physical_value, encode_state, bit_position=bit_position_int)
87
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
88
+ encode_state: EncodeState) -> None:
89
+ self.dop.encode_into_pdu(cast(AtomicOdxType, physical_value), encode_state)
100
90
 
101
91
  @override
102
92
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -1,6 +1,6 @@
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, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -8,16 +8,14 @@ from typing_extensions import override
8
8
  from ..dataobjectproperty import DataObjectProperty
9
9
  from ..decodestate import DecodeState
10
10
  from ..encodestate import EncodeState
11
- from ..exceptions import DecodeError, odxraise, odxrequire
11
+ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
12
12
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
13
13
  from ..odxtypes import ParameterValue
14
+ from ..snrefcontext import SnRefContext
14
15
  from ..utils import dataclass_fields_asdict
15
16
  from .parameter import ParameterType
16
17
  from .parameterwithdop import ParameterWithDOP
17
18
 
18
- if TYPE_CHECKING:
19
- from ..diaglayer import DiagLayer
20
-
21
19
 
22
20
  @dataclass
23
21
  class PhysicalConstantParameter(ParameterWithDOP):
@@ -50,8 +48,8 @@ class PhysicalConstantParameter(ParameterWithDOP):
50
48
  super()._resolve_odxlinks(odxlinks)
51
49
 
52
50
  @override
53
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
54
- super()._resolve_snrefs(diag_layer)
51
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
52
+ super()._resolve_snrefs(context)
55
53
 
56
54
  dop = odxrequire(self.dop)
57
55
  if not isinstance(dop, DataObjectProperty):
@@ -74,17 +72,15 @@ class PhysicalConstantParameter(ParameterWithDOP):
74
72
  return False
75
73
 
76
74
  @override
77
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
78
- dop = odxrequire(self.dop, "Reference to DOP is not resolved")
79
- if (self.short_name in encode_state.parameter_values and
80
- encode_state.parameter_values[self.short_name] != self.physical_constant_value):
81
- raise TypeError(
82
- f"The parameter '{self.short_name}' is constant {self.physical_constant_value!r}"
83
- f" and thus can not be changed.")
84
-
85
- bit_position_int = self.bit_position if self.bit_position is not None else 0
86
- return dop.convert_physical_to_bytes(
87
- self.physical_constant_value, encode_state, bit_position=bit_position_int)
75
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
76
+ encode_state: EncodeState) -> None:
77
+ if physical_value is not None and physical_value != self.physical_constant_value:
78
+ odxraise(
79
+ f"Value for constant parameter `{self.short_name}` name can "
80
+ f"only be specified as {self.physical_constant_value!r} (is: {physical_value!r})",
81
+ EncodeError)
82
+
83
+ self.dop.encode_into_pdu(self.physical_constant_value, encode_state)
88
84
 
89
85
  @override
90
86
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -99,4 +95,5 @@ class PhysicalConstantParameter(ParameterWithDOP):
99
95
  f"{self.physical_constant_value!r} but got {phys_val!r} "
100
96
  f"at byte position {decode_state.cursor_byte_position} "
101
97
  f"in coded message {decode_state.coded_message.hex()}.", DecodeError)
98
+
102
99
  return phys_val
@@ -49,8 +49,12 @@ class ReservedParameter(Parameter):
49
49
  return self.bit_length
50
50
 
51
51
  @override
52
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
53
- return (0).to_bytes(((self.bit_position or 0) + self.bit_length + 7) // 8, "big")
52
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
53
+ encode_state: EncodeState) -> None:
54
+ encode_state.cursor_byte_position += (encode_state.cursor_bit_position + self.bit_length +
55
+ 7) // 8
56
+ encode_state.cursor_bit_position = 0
57
+ encode_state.emplace_bytes(b'', self.short_name)
54
58
 
55
59
  @override
56
60
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -1,19 +1,29 @@
1
1
  # SPDX-License-Identifier: MIT
2
+ import getpass
2
3
  from dataclasses import dataclass
3
- from typing import List
4
+ from datetime import datetime
5
+ from typing import List, Optional
4
6
  from xml.etree import ElementTree
5
7
 
6
8
  from typing_extensions import override
7
9
 
8
- from ..decodestate import DecodeState
9
10
  from ..encodestate import EncodeState
10
- from ..exceptions import odxrequire
11
+ from ..exceptions import odxraise, odxrequire
11
12
  from ..odxlink import OdxDocFragment
12
13
  from ..odxtypes import ParameterValue
13
14
  from ..utils import dataclass_fields_asdict
14
15
  from .parameter import ParameterType
15
16
  from .parameterwithdop import ParameterWithDOP
16
17
 
18
+ # The SYSTEM parameter types mandated by the ODX 2.2 standard. Users
19
+ # are free to specify additional types, but these must be handled
20
+ # (cf. table 5 in section 7.3.5.4 of the ASAM ODX 2.2 specification
21
+ # document.)
22
+ PREDEFINED_SYSPARAM_VALUES = [
23
+ "TIMESTAMP", "SECOND", "MINUTE", "HOUR", "TIMEZONE", "DAY", "WEEK", "MONTH", "YEAR", "CENTURY",
24
+ "TESTERID", "USERID"
25
+ ]
26
+
17
27
 
18
28
  @dataclass
19
29
  class SystemParameter(ParameterWithDOP):
@@ -26,7 +36,7 @@ class SystemParameter(ParameterWithDOP):
26
36
 
27
37
  kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags))
28
38
 
29
- sysparam = odxrequire(et_element.findtext("SYSPARAM"))
39
+ sysparam = odxrequire(et_element.get("SYSPARAM"))
30
40
 
31
41
  return SystemParameter(sysparam=sysparam, **kwargs)
32
42
 
@@ -38,17 +48,51 @@ class SystemParameter(ParameterWithDOP):
38
48
  @property
39
49
  @override
40
50
  def is_required(self) -> bool:
41
- raise NotImplementedError("SystemParameter.is_required is not implemented yet.")
51
+ # if a SYSTEM parameter is not specified explicitly, its value
52
+ # can be determined from the operating system if it is type is
53
+ # predefined
54
+ return self.sysparam not in PREDEFINED_SYSPARAM_VALUES
42
55
 
43
56
  @property
44
57
  @override
45
58
  def is_settable(self) -> bool:
46
- raise NotImplementedError("SystemParameter.is_settable is not implemented yet.")
59
+ return True
47
60
 
48
61
  @override
49
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
50
- raise NotImplementedError("Encoding a SystemParameter is not implemented yet.")
62
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
63
+ encode_state: EncodeState) -> None:
64
+ if physical_value is None:
65
+ # determine the value to be encoded automatically
66
+ now = datetime.now()
67
+ if self.sysparam == "TIMESTAMP":
68
+ physical_value = round(now.timestamp() * 1000).to_bytes(8, "big")
69
+ elif self.sysparam == "SECOND":
70
+ physical_value = now.second
71
+ elif self.sysparam == "MINUTE":
72
+ physical_value = now.minute
73
+ elif self.sysparam == "HOUR":
74
+ physical_value = now.hour
75
+ elif self.sysparam == "TIMEZONE":
76
+ if (utc_offset := now.astimezone().utcoffset()) is not None:
77
+ physical_value = utc_offset.seconds // 60
78
+ else:
79
+ physical_value = 0
80
+ elif self.sysparam == "DAY":
81
+ physical_value = now.day
82
+ elif self.sysparam == "WEEK":
83
+ physical_value = now.isocalendar()[1]
84
+ elif self.sysparam == "MONTH":
85
+ physical_value = now.month
86
+ elif self.sysparam == "YEAR":
87
+ physical_value = now.year
88
+ elif self.sysparam == "CENTURY":
89
+ physical_value = now.year // 100
90
+ elif self.sysparam == "TESTERID":
91
+ physical_value = "odxtools".encode("latin1")
92
+ elif self.sysparam == "USERID":
93
+ physical_value = getpass.getuser().encode("latin1")
94
+ else:
95
+ odxraise(f"Unknown system parameter type '{self.sysparam}'")
96
+ physical_value = 0
51
97
 
52
- @override
53
- def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
54
- raise NotImplementedError("Decoding SystemParameter is not implemented yet.")
98
+ self.dop.encode_into_pdu(physical_value, encode_state=encode_state)
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, List
3
+ from typing import TYPE_CHECKING, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -59,7 +59,8 @@ class TableEntryParameter(Parameter):
59
59
  raise NotImplementedError("TableEntryParameter.is_settable is not implemented yet.")
60
60
 
61
61
  @override
62
- def get_coded_value_as_bytes(self, encode_state: EncodeState) -> bytes:
62
+ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
63
+ encode_state: EncodeState) -> None:
63
64
  raise NotImplementedError("Encoding a TableEntryParameter is not implemented yet.")
64
65
 
65
66
  @property