odxtools 7.1.1__py3-none-any.whl → 7.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 (135) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +3 -5
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +19 -23
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +3 -2
  9. odxtools/cli/compare.py +1 -1
  10. odxtools/companydata.py +5 -7
  11. odxtools/companydocinfo.py +7 -8
  12. odxtools/companyrevisioninfo.py +3 -5
  13. odxtools/companyspecificinfo.py +8 -9
  14. odxtools/comparam.py +4 -6
  15. odxtools/comparaminstance.py +6 -8
  16. odxtools/comparamspec.py +14 -13
  17. odxtools/comparamsubset.py +17 -16
  18. odxtools/complexcomparam.py +5 -7
  19. odxtools/compumethods/compuconst.py +31 -0
  20. odxtools/compumethods/compudefaultvalue.py +27 -0
  21. odxtools/compumethods/compuinternaltophys.py +39 -0
  22. odxtools/compumethods/compuinversevalue.py +7 -0
  23. odxtools/compumethods/compumethod.py +67 -12
  24. odxtools/compumethods/compuphystointernal.py +39 -0
  25. odxtools/compumethods/compuscale.py +15 -26
  26. odxtools/compumethods/createanycompumethod.py +14 -160
  27. odxtools/compumethods/identicalcompumethod.py +31 -6
  28. odxtools/compumethods/linearcompumethod.py +69 -189
  29. odxtools/compumethods/linearsegment.py +191 -0
  30. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  31. odxtools/compumethods/tabintpcompumethod.py +119 -99
  32. odxtools/compumethods/texttablecompumethod.py +107 -43
  33. odxtools/createanydiagcodedtype.py +10 -67
  34. odxtools/database.py +84 -72
  35. odxtools/dataobjectproperty.py +10 -19
  36. odxtools/decodestate.py +8 -2
  37. odxtools/description.py +47 -0
  38. odxtools/determinenumberofitems.py +4 -5
  39. odxtools/diagcodedtype.py +29 -12
  40. odxtools/diagcomm.py +10 -6
  41. odxtools/diagdatadictionaryspec.py +20 -21
  42. odxtools/diaglayer.py +39 -9
  43. odxtools/diaglayercontainer.py +17 -11
  44. odxtools/diaglayerraw.py +20 -21
  45. odxtools/diagnostictroublecode.py +8 -9
  46. odxtools/diagservice.py +42 -27
  47. odxtools/docrevision.py +5 -7
  48. odxtools/dopbase.py +7 -8
  49. odxtools/dtcdop.py +44 -22
  50. odxtools/dynamicendmarkerfield.py +22 -9
  51. odxtools/dynamiclengthfield.py +5 -11
  52. odxtools/element.py +4 -3
  53. odxtools/encodestate.py +14 -2
  54. odxtools/endofpdufield.py +0 -2
  55. odxtools/environmentdatadescription.py +137 -19
  56. odxtools/exceptions.py +11 -2
  57. odxtools/field.py +9 -9
  58. odxtools/functionalclass.py +3 -5
  59. odxtools/inputparam.py +3 -5
  60. odxtools/leadinglengthinfotype.py +15 -2
  61. odxtools/loadfile.py +64 -0
  62. odxtools/minmaxlengthtype.py +20 -2
  63. odxtools/modification.py +3 -5
  64. odxtools/multiplexer.py +98 -69
  65. odxtools/multiplexercase.py +10 -11
  66. odxtools/multiplexerdefaultcase.py +11 -12
  67. odxtools/multiplexerswitchkey.py +4 -5
  68. odxtools/negoutputparam.py +3 -5
  69. odxtools/odxlink.py +12 -26
  70. odxtools/odxtypes.py +1 -1
  71. odxtools/outputparam.py +3 -5
  72. odxtools/parameterinfo.py +5 -5
  73. odxtools/parameters/codedconstparameter.py +2 -14
  74. odxtools/parameters/lengthkeyparameter.py +3 -17
  75. odxtools/parameters/nrcconstparameter.py +29 -50
  76. odxtools/parameters/parameter.py +22 -22
  77. odxtools/parameters/parameterwithdop.py +6 -8
  78. odxtools/parameters/physicalconstantparameter.py +5 -8
  79. odxtools/parameters/reservedparameter.py +4 -3
  80. odxtools/parameters/systemparameter.py +1 -1
  81. odxtools/parameters/tablekeyparameter.py +6 -9
  82. odxtools/parameters/tablestructparameter.py +6 -8
  83. odxtools/parameters/valueparameter.py +5 -8
  84. odxtools/paramlengthinfotype.py +19 -6
  85. odxtools/parentref.py +15 -1
  86. odxtools/physicaldimension.py +3 -5
  87. odxtools/progcode.py +18 -7
  88. odxtools/protstack.py +3 -5
  89. odxtools/relateddoc.py +7 -9
  90. odxtools/request.py +8 -0
  91. odxtools/response.py +8 -0
  92. odxtools/scaleconstr.py +3 -3
  93. odxtools/singleecujob.py +12 -10
  94. odxtools/snrefcontext.py +29 -0
  95. odxtools/specialdata.py +3 -5
  96. odxtools/specialdatagroup.py +5 -7
  97. odxtools/specialdatagroupcaption.py +3 -6
  98. odxtools/standardlengthtype.py +27 -2
  99. odxtools/state.py +3 -5
  100. odxtools/statechart.py +9 -11
  101. odxtools/statetransition.py +4 -9
  102. odxtools/staticfield.py +4 -8
  103. odxtools/table.py +7 -8
  104. odxtools/tablerow.py +7 -6
  105. odxtools/teammember.py +3 -5
  106. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +2 -5
  107. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +2 -5
  108. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -5
  109. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  110. odxtools/templates/macros/printCompuMethod.xml.jinja2 +153 -0
  111. odxtools/templates/macros/printDOP.xml.jinja2 +10 -132
  112. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  113. odxtools/templates/macros/printElementId.xml.jinja2 +3 -3
  114. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  115. odxtools/templates/macros/printTable.xml.jinja2 +2 -3
  116. odxtools/unit.py +3 -5
  117. odxtools/unitgroup.py +3 -5
  118. odxtools/unitspec.py +9 -10
  119. odxtools/utils.py +1 -26
  120. odxtools/version.py +2 -2
  121. odxtools/{write_pdx_file.py → writepdxfile.py} +19 -10
  122. odxtools/xdoc.py +3 -5
  123. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/METADATA +1 -1
  124. odxtools-7.3.0.dist-info/RECORD +192 -0
  125. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/WHEEL +1 -1
  126. odxtools/createcompanydatas.py +0 -17
  127. odxtools/createsdgs.py +0 -19
  128. odxtools/load_file.py +0 -13
  129. odxtools/load_odx_d_file.py +0 -6
  130. odxtools/load_pdx_file.py +0 -8
  131. odxtools-7.1.1.dist-info/RECORD +0 -186
  132. /odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +0 -0
  133. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/LICENSE +0 -0
  134. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/entry_points.txt +0 -0
  135. {odxtools-7.1.1.dist-info → odxtools-7.3.0.dist-info}/top_level.txt +0 -0
