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

Final states

A state that declares a type: final property is a final state:

- name: s1
  type: final

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_statesNone, 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.