Skip to main content

DISCLAIMER: This example only work with UNIX based systems due to the nature of Eclipse console

C Code Generation for Multi State Machines

This example demonstrates how to generate C code for a multi state machine scenario and how to set everything up in the application code. As example, we will use a light controller that controls two lights. The controller as well as the lights are defined by state machines that communicate with each other by the means of events.

You can find more information on using the multi state machine feature in the online documentation.

Example Application

The example application consists of two state machines, one for the controller, and one that describes a simple light switch.

Light switch model

The light switch model simply consists of two states, On and Off, and respectively two incoming events on and off. Each time the event on is received, the brightness is increased until a maximum brightness is reached. Whenever the brightness changes, the outgoing event brightness_changed is raised with the new brightness value. Both, the incoming and outgoing events, are used in the controller machine to control two different lights.

Light controller model

Let's first take a look at the definition section. The controller machine declares two variables light1 and light2 of type Light which represents the light state machine above. When the controller machine is entered, also these two light machines are entered. In addition, they get initialized with different maximum brightness values.

Light controller model

The state machine basically describes two modes, a regular one in which the switch_on event is just passed on to both light machines, and a blink mode in which the lights' on events are raised periodically. In this mode, both lights' brightness will be automatically increased until its maximum where it starts from zero again. In order to notice when the maximum brightness of a light is reached, the BlinkMode state reacts to the light machines' brightness_changed events and reads the transmitted value.

Generator Model

The generator model simply defines the configuration for both state machines. As the state machines use time events, we ensure that a timer service implementation is generated with the TimerService parameter.

GeneratorModel for create::c {

const PROJECT : string = "itemis.create.examples.codegen.multism.c"

const FOLDER : string = "src-gen"

statechart LightController {

feature Outlet {
targetProject = PROJECT
targetFolder = FOLDER
}
feature GeneralFeatures {
timerService = true
}
}

statechart Light {

feature Outlet {
targetProject = PROJECT
targetFolder = FOLDER
}
}
}

Invoking Code Generation

Code generation is usually invoked each time the statechart model is saved. This behavior can be disabled by unchecking the option Project -> Build Automatically. You can always manually invoke the code generation with Generate Code Artifacts in the context menu of the generator model.

Application Code

Our application is a simple interactive console with which the user can switch the lights on or off as well as toggle the blink mode. The complete application code is implemented in main.c:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

#include "../src-gen/Light.h"
#include "../src-gen/LightController.h"
#include "../src-gen/sc_rxc.h"
#include "../src-gen/sc_timer_service.h"

/* ! As we make use of time triggers (after & every)
* we make use of a generic timer implementation
* and need a defined number of timers. */
#define MAX_TIMERS 4

//! We allocate the desired array of timers.
static sc_timer_t timers[MAX_TIMERS];

//! The timers are managed by a timer service. */
static sc_timer_service_t timer_service;

// Start point of the execution.
unsigned long time_offset = 0;

// Last execution time.
unsigned long last_time = 0;

// Current time.
unsigned long current_time = 0;

// Stores the time to sleep.
struct timespec sleep_time;

static char buf[20];

/*! Instantiates the state machine */
LightController controller;
/* Instantiates and sets the sub machines */
Light light1;
Light light2;

/*! This function will be called when brightness_changed event is called in light1 */
static void on_light1_brightness_changed(LightController *o, sc_integer value) {
printf("Light 1 Brightness: %d\n", value);
}

/*! This function will be called when brightness_changed event is called in light2 */
static void on_light2_brightness_changed(LightController *o, sc_integer value) {
printf("Light 2 Brightness: %d\n", value);
}

/*! This function is used in the timer service and required to reuse the same timer service for all state machines */
static void dispatchTimeEvent(void *handle, sc_eventid evid) {
if (handle == &controller) {
lightController_raise_time_event(&controller, evid);
}
if (handle == &light1) {
light_raise_time_event(&light1, evid);
}
if (handle == &light2) {
light_raise_time_event(&light2, evid);
}
}

