Edit on GitHub

What are YAKINDU Statechart Tools?

The statechart tools are a central part of YAKINDU: the modular toolkit for model-driven development of embedded systems. It is based on the open-source development platform Eclipse. YAKINDU Statechart Tools (YAKINDU SCT) provide the following tools to deal with state machine diagrams:

  • Statechart diagram editor to graphically create and edit statecharts
  • Statechart simulator to simulate the behavior of statecharts
  • Code generators for Java, C, C++ to transform statecharts into code
  • Custom generator projects to easily create model-to-text transformations with Xtend or Java
  • Integrated validator to check for syntactical or semantical problems of the statechart model

The following graph shows these features and their relation to each other:

Features of YAKINDU Statechart Tools

Features of YAKINDU Statechart Tools

Which licensing rules do apply to YAKINDU Statechart Tools?

The source code of YAKINDU Statechart Tools is provided under the Eclipse Public License.

The open source code generators coming with YAKINDU Statechart Tools do not imply any license constraints on the generated code whatsoever. The generated code is property of the user (as person or organization). There is no need to make the generated code open source. The user is free to choose any license model for the generated code.

Who is behind YAKINDU Statechart Tools?

The main part of YAKINDU Statechart Tools is an open-source project, available at http://www.yakindu.org/. Most developers are working for itemis, a well-known consulting company specialized on model-based development.

You may ask: Where does the money for open-source development come from? Well, itemis is providing professional services around YAKINDU, be it training or on-site consulting, be it development of prototypes or implementation of full-blown IDEs for programming languages, you name it. The itemis YAKINDU developers do not only know the framework very well but are also experts in programming and domain-specific language design. If you need advice get in contact with itemis' YAKINDU team.

Who is using YAKINDU Statechart Tools?

Initially YAKINDU Statechart Tools have been designed for the embedded systems industry: automotive, system controls, vending machines etc. However, YAKINDU Statechart Tools are bringing the benefits of Finite State Machines (FSM) and Harel Statecharts to everyone who needs to design, simulate and develop behavior. People can use YAKINDU Statechart Tools to generate Java, C, or C++ code.

Edit on GitHub

Editing statecharts

Creating and deleting statecharts

Statecharts are comprised in statechart model files. The filename extension of these files is .sct. Their internal format is XML Metadata Interchange or XMI, which is an XML language.

Creating a statechart

In order to create a new statechart file, use Project Explorer view:

  1. Right-click on a project or on a folder you want to create the new statechart in. The context menu appears.
  2. In the context menu, select New → Other…. The New dialog appears.
  3. In the New dialog, select YAKINDU SCT → Statechart Model. The New YAKINDU Statechart wizard appears.
  4. In the wizard, select a statechart domain.
  5. Click Next >.
  6. Enter a filename for the statechart file to be created. The filename extension must be .sct.
  7. Optionally you can change the project or folder to create the new statechart file in.
  8. Click Finish.
  9. If the Confirm Perspective Switch dialog appears, answer its question as you see fit.
  10. The new statechart file is created in the location you specified and opened in the statechart editor.

Copying an existing statechart

In order to copy an existing statechart file, proceed as follows:

  1. Right-click on its filename in the Project Explorer view. The context menu appears.
  2. In the context menu, select Copy.
  3. Right-click on the project or folder you want to insert the copied file in. The context menu appears.
  4. In the context menu, select Paste.
  5. If there already is a file with the same name as the file to be copied in the target project or folder – as is always the case if you are copying a file within the same project or directory –, the Resource Exists dialog appears. It displays the names and last modifications times of source and target files and asks you whether you want to overwrite the target file with the source file.
    1. Click Yes To All to overwrite the target file plus any further target files. This option might be usefule in case you are copying multiple files.
    2. Click No to not overwrite the target file.
    3. Click Cancel to cancel to copying operation.
    4. Click Yes to overwrite the target file.

Deleting a statechart

In order to delete a statechart file, proceed as follows:

  1. Right-click on its filename in the Project Explorer view. The context menu appears.
  2. In the context menu, select Delete.
  3. The Delete Resources confirmation dialog appears. You have three choices:
    1. Click Preview > to inspect what the delete operation is going to do, if confirmed.
    2. Click Cancel to cancel the delete operation. Your statechart file will remain in your project.
    3. Click OK to actually delete the statechart file.

Editor UI

YAKINDU Statechart Tools comes with a statechart editor. This section explains the statechart editor and how you can use it to graphically edit your statecharts.

„SC Modeling” perspective

SC Modeling is an Eclipse perspective supporting the modeling of statecharts. The perspective defines the following views and their positions:

  • Project Explorer (left): This view displays your workspace and projects, folders, and files contained therein. You can also use the Project Explorer to inspect the internal structure of your statechart models.
  • Properties (bottom): This view displays properties, regarding the semantic model or the graphical appearance, of the selected element in the statechart editor. You can directly edit these properties in this view.
  • Problems (bottom): This view displays errors and warnings existing in your workspace. Double-clicking on an entry typically opens the location of the respective error or warning.
  • Outline (right): This view is a bird’s eye view on the opened statechart. It also indicates the current viewport for better orientation in large models.

Canvas

The canvas is the statechart editor’s drawing area. When you create a new statechart model, the canvas comprises the definition section and a single region.

The following list gives an overview of what kind of actions you can perform on the canvas:

  • Add or remove a region
    • To add a region to the canvas, select Region in the editor palette, then click on the canvas location you want to place the region.
    • To remove a region from the canvas, select the region, then press the [Del] key or select Edit → Delete from the menu bar.
  • Zooming
    • Press and hold [Ctrl] and turn the mouse wheel to zoom in or out.
    • Right-click on the canvas to open its context menu. In the context menu, several zooming functions are available in the Zoom submenu.

Editor palette

The editor palette provides you with a set of various actions and statechart editing tools. By default, the palette is located right of the canvas, but you can also drag it to the left.

You can hide the palette by clicking on the small triangle in the palette’s title bar. Click on the triangle again to make the palette reappear.

Editor palette

Editor palette

Editing action tools

Below its title bar, the palette contains a toolbar with the following editing action tools (from left to right):

Editor palette symbol "Select" Select
Editor palette symbol "Zoom in" Zoom in Left-click to zoom in. Press [Shift] and left-click to zoom out. Drag to zoom to selection.
Editor palette symbol "Zoom out" Zoom out Left-click to zoom out. Press [Shift] and left-click to zoom in.
Editor palette symbol "Note" Note Create a note, a text or a note attachment.

Statechart elements tools

The palette comprises a couple of tools serving to add statechart elements to the diagram (from top to bottom):

Editor palette element tool "Transition"
Editor palette element tool "State"
Editor palette element tool "Composite state"
Editor palette element tool "Orthogonal state"
Editor palette element tool "Region"
Editor palette element tool "Entry"
Editor palette element tool "Shallow history"
Editor palette element tool "Deep history"
Editor palette element tool "Final state"
Editor palette element tool "Exit node"
Editor palette element tool "Choice"
Editor palette element tool "Synchronization"

Outline view

The Outline view allows you to keep the big picture of your statechart model and navigate it easily. It displays the model outline either as a graphical overview or as a hierarchical outline.

Graphical overview

Click on the Overview icon in the Outline view’s title bar to engage the graphical overview.

While the statechart editor window, due to zooming or the size of the whole statechart, might display a cutout only, the Outline view shows the whole diagram as an overview. It is scaled down as needed to completely fit into the available area.

A light-grey overlay rectangle represents the statechart editor’s viewport. Drag this rectangle to move the statechart editor’s viewport. Click into the outline view to position the rectangle’s center to the point you have clicked.

Hierarchical outline

Click on the Outline icon in the Outline view’s title bar to engage the hierarchical outline.

Problems view

The Problems view by default lists all errors, warnings and other types of messages in all open projects.

The messages are grouped by message type, typically error or warning. Click on the show/hide symbol to open or close the respective message group’s contents.

Double-clicking on an entry in the Problems view takes you directly to the resource or model element causing the problem.

You can configure the Problems view in a multitude of ways, e. g. to group entries by different criteria, to sort them in a specific way, or to restrict them to certain projects. You can even define multiple Problems views, each with different selection or display criteria.

To start configuring the Problems view, click on the small triangle pointing downwards in the Problem view’s title bar. A drop-down menu will open and show the options you have.

To configure the view click the small triangle pointing downwards in the view’s title bar. A drop-down menu opens and shows the options you have.

Editing states and other nodes

Generally, there are two different ways to edit states and other nodes:

  • Use the graphical editor to modify a node in the diagram.
  • Select a node and edit its properties in the Properties view.

There are certain properties you can only edit with one of these methods. For example, to modify a state’s position or size, use the statechart editor. To change a state’s transitions' priority, use the Properties view.

Editing transitions

Generally, there are two different ways to edit transitions:

  • Use the graphical editor to modify a transition in the diagram.
  • Select a transition and edit its properties in the Properties view.

There are certain properties you can only edit with one of these methods. For example, to add guidance points to a transition’s arrow, use the statechart editor. To change a transition’s arrow’s color, use the Properties view.

Editing regions

To change the size or the location of a region, use the graphical editor. Drag the region to move it elsewhere. Use a selected region’s handles to resize it.

You can change the name of a region in the statechart editor as well as in the properties view.

  • In the statechart editor,
    • double-click on the region’s current name,
    • edit the name in the text field that appeared after double-clicking,
    • press the [Return] key.
  • In the properties view,
    • modify the Region Name property. It is a region’s only property you can access through the properties view.

Editing hierarchies

Statecharts can get rather big and complex. Composite states are a way to reduce complexity and thus make statecharts easier to create, comprehend and maintain. A composite state comprises a state machine of its own within a region. The states belonging to such a nested state machine are called substates. Orthogonal states are a generalization of composite states, comprising two or more independent state machines in separate regions that are executed in parallel.

A complementary way to mitigate size are subdiagrams. A subdiagram externalizes the possibly large region(s) contained by a composite state into a subdiagram. In this case the composite state no longer displays its substates. Instead it is visualized very similar to a regular state, aside from a small label marking it as a composite state and giving access to its internal structure: the subdiagram. In this way a composite state consumes much less space and gives the user the opportunity to better see the overall picture. Section Using subdiagrams explains how to work with subdiagrams, how to create them and how to inline them again, if needed.

Composite states resp. subdiagrams can be nested to any depth.

The statechart editor provides various refactorings to support editing these hierarchies.

Using subdiagrams

When using composite states, a statechart model may easily become too big to give a comprehensive overview of the whole diagram. Subdiagrams come as a solution.

Composite state

Composite state

When the Extract Subdiagram refactoring is executed on a composite state, all containing regions are extracted into a separate diagram. The composite state no longer clutters the diagram with all its internal details, but instead appears almost like a normal state. The only difference is a small decorator in the lower-right corner of the state, indicating the existence of a subdiagram. When you hover over this decorator with the mouse cursor, you’ll see a small preview of the subdiagram’s content.

Extracting a subdiagram creates entry and exit nodes in the subdiagram as needed.

Subdiagram popup window

Subdiagram popup window

A click on the decorator opens the subdiagram in a separate editor tab. The breadcrumb at the top allows easy navigation throughout the hierachy levels.

Subdiagram editor

Subdiagram editor

Using the inlining subdiagram refactoring, you can move the subdiagram back into the composite state.

Refactorings

Refactoring means modifying certain model aspects while maintaining its semantics. The statechart editor allows for the refactoring of variables, events, interfaces, and states, including composite and orthogonal states. A state’s context menu contains the Refactor submenu with the refactoring actions explained below. Depending on certain conditions, a refactoring might be executable or not, which will be explained below.

Renaming variables, events and interfaces

Using the Rename refactoring, you can change the name of a variable, event or interface throughout your statechart model. Each occurrence of that name will be changed to the new name.

To initiate renaming, right-click on the name of a variable, event or interface in the diagram editor, in the definition section, or in a text field in the Properties view, then select Rename ….

Renaming a variable

Renaming a variable

Folding incoming actions

When building a statechart model step by step, you may come into a situation where you have defined several transitions having the same target state and share a common set of actions.

The Fold Incoming Actions refactoring moves these actions from the transitions to the target state’s entry block. To preserve model semantics, only actions that are defined on all incoming transitions will be moved. Since the execution order must be preserved, the refactoring algorithm starts with the right-most action and proceeds action by action to the left. As soon as it detects an action that is not defined on all incoming transitions, it stops moving actions to the entry block.

Consider the following model:

Moving incoming actions to entry block

Moving incoming actions to entry block

Only the most-right action y += 42 can be moved to the entry block of the target state. Although x += 1 is also a common action of both transitions, it cannot be moved to the target state, because the semantics of the B → Target transition would change, in that y = x would be executed before x had been incremented.

Another aspect to take into account are transitions leading to target states that are nested in composite states. Consider the following example:

Moving incoming actions into a nested state's entry block

Moving incoming actions into a nested state’s entry block

The actions y = x of the two incoming transitions leading to the Target state cannot be moved to Target's entry block, because doing so would change the model’s semantics. The reason consists in the composite state’s entry action x += 1. It will be executed immediately after the action of transition A → Target and before the actions in the entry block of Target. Moving y = x from the transition to Target's entry block would change that order. The statechart editor pays regard to this constraint and prohibits the refactoring.

To fold incoming actions, right-click on the state to refactor, then select Refactor → Fold Incoming Actions in the context menu. The menu entry is active only if there are actual actions to move into the target state’s entry block with the above rules applied.

Folding outgoing actions

The Fold Outgoing Actions refactoring is similar to folding incoming actions, except that it moves actions from outgoing transitions to the source state’s exit block. To preserve model semantics, only actions that are defined on all outgoing transitions will be moved. Since the execution order must be preserved, the refactoring algorithm starts at the left-most action and proceeds action by action to the right. As soon as it detects an action that is not defined on all outgoing transitions, it stops moving actions to the exit block.

Preconditions for this refactoring are analog to "Folding incoming actions". Consider the following example:

Moving outgoing actions to exit block

Moving outgoing actions to exit block

Here, the actions y = x cannot be moved from the outgoing transitions to the exit block of the source state, because the composite state has an exit action. For the Source → A transition, the proper execution order is to first execute x += 1 of the nesting composite state’s exit block, followed by y = x of the transition. Moving y = x to the exit block of Source would reverse this order and thus will be prohibited by the statechart editor.

To fold outgoing actions, right-click on the state to refactor, then select Refactor → Fold Outgoing Actions in the context menu. The menu entry is active only if there are actual actions to move into the source state’s exit block with the above rules applied.

Unfolding entry actions

This refactoring is the reverse of folding incoming actions. It removes all entry actions from a target state and appends them to each of its incoming transition’s actions.

Transitions crossing the borders of composite states enclosing the target state might inhibit refactoring, see section "Unfolding exit actions" for an analogous example.

To unfold entry actions, right-click on the state to refactor, then select Refactor → Unfold Entry Actions in the context menu. The menu entry is active only if there are actual actions in the state’s entry block that can be moved to the state’s incoming transitions while maintaining semantic equivalence and preserving execution order.

Unfolding exit actions

This refactoring is the reverse of folding outgoing actions. It moves all exit actions from a source state and prepends them to each of its outgoing transition’s actions.

Transitions crossing the borders of composite states enclosing the source state might inhibit refactoring. Consider the following example:

Unfolding exit actions to outgoing transitions

Unfolding exit actions to outgoing transitions

Unfolding the exit action y = x of the Source state to the two outgoing transitions would be invalid, because the execution order of said action and the composite state’s exit action would be reversed.

To unfold exit actions, right-click on the state to refactor, then select Refactor → Unfold Exit Actions in the context menu. The menu entry is active only if there are actual actions in the state’s exit block that can be moved to the state’s incoming transitions, while maintaining semantic equivalence and preserving execution order.

Grouping states into composite

This refactoring creates a new composite state containing the selected states. The latter must belong to the same region.

To execute this refactoring, select one or more states from the same region, right-click on one of them, and select Refactor → Group States Into Composite in the context menu. The menu entry is active only if the selected states belong to the same region.

Extracting subdiagram

This refactoring extracts the regions of the selected composite or orthogonal state into a subdiagram. Entry and exit nodes are created as needed in the subdiagram. See section Using subdiagrams for more details.

To extract a subdiagram, right-click on the composite state to refactor, then select Refactor → Extract Subdiagram in the context menu.

Inlining subdiagram

This refactoring inlines the selected node’s subdiagram in order to show it directly in the composite state’s diagram region. See section Using subdiagrams for more details.

To inline the subdiagram, right-click on a composite state with a subdiagram, then select Refactor → Inline Subdiagram in the context menu.

Using editing proposals

Using text proposals

Proposals assist you when writing statechart language expressions. Whenever editing some text anywhere in the graphical statechart editor or in the properties view, you can always press the [Ctrl+Space] key combination to get some context-sensitive help.

Certain proposals, like statechart language keywords, have documentation associated with them. When such a proposal is selected either using the mouse or the keyboard, this information is shown in a secondary popup window next to the proposal.

You can either use the mouse or the keyboard to select and insert a proposal in the text:

  • Double-click on a proposal for insertion in the text.
  • Use the up and down arrow keys to navigate to the proposal you want to insert in the text, then press [Return] to actually insert the proposal.

Using actions proposals on states

When a state is selected, [Ctrl+Space] opens a popup window showing a context-sensitive menu with possible action choices to perform on the state.

These action proposals have additional information associated to them. When a proposal is selected either using the mouse or the keyboard, this information is shown in a secondary popup window next to the proposal.

You can either use the mouse or the keyboard to execute a proposal:

  • Double-click on a proposal to execute it.
  • Use the up and down arrow keys to navigate to the proposal you want to execute, then press [Return] to actually execute it.

Comparing statecharts

The statechart editor allows for comparing two or even three statecharts to each other, displaying the results, and possibly merging selected differences. Figure "Comparing two statecharts" shows a sample comparison result.

Comparing two statecharts

Comparing two statecharts

Comparing a statechart to its local history

  1. In the project view, right-click on a single statechart model file. The context menu opens.
  2. In the context menu, select Compare With → Local History….
  3. In the local history, double-click on the file’s version to compare to.
  4. The comparison results are shown.

Comparing two statecharts

  1. In the project view, select two statechart model files.
  2. Right-click on one of the selected statechart model files. The context menu opens.
  3. In the context menu, select Compare With → Each Other.
  4. The comparison results are shown.

Comparing three statecharts

  1. In the project view, select three statechart model files.
  2. Right-click on one of the selected statechart model files. The context menu opens.
  3. In the context menu, select Compare With → Each Other.
  4. In the dialog to appear, select one of the statecharts that should be regarded as a common ancestor of the two others.
  5. The comparison results are shown.

Exporting a statechart as an image file

A statechart can be saved as an image file as shown in the following steps:

In the statechart editor, right-click on the main region. The context menu appears.

Selecting the "Save As Image File..." menu item

Selecting the Save As Image File... menu item

In the context menu, select File → Save As Image File.... The Save As Image dialog appears.

The "Save As Image File" dialog

The Save As Image File dialog

Specify the filesystem folder for the exported image file in the Folder text field.

Enter the name of the export image file into the File Name text field. The file name extension depends on the selected image format (see below).

Select the image format from the Image Format drop-down menu. YAKINDU Statechart Tools supports the following formats:

BMP, GIF, PNG Lossless pixel image formats
JPG, JPEG Lossy pixel image format. You can specify the image quality via the Quality (%) setting.
SVG Scalable Vector Graphics
PDF Portable Document Format

Note

The image export functionality is subject to the capabilities of your Java Runtime Environment (JRE). You can export images only in those image formats your JRE actually supports.

The Quality (%) text field is active for JPEG images only. JPEG is a lossy format, and reducing the quality results in a smaller file size. However, due to the nature of statechart images, a lossless format like PNG is most often a better choice, both in quality and in file size.

Check Overwrite existing file without warning if you don’t want to be bothered by a confirmation dialog which will appear if the export file already exists.

Check Export to HTML to create both the image file plus an HTML file including it.

Working with statechart tasks

While you are busily developing a statechart, it is quite common that you have ideas about what else you could or should do to improve the statechart, but later, in order to not get side-tracked. Examples are to write a proper documentation for a state, to refine a transition whose final specification you don’t have yet etc.

For all these and other purposes you can define tasks anywhere in your statechart where a comment is allowed. A task is a special comment comprising either FIXME or TODO. Adding one of these words to a comment is called „tagging” the comment as a task.

The nice thing is that you don’t have to remember all the places you took a note and defined a task. YAKINDU Statechart Tools lists all of your tasks in the Tasks view. Figure "Tasks defined by tags in the statechart showing up in the Tasks view" is showing an example with various tasks being defined in a transition, in a state’s behavior, in a state’s documentation, and in the statecharts definition section.

Tasks defined by tags in the statechart showing up in the "Tasks" view

Tasks defined by tags in the statechart showing up in the Tasks view

In the example, state B has been selected in the statechart editor (top), so that the tasks defined in the state’s behavior and in its documentation are shown in the Properties view (middle).

Task have a priority. While TODO stamps a task as being of normal priority, FIXME indicates a high-priority task.

By default, tasks are ordered by priority in the Tasks view (bottom), and high-priority tasks are accentuated by a red exclamation mark. However, you can change the sorting order and other settings using the view menu. Click on the little triangle on the right-hand side of the Task view’s title to open the view menu.

Double-clicking on a task in the Tasks view navigates to the location where the task is defined. If needed, the corresponding statechart diagram is opened. The graphical element holding the task definition is highlighted.

Note

The Tasks view is updated only when the statechart is saved.

Using the example wizard

The Example Wizard, which is part of YAKINDU Statechart Tools since release 2.7, gives you convenient access to the examples in our examples repository. You can browse the available examples and read their documentation in the wizard. By a simple click you can instantiate an example as a new Eclipse project. Within an example project, you can then explore and modify the state machine models using the statechart editor, run the models in the simulator, generate source code, etc.

Upon its first invocation, the example wizard downloads the online examples repository and creates a copy on your local disk. After that, all examples are immediately available to you, even if you are offline. The example wizard will notice when new examples are available in the online repository and offers to download them. It is also possible to download the examples repository out-of-band and later tell the example wizard where it can find the local copy.

Downloading the examples repository

When you start the example wizard for the first time, it does not yet have any examples available that it could show to you. Thus downloading the online examples repository is required as a special first step.

  1. In order to start the example wizard, select File → New → Example… in the YAKINDU Statechart Tools main menu. The New Example dialog opens.
  2. In the New Example dialog, select YAKINDU Statechart Examples, then click on Next >. The example wizard opens.
  3. The example wizard outputs a message saying that it could not find any examples and offers to download them.
  4. Click on the Download button. The example wizard downloads the online examples repository and creates a clone of it on your local computer. This can take some time. – Downloading can fail for a variety of reasons, not being able to access the Github server being one of them. If this happens, don’t panic – and read how to manually create a local copy of the examples repository.

Example wizard when invoked for the first time

Example wizard when invoked for the first time

When the download is completed, you can browse the examples repository.

Changing the repository location

You can change the location used by the example wizard to store the online repository’s local clone.

  1. When the example wizard shows the Download button on its first start, click on the link "change the storage location here". The example wizard’s preferences dialog opens. Alternatively, open Window → Preferences and select YAKINDU SCT → Example Wizard.
  2. On the examples preferences page, enter the desired directory into the Storage Location text field.
  3. Click on OK. The dialog closes.