@@ -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, Sequence
3
+ from typing import Any, Dict, List, Sequence
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -13,11 +13,9 @@ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequir
13
13
  from .field import Field
14
14
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
15
  from .odxtypes import AtomicOdxType, ParameterValue
16
+ from .snrefcontext import SnRefContext
16
17
  from .utils import dataclass_fields_asdict
17
18
 
18
- if TYPE_CHECKING:
19
- from .diaglayer import DiagLayer
20
-
21
19
 
22
20
  @dataclass
23
21
  class DynamicEndmarkerField(Field):
@@ -49,8 +47,8 @@ class DynamicEndmarkerField(Field):
49
47
 
50
48
  self._termination_value = tv_physical
51
49
 
52
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
53
- super()._resolve_snrefs(diag_layer)
50
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
51
+ super()._resolve_snrefs(context)
54
52
 
55
53
  @property
56
54
  def dyn_end_dop(self) -> DataObjectProperty:
@@ -81,8 +79,17 @@ class DynamicEndmarkerField(Field):
81
79
  encode_state.is_end_of_pdu = orig_is_end_of_pdu
82
80
 
83
81
  if not encode_state.is_end_of_pdu:
84
- # only add an endmarker if we are not at the end of the PDU
82
+ # only add an endmarker if we are not at the end of the
83
+ # PDU. note that since section 7.3.6.10.5 of the MCD-2
84
+ # specification states that the data used by the endmarker
85
+ # ought to be considered to be not consumed (why?!), we
86
+ # need to keep the cursor where it is before adding the
87
+ # endmarker. (we still consider its bits to be used
88
+ # "used", in order to produce a warning if it is attempted
89
+ # to be overridden.)
90
+ tmp_cursor = encode_state.cursor_byte_position
85
91
  self.dyn_end_dop.encode_into_pdu(self.termination_value, encode_state)
