odxtools 9.4.1__py3-none-any.whl → 9.6.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 (113) hide show
  1. odxtools/additionalaudience.py +2 -2
  2. odxtools/admindata.py +3 -0
  3. odxtools/audience.py +9 -13
  4. odxtools/basecomparam.py +1 -2
  5. odxtools/basevariantpattern.py +38 -0
  6. odxtools/basicstructure.py +34 -35
  7. odxtools/commrelation.py +2 -1
  8. odxtools/companydata.py +1 -2
  9. odxtools/companyspecificinfo.py +3 -0
  10. odxtools/comparam.py +16 -8
  11. odxtools/comparaminstance.py +12 -12
  12. odxtools/comparamspec.py +4 -3
  13. odxtools/comparamsubset.py +26 -24
  14. odxtools/compumethods/compuconst.py +4 -4
  15. odxtools/compumethods/limit.py +9 -9
  16. odxtools/compumethods/linearsegment.py +8 -8
  17. odxtools/dataobjectproperty.py +16 -18
  18. odxtools/description.py +4 -2
  19. odxtools/determinenumberofitems.py +4 -4
  20. odxtools/diagcodedtype.py +20 -20
  21. odxtools/diagcomm.py +61 -41
  22. odxtools/diagdatadictionaryspec.py +51 -55
  23. odxtools/diaglayercontainer.py +25 -25
  24. odxtools/diaglayers/basevariant.py +5 -0
  25. odxtools/diaglayers/basevariantraw.py +7 -1
  26. odxtools/diaglayers/diaglayerraw.py +26 -27
  27. odxtools/diaglayers/ecuvariant.py +16 -0
  28. odxtools/diagnostictroublecode.py +13 -10
  29. odxtools/diagservice.py +48 -50
  30. odxtools/diagvariable.py +10 -8
  31. odxtools/docrevision.py +5 -5
  32. odxtools/dtcdop.py +17 -17
  33. odxtools/dynamicendmarkerfield.py +8 -8
  34. odxtools/dynamiclengthfield.py +2 -0
  35. odxtools/dyndefinedspec.py +21 -8
  36. odxtools/ecuvariantpattern.py +20 -9
  37. odxtools/encodestate.py +3 -3
  38. odxtools/endofpdufield.py +7 -9
  39. odxtools/environmentdatadescription.py +9 -20
  40. odxtools/field.py +21 -21
  41. odxtools/inputparam.py +15 -14
  42. odxtools/leadinglengthinfotype.py +4 -4
  43. odxtools/matchingbasevariantparameter.py +38 -0
  44. odxtools/matchingparameter.py +108 -28
  45. odxtools/minmaxlengthtype.py +6 -6
  46. odxtools/multiplexer.py +38 -39
  47. odxtools/multiplexercase.py +3 -6
  48. odxtools/multiplexerdefaultcase.py +3 -6
  49. odxtools/multiplexerswitchkey.py +4 -4
  50. odxtools/negoutputparam.py +6 -9
  51. odxtools/odxlink.py +21 -5
  52. odxtools/odxtypes.py +7 -6
  53. odxtools/outputparam.py +9 -8
  54. odxtools/parameterinfo.py +1 -1
  55. odxtools/parameters/codedconstparameter.py +28 -27
  56. odxtools/parameters/dynamicparameter.py +9 -9
  57. odxtools/parameters/lengthkeyparameter.py +18 -18
  58. odxtools/parameters/matchingrequestparameter.py +15 -15
  59. odxtools/parameters/nrcconstparameter.py +32 -24
  60. odxtools/parameters/parameter.py +35 -37
  61. odxtools/parameters/parameterwithdop.py +6 -6
  62. odxtools/parameters/physicalconstantparameter.py +19 -20
  63. odxtools/parameters/reservedparameter.py +10 -11
  64. odxtools/parameters/systemparameter.py +10 -11
  65. odxtools/parameters/tableentryparameter.py +19 -20
  66. odxtools/parameters/tablekeyparameter.py +0 -2
  67. odxtools/parameters/tablestructparameter.py +27 -21
  68. odxtools/parameters/valueparameter.py +20 -20
  69. odxtools/parentref.py +6 -7
  70. odxtools/physicaldimension.py +11 -11
  71. odxtools/physicaltype.py +9 -14
  72. odxtools/preconditionstateref.py +85 -0
  73. odxtools/progcode.py +1 -2
  74. odxtools/protstack.py +4 -4
  75. odxtools/relateddoc.py +3 -4
  76. odxtools/scaleconstr.py +0 -1
  77. odxtools/singleecujob.py +8 -4
  78. odxtools/specialdata.py +10 -9
  79. odxtools/specialdatagroup.py +1 -0
  80. odxtools/standardlengthtype.py +18 -18
  81. odxtools/statechart.py +10 -6
  82. odxtools/statemachine.py +186 -0
  83. odxtools/statetransitionref.py +231 -0
  84. odxtools/structure.py +4 -4
  85. odxtools/subcomponent.py +78 -11
  86. odxtools/table.py +23 -13
  87. odxtools/tablerow.py +86 -69
  88. odxtools/teammember.py +4 -4
  89. odxtools/templates/macros/printBaseVariant.xml.jinja2 +4 -9
  90. odxtools/templates/macros/printBaseVariantPattern.xml.jinja2 +32 -0
  91. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  92. odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
  93. odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
  94. odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
  95. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +7 -6
  96. odxtools/templates/macros/printParam.xml.jinja2 +5 -5
  97. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
  98. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
  99. odxtools/templates/macros/printTable.xml.jinja2 +13 -9
  100. odxtools/text.py +35 -0
  101. odxtools/unit.py +1 -3
  102. odxtools/unitgroup.py +6 -8
  103. odxtools/variantmatcher.py +209 -0
  104. odxtools/variantpattern.py +38 -0
  105. odxtools/version.py +2 -2
  106. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
  107. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/RECORD +111 -102
  108. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
  109. odxtools/createecuvariantpatterns.py +0 -18
  110. odxtools/ecuvariantmatcher.py +0 -171
  111. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
  112. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
  113. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxassert, odxrequire
