Defining statecharts¶
Defining statecharts in YAML¶
Statecharts can be defined using a YAML format.
A YAML definition of a statechart can be easily imported to a StateChart
instance.
The module pyss.io
provides a convenient loader import_from_yaml()
which takes a textual YAML definition
of a statechart. It also provides ways to export statechart to YAML.
-
pyss.io.
import_from_yaml
(data: str) → pyss.model.StateChart¶ Import a statechart from a YAML representation.
Parameters: data – string or any equivalent object Returns: a StateChart instance
-
pyss.io.
export_to_yaml
(statechart: pyss.model.StateChart) → str¶ Export given StateChart instance to YAML
Parameters: statechart – Returns: A textual YAML representation
For example:
from pyss import io, model
statechart = io.import_from_yaml(open('examples/concrete/elevator.yaml'))
assert isinstance(statechart, model.StateChart)
Although the parser is quite robut and should warn about most syntaxic problems, a StateChart
instance has a
validate()
method that can perform numerous other checks.
This method either returns True
if the statechart seems to
be valid, or raises an AssertionError
exception with a meaningful message.
Statechart elements¶
This section explains how the elements that compose a statechart can be defined using YAML. If you are not familiar with YAML, have a look at YAML official documentation.
Statechart¶
The root of the YAML file must declare a statechart:
statechart:
name: Name of this statechart
initial: name of the initial state
The name
and the initial
state are mandatory.
You can declare code to execute on the initialization of the statechart using on entry
, as follows:
statechart:
name: with code
initial: s1
on entry: x = 1
Code can be written on multiple lines:
on entry: |
x = 1
y = 2
States¶
A statechart has to declare a (nonempty) list of states using states
.
Each state consist of at least a name
. Depending on the state type, several other fields can be declared.
statemachine:
name: with state
initial: s1
states:
- name: s1
Entry and exit actions¶
For each state, it is possible to specify the code that has to be executed when entering and leaving the
state using on entry
and on exit
as follows:
- name: s1
on entry: x += 1
on exit: |
x -= 1
y = 2
Shallow and deep history sates¶
A state that declares a type: shallow history
property is an shallow history state.
If you want an history state to follow the deep semantic, set type
to deep history
.
- name: history state
type: shallow history
deep: True
An history state can optionally define an initial memory using initial
.
- name: history state
type: deep history
initial: s1
Importantly, the initial
memory value must refer to a parent’s substate.
Compound states¶
A state that is neither a final state nor an history state can contain nested states. Such a state is a compound state.
- name: compound state
states:
- name: nested state 1
- name: nested state 2
states:
- name: nested state 2a
Notice that PySS does not explicit the concept of region. As a region is mainly a logical set of nested states, it can be emulated using a compound state.
Orthogonal states¶
Orthogonal states (sometimes referred as parallel states) must declare their nested states using parallel states
instead of states
.
statechart:
name: Concurrent processes state machine
initial: processes
states:
- name: processes
parallel states:
- name: process 1
- name: process 2
A compound orthogonal state can not be declared at top level, and should be nested in a compound state, as
illustrated in the previous example. In other words, it is not allowed to define parallel states
instead of states
in this previous example.
Transitions¶
States, compound states and parallel states can declare transitions with transitions
:
- name: state with transitions
transitions:
- target: other state
A transition can define a target
(name of the target state), a guard
(a Boolean expression
that will be evaluated), an event
(name of the event) and an action
(code that will be executed if the
transition is processed). All those fields are optional. A full example of a transition:
- name: state with a transition
transitions:
- target: other state
event: click
guard: x > 1
action: print('Hello World!')
An internal transition is a transition that does not declare a target
.
To prevent trivial infinite loops on execution, an internal transition must either define an event
or define a guard.
Example¶
Full example of a statechart definition using YAML.
statechart:
name: Elevator
initial: active
on entry: |
current = 0
destination = 0
class Doors:
def __init__(self):
self.opened = True
def open(self):
print('Opening doors')
self.opened = True
def close(self):
print('Closing doors')
self.opened = False
doors = Doors()
states:
- name: active
parallel states:
- name: movingElevator
initial: doorsOpen
states:
- name: doorsOpen
transitions:
- target: doorsClosed
guard: destination != current
action: doors.close()
- target: doorsClosed
event: after10s
guard: current > 0
action: destination = 0
- name: doorsClosed
transitions:
- target: movingUp
guard: destination > current
- target: movingDown
guard: destination < current and destination >= 0
- name: moving
transitions:
- target: doorsOpen
guard: destination == current
action: doors.open()
states:
- name: movingUp
on entry: current = current + 1
transitions:
- target: movingUp
guard: destination > current
- name: movingDown
on entry: current = current - 1
transitions:
- target: movingDown
guard: destination < current
- name: floorListener
initial: floorSelecting
states:
- name: floorSelecting
transitions:
- target: floorSelecting
event: floorSelected
action: destination = event.data['floor']
Defining statecharts in Python¶
While it is not very convenient, it is still possible to define the statechart using Python objects. The following sections detail the Python structure of a statechart.
The module pyss.model
contains several classes and mixins to define
states, transitions and events. Apart from
StateMixin
, ActionStateMixin
,
TransitionStateMixin
, and CompositeStateMixin
, it defines:
-
class
pyss.model.
Event
(name: str, data: dict=None)¶ Simple event with a name and (optionally) some data.
Parameters: - name – Name of the event
- data – additional data (mapping, dict-like)
-
class
pyss.model.
Transition
(from_state: str, to_state: str=None, event: pyss.model.Event=None, guard: str=None, action: str=None)¶ A Transition between two states. Transition can be eventless or internal (but not both at once). A condition (code as string) can be specified as a guard.
Parameters: - from_state – name of the source state
- to_state – name of the target state (if transition is not internal)
- event – event (if any)
- guard – condition as code (if any)
- action – action as code (if any)
-
internal
¶ Boolean indicating whether this transition is an internal transition.
-
eventless
¶ Boolean indicating whether this transition is an eventless transition.
-
class
pyss.model.
BasicState
(name: str, on_entry: str=None, on_exit: str=None)¶ A basic state, with a name, transitions, actions, etc. but no child state.
Parameters: - name – name of this state
- on_entry – code to execute when state is entered
- on_exit – code to execute when state is exited
-
class
pyss.model.
CompoundState
(name: str, initial: str=None, on_entry: str=None, on_exit: str=None)¶ Compound states must have children states.
Parameters: - name – name of this state
- initial – name of the initial state
- on_entry – code to execute when state is entered
- on_exit – code to execute when state is exited
-
class
pyss.model.
OrthogonalState
(name: str, on_entry: str=None, on_exit: str=None)¶ Orthogonal states run their children simultaneously.
Parameters: - name – name of this state
- on_entry – code to execute when state is entered
- on_exit – code to execute when state is exited
-
class
pyss.model.
HistoryState
(name: str, initial: str=None, deep: bool=False)¶ History state can be either ‘shallow’ (default) or ‘deep’. A shallow history state resumes the execution of its parent. A deep history state resumes the execution of its parent, and of every nested active states in its parent.
Parameters: - name – name of this state
- initial – name of the initial state
- deep – Boolean indicating whether a deep semantic (True) or a shallow semantic (False) should be used
-
class
pyss.model.
FinalState
(name: str, on_entry: str=None, on_exit: str=None)¶ Final state has NO transition and is used to detect state machine termination.
Parameters: - name – name of this state
- on_entry – code to execute when state is entered
- on_exit – code to execute when state is exited
-
class
pyss.model.
StateChart
(name: str, initial: str, on_entry: str=None)¶ Python structure for a statechart
Parameters: - name – Name of this statechart
- initial – Initial state
- on_entry – Code to execute when this statechart is initialized for execution
-
register_state
(state: pyss.model.StateMixin, parent: str)¶ Register given state. This method also register the given state to its parent.
Parameters: - state – state to add
- parent – name of its parent
-
register_transition
(transition: pyss.model.Transition)¶ Register given transition and register it on the source state
Parameters: transition – transition to add
-
states
¶ A dictionary that associates a
StateMixin
to a state name
-
parent
¶ A dictionary that associates to each state (name) the name of its parent, or
None
if it has no parent.
-
events
(state_or_states=None) → list¶ List of possible event names. If state_or_states is omitted, returns all possible event names. If state_or_states is a string, return the events for which a transition exists with a from_state equals to given string. If state_or_states is a list of state names, return the events for all those states.
Parameters: state_or_states – None
, a state name or a list of state names.Returns: A list of event names
-
ancestors_for
(state: str) → list¶ Return an ordered list of ancestors for the given state. Ancestors are ordered by decreasing depth.
Parameters: state – name of the state Returns: state’s ancestors
-
descendants_for
(state: str) → list¶ Return an ordered list of descendants for the given state. Descendants are ordered by increasing depth.
Parameters: state – name of the state Returns: state’s descendants
-
depth_of
(state: str) → int¶ Return the depth of given state (0-indexed).
Parameters: state – name of the state Returns: state depth
-
least_common_ancestor
(s1: str, s2: str) → str¶ Return the deepest common ancestor for s1 and s2, or
None
if there is no common ancestor except root (top-level) state.Parameters: - s1 – name of first state
- s2 – name of second state
Returns: name of deepest common ancestor or
None
-
leaf_for
(states: list) → list¶ Considering the list of states names in states, return a list containing each element of states such that this element has no descendant in states. In other words, this method returns the leaves from the given list of states.
Parameters: states – a list of names Returns: the names of the leaves in states
-
validate
() → bool¶ Validate current statechart:
- C1. Check that transitions refer to existing states
- C2. Check that history can only be defined as a child of a CompoundState
- C3. Check that initial state refer to a parent’s child
- C4. Check that orthogonal states have at least one child
- C5. Check that there is no internal eventless guardless transition
Returns: True if no check fails Raises AssertionError: if a check fails
Consider the source of pyss.io
as an example of how to construct a statechart using pyss.model
.