92
+ encode_state.cursor_byte_position = tmp_cursor
86
93
 
87
94
  @override
88
95
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -91,7 +98,6 @@ class DynamicEndmarkerField(Field):
91
98
  "No bit position can be specified for dynamic endmarker fields!")
92
99
 
93
100
  orig_origin = decode_state.origin_byte_position
94
- orig_cursor = decode_state.cursor_byte_position
95
101
  decode_state.origin_byte_position = decode_state.cursor_byte_position
96
102
 
97
103
  result: List[ParameterValue] = []
@@ -106,6 +112,14 @@ class DynamicEndmarkerField(Field):
106
112
  try:
107
113
  tv_candidate = self.dyn_end_dop.decode_from_pdu(decode_state)
108
114
  if tv_candidate == self.termination_value:
115
+ # note that section 7.3.6.10.5 of the MCD-2
116
+ # specification states that the bytes occupied by
117
+ # the endmarker ought to be considered to be not
118
+ # consumed (why?!), i.e., we need to keep the
119
+ # cursor where it is before adding the
120
+ # endmarker. (we still consider its to be used
121
+ # "used", though.)
122
+ decode_state.cursor_byte_position = tmp_cursor
109
123
  break
110
124
  except DecodeError:
111
125
  pass
@@ -114,6 +128,5 @@ class DynamicEndmarkerField(Field):
114
128
  result.append(self.structure.decode_from_pdu(decode_state))
115
129
 
116
130
  decode_state.origin_byte_position = orig_origin
117
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
118
131
 
119
132
  return result
@@ -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, Sequence
3
+ from typing import Any, Dict, List, Sequence
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -12,11 +12,9 @@ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequir
12
12
  from .field import Field
13
13
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
14
14
  from .odxtypes import ParameterValue
15
+ from .snrefcontext import SnRefContext
15
16
  from .utils import dataclass_fields_asdict
16
17
 
17
- if TYPE_CHECKING:
18
- from .diaglayer import DiagLayer
19
-
20
18
 
21
19
  @dataclass
22
20
  class DynamicLengthField(Field):
@@ -45,9 +43,9 @@ class DynamicLengthField(Field):
45
43
  super()._resolve_odxlinks(odxlinks)
46
44
  self.determine_number_of_items._resolve_odxlinks(odxlinks)
47
45
 
48
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
49
- super()._resolve_snrefs(diag_layer)
50
- self.determine_number_of_items._resolve_snrefs(diag_layer)
46
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
47
+ super()._resolve_snrefs(context)
48
+ self.determine_number_of_items._resolve_snrefs(context)
51
49
 
52
50
  @override
53
51
  def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
@@ -61,7 +59,6 @@ class DynamicLengthField(Field):
61
59
  f"got {type(physical_value)}", EncodeError)
62
60
 
63
61
  # move the origin to the cursor position
64
- orig_cursor = encode_state.cursor_byte_position
65
62
  orig_origin = encode_state.origin_byte_position
66
63
  encode_state.origin_byte_position = encode_state.cursor_byte_position
67
64
 
@@ -92,7 +89,6 @@ class DynamicLengthField(Field):
92
89
 
93
90
  # move cursor and origin positions
94
91
  encode_state.origin_byte_position = orig_origin
95
- encode_state.cursor_byte_position = max(orig_cursor, encode_state.cursor_byte_position)
96
92
 
97
93
  @override
98
94
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -101,7 +97,6 @@ class DynamicLengthField(Field):
101
97
  "No bit position can be specified for dynamic length fields!")
102
98
 
103
99
  orig_origin = decode_state.origin_byte_position