unsigned long get_ms() {
struct timeval tv;
unsigned long ms;
gettimeofday(&tv, 0);
ms = tv.tv_sec * 1000 + (tv.tv_usec / 1000);
return ms;
}

int main(int argc, char **argv) {
/*! Initializes the timer service */
sc_timer_service_init(&timer_service, timers, MAX_TIMERS,
(sc_raise_time_event_fp) &dispatchTimeEvent);

/*! Initializes the state machines and sets all sub machines */
lightController_init(&controller);
light_init(&light1);
light_init(&light2);
lightController_set_light1(&controller, &light1);
lightController_set_light2(&controller, &light2);

/*! Subscribes observers to the lights' brightness_changed events */
sc_single_subscription_observer_sc_integer light1Observer;
sc_single_subscription_observer_sc_integer_init(&light1Observer, &light1,
(sc_observer_next_sc_integer_fp) on_light1_brightness_changed);
sc_single_subscription_observer_sc_integer_subscribe(&light1Observer,
&light1.iface.brightness_changed);
sc_single_subscription_observer_sc_integer light2Observer;
sc_single_subscription_observer_sc_integer_init(&light2Observer, &light2,
(sc_observer_next_sc_integer_fp) on_light2_brightness_changed);
sc_single_subscription_observer_sc_integer_subscribe(&light2Observer,
&light2.iface.brightness_changed);

/*! Enters the state machine; from this point on the state machine is ready to react on incoming event */
lightController_enter(&controller);

sleep_time.tv_sec = 0;
sleep_time.tv_nsec = 100;
time_offset = get_ms();

/*! Ensures non-blocking read() call. */
//fcntl(STDIN_FILENO, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
printf("Type 1 or 0 to switch the lights on or off.\n");
printf("Type 2 to toggle the blink mode.\n");
while (1) {
current_time = get_ms() - time_offset;
sc_timer_service_proceed(&timer_service, current_time - last_time);
int numRead = read(STDIN_FILENO, buf, 1);
if (numRead > 0) {
char input = buf[0];
if (input == '1') {
/*! Raises the switch_on event in the state machine which causes the corresponding transition to be taken */
lightController_user_raise_switch_on(&controller);
} else if (input == '0') {
/*! Raises the switch_off event in the state machine */
lightController_user_raise_switch_off(&controller);
} else if (input == '2') {
/*! Raises the blink event in the state machine */
lightController_user_raise_blink_mode(&controller);
}
}
last_time = current_time;
nanosleep(&sleep_time, 0);
}
return 0;
}

/*! This function will be called for each time event in LightController when a state is entered. */
void lightController_set_timer(LightController *handle, const sc_eventid evid,
const sc_integer time_ms, const sc_boolean periodic) {
sc_timer_set(&timer_service, handle, evid, time_ms, periodic);
}

/*! This function will be called for each time event in LightController when a state will be left. */
void lightController_unset_timer(LightController *handle, const sc_eventid evid) {
sc_timer_unset(&timer_service, evid);
}

/*! This function will be called for each time event in Light when a state is entered. */
void light_set_timer(Light *handle, const sc_eventid evid,
const sc_integer time_ms, const sc_boolean periodic) {
sc_timer_set(&timer_service, handle, evid, time_ms, periodic);
}

/*! This function will be called for each time event in Light when a state will be left. */
void light_unset_timer(Light *handle, const sc_eventid evid) {
sc_timer_unset(&timer_service, evid);
}

The most important parts are commented in the main.c file. These are the bullet points:

  • Initialize the timer service with a dispatch function
  • Initialize the state machines and set the sub machines
  • Subscribe observers to get notified whenever the brightness of a light changes
  • Enter the controller machine; from this point on the state machine is ready to react on incoming event
  • Hook up the console input to raise events on the controller machine

You can run the application with Run As -> Local C/C++ Application on the main.c file. A console should open and ask you for input like in the screenshot below.

Light switch console application

Get this example

The complete project — statechart models, sources and build files — lives in the itemis CREATE examples repository. Inside itemis CREATE for Eclipse you can import it directly with the example wizard.