7
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
+ from .odxtypes import ParameterValueDict
9
+ from .parameters.parameter import Parameter
10
+ from .snrefcontext import SnRefContext
11
+ from .state import State
12
+ from .statetransitionref import _check_applies
13
+ from .utils import dataclass_fields_asdict
14
+
15
+ if TYPE_CHECKING:
16
+ from .statemachine import StateMachine
17
+
18
+
19
+ @dataclass
20
+ class PreConditionStateRef(OdxLinkRef):
21
+ """
22
+ This class represents the PRE-CONDITION-STATE-REF XML tag.
23
+ """
24
+ value: Optional[str]
25
+
26
+ in_param_if_snref: Optional[str]
27
+ in_param_if_snpathref: Optional[str]
28
+
29
+ @property
30
+ def state(self) -> "State":
31
+ return self._state
32
+
33
+ @staticmethod
34
+ def from_et( # type: ignore[override]
35
+ et_element: ElementTree.Element,
36
+ doc_frags: List[OdxDocFragment]) -> "PreConditionStateRef":
37
+ kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, doc_frags))
38
+
39
+ value = et_element.findtext("VALUE")
40
+
41
+ in_param_if_snref = None
42
+ if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
43
+ in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
44
+
45
+ in_param_if_snpathref = None
46
+ if (in_param_if_snpathref_elem := et_element.find("IN-PARAM-IF-SNPATHREF")) is not None:
47
+ in_param_if_snpathref = odxrequire(in_param_if_snpathref_elem.get("SHORT-NAME-PATH"))
48
+
49
+ return PreConditionStateRef(
50
+ value=value,
51
+ in_param_if_snref=in_param_if_snref,
52
+ in_param_if_snpathref=in_param_if_snpathref,
53
+ **kwargs)
54
+
55
+ def __post_init__(self) -> None:
56
+ if self.value is not None:
57
+ odxassert(self.in_param_if_snref is not None or self.in_param_if_snref is not None,
58
+ "If VALUE is specified, a parameter must be referenced")
59
+
60
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
61
+ return {}
62
+
63
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
64
+ self._state = odxlinks.resolve(self, State)
65
+
66
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
67
+ pass
68
+
69
+ def applies(self, state_machine: "StateMachine", params: List[Parameter],
70
+ param_value_dict: ParameterValueDict) -> bool:
71
+ """Given a state machine, evaluate whether the precondition is fulfilled or not
72
+
73
+ Note that the specification is unclear about what the
74
+ parameters are: It says "The optional VALUE together with the
75
+ also optional IN-PARAM-IF snref at STATE-TRANSITION-REF and
76
+ PRE-CONDITION-STATE-REF can be used if the STATE-TRANSITIONs
77
+ and pre-condition STATEs are dependent on the values of the
78
+ referenced PARAMs.", but it does not specify what the
79
+ "referenced PARAMs" are. For the state transition refs, let's
80
+ assume that they are the parameters of a positive response
81
+ received from the ECU, whilst we assume that they are the
82
+ parameters of the request for PRE-CONDITION-STATE-REFs.
83
+ """
84
+
85
+ return _check_applies(self, state_machine, params, param_value_dict)
odxtools/progcode.py CHANGED
@@ -31,7 +31,6 @@ class ProgCode:
31
31
  @staticmethod