104
- orig_cursor = decode_state.cursor_byte_position
105
100
 
106
101
  det_num_items = self.determine_number_of_items
107
102
  decode_state.origin_byte_position = decode_state.cursor_byte_position
@@ -125,6 +120,5 @@ class DynamicLengthField(Field):
125
120
  result.append(self.structure.decode_from_pdu(decode_state))
126
121
 
127
122
  decode_state.origin_byte_position = orig_origin
128
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
129
123
 
130
124
  return result
odxtools/element.py CHANGED
@@ -2,16 +2,17 @@ from dataclasses import dataclass
2
2
  from typing import List, Optional
3
3
  from xml.etree import ElementTree
4
4
 
5
+ from .description import Description
5
6
  from .exceptions import odxrequire
6
7
  from .odxlink import OdxDocFragment, OdxLinkId
7
- from .utils import create_description_from_et, dataclass_fields_asdict
8
+ from .utils import dataclass_fields_asdict
8
9
 
9
10
 
10
11
  @dataclass
11
12
  class NamedElement:
12
13
  short_name: str
13
14
  long_name: Optional[str]
14
- description: Optional[str]
15
+ description: Optional[Description]
15
16
 
16
17
  @staticmethod
17
18
  def from_et(
@@ -22,7 +23,7 @@ class NamedElement:
22
23
  return NamedElement(
23
24
  short_name=odxrequire(et_element.findtext("SHORT-NAME")),
24
25
  long_name=et_element.findtext("LONG-NAME"),
25
- description=create_description_from_et(et_element.find("DESC")),
26
+ description=Description.from_et(et_element.find("DESC"), doc_frags),
26
27
  )
27
28
 
28
29
 
odxtools/encodestate.py CHANGED
@@ -1,16 +1,19 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import Dict, Optional, SupportsBytes
4
+ from typing import TYPE_CHECKING, Dict, List, Optional, SupportsBytes, Tuple
5
5
 
6
6
  from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
7
- from .odxtypes import AtomicOdxType, DataType
7
+ from .odxtypes import AtomicOdxType, DataType, ParameterValue
8
8
 
9
9
  try:
10
10
  import bitstruct.c as bitstruct
11
11
  except ImportError:
12
12
  import bitstruct
13
13
 
14
+ if TYPE_CHECKING:
15
+ from .parameters.parameter import Parameter
16
+
14
17
 
15
18
  @dataclass
16
19
  class EncodeState:
@@ -56,6 +59,15 @@ class EncodeState:
56
59
  #: (needed for MinMaxLengthType, EndOfPduField, etc.)
57
60
  is_end_of_pdu: bool = True
58
61
 
62
+ #: list of parameters that have been encoded so far. The journal
63
+ #: is used by some types of parameters which depend on the values of
64
+ #: other parameters; e.g., environment data description parameters
65
+ journal: List[Tuple["Parameter", Optional[ParameterValue]]] = field(default_factory=list)
66
+
67
+ #: If this is True, specifying unknown parameters for encoding
68
+ #: will raise an OdxError exception in strict mode.
69
+ allow_unknown_parameters = False
70
+
59
71
  def __post_init__(self) -> None:
60
72
  # if a coded message has been specified, but no used_mask, we
61
73
  # assume that all of the bits of the coded message are
odxtools/endofpdufield.py CHANGED
@@ -72,7 +72,6 @@ class EndOfPduField(Field):
72
72
  "No bit position can be specified for end-of-pdu fields!")
73
73
 
74
74
  orig_origin = decode_state.origin_byte_position
75
- orig_cursor = decode_state.cursor_byte_position
76
75
  decode_state.origin_byte_position = decode_state.cursor_byte_position
77
76
 
78
77
  result: List[ParameterValue] = []
@@ -84,6 +83,5 @@ class EndOfPduField(Field):
84
83
  result.append(self.structure.decode_from_pdu(decode_state))
85
84
 
86
85
  decode_state.origin_byte_position = orig_origin
87
- decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
88
86
 
89
87
  return result
@@ -1,22 +1,22 @@
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
7
7
 
8
8
  from .complexdop import ComplexDop
9
9
  from .decodestate import DecodeState
