Definition section

The definition section is a text area. Per default, it is located to the left of the canvas. You can move or resize it like other elements on the canvas.

In the definition section, you have to define entities you want to use in your statechart. This includes variables, events, and operations. Variables and events have to be defined in the scope of a named interface or the internal interface. Especially useful for larger parts of a statechart model is the possibility to use namespaces.

When it comes to code generation all these elements are properly reflected in the generated source code as far as the respective target language supports it.

Scopes

Namespaces

The statechart language allows to define unique namespaces. They can be used to qualify references to statechart elements.

namespace trafficlights

Imports

Imports can be used to make elements visible that are defined in another file. In the default domain it is possible to import other statechart types (see also multi state machine modeling ).

Different statechart domains allow to import different file types. For example, the C/C++ domain allows to import header files.

// multi state machine modeling (default domain)
import: "LED.sct"
// header import in C/C++ domain
import: "MyHeader.h"

Interface scopes

Declarations in the interface scope are externally visible. They can be shared within the environment, e.g., client code that uses the state machine.

interface NamedInterface:
  in event event1
  out event event3 : integer
  var variable1 : integer

Please note: All elements defined in a named interface must be referenced using that interface’s name, such as NamedInterface.event1, NamedInterface.event3, or NamedInterface.variable1.

It is also possible to have a single unnamed interface, which is externally visible as well:

interface:
  in event event1

It behaves exactly as a named interface.

Internal scope

Definitions made in the internal scope are visible within the statechart only, but not to the outside. It is not possible to access them from client code.

internal:
  var localVariable1: integer
  event localEvent: integer
  operation localOperation (int1 : integer, int2 : integer): integer
  localEvent2 / raise NamedInterface.event3 :
  localOperation(valueof(localEvent) , NamedInterface.variable1)

Variables

Variables have to be defined in an internal or external interface. Variables in the internal scope are not visible to any client code.

var variable1: real

Variables that are defined in a named interface have to be referenced using the interface name, see section "Interfaces".

Variables are always typed, see section "Types".

Variables can be marked by the readonly keyword, which has the effect that client code can only read them. Please note that this restriction is imposed on the client code only. Readonly variables can still be modified from within the statechart.

var readonly pi: real = 3.1415

Constants

A variable can be immutable, i.e., constant. Such variables are marked by the const keyword. They can neither be modified by the client code nor from within the statechart.

const variable1: real

Events

An event is something of importance that happens at a certain point in time in the context of a state machine. For example, a user pushes a button, a temperature sensor delivers a value, a period of time has passed, etc. An event can be of one of three basic types:

  • Internal events are meant to be raised and processed by the state machine internally only.
  • External events are defined in an interface scope and are either incoming or outgoing.

Events that are defined in a named interface need to be referenced using the interface’s name, see section "Interfaces".

Events can be processed in triggers, see section "Trigger specification". In order to raise an event in the state machine, see section "Raising an event". For details on the processing of events see section "Raising and processing an event".

Incoming and outgoing events

An event in an interface scope has a direction. It is either incoming or outgoing.

  • An incoming event is raised somewhere in the environment, delivered to the state machine, and processed by the latter.
  • An outgoing event is raised within the state machine and delivered to the outside.

In an event declaration, the corresponding keywords are in and out, followed by the keyword event, followed by the event’s name.

interface NamedInterface:
  in event event1
  out event event2

Events in the internal scope do neither enter nor leave the state machine, thus they don’t have a direction.

internal:
  event ev1

Events with values

An event can be typed and can carry a value:

internal:
  event event1 : integer

Read access to an event’s value is possible in the statechart using the valueof() built-in method, see section "Built-in methods" for details.

Please note: Reading an event’s value is possible only when the event actually occurs, for example in a guard condition.

Example (reading an event’s value in a transition’s guard condition):

event1 [valueof(event1) == 6]

An event parameter can be specified when raising an event, as in the following example:

raise event1 : 3+3

Regarding the syntax of raising an event, see section "Raising an event". Regarding the more complicated details of processing an event, see section "Raising and processing an event".

Operations

An operation connects a state machine to the outside world by making external behaviour accessible to the state machine.

A state machine typically interacts with the outside world, in order, for example, to read data from a sensor, engage an actuator, etc. The client software has to provide such behaviour as functions or methods in a programming language the state machine can interact with. An operation is the statechart language’s means to call such an external procedure from the state machine.

Consider, for example, a C function float read_sensor() that should be called by the state machine. To interface with that function, an operation read_sensor must first be defined in the state machine, and can then be called from, e.g., a state or transition. At execution time, a call to the read_sensor operation is mapped to an actual call of the external C function with the same name. To make this work, the statechart must have been generated as source code of the respective target language, of course.

It is the purpose of a code generator to create a suitable construct in the respective target language for the procedure call. For details on code generation, please see section "Generating state machine code".