32
32
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ProgCode":
33
33
  code_file = odxrequire(et_element.findtext("CODE-FILE"))
34
-
35
34
  encryption = et_element.findtext("ENCRYPTION")
36
35
  syntax = odxrequire(et_element.findtext("SYNTAX"))
37
36
  revision = odxrequire(et_element.findtext("REVISION"))
@@ -44,9 +43,9 @@ class ProgCode:
44
43
 
45
44
  return ProgCode(
46
45
  code_file=code_file,
46
+ encryption=encryption,
47
47
  syntax=syntax,
48
48
  revision=revision,
49
- encryption=encryption,
50
49
  entrypoint=entrypoint,
51
50
  library_refs=library_refs,
52
51
  )
odxtools/protstack.py CHANGED
@@ -19,6 +19,10 @@ class ProtStack(IdentifiableElement):
19
19
  physical_link_type: str
20
20
  comparam_subset_refs: List[OdxLinkRef]
21
21
 
22
+ @property
23
+ def comparam_subsets(self) -> NamedItemList[ComparamSubset]:
24
+ return self._comparam_subsets
25
+
22
26
  @staticmethod
23
27
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "ProtStack":
24
28
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
@@ -47,7 +51,3 @@ class ProtStack(IdentifiableElement):
47
51
 
48
52
  def _resolve_snrefs(self, context: SnRefContext) -> None:
49
53
  pass
50
-
51
- @property
52
- def comparam_subsets(self) -> NamedItemList[ComparamSubset]:
53
- return self._comparam_subsets
odxtools/relateddoc.py CHANGED
@@ -11,20 +11,19 @@ from .xdoc import XDoc
11
11
 
12
12
  @dataclass
13
13
  class RelatedDoc:
14
- description: Optional[Description]
15
14
  xdoc: Optional[XDoc]
15
+ description: Optional[Description]
16
16
 
17
17
  @staticmethod
18
18
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "RelatedDoc":
19
- description = Description.from_et(et_element.find("DESC"), doc_frags)
20
-
21
19
  xdoc: Optional[XDoc] = None
22
20
  if (xdoc_elem := et_element.find("XDOC")) is not None:
23
21
  xdoc = XDoc.from_et(xdoc_elem, doc_frags)
22
+ description = Description.from_et(et_element.find("DESC"), doc_frags)
24
23
 
25
24
  return RelatedDoc(
26
- description=description,
27
25
  xdoc=xdoc,
26
+ description=description,
28
27
  )
29
28
 
30
29
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxtools/scaleconstr.py CHANGED
@@ -35,7 +35,6 @@ class ScaleConstr:
35
35
  value_type: DataType) -> "ScaleConstr":
36
36
  short_label = et_element.findtext("SHORT-LABEL")
37
37
  description = Description.from_et(et_element.find("DESC"), doc_frags)
38
-
39
38
  lower_limit = Limit.limit_from_et(
40
39
  odxrequire(et_element.find("LOWER-LIMIT")), doc_frags, value_type=value_type)
