Skip to content

VFSM C++ Library Reference

VFSM library Programmer’s Guide

The specification of state machines in the StateWORKS Studio is done in a virtual environment. For a programmer who develops an RTDB-based application, this issue is transparent and, in principle, irrelevant. The RTDB realizes the interface between the world of real signals and the virtual environment. Thus, a programmer has no points of contact with the VFSM Executor and concentrates on I/O Handlers and output functions triggered by the OFUN object.

In contrast, the VFSM library contains only the FSM Executor, which can be built into the application using a limited number of access methods, essentially a few variants of the Execute() function. In such a development environment, the important task would be the conversion of real input signals into virtual inputs (Input Names) and the triggering of appropriate functions in response to virtual outputs (Output Names). This task is very error-prone, as changes in the specification can change the virtual values. To reduce the burden of these real/virtual and virtual/real conversions, the StateWORKS Studio generates a file MyStateMachine_out.h that contains initialized C++ constants that hold those conversions.

Those constants are only relevant to specifications that do not use complement values. For a specification with complement values, a programmer has to realize the input real/virtual conversions by hand. For that purpose, he should use the methods of the class CSet provided in the VFSM library package. Of course, a mixed approach is possible, where state machines without complement values use the provided constants. Frankly speaking, we are not too enthusiastic about that approach. This task is complex and doubly error-prone, and we suggest switching to the RTDB for any such applications.

A development system that uses ready-made libraries must have tools that allow (logical) debugging, as we are not able to step into the code of VFSM Executor functions. RTDB-based applications have very comfortable monitors to supervise all objects in the real-time database. When building an application with the VFSM library, we do not have these possibilities, which are based on the TCP/IP Server functionality of the RTDB. The trace feature built into the VFSM Executor compensates for the lack of TCP/IP. A trace file contains state and virtual input changes of all state machines in the system. This is a truly powerful method of debugging state machines when using the VFSM library.

1.1 An application with a single state machine

Section titled “1.1 An application with a single state machine”

Using the VFSM library in an application is a relatively simple task. On the one hand, we have to create the state machine and call it whenever an input changes. On the other hand, we have to write output functions triggered by the state machine actions.

We illustrate the use of VFSM library functions with an example, which is a coded version of the state machine OnOff from our Sample directory (see the User’s Guide). The program is a simple console application whose only aim is to show the use of VFSM library functions.

The state machine OnOff is created in the following code line:

CVfsm OnOff("onoff",
stConfigPath,
&aOnOff_Action[0],
ONOFFNUMBFUNC,
(std::vector<pSet> *) &ONOI,
pOwner);

where:

  • "onoff" is a string, the name of the state machine
  • stConfigPath is the path of the directory Conf containing the specification files
  • &aOnOff_Action[0] is the address of the table of pointers to output functions.
  • ONOFFNUMBFUNC is the number of output functions
  • (std::vector<pSet> *) &ONOI is the address of the set of pointers to sets of input names
  • pOwner is a pointer to the owner (context) object of the state machine; it is passed to every output function

The 3rd, 4th and 5th parameters are declared in the file onoff_out.h (to be found in the directory Conf). The parameter pOwner is supplied by the application.

When the state machine is created, the code has to contain the function GetSpecInfo(), which loads the files onoff.str, defining the behavior of the state machine, and onoff.iod, which contains names used in the specification. If any of those files cannot be opened, the program should terminate. This is the purpose of the line:

if (!OnOff.GetSpecInfo())

At this moment, we may also decide to switch on tracing, which happens in the line:

bool bTrace = OnOff.Trace(true);

In that case, we have accepted the default name (MyTrace.txt) of the trace file. Tracing can be disabled or enabled at any time by calling the function Trace with the parameter false or true. We may also create a new trace file at any time by giving the file a new name, but we should have only one trace file open at any time (the trace file is shared by all state machines of the application); therefore, before creating a new trace file, the old one has to be closed.

The state machine should be called if an input changes. We assume that inputs may have only those values used in the specification. The state machine is then called using the function Execute(). The example OnOff uses 3 input types: a command (CMD), a timer (TI), and a digital input (DI). On receiving an input of a given type, we call the following functions accordingly:

OnOff.Execute(aONOI_MyCmd[iInput]);
OnOff.Execute(aONOI_Di[0]);

where the parameter is the virtual input taken from the conversion array defined in the onoff_out file.

Alternatively, we might have used:

OnOff.Execute(aONOI_MyCmd[iInput], ONOI_MyCmd);
OnOff.Execute(aONOI_Di[0], ONOI_Di);

but this is, in principle, redundant, as the function Execute will find the proper remove sets. It makes sense if time considerations play a role, as this variant of the function Execute is faster. The alternative form of the function Execute must be used if the specification uses complement values, as StateWORKS Studio does not provide correct conversion arrays in such a case.

The call to the function Execute when the timer elapses is made with a direct value (ONOI_Timer_OVER) taken from the file onoff.h (see the ThreadTimer). This results from the implementation of the timer in this example, which is done in the TimerThread. If we want to make everything work as in the RTDB, we should define a true Input corresponding to the control value OVER and translate it into the virtual value using the conversion array aONOI_Timer[iInput].

The output functions triggered by the Executor are declared in the file onoff_out.h. Each has the form int Function(int *pOwner), where pOwner is the pointer given to the constructor. The return value is 0 if the function has nothing to feed back to the state machine, or an Input Name to be inserted into the VI (a negative value removes the input). Our task is to implement the functions, and we have done so in the file onoff_out.cpp. The implementation is primitive; the communication between the output functions and the TimerThread is done via global variables. The aim of this example is not to teach how to realize a multitasking system but to demonstrate the use of the VFSM library functions. The functions do not do anything useful: in principle, they write to the console that they have been called.

1.2 An application with a system of state machines

Section titled “1.2 An application with a system of state machines”

If the application contains several (cooperating) state machines, the task becomes more complex. The programming task is similar: we have to create the state machines and call the Execute function if an input changes. Typically, we would create a separate thread for each state machine and exchange messages among the threads. The programming task becomes essentially more complex than that for a single state machine.

The class CVfsm contains the VFSM Executor. The Executor is to be used when programming applications that use state machine specifications generated by StateWORKS Studio. The actual VFSM Executor methods are of no use to an application programmer. Their private access specifier makes them irrelevant. Therefore, we confine ourselves to discussing in this guide only the public methods of the CVfsm class, i.e., methods that could be used when writing the application.

FunctionDescription
CVfsmConstructor, creates the state machine
~CVfsmDestructor, destroys the state machine
GetSpecInfoReads *.str and *.iod files of a state machine
ExecutePerforms state actions and transitions
GetStateReturns the number of the present state
DisplayStateReturns a string, the state name
DisplayVIReturns a string, the virtual input (VI) content
TraceSwitches tracing off or on
GetConfPathReturns the configuration path
SetConfPathSets the configuration path
CVfsm(const std::string stVfsmName,
const std::string stConfPath,
const p1Func *aActions,
unsigned short iActNum,
std::vector<pSet> *InputObjectName,
int *pOwner)

Parameters
stVfsmName — The name of the state machine.
stConfPath — The full path to the Conf directory containing the specification files.
aActions — The address of the array of output function (action) pointers.
iActNum — The number of output functions.
InputObjectName — The address of the set of pointers to sets of input names.
pOwner — A pointer to the owner (context) object of the state machine. It is passed unchanged to every output function.

Return value
none

Remarks
The parameters aActions, iActNum and InputObjectName are declared in the file MyStateMachine_out.h. pOwner is supplied by the application.

An output function (action) has the type p1Func, i.e. int (*)(int *pOwner). It receives pOwner as its argument. Its return value is either 0 (nothing to do) or an Input Name to be inserted into the VI (a negative value removes the input).

~CVfsm()
bool GetSpecInfo()

Return value
true if config files (*.str, *.iod) are found, else false.

Remarks
The function reads the specification files and builds the tables of input, output and state names. It must be called before the first call of Execute.

The application should terminate if this function fails.

bool Execute(const short &iEvent)
bool Execute(const short &iEvent, const CSet &Remove)

Parameters
iEvent — Input Name (number) as defined in the specification. A negative value removes the Input Name instead of inserting it.
Remove — A set of Input Names to be removed from the VI.