Operations can have none, one, or multiple parameters. A parameter is declared with a name and a type. An operation may have a single or no return type similar to usual programming languages. Please see section "Types" for details.

operation myOperation (xValue: integer, yValue: integer): integer

You can call myOperation by using positional parameters:

myOperation(27, 42)

This call assigns the value 27 to the operation’s xValue parameter and 42 to yValue.

Alternatively, you can use named parameters, like in the following example:

myOperation(xValue = 27, yValue = 42)

Named parameters make their order irrelevant. The following call is semantically equivalent to the one above:

myOperation(yValue = 42, xValue = 27)

While an operation call with named parameters is longer than the equivalent call with positional parameters, named parameters are a great means for self-documenting code (especially if the parameter names are more telling than in the example above).

Operations with a variable number of parameters are supported, too. To be more exact, it is possible to specify an operation’s last parameter as being allowed to occur an arbitrary number of times. This feature is usually called „varargs”, short for „variable number of arguments”. Compared to a „regular” operation parameter declaration, the varargs parameter name is followed by three dots ( ...) to indicate it may occur zero or more times.

For example, an operation sum adding a variable number of summands and returning their sum could be defined as follows:

operation sum(count: integer, summand...: integer): integer

Sample calls of this operation are sum(1, 42), sum(2, 4711, 815), sum(7, 555, 338, 881, 192, 69, 999, 610), or even sum(0). In this example the first parameter advises the called function or method of how many instances of the last parameter it should expect. Whether such information is needed or not depends on the target language. For example, a C function needs to receive this information while a Java method does not.

Specifying semantic variants

Statecharts exist in different semantic flavors and YAKINDU supports a large range of semantical options. These variants can be defined for each statechart individually by specifying by annotating statecharts.This section gives an overview of the different annotations which are available. As all annotations influence the behavior of statecharts specifying these will have impact on the simulation and the generated code. All annotations are supported by all code generators.

@CycleBased

The @CycleBased annotation specifies that the cycle-based execution scheme is to be used.

Synopsis: @CycleBased( period)

The mandatory parameter period indicates the suggested period of time between two successive run-to-completion steps in milliseconds. Only the statechart simulator and the SCTUnit testing framework take the period value into account, however. It is neither of significance to nor reflected in the generated code, and thus it remains the client code’s responsibility to explicitly call runCycle() – and to decide when to do so.

If the definition section contains neither the @CycleBased nor the @EventDriven annotation, the state machine will behave as if @EventDriven would have been coded.

Example: The definition section below specifies that the state machine should behave according to the cycle-based execution scheme with an interval of 100 milliseconds between two cycles.

@CycleBased(100)

interface:
    in event e

@EventDriven

The @EventDriven annotation specifies that the event-driven execution scheme is to be used.

Synopsis: @EventDriven

If the definition section contains neither the @CycleBased nor the @EventDriven annotation, the state machine will behave as if @EventDriven would have been coded.

Example: The definition section below specifies that the state machine should behave according to the event-driven execution scheme.

@EventDriven

interface:
    in event e

@SuperSteps

The @SuperSteps(yes | no) annotation enables or disables the superstep semantic for a run-to-completion step. In contrast to a regular step, a superstep executes all valid subsequent state transitions. If no @SuperSteps annotation is specified, the state machine will behave as if @SuperSteps(no) would have been coded.

Take a look at the example model below:

Example model for superstep semantic

Without the superstep semantic, an incoming event e causes only one state transition, e.g. from state A to state B. However, when superstep semantic is enabled, raising event e causes a state transition from A over B to C. As there is no other outgoing transition from C, the local reaction is also executed, hence x is set to 42.

You can think of the superstep semantic as a loop which performs a regular step until no state is entered in the regular step. See the following pseudo-code (a regular step is called microStep here):

do {
    stateEntered = false
    microStep()
} while (stateEntered)

Please note, the flag stateEntered is set to true inside a microStep whenever a state is entered. During the execution of a superstep all activated events remain active. The choice of superstep semantics has no impact on the semantic variants specified by other annotations like event-driven or cycle-based state machines, event buffering or parent-first / child-first execution order.

Local events can also lead to a multi-step execution of a state machine. The execution of these multiple steps are a result of the iterative processing of the local event buffer (queue in event-driven case or vector when cycle-based). Supersteps are applied to each of these local event processing steps. So event loops are executed on a higher execution level than supersteps. To sum up, let’s take a look at a slightly more complicated example:

Example model for superstep semantic in event-driven statechart

What happens when state A is active and event e is raised? First, event e invokes a superstep like in the previous example. While taking the state transition to B and C, the local events local1 and local2 are put into the internal event buffer (queue or vector). However, they are not visible until the processing of event e is finished. That means that the local reaction in state C is executed because no outgoing transition can be taken. Hence, x is set to 42. Afterwards, the local events are processed, event by event in the event-driven case or all at once if cycle-based. This again invokes a superstep and state L is entered. As with each self-transition, state L is exited and re-entered again, the superstep loop continues until x equals to 17. Next, internal event local2 is considered and state C is entered. The local reaction in state C is not executed, as event e is no more raised.

