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.
The statechart language allows to define unique namespaces. They can be used to qualify references to statechart elements.
namespace trafficlights
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"
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.
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 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 = 2.5
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 may have an initial value.
var variable1: real = 2.5
The variable type may be omitted and inferred from the specified initial value.
var variable1 = 2.5
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
For map types the value can be assigned within curly brackets.
var myMap : map<string, string> = {'key1' : 'value1', 'key2' : 'value2}
Access for assigning and accessing values reuses the array notation.
myString = myMap['key1']
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
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:
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". As events are treated as booleans, they can also be used in guards, see section "Guard conditions". 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".
An event in an interface scope has a direction. It is either incoming or outgoing.
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
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".
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.
Statecharts exist in different semantic flavors and itemis CREATE 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.
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
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
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:
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:
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.
Please note: The example above also reveals a potential problem when using superstep semantics. If we omitted the guard
[x > 17]
at the self-transition of state L, the execution of the state machine would end up in an infinite loop. This is because a self-transition leaves and re-enters its target state, causing the superstep loop to continue. As during its execution all activated events remain active, the state machine ends up in an infinite loop. This is not the case for time triggers. Time triggers are always deactivated after the transition has been taken.
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 @ChildFirstExecution
would have been coded.
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 @ChildFirstExecution
would have been coded.
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)
true
.
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:
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.@EventBuffering(inEvents=false, localEvents=false)
.