You can change the storage location of your local examples repository clone at any time. In fact, it doesn’t even need to be a clone of the official examples repository. Any directory containing subdirectories with YAKINDU Statechart Tools examples suffices.

  • If the directory exists, the example wizard searches it for examples and displays them.
  • If the directory does not exist, the example wizard behaves as if being called for the first time. That is, it informs the user that it does not have any examples yet and offers to download them.

Browsing the examples repository

The example wizard shows the available examples on the left-hand side.

Click on an example to select it and show its documentation on the right-hand side.

Click on Finish to create the selected example as a new project in your workspace.

Example wizard showing all available examples

Example wizard showing all available examples

Manually creating a local copy of the examples repository

The example wizard tries to download a copy of the YAKINDU Statechart Tools examples repository and install it on your local machine. However, if your Internet access is restricted or you don’t have Internet access at all, this will fail.

To circumvent this problem, you can

  1. download the examples repository elsewhere, i. e. on a computer with Internet access,
  2. copy it to the computer you want to run YAKINDU Statechart Tools, and
  3. have the example wizard work with that local copy.

Subsequently we will explain the necessary steps in detail.

Downloading the repository as a ZIP archive:

  1. On a computer with Internet access, go to the examples repository’s release branch.
  2. Click on the green Clone or download button. A submenu opens.
  3. In the submenu, click on Download ZIP.
  4. The download starts and transfers the ZIP archive file examples-release.zip to your computer.
  5. Copy the examples-release.zip file to the computer without Internet access and continue with the following steps on that machine.
  6. Unpack the archive file. A directory named examples-release is created, containing the YAKINDU Statechart Tools examples repository.
  7. In YAKINDU SCT, select Window → Preferences.
  8. Inside there, navigate to YAKINDU SCT → Example Wizard.
  9. Click on Browse..., and select the folder you just extracted from the zip-file and click OK.
  10. On the next try, the example wizard will now show a catalogue of YAKINDU SCT examples.

Alternatively, you can clone the repository. Cloning the examples repository containing the YAKINDU Statechart Tools examples is more complex than just downloading a ZIP archive. However, you will get certain advantages in return, like the ability to detect and receive updates, the option to create your own examples and submit them as a pull request, or the complete history of the examples repository – which could also be considered a drawback, taking its size into account.

  1. Clone https://github.com/Yakindu/examples to your local computer. If you are using a command-line tool, the proper command is git clone 'https://github.com/Yakindu/examples.git'. It will create the directory examples on your disk.
  2. Step into that directory: cd examples.
  3. Checkout the release branch: git checkout release. Now the examples directory reflects the current release state.
  4. In the YAKINDU Statechart Tools main menu, select File → New → Example…. The New Example dialog opens.
  5. In the New Example dialog, select YAKINDU Statechart Examples and click on Next >. The example wizard opens.
  6. When the example wizard is opened for the first time, there are no examples on the local disk yet.
  7. In YAKINDU SCT, select Window → Preferences.
  8. Inside there, navigate to YAKINDU SCT → Example Wizard.
  9. Click on Browse..., and select the folder you just cloned and click OK.
  10. On the next try, the example wizard will now show a catalogue of YAKINDU SCT examples.

Updating the examples repository

When the example wizard is started and the storage location is a clone of the online examples directory, it checks whether there are any updates in the online examples repository. If new examples are available or existing examples have changed in the online repository the example wizard offers to update your local examples repository clone accordingly.

Example wizard offering to update the examples repository

Example wizard offering to update the examples repository

  • Click on the Update button to update your local repository.

If your storage location is a plain directory and not a clone of the online examples repository, no update is ever done.

Contributing your own examples

If you have a project that you want to share with the community, you can contribute it as an example. Once your contribution is reviewed and applied by the YAKINDU team, it is available for all users of YAKINDU Statechart Tools via the example wizard’s update functionality.

For more information on how to contribute examples, please visit our Wiki page.

Preferences

Many aspects of YAKINDU Statechart Tools can be configured by preferences. You can find them here:

  1. In the main menu, select Window → Preferences. The Preferences dialog appears.
  2. In the navigation on the left-hand side, scroll to YAKINDU SCT. Open it and its subentries as needed.
  3. Click on an entry to display the associated preference pane on the right-hand side of the Preferences dialog, see e. g. figure Diagram Appearance.

In the subsequent sections we will quickly walk through the preferences.

Diagram appearance


Preferences: Diagram Appearance

Preferences: Diagram Appearance

Colors and line styles

The diagram appearance preferences define default colors for the backgrounds and borders of states and regions, as well as the default routing style for transitions and other lines, with oblique and rectilinear being the options at your choice.

Please keep in mind that colors and line styles will apply to new elements only. Existing regions, states, etc. will remain as they are. You can always modify the properties of an existing element using the Properties view.

Showing transition priorities

In order to display transition priorities in a statechart, set a check mark at the respective preference option. Section "Transition priorities" has an example.

Disabling live validation

While you are editing a statechart, the statechart editor validates your model on each and every modification you make. That’s very helpful, because the editor provides you with instant feedback, so you can see immediately what is right, wrong, or dubious, and you can take corrective action, if needed.

However, on very large and complex statechart models, validation may take a considerable amount of time, causing delays and impeding your editing. Remove the check mark from Enable live validation, and your model will be validated before it is saved to the model file only.

Example wizard


Preferences: Example wizard

Preferences: Example wizard

You can change the example wizard storage location here. It is a local directory where the example wizard stores your local clone of the examples Git repository. Default is the sct_examples directory in your home directory.

Expressions preferences


Preferences: Expressions

Preferences: Expressions

Certain dialogs in the statechart editor allow you to opt for never seeing them again. By clicking on the Clear button on this preference pane these hidden dialogs will be shown again at their respective locations.

Expression syntax coloring


Preferences: Expression syntax coloring

Preferences: Expression syntax coloring

This preferences pane defines foreground color, background color, font, and style for displaying certain syntactical elements in statechart language texts.

Expression templates


Preferences: Expression templates

Preferences: Expression templates

Templates are sections of textual code that occur frequently enough that you would like to be able to insert them with a few keystrokes. This function is known as content assist; the sections of code that are inserted are known as templates.

To insert an existing content assist template, type the initial character, then press [Ctrl+Space]. The templates that begin with that character appear. Double-click on a template to insert it.

On this preference pane you can create, edit, and delete statechart language templates.

Generator model


Preferences: Generator model

Preferences: Generator model

By default code generators automatically generate artifacts defined by generator models in .sgen files. Using this preference setting, you can switch this behavior off or on.

Generator model refactoring


Preferences: Generator model refactoring

Preferences: Generator model refactoring

Change these preference settings for refactoring in generator models as you see fit.

Generator model syntax coloring


Preferences: Generator model syntax coloring

Preferences: Generator model syntax coloring

This preferences pane defines foreground color, background color, font, and style for displaying certain syntactical elements in generator model texts.

Generator model templates


Preferences: Generator model templates

Preferences: Generator model templates

Templates are sections of textual code that occur frequently enough that you would like to be able to insert them with a few keystrokes. This function is known as content assist; the sections of code that are inserted are known as templates.

To insert an existing content assist template, type the initial character, then press [Ctrl+Space]. The templates that begin with that character appear. Double-click on a template to insert it.

On this preference pane you can create, edit, and delete generator model language templates.

Simulation


Preferences: Simulation

Preferences: Simulation

Set your color preferences for the statechart simulator on this preference pane.

Edit on GitHub

Simulating statecharts

Simulating a statechart model means to execute it, raise events manually, have time-based and other events being triggered automatically, and observe the model’s behavior.

You can run multiple state machines in parallel and even multiple instances of the same state machine.

An introduction to simulation is given in section "Simulating the light switch model".

Starting a simulation

You have several options to start a statechart simulation.

Using the statechart model file context menu

The most direct way is to start the simulation based on the statechart model file.

  1. In the Project Explorer view, right-click on the statechart model file. The context menu opens.
  2. In the context menu, select Run As → Statechart Simulation, see figure "Selecting Run As → Statechart Simulation in the context menu" .

Selecting "Run As → Statechart Simulation" in the context menu

Selecting Run As → Statechart Simulation in the context menu

Repeating the last simulation

In order to re-run the simulation you have most recently executed, simply

  • press [Ctrl+F11] on the keyboard

or

  • select Run → Run in the main menu.

To be exact, this operation does not necessarily re-run the last simulation, but rather the last executed launch. So if, for example, you first run a statechart simulation followed by running a Java program, then upon press [Ctrl+F11], that Java program is executed once again, not the statechart simulation.

Repeating an earlier simulation

Let’s consider a scenario where you want to execute a simulation you have run before, but not as the most recently executed launch. So you cannot use the procedure described in section "Repeating the last simulation".

However, as long as you haven’t launched too many other programs in between, chances are good to find your simulation in the history.

Try the following:

  1. In the main menu, open the Run menu and move your mouse pointer over the Run History entry.
  2. A submenu attached to the Run History menu entry opens, containing the most recently executed launches. Check whether the simulation you want to execute is available in the submenu. If it is, select it to start the simulation.

Creating and executing a launch configuration

When a statechart is simulated for the first time, a launch configuration is automatically created. A launch configuration describes the parameters used for a particular launch. In case of a statechart simulation, it describes which statechart is to be simulated and the simulation mode (event-driven or cycle-based). For details on how to create and modify a launch configuration, see section "Configuring a simulation".

To execute an existing launch configuration, proceed as follows:

  1. In the main menu, select Run → Run Configurations…. The Run Configurations dialog appears.
  2. The list on the left-hand side of the Run Configurations dialog displays all available launch configurations. Select the launch configuration you want to execute.
  3. Click on Run to execute the launch configuration.

The SC Simulation perspective

The SC Simulation perspective provides selected views that are most useful when running a statechart simulation.

Engaging the SC Simulation perspective

When a simulation starts, the perspective usually changes to the SC Simulation perspective. If this doesn’t happen, you can manually engage the SC Simulation perspective as follows:

  • In the main menu, select Window → Perspective → Open Perspective → SC Simulation.

Alternatively, you can do the following:

  • In the main menu, select Window → Perspective → Open Perspective → Other…. The Open Perspective dialog appears.
  • In the Open Perspective dialog, select SC Simulation.
  • Click on Okay. The SC Simulation perspective opens.

Views contained in the SC Simulation perspective

By default, the SC Simulation perspective shows the following views:

  • Project Explorer (left): This view displays your workspace and projects, folders, and files contained therein. You can also use the Project Explorer to inspect the internal structure of your statechart models.
  • Debug (top middle): This view is showing all statechart instances and allows to select one of them. YAKINDU Statechart Tools allow multiple executions of the same statechart as well as parallel executions of different statecharts at the same time.
  • Outline (right): This view is a bird’s eye view on the opened statechart. It also indicates the current viewport for better orientation in large models.
  • Simulation (right): This view shows the current state of all variables and events during a simulation. A detailed description is available in section "The Simulation view".
  • Breakpoints (right): This view shows a list of all breakpoints. You can use it for disabling, enabling, or removing breakpoints as well as for defining conditional breakpoints.
  • Snapshots (right): This view contains all snapshots with their respective names and timestamps.

Displaying simulation progress in the statechart editor

The SC Simulation perspective also includes the statechart editor. In a running simulation, the statechart editor highlights active states by coloring their backgrounds in red.

When a transition is taken, the transition arc leading from the source state to the target state flashes briefly in red. After that, the target state becomes active and changes its background color to red. The source state’s background color becomes normal again.

The "SC Simulation" perspective

The SC Simulation perspective

The Simulation view

The Simulation view is used to manually raise events and to inspect and modify variables of a running simulation. By default that view is located on the right-hand side of the SC Simulation perspective, see figure "Simulation view" for an example.

The Simulation view groups events and variables by their interfaces. The unnamed interface appears as default in the interface list. Click on the small triangle left from a interface’s name to show or hide the interface’s contents, i. e. events and variables.

Note

Depending on your screen resolution and font size settings, you might not be able to spot the Simulation view by its name, because the tab containing it is quite narrow and might not provide enough space for displaying the title. Hover over the tabs to reveal their respective titles in a popup window.

Figure "The SC Simulation perspective" is demonstrating this: The user has hovered the mouse pointer over a tab that is just displaying the starting letter ‚S’ of its title. However, a popup window right besides the pointer is showing the tab’s full title „Simulation”.


Simulation view – The actual "Simulation" view is the pane right from the statechart editor.

Simulation view – The actual Simulation view is the pane right from the statechart editor.

Controlling a simulation

  • To terminate the simulation, click on the Terminate button Symbol: Terminate or select Run → Terminate in the main menu.
  • To suspend the simulation, click on the Suspend button Symbol: Suspend or select Run → Suspend in the main menu.
  • To resume a suspended simulation, click on the Resume button Symbol: Resume or select Run → Resume in the main menu.
  • Use the Step Over button Symbol: Step Over or select Run → Step Over in the main menu to execute a single run-to-completion step.

Interacting with a simulation

You can interact with a running simulation by manually raising events and by inspecting and modifying variables. You can do so at any point in time, but in most cases you will do so while the simulation „sits idle” at its active state and waits for an event to trigger a transition.

Raising an event in the simulation

To raise an event, proceed as follows:

  1. In the Simulation view, click on the small triangle to open the interface containing the event, if needed.
  2. Click on the event to it for the next run-to-completion step.

Inspecting a variable

To inspect a variable’s value, proceed as follows:

  1. In the Simulation view, click on the small triangle to open the interface containing the variable, if needed.
  2. The variables contained in the interface are displayed.

Watch the displayed value change as the simulation progresses and actions in states or transitions are executed that modify the variable’s contents.

Modifying a variable

To manually modify a variable’s value, proceed as follows:

  1. In the Simulation view, click on the small triangle to open the interface containing the variable, if needed.
  2. Double-click on the variable’s name. It is replaced by a text field containing the variable’s value.
  3. Enter the variable’s new value and press [Enter]. The former value is replaced by the new one.

Configuring a simulation

Section "Creating and executing a launch configuration" describes how to start an existing launch configuration.

The present chapter describes how to create and configure a new launch configuration for a statechart simulation.

  1. In the main menu, select Run → Run Configurations…. The Run Configurations dialog appears. The "Run Configurations" dialog
  2. In the Run Configurations dialog, right-click on Statechart Simulation and select New in the context menu. The "Run Configurations" dialog
    Alternatively, you can select Statechart Simulation and then click on the New symbol near the top-left corner of the dialog. The "Run Configurations" dialog
    However you do it, a new launch configuration is created and displayed in the main area of the Run Configurations dialog. The launch configuration’s Main tab is opened.
  3. Enter the launch configuration’s parameters as necessary:
    • In the Name text field, change the default name New_configuration to something sensible, e. g. Light switch.
    • In the Model file text field, enter the path to the statechart model you want to simulate. Click on the Search button to browse for statechart models.
    • If your model uses Java operations, specify the Java class implementing those operations in the Operation class text field. If you have multiple Java classes, specify them as a comma-separated list.
    • Specify the Execution type as being either cycle-based (default) or event-based. In a cycle-based execution, the simulation performs a run-to-completion step in regular intervalls and processes the events that have occurred since the previous run-to-completion step. See the next field for the period of time between two consecutive run-to-completion steps. – In an event-based execution, the simulation performs a run-to-completion step each time an event occurs. Please note: In contrast to the statechart simulation any generated code does not necessarily conform to the event-based execution semantics.
    • If the execution type is cycle-based, specify the period of time between two run-to-completion steps in the Cycle time text field. If the execution type is event-based, this field is deactivated. The "Run Configurations" dialog

Note

Besides the Main tab described above, a statechart simulation launch configuration also has a tab named Common. This tab is a common standard to all types of launch configurations and thus not documented here.

In addition to create new launch configurations, you can also duplicate or delete launch configurations in the Run Configurations dialog. Right-click on a launch configuration and select Duplicate or Delete from the context menu.

Edit on GitHub

Debugging with breakpoints and snapshots

Introduction

This advanced feature makes it possible to attach breakpoints to states and transitions. If a statechart simulation reaches a transition or a state with a breakpoint, it suspends execution of the simulation. A breakpoint can be amended with a condition in order to suspend the simulation only if the condition is fulfilled (true).

You can also create snapshots of your statechart simulation. A snapshot contains everything making up your state machine simulation at any point in time. It describes the state of the state machine, if you will. Snapshots can be saved and restored later in order to continue the simulation from that point on that is specified by the snapshot.

The light switch example

Throughout this chapter we will be using the light switch statechart as an example. It models a lamp which can be turned on and off and also supports various brightness values.

If you press the pressLightOn button, the lamp turns on at its lowest brightness value. If you operate pressLightOn repeatedly, each time the lamp becomes brighter until it reaches its maximum brightness. Pressing the pressLightOff button, immediately turns off the light completely. The brightness can only be raised as long as it hasn’t yet reached its maximum value of five. After that, the guard condition disallows to raise it any further.


The light switch sample statechart

The light switch sample statechart

Breakpoints

Breakpoints allow for automatically suspending the simulation when a certain element of the state machine is activated. Optionally, a halting condition can be specified to better control the behavior of a breakpoint. Breakpoints can be set on transitions or states. When a breakpoint is reached, the simulation pauses and the current state of variable values can be examined in the simulation view. It is possible to change values and to trigger events that will be raised when the simulation run is manually resumed.

Executing in debugging mode

To make use of breakpoints, the statechart simulation needs to be executed in debugging mode:

  1. Right-click on the statechart model. The context menu opens.
  2. Select Debug As→Statechart Simulation, see Figure "Starting a simulation in debugging mode".
  3. The statechart simulation starts in the debugging mode.


Starting a simulation in debugging mode

Starting a simulation in debugging mode

Setting a breakpoint

  1. Right-click on a state or transition. The context menu opens.
  2. Select Toggle Breakpoint from the context menu, see Figure "Setting a breakpoint".


Setting a breakpoint

Setting a breakpoint

States and transitions having a breakpoint attached are labeled with a Symbol: Breakpoint enabled symbol. Figure "Breakpoints on transition and state" shows an example.


Breakpoints on transition and state

Breakpoints on transition and state

Hitting a breakpoint

If the simulation runs into a state with a breakpoint, the state’s entry actions, if any, are executed. After that, execution of the state machine is suspended. The state is highlighted by a small green border.

Highlighting a suspended state

Highlighting a suspended state

If the simulation runs into a transition with a breakpoint, execution of the state machine is suspended. The transition is highlighted by drawing the transition arrow in green. The transition’s actions, if any, are executed when the state machine is resumed.

Highlighting a suspended transition

Highlighting a suspended transition

Continuing the simulation

In order to continue from a breakpoint, you have two options:

  • To continue execution, click the resume button Symbol: Resume or select Run → Resume in the main menu. The statechart simulation continues until the next breakpoint is hit or the simulation is terminated.
  • To execute the next run-cycle of the simulation only and then suspend again, click the step-over button Symbol: Step Over or select Run → Step Over in the main menu.

Using the breakpoints view

The breakpoints view shows a list of all breakpoints. The respective breakpoint name identifies the state or transition in question. See figure "Breakpoints view" for an example.

You can use the breakpoints view for disabling, enabling, and removing breakpoints as well as defining conditional breakpoints.


The "Breakpoints" view

The Breakpoints view

Enabling and disabling breakpoints

A breakpoint is either enabled or disabled.

  • An enabled breakpoint causes the statechart simulation to suspend when reaching it. In the statechart and in the breakpoints view, an enabled breakpoint is visualized by a filled light blue circle with a grey border: Symbol: Breakpoint enabled.
  • A disabled breakpoint is ignored by the statechart simulation. In the statechart and in the breakpoints view, a disabled breakpoint is visualized by a hollow circle with a grey border: Symbol: Breakpoint disabled.

Figure "Breakpoints view" is showing an enabled and a disabled breakpoint in the statechart editor and in the breakpoints view, respectively.

You can instruct the statechart simulation to skip all breakpoints by clicking at the Button [disengaged]: Skip all breakpoints button in the breakpoints view. The button will appear „pressed”, and while it is, the „skip breakpoints” functionality is engaged. That means, the simulation will not suspend at any breakpoint.

This is different from disabling all breakpoints, in that each breakpoint keeps its state of being enabled or disabled. Once you disengage the skip breakpoints functionality by clicking at the Button [engaged]: Skip all breakpoints button again, the simulation will suspend again at enabled breakpoints and will not suspend at disabled breakpoints.

Removing breakpoints

In order to remove some breakpoints, select these breakpoints in the breakpoints view, then click at the Button: Remove button. The selected breakpoints will be removed.

To remove all breakpoints, click at the Button: Remove all button

Conditional breakpoints

A conditional breakpoint has an associated condition and suspends the simulation only if

  • that condition is fulfilled (true) and
  • the breakpoint is enabled.

In order to attach a condition to a breakpoint, proceed as follows:

  • In the breakpoints view, select the breakpoint in question.
  • Check the Conditional checkbox, see figure "Breakpoints view" in the lower right area. The associated text field becomes writable.

Enter the condition into the text field. Like in the statechart editor, a content assist is available when pressing [Ctrl+Space]. The expression is validated automatically. In the example shown in figure "Breakpoints view" the transition suspends the simulation only if the variable brightness has a value of 4.

Debugging a statechart

Changing variable values

In the suspended status of a statechart simulation you can change variable values using the simulation view. When continuing execution – see section Continuing the simulation – you can observe how your state machine behaves with those modified values.

Raising multiple events simultaneously

If you click on an event’s name in the simulation view to raise that event in normal simulation, i. e. while execution isn’t suspended, the state machine immediately processes that event and takes the corresponding transition, if any.

However, while the simulation is suspended, you can raise multiple events, without instant execution. Once execution resumes, both events are handled at the same time, or, to be more exact, in the same run-to-completion step (RTS).

Consider for example, you want to press the "light on" and "light off" buttons at the same time and observe what happens. Figure "Raising multiple events simultaneously" is showing the scenario:


Raising multiple events simultaneously [1]

Raising multiple events simultaneously [1]

  • The simulation has encountered a breakpoint at the LightOn state and has been suspended there.
  • The simulation view shows the LightOn and the LightOff events. Both events are labeled with a Symbol: Event [not raised] symbol, meaning the respective event is not raised.


Raising multiple events simultaneously [2]

Raising multiple events simultaneously [2]

  • Clicking at an event raises it and adds a blue triangle to the event symbol: Symbol: Event [raised]. Since the simulation remains suspended, the user can raise multiple events.

Both events are raised and will be handled by the state machine during the next run-to-completion step. The latter will be performed as soon as the user clicks on the step-over button Symbol: Step Over or the resume button Symbol: Resume.

Please note: While the execution is still suspended, you can „unraise” an already raised event by clicking at the event symbol Symbol: Event [raised] a second time. The blue triangle will disappear, and upon continuation of the simulation the event will not be handled.

Transition priorities

It is important to understand that there is not queue of events. That is, in case several events occur simultaneously, the state machine consults the active state’s transitions priorities in the order that is specified in the corresponding property, see figure "Transition priorities". You can change the transitions priorities by selecting a transition and moving it up or down by clicking at the respective button.

The first transition whose condition is fulfilled will be executed. All remaining events are quashed.


Transition priorities

Transition priorities

Snapshots

The snapshot feature allows to store and restore the state of a simulation run. A snapshot comprises all active states of the state machine as well as all variable values at the time of snapshot creation.

This feature is especially useful when testing complex state machines in which a number of steps need to be taken before reaching a desired situation. Using snapshots, you can store this desired situation once and simply restore it again without repeating all the steps to reach it. Depending on the complexity of the usecase, this can be a huge time-saver.

