State Machines

Getting Started

The smallest state machine:

statechart MyStateMachine init MyState {
  state MyState {}
}

Each state machine is given a name and must have an initial state.

States and composite states

ThingML supports simple states, composite states and history states.

// simple state  
state MyState {}

// composite state
composite state MyCompositeState init InnerState1 {
  state InnerState1 {}
  state InnerState2 {}
}

// history state
composite state MyCompositeState init InnerState3 keeps history {
  state InnerState3 {}
  state InnerState4 {}
}

Regular composite state enter their initial states whenever they are activated.

History states enter their initial state the first time they are activated, save their current state when exited and reenter that state when activated again.

Entry and exit actions

All states (composite or not) can have entry and exit actions which are executed when entering and leaving the state.

Here is an example for a simple state (the entry and exit action must be defined before any transition):

state MyState {
  on entry do /* some actions */ end
  on exit do /* some actions */ end
}

In the same way, composite states and the state machine itself can have entry and exit actions. When entering composite states the order of execution of the entry action is from the outer state to the inner state. When leaving a composite state the exit action of the inner state is executed before the exit action of the composite state.

Transitions

Transitions can be defined both on simple states and composite states. Transition can only be defined between sibling states: they cannot cross neither composite states boundaries nor region boundaries.

Transitions must have at least a target state and a trigger event (the name is optional):

state MyState1 {
  transition t1 -> MyState2
  event port?message
}

state MyState2 {}

The current version of ThingML only support events which correspond to the reception of at least one message.

Optionally transitions can have a guard condition and actions:

transition t2 -> MyState2
event myPort?myMessage
guard /* some condition */
action /* some action */

For such a transition the guard is evaluated every time the message "myMessage" is received on port "myPort". If the guard is true the transition is fired, i.e. the exit action of the current state is executed, the action of the transition is executed and the entry action of the target state is executed.

Several events can trigger a transition:

// Transition with multiple events
transition t3 -> MyState2
event myPort1?myMessage1
event myPort1?myMessage2
event myPort2?myMessage3

Internal transitions

Internal transitions can be used to implement event handlers on states which do not exit and reenter the state.

The example below shows a state with an auto-transition (i.e. a transition to itself) which is triggered by the message "myMessage1" and an internal transition triggered by the message "myMessage2".

state MyState {

  on entry /* some entry action */
  on exit /* some exit action */

  transition -> MyState 
  event myPort?myMessage1
  action /* some action */

  internal event myPort?myMessage2
  action /* some action */
}

When "myMessage1" is received, the transition is fired and the exit action is executed, followed by the transition action and the entry action.

When "myMessage2" is received the internal transition is fired and only the internal transition action is executed.

Remark: since internal transition do not execute entry and exit actions they do not support "before" and "after" actions.

Variables

Local variables can be defined on states. The scope of variables are the state on which they are defined (including its sub-states).

statechart MyStateMachine init MyState {

  property counter : Integer

  state MyState {
    on entry counter = counter + 1
  }
}

Parallel regions

State machines and composite states can contain one or more parallel regions. Each region executes independently from each other: they have their own initial, history and current state.

By default a state machine and composite states define one region. Additional regions can be added using the "region" keyword:

statechart MyStateMachine init MyState1 {

  state MyState1 {}

  region MyRegion2 init MyState2 {
    state MyState2 {}
  }

  region MyRegion3 init MyState3 {
    state MyState3 {}
  }
}

In this example the state machine is made of 3 parallel regions. The first one is defined by the state machine "MyStateMachine" and the two other ("MyRegion2" and "MyRegion3") are explicitly added.

Remark: There is no semantic difference between the three regions, the first one is defined by default just to make the syntax a bit more compact.

Here is a second example which illustrates the use of regions with history:

statechart MyStateMachine init MyState1 {

  state MyState1 {}

  composite state MyState2 init MyState3 keeps history {

    state MyState3 {}

    region MyRegion init MyState4 keeps history {
      state MyState4 {}
    }
  }
}

In the example above, the state machine contains only one region with two states. The composite state "MyState2" is made of two parallel regions: one contains the state "MyState3" and the other contains "MyState4". Both region are marked to keep history.