Return value
false if the VI could not be updated correctly and consequently the executor has not been called; otherwise, true.

Remarks
The iEvent value should be taken from the file MyStateMachine_out.h.

The second variant of the function should be used only if the file MyStateMachine_out.h does not contain an appropriate remove set (this happens only with specifications that use complement values).

bool Execute(CSet &Events)
bool Execute(CSet &Events, const CSet &Remove)

Parameters
Events — A set of Input Names as defined in the specification.
Remove — A set of Input Names to be removed from the VI.

Return value
false if the VI could not be updated correctly and consequently the executor has not been called; otherwise, true.

Remarks
The Event value should be taken from the file MyStateMachine_out.h.

The second variant of the function should be used only if the file MyStateMachine_out.h does not contain an appropriate remove set (this happens only with specifications that use complement values).

bool Execute(const short *piEvent)

Parameters
piEvent — A pointer to the constant array that converts the Slave’s states into the virtual Input Names as defined in the specification. The element used is the one indexed by the number of the present state.

Return value
false if the VI could not be updated correctly and consequently the executor has not been called; otherwise, true.

Remarks
The piEvent value should be taken from the file MyStateMachine_out.h.

unsigned short GetState()

Return value
The number of the present state.

std::string DisplayState()

Return value
A string, the state name in the form illustrated by the example:
OffBusy (3)
where the state name is followed by the corresponding state number in brackets.

Remarks
Used in the trace file.

std::string DisplayVI()

Return value
A string, the content of the VI in the form illustrated by the example:
{ always (1), CmdOn (3), Timer_OVER (4) }
where each entry is displayed as a name followed by the corresponding input name number (as used in the specification) in brackets.

Remarks
Used in the trace file.

bool Trace(const bool bTraceEnable, std::string stFileName = "MyTrace.txt")

Parameters
bTraceEnable — true opens the trace file and starts tracing. false closes the trace file.
stFileName — The name of the trace file, with the default name MyTrace.txt, created in the Conf directory.

Return value
false if the trace file could not be opened; otherwise, true.

Remarks
The trace function can be used at any time in the application.

The trace file is shared by all CVfsm objects of the application: tracing started or stopped through one state machine applies to all of them.

std::string GetConfPath()

Return value
The path of the configuration directory.

void SetConfPath(const std::string &st)

Parameters
st — The new path of the configuration directory.

Return value
none

The class CSet is needed for the class CVfsm. For an application programmer, the class CSet is less interesting, as, in principle, he does not need it. Anyway, in some cases, he might construct sets, and in that case, he needs some member functions of that class. The class CSet is relatively large, and we limit our discussion to a few methods that may be useful in application programming.

The types used are SBTYPE (unsigned short) and SET (SBTYPE *).

FunctionDescription
CSetConstructor, creates a set
CopyCopies a set to a destination set
InsertInserts an element or a set of elements into a set; before inserting it, it may remove a set of elements
RemoveRemoves an element from a set
ClearRemoves all elements from a set
UnionsMerges two sets by inserting into a destination set all elements of the source set that do not exist in the destination set.
CSet()
CSet(SBTYPE bits)
CSet(SBTYPE bits, SET arr, SBTYPE size)

Parameters
bits — Number of elements in the set.
arr — Array of elements (unsigned short) to be inserted into the set.
size — Size of the array.

Return value
none

Remarks
The first constructor creates an empty set with a default size equal to 16 (the size of unsigned short).

The second constructor creates a set with a size defined by the bits parameter.

The third constructor creates a set with a size defined by the bits parameter and initializes the set with the elements contained in the array.

void Copy(CSet *set)

Parameters
set — A pointer to the set to be copied.

Return value
none

Remarks
If the capacity of the destination set is less than that of the copied set, only the elements that “fit” into the destination set are copied.

bool Insert(SBTYPE n)
bool Insert(CSet *remset, SBTYPE n)
bool Insert(CSet *remset, CSet *uniset)
bool Insert(SET arr, SBTYPE size)

Parameters
n — The element (unsigned short) to be inserted.
remset — A pointer to the set of elements to be removed before insertion.
uniset — A pointer to the set of elements to be inserted.
arr — A pointer to the array of elements to be inserted.
size — The size of the array to be inserted.