41
40
  upper_limit = Limit.limit_from_et(
odxtools/singleecujob.py CHANGED
@@ -18,12 +18,16 @@ from .utils import dataclass_fields_asdict
18
18
  class SingleEcuJob(DiagComm):
19
19
  """A single ECU job is a diagnostic communication primitive.
20
20
 
21
- A single ECU job is more complex than a diagnostic service and is not provided natively by the ECU.
22
- In particular, the job is defined in external programs which are referenced by the attribute `.prog_codes`.
21
+ A single ECU job is more complex than a diagnostic service and is
22
+ not provided natively by the ECU. In particular, the job is
23
+ defined in external programs which are referenced by the attribute
24
+ `.prog_codes`.
23
25
 
24
- In contrast to "multiple ECU jobs", a single ECU job only does service calls to a single ECU.
26
+ In contrast to "multiple ECU jobs", a single ECU job only involves
27
+ calls to services provided by a single ECU.
25
28
 
26
- Single ECU jobs are defined in section 7.3.5.7 of the ASAM MCD-2 standard.
29
+ Single ECU jobs are defined in section 7.3.5.7 of the ASAM MCD-2
30
+ standard.
27
31
  """
28
32
 
29
33
  prog_codes: List[ProgCode]
odxtools/specialdata.py CHANGED
@@ -9,19 +9,11 @@ from .snrefcontext import SnRefContext
9
9
 
10
10
  @dataclass
11
11
  class SpecialData:
12
+ """This corresponds to the SD XML tag"""
12
13
  semantic_info: Optional[str] # the "SI" attribute
13
14
  text_identifier: Optional[str] # the "TI" attribute, specifies the language used
14
15
  value: str
15
16
 
16
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
17
- return {}
18
-
19
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
20
- pass
21
-
22
- def _resolve_snrefs(self, context: SnRefContext) -> None:
23
- pass
24
-
25
17
  @staticmethod
26
18
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "SpecialData":
27
19
  semantic_info = et_element.get("SI")
@@ -30,3 +22,12 @@ class SpecialData:
30
22
 
31
23
  return SpecialData(
32
24
  semantic_info=semantic_info, text_identifier=text_identifier, value=value)
25
+
26
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
27
+ return {}
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ pass
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ pass
@@ -11,6 +11,7 @@ from .specialdatagroupcaption import SpecialDataGroupCaption
11
11
 
12
12
  @dataclass
13
13
  class SpecialDataGroup:
14
+ """This corresponds to the SDG XML tag"""
14
15
  sdg_caption: Optional[SpecialDataGroupCaption]
15
16
  sdg_caption_ref: Optional[OdxLinkRef]
16
17
  values: List[Union["SpecialDataGroup", SpecialData]]
@@ -3,14 +3,14 @@ from dataclasses import dataclass
3
3
  from typing import List, Literal, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from typing_extensions import SupportsBytes, override
6
+ from typing_extensions import override
7
7
 
8
8
  from .decodestate import DecodeState
9
9
  from .diagcodedtype import DctType, DiagCodedType
10
10
  from .encodestate import EncodeState
11
11
  from .exceptions import odxassert, odxraise, odxrequire
12
12
  from .odxlink import OdxDocFragment
13
- from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
13
+ from .odxtypes import AtomicOdxType, BytesTypes, DataType, odxstr_to_bool
14
14
  from .utils import dataclass_fields_asdict
15
15
 
16
16
 
@@ -21,6 +21,14 @@ class StandardLengthType(DiagCodedType):
21
21
  bit_mask: Optional[int]
22
22
  is_condensed_raw: Optional[bool]
23
23
 
24
+ @property
25
+ def dct_type(self) -> DctType:
26
+ return "STANDARD-LENGTH-TYPE"
27
+
28
+ @property
29
+ def is_condensed(self) -> bool:
30
+ return self.is_condensed_raw is True
31
+
24
32
  @staticmethod
25
33
  @override
26
34
  def from_et(et_element: ElementTree.Element,
@@ -43,14 +51,6 @@ class StandardLengthType(DiagCodedType):
43
51
  return StandardLengthType(
44
52
  bit_length=bit_length, bit_mask=bit_mask, is_condensed_raw=is_condensed_raw, **kwargs)
45
53
 
46
- @property
47
- def dct_type(self) -> DctType:
48
- return "STANDARD-LENGTH-TYPE"
49
-
50
- @property
51
- def is_condensed(self) -> bool:
52
- return self.is_condensed_raw is True
53
-
54
54
  def __post_init__(self) -> None:
55
55
  if self.bit_mask is not None:
56
56
  maskable_types = (DataType.A_UINT32, DataType.A_INT32, DataType.A_BYTEFIELD)
@@ -91,7 +91,7 @@ class StandardLengthType(DiagCodedType):
91
91
  return used_mask.to_bytes((bit_sz + 7) // 8, endianness)
92
92
 
93
93
  sz: int
94
- if isinstance(internal_value, (bytearray, SupportsBytes)):
94
+ if isinstance(internal_value, BytesTypes):
95
95
  sz = len(bytes(internal_value))
96
96
  else:
97
97
  sz = (odxrequire(self.get_static_bit_length()) + 7) // 8
@@ -107,7 +107,7 @@ class StandardLengthType(DiagCodedType):
107
107
 
108
108
  if self.is_condensed:
109
109
  int_value: int
110
- if isinstance(internal_value, (bytearray, SupportsBytes)):
110
+ if isinstance(internal_value, BytesTypes):
111
111
  int_value = int.from_bytes(internal_value, 'big')
112
112
  elif isinstance(internal_value, int):
113
113
  int_value = internal_value
@@ -126,14 +126,14 @@ class StandardLengthType(DiagCodedType):
126
126
 
127
127
  mask_bit += 1
128
128
 
129
- if isinstance(internal_value, (bytearray, SupportsBytes)):
129
+ if isinstance(internal_value, BytesTypes):
130
130
  return result.to_bytes(len(internal_value), 'big')
131
131
 
132
132
  return result
133
133
 
134
134
  if isinstance(internal_value, int):
135
135
  return internal_value & self.bit_mask
136
- if isinstance(internal_value, (bytearray, SupportsBytes)):
136
+ if isinstance(internal_value, BytesTypes):
137
137
  int_value = int.from_bytes(internal_value, 'big')
138
138
  int_value &= self.bit_mask
139
139
  return int_value.to_bytes(len(bytes(internal_value)), 'big')
@@ -146,7 +146,7 @@ class StandardLengthType(DiagCodedType):
146
146
  return raw_value
147
147
  if self.is_condensed:
148
148
  int_value: int
149
- if isinstance(raw_value, (bytearray, SupportsBytes)):
149
+ if isinstance(raw_value, BytesTypes):
150
150
  int_value = int.from_bytes(raw_value, 'big')
151
151
  elif isinstance(raw_value, int):
152
152
  int_value = raw_value
@@ -164,16 +164,16 @@ class StandardLengthType(DiagCodedType):
164
164
 
165
165
  mask_bit += 1
166
166
 
167
- if isinstance(raw_value, (bytearray, SupportsBytes)):
167
+ if isinstance(raw_value, BytesTypes):
168
168
  return result.to_bytes(len(raw_value), 'big')
169
169
 
170
170
  return result
171
171
  if isinstance(raw_value, int):
172
172
  return raw_value & self.bit_mask
173
- if isinstance(raw_value, (bytearray, SupportsBytes)):
173
+ if isinstance(raw_value, BytesTypes):
174
174
  int_value = int.from_bytes(raw_value, 'big')
175
175
  int_value &= self.bit_mask
176
- return int_value
176
+ return int_value.to_bytes(len(raw_value), 'big')
177
177
 
178
178
  odxraise(f'Can not apply a bit_mask on a value of type {type(raw_value)}')
179
179
  return raw_value
odxtools/statechart.py CHANGED
@@ -30,6 +30,7 @@ class StateChart(IdentifiableElement):
30
30
  @staticmethod
31
31
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "StateChart":
32
32
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
33
+
33
34
  semantic: str = odxrequire(et_element.findtext("SEMANTIC"))
34
35
 
35
36
  state_transitions = [
@@ -54,15 +55,18 @@ class StateChart(IdentifiableElement):
54
55
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
55
56
  odxlinks = {self.odx_id: self}
56
57
 
57
- for st in self.states:
58
- odxlinks.update(st._build_odxlinks())
59
-
60
58
  for strans in self.state_transitions:
61
59
  odxlinks.update(strans._build_odxlinks())
62
60
 
61
+ for st in self.states:
62
+ odxlinks.update(st._build_odxlinks())
63
+
63
64
  return odxlinks
64
65
 
65
66
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
67
+ for strans in self.state_transitions:
68
+ strans._resolve_odxlinks(odxlinks)
69
+
66
70
  for st in self.states:
67
71
  st._resolve_odxlinks(odxlinks)
68
72
 
@@ -77,10 +81,10 @@ class StateChart(IdentifiableElement):
77
81
  # does that mean?
78
82
  self._start_state = resolve_snref(self.start_state_snref, self.states, State)
79
83
 
80
- for st in self.states:
81
- st._resolve_snrefs(context)
82
-
83
84
  for strans in self.state_transitions:
84
85
  strans._resolve_snrefs(context)
85
86
 
87
+ for st in self.states:
88
+ st._resolve_snrefs(context)
89
+
86
90
  context.state_chart = None
@@ -0,0 +1,186 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Generator, Union
4
+
5
+ from .exceptions import odxraise
6
+ from .odxtypes import ParameterValueDict
7
+ from .state import State
8
+ from .statechart import StateChart
9
+
10
+ if TYPE_CHECKING:
11
+ from .diaglayers.diaglayer import DiagLayer
12
+ from .diagservice import DiagService
13
+
14
+
15
+ @dataclass
16
+ class StateMachine:
17
+ """Objects of this class represent the runtime state of a state chart
18
+
19
+ It is used to track which services can be executed at a given time.
20
+
21
+ Usage example (using asynchronous communication routines):
22
+
23
+ ```python
24
+ ecu = db.ecu_variants.my_ecu_variant
25
+ fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
26
+
27
+ for raw_request in (executor := fsm.execute(ecu.services.my_service,
28
+ param1="hello",
29
+ param2=123)):
30
+ # send raw request to the ECU (usually using ISO-TP or DoIP), and
31
+ # receive the response
32
+ await send_to_ecu(raw_request)
33
+ while odxtools.is_response_pending(raw_response := await receive_from_ecu()):
34
+ pass
35
+
36
+ executor.send(raw_response)
37
+
38
+ print(f"Request send: {fsm.succeeded}")
39
+ ```
40
+
41
+ Alternatively, more of the glue functionality can be handled by
42
+ the calling code:
43
+
44
+ ```python
45
+
46
+ ecu = db.ecu_variants.my_ecu_variant
47
+ fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
48
+
49
+ service = ecu.services.my_service
50
+ request_param_values = { "param1": "hello", "param2":123 }
51
+ if any(pcs.applies(fsm, service.request.parameters, **request_param_values)
52
+ for pcs in service.pre_condition_states):
53
+
54
+ # send raw request to the ECU (usually using ISO-TP or DoIP), and
55
+ # receive the response
56
+ raw_req = service.request.encode(**request_param_values)
57
+ await send_data_to_ecu(raw_req)
58
+ while odxtools.is_response_pending(raw_resp := await receive_from_ecu()):
59
+ pass
60
+
61
+ done = False
62
+ for decoded_resp_msg in ecu.decode_response(raw_resp, raw_req):
63
+ for stransref in service.state_transition_refs:
64
+ if stransref.execute(decoded_resp.parameters, decoded_resp_msg.param_dict):
65
+ done = True
66
+ break
67
+ if done:
68
+ break
69
+ else:
70
+ raise RuntimeException(f"Cannot execute request for service {service.short_name}")
71
+ ```
72
+
73
+ """
74
+
75
+ succeeded: bool
76
+
77
+ @property
78
+ def diag_layer(self) -> "DiagLayer":
79
+ return self._diag_layer
80
+
81
+ @property
82
+ def state_chart(self) -> StateChart:
83
+ return self._state_chart
84
+
85
+ @property
86
+ def active_state(self) -> State:
87
+ return self._active_state
88
+
89
+ @active_state.setter
90
+ def active_state(self, value: State) -> None:
91
+ self._active_state = value
92
+
93
+ def __init__(self, diag_layer: "DiagLayer", state_chart: StateChart) -> None:
94
+ self.succeeded = True
95
+ self._diag_layer = diag_layer
96
+ self._state_chart = state_chart
97
+ self._active_state = state_chart.start_state
98
+
99
+ def execute(self, service: "DiagService", **service_params: Any
100
+ ) -> Generator[bytes, Union[bytes, bytearray, ParameterValueDict], None]:
101
+ """Run a diagnostic service and update the state machine
102
+ depending on the outcome.
103
+
104
+ This simplifies error handling, en- and decoding etc.
105
+
106
+ Usage example (using asynchronous communication routines):
107
+
108
+ ```python
109
+ ecu = db.ecu_variants.my_ecu_variant
110
+ fsm = StateMachine(ecu, ecu.state_charts.my_state_chart)
111
+
112
+ for raw_request in (executor := fsm.execute(ecu.services.my_service,
113
+ param1="hello",
114
+ param2=123)):
115
+ # send raw request to the ECU (usually using ISO-TP or DoIP), and
116
+ # receive the response
117
+ await send_to_ecu(raw_request)
118
+ while odxtools.is_response_pending(raw_response := await receive_from_ecu()):
119
+ pass
120
+
121
+ executor.send(raw_response)
122
+
123
+ print(f"Request send: {fsm.succeeded}")
124
+ ```
125
+
126
+ """
127
+ if service.request is None:
128
+ odxraise("Services without requests are not allowed in this context")
129
+ self.succeeded = False
130
+ return
131
+
132
+ # the service can be executed if any of the specified
133
+ # precondition states is fulfilled. (TODO: correct?)
134
+ if service.pre_condition_state_refs is not None:
135
+ if not any(
136
+ x.applies(self, service.request.parameters, service_params)
137
+ for x in service.pre_condition_state_refs):
138
+ # if all preconditions which are applicable are
139
+ # invalid (i.e., they evaluate to False), we must not
140
+ # execute the service.
141
+ self.succeeded = False
142
+ return
143
+
144
+ raw_req = service.request.encode(**service_params)
145
+ # ask the calling code to send the request to the ECU and
146
+ # report back the reply
147
+ raw_resp = yield raw_req
148
+
149
+ decoded_req_params = service.request.decode(raw_req)
150
+ for stransref in service.state_transition_refs:
151
+ # check if the state transition applies for the
152
+ # request. Note that the ODX specification is unclear
153
+ # about which kind of parameters are relevant for
154
+ # STATE-TRANSITION-REF
155
+ if stransref.execute(self, service.request.parameters, decoded_req_params):
156
+ self.succeeded = True
157
+ yield b''
158
+ return
159
+
160
+ if raw_resp is None:
161
+ raise RuntimeError("The calling code must send back a reply")
162
+ elif isinstance(raw_resp, (bytes, bytearray)):
163
+ for decoded_resp_msg in self.diag_layer.decode_response(raw_resp, raw_req):
164
+ for stransref in service.state_transition_refs:
165
+ # we only execute the first applicable state
166
+ # transition: The spec seems to imply a
167
+ # deterministic state machine and chaining
168
+ # transistions most likely is not what the user
169
+ # expects. (The spec seems to be a bit loose on
170
+ # this front...)
171
+ if stransref.execute(self, decoded_resp_msg.coding_object.parameters,
172
+ decoded_resp_msg.param_dict):
173
+ self.succeeded = True
174
+ yield b''
175
+ return
176
+ else:
177
+ resp_object = service.positive_responses[0]
178
+ for stransref in service.state_transition_refs:
179
+ if stransref.execute(self, resp_object.parameters, raw_resp):
180
+ self.succeeded = True
181
+ yield b''
182
+ return
183
+
184
+ self.succeeded = True
185
+ yield b''
186
+ return