Creating a snapshot

To create a snapshot of the current statechart simulation, proceed as follows:

  1. Change to Snapshots view.
  2. Click at the camera button Button: Camera in the view’s toolbar.
  3. The snapshot is taken and appears in the snapshot list. It is labeled as „Snapshot” and tagged with the current timestamp. See figure "A freshly taken snapshot".


A freshly taken snapshot

A freshly taken snapshot

The snapshots view

The snapshots view consists of two parts.

  • The snapshot list contains all snapshots with their respective names and timestamps.
  • The snapshot details part displays the contents of the snapshot. It contains two different views which can be toggled via the toolbar buttons:
    • Button: Show all variable values: shows all variable values
    • Button: Show image overview: shows an image of the state machine with highlighted active elements


Inspecting snapshot details: variables overview [left], image overview [right]

Inspecting snapshot details: variables overview [left], image overview [right]

Restoring a snapshot

To restore a snapshot for execution, proceed as follows:

  1. Select the snapshot to be restored.
  2. Click at the restore button Button: Restore snapshot.
  3. The snapshot is restored as an additional executing state machine instance.

Please note: When the semantics of the underlying state machine have been changed, it might not be possible to restore a snapshot, e.g. when the active state has been deleted.

Naming a snapshot

The label of a snapshot can be changed as follows:

  1. Click at its label. The label becomes an editable field.
  2. Enter the new snapshot name and press [Return] or click anywhere outside the editable field.

Deleting a snapshot

  • To delete one or more snapshots, select the snapshots to be deleted, then click at the remove button Button: Remove.
  • To delete all snapshots, click at the remove all button Button: Remove all.

Edit on GitHub

Deep C Integration: Integrating your C source code with your state machines

Introduction

The YAKINDU Statechart Tools Professional Edition comes with a Deep C Integration feature which allows for using C types, variables, and operations directly within the statechart model. C header files located in your workspace are automatically recognized by the tool and all contained type and operation declarations are made accessible in the statechart editor with all its editing features like code completion and validation. In addition to your custom C types, the C99 standard primitive types, like int16_t, are also available out of the box.

Making your self-defined C types, structs, and unions available in your YAKINDU statecharts saves you a lot of time and hassle that would otherwise be needed to map data from your C-type variables to statechart variables and vice versa. The Deep C Integration, however, allows you to directly create and manipulate your data structures in their native C types.

Please note:

Instead of „types, structs, and unions”, subsequently we will speak of „types” only. While this does not precisely confirm with the C programming language nomenclature, it is much easier to read in a text. From a statechart’s point of view, there is no relevant difference between types, structs, and unions anyway. Should it become necessary to differentiate between types, structs, and unions, we will do so explicitly.

The subsequent sections will explain how to use the C integration in practice, using a sample project. In this example, we will define some geometry types like Point or Triangle in C header files and demonstrate how to make them available and use them in a statechart model.

Creating a new C project

  1. In the Eclipse main menu, select File → New → Project…. The New Project wizard opens.
  2. Select C/C++ → C Project.
    Creating a new C project
  3. Click Next >. The C Project dialog appears.
  4. Enter the name of your project into the Project name field. For the sake of this example, we call the project Geometry.
  5. Specify the location of the project folder by either using the default location or by explicitly specifying a directory in the Location field.
  6. Select the Project type. In order to keep things plain and simple, for this example we create an Empty Project.
  7. Select the toolchain you want to work with. It contains the necessary tools for C development. By default only the toolchains supporting your local platform are displayed. Since this example has been created on a Linux machine, the toolchain Linux GCC is shown.
    Specifying the C project's properties
  8. Click Next >.
  9. Specify platforms, configurations, and project settings. This is more specific to C than to YAKINDU Statechart Tools, so we won’t go into any details here.
    Specifying platforms, configurations, and project settings
  10. Click Finish.
  11. Eclipse asks whether it should associate this kind of project with the C/C++ perspective. Usually this is what you want, so set a checkmark at Remember my decision and click Yes.
  12. Eclipse creates the C project, here Geometry.

Creating a C header file

Now we can create a C header file specifying our own C type definitions which we can use in a state machine later. In order to create the file, let’s proceed as follows:

  1. In the project explorer view, right-click on the project. The context menu opens.
  2. In the context menu, select New → Header File.
    Creating a C header file
  3. The dialog New Header File is shown. Specify the name of the header file. Here we choose point.h.
    Selecting a header file name
  4. Click Finish.
  5. The header file point.h is created.

Defining a C struct

In the created header file we define a struct type named Point, which we will later use in a statechart. A (two-dimensional) point consists of an x and a y coordinate. We choose int16_t to represent a coordinate, i. e. a 16-bit signed integer. The complete header file containing the struct definition looks like this:

/*
 * point.h
 *
 */

#ifndef POINT_H_
#define POINT_H_

#include <stdint.h>

typedef struct {
    int16_t x;
    int16_t y;
} Point;

#endif /* POINT_H_ */

Please note:

In C it is possible to define structs, unions and enums without a typedef. They can be referenced by using the corresponding qualifying keyword ( struct, union, or enum, respectively). As the statechart language does not support these qualifiers, the usage of struct, union and enumeration types is currently restricted to those defined by a typedef.

Using C types in a statechart

Creating a statechart model

Let’s create a statechart model now to make use of the C type Point we have just defined.

  1. Right-click on the project. The context menu opens.
  2. Select New → C Statechart Model …. The New YAKINDU Statechart wizard is shown.
  3. In the dialog, specify the directory and the filename for the new statechart model file. The filename should end with .sct.
  4. Click Finish. If Eclipse asks you whether to switch to the YAKINDU Modeling perspective, please confirm.
  5. The new statechart model is created:
    New statechart model

Defining a C-type variable in a statechart

Variables are defined in the definition section on the left-hand side of the statechart editor. Double-click into the section to edit it.

Let’s declare a variable pointA of the Point type defined above. In the statechart’s definition section, enter the following text:

interface:
	var pointA:

On the right-hand side of the colon in the variable declaration, the variable’s type must follow. In order to see which types are available, press [Ctrl+Space]. The content assist opens and shows the C types available in our C project, i. e.

  • the C basic standard types,
  • the C99 types provided by including stdint.h,
  • the self-defined Point type,
  • the qualifier pointer.

Using content assist to display available types

Using content assist to display available types

Selecting the Point menu entry completes the variable definition:

A "Point" variable

A Point variable

Using a C-type variable in a statechart

A statechart variable with a C type can be used everywhere a „normal” statechart variable can be used.

Let’s consider the above example extended by an additional count variable of the C99 int8_t standard type. Additionally, we add an event that will be used as a trigger to switch between states.

interface:
    var count: int8_t
    var pointA: point.Point
    in event tick

The statechart below uses these variables in various places, i. e. in transition actions, in internal actions, and in guard conditions.

Using C-type variables

Using C-type variables

Variables of primitive types like var count: int8_t are accessed as expected, e. g. count = 0 or count += 1;

The dot notation is used to access structure elements. For example, pointA.x = 0; pointA.y = 0 sets pointA to the origin of the coordinate system.

The statechart type system

When parsing a C header file YAKINDU Statechart Tools are mapping the C data types to an internal type system. You can open a C header file in Eclipse with the Sample Reflective Ecore Model Editor to see how the mapping result looks like.

In case you are interested in the EMF model underlying SCT’s type system, you can find it in the source code of the YAKINDU Statechart Tools open edition at /org.yakindu.base.types/model/types.ecore.

Namespaces

Importing a namespace

Instead of using the fully-qualified type name – as in point.Point – it is also possible to import the definitions provided by a C header file as a namespace.

At the beginning of the definition section, enter import: and hit [Ctrl+Space]. The content assist shows all namespaces you can import, besides other syntactical elements that would be valid here. In our example there’s a single namespace point that can be imported. The content assist explains that it comes from /Geometry/point.h, i. e. from the file point.h in the Geometry project.

Selecting a namespace to import

Selecting a namespace to import

If we had more than a single header file in the project, we would see them all. The content assist shows all header files in a project, including those in subdirectories.

Click on the namespace entry in the menu to complete the import statement. The result looks like this:

import: point

interface:
	var pointA: point.Point

Please note that adding the import statement does not change the variable declaration. However, you can now change the latter to:

var pointA: Point

Differentiating between namespaces

Let’s consider a scenario with several different Point definitions now. There are two header files, each defining a Point type, but with different properties.

The file point.h in the main project directory contains the following defintion:

typedef struct {
    int16_t x;
    int16_t y;
} Point;

Additionally, there is a file three-d/point.h which also defines a struct Point, but with different properties. This Point is three-dimensional and supports double coordinates:

typedef struct {
    double x;
    double y;
    double z;
} Point;

On the C side we would run into problems if we tried to use both Point definitions simultaneously. However, in a statechart this is possible, because each header file represents a different namespace.

A statechart definition section using a two-dimensional pointA and a three-dimensional pointB would look like this:

interface:
    var pointA: point.Point
    var pointB: three_d.point.Point

The example above shows that pathnames of header files are mapped to namespace names. The example also shows that characters which are valid in a filesystem pathname (here: ‚-’) are mapped to replacement characters that are allowed to occur in statechart type names (here: ‚_’). If you are unsure how to map a pathname to a type name, you can always use the content assist to do the mapping for you.

Although it is possible to import both namespaces as described in section "Importing a namespace", it is not advisable. Consider the following example:

import: point
import: three_d.point

interface:
    var pointA: Point
    var pointB: Point

This won’t work, because Point is ambiguous. To define pointA and pointB correctly, you would still have to fully qualify the Point types:

import: point
import: three_d.point

interface:
    var pointA: point.Point
    var pointB: three_d.point.Point

However, in this case the imports would be pointless (pun intended). What you could do is to import only one of the namespaces. Then an unqualified Point denotes the one in the imported namespace, while all other Point types must be fully-qualified. Example:

import: point

interface:
    var pointA: Point
    var pointB: three_d.point.Point

Data structure traversal via dot notation

The dot notation to access structure members can traverse an arbitrary number of stages. As an example, let’s define a datatype named Triangle. A triangle is defined by three points. Using dot notation in a statechart, you can navigate from a triangle to its individual points and further on to the points' coordinates.

The C header file triangle.h specifies the Triangle type:

#ifndef TRIANGLE_H_
#define TRIANGLE_H_

#include "./point.h"

typedef struct {
    Point a, b, c;
} Triangle;

#endif /* TRIANGLE_H_ */

A Triangle consists of the three Points a, b, and c. Let’s define a Triangle t in a statechart’s definition section as follows:

import: triangle

interface:
    var t: Triangle

With this definition we can use expressions like t.a.x, see the image below. Regardless of where you are currently editing an expression, you can always use the code assist to explore which fields are available at that very point and of which types these fields are. Example:

Content assist in data structure traversal

Content assist in data structure traversal

Pointers

Pointers are a core feature of the C programming language. YAKINDU Statechart Tools' Deep C Integration is making C pointers available to you in your statecharts. In particular, you can

  • declare typed pointer variables,
  • assign a pointer to a pointer variable of the same type,
  • retrieve the pointer pointing to a given variable,
  • pass pointers as parameters to functions, and
  • receive a pointer as a functions return value.

Declaring pointer variables

Pointer variables are declared in a statechart’s definition section as shown in the following example:

var n: int32_t
var pInt: pointer<int32_t>
var ppInt: pointer<pointer<int32_t> >
var pTriangle: pointer<Triangle>;

The declarations above declare

  • n as a (non-pointer) variable of type int32_t,
  • pInt as a pointer variable that is pointing to a variable of type int32_t,
  • ppInt as a pointer that is pointing to a pointer that is pointing to a variable of type int32_t, and
  • pTriangle as a pointer to a variable of the self-defined type Triangle.

Please note:

When closing the type specification in a pointer declaration with angle brackets, e. g. pointer<pointer<int32_t> >, the > characters must be separated from each other by one or more white space characters. Writing e. g. pointer<pointer<int32_t>> would result in an error. This restrictions pertains to the current release candidate of YAKINDU Statechart Tools PRO only and will be fixed in the final release.

Using pointer variables

In order to actually assign a pointer to a pointer variable, you have to get hold of that pointer. To retrieve the pointer to a variable v, use v's extension function pointer. That is, for a variable v, the expression v.pointer evaluates to a pointer to v. Each variable has the pointer extension function.

Example: Let’s say the pointer variable pInt (declared in the example above) should point to the variable n. The following assignment accomplishes this:

pInt = n.pointer

Similarly, a pointer to a pointer to a base type can be retrieved as follows:

ppInt = pInt.pointer;

Or even:

ppInt = n.pointer.pointer

In order to deference a pointer, i. e. to retrieve the value of what the pointer is pointing to, use the value extension function, which is available on all pointer-type variables.

Example: Let’s say the pointer variable pInt (declared in the example above) is pointing to some int32_t variable. The value of the variable pInt is pointing to should be assigned to the int32_t variable n. The following assignment accomplishes this:

n = pInt.value;

Similarly, if ppInt points to a pointer pointing to some int32_t variable, the following statement retrieves the latter’s value:

n = ppInt.value.value;

Passing pointer parameters to C functions is straightforward. Let’s say you have a C function to rotate a triangle around a center point by a given angle. The C function is defined like this:

Triangle* rotateTriangle(Triangle* triangle, Point* centerPoint, float angle) { … }

Provided the function is declared in an imported C header file, you can call it directly like this:

pTriangle2 = rotateTriangle(pTriangle, pCenterPoint, 45.0);

Please note:

Assigning a pointer to a pointer variable is only possible if the pointer types are the same.

Arrays

Unlike other variables, arrays are not defined in a statechart’s definition section, but rather on the C side in header files. Importing a C header containing an array definition makes the array available to a statechart.

While YAKINDU Statechart Tool’s Deep C Integration provides mechanisms for accessing individual elements of an existent array, arrays must be allocated statically or dynamically in C. Initializing the array elements is possible in C as well as in the statechart. However, depending on the concrete application it might generally be easier in C.

The header file sample_arrays.h defines a couple of sample arrays:

#ifndef SAMPLE_ARRAYS_H_
#define SAMPLE_ARRAYS_H_

#include <stdint.h>
#include "triangle.h"

int32_t coordinates[] = {0, 0, 10, 0, 5, 5};

Triangle manyTriangles[200];

int32_t * pArray[10];

#endif /* SAMPLE_ARRAYS_H_ */

The following arrays are defined:

  • coordinates is statically allocated to hold six int32_t elements and it is initialized with values for all six of them. More precisely, the number of elements in the initializer determines the size of the array.
  • manyTriangles is statically allocated with enough memory to hold 200 elements of the self-defined Triangle type. However, these elements are not initialized. This can and should be done either in C or in the state machine. An example is given below.
  • pArray is of size 10 and holds pointers to int32_t values.

As mentioned above, importing a header file containing array definitions into the statechart’s definition section is sufficient to make the arrays available in a statechart. Example:

import: sample_arrays

With this import, you can access the arrays in statechart language expressions, for example in a state’s local reactions:

entry /
coordinates[2] = 42

Writing to array elements is as straightforward as you would expect. Examples:

coordinates[0] = coordinates[0] + 1;
pArray[3] = n.pointer;
pArray[4] = coordinates[0].pointer

Passing arrays as parameters to C functions is straightforward. Let’s say you have a C function sort to sort the elements of a one-dimensional int32_t array and return a pointer to the sorted array:

int32_t* sort(int32_t data[], int size) {…}

Please note that in C a function cannot return an array as such, but only a pointer to it. Analogously you cannot pass an array by value as a parameter to a function, i. e. the data bytes the array is consisting of are not copied into the function’s formal parameter. Instead a pointer to the array is passed to the function, or – to be more exact – a pointer to the array’s first element. To express this in the function’s formal parameter type, you can specify the sort function equivalently to the above definition as follows. The data parameter is now specified as int32_t* data instead of int32_t data[], but the meaning is exactly the same.

int32_t* sort(int32_t* data, int size) {…}

Provided the function is declared in an imported C header file, you can call it directly like this:

sort(coordinates, 6)

Please note:

The current YAKINDU Statecharts Tools release only supports statically allocated arrays. Arrays dynamically allocated using malloc() or calloc() will be supported in a later version.

Simulation

During a statechart simulation full access to the C data structures is possible on all layers. The user can inspect them as well as modify them in the simulation view.

The state machine below exemplifies this. Initially it defines two rectangles a and b with certain widths and heights. The state machine calculates the rectangles' respective area size, stores their sizes in two int32_t variables named area_a and area_b, and compares them. Depending on the result, it proceeds to state A is larger or to A is smaller. Only if both a and b have the same area – not necessarily the same width and height –, the state machine proceeds to its final state.

When one of the states A is larger or A is smaller is active, the rectangles' properties can be changed. Triggering the compare_size event transitions to the Check state which repeats the area size comparison as described above.

The rectangle comparison statechart

The rectangle comparison statechart

The state machine’s definitions are as follows:

import: rectangle

interface:
    var a: Rectangle
    var b: Rectangle
    var area_a: int16_t
    var area_b: int16_t

internal:
    event compare_size

The Rectangle datatype is defined in a new header file rectangle.h with the following contents:

#include "./point.h"

typedef struct {
    Point lowerLeft;
    int16_t width, height;
} Rectangle;

In order to simulate the statechart, right-click on the statechart file in the project explorer and select Run As → Statechart Simulation from the context menu.

The statechart simulation

  • starts,
  • performs the initializing actions specified in the transition from the initial state to the Check state,
  • in the Check state, calculates the rectangles' areas and stores the results in the area_a and area_b variables,
  • transitions to the A is larger state, because its guard condition is fulfilled, and
  • stops, waiting for the compare_size event to occur.

Inspecting C data structures

Inspecting C data structures

Inspecting C data structures

The simulation view in the screenshot above is showing the state machine’s variables and their values. Click to open or close the nested data structures. The image below shows in particular

  • the rectangles' respective width and height values as have been set initially,
  • the calculated areas of the a and b rectangles,
  • the coordinates of the points defining the respective lower left corner of the rectangles.

Warning:

Simple C variables and fields in C data structure are not initialized. Never try to read a variable or field you haven’t written before, because it might contain arbitrary values.

Even if the Point data structures in the example above look like having been initialized to defined values, they are not. Without going into details, in C, variables are generally not initialized. This also holds for statechart variables from the C integration. If you are reading a variable, make sure you have written to it before. Otherwise you might get surprising and non-deterministic results.

Modifying C data structures

Change a variable’s or field’s value as follows:

  1. Click on the value displayed in the simulation view.
  2. Enter the new value into the text field, see figure "Modifying C data values" where a.height is being edited and the previous value 7 is replaced by 3.
  3. Press the [Enter] key to quit editing and to write the new value to the variable or field. Giving the input focus to another window has the same effect.
  4. You can cancel editing by pressing the [Esc] key. The variable’s or field’s value remains unchanged.

Modifying C data values

Modifying C data values

In the example, click compare_size to trigger the event. The state machine transitions to the Check state, recalculates the areas, and behaves as explained above.

Rectangle areas modified and rechecked

Rectangle areas modified and rechecked

Looking up a type definition

Given a variable definition in a statechart’s definition section, you can lookup the corresponding type definition. The definition section must be in editing mode, i. e. you must have double-clicked into it. Now press the [Ctrl] key and move the mouse pointer over the type name. The former changes its shape into a hand symbol and the latter changes into a hyperlink:

Looking up a C type

Looking up a C type

Click on the hyperlink to open the header file containing the respective type declaration.

Showing the C type definition

Showing the C type definition

Enums

Besides specifying structure types, it is also possible to declare enumeration types in a C header. Here’s the header file color.h which defines the Color enumeration type:

#ifndef COLOR_H_
#define COLOR_H_

typedef enum {
	RED, GREEN, BLUE, YELLOW, BLACK, WHITE
} Color;

#endif /* COLOR_H_ */

Now let’s extend the Triangle defined above by a fill color:

#ifndef TRIANGLE_H_
#define TRIANGLE_H_

#include "./point.h"
#include "./color.h"

typedef struct {
	Point a, b, c;
	Color fillColor;

} Triangle;

#endif /* TRIANGLE_H_ */


Similar to the Triangle type or any other C type, the Color enumeration type can be used in the statechart, e. g. by declaring an additional interface variable:

import: color
import: triangle

interface:
	var t: Triangle
	var c: Color = Color.BLUE

Please note that in contrast to structured types, enumeration variables can be initialized directly in their definitions.

In order to see which values are available the content assist, triggered by [Ctrl+Space], is helpful again.

Using content assist to select an enumeration value

Using content assist to select an enumeration value

Once initialized, the c variable can now be used e. g. in an assignment to the triangle t's fill color:

t.fillColor = c;

Accordingly, during simulation the values of enum variables are displayed in the simulation view. It is also possible to modify them manually.

Using content assist to select an enumeration value

Using content assist to select an enumeration value

Operations

Functions declared in a C header file become available in a statechart. The state machine can call it as an operation.

Let’s say our rectangle.h header file not only defines the data type, but also declares one or more C functions to operate on them. The following line declares a function named area, taking a Rectangle parameter by value and returning an int32_t result.

extern int32_t area(Rectangle r);

For the sake of the example, let’s assume the function calculates the area of the given rectangle. Of course we could also do this with means built into the statechart language. However, in the general case you neither can nor want to do that.

  • Implementing the functionality in the statechart language might not be possible, because the latter does not provide the necessary means, e. g. to pull some data from an external source.
  • Even if it would be possible to implement the functionality in the statechart language, it might still not be desirable, if the functionality has been developed and fully tested in C already. You will neither want to re-invent the wheel nor would you want to introduce any new errors into an alternative implementation.

YAKINDU Statechart Tools parses function declarations in header files and makes the functions available in the statechart language. It doesn’t care where those functions are defined – or whether they are defined at all – nor what they do. Questions like these will become relevant later when the state machine is generated as C source code, compiled and linked to the functions' implementations.

For now, once the statechart knows about the area function’s declaration in the C header file, the function can be used immediately in statechart language operations. Any operation declaration in the statechart’s definition section is not needed. Example:

Using content assist to enter a C function call

Using content assist to enter a C function call

Here’s the complete example with the area calculations done by the area function:

Example calling the "area" function

Example calling the „area” function

Please note:

State machines calling C functions as operations are debarred from simulation and debugging. The simulator is not yet capable to call C functions.

Macro Definitions

Macro definitions declared in a C header file via the #define keyword are also accessible in the statechart. There are two kinds of macro definitions, leading to different interpretations in the context of state machines:

  • Constant definitions, e. g. #define PI 3.14, are translated into const properties. They can be used within a statechart just like normal variables, but are not changeable during a simulation. The value of such a constant is derived from the value part of the macro definition. However, there are cases where this derivation is not successful, e. g. when other macros are referenced.
  • Parameterized macros, like #define MIN(x,y) ((x<y) ? x : y), are translated into operations. They can be used within a statechart just like normal operations, but are not interpreted during a simulation.

Generating C source code

Code generation, i. e. turning a statechart model into source code of a programming language, is explained in the section Generating state machine code. Therefore we won’t go into the details here, but instead only put some emphasis on code generation specialties of the Deep C Integration.

Creating a generator model

In the statechart model introduced above, do the following:

  1. In the project view, right-click on the project’s name. The context menu opens.
  2. In the context menu, select New → Code Generator Model. The YAKINDU Generator Model wizard opens.
  3. Enter a file name into the File name field, e. g. c.sgen, and click Next >.
  4. In the Generator drop-down menu, select YAKINDU SCT C Code Generator.
  5. Select the statechart models to generate C source code for. Click Finish.