Return value
true if the function has been successful; otherwise, false.

bool Remove(SBTYPE n)

Parameters
n — The element (unsigned short) to be removed.

Return value
true if the element has been removed; otherwise, false.

void Clear()

Return value
none

Remarks
After the operation, the set is empty.

void Unions(CSet *set)

Parameters
set — The set to be merged with a destination set.

Return value
none

Remarks
Effectively, it inserts the set into the destination set. The elements of the set that do not “fit” into the destination set are ignored.

OnOff example:

A.01 main.cpp — The Application
main.cpp
// This application tests the fsm executor concept
#include <iostream>
#include <string>
#include <cstdlib>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <thread>
#include "vfsm.h"
#include "onoff_out.h"
std::atomic<bool> g_bRunning(true);
std::atomic<bool> g_bStarted(false);
void TimerThread(CVfsm *Vfsm)
{
unsigned int iTimeout = 3000;
unsigned short iInput = ONOI_Timer_OVER;
while (g_bRunning) {
if (g_bStarted) {
iTimeout = 3000;
std::this_thread::sleep_for(std::chrono::milliseconds(iTimeout));
if (g_bStarted) {
Vfsm->Execute(iInput);
g_bStarted = false;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
int main(int argc, char *argv[])
{
std::string input;
// get config path
std::string stConfigFile;
if (argc > 1)
stConfigFile = argv[1];
else {
std::cout << "Enter the full path of the project's .swd file (Conf directory): ";
std::getline(std::cin, stConfigFile);
}
std::string stConfigPath = std::filesystem::path(stConfigFile).parent_path().string();
CVfsm OnOff("onoff",
stConfigPath,
&aOnOff_Action[0],
ONOFFNUMBFUNC,
(std::vector<pSet> *) &ONOI,
NULL);
// Create the Timer thread
std::thread TimerThreadHandle(TimerThread, &OnOff);
std::cout << "Timer thread created" << std::endl;
if (!OnOff.GetSpecInfo()) {
g_bRunning = false;
TimerThreadHandle.join();
std::cout << "Terminate with Enter" << std::endl;
std::getline(std::cin, input);
return 1;
}
bool bTrace = OnOff.Trace(true);
int i;
do {
if (bTrace)
std::cout << std::endl << "State = " << OnOff.DisplayState() << std::endl;
std::cout << "Enter Cmd or DevDi\n"
<< "\t1: CmdOff 3: DeviceOff (0) 7: Trace off\n"
<< "\t2: CmdOn 4: DeviceOn (1) 8: Trace on" << std::endl;
std::getline(std::cin, input);
i = input.empty() ? 9 : std::stoi(input);
switch (i) {
case 1: // Command Stop
case 2: // Command Start
OnOff.Execute(aONOI_MyCmd[i]);
break;
case 3: // DeviceDi Off (0)
OnOff.Execute(aONOI_Di[0]);
break;
case 4: // DeviceDi On (1)
OnOff.Execute(aONOI_Di[1]);
break;
case 7:
bTrace = OnOff.Trace(false); // trace off
break;
case 8:
bTrace = OnOff.Trace(true); // trace on
break;
default:
break;
}
} while (i < 9);
g_bRunning = false;
TimerThreadHandle.join();
std::cout << std::endl << "Terminate with Enter" << std::endl;
std::getline(std::cin, input);
return 0;
}
A.02 onoff_out.cpp — The Output Functions
onoff_out.cpp
/*******************************************************************/
// User written output (action) functions
// Definition of functions declared in ..._out.h
/*******************************************************************/
// The action functions (declared in ..._out.h file) returns
// typically 0.
// The functions may return a control (virtual) value taken from
// constants declared int ..._out.h ( typically for OFUN objects)
// If the return value (taken from constants declared int ..._out.h)
// is negative this value will be removed from the virtual input
// (use for the action Cmd_Clear).
//
#include "onoff_out.h"
#include <iostream>
extern std::atomic<bool> g_bStarted;
bool bAlarm = false;
bool bOfu = false;
int OnOff_Dummy(int *pOwner)
{
return 0;
};
int OnOff_Timer_ResetStart(int *pOwner)
{
g_bStarted = true;
return 0;
};
int OnOff_Timer_Stop(int *pOwner)
{
g_bStarted = false;
return 0;
};
int OnOff_Alarm_Coming(int *pOwner)
{
bAlarm = true;
std::cout << "\tDevice switching too long - Coming\n";
return 0;
};
int OnOff_Alarm_Going(int *pOwner)
{
if (bAlarm) {
std::cout << "\tDevice switching too long - Going\n";
bAlarm = false;
}
return 0;
};
int OnOff_SwitchOff(int *pOwner)
{
std::cout << "\tSwitch off the device" << std::endl;
return 0;
};
int OnOff_SwitchOn(int *pOwner)
{
std::cout << "\tSwitch on the device" << std::endl;
return 0;
};
A.03 onoff_out.h — The declaration of output functions, conversation arrays and sets
onoff_out.h
/*******************************************************************/
// Declarations of output functions (actions) and
// definitions of sets of input names.
// The output functions have to be implemented while writing
// the applications.
// The set of input names is used by the vfsm library while
// carrying out the function Execute().
//
// The file is generated by SWStudio and must not be changed!
// If the output functions will be accessed from a C++ class
// they may be declared there as friends with appropriate pOwner
// pointer.
// In such a case the symbol __CPLUSPLUS_APP should be used
// to comment out the output functions declared here
// and to activate the corresponding "cplusplus_app.h".
// In a C application the parameter pOwner does not play any role
// and it will be ignored.
//
#ifndef ONOFF_OUT_H
#include "vfsm.h"
#include "OnOff.h"
#ifdef __CPLUSPLUS_APP
#include "cplusplus_app.h"
#else
// output functions (actions)
int OnOff_Dummy( int* pOwner );
int OnOff_Timer_ResetStart( int* pOwner );
int OnOff_Timer_Stop( int* pOwner );
int OnOff_Alarm_Coming( int* pOwner );
int OnOff_Alarm_Going( int* pOwner );
int OnOff_SwitchOff( int* pOwner );
int OnOff_SwitchOn( int* pOwner );
#endif
const p1Func aOnOff_Action[] =
{
(p1Func)OnOff_Dummy,
(p1Func)OnOff_Timer_ResetStart,
(p1Func)OnOff_Timer_Stop,
(p1Func)OnOff_Alarm_Coming,
(p1Func)OnOff_Alarm_Going,
(p1Func)OnOff_SwitchOff,
(p1Func)OnOff_SwitchOn,
};
#define ONOFFNUMBFUNC (sizeof(aOnOff_Action)/sizeof(aOnOff_Action[0]))
// Array of input names used for translation of real signals
// into virtual ones.
// Set of input names used as remove sets while actualizing
// the virtual input.
const unsigned short aONOI_MyCmd[] = {
/*0*/ 0,
/*1*/ ONOI_CmdOff,
/*2*/ ONOI_CmdOn,
};
const CSet ONOI_MyCmd( ONOI_DeviceOn+1, (SET)aONOI_MyCmd, sizeof(aONOI_MyCmd)/sizeof(aONOI_MyCmd[0]) );
const unsigned short aONOI_Timer[] = {
/*0*/ 0,
/*1*/ 0,
/*2*/ 0,
/*3*/ 0,
/*4*/ ONOI_Timer_OVER,
};
const CSet ONOI_Timer( ONOI_DeviceOn+1, (SET)aONOI_Timer, sizeof(aONOI_Timer)/sizeof(aONOI_Timer[0]) );
const unsigned short aONOI_Di[] = {
/*0*/ ONOI_DeviceOff,
/*1*/ ONOI_DeviceOn,
};
const CSet ONOI_Di( ONOI_DeviceOn+1, (SET)aONOI_Di, sizeof(aONOI_Di)/sizeof(aONOI_Di[0]) );
// Array of pointers to sets of input names.
// Set of pointers to sets of input names used while actualizing
// the virtual input.
const pSet aONOI[] = {
&ONOI_MyCmd,
&ONOI_Timer,
&ONOI_Di,
};
const std::vector<pSet> ONOI( aONOI, aONOI+sizeof(aONOI)/sizeof(aONOI[0]));
#define ONOFF_OUT_H 1
#endif