10
+ from .dtcdop import DtcDop
10
11
  from .encodestate import EncodeState
11
12
  from .environmentdata import EnvironmentData
12
13
  from .exceptions import odxraise, odxrequire
13
14
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
14
- from .odxtypes import ParameterValue
15
+ from .odxtypes import ParameterValue, ParameterValueDict
16
+ from .parameters.parameter import Parameter
17
+ from .snrefcontext import SnRefContext
15
18
  from .utils import dataclass_fields_asdict
16
19
 
17
- if TYPE_CHECKING:
18
- from .diaglayer import DiagLayer
19
-
20
20
 
21
21
  @dataclass
22
22
  class EnvironmentDataDescription(ComplexDop):
@@ -29,16 +29,26 @@ class EnvironmentDataDescription(ComplexDop):
29
29
 
30
30
  """
31
31
 
32
+ param_snref: Optional[str]
33
+ param_snpathref: Optional[str]
34
+
32
35
  # in ODX 2.0.0, ENV-DATAS seems to be a mandatory
33
36
  # sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
34
37
  # present
35
38
  env_datas: List[EnvironmentData]
36
39
  env_data_refs: List[OdxLinkRef]
37
- param_snref: Optional[str]
38
- param_snpathref: Optional[str]
39
40
 
40
- def __post_init__(self) -> None:
41
- self.bit_length = None
41
+ @property
42
+ def param(self) -> Parameter:
43
+ # the parameter referenced via SNREF cannot be resolved here
44
+ # because the relevant list of parameters depends on the
45
+ # concrete codec object processed, whilst an environment data
46
+ # description object can be featured in an arbitrary number of
47
+ # responses. Instead, lookup of the appropriate parameter is
48
+ # done within the encode and decode methods.
49
+ odxraise("The parameter of ENV-DATA-DESC objects cannot be resolved "
50
+ "because it depends on the context")
51
+ return cast(None, Parameter)
42
52
 
43
53
  @staticmethod
44
54
  def from_et(et_element: ElementTree.Element,
@@ -87,28 +97,136 @@ class EnvironmentDataDescription(ComplexDop):
87
97
  for ed in self.env_datas:
88
98
  ed._resolve_odxlinks(odxlinks)
89
99
 
90
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
100
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
91
101
  # ODX 2.0 specifies environment data objects here, ODX 2.2
92
102
  # uses references
93
103
  if self.env_data_refs:
94
104
  for ed in self.env_datas:
95
- ed._resolve_snrefs(diag_layer)
105
+ ed._resolve_snrefs(context)
96
106
 
97
107
  @override
98
108
  def encode_into_pdu(self, physical_value: Optional[ParameterValue],
99
109
  encode_state: EncodeState) -> None:
100
110
  """Convert a physical value into bytes and emplace them into a PDU.
101
-
102
- Since environmental data is supposed to never appear on the
103
- wire, this method just raises an EncodeError exception.
104
111
  """
105
- odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
112
+
113
+ # retrieve the relevant DTC parameter which must be located in
114
+ # front of the environment data description.
115
+ if self.param_snref is None:
116
+ odxraise("Specifying the DTC parameter for environment data "
117
+ "descriptions via SNPATHREF is not supported yet")
118
+ return None
119
+
120
+ dtc_param: Optional[Parameter] = None
121
+ dtc_dop: Optional[DtcDop] = None
122
+ dtc_param_value: Optional[ParameterValue] = None
123
+ for prev_param, prev_param_value in reversed(encode_state.journal):
124
+ if prev_param.short_name == self.param_snref:
125
+ dtc_param = prev_param
126
+ prev_dop = getattr(prev_param, "dop", None)
127
+ if not isinstance(prev_dop, DtcDop):
128
+ odxraise(f"The DOP of the parameter referenced by environment data "
129
+ f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
130
+ return
131
+ dtc_dop = prev_dop
132
+ dtc_param_value = prev_param_value
133
+ break
134
+
135
+ if dtc_param is None:
136
+ odxraise("Environment data description parameters are only allowed following "
137
+ "the referenced value parameter.")
138
+ return
139
+
140
+ if dtc_param_value is None or dtc_dop is None:
141
+ # this should never happen
142
+ odxraise()
143
+ return
144
+
145
+ numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
146
+
147
+ # deal with the "all value" environment data. This holds
148
+ # parameters that are common to all DTCs. Be aware that the
149
+ # specification mandates that there is at most one such
150
+ # environment data object
151
+ for env_data in self.env_datas:
152
+ if env_data.all_value:
153
+ tmp = encode_state.allow_unknown_parameters
154
+ encode_state.allow_unknown_parameters = True
155
+ env_data.encode_into_pdu(physical_value, encode_state)
156
+ encode_state.allow_unknown_parameters = tmp
157
+ break
158
+
159
+ # find the environment data corresponding to the given trouble
160
+ # code
161
+ for env_data in self.env_datas:
162
+ if numerical_dtc in env_data.dtc_values:
163
+ tmp = encode_state.allow_unknown_parameters
164
+ encode_state.allow_unknown_parameters = True
165
+ env_data.encode_into_pdu(physical_value, encode_state)
166
+ encode_state.allow_unknown_parameters = tmp
167
+ break
106
168
 
107
169
  @override
108
170
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
109
171
  """Extract the bytes from a PDU and convert them to a physical value.