YAKINDU Statechart Tools creates the following generator model in the file c.sgen:

GeneratorModel for yakindu::c {

    statechart statechart {

        feature Outlet {
            targetProject = "Geometry"
            targetFolder = "src-gen"
            libraryTargetFolder = "src"
        }
    }
}

YAKINDU Statechart Tools creates the target folders src and src-gen and generates the C source representing the statemachine into them.

The generated C code

Particularly interesting are the files Statechart.h and Statechart.c.

Statechart.h first includes the sc_types.h header followed by very same C header files that have been included in the statechart:

#include "sc_types.h"
#include "rectangle.h"

The generated code in Statechart.h then uses the native standard and user-defined C data types. For example, the statechart implementation defines the type StatechartIface as follows:

/*! Type definition of the data structure for the StatechartIface interface scope. */
typedef struct
{
	Rectangle a;
	Rectangle b;
	int32_t area_a;
	int32_t area_b;
	Point p;
} StatechartIface;

By including Statechart.h all definitions are available in Statechart.c, too. For example, a getter and a setter function for the Rectangle variable a are defined as follows:

Rectangle statechartIface_get_a(const Statechart* handle)
{
	return handle->iface.a;
}
void statechartIface_set_a(Statechart* handle, Rectangle value)
{
	handle->iface.a = value;
}

The external area function is called in the entry actions section of state Check:

/* Entry action for state 'Check'. */
static void statechart_enact_main_region_Check(Statechart* handle)
{
	/* Entry action for state 'Check'. */
	handle->iface.area_a = area(handle->iface.a);
	handle->iface.area_b = area(handle->iface.b);
}

Currently supported primitive types

The Deep C Integration natively supports the following primitive C types. That is, in a statechart without any additional data type definitions, the following types are readily available:

  • bool
  • double
  • float
  • int16_t
  • int32_t
  • int64_t
  • int8_t
  • string
  • uint16_t
  • uint32_t
  • uint64_t
  • uint8_t
  • void

Current restrictions

The current release candidate of YAKINDU Statechart Tools PRO is still missing some C functionalities that will be approached as soon as possible by subsequent releases. Among others, the following issues are known to be not available yet:

Type range checks

Type range validations are currently not implemented. As a consequence, it is possible to e. g. assign an int32_t value to an int8_t variable one without any warning.

Plain struct, union, and enum types

In C it is possible to define structs, unions and enums without a typedef. They can be referenced by using the corresponding qualifying keyword ( struct, union, or enum, respectively). As the statechart language does not support these qualifiers, the usage of struct, union and enumeration types is currently restricted to those defined by a typedef.

Performance issues with very large C projects