@ChildFirstExecution

The @ChildFirstExecution annotation specifies that the state machine should always execute the substates („children”) of an active composite state first, before executing the composite state („parent”) itself. Please see section "Parent-first versus child-first execution" for details and examples.

Synopsis: @ChildFirstExecution

Please note that @ParentFirstExecution specifies the opposite behaviour. Both @ChildFirstExecution and @ParentFirstExecution are global settings for all composite states.

If the definition section contains neither the @ParentFirstExecution nor the @ChildFirstExecution annotation, the state machine will behave as if @ParentFirstExecution would have been coded.

@ParentFirstExecution

The @ParentFirstExecution annotation specifies that the state machine should always first execute an active composite state („parent”) itself, before executing its substates („children”). Please see section "Parent-first versus child-first execution" for details and examples.

Synopsis: @ParentFirstExecution

Please note that @ChildFirstExecution specifies the opposite behaviour. Both @ChildFirstExecution and @ParentFirstExecution are global settings for all composite states.

If the definition section contains neither the @ParentFirstExecution nor the @ChildFirstExecution annotation, the state machine will behave as if @ParentFirstExecution would have been coded.

@EventBuffering

The @EventBuffering annotation specifies the strategy for buffering incoming and local events. By default event buffering is enabled for incoming and local events.

Synopsis: @EventBuffering( inEvents, localEvents)

The event buffering strategy has a large impact on event processing and which constraints must be considered when integrating generated state machine code. With event buffering enabled all events will be buffered before they are processed. At the beginning of a run to completion step (RTCS) all events which will be processed during the step will be taken from the buffer and activated for processing. After the step all events are consumed. If events are raised during a run to completion step they will be stored in the buffer. So they won’t have an effect on the current step but are processed in following steps.

The kind of buffers are different for event-driven and cycle-based state machines. An event-driven state machine will use a queue for buffering events. Each event from the queue is processed in a single RTCS and events are processed in the order of arrival. As events are the trigger for run to completion steps the queue will be processed until it is empty. This means that all events which are raised during a RTCS will directly be processed so that multiple RTCS might be executed as the result of raising one event.

In contrast, cycle-based state machines are triggered by explicit calls to the state machines runCycle() method. This call triggers a RTCS in which all buffered events will be activated for processing. As cycle-based state machines process an event vector during a RTCS also the event buffer is a vector.

Statecharts support incoming events which can be raised on their interface and local events which are only visible within the state machine. Event buffering covers both kind of events but uses two separate buffers. Local events are guaranteed to be always processed before any other incoming event. This means that if local events are raised by the state machine during a RTCS these will be buffered and then be processed directly after the step finished. During processing of an local event further local events may be raised which also trigger the next step. So you have to be careful not to construct infinite local event processing loops. While the general approach is the same for event-driven and cycle-based state machines the details differ again. In the cycle-based case all local events raised within a step will be activated in the following step while in the event-driven case local events are processed one by one.

Event buffering is enabled by default but you can explicitly specify the buffering strategy using an annotation.

@EventBuffering(inEvents = true, localEvents = true)

  • inEvents (Boolean): The state machine applies event buffering for incoming events. If no incoming events are defined then this property is ignored as no buffer is used in that case. Default true.
  • localEvents (Boolean): The state machine applies event buffering for local events. This setting is ignored in the event-driven case as local events are always queued. If no local events are defined then it is also ignored as no buffer is used in that case. Default true.

While enabling event buffering is the best choice in general there are two main reasons why you may want to disable it. The first is backward compatibility to state machines developed with tool versions 3.x and earlier. The second reason is to save memory required by event buffers. You should consider the following points:

  1. If event buffering for incoming events is disabled then you must make sure that no event is raised on the state machine while the machine is processing a RTCS. This is during a runCycle() call of cycle-based state machines or during raising an event in the event-driven case. Events raised during an RTCS won’t be processed properly.
  2. If you use an external event queue e.g. provided by an RTOS then the in event queue may not be required in the event-driven case.
  3. For event-driven statecharts the local event buffering can’t be deactivated.
  4. To make cycle-based state machine code to behave like in version 3.x then you have to use @EventBuffering(inEvents=false, localEvents=false).
  5. When deactivating local event buffering for cycle-based statecharts then behavior may change considerably. Without a buffer local events are directly activated when they are raised and are only visible in the current RTCS. As a result these events are only visible to those parts in parallel regions which are processed after raising. This is called downstream visibility.
  6. When deactivating local event buffering for cycle-based statecharts then the RTCS step is guaranteed to be processed in constant time as no iteration produced by local events can occur.