110
-
111
- Since environmental data is supposed to never appear on the
112
- wire, this method just raises an DecodeError exception.
113
172
  """
114
- odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
173
+
174
+ # retrieve the relevant DTC parameter which must be located in
175
+ # front of the environment data description.
176
+ if self.param_snref is None:
177
+ odxraise("Specifying the DTC parameter for environment data "
178
+ "descriptions via SNPATHREF is not supported yet")
179
+ return None
180
+
181
+ dtc_param: Optional[Parameter] = None
182
+ dtc_dop: Optional[DtcDop] = None
183
+ dtc_param_value: Optional[ParameterValue] = None
184
+ for prev_param, prev_param_value in reversed(decode_state.journal):
185
+ if prev_param.short_name == self.param_snref:
186
+ dtc_param = prev_param
187
+ prev_dop = getattr(prev_param, "dop", None)
188
+ if not isinstance(prev_dop, DtcDop):
189
+ odxraise(f"The DOP of the parameter referenced by environment data "
190
+ f"descriptions must be a DTC-DOP (is '{type(prev_dop).__name__}')")
191
+ return
192
+ dtc_dop = prev_dop
193
+ dtc_param_value = prev_param_value
194
+ break
195
+
196
+ if dtc_param is None:
197
+ odxraise("Environment data description parameters are only allowed following "
198
+ "the referenced value parameter.")
199
+ return
200
+
201
+ if dtc_param_value is None or dtc_dop is None:
202
+ # this should never happen
203
+ odxraise()
204
+ return
205
+
206
+ numerical_dtc = dtc_dop.convert_to_numerical_trouble_code(dtc_param_value)
207
+
208
+ result: ParameterValueDict = {}
209
+
210
+ # deal with the "all value" environment data. This holds
211
+ # parameters that are common to all DTCs. Be aware that the
212
+ # specification mandates that there is at most one such
213
+ # environment data object
214
+ for env_data in self.env_datas:
215
+ if env_data.all_value:
216
+ tmp = env_data.decode_from_pdu(decode_state)
217
+ if not isinstance(tmp, dict):
218
+ odxraise()
219
+ result.update(tmp)
220
+ break
221
+
222
+ # find the environment data corresponding to the given trouble
223
+ # code
224
+ for env_data in self.env_datas:
225
+ if numerical_dtc in env_data.dtc_values:
226
+ tmp = env_data.decode_from_pdu(decode_state)
227
+ if not isinstance(tmp, dict):
228
+ odxraise()
229
+ result.update(tmp)
230
+ break
231
+
232
+ return result
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,5 +1,6 @@
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
@@ -8,11 +9,9 @@ from .environmentdatadescription import EnvironmentDataDescription
8
9
  from .exceptions import odxassert, odxrequire
9
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,13 +79,14 @@ 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
+ ddd_spec = 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 = resolve_snref(self.structure_snref, structures, BasicStructure)
87
+ self._structure = resolve_snref(self.structure_snref, ddd_spec.structures,
88
+ BasicStructure)
88
89
 
89
90
  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 = resolve_snref(self.env_data_desc_snref, env_data_descs,
91
+ self._env_data_desc = resolve_snref(self.env_data_desc_snref, ddd_spec.env_data_descs,
92
92
  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,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
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from deprecation import deprecated
@@ -9,11 +9,9 @@ from .dopbase import DopBase
9
9
  from .element import NamedElement
10
10
  from .exceptions import odxrequire
11
11
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
+ from .snrefcontext import SnRefContext
12
13
  from .utils import dataclass_fields_asdict
13
14
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
15
 
18
16
  @dataclass
19
17
  class InputParam(NamedElement):
@@ -44,7 +42,7 @@ class InputParam(NamedElement):
44
42
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
45
43
  self._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
46
44
 
47
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
45
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
48
46
  pass
49
47
 
50
48
  @property
@@ -1,14 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Optional
3
+ from typing import List, Optional
4
+ from xml.etree import ElementTree
4
5
 
5
6
  from typing_extensions import override
6
7
 
7
8
  from .decodestate import DecodeState
8
9
  from .diagcodedtype import DctType, DiagCodedType
9
10
  from .encodestate import EncodeState
10
- from .exceptions import EncodeError, odxassert, odxraise
11
+ from .exceptions import EncodeError, odxassert, odxraise, odxrequire
12
+ from .odxlink import OdxDocFragment
11
13
  from .odxtypes import AtomicOdxType, DataType
14
+ from .utils import dataclass_fields_asdict
12
15
 
13
16
 
14
17
  @dataclass
@@ -20,6 +23,16 @@ class LeadingLengthInfoType(DiagCodedType):
20
23
  #: object.
21
24
  bit_length: int
22
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
+
23
36
  def __post_init__(self) -> None:
24
37
  odxassert(self.bit_length > 0,
25
38
  "A Leading length info type with bit length == 0 does not make sense.")
odxtools/loadfile.py ADDED
@@ -0,0 +1,64 @@
1
+ # SPDX-License-Identifier: MIT
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Union
5
+
6
+ from .database import Database
7
+
8
+
9
+ def load_pdx_file(pdx_file: Union[str, Path]) -> Database:
10
+ db = Database()
11
+ db.add_pdx_file(str(pdx_file))
12
+ db.refresh()
13
+ return db
14
+
15
+
16
+ def load_odx_d_file(odx_d_file_name: Union[str, Path]) -> Database:
17
+ db = Database()
18
+ db.add_odx_file(str(odx_d_file_name))
19
+ db.refresh()
20
+
21
+ return db
22
+
23
+
24
+ def load_file(file_name: Union[str, Path]) -> Database:
25
+ if str(file_name).lower().endswith(".pdx"):
26
+ return load_pdx_file(str(file_name))
27
+ elif str(file_name).lower().endswith(".odx-d"):
28
+ return load_odx_d_file(str(file_name))
29
+ else:
30
+ raise RuntimeError(f"Could not guess the file format of file '{file_name}'!")
31
+
32
+
33
+ def load_files(*file_names: Union[str, Path]) -> Database:
34
+ db = Database()
35
+ for file_name in file_names:
36
+ p = Path(file_name)
37
+ if p.suffix.lower() == ".pdx":
38
+ db.add_pdx_file(str(file_name))
39
+ elif p.suffix.lower().startswith(".odx"):
40
+ db.add_odx_file(str(file_name))
41
+ elif p.name.lower() != "index.xml":
42
+ db.add_auxiliary_file(str(file_name))
43
+
44
+ db.refresh()
45
+ return db
46
+
47
+
48
+ def load_directory(dir_name: Union[str, Path]) -> Database:
49
+ db = Database()
50
+ for file_name in os.listdir(str(dir_name)):
51
+ p = Path(dir_name) / file_name
52
+
53
+ if not p.is_file():
54
+ continue
55
+
56
+ if p.suffix.lower() == ".pdx":
57
+ db.add_pdx_file(str(p))
58
+ elif p.suffix.lower().startswith(".odx"):
59
+ db.add_odx_file(str(p))
60
+ elif p.name.lower() != "index.xml":
61
+ db.add_auxiliary_file(p.name, open(str(p), "rb"))
62
+
63
+ db.refresh()
64
+ return db