Statecharts that

  • include a very large number of header files ( .h files), or
  • use a very large number of preprocessor macro definitions ( #define)
  • or both,

might cause YAKINDU Statechart Tools to spend a considerable amount of time processing („parse”) them.

Typically, #define directives are coded in header files. Header files in turn can include further header files using the #include directive. The total amount of header files and #define directives involved is not always transparent to the developer, especially if system header files or header files of a programming framework are included.

YAKINDU Statechart Tools parses header files to make types and names defined therein available to your statechart. However, you typically won’t need all of them. In order to save processing time you can explicitly exclude header files or folders from parsing.

YAKINDU Statechart Tools maintains excluded header files and folders in a blacklist. Initially that list is empty. To modify the blacklist, proceed as follows:

  1. In the project explorer, right-click on your project. The context menu opens.
  2. Select the Properties context menu entry. The Properties dialog opens.
  3. In the navigation menu on the left hand side of the dialog, open the YAKINDU SCT entry.
  4. Click on the C Domain entry. The blacklist appears at the right.
  5. To add one or more header files or folders to the blacklist, click on the Add button. A file selection dialog appears, allowing you to select files with a .h extension or folders.
    Excluding C header files from parsing
  6. To remove files or folders from the blacklist, select them in the list and click on the Remove button.
  7. Click on the OK button to make your changes permanent and close the dialog.

Aside from the buttons mentioned above, the blacklist dialog has three more buttons:

  • Apply: Makes your changes permanent, but leaves the dialog open.
  • Cancel: Closes the dialog without making your changes permanent – as long as you haven’t applied them yet.
  • Restore Defaults: Empties the list.

After changing the blacklist, please perform a clean on the project, followed by a build. This is needed for your changes to take effect. After that, any imports excluded by the blacklist will no longer show up in your statechart’s content assist.

Please get in touch with us

Please note that the preceding list of restrictions might not be complete. If you discover any further problems, please do not hesitate to contact us! Your feedback is highly appreciated!

Edit on GitHub

The statechart language

This chapter describes the language used to describe statecharts. It consists of graphical and textual elements.

State machine basics

Words and Definitions

To prevent any confusion, some definitions:

  • A finite state machine (FSM) is an abstract machine that consists of a finite set of states and its behavior.
  • A state diagram is one of many possible representations of an FSM. A state transition table would be different way to describe an FSM.
  • A statechart is an extended form of the classic state diagram. However, the basic principles are the same. In this documentation, the term „statechart” usually means the graphical representation of a state machine, while the term „state machine” usually pertains to its dynamic aspects.

Automata theory

YAKINDU SCT was designed to create statecharts according to David Harel’s statechart theory. Harel statecharts have become part of the Unified Modeling Language (UML). Harel introduced a couple of extensions to convetional state diagrams, making them more concise and more readable.

Classic state diagrams

Classic state diagrams describe finite state machines (FSM) and consist of the following things only:

  • A (finite) set of possible states
  • A start state
  • An accepting or final state
  • A set of transitions connecting states
  • A set of input symbols
  • A set of output symbols
  • An output function, mapping pairs of input symbols and states to output symbols

These parameters describe a finite state machine completely.

Moore and Mealy machines

In classic automata theory, two distinct types of finite state machines exist:

  • The Moore machine , where the output of the machine depends on its current state only
  • The Mealy machine , where the output of the machine depends on its current state and its input.

It is possible to transform these types of machine into each other, however, states, transitions and the output function need to be changed to achieve this.

Harel statecharts

Harel statecharts extend the classic state diagrams by a couple of additional aspects, resulting in representations that need much less states and transitions, making them much more compact, expressive, manageable and comprehensible – though not as mathematically concise as Moore or Mealy machines.

Harel added the following notations, dealing with hierarchy, concurrency, and communication:

  • Composite states and regions, containing nested states or regions.
  • History states, allowing to re-enter a composite state or region at the point where they had been left.
  • Orthogonal states, allowing to run state machines concurrently.
  • Events, allowing for communication between orthogonal states.
  • Variables, allowing to memorize values and reducing the necessary number of states drastically.
  • Actions (output) can not only occur along transitions, but additionally inside of states, especially as entry actions or exit actions.
  • Activities (operations), bridging the gap between a state machine and real-world behavior.

Since Harel statecharts are a superset of Mealy and Moore machines, it is possible to model the latter in YAKINDU Statechart Tools as well.

Statechart model

A statechart or statechart model consists of several components.

The most important part is the diagram, comprising states and transitions. They are organized into zero or more regions. For hints how to use the statechart editor to modify a statechart, see section „Editing statecharts”.

The definition section is a textual statechart model component. It contains definitions of namespaces, interfaces, variables, events, operations, etc.

Most objects defined in the definition section have a type. For example, a variable could be of the integer type. Another example is the return type of an operation (if any). Events can be typed as well. For details, see the section about types.

A type system or domain specifies which types are available. YAKINDU Statechart Tools has a built-in type system.


YAKINDU Statechart Tools Professional Edition allows for additional domains to be installed and includes and domain for the C programming language.

Statechart elements

Region

YAKINDU statecharts are organized in regions. Hence it is possible to organize multiple state machines in different regions and to run them virtually concurrently.

Parallel regions

Parallel regions

Regions can contain states. Regions can be contained in states as well, turning them into composite states.

State

States are the central elements of a state machine. A state has to be placed inside a region and must have a name that is unique inside this region. During state machine execution, a state can be active or passive.

A state can have behavior. The behavior specifies which actions are executed. Such actions can be triggered by entering the state, leaving the state, occurrence of events, conditions becoming true, or time passing. The behavior specification is a text inside the state’s box and consists of a sequence of so-called local reactions .

The state Countdown in the following example has three local reactions:


State example

State example: A simple state machine to launch a rocket.

When the Countdown state is entered, it sets the counting variable t to 10. As long as that variable’s value is greater than 0, each second it gets counted down by 1. The operation speak provides audible information on how many seconds to go until rocket launch. If t has been counted down to 0 – checked by a transition’s guard condition –, the Countdown state will become inactive and launches the rocket upon exiting. The countdown can be aborted anytime before launch by a cancel event. (See figure "Transition example" for a different state machine which solves the same task but puts more emphasis on transitions.)

Can you find the mistake in the state behavior?

Transition

A transition is the transfer of one state to another. A transition is diagrammed as an arrow leading from a source state to a target state.

A transition should have a transition reaction . The transition reaction specifies what the conditions are for this transition to be taken and which actions, if any, are to be executed. The occurrence of an event, a condition becoming true, or time passing can trigger a transition. The transition’s reaction is attached to the transition’s arrow as a text. Its syntax can be found in the section on reactions.

Please note: Transitions without any reaction are possible, but useless. They will never be taken.

Here’s an example:


Transition example

Transition example: A simple state machine to launch a rocket.

The initial transition’s effect sets the counting variable t to 10. As long as that variable’s value is greater than 0, each second it gets counted down by 1. The operation speak – executed as one of a transition’s actions – cares for audible information on how many seconds to go until rocket launch. If t has been counted down to 0 – checked by a transition’s guard condition –, the rocket is launched. The countdown can be aborted anytime before launch by a cancel event which triggers the appropriate transition to fire. (See figure "State example" for a different state machine which solves the same task but puts more emphasis on states.)

Figure "Transition syntax overview" shows how the syntax of a transition reaction is defined.


Transition syntax overview

Transition syntax overview

Entry and exit points

Unlike UML transitions, transitions in YAKINDU statecharts can denote specific entry or exit points when leaving or entering composite states. This is denoted by the TransitionProperties non-terminal symbol in figure "Transition syntax overview".

The syntax to connect a transition to a named entry is as follows:

# >entry-name

A named exit can be used as follows:

# exit-point-1> [ exit-point-2> ]…

It is also possible to have both in one transition specification. The order of exit and entry points is irrelevant. Instead, the position of the > character is decisive:

  • If the > character is to the right of a name, like in exit_name>, that name denotes an exit.
  • If the > character is to the left of a name, like in >entry_name, that name denotes an entry.

The sample statechart in figure "Entries and exits" contains two composite states:

  • The composite state Process models a process with two passes (states) A and B. If both passes succeed, the composite state is left via the no_problem exit node. However, if an error occurs in either A or B the execution flow proceeds to the problem exit point and leaves the composite state there.
  • The composite state Handle result is intended to handle the processing result, be it success or failure. It has two entry points success and failure.

The question is how to connect the exit points of Process to the corresponding entry points of Handle result. This is done by two transitions leading from Process to Handle result and appropriate specifications.

The transition shown on the left specifies # no_problem> >success. This means: If the source composite state is left via the no_problem exit point, then enter the target composite state at the success entry point. The specification of the transition on the right is analogous: If the source state is left via the problem exit point, then enter the target state at the failure entry point.

Alternatively, Process could have been modeled with two different error exit states, say error_1 and error_2. This would allow to respond differently to different error conditions, while still enabling to catch them both with a single flow. A transition with # >error_1 >error_2 problem> would do so.


Entries and exits

Entries and exits

Transition priorities

If a state has several outgoing transitions, based on the events that occured and on guard conditions, several of these transitions could fire at the same time. Here’s simple example:


Transition priorities [1]

Transition priorities [1]

Which transition will fire when the state machine is in state A and event e1 occurs? Fortunately YAKINDU state machines behave deterministically, so this question can be answered unambiguously. Outgoing transitions of a state have a definite order and are prioritized by that order. The state machine checks the transitions one by one in their order and executes the first transition that fulfills all prerequisites for firing.

As you can see in figure "Transition priorities [1]", little numbers indicate the transition priorities. These numbering is shown only if the option Show transition priority in Window → Preferences → YAKINDU SCT → Diagram Appearance is activated. By default, this option is not activated.

The order of transitions is a property of their source state. In the example above, the transition priorities of state A appear like this in the Properties view:


Transition priorities [2]

Transition priorities [2]

To change priorities, select a transition in the Properties view and click on the up or down button as appropriate.

Entry

When a state machine starts or when the control flow enters a region, an entry defines which state is to be activated first. The flow passes through the state machine’s or the region’s entry and transitions to the target state the entry’s transition is pointing to. This state becomes active.

An entry has a single outgoing transition and no incoming ones. Its outgoing transition has neither a trigger nor a guard condition. It is always taken immediately.

An entry is depicted as a filled circle, see figure "Entry, exit, and final state".


Entry, exit, and final state

Entry, exit, and final state

Default entry and initial state

An entry without a name is called the default entry. It is similar to the UML’s initial state. However, while the transition from the initial state to an ordinary state may specify a guard, the transition sourced at an entry cannot.

A region may have at most one default entry.

Named entries

The default entry specifies a single entry point into a region. A region can provide a multitude of additional or alternative entry points, thus implementing different kinds of behavior, depending on which entry point has been taken. If a region comprises multiple entries, these entries must be named. An entry’s name must be unique within its region.

The default entry implicitly has the name default. Alternatively, it is possible to explicitly give the name default to the default entry. Semantically both variants are equivalent. Whether an entry has no name or it has the name default: in both cases the entry is called default entry.

An incoming transition to a composite state can specify a named entry it shall lead to. For details, see Entry and Exit points.

Named entries have no equivalent in the UML.

The sample statechart in figure "Entry state" has a composite state named Handle result. This composite state has a default entry as well as an entry called failure. If state A is active and the error trigger fires, control is transitioned to the Handle result composite state. The notation error # >failure specifies that the failure entry is to be used; see Entry and exit points.


Entry state

Entry state

Exit

An exit is a pseudo state that is used to leave and deactivate a composite state. Exits are counterpart to entries. They are also known as exit points, exit states, or exit nodes. See section "Final state" for a different way to terminate a region or state machine.

An exit may have multiple incoming transitions and has no outgoing one.

An exit state is depicted as an unfilled circle with an X-shaped cross, see figure "Entry, exit, and final state".

Within a region, multiple exits are allowed. Each exit must either have a name that is unique within its region or be the default exit. The default exit is either unnamed or has the name default, which is semantically equivalent. A region may have at most one default exit. For details on how to connect named exits to transitions, see subsection "Entry and exit points" in the section on transitions.

When a composite state reaches an exit in any of its regions, all states in other regions of that composite state, if any, are deactivated immediately. The composite state will be left and be deactivated. It maintains no status information that could be probed from the outside. In other words, reaching an exit in one of a composite state’s regions has severe consequences for all the other regions since they are exited and disposed immediately. After that, the containing composite state is also exited. There must be an unguarded transition that catches the exit and leads to another state outside of the composite state. The semantics of exit is different from that of final state; please see section "Final state" for details.

Exits have no equivalent in the UML.

Final state

A final state denotes the end of the execution flow of a state machine or region. See section "Exit" for a different way to terminate a composite state.

A final state is depicted as an unfilled circle with a smaller filled black circle inside, see figure "Entry, exit, and final state".

A final state can have multiple incoming transitions and has no outgoing ones.

Within a region, only a single final state is allowed, but each region may have its own final state.

When a region reaches its final state, control stops there and waits until all other orthogonal regions, if any, have reached their respective final states, too. The semantics of final states is different from that of exits; please see section "Exit" for details.

Note

Final states are proper states. This is different from the UML, where a final state is a pseudo state, i.e. in the UML a final state cannot have any properties normal states can have, except for a name.

Choice

A choice is a pseudo state. It can be used to model a conditional path. A choice node divides a transition into multiple parts.

Usually the first transition points towards the choice node. One of the choice’s outgoing transitions can carry a condition.

Synchronization

Synchronization is a means to either split a flow into several parallel regions of a substate or to join several parallel flows from orthogonal regions into a single flow. The synchronization state corresponds to the UML’s concepts of fork and join.

Whether a synchronization state behaves as a fork or as a join depends on its usage.

  • A synchronization with a single incoming transition and several outgoing ones forks the flow into several regions of the same substate. These regions are executed in parallel, see section "Orthogonal states".
  • A synchronization with several incoming transitions from parallel regions of the same substate and a single outgoing transition joins the incoming flows into a single flow of execution.

A synchronization state is shown as a thick horizontal or vertical line, as can be seen in figure "Synchronization state".

Synchronization state

Synchronization state

For a synchronization to actually join the incoming transitions and execute the outgoing one, all of the following conditions must be met:

  • All source states must be active.
  • All guard conditions that are specified must be fulfilled.
  • If one or more triggers are defined, at least one trigger must fire at a point in time while the conditions above are met.

Figure "Synchronization state" shows a sample statechart containing a forking and a joining synchronization. After having left the Initialize state, the synchronization state forks the execution flow into two regions r1 and r2. Both are part of the Process composite state and both are executed virtually concurrently. That is, when activating Process, the substates Line A 1 and Line B 1 also become active. When the flows continues and both Line A 2 and Line B 2 have been reached, the synchronization state on the right-hand side joins the flows and transitions to substates Cleanup, making it the active state. However, as long as only one of Line A 2 and Line B 2 is active, the synchronization will wait for the other one to also become active, before proceeding to Process.

The example also demonstrates different lengths and orientations of the synchronization symbol. In the statechart editor, first select the synchronization symbol, then use a handle in one of the symbol’s corners to change length or orientation. The handles in the middle of the symbol have no effect.

Composite state

A composite state is a state containing a region. All usual characteristics of regions apply – they can have entry states, exit states, and so on. Transitions can connect inner states of composite states to states that are at the same hierarchical level as the composite state, or they can directly be sourced at the composite states. This is useful for constructs like „cancel”, where the operation of a composite state should be able to be aborted every time, no matter the active substate.

See entry state, exit state, final state and connecting entries and exits to transitions for further information.

Orthogonal states

Orthogonal states are basically composite states with more than one region. These regions are executed virtually concurrently. Please note the word virtually! YAKINDU SCT does not guarantee in any way that orthogonal regions are really executed concurrently. At the time being, no code generator utilizes threads to achieve this. Orthogonal states should rather be understood as a manner to have two or more sub-statecharts working together, however, they are executed one after the other other in every cycle, in a defined order: top to bottom, left to right. The same applies to multiple regions in the statechart itself. Consult section "Raising and processing an event" for further information on region priorities and their meanings for the statechart execution.

The presumably most famous example for orthogonal states is the keyboard example:

Orthogonal states

Orthogonal states

Shallow history

The shallow history state is a pseudo state that is placed inside regions of composite states. It is used to remember the last active state inside a composite state. This makes it possible to jump back to the remembered state instead of starting at the inner initial state again. History states save the current state exactly when their containing region is left.

The following example, showing the answering of a questionaire, explains this:

Shallow history [1]

Shallow history [1]

Particularly interesting in this statechart are the checkProgress and goon events. The checkProgress event jumps back to the init state while assigning the current progress count to the variable temp. The event goon jumps to the shallow history state that was placed inside the composite state.

Shallow history [2]

Shallow history [2]

Shallow history [3]

Shallow history [3]

When the goon event is triggered, the most recent active state inside of the composite state answeringQuestions is activated again.

Deep history

Deep history is similar to shallow history, but more complex. With a deep history the latest state of multiple nested states is remembered.

Definition section

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

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 an internal interface. Especially useful for larger parts of a statechart model is the possibility to use namespaces. All these elements are properly reflected in generated source code as much as the respective target language supports it.

Scopes

Namespace

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

namespace trafficlights

Interface scope

Declarations in the interface scope are externally visible. They can be shared within the environment.

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

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 the same as a named interface.

Internal scope

Definitions made in the internal scope are visible to the statechart only. 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 outside of generated state machine 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 readonly (constants):

var readonly pi: real = 3.1415

Variables can be referenced by the environment.

var external variable3: integer = 34

Constants

Variables can be immutable. For this special variable the keyword const is used:

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 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 can be incoming or outgoing. An external event is either raised by the environment and processed by the state machine or it is raised within the state machine and can be processed outside.

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.

interface NamedInterface:
  in event event1
  out event event2

Events in the internal scope do not have a direction, because they do not leave the state machine.

internal:
  event ev1

Events with variables

An event can be typed and can carry a value:

internal:
  event event1 : integer

An event can have a value assignment:

internal:
  event event1: integer = 25

Read access to the value of an event is possible in the statechart using the valueof() built-in method, see section "Built-in methods" for details. Please note that reading an event’s value is possible only when the event actually occurs, for example in a guard:

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 event raising, see section "Raising an event". Regarding the more complicated details of processing an event, see section "Raising and processing an event".

Operations

An operation makes external behaviour defined as a function or method of a programming language accessible to a state machine. It is mapped to a suitable construct in a target language, 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

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

Reactions

Reactions are one of the most important features of YAKINDU SCT’s statechart language. Basically everything that happens in a statechart happens in a reaction.

Reactions can be attached to states as well as transitions. While states can have as many reactions as you wish, transitions can only have one.

The syntax follows the standards of automata theory, where the output is put into relation to the input of the automaton using the following notation (especially Mealy automata): input / output

YAKINDU SCT uses the following syntax for all reactions:

Trigger [Guard] / Effect

The possible values for the trigger depend on the context, either a state or a transition.

All three components are grammatically optional, though a reaction with neither a trigger nor a guard will never happen. Reactions with no effect are useful for transitions, the slash / can be omitted as well then.

Trigger specifications

Reactions can define a trigger that needs to happen to activate the reaction. Events are possible triggers, as well as certain keywords defined in the statechart language.

Triggers can be combined with guards to further define the execution conditions of the reaction. In that case, the reaction is only executed when both the trigger fires and the guard is true.

You can define multiple triggers for the reaction: trigger1, trigger2. When at least one of them occurs, the reaction’s trigger fires.

The special triggers defined by the statechart language are the following:

Examples:

  • A reaction shall occur when an event named ev1 happens: ev1 / effect
  • A reaction shall occur when ev1 or ev2 happen: ev1, ev2 / effect
  • A reaction shall occur when ev1 happened or three seconds passed: ev1, after 3s / effect

Figure "Event specifications syntax overview" shows where to put event specifications in a reaction trigger and how their syntax is defined.


Event specifications syntax overview

Event specifications syntax overview

Reaction trigger "after"

The after trigger specifies a one-shot time event.

After the specified time the reaction is triggered. An after trigger can be used in transitions of states as well as in local reactions of states and statecharts. The specified time starts when the state or statechart is entered.

after 20 s

Synopsis: after time unit

The time value is a integer literal or an expression with an integer value.

The time unit is one of:

  • s: seconds
  • ms: milliseconds
  • us: microseconds
  • ns: nanoseconds


Reaction trigger "every"

The every trigger specifies periodic time events.

The reaction is triggered recurrently after each passing of the specified period of time. An every trigger can be used in transitions as well as in local reactions of states and statecharts. The specified period of time starts when the state or statechart is entered and repeats periodically.

every 200 ms

Synopsis: every time unit

The time value is a integer literal or an expression with an integer value.

The time unit is one of:

  • s: seconds
  • ms: milliseconds
  • us: microseconds
  • ns: nanoseconds


Reaction trigger "always"

This trigger is always true and enables a reaction to be executed in every run-to-completion step (RTC). The always trigger is equivalent to the oncycle trigger.


Reaction trigger "oncycle"

This trigger is always true and enables a reaction to be executed in every run-to-completion step (RTC). The always trigger is equivalent to the oncycle trigger.


Reaction trigger "else"

This trigger is intended to be used for the outgoing transitions of choice pseudo states to make sure that there is always an outgoing transition that can be taken. It can only be be used in transitions and implies the lowest evaluation priority for that transition. The default trigger is equivalent to the else trigger.


Reaction trigger "default"

This trigger is intended to be used for the outgoing transitions of choice pseudo states to make sure that there is always an outgoing transition that can be taken. It can only be be used in transitions and implies the lowest evaluation priority for that transition. The default trigger is equivalent to the else trigger.


Reaction trigger "entry"

An entry trigger marks actions that are carried out when entering a state or state machine, immediately before making it active.

Please note: A self-transition, leading from a state back to itself, actually first leaves and then re-enters the state. That means that its exit effects as well as its entry effects are executed. This is in contrast to a local reaction, which is executed without leaving the active state. Thus exit and entry effects are not executed in the latter case.


Reaction trigger "exit"

An exit trigger marks actions that are carried out when exiting a state or state machine, immediately after making it inactive.

Please note: A self-transition, leading from a state back to itself, actually first leaves and then re-enters the state. That means that its exit effects as well as its entry effects are executed. This is in contrast to a local reaction, which is executed without leaving the active state. Thus exit and entry effects are not executed in the latter case.

Guard condition

A guard is another option to specify when a reaction should be executed. They can be used on their own or in conjunction with a specified trigger . In the latter case, the trigger needs to be fired and the guard needs to be true for the reaction to fire its effect.

As a reminder, the complete reaction syntax is as follows:

Trigger [Guard] / Effect

In the square brackets you can use any expression that evaluates to a boolean – boolean variables, all kinds of comparisons and operations returning a boolean fall into this category. Please note: YAKINDU SCT does not support the concept of „truthy and falsey” seen in C, Javascript or Python.
[x] is not a valid guard condition unless the type of x is boolean.

Please consult figure "Guard condition syntax overview" to find out how a guard condition syntactically fits into a reaction.


Guard condition syntax overview

Guard condition syntax overview

Reaction effect

A transition specification may contain zero, one or more actions that are executed when the transition fires, like assigning a value to a variable, calling an operation, or raising an event. All actions taken together are called the transition’s reaction effect. The complete syntax for reactions is as follows:

Trigger [Guard] / Effect

This means that the effect will only be executed when the trigger fires or does not exist and the guard condition evaluates to true or does not exist and at least a trigger or a guard exist. See the sections about triggers and guard conditions.

Normally, actions are statements. Note that you can also use the conditional expression to make some constructs more elegant.

If an effect shall consist of multiple actions, they need to be separated by a semicolon ;. That means the last action is not followed by a semicolon: Trigger [Guard] / action1; action2; action3

A reaction in a state always has a reaction effect with at least one action.

Figure "Reaction effect syntax" shows the syntax definition.


Reaction effect syntax

Reaction effect syntax

Example: When a transition or local reaction specified as ev1 [x > 3] / x += 1; notify(x); raise ev2 is executed, it increments variable x by 1, calls the notify operation with x as parameter, and raises event ev2.

Raising and processing an event

For the syntax of event raising, see Raising an event.
For the syntax of event declaration, see Events.

When a reaction raises an event, please consider two important points:

  • The event exists in the current run-to-completion step (RTC) only and thus can be dealt with during the rest of that particular RTC only.
  • Orthogonal regions are executed sequentially and in a deterministic order.

This has remarkable consequences on where an event is „visible” and where it is not. Let’s say we have a state machine at a certain point in time where n orthogonal regions are active. The regions ri have a defined execution order: r1r2 → … → rn-1rn.

If an event e is raised in region rm, then e is available only in those regions that have not been processed yet, i. e. in ri where i > m and in.

The following example illustrates this. When the state machine is started, the situation is as shown in figure "Raising an event [1]": A and X in the parallel regions r1 and r2 are active states. Important: Region r1 is executed before r2.


Raising an event [1]

Raising an event [1]

Now event e1 occurs. It triggers transition A → B in r1. This transition’s effect raise e2 causes event e2 to be raised. This event triggers transition X → Y in r2 to fire within the same RTC.

After that the situation is now as can be seen in figure "Raising an event [2]": substates B and Y are now active states. However, the transition B → C will not be taken, because that would have to happen in another RTC. But by then the particular e2 instance raised by A → B will be long gone, since it was restricted to the RTC it has been raised in.


Raising an event [2]

Raising an event [2]

Orthogonal regions within a state are always executed from left to right or from top to bottom, respectively. On the top level, where orthogonal regions can be graphically arranged at will, the execution order is defined by the statechart model’s property region priority. To inspect or change it, open the Properties view and select the statechart’s definition section.

The region priorities of the example above are shown in figure "Region priorities". Change the execution order and the example will behave differently than described.


Region priorities

Region priorities

Not being able to raise an event and process it in „earlier” regions is a restriction you can overcome using variables. The idea is to set a „communication variable” to a certain value in a region that is processed „later”. During the next RTC the earlier region could act on the variables value.

Types

The language has a small integrated type system consisting of the following simple types:

  • integer
  • real
  • boolean
  • string
  • void

Statements

A statement is one of three kinds:

  • assignment
  • event raising
  • operation call

Assignments

The language has the following assignment operators:

simple assignment =
multiply and assign *=
divide and assign /=
calculate modulo and assign %=
add and assign +=
subtract and assign -=
bitshift left and assign <<=
bitshift right and assign >>=
bitwise AND and assign &=
bitwise XOR and assign ^=
bitwise OR and assign |=

Raising an event

An event is raised by using the keyword raise, followed by the event name, e. g. raise Incoming_call. If the event is an interface event, the name of the interface must also be prepended as usual.

Example:

interface intrfc:
  out event ev

Usage in the statechart:

always / raise intrfc.ev

See also sections "Declaring events" and "Raising and processing an event".

Calling an operation

An operation is called similarly to other programming languages using the operation name and passing concrete parameters in parenthesis and separated by comma. The parameters can be expressions. The parenthesis can be omitted when no parameters are needed. Please see section "Operations" for an example.

Expressions

Expressions in YAKINDU Statechart Tools are similar to expressions in other programming languages. The language provides operators for logical expressions, number arithmetic, bitwise arithmetic, and bit shifting.

The type of a logical expression is boolean.

Logical AND

expr1 && expr2

Logical OR

expr1 ||  expr2

Logical NOT

!expr1

Conditional expression

expr1 ? expr2 : expr3

When expr1 evaluates to true, this returns expr2, else exp3. This can be used to assign values to variables:

y = x > 5 ? 1 : 2

This does the same as the following snippet of C code:

if(x > 5) {
  y = 1;
} else {
  y = 2;
}

Bitwise XOR

expr1 ^ expr2

Bitwise OR

expr1 | expr2

Bitwise AND

expr1 & expr2

Logical relations and shift operators

less than <
equal or less than <=
greater than >
equal or greater than >=
equal ==
not equal !=
shift left <<
shift right >>

Binary arithmetic operators

plus +
minus -
multiply *
divide /
modulo %

Unary arithmetic operators

positive +
negative -
complement ~

Built-in functions

valueof(event)

An event can have a value. This function returns the value of the specified event.

myVar = valueof(myEvent)


as

Casts a variable. The following example casts a literal from integer to real.

myReal = 12 as real


active(state)

Returns true if the specified state is active and false otherwise.

myBool = active(StateA)

Complete statechart language grammar

This section presents the complete grammar of the statechart language. If you are in doubt how to write down a transition specification, a variable declaration, or any other syntactical construct of the statechart language, this is your definite reference that clarifies all syntactical questions.

The grammar is visualized using railroad diagrams. Railroad diagrams are easy to read and comprise all syntactically valid language constructs. Figure "Railroad diagrams explained" shows an example:

Railroad diagrams explained

Railroad diagrams explained

On the left-hand side you’ll find the so-called non-terminal symbol (or non-terminal for short) that is being defined by this particular diagram. Here the non-terminal is SimpleElementReferenceExpression. To find out what a SimpleElementReferenceExpression is allowed to look like, follow the „railroad track” just like a train would do. At each junction, the train either keeps its direction or changes it by 45 degrees, but it cannot make sharp turns of 90 degrees or more.

On its way, the train passes several „stations”. Each station represents a language element that is valid at this point. In our example, the first station is a rectangle with a grey background. The grey background denotes a non-terminal, and the word inside the rectangle is the non-terminal’s name, here: ID. You will have to look up this particular non-terminal’s railroad diagram to learn more about it. However, for simplicity and because their meanings are more or less obvious a few non-terminals are not refined further. Examples are STRING, INT, BOOL, or ID.

In a SimpleElementReferenceExpression, a valid ID – a name – is always the first element, e. g. init42. If the train turns right at the junction, it moves directly to the railroad diagram’s exit. That means just an ID like init42 is a valid SimpleElementReferenceExpression. Please note that when the train reaches the junction right before the exit, it cannot turn left, because trains don’t move that way. That is, the train is unable to travel to the ( station from this direction.

The train can reach the ) from the opposite direction only. After having left the initial ID station, the train can move to the left at the junction and then run a stretch touching only the ( and ) stations, in that order. These rectangles have a white background color, denoting a terminal symbol or terminal for short. A terminal stands for itself; write it down as it is.

Following that rule, init42() is a valid SimpleElementReferenceExpression. One or more Expressions inside the paranthesis, separated by the , terminal, are also valid. Example: init42(foo, bar + 27).

Please note:

The railroad diagrams themselves are generated from a textual grammar representation established and maintained with Xtext. If you suspect the diagrams shown here are lagging behind the actual implementation, look for files with the .xtext extension in the YAKINDU Statechart Tools source code distribution!

Please also note:

Not each and every construction that is syntactically allowed does make sense semantically. The statechart editor will flag such constructions as errors.

Statechart grammar

Figure "Statechart grammar" shows the statechart grammar. It makes use of the expressions grammar.

Statechart grammar

Statechart grammar

Expressions grammar

Figure "Expressions grammar" shows the expressions grammar. It is used by the statechart grammar.

Expressions grammar

Expressions grammar

Edit on GitHub

Generating state machine code

For configuring the code generation process, YAKINDU Statechart Tools uses a textual generator model called SGen. It can be created either by using the provided wizard YAKINDU Statechart Generator Model or by creating a new text file with the file extension .sgen.

To create a generator model with the wizard, proceed as follows:

  1. Select File → New → YAKINDU SCT → Code Generator Model.
  2. Enter a name and click Next.
  3. Choose the desired generator, i. e. YAKINDU SCT Java Code Generator.
  4. Check the model(s) to generate code from and click Finish.

Selecting code generator and models

Selecting code generator and models

The result is an .sgen file of the following format:

GeneratorModel for [GeneratorId] {
	statechart [StatechartReference] {
		feature [Feature] {
			[ParameterName] = [ParameterValue]
		}
	}
}
 

The [GeneratorId] represents the unique ID of the generator. Currently, the following generators are supported out of the box:

  • yakindu::java – Generator ID for the Java code generator
  • yakindu::c – Generator ID for the C code generator
  • yakindu::cpp – Generator ID for the C++ code generator
  • yakindu::generic – Generator ID for custom Java-based code generators

A single generator model can contain several StatechartReferences. These are cross references to statechart models for whom code is to be generated. For each reference, the generator process can be configured with Feature zs. Each feature has one or more parameters. These parameters can be configured with ParameterName = ParameterValue.

Running a generator

Provided you have created the generator model, proceed as follows:

  1. In the project explorer view, right-click on the .sgen file containing the generator model. The context menu opens.
  2. In the context menu, select Generate Code Artifacts.
  3. The generator is executed.

The source files the generator produces depend on the generator itself. The generators make use of their respective language and their features and best practices, i. e. the Java-generator generates a Java-Interface, and the C and the C++ generator generate header files. The location of these source files can be changed with the generator model options explained in the following section.

The generator model is executed by a so-called Eclipse builder. Thus, the artifacts are generated automatically if Project → Build Automatically is checked. If you want or need to execute your Generator Model manually, select Generate Statechart Artifacts from the Package Explorer's context menu.

Configuring a generator

As mentioned above, all generators can be set up and customized by a generator model. The following screenshot shows a sample configuration for the Java code generator.

SGen generator model with default values

SGen generator model with default values

The following section describes the Core Features which are available for all code generators.

Outlet feature

The Outlet feature specifies target project and target folder for the generated artifacts. It is a required feature and has the following parameters:

  1. targetProject (String, required): The project to store the generated artifacts to.
  2. targetFolder (String, required): The folder to store the generated artifacts to. If a library folder is given, only the dynamic (i. e. model-dependent artifacts) are generated into the target folder, if not all generated artifacts will be generated into it. All artifacts in this folder will be overwritten during re-generation.
  3. libraryTargetFolder (String, optional): The folder to store the static (i. e. model-independent artifacts) to. In case this is not specified, all artifacts will be generated into the target folder. All artifacts in this folder will be preserved during re-generation.
  4. apiTargetFolder (String, optional): The folder to store API code, e.g. interfaces or header files. In case this is not specified, all artifacts will be generated into the target folder.

Example:

feature Outlet {
    targetProject = "SampleProject"
    targetFolder = "src-gen"
    libraryTargetFolder = "src"
    apiTargetFolder = "api-gen"
}

LicenseHeader feature

The LicenseHeader feature specifies the license text to be added as a header to the generated artifacts. It is an optional feature and has the following parameter:

  1. licenseText (String, required): License text to be added as a file header

Example:

feature LicenseHeader {
    licenseText = "Copyright (c) 2016 committers of YAKINDU and others."
}

FunctionInlining feature

The FunctionInlining feature enables the inlining of expressions instead of generating separate functions or methods. This might reduce the readability of the generated code, but increases performance, because less operation calls are necessary. This is an optional feature and has the following parameters:

  1. inlineReactions (Boolean, optional): inlines the expression for reactions
  2. inlineEntryActions (Boolean, optional): inlines the expression for entry actions
  3. inlineExitActions (Boolean, optional): inlines the expression for exit actions
  4. inlineEnterSequences (Boolean, optional): inlines the expression for enter sequences
  5. inlineExitSequences (Boolean, optional): inlines the expression for exit sequences
  6. inlineChoices (Boolean, optional): inlines the expression for choices
  7. inlineEnterRegion (Boolean, optional): inlines the expression for enter regions
  8. inlineExitRegion (Boolean, optional): inlines the expression for exit regions
  9. inlineEntries (Boolean, optional): inlines the expression for entries

ALl parameters of the FunctionInlining feature are false by default.

Example:

feature FunctionInlining {
    inlineChoices = false
    inlineEnterRegion = true
    inlineEntries = true
}

Debug feature

The Debug feature dumps the execution model to the target folder as an XMI model. It is an optional feature and has the following parameter:

  1. dumpSexec (Boolean, required): dumps the execution model as XMI model (default: false)

Example:

feature Debug {
    dumpSexec = true
}

General concepts of the state machine code

Cycle-based execution

The generated state machine code implements a so-called cycle-based execution scheme. Each run cycle consists of two different phases:

  1. In the first phase, incoming events are collected.
  2. In the second phase, runCycle() is executed.

This approach allows for explicitly processing several events at the same time as well as for checking combinations of events, e. g. something like [eventA && eventB]. This is very useful for systems that are close to hardware and input signals. Basically it is the IPO (input processing output) model.

In other cases an event-driven approach might be more suitable. This can be implemented e. g. by directly calling runCycle() as soon as a certain event occurs, like in:

sc_raiseEventA(handle);
sc_runCycle(handle);

More sophisticated approaches could care about buffering of events, managing an event queue, dealing with priority events, and so on. However, since this is out of the the generated code’s scope, it is the client code’s responsibility.

Responsibilities of generated code

The generated source code supports a basic statechart execution model that can be used to implement different variants. It is important to understand the responsibilities of the generated code, i. e. what it cares about and what it doesn’t. The remaining issues are out of scope and must be dealt with in the client code the developer has to provide.

The generated code basically takes care about the following:

  • It provides the interfaces to access state machine variables and events.
  • It initializes the state machine.
  • It implements the execution semantics within a run-to-completion (RTC) step. This is everything that is happening within the runCycle() function.

There are some predefined constraints:

  • The implementation is not thread-safe. Therefore the runCycle() method must never be called in parallel by different threads.
  • All events for a run-to-completion step will be consumed.

Out of scope are:

  • Scheduling: The code generator cannot make any general assumptions about scheduling mechanisms and constraints.
  • Timers: The code generator does not know how the system handles times. If any specific time-dependend behavior is required, the client code has to contribute it.
  • Event handling: The code generator cannot know and does not prescribe how events are entering the system. Therefore deciding when events will be delivered to the state machine is an outside (client code) responsibility.
  • Concurrency: The client code has to care about any thread synchronization.
  • Memory management: The generated state machine code will never allocate any memory. Allocating the memory needed for the statechart structure is a responsibility of the client code.

All these things are out of the generated code’s scope, since the code generators coming with YAKINDU Statechart Tools cannot provide tailored solutions without knowing any details about a respective specific environment, like runtime system, libraries, etc.

In order to have a better code generator support for specific usage patterns, you would have to implement corresponding code generator extensions.

Thread-safe execution

In order to circumvent the missing thread-safety of the runCycle() method, the Java code generator has an option for generating a so-called runnable wrapper, providing event queueing and multi-threading support. For details, see section "RunnableWrapper". It is easy to implement this as a general solution for Java, since the programming language provides corresponding standard features that by definition are available everywhere by can simply be used.

C, however, does not have any standardized threading features. Therefore a general solution cannot be given. Specific solutions for specific threading libraries would require the implementation of a suitable code generator extension.

C code generator

C generator features

IdentifierSettings feature

The IdentifierSettings feature allows the configuration of module names and identifier character length:

  1. moduleName (String, optional): name for header and implementation, default: statechart name
  2. statemachinePrefix (Boolean, optional): prefix which is prepended to function, state, and type names.
  3. maxIdentifierLength (Integer, optional): maximum number of characters of an identifier, default: 31 characters, which is complying with the ANSI C99 standard.
  4. separator (String, optional): character to replace whitespace and otherwise illegal characters in manes.

Example:

feature IdentifierSettings {
    moduleName =  "MyStatechart"
    statemachinePrefix =  "myStatechart"
    maxIdentifierLength = 31
    separator =  "_"
}

Tracing feature

The Tracing feature enables the generation of tracing callback functions:

  • enterState (boolean, optional): specifies whether to generate a callback function that is used to notify about state entering events
  • exitState (boolean, optional): specifies whether to generate a callback that is used to notify about state exiting events.

Example:

feature Tracing {
    enterState = true
    exitState  = true
}

Specification of C code

The explanations below are using the Traffic Light (C) sample state machine to describe the API specifications of the code generated by the YAKINDU C code generator. The image below is showing the statechart. It models a pedestrian crossing with push-button operated traffic lights („pelican crossing”).

The traffic light model

The traffic light model

You can find the C sample project Traffic Light (C) in the YAKINDU Statechart Tools examples repository. The C example contains statechart, SGen model, graphical widgets, and some glue code to connect the generated code with the widgets. The graphical widgets are based on Qt.

To execute the C example, run the file org_yakindu_sct_examples_c_trafficlight_ as Local C/C++ application from the Eclipse Run As context menu.

Generated code files

You will find the generated code in the src-gen folder of the traffic light example.

The C code generator generates three header files. The first one is sc_types.h:


#ifndef SC_TYPES_H_
#define SC_TYPES_H_

#ifdef __cplusplus
extern "C"
{
#endif 

#include <stdint.h>
#include <stdbool.h>

#define sc_string		char*
 
typedef bool			sc_boolean;
typedef int_fast16_t	sc_short;
typedef uint_fast16_t	sc_ushort;
typedef int32_t			sc_integer;
typedef uint32_t		sc_uinteger;

typedef double			sc_real;

typedef void*			sc_eventid;

typedef intptr_t		sc_intptr_t;

#ifdef __cplusplus
}
#endif

#ifndef null
	#ifdef __cplusplus
		#define null 0
	#else
		#define null ((void *)0)
	#endif
#endif

#define bool_true true
#define bool_false false

#endif /* SC_TYPES_H_ */

The header file contains some basic definitions for C++ compiler compatibility and typedefs to map the YAKINDU statechart types to C types. The next header file is named after the statechart. In case of the traffic light example it is called TrafficLight.h:


#ifndef TRAFFICLIGHT_H_
#define TRAFFICLIGHT_H_

#include "../src/sc_types.h"
		
#ifdef __cplusplus
extern "C" { 
#endif 

/*! \file Header of the state machine 'TrafficLight'.
*/

/*! Enumeration of all states */ 
typedef enum
{
	TrafficLight_main_region_on,
	TrafficLight_main_region_on_r1_StreetGreen,
	TrafficLight_main_region_on_r1_PedWaiting,
	TrafficLight_main_region_on_r1_PedWaiting_r1_waitOn,
	TrafficLight_main_region_on_r1_PedWaiting_r1_waitOff,
	TrafficLight_main_region_on_r1_StreetAttention,
	TrafficLight_main_region_on_r1_StreetRed,
	TrafficLight_main_region_on_r1_PedestrianGreen,
	TrafficLight_main_region_on_r1_PedestrianRed,
	TrafficLight_main_region_on_r1_StreetPrepare,
	TrafficLight_main_region_on_r1_Safe,
	TrafficLight_main_region_off,
	TrafficLight_main_region_off_r1_YellowOn,
	TrafficLight_main_region_off_r1_YellowOff,
	TrafficLight_last_state
} TrafficLightStates;

/*! Type definition of the data structure for the TrafficLightIfaceTrafficLight interface scope. */
typedef struct
{
	sc_boolean red;
	sc_boolean yellow;
	sc_boolean green;
} TrafficLightIfaceTrafficLight;

/*! Type definition of the data structure for the TrafficLightIfacePedestrian interface scope. */
typedef struct
{
	sc_boolean request;
	sc_boolean red;
	sc_boolean green;
} TrafficLightIfacePedestrian;

/*! Type definition of the data structure for the TrafficLightIface interface scope. */
typedef struct
{
	sc_boolean pedestrianRequest_raised;
	sc_boolean onOff_raised;
} TrafficLightIface;

/*! Type definition of the data structure for the TrafficLightTimeEvents interface scope. */
typedef struct
{
	sc_boolean trafficLight_main_region_on_r1_PedWaiting_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_PedWaiting_r1_waitOn_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_PedWaiting_r1_waitOff_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_StreetAttention_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_StreetRed_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_PedestrianGreen_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_PedestrianRed_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_StreetPrepare_tev0_raised;
	sc_boolean trafficLight_main_region_on_r1_Safe_tev0_raised;
	sc_boolean trafficLight_main_region_off_r1_YellowOn_tev0_raised;
	sc_boolean trafficLight_main_region_off_r1_YellowOff_tev0_raised;
	sc_boolean trafficLight_tev0_raised;
} TrafficLightTimeEvents;


/*! Define dimension of the state configuration vector for orthogonal states. */
#define TRAFFICLIGHT_MAX_ORTHOGONAL_STATES 1

/*! 
 * Type definition of the data structure for the TrafficLight state machine.
 * This data structure has to be allocated by the client code. 
 */
typedef struct
{
	TrafficLightStates stateConfVector[TRAFFICLIGHT_MAX_ORTHOGONAL_STATES];
	sc_ushort stateConfVectorPosition; 
	
	TrafficLightIfaceTrafficLight ifaceTrafficLight;
	TrafficLightIfacePedestrian ifacePedestrian;
	TrafficLightIface iface;
	TrafficLightTimeEvents timeEvents;
} TrafficLight;

/*! Initializes the TrafficLight state machine data structures. Must be called before first usage.*/
extern void trafficLight_init(TrafficLight* handle);

/*! Activates the state machine */
extern void trafficLight_enter(TrafficLight* handle);

/*! Deactivates the state machine */
extern void trafficLight_exit(TrafficLight* handle);

/*! Performs a 'run to completion' step. */
extern void trafficLight_runCycle(TrafficLight* handle);

/*! Raises a time event. */
extern void trafficLight_raiseTimeEvent(const TrafficLight* handle, sc_eventid evid);

/*! Gets the value of the variable 'red' that is defined in the interface scope 'TrafficLight'. */ 
extern sc_boolean trafficLightIfaceTrafficLight_get_red(const TrafficLight* handle);
/*! Sets the value of the variable 'red' that is defined in the interface scope 'TrafficLight'. */ 
extern void trafficLightIfaceTrafficLight_set_red(TrafficLight* handle, sc_boolean value);
/*! Gets the value of the variable 'yellow' that is defined in the interface scope 'TrafficLight'. */ 
extern sc_boolean trafficLightIfaceTrafficLight_get_yellow(const TrafficLight* handle);
/*! Sets the value of the variable 'yellow' that is defined in the interface scope 'TrafficLight'. */ 
extern void trafficLightIfaceTrafficLight_set_yellow(TrafficLight* handle, sc_boolean value);
/*! Gets the value of the variable 'green' that is defined in the interface scope 'TrafficLight'. */ 
extern sc_boolean trafficLightIfaceTrafficLight_get_green(const TrafficLight* handle);
/*! Sets the value of the variable 'green' that is defined in the interface scope 'TrafficLight'. */ 
extern void trafficLightIfaceTrafficLight_set_green(TrafficLight* handle, sc_boolean value);
/*! Gets the value of the variable 'request' that is defined in the interface scope 'Pedestrian'. */ 
extern sc_boolean trafficLightIfacePedestrian_get_request(const TrafficLight* handle);
/*! Sets the value of the variable 'request' that is defined in the interface scope 'Pedestrian'. */ 
extern void trafficLightIfacePedestrian_set_request(TrafficLight* handle, sc_boolean value);
/*! Gets the value of the variable 'red' that is defined in the interface scope 'Pedestrian'. */ 
extern sc_boolean trafficLightIfacePedestrian_get_red(const TrafficLight* handle);
/*! Sets the value of the variable 'red' that is defined in the interface scope 'Pedestrian'. */ 
extern void trafficLightIfacePedestrian_set_red(TrafficLight* handle, sc_boolean value);
/*! Gets the value of the variable 'green' that is defined in the interface scope 'Pedestrian'. */ 
extern sc_boolean trafficLightIfacePedestrian_get_green(const TrafficLight* handle);
/*! Sets the value of the variable 'green' that is defined in the interface scope 'Pedestrian'. */ 
extern void trafficLightIfacePedestrian_set_green(TrafficLight* handle, sc_boolean value);
/*! Raises the in event 'pedestrianRequest' that is defined in the default interface scope. */ 
extern void trafficLightIface_raise_pedestrianRequest(TrafficLight* handle);

/*! Raises the in event 'onOff' that is defined in the default interface scope. */ 
extern void trafficLightIface_raise_onOff(TrafficLight* handle);


/*!
 * Checks whether the state machine is active (until 2.4.1 this method was used for states).
 * A state machine is active if it was entered. It is inactive if it has not been entered at all or if it has been exited.
 */
extern sc_boolean trafficLight_isActive(const TrafficLight* handle);

/*!
 * Checks if all active states are final. 
 * If there are no active states then the state machine is considered being inactive. In this case this method returns false.
 */
extern sc_boolean trafficLight_isFinal(const TrafficLight* handle);

/*! Checks if the specified state is active (until 2.4.1 the used method for states was called isActive()). */
extern sc_boolean trafficLight_isStateActive(const TrafficLight* handle, TrafficLightStates state);

#ifdef __cplusplus
}
#endif 

#endif /* TRAFFICLIGHT_H_ */

Within this header file an enum containing the state names is defined as well as data structures for each of the statechart’s interfaces. Additionally a structure for the statechart’s time events is defined. The interfaces' and time events' data structures are nested into the parent structure TrafficLight. The client has to allocate this structure. It is a common parameter of most functions the statechart defines. Below this structure is called the statechart data structure.

Fundamental statechart functions

The generated code contains fundamental functions to initialize, enter, and exit a state machine, as well as a function to start a run-to-completion step.

In the header file the function names are made up of the state machine name followed by the name of the respective functionality. For example, the functions of the traffic light example are generated as follows:

/*! Initializes the TrafficLight state machine data structures. Must be called before first usage.*/
extern void trafficLight_init(TrafficLight* handle);

/*! Activates the state machine */
extern void trafficLight_enter(TrafficLight* handle);

/*! Deactivates the state machine */
extern void trafficLight_exit(TrafficLight* handle);

/*! Performs a 'run to completion' step. */
extern void trafficLight_runCycle(TrafficLight* handle);

  • The init() function is used to initialize the internal objects of the state machine right after its instantiation. Variables are initialized to their respective default values. If the statechart defines initialized variables, these initializations are also done in the init() function.

  • The enter() function must be called to enter the state machine. It brings the state machine to a well-defined state.

  • The exit() function is used to leave a state machine statefully. If for example a history state is used in one of the top regions, the last active state is stored and the state machine is left via exit(). Re-entering it via enter() continues to work with the saved state.

  • The runCycle() function is used to trigger a run-to-completion step in which the state machine evaluates arising events and computes possible state changes. Somewhat simplified, a run-to-completion cycle consists of the following steps:

    1. Clear list of outgoing events.

    2. Check whether events have occurred which are leading to a state change.

    3. If a state change has to be done:

      1. Make the present state inactive.

      2. Execute exit actions of the present state.

      3. Save history state, if necessary.

      4. Execute transition actions, if any.

      5. Execute entry actions of the new state.

      6. Make the new state active.

    4. Clear list of incoming events.

Accessing variables and events

The getters and setters for each variable and event are also contained in the header file. The function names are matching the following pattern:

statechart_name Iface interface_name _ [ set | get | raise ] _ [ variable_name|_event_name_]

For example, the getter of the red variable of the Pedestrian interface is named trafficLightIfacePedestrian_get_red(TrafficLight* handle)

Time-controlled state machines

If a statechart uses timing functionality or external operations or tracing is activated in the generator model, an additional header file is generated. Its name matches the following pattern:

statechart_name Required.h

This header file declares functions the client code has to implement externally.

Example

The traffic light example uses timing functionality, namely after clauses. To support time-controlled behavior, the additional header file TrafficLightRequired.h is generated. Note the two functions trafficLight_setTimer and trafficLight_unsetTimer.


#ifndef TRAFFICLIGHTREQUIRED_H_
#define TRAFFICLIGHTREQUIRED_H_

#include "../src/sc_types.h"
#include "TrafficLight.h"

#ifdef __cplusplus
extern "C"
{
#endif 

/*! \file This header defines prototypes for all functions that are required by the state machine implementation.

This is a state machine uses time events which require access to a timing service. Thus the function prototypes:
	- trafficLight_setTimer and
	- trafficLight_unsetTimer
are defined.

This state machine makes use of operations declared in the state machines interface or internal scopes. Thus the function prototypes:
	- trafficLightIface_synchronize
are defined.

These functions will be called during a 'run to completion step' (runCycle) of the statechart. 
There are some constraints that have to be considered for the implementation of these functions:
	- never call the statechart API functions from within these functions.
	- make sure that the execution time is as short as possible.
 
*/


extern void trafficLightIface_synchronize(const TrafficLight* handle);



/*!
 * This is a timed state machine that requires timer services
 */ 

/*! This function has to set up timers for the time events that are required by the state machine. */
/*! 
	This function will be called for each time event that is relevant for a state when a state will be entered.
	\param evid An unique identifier of the event.
	\time_ms The time in milli seconds
	\periodic Indicates the the time event must be raised periodically until the timer is unset 
*/
extern void trafficLight_setTimer(TrafficLight* handle, const sc_eventid evid, const sc_integer time_ms, const sc_boolean periodic);

/*! This function has to unset timers for the time events that are required by the state machine. */
/*! 
	This function will be called for each time event taht is relevant for a state when a state will be left.
	\param evid An unique identifier of the event.
*/
extern void trafficLight_unsetTimer(TrafficLight* handle, const sc_eventid evid);



#ifdef __cplusplus
}
#endif 

#endif /* TRAFFICLIGHTREQUIRED_H_ */

Basically the proper time handling has to be implemented by the developer, because timer functions generally depend on the hardware target used. So for each hardware target the client code must provide a function to set a timer and another function to unset it. These functions have to be implemented externally and linked to the generated code.

The following functions are dealing with timing functionality:

Function setTimer

A state machine calls the setTimer() function – short for the function’s full name like e. g. void trafficLight_setTimer(TrafficLight* handle, const sc_eventid evid, const sc_integer time_ms, const sc_boolean periodic) – to tell the timer service that it has to start a timer for the given time event identifier and raise it after the period of time specified by the time_ms parameter has expired. It is important to only start a timer thread or a hardware timer interrupt within the setTimer() function and avoid any time-consuming operations like extensive computations, sleeping or waiting. Never call the statechart API functions from within these functions! Otherwise the state machine execution might hang within the timer service or might not show the expected runtime behavior.

In order to have the timer service raise the time event periodically, the parameter periodic must be true.

Function unsetTimer

The state machine calls the function trafficLight_unsetTimer(TrafficLight* handle, const sc_eventid evid) to notify the timer service to unset the timer for the given event ID.

Function raiseTimeEvent

In order to notify the state machine about the occurence of a time event after a period of time has expired, the raiseTimeEvent() function – defined in the header file of the state machine – needs to be called on the state machine. In the case of the traffic light example it is named trafficLight_raiseTimeEvent(const TrafficLight* handle, sc_eventid evid) (in file TrafficLight.h).

The time event is recognized by the state machine and will be processed during the next run cycle.

You can conclude that in order to process the time events raised by the timing service without too much latency, the runtime environment has to call the state machine’s runCycle() function as frequently as needed. Consider for example a time event which is raised by the timer service after 500 ms. However, if the runtime environment calls the state machine’s runCycle() function only once per 1000 ms, the event will quite likely not be processed at the correct points in time.

Operation callbacks

YAKINDU Statechart Tools support client code operations that can be used by a state machine and are executed as actions. These operations have to be implemented in order to make a statechart executable. The figure below shows a sample statechart using an operation:

Specifying an operation callback in the model

Specifying an operation callback in the model

Let’s have a look at the generated code in DefaultSMRequired.h:


#ifndef DEFAULTSMREQUIRED_H_
#define DEFAULTSMREQUIRED_H_

#include "sc_types.h"
#include "DefaultSM.h"

#ifdef __cplusplus
extern "C"
{
#endif 

/*! \file This header defines prototypes for all functions that are required by the state machine implementation.

This state machine makes use of operations declared in the state machines interface or internal scopes. Thus the function prototypes:
	- defaultSMIfaceSample_myOperation
are defined.

These functions will be called during a 'run to completion step' (runCycle) of the statechart. 
There are some constraints that have to be considered for the implementation of these functions:
	- never call the statechart API functions from within these functions.
	- make sure that the execution time is as short as possible.
 
*/
extern sc_integer defaultSMIfaceSample_myOperation(DefaultSM* handle, const sc_integer p1, const sc_boolean p2);




#ifdef __cplusplus
}
#endif 

#endif /* DEFAULTSMREQUIRED_H_ */

An additional external function declaration sc_integer defaultSMIfaceSample_myOperation(DefaultSM* handle, const sc_integer p1, const sc_boolean p2) has been generated. This function has to be implemented and linked with the generated code, so that the state machine can use it.

Integrating generated code

To get a clue how to integrate a generated C state machine with your project have a look at this main.c file and its main() function. Please note that the trafficLight_setTimer and trafficLight_unsetTimer functions are implemented here as well:

#include "org_yakindu_sct_examples_c_trafficlight.h"

#include "src-gen/sc_types.h"
#include "src-gen/TrafficLight.h"
#include "statemachine/TrafficLightTimer.h"
#include "statemachine/TrafficLightRunner.h"

TrafficLightTimer *timer;

int main(int argc, char *argv[]) {
    TrafficLight handle;
    trafficLight_init(&handle);
    timer = new TrafficLightTimer(&handle);
    trafficLight_enter(&handle);
    QApplication a(argc, argv);
    TrafficLightRunner *runner = new TrafficLightRunner(&handle, 100);
    org_yakindu_sct_examples_c_trafficlight w(0, runner);
    w.show();
    int ret = a.exec();
    return ret;
}

void trafficLight_setTimer(const sc_eventid evid,
        const sc_integer time_ms, const sc_boolean periodic) {
    timer->setTimer(evid, time_ms, periodic);
}

void trafficLight_unsetTimer(const sc_eventid evid) {
    timer->unsetTimer(evid);
}

First an instance of the statechart data structure is created and initialized by the trafficLight_init(&handle) function. The next step instantiates the timer. The class TrafficLightTimer represents an implementation of a timer service and uses the timer functionality of the Qt framework. The TrafficLightRunner is a runtime service which executes a run-to-completion step of the state machine every 100 ms. The runner class and the GUI are wired in the class org_yakindu_sct_examples_c_trafficlight:

#include "org_yakindu_sct_examples_c_trafficlight.h"

org_yakindu_sct_examples_c_trafficlight::org_yakindu_sct_examples_c_trafficlight(
        QWidget *parent, TrafficLightRunner *runner) :
        QMainWindow(parent) {

    ui.setupUi(this);
    crossing = new CrossingWidget(this);

    trafficLight = new TrafficLightWidget(crossing);
    trafficLight->setGeometry(275, 75, 30, 90);

    pedestrianLight = new PedestrianLightWidget(crossing);
    pedestrianLight->setGeometry(50, 10, 70, 20);

    connect(runner, SIGNAL(cycleDone(TrafficLight*)), this, SLOT(update(TrafficLight*)));

    pedestrianReq = new QPushButton("pedestrian request", this);
    pedestrianReq->setGeometry(1, 365, 150, 30);
    connect(pedestrianReq, SIGNAL(released()), runner, SLOT(raisePedestrianRequest()));


    off = new QPushButton("off / on", this);
    off->setGeometry(249, 365, 150, 30);
    connect(off, SIGNAL(released()), runner, SLOT(raiseOnOff()));
}

void org_yakindu_sct_examples_c_trafficlight::update(
        TrafficLight *handle) {
    trafficLight->setSignals(handle-&gt;ifaceTrafficLight.red,
            handle->ifaceTrafficLight.yellow, handle->ifaceTrafficLight.green);
    pedestrianLight->setSignals(handle->ifacePedestrian.request,
            handle->ifacePedestrian.red, handle->ifacePedestrian.green);
    QMainWindow::update();
}

org_yakindu_sct_examples_c_trafficlight::~org_yakindu_sct_examples_c_trafficlight() {

}




C++ code generator

C++ generator features

The C++ generator features are the same a the C generator features, plus one:

GeneratorOptions feature

The GeneratorOptions feature allows to change the behavior of the C++ generator:

  1. innerFunctionVisibility (String, optional): This parameter is used to change the visibility of inner functions and variables. By default private visibility is used. It can be changed to protected to allow function overriding for a class which inherits from the generated state machine base class.
  2. staticOperationCallback (Boolean, optional): If this parameter is set to true, the callback function declaration for statechart operations is static and the functions are called statically by the state machine code.

Example:

feature GeneratorOptions {
    innerFunctionVisibility =  "protected"
    staticOperationCallback = true
}

Specification of C++ code

The explanations below are using the Traffic Light (C++) sample state machine to describe the API specifications of the code generated by the YAKINDU C++ generator. The image below is showing the statechart. It models a pedestrian crossing with push-button operated traffic lights („pelican crossing”).

The traffic light model

The traffic light model

You can find the C++ sample project Traffic Light (C++) in the YAKINDU Statechart Tools examples repository. The C++ example contains statechart, SGen model, graphical widgets, and some glue code to connect the generated code with the widgets. The graphical widgets are based on Qt.

Generated code files

You will find the generated code in the src-gen folder of the traffic light example.

The StatemachineInterface.h header file defines the fundamental state machine interface methods. This file also contains the definition of the abstract class StatemachineInterface which contains pure virtual functions only. It is needed by each particular state machine and is independend from concrete ones.

Statemachine class

The state machine source code is generated as a C++ class with the same name as the statechart. For example, if the statechart is named DefaultSM the C++ class will also be called DefaultSM and will be generated as the source code file DefaultSM.cpp.

Abstract class StatemachineInterface

Each generated state machine implements the interface StatemachineInterface:


#ifndef STATEMACHINEINTERFACE_H_
#define STATEMACHINEINTERFACE_H_

/*
 * Basic interface for state machines.
 */
class StatemachineInterface
{
	public:
	
		virtual ~StatemachineInterface() = 0;
		
		/*
		* Initializes the state machine. Used to initialize internal variables etc.
		*/
		virtual void init() = 0;
	
		/*
		* Enters the state machine. Sets the state machine into a defined state.
		*/
		virtual void enter() = 0;
	
		/*
		* Exits the state machine. Leaves the state machine with a defined state.
		*/
		virtual void exit() = 0;
	
		/*
		* Start a run-to-completion cycle.
		*/
		virtual void runCycle() = 0;
		
		/*
		* Checks whether the state machine is active. 
	 	* A state machine is active if it has been entered. It is inactive if it has not been entered at all or if it has been exited.
	 	*/	
		virtual	sc_boolean isActive() = 0;
		
		/*
		* Checks if all active states are final. 
	 	* If there are no active states then the state machine is considered being inactive. In this case this method returns false.
	 	*/
		virtual sc_boolean isFinal() = 0;
};

inline StatemachineInterface::~StatemachineInterface() {}

#endif /* STATEMACHINEINTERFACE_H_ */

Fundamental statechart methods

The generated code contains fundamental methods to initialize, enter, and exit a state machine, as well as a method to start a run-to-completion step.

The StatemachineInterface interface specifies the four functions init(), enter(), exit() and runCycle().

  • The init() function is used to initialize the internal objects of the state machine right after its instantiation. Variables are initialized to their respective default values. If the statechart defines initialized variables, these initializations are also done in the init() function.

  • The enter() function must be called to enter the state machine. It brings the state machine to a well-defined state.

  • The exit() function is used to leave a state machine statefully. If for example a history state is used in one of the top regions, the last active state is stored and the state machine is left via exit(). Re-entering it via enter() continues to work with the saved state.

  • The runCycle() function is used to trigger a run-to-completion step in which the state machine evaluates arising events and computes possible state changes. Somewhat simplified, a run-to-completion cycle consists of the following steps:

    1. Clear list of outgoing events.

    2. Check whether events have occurred which are leading to a state change.

    3. If a state change has to be done:

      1. Make the present state inactive.

      2. Execute exit actions of the present state.

      3. Save history state, if necessary.

      4. Execute transition actions, if any.

      5. Execute entry actions of the new state.

      6. Make the new state active.

    4. Clear list of incoming events.

Time-controlled state machines

If a statechart uses timing functionality, additional classes are generated.

The traffic light example uses timing funtionality, namely after clauses. To support time-controlled behavior, the abstract classes TimedStatemachineInterface and TimerInterface are generated.

The TimedStatemachineInterface interface extends the generated state machine by a TimerInterface data member. The client code must provide an implementation of that interface.

TimedStatemachineInterface also specifies the callback function raiseTimeEvent(sc_eventid event), enabling the timer service to raise time events.


#ifndef TIMEDSTATEMACHINEINTERFACE_H_
#define TIMEDSTATEMACHINEINTERFACE_H_

#include "sc_types.h"
#include "TimerInterface.h"

/*
* Interface for state machines which use timed event triggers.
*/
class TimedStatemachineInterface {
	public:
	
		virtual ~TimedStatemachineInterface() = 0;
		
		/*
		* Set the ITimerService for the state machine. It must be set
		* externally on a timed state machine before a run cycle can be correct
		* executed.
		*/
		virtual void setTimer(TimerInterface* timer) = 0;
		
		/*
		* Returns the currently used timer service.
		*/
		virtual TimerInterface* getTimer() = 0;
		
		/*
		* Callback method if a time event occurred.
		*/
		virtual void raiseTimeEvent(sc_eventid event) = 0;
};

inline TimedStatemachineInterface::~TimedStatemachineInterface() {}

#endif /* TIMEDSTATEMACHINEINTERFACE_H_ */

Basically the proper time handling has to be implemented by the developer, because timer functions generally depend on the hardware target used. So for each hardware target a timer service class implementing the TimerInterface interface has to be developed.

Let’s have a look at the TimerInterface interface:


#ifndef TIMERINTERFACE_H_
#define TIMERINTERFACE_H_

#include "sc_types.h"

//forward declaration of TimedStatemachineInterface to avoid cyclic dependency
class TimedStatemachineInterface;

/*
 * Basic interface for state machines.
 */
class TimerInterface
{
	public:
		
		virtual ~TimerInterface() = 0;
	
		/*
		 * Starts the timing for a time event.
		 */ 
		virtual void setTimer(TimedStatemachineInterface* statemachine, sc_eventid event, sc_integer time, sc_boolean isPeriodic) = 0;
		
		/*
		 * Unsets the given time event.
		 */
		virtual void unsetTimer(TimedStatemachineInterface* statemachine, sc_eventid event) = 0;
	
		/*
		 * Cancel timer service. Use this to end possible timing threads and free
		 * memory resources.
		 */
		virtual void cancel() = 0;
};

inline TimerInterface::~TimerInterface() {}

#endif /* TIMERINTERFACE_H_ */

The TimerInterface interface defines the following functions dealing with timing functionality:

Function setTimer

A state machine calls the setTimer(TimedStatemachineInterface* statemachine, sc_eventid event, sc_integer time, sc_boolean isPeriodic) function to tell the timer service that it has to start a timer for the given time event and raise it after the period of time specified by the time parameter has expired. It is important to only start a timer thread or a hardware timer interrupt within the setTimer() function and to avoid any time-consuming operations like extensive computations, Thread.sleep(…) or waiting. Otherwise the state machine execution might hang within the timer service or might not show the expected runtime behavior.

In order to have the timer service raise the time event periodically, the parameter isPeriodic must be true.

Function unsetTimer

The state machine calls the function unsetTimer(TimedStatemachineInterface* statemachine, sc_eventid event) to notify the timer service to unset the timer for the given event ID.

Function raiseTimeEvent

In order to notify the state machine about the occurence of a time event after a period of time has expired, the function raiseTimeEvent(sc_eventid event) must be called on the state machine. For this purpose, the state machine must implement the TimedStatemachineInterface) interface.

The time event is recognized by the state machine and will be processed during the next run cycle.

You can conclude that in order to process the time events raised by the timing service without too much latency, the runtime environment has to call the state machine’s runCycle() function as frequently as needed. Consider for example a time event which is raised by the timer service after 500 ms. However, if the runtime environment calls the state machine’s runCycle() function with a frequency of once per 1000 ms only, the event will quite likely not be processed at the correct points in time.

Accessing interfaces, variables and events

In a YAKINDU statechart, variables and events are contained in so-called interfaces. There can be at most one default, unnamed interface plus zero or more named interfaces. In the generated C++ code, these interfaces can be found as internal subclasses of the main state machine class. This outer class' name is derived from the statechart’s name while the internal subclasses' names are derived from the respective names of the statechart interfaces.

Let’s have a look at the following sample statechart interface declaration of a statechart named DefaultSM:

interface Sample:
    var a:boolean
    in event evA:boolean
    out event evB:integer

The generated interface code is shown below in the inner class SCI_Sample. Since the statechart’s name is DefaultSM the state machine class' name is also DefaultSM, to be found in the DefaultSM.h file:


#ifndef DEFAULTSM_H_
#define DEFAULTSM_H_

#include "sc_types.h"
#include "StatemachineInterface.h"

/*! \file Header of the state machine 'DefaultSM'.
*/

class DefaultSM : public StatemachineInterface
{
	
	public:
		
		DefaultSM();
		
		~DefaultSM();
		
		/*! Enumeration of all states */ 
		typedef enum
		{
			main_region_MyState,
			DefaultSM_last_state
		} DefaultSMStates;
		
		//! Inner class for Sample interface scope.
		class SCI_Sample
		{
			
			public:
				/*! Gets the value of the variable 'a' that is defined in the interface scope 'Sample'. */ 
				sc_boolean get_a();
				
				/*! Sets the value of the variable 'a' that is defined in the interface scope 'Sample'. */ 
				void set_a(sc_boolean value);
				
				/*! Raises the in event 'evA' that is defined in the interface scope 'Sample'. */ 
				void raise_evA(sc_boolean value);
				
				/*! Checks if the out event 'evB' that is defined in the interface scope 'Sample' has been raised. */ 
				sc_boolean isRaised_evB();
				
				/*! Gets the value of the out event 'evB' that is defined in the interface scope 'Sample'. */ 
				sc_integer get_evB_value();
				
				
			private:
				friend class DefaultSM;
				sc_boolean a;
				sc_boolean evA_raised;
				sc_boolean evA_value;
				sc_boolean evB_raised;
				sc_integer evB_value;
		};
				
		
		/*! Returns an instance of the interface class 'SCI_Sample'. */
		SCI_Sample* getSCI_Sample();
		
		
		void init();
		
		void enter();
		
		void exit();
		
		void runCycle();
		
		/*!
		* Checks if the state machine is active (until 2.4.1 this method was used for states).
		* A state machine is active if it has been entered. It is inactive if it has not been entered at all or if it has been exited.
		*/
		sc_boolean isActive();
		
		
		/*!
		* Checks if all active states are final. 
		* If there are no active states then the state machine is considered being inactive. In this case this method returns false.
		*/
		sc_boolean isFinal();
		
		
		/*! Checks if the specified state is active (until 2.4.1 the used method for states was calles isActive()). */
		sc_boolean isStateActive(DefaultSMStates state);
	
	private:
	
	
		//! the maximum number of orthogonal states defines the dimension of the state configuration vector.
		static const sc_integer maxOrthogonalStates = 1;
		
		
		DefaultSMStates stateConfVector[maxOrthogonalStates];
		
		sc_ushort stateConfVectorPosition;
		
		SCI_Sample ifaceSample;
		
		// prototypes of all internal functions
		
		void enseq_main_region_MyState_default();
		void enseq_main_region_default();
		void exseq_main_region_MyState();
		void exseq_main_region();
		void react_main_region_MyState();
		void react_main_region__entry_Default();
		void clearInEvents();
		void clearOutEvents();
		
};
#endif /* DEFAULTSM_H_ */

A statechart interface is generated as an internal subclass within the state machine class. The subclass' name is derived from the statechart interface’s name by prepending the string SCI_.

A special case is the unnamed statechart interface: It is generated as the C++ subclass SCInterface.

An incoming event evA:boolean is generated as the raise function raise_evA(boolean value). Since the event is of type boolean the function has a boolean parameter.

For an outgoing event evB:integer the functions isRaised_evB() and get_evB_value() are generated. The former can be used to determine whether the event has already been raised by the state machine or not. The latter serves to query the value of the event.

For variables, the code generator creates getter and setter functions, here sc_boolean get_a() and void set_a(sc_boolean value).

The code generator also creates appropriately named getter functions in the enclosing class which can be used to acquire the inner classes, here: SCI_Sample* getSCI_Sample().

The nesting class is the generated state machine source code. It holds instances of the nested interface class implementations and provides them via getter functions. Have a look at (some snippets of) the source code generated for the Sample statechart interface in the source file DefaultSM.cpp:

void DefaultSM::SCI_Sample::raise_evA(sc_boolean value)
{
	evA_value = value;
	evA_raised = true;
}

sc_boolean DefaultSM::SCI_Sample::isRaised_evB()
{
	return evB_raised;
}

sc_integer DefaultSM::SCI_Sample::get_evB_value()
{
	return evB_value;
}


sc_boolean DefaultSM::SCI_Sample::get_a()
{
	return a;
}

void DefaultSM::SCI_Sample::set_a(sc_boolean value)
{
	a = value;
}

The value of an event can be accessed only if the event has been processed in a run-to-completion step. Otherwise the event could contain an illegal value.

Operation callbacks

YAKINDU Statechart Tools support client code operations that can be used by a state machine and are executed as as actions. These operations have to be implemented in order to make a statechart executable. The figure below shows a sample statechart using an operation:

Specifying an operation callback in the model

Specifying an operation callback in the model

Let’s have a look at the additionally generated code for operation support in the DefaultSM.h header file:

				//! Inner class for Sample interface scope operation callbacks.
				class SCI_Sample_OCB
				{
					public:
						virtual ~SCI_Sample_OCB() = 0;
						
						virtual sc_integer myOperation(sc_integer p1, sc_boolean p2) = 0;
				};
				
				/*! Set the working instance of the operation callback interface 'SCI_Sample_OCB'. */
				void setSCI_Sample_OCB(SCI_Sample_OCB* operationCallback);

An additional interface SCI_Sample_OCB with the pure virtual function sc_integer myOperation(sc_integer p1, sc_boolean p2) has been generated. This interface has to be implemented, and an instance of the implementing class has to be provided to the state machine via the setSCI_Sample_OCB(SCI_Sample_OCB* operationCallback) function, so that the state machine can use it.

#include "DefaultSM.h"

sc_integer DefaultSM::SCI_Sample_OCB::myOperation(sc_integer p1, sc_boolean p2) {
    // Your operation code should be placed here;
    return 0;
}

int main(int argc, char *argv[]) {
    DefaultSM *defaultSM = new DefaultSM();
    SCI_Sample_OCB *sci_Sample_OCB = new SCI_Sample_OCB();

    defaultSM->setSCI_Sample_OCB(sci_Sample_OCB);

    defaultSM->init();
    defaultSM->enter();
    defaultSM->runCycle();
}




Java code generator

Java generator features

Naming feature

The Naming feature allows the configuration of package names as well as class name prefix / suffix.

It is an optional feature and has the following parameters:

  1. basePackage (String, required): The package to create for the generated java classes
  2. implementationSuffix (String, optional): The suffix for the implementing classes

Example:

feature Naming {
    basePackage = "org.yakindu.sct"
    implementationSuffix = "Impl"
}

SynchronizedWrapper feature

The SynchronizedWrapper feature generates a synchronized wrapper for the state machine. This is an additional Java class providing thread-safe access to the generated state machine implementation. As the wrapper adds thread safety any number of client threads may call the state machine.

It is an optional feature and has the following parameters:

  1. namePrefix (String, optional): name prefix for the generated wrapper class
  2. nameSuffix (String, optional): name suffix for the generated wrapper class

Example:

feature SynchronizedWrapper {
    namePrefix = "Synchronized"
    nameSuffix = "Wrapper"
}

RunnableWrapper feature

The RunnableWrapper feature generates a runnable wrapper for the state machine. This is an additional Java class providing a thread-safe wrapper for the generated state machine implementation. In addition to the specific state machine interface it implements the Runnable interface and can be executed in a thread. It implements an event queue and event-driven execution semantics. As the wrapper adds thread safety any number of client threads may call the state machine.

It is an optional feature and has the following parameters:

  1. namePrefix (String, optional): name prefix for the generated wrapper class
  2. nameSuffix (String, optional): name suffix for the generated wrapper class

Example:

feature RunnableWrapper {
    namePrefix = "Runnable"
    nameSuffix = "Wrapper"
}

GeneralFeatures feature

The GeneralFeatures feature allows to configure additional services to be generated along with the state machine. Per default, all parameters are false. It is an optional feature and has the following parameters:

  1. InterfaceObserverSupport (Boolean, optional): enables/disables the generation of listener interfaces for the state machine
  2. RuntimeService (Boolean, optional): enables/disables the generation of a runtime service that triggers the run cycle of a cycle-based state machine
  3. TimerService (Boolean, optional): enables/disables the generation of a timer service implementation using java.util.Timer

Example:

feature GeneralFeatures {
    InterfaceObserverSupport = true
    RuntimeService = true
    TimerService = true
}

Specification of Java code

The explanations below are using the Traffic Light (Java) sample state machine to describe the API specifications of the code generated by the YAKINDU Java code generator. The image below is showing the statechart. It models a pedestrian crossing with push-button operated traffic lights („pelican crossing”).

The traffic light model

The traffic light model

You can find the Java sample project Traffic Light (Java) in the YAKINDU Statechart Tools examples repository. The Java example contains statechart, SGen model, graphical widgets, and some glue code to connect the generated code with the widgets. The graphical widgets are based on SWT.

To execute the Java example, run the file CrossingDemoCycleBased.java as „Java Application” from the Eclipse Run As context menu.

Generated code files

Generally you will find generated code at the places specified in the SGen model, see section "Outlet" for details.

In the case of the traffic light example, you will find the generated code in the src-gen folder.

The package org.yakindu.sct.examples.trafficlight.cyclebased contains the general state machine interfaces and classes. They are needed by each particular state machine and are independend from concrete ones.

The state machine interface

Each generated state machine implements the IStatemachine interface:

package org.yakindu.sct.examples.trafficlight;

/**
 * Basic interface for state machines.
 */
public interface IStatemachine {

	/**
	 * Initializes the state machine. Used to initialize internal variables etc.
	 */
	public void init();

	/**
	 * Enters the state machine. Sets the state machine into a defined state.
	 */
	public void enter();

	/**
	 * Exits the state machine. Leaves the state machine with a defined state.
	 */
	public void exit();

	/**
	 * Checks whether the state machine is active. 
	 * A state machine is active if it has been entered. It is inactive if it has not been entered at all or if it has been exited.
	 */
	public boolean isActive();

	/**
	 * Checks whether all active states are final. 
	 * If there are no active states then the state machine is considered being incative. In this case this method returns <code>false</code>.
	 */
	public boolean isFinal();

	/**
	* Start a run-to-completion cycle.
	*/
	public void runCycle();
}


Fundamental statechart methods

The generated code contains fundamental methods to initialize, enter, and exit a state machine, as well as a method to start a run-to-completion step.

The IStatemachine interface specifies the four methods init(), enter(), exit(), and runCycle().

  • The init() method is used to initialize the internal objects of the state machine right after its instantiation. Variables are initialized to their respective default values. If the statechart defines initialized variables, these initializations are also done in the init() method.

  • The enter() method must be called to enter the state machine. It brings the state machine to a well-defined state.

  • The exit() method is used to leave a state machine statefully. If for example a history state is used in one of the top regions, the last active state is stored and the state machine is left via exit(). Re-entering it via enter() continues to work with the saved state.

  • The runCycle() method is used to trigger a run-to-completion step in which the state machine evaluates arising events and computes possible state changes. Somewhat simplified, a run-to-completion cycle consists of the following steps:

    1. Clear list of outgoing events.

    2. Check whether events have occurred which are leading to a state change.

    3. If a state change has to be done:

      1. Make the present state inactive.

      2. Execute exit actions of the present state.

      3. Save history state, if necessary.

      4. Execute transition actions, if any.

      5. Execute entry actions of the new state.

      6. Make the new state active.

    4. Clear list of incoming events.

Time-controlled state machines

If a statechart uses timing functionality, additional classes are generated.

The traffic light example uses timing funtionality, namely after clauses. To support time-controlled behavior, the interfaces ITimerCallback and ITimer are generated. Like IStatemachine, they are independend of any particular state machine and are generated in the libraryTargetFolder directory, if specified. See section "Outlet" for details.

The generated state machine class implements the ITimerCallback and has a property timer of type ITimer. The client code must provide an ITimer implementation to the state machine by calling the latter’s setTimer() method.

Here’s an example showing how to create a new instance of the state machine (here: class MyTimedStatemachine), create a new instance of a timer (here: class MyTimer), set the latter on the former, and start the state machine by entering it:

MyTimedStatemachine sm = new MyTimedStatemachine();
sm.setTimer(new MyTimer());
sm.enter(); // Enter the state machine

Timer functions generally depend on the hardware target used, therefore the proper time handling has to be implemented by the developer. In principle, for each hardware target a dedicated timer service class implementing the ITimer interface has to be developed.

Default timer implementation

However, upon request the Java code generator can create a default implementation of the ITimer interface, and in many cases it will be sufficient. This implementation is based on java.util.Timer and java.util.TimerTask and should be compatible with the Oracle JVM or the OpenJDK JVM.

To generate the default timer service class, set the TimerService feature in the SGen model to true. Example:

GeneratorModel for yakindu::java {

    statechart MyStateMachine {

        /* … */

        feature GeneralFeatures {
            TimerService = true
        }

    }
}

The generated class is named TimerService and looks like this:

package org.yakindu.sct.examples.trafficlight;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Default timer service implementation.
 *
 */
public class TimerService implements ITimer {

	private final Timer timer = new Timer();
	
	private final List<TimeEventTask> timerTaskList = new ArrayList<TimeEventTask>();
	
	private final Lock lock = new ReentrantLock();
	
	/**
	 * Timer task that reflects a time event. It's internally used by
	 * {@link TimerService}.
	 *
	 */
	private class TimeEventTask extends TimerTask {
	
		private ITimerCallback callback;
	
		int eventID;
	
		/**
		 * Constructor for a time event.
		 *
		 * @param callback
		 *            : Set to {@code true} if event should be repeated
		 *            periodically.
		 *
		 * @param eventID
		 *            : Index position within the state machine's timeEvent
		 *            array.
		 */
		public TimeEventTask(ITimerCallback callback, int eventID) {
			this.callback = callback;
			this.eventID = eventID;
		}
	
		public void run() {
			callback.timeElapsed(eventID);
		}
	
		public boolean equals(Object obj) {
			if (obj instanceof TimeEventTask) {
				return ((TimeEventTask) obj).callback.equals(callback)
						&& ((TimeEventTask) obj).eventID == eventID;
			}
			return super.equals(obj);
		}
	}
	
	public void setTimer(final ITimerCallback callback, final int eventID,
			long time, boolean isPeriodic) {
	
		// Create a new TimerTask for given event and store it.
		TimeEventTask timerTask = new TimeEventTask(callback, eventID);
		lock.lock();
		timerTaskList.add(timerTask);
	
		// start scheduling the timer
		if (isPeriodic) {
			timer.scheduleAtFixedRate(timerTask, time, time);
		} else {
			timer.schedule(timerTask, time);
		}
		lock.unlock();
	}
	
	public void unsetTimer(ITimerCallback callback, int eventID) {
		lock.lock();
		int index = timerTaskList.indexOf(new TimeEventTask(callback, eventID));
		if (index != -1) {
			timerTaskList.get(index).cancel();
			timer.purge();
			timerTaskList.remove(index);
		}
		lock.unlock();
	}
	
	/**
	 * Cancel timer service. Use this to end possible timing threads and free
	 * memory resources.
	 */
	public void cancel() {
		lock.lock();
		timer.cancel();
		timer.purge();
		lock.unlock();
	}
}


Timer service

A timer service must implement the ITimer interface and must be able to maintain a number of time events and the timers associated with them. A time event is identified by a numeric ID.

If suitable, an application can use the default timer service class TimerService, see section "Default timer implementation" for details.

The ITimer interface looks like this:

package org.yakindu.sct.examples.trafficlight;

/**
 * Interface a timer has to implement. Use to implement your own timer
 * service.
 * 
 */
public interface ITimer {

	/**
	 * Starts the timing for a given time event id.
	 * 
	 * @param callback
	 * 			  : The target callback where the time event has to be raised.
	 * 
	 * @param eventID
	 *            : The eventID the timer should use if timed out.
	 *            
	 * @param time
	 *            : Time in milliseconds after the given time event should be
	 *            triggered
	 *            
	 * @param isPeriodic
	 * 			  : Set to true if the time event should be triggered periodically
	 */
	public void setTimer(ITimerCallback callback, int eventID, long time, boolean isPeriodic);

	/**
	 * Unset a time event.
	 * 
	 * @param callback
	 * 			: The target callback for which the time event has to be unset.
	 * 
	 * @param eventID
	 * 			: The time event id.
	 */
	public void unsetTimer(ITimerCallback callback, int eventID);
}


Method setTimer

A state machine calls the setTimer(ITimerCallback callback, int eventID, long time, boolean isPeriodic) method to tell the timer service that it has to start a timer for the given eventID. The time parameter specifies the number of milliseconds until the timer expires. When this period of time has elapsed, the timer service must raise the time event by calling the method public void timeElapsed(int eventID) on the ITimerCallback specified by the callback parameter, i. e. usually the state machine.

It is important to keep the execution of the setTimer() method short and use it only to start a timer thread, a hardware timer interrupt, or the like. Avoid any time-consuming operations like extensive computations, Thread.sleep(…), waiting, etc. Otherwise the state machine execution might hang within the timer service or might not show the expected runtime behavior.

If the parameter isPeriodic is false, the timer service raises the time event only once. If isPeriodic is true, the timer service raises the time event every time milliseconds.

Method unsetTimer

If the state machine calls the unsetTimer(ITimerCallback callback, int eventID) method the timer service must unset the timer for the given eventID, i. e. the time event will not be raised.

Raising time events on a state machine

If a statechart is using time events, the generated Java state machine class not only implements the IStatemachine interface, but it also implements the ITimerCallback interface. ITimerCallback is defined as follows. It specifies a single method: public void timeElapsed(int eventID).

package org.yakindu.sct.examples.trafficlight;

/**
* Interface for state machines which use timed event triggers.
*/
public interface ITimerCallback {
	
	/**
	* Callback method if a time event occurred.
	* 
	* @param eventID
	* 			:The id of the occurred event.
	*/
	public void timeElapsed(int eventID);
}


Method timeElapsed

It is the timer service’s responsibility to actually raise a time event on a state machine. To do so, the timer service calls the state machine’s timeElapsed() method and supplies the time event’s eventID as a parameter. The state machine recognizes the time event and will process it during the next run cycle.

You can conclude that in order to process time events without too much latency, the runtime environment has to call the state machine’s runCycle() method as frequently as needed. Consider for example a time event which is raised by the timer service after 500 ms. However, if the runtime environment calls the state machine’s runCycle() method with a frequency of once per 1000 ms only, the event will quite likely not be processed at the correct points in time.

Runtime service

The RuntimeService class maintains all state machines that are expected to execute run-to-completion steps periodically. A client application can retrieve the RuntimeService singleton using RuntimeService.getInstance(). It can then pause, resume or cancel all state machines that are poised to run at a specified intervall.

Please note: To enable this feature, set the RuntimeService parameter of GeneralFeatures to true, see Java: General Features.

Accessing interfaces, variables and events

In a YAKINDU statechart, variables and events are contained in so-called interfaces. There can be at most one default, unnamed interface plus zero or more named interfaces. In the generated Java code, these interfaces can be found as inner interface of the interface specifying the state machine. The outer interface’s name is derived from the statechart’s name while the inner interfaces' names are derived from the respective names of the statechart interfaces.

Let’s have a look at the following sample statechart interface declaration:

interface Sample:
    var a:boolean
    in event evA:boolean
    out event evB:integer

The generated interface code looks like this:

package org.yakindu.scr.defaultsm;
import org.yakindu.scr.IStatemachine;

public interface IDefaultSMStatemachine extends IStatemachine {
	public interface SCISample {
		public void raiseEvA(boolean value);
		public boolean isRaisedEvB();
		public long getEvBValue();
		public boolean getA();
		public void setA(boolean value);

	}

	public SCISample getSCISample();

}

A statechart interface is generated as an inner Java interface within the state machine interface. The Java interface’s name is derived from the statechart interface’s name by prepending the string SCI.

A special case is the unnamed statechart interface: It is generated as the Java interface SCInterface.

An incoming event evA:boolean is generated as the raise method raiseEvA(boolean value). Since the event is of type boolean the method has a boolean parameter.

For an outgoing event evB:integer the methods boolean isRaisedEvB() and long getEvBValue() are generated. The former can be used to determine whether the event has already been raised by the state machine or not. The latter serves to query the value of the event.

For variables, the code generator creates getter and setter methods, here boolean getA() and void setA(boolean value).

The code generator also creates appropriately named getter methods in the enclosing interface which can be used to acquire the nested interfaces, here: SCISample getSCISample().

The nesting interface is implemented by the generated state machine source code. Each nested interface is implemented as an internal class of the state machine class. The latter holds instances of the nested interface implementations and provides them via getter methods. Have a look at the source code generated for the Sample interface:

package org.yakindu.scr.defaultsm;

public class DefaultSMStatemachine implements IDefaultSMStatemachine {

	protected class SCISampleImpl implements SCISample {

		private boolean evA;

		private boolean evAValue;

		public void raiseEvA(boolean value) {
			evA = true;
			evAValue = value;
		}

		protected boolean getEvAValue() {
			if (!evA)
				throw new IllegalStateException("Illegal event value access. Event EvA is not raised!");
			return evAValue;
		}

		private boolean evB;

		private long evBValue;

		public boolean isRaisedEvB() {
			return evB;
		}

		protected void raiseEvB(long value) {
			evB = true;
			evBValue = value;
		}

		public long getEvBValue() {
			if (!evB)
				throw new IllegalStateException("Illegal event value access. Event EvB is not raised!");
			return evBValue;
		}

		private boolean a;

		public boolean getA() {
			return a;
		}

		public void setA(boolean value) {
			this.a = value;
		}

		protected void clearEvents() {
			evA = false;
		}

		protected void clearOutEvents() {
			evB = false;
		}
	}

	protected SCISampleImpl sCISample;

	private boolean initialized = false;

	public enum State {
		main_region_MyState, $NullState$
	};

	private final State[] stateVector = new State[1];

	private int nextStateIndex;

	public DefaultSMStatemachine() {

		sCISample = new SCISampleImpl();
	}

	public void init() {
		this.initialized = true;
		for (int i = 0; i < 1; i++) {
			stateVector[i] = State.$NullState$;
		}

		clearEvents();
		clearOutEvents();

		sCISample.setA(false);
	}

	public void enter() {
		if (!initialized)
			throw new IllegalStateException(
					"The state machine needs to be initialized first by calling the init() function.");

		enterSequence_main_region_default();
	}

	public void exit() {
		exitSequence_main_region();
	}

	/**
	 * @see IStatemachine#isActive()
	 */
	public boolean isActive() {

		return stateVector[0] != State.$NullState$;
	}

	/** 
	* Always returns 'false' since this state machine can never become final.
	*
	 * @see IStatemachine#isFinal() 
	 */
	public boolean isFinal() {
		return false;
	}

	/**
	* This method resets the incoming events (time events included).
	*/
	protected void clearEvents() {
		sCISample.clearEvents();

	}

	/**
	* This method resets the outgoing events.
	*/
	protected void clearOutEvents() {
		sCISample.clearOutEvents();
	}

	/**
	* Returns true if the given state is currently active otherwise false.
	*/
	public boolean isStateActive(State state) {
		switch (state) {
			case main_region_MyState :
				return stateVector[0] == State.main_region_MyState;
			default :
				return false;
		}
	}

	public SCISample getSCISample() {
		return sCISample;
	}

	/* 'default' enter sequence for state MyState */
	private void enterSequence_main_region_MyState_default() {
		nextStateIndex = 0;
		stateVector[0] = State.main_region_MyState;
	}

	/* 'default' enter sequence for region main region */
	private void enterSequence_main_region_default() {
		react_main_region__entry_Default();
	}

	/* Default exit sequence for state MyState */
	private void exitSequence_main_region_MyState() {
		nextStateIndex = 0;
		stateVector[0] = State.$NullState$;
	}

	/* Default exit sequence for region main region */
	private void exitSequence_main_region() {
		switch (stateVector[0]) {
			case main_region_MyState :
				exitSequence_main_region_MyState();
				break;

			default :
				break;
		}
	}

	/* The reactions of state MyState. */
	private void react_main_region_MyState() {
	}

	/* Default react sequence for initial entry  */
	private void react_main_region__entry_Default() {
		enterSequence_main_region_MyState_default();
	}

	public void runCycle() {
		if (!initialized)
			throw new IllegalStateException(
					"The state machine needs to be initialized first by calling the init() function.");

		clearOutEvents();

		for (nextStateIndex = 0; nextStateIndex < stateVector.length; nextStateIndex++) {

			switch (stateVector[nextStateIndex]) {
				case main_region_MyState :
					react_main_region_MyState();
					break;
				default :
					// $NullState$
			}
		}

		clearEvents();
	}
}

The value of an event can be accessed only if the event has been processed in a run-to-completion step. Otherwise an IllegalStateException will be thrown.

Interface observers

If the general feature InterfaceObserverSupport is enabled in the SGen model, the generated interfaces will support the registration of observers.

Enabling the InterfaceObserverSupport feature looks like this in the .sgen file:

feature GeneralFeatures {
    InterfaceObserverSupport = true
}

Now the generated code has additional features:

package org.yakindu.scr.defaultsm;
import java.util.List;
import org.yakindu.scr.IStatemachine;

public interface IDefaultSMStatemachine extends IStatemachine {
	public interface SCISample {
		public void raiseEvA(boolean value);
		public boolean isRaisedEvB();
		public long getEvBValue();
		public boolean getA();
		public void setA(boolean value);
		public List<SCISampleListener> getListeners();

	}

	public interface SCISampleListener {
		public void onEvBRaised(long value);
	}

	public SCISample getSCISample();

}

An additional listener interface is generated, here SCISampleListener. It contains a callback method for each outgoing event. Here it is a single one: void onEvBRaised(long value).

The client code has to provide an implementation of the listener interface. A listener method gets called by the state machine when it raises an outgoing event.

To register or unregister a listener, use the getListeners() method of the nesting interface. This method returns a java.util.List parameterized with the appropriate listener type. Initially this list is empty. Add or remove listeners as needed.

A callback method specified by the listener interface should complete its operations quickly, because otherwise the state machine execution might be delayed for too long, potentially leading to unexpected runtime behavior.

Operation callbacks

YAKINDU Statechart Tools support operations that are executed by a state machine as actions, but are implemented by client-side code. The figure below shows a sample statechart using an operation:

Specifying an operation callback in the model

Specifying an operation callback in the model

Let’s have a look at the generated code:

package org.yakindu.scr.defaultsm;
import java.util.List;
import org.yakindu.scr.IStatemachine;

public interface IDefaultSMStatemachine extends IStatemachine {
	public interface SCISample {
		public void raiseEvA(boolean value);
		public boolean isRaisedEvB();
		public long getEvBValue();
		public boolean getA();
		public void setA(boolean value);
		public List<SCISampleListener> getListeners();

		public void setSCISampleOperationCallback(SCISampleOperationCallback operationCallback);
	}

	public interface SCISampleListener {
		public void onEvBRaised(long value);
	}

	public interface SCISampleOperationCallback {
		public long myOperation(long p1, boolean p2);
	}

	public SCISample getSCISample();

}

An additional interface SCISampleOperationCallback specifying the method public long myOperation(long p1, boolean p2) has been generated. The client code has to

  • provide an implementation of this interface and
  • pass an instance of it to the state machine via the setSCISampleOperationCallback(SCISampleOperationCallback operationCallback) method.

Here’s some sample code that passes an implementation of the operation to a state machine, and then executes the latter:

public static void main(String[] args) {
    DefaultSMStatemachine statemachine = new DefaultSMStatemachine();

    SCISampleOperationCallback callback = new SCISampleOperationCallback() {

        @Override
        public long myOperation(long p1, boolean p2) {
            // Your operation code should be placed here;
            return 0;
        }
    };

    statemachine.getSCISample().setSCISampleOperationCallback(callback);

    statemachine.init();
    statemachine.enter();
    statemachine.runCycle();
}


Integrating generated code

To get a clue how to integrate a generated Java state machine with your project have a look at the CrossingDemoCycleBased class and its abstract superclass CrossingDemoBase. The main() method is in CrossingDemoCycleBased:

public static void main(String[] args) {

    new CrossingDemoCycleBased().runTrafficLight();
}

A new instance of the class is created and the method runTrafficLight() is called. This method can be found in the superclass:

public void runTrafficLight() {

        setUpAndRunStatemachine();
        createUIContent();

        shell.open();
        while (!shell.isDisposed()) {
            // update traffic lights
            readStatemachineOutput();

            crossing.repaint();

            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }

        tearDownStatemachine();
}

This method sets up the state machine and creates the GUI content. In a while loop it reads the content of the state machine and repaints the GUI. If the user exits the GUI shell, the loop terminates and the state machine is torn down. The really interesting methods are setUpAndRunStatemachine(), readStatemachineOutput(), and tearDownStatemachine():

protected void setUpAndRunStatemachine() {

    statemachine = new TrafficLightStatemachine();
    statemachine.setTimerService(new TimerService());
    statemachine.init();
    statemachine.enter();

    RuntimeService.getInstance().registerStatemachine(statemachine, 100);
}

First a new instance of the generated state machine is created. Since the traffic light statechart uses timing clauses, it is provided with a timer service, here with the default implementation of the ITimerService interface. In the next steps the state machine is initialized and entered. After the enter() method has been executed, the machine is in a defined state.

Finally the state machine is passed to the runtime service. This service executes the runCycle() method of the state machine every 100 ms, that is the state machine executes a run-to-completion step every 100 ms.

protected void readStatemachineOutput() {
    trafficLightFigure.setRed(statemachine.getSCITrafficLight()
            .getRed());
    trafficLightFigure.setYellow(statemachine.getSCITrafficLight()
            .getYellow());
    trafficLightFigure.setGreen(statemachine.getSCITrafficLight()
            .getGreen());
        pedestrianLightFigure.setWhite(statemachine.getSCIPedestrian()
            .getRequest());
    pedestrianLightFigure.setRed(statemachine.getSCIPedestrian()
            .getRed());
    pedestrianLightFigure.setGreen(statemachine.getSCIPedestrian()
            .getGreen());
}

The generated code contains getters and setters for each variable and event. So it’s easy to read values from or write values to a state machine, raise events, or ask the state machine whether outgoing events have been raised during the last run-to-completion step. Within the readStatemachineOutput() method, these methods are used to get the lights values from the state machine and set them to the UI elements. The methods pedestrianRequestButtonClicked() and onOffButtonClicked() raise some events.

Hint: When outgoing events are raised within the state machine, they remain active until the next run-to-completion step is started.

@Override
protected void tearDownStatemachine() {
    // End TimerHandler and RuntimeService.
    statemachine.getTimerService().cancel();
    RuntimeService.getInstance().cancelTimer();
}

If the UI thread has been terminated by the user, the state machine will be shut down. It is necessary to explicitly end the timer service. Finally the runtime service is cancelled.

Simulating operations with custom Java code

To simulate a model with operations it is possible to use custom Java code that mocks the desired behavior or even to simulate against an existing Java backend. For this purpose it is required to provide one or more custom Java classes having a method with a matching signature.

A statechart model with an operation

A statechart model with an operation

To simulate the statechart above, a new Java class must be created matching the method signature defined in the statechart. This class must be placed onto the classpath of the
statecharts project.

YAKINDU Statechart Tools' default types are mapped to Java types as follows:

SCT Type Java Type
integer long
real double
boolean boolean
string String
void void
package example;
public class Calculator {
   public long add(long param1, long param2) {
      return param1 + param2;
   }
}

This custom class can be passed to Eclipse’s run configuration as an Operation Class, see the figure below. It is possible to specify multiple Java classes, separated by comma.

When the simulation is executed, the variable result gets the value 2.

Configuring an operations class

Configuring an operations class

Custom code generators

Although YAKINDU Statechart Tools are shipped with powerful code generators for C, C++ and Java, it may be necessary to create a custom code generator to support a variety of use cases. One reason for a custom code generator could be to support additional programming languages – in this case we would be happy to receive a contribution! – or to generate code for an existing framework.

Prerequisits

Implementing a custom code generator is no trivial task. Before you get started, you should have a basic understanding about the Eclipse Modeling Framework that we are using to structure our data model. Furthermore, we highly recommend to use Xtend as your code generator’s template language, although plain old Java is still supported. Xtend provides some great features like Template Expressions,
Lambdas and Polymorphic Method Invocation, boosting readability and productivity.

Creating a new custom code generator project

Creating custom code generators is a built-in concept in YAKINDU Statechart Tools. You can develop custom code generators directly within your project workspace. You can choose between Xtend2 (preferred) or simply Java for implementing the code generator templates.

To set up a new (Xtend2/Java) generator project, select File → New → Other... → YAKINDU SCT → Xtend2/Java Generator Project_ and click Next.

Creating an Xtend2 generator project

Creating an Xtend2 generator project

The wizard asks for a Project name and the name of the Generator class, which has to be the fully-qualified class name. If you check the Use Xtend checkbox, the generator class will initially be created as an Xtend class. Otherwise, Java will be used for the generator.

The check box Configure for Plugin Export adds all required extension point registrations to the new project for exporting it as a plugin. The generator model can refer to the new generator plugin via its unique Generator ID. If you want to contribute custom generator features for your code generator, check the Create Feature Library check box. Click Finish to close the wizard.

Created generator project

Created generator project

Voilà! The wizard creates a new generator project for you with a structure as shown above. The file CustomGenerator.xtend contains a simple default code generator that simply prints the name of the statechart and all of its states to the target file.

Executing the custom code generator

To test your custom code generator, create a new project containing a YAKINDU Statechart Tools model as described in section "Editing statecharts".

After that, create a new generator model as described in section "Configuring a generator". Select Custom Xtend2/Java based code generator as the generator to use. As you may have noticed, the generator model for the yakindu::generic generator contains an additional feature called Generator. This is where you can specify the name of your custom generator project and the fully-qualified generator class name as shown below.

GeneratorModel for yakindu::generic {
	statechart MyStatechart {
		feature Outlet {
			targetProject = "SCTExample"
			targetFolder = "src-gen"
		}
		feature Generator {
			generatorProject = "MyCustomGenerator"
			generatorClass = "org.yakindu.CustomGenerator"
		}
	}
}

If you right click the .sgen file and select „Generate Statechart Artifacts” from the context menu the generator is executed and creates a new file src-gen/MyStatechart.txt with the following content.

The name of the Statemachine is 'MyStatechart'
The Statemachine has the following states:
main_region.A
main_region.B

Congratulations, you successfully created a custom generator project! Add

The Statemachine contains «flow.states.size» states

to the CustomGenerator.xtend file and regenerate. This results in MyStatechart.txt being updated immediately. This is a very powerful feature of YAKINDU Statechart Tools. You can develop your code generator at runtime with zero turnaround time. Just click generate and see the results.

SGen features for custom code generators

Generator feature

The Generator feature allows to configure a custom code generator located in the workspace and written in Java or in another JVM language. It is a required feature and has the following parameters:

  1. generatorProject (String, required): the name of the generator project
  2. generatorClass (String, required): the fully-qualified name of the code generator class
feature Generator {
    generatorProject = "org.yakindu.sct.mygenerator"
    generatorClass = "org.yakindu.sct.MyGenerator"
}

Different meta models for different use cases

The SGraph meta model
The SGraph meta model defines the structural aspects of the Statechart model and is similiar to the statemachine model defined by the Unified Modeling Language (UML). A simplified version is shown in the following diagram.

Simplified SGraph meta model

Simplified SGraph meta model

  • Statechart extends CompositeElement, therefore it contains 0..* Regions. It is the root element of the model.
  • CompositeElement is an abstract type that contains Regions. Known subclasses are Statechart and State.
  • Region contains 1..* Vertices.
  • Vertex is an abstract type representing nodes in the SGraph tree. Vertices contain outgoing Transitions.
  • RegularState is an abstract type derived from Vertex. It has no additional features, but it is the common base type for State and FinalState.
  • State is derived from RegularState and CompositeElement, thus it may contain Regions and Transitions.
  • FinalState is derived from RegularState. It indicates the completion of its containing Region.
  • PseudoState is an abstract type derived from Vertex. It has no additional features, but is the common base type for Choice, Entry, Exit and Synchronization.
  • Choice is a Pseudostate with the additional attribute kind, that determines whether the element has static or dynamic execution semantics.
  • Entry is a Pseudostate with the additional attribute kind. An Entry may be of the kind Initial, ShallowHistory or DeepHistory.
  • Exit is a Pseudostate. It is defined as the point where a region is left.
  • Synchronization is a Pseudostate. It is equivalent to a Fork and to a Join.
  • Transition is defined as a directed relationship between two vertices.

Edit on GitHub

Headless code generation

State machine code is fully derived from a statechart model, and section Generating state machine code explains how to interactively run a code generator. Since release 2.9.0, YAKINDU Statechart Tools additionally provides a headless code generator infrastructure. This allows for code generation without a user interface which in turn allows to integrate code generation into continuous integration (CI) build environments.

Best practice is to generate code on the fly during a continuous integration build instead of cluttering your version control system with generated artifacts.

The headless code generator can simply be called from a command line and thus can be integrated with different CI tools easily. No matter if you are using Gradle, Maven or make, all you need is a Java runtime on your build machine.

Please note:

Headless code generation is a new feature the YAKINDU team wants to make available as quickly as possible. Since it is still at an early development stage it might still have some rough edges. If you encounter any problems, please consider section "Troubleshooting" for help, ask a question in the support forum, or file a bug report to the issue tracker.

Installation

If you are working with the Professional Edition of YAKINDU Statechart Tools, the headless code generation feature is already included and no further installation procedure is required. You can skip this section and continue reading at "The scc statechart compiler".

As a user of the Open Edition you can install the headless code generation feature from the YAKINDU Statechart Tools Professional update site.

Please note:

Don’t worry! Even though the headless generation feature is coming to you from the YAKINDU Statechart Tools Professional update site, no license fees will be incurred. Headless generation is completely free to use, though not open source.

To install the headless generation feature into the open edition or another eclpse instance please follow the following steps.

  • Open the registration form with your web browser. If you have never registered before, please fill in your name, profession, and e-mail address. Click on Send.
  • You will receive an e-mail containing a download link. In that e-mail, click on Download SCT Pro Edition.
  • Your personalized download page will open in your web browser, showing the following download options.

YAKINDU Statechart Tools Professional Edition download options

YAKINDU Statechart Tools Professional Edition download options

  • Click on Install from update site. The browser will take to another page and display the URL of the YAKINDU Statechart Tools Pro update site. You will need it subsequently.
  • In the Eclipse main menu, select Help → Install New Software….
  • The Install wizard opens.
  • Click on the Add… button. The Add Repository dialog opens.
  • Paste the above-mentioned URL into the Location text field. Enter a meaningful name into the Name field, e.g. „YAKINDU Statechart Tools Professional”.
  • Click on OK.
  • In the Install wizard, open the YAKINDU Statechart Tools Professional Edition entry.
  • Select YAKINDU Headless Code Generator, see figure "Installing the headless code generation feature". Click on Next >.
  • The Install wizard displays all items to be installed, i.e. YAKINDU Headless Code Generator. Click on Next >.
  • The license agreements for the software to be installed are displayed. Accept them and click on Finish.
  • The headless generation feature is installed.

Installing the headless code generation feature

Installing the headless code generation feature

The scc statechart compiler

The SCT installation directory contains the file scc (Linux, macOS) resp. scc.bat (Windows): the statechart compiler. Technically, it is a script file launching the SCT executable, passing proper parameters for headless code generation.

Please include the SCT installation directory in your PATH environment variable, so the scc command can be executed anywhere from a command-line shell!

The following Linux example assumes YAKINDU Statechart Tools to be installed in /opt/software/yakindu-sct:

export PATH="/opt/software/yakindu-sct:${PATH}"

Calling scc with the -h option prints the the integrated help information. In a command-line shell, enter the following command:

scc -h

The output should be similar to the following:

--------------------------------------------------------
YAKINDU Statechart Tools Headless Generator ((c) by itemis AG)

          Visit http://www.statecharts.org
--------------------------------------------------------
usage: scc [-d <path>] [-h] [-m <path>{,<path>}…]
 -d,--baseDir <path>   Relative/absolute path to the working directory that contains your statechart projects. If not
                       set the current directory is used.
 -h                    Shows help content.
 -m,--model <path>{,<path>}…  A list of comma separated Relative/absolute paths
                              to model(s) used during execution. If not set
                              the runtime value of basedir is used.

Integrating custom code generators

YAKINDU Statechart Tools supports custom generators as described in Custom code generators. These custom code generators can be executed by use of the headless code generator infrastructure also. To do so the custom code generator has to be installed as an eclipse plug-in within the distribution where the headless code generator feature was installed as described in Installation - thats it: the custom generator will be used whenever a generator model (*.sgen), loaded by the headless generator infrastructure, referencing the custom code generator id.

Generating code

For the purpose of this documentation we are assuming the following directory structure. This includes SCT-related files like generator models and statechart models.

Headless Directory Structure

Within our sample directory structure the generator model project1/default.sgen contains the following:

GeneratorModel for yakindu::java {
	statechart default {
		feature Outlet {
			targetProject = "project1"
			targetFolder = "src-gen"
		}
	}
}

Generating code for an Eclipse project

The most simple way to invoke the code generator on the command line is to generate the code for a single Eclipse project. Using a commend-line shell, change to the project directory you want to generate code. Example:

cd [somepath]/basedir/project1

Then invoke the statechart compiler without any parameters:

scc

Please make sure scc is on your PATH environment variable. Alternatively specify the path to the scc executable in the command.

--------------------------------------------------------
YAKINDU Statechart Tools Headless Generator ((c) by itemis AG)
	  Visit http://www.statecharts.org
--------------------------------------------------------
1 gen model(s) and 1 statechart(s) loaded.
Generating 'default' to target project 'project1' ...
default done.
Generated (1 of 1) gen model(s).

The statechart compiler will invoke the code generator for all .sgen files contained in the project directory. It will look them up recursively. You’ll find the generated code at the location specified in the .sgen file:

[somepath]/basedir/[sgen.targetProject]/[sgen.targetFolder]

In this case the statechart compiler determines whether a .project file is available in the current directory and will automatically adjust the generation target to be the parent of the actual project directory to ensure the parameters defined in an .sgen file are interpreted correctly.

Using scc options

Within the root folder of your YAKINDU Statechart Tools installation enter one of the following platform-specific commands. The string [pathToBasedir] must be replaced by the concrete path to the base directory, i.e. the common parent directory of the project directories.

Windows:

scc -d [pathToBasedir] -m project1/default.sgen,project2/default.sct

Linux:

./scc -d [pathToBasedir] -m project1/default.sgen,project2/default.sct

macOS:

cd SCT.app/Contents/Eclipse/
./scc -d [pathToBasedir] -m project1/default.sgen,project2/default.sct

Please see the following sample output as a result of the command:

--------------------------------------------------------
YAKINDU Statechart Tools Headless Generator ((c) by itemis AG)
	  Visit http://www.statecharts.org
--------------------------------------------------------
1 gen model(s) and 1 statechart(s) loaded.
Generating 'default' to target project 'project1' ...
default done.
Generated (1 of 1) gen model(s).

As you can see the headless code generation has properly executed. The generated code will be placed into a folder depending on the values configured within your generator model file.

For our example this means the generated code can be found in

basedir/project1/src-gen/

Available scc options

All parameters are essentially optional. The reason is that for a directory structure adhering to Eclipse workspace standards like projects within a root folder, or no additional hierarchies, everything can be calculated automatically.

Specifying a base directory

You can specify a base directory using the -d basedir option. It is used for two major tasks:

  1. It is used to evaluate the absolute paths to model locations, provided they are given as relative paths.
  2. It is used to evaluate the respective generation target folders, depending on the values given in the generator models.

Default: If the -d option is not specified, this is equivalent to -d ., i.e. the current working directory will be the base directory.

The target folder of generated artifacts will be evaluated as follows:

basedir/sgen.targetProject/sgen.targetFolder

Specifying model files

Use the -m models option to select certain models known by the statechart compiler. Usually these will be .sgen or .sct files. However, other model files that are supported by particular code generators, can be specified, too, e.g. C header files for YAKINDU Statechart Tool PRO’s deep C integration. The -m option’s value can be absolute or relative, can be a file or a folder and can be a single value or a comma-separated list of values.

  • If model is a comma-separated list of values, the statechart compiler will handle each value as described for model in the subsequent bullet points.
  • If model is relative, it is evaluated as basedir/model.
  • If model is a folder, the generator will search recursively for all model files known by the statechart compiler within basedir/statechart_model.

Default: If the -m option is not specified, this is equivalent to -m basedir, i.e. all model files in and beneath basedir are processed.

Troubleshooting

If the scc command fails, here are some tips and fixes.

[ERROR] Neither ‚SCT’ nor ‚eclipse’ executable found!

If you encounter this error message on Linux, this means the scc command was neither able to locate a SCT executable binary nor an eclipse executable. This happens if the scc executable, which is really only a launcher, is not in the same directory as the SCT or eclipse executables. In a YAKINDU Statechart Tools stand-alone installation, the executable binary is named SCT. However, its name is eclipse if you have installed YAKINDU Statechart Tools from an update site into an already existing Eclipse installation. The scc launcher tries to find one of SCT or eclipse and launches the correct one.

The Eclipse executable launcher was unable to locate its companion shared library

This error message is usually issued if you have installed YAKINDU Statechart Tools from an update site into an already existing Eclipse installation and you installed the latter using the Eclipse Installer / Oomph.

Fixing this is easy: You have to change a single line in the headless.ini file in the Eclipse installation directory.

At first, open the eclipse.ini file in the same directory and search for the --launcher.library line. Copy the path that is on the next line. The two lines might look like this:

--launcher.library
/home/user/.p2/pool/plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.400.v20160518-1444

You’ll find a similar entry like in the headless.ini as well, probably looking like this:

--launcher.library
plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.400.v20160518-1444

Replace the path in the headless.ini file with the path from eclipse.ini. After saving headless.ini, scc should work.