clfsm is a powerful scheduler for arrangements of compiled C++ (or Objective-C++)
logic-labelled finite state machines (LLFSMs). It schedules.machine FSM bundles
for deterministic low-latency execution.
libclfsm is the runtime library that provides the FSM execution engine, state management, transitions, and scheduling infrastructure.
- libclfsm - C++ LLFSM runtime library
- Static library:
libclfsm.a - Shared library:
libclfsm.dylib(macOS) /libclfsm.so(Linux) - Headers for embedding FSMs in your applications
- Static library:
- clfsm - FSM scheduler
- Compiles
.machinedirectories into executable code - Executes and schedules finite state machines
- Man page:
clfsm(1)
- Compiles
- Unit tests - Test suite for libclfsm
- Uses Google Test (GTest) framework
- Tests FSM functionality and machine management
You need a POSIX system, a C/C++ compiler, cmake, and a build system supported by cmake, such as Ninja or gmake.
You will also need to build and install the following packages:
Follow the links to the above packages for building instructions.
To build, you simply create a build directory (e.g. build)
using cmake, then use your build system to
build and install. Here is an example using
Ninja:
mkdir build
cd build
cmake -G Ninja ..
ninja
ninja installIf you require root permissions, run ninja install as root,
e.g. by using sudo:
sudo ninja install# Build only the library (skip clfsm executable)
cmake .. -DBUILD_CLFSM=OFF
# Build only the compiler (skip libclfsm)
cmake .. -DBUILD_LIBCLFSM=OFFThe whiteboard (gusimplewhiteboard) is automatically detected at build time using find_package(gusimplewhiteboard QUIET):
- Found: Builds with
WITH_WHITEBOARDdefined, enabling whiteboard integration for inter-process communication - Not Found: Builds without whiteboard support using the standard FSM vector factory
Both modes are fully functional and the code uses conditional compilation (#ifdef WITH_WHITEBOARD) to support either configuration.
By default, the CLReflect API is disabled. To enable it:
cmake .. -DLIBCLFSM_WITH_REFLECT=ONThis requires the CLReflect source files to be present in the repository and enables runtime introspection of state machines, including querying machine structure, states, transitions, and variables.
Availability: macOS (system), Linux (requires installation), Windows (not supported)
libdispatch support enables advanced time-triggered operations and parallel processing capabilities. By default, the system attempts to enable libdispatch support automatically.
When ENABLED (default on macOS):
- Time-triggered FSM operations use high-precision dispatch timers
- Machine compilation uses parallel processing for faster builds
- Asynchronous FSM execution with dispatch queues
- Enhanced test functionality with dispatch-based timing
When DISABLED:
- Time-triggered operations fall back to standard library timers
- Sequential machine compilation (slower but more compatible)
- Synchronous FSM execution
- Basic test timing functionality
To disable libdispatch support:
cmake .. -DWITH_LIBDISPATCH=OFFNote: On platforms without libdispatch (e.g. Linux without Swift or libdispatch installed), the system automatically disables libdispatch support and uses fallback implementations. The FSM functionality remains fully compatible in both modes.
Unit tests are disabled by default. To build and run tests:
cmake .. -DBUILD_TESTS=ON
ninja
ctest --output-on-failureRequirements: Tests use Google Test (GTest). If GTest is not found on the system, it will be automatically downloaded via FetchContent from GitHub.
Test Coverage: The test suite includes:
- FSM vector factory tests
- Machine creation and management tests
Tests properly handle optional features using conditional compilation, skipping whiteboard and reflection tests when those features are disabled.
Runtime machine compilation is enabled by default. This allows clfsm to automatically compile .machine directories at runtime when the compiled .so file is not found.
To disable machine compilation (for environments where runtime compilation is not needed or desired):
cmake .. -DCOMPILE_MACHINES=OFF
ninjaWith COMPILE_MACHINES=ON (default):
- clfsm can compile
.machinedirectories on-the-fly - Supports
-c,-f,-I,-L,-lcompiler/linker flags - Automatically compiles missing
.sofiles
With COMPILE_MACHINES=OFF:
- clfsm can only load precompiled
.sofiles - Compilation flags are not available
- Smaller binary with fewer dependencies
- Suitable for deployment environments with precompiled machines
ninja installBy default, this installs to /usr/local:
- Libraries →
/usr/local/lib/ - Headers →
/usr/local/include/gufsm/ - Executable →
/usr/local/bin/clfsm - Man page →
/usr/local/share/man/man1/clfsm.1 - CMake config →
/usr/local/lib/cmake/clfsm/
To install to a different location:
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
ninja installTo create a self-contained snapshot that includes all dependencies (gu_util) and can be built independently of the GUNao infrastructure:
cd build
ninja exportThis creates build/export/ containing:
- libclfsm/ - FSM runtime library (source files, headers, including resolved gu_util.cpp/h)
- clfsm/ - FSM compiler (source files, headers, man page)
- CMakeLists.txt - Top-level build configuration
- README.md - Build instructions for the export (conditionally generated)
- LICENSE - Licence file
The export is truly minimal with:
- NO symlinks (all resolved to actual files)
- NO gufsm/, Common/, or other GUNao infrastructure directories
- NO examples or unnecessary files
- Only files required to build and run libclfsm and clfsm
- All dependencies embedded in their target directories
The exported directory can be built completely independently of the GUNao repository.
The export can be customised using CMake options:
Include unit tests in export:
cmake .. -DBUILD_TESTS=ON
ninja exportInclude development sections in README (Prerequisites, Build Options, ROS, bmake):
cmake .. -DEXPORT_DEVEL=ON
ninja exportCreate end-user export (minimal README without development information):
cmake .. -DEXPORT_DEVEL=OFF
ninja exportControl machine compilation support:
# Enable runtime compilation (default)
cmake .. -DCOMPILE_MACHINES=ON
ninja export
# Disable runtime compilation (precompiled machines only)
cmake .. -DCOMPILE_MACHINES=OFF
ninja exportCombine options:
# End-user export with tests, no compilation support, no dev sections
cmake .. -DBUILD_TESTS=ON -DCOMPILE_MACHINES=OFF -DEXPORT_DEVEL=OFF
ninja exportThe exported README.md is automatically generated with only the relevant sections based on these build options.
This build includes whiteboard integration for inter-process communication via gusimplewhiteboard.
The whiteboard provides a publish-subscribe blackboard architecture for communication between FSMs and other system components.
This build does not include whiteboard integration. FSMs run standalone without inter-process communication via whiteboard.
This build includes the CLReflect API for runtime introspection of state machines.
The reflection API allows querying machine structure, states, transitions, and variables at runtime.
This build does not include the reflection API. Runtime introspection is not available.
This build supports runtime compilation of .machine directories.
clfsm can automatically compile .machine source directories into shared libraries at runtime when the compiled .so file is not found.
This build does not support runtime compilation of .machine directories.
clfsm can only load precompiled .so files. You must compile your machines separately before running them.
clfsm [-c][-d][-fPIC]{-I includedir}{-L linkdir}{-l lib}[-n][-P <machine>][-s][-S <machine>][-v] {machines}
-c
– compile only flag, don't execute machine.
-fPIC
– specify to generate Position Independent Code, required by some machines.
-I includedir
– include directory to add to the search path during compilation.
-L linkdir
– library directory to add to the search path during linking.
-l lib
– library to link against
-n
– restart CLFSM after SIGABRT or SIGIOT signals.
-P
- Preload a machine. Load a machine and place it in memory, but do not add it to the schedule. This flag speeds up loading machines that are dynamically loaded and unloaded using the dynamic loading function in CLMacros (loadAndAddMachine, scheduleMachine, unscheduleMachineAtIndex, unloadMachineAtIndex). This is also advantageous when using parameterised machines that are dynamically loaded (when using call_machine_at_path for example).
-s
– turn on debugging output on suspend, resume, and restart of machines.
-S
- Loads a machine suspended. This flag effectively sets the current state of the machine to the machine's suspend state. All machines scheduled with -S are placed at the end of the schedule in the order that they are in when invoking clfsm. Note, that the entire ringlet of the suspend state executes the first time the machine runs, including the OnEntry action. This flag is necessary when using parameterised machines that are not dynamically loaded and which should exist in the schedule waiting to be called (when using call_static_machine_at for example).
-v
– Verbose: output MachineID, State, and name of machine for every state change
-d
– print additional debug information (requires -v switch to be set as well).
machines
– list of finite state machine bundles (with or without the .machine extension) to compile and run.
clfsm [-d][-i idlesleep][-n][-P <machine>][-s][-S <machine>][-t][-v] {machines}
-n
– restart CLFSM after SIGABRT or SIGIOT signals.
-P
- Preload a machine. Load a machine and place it in memory, but do not add it to the schedule. This flag speeds up loading machines that are dynamically loaded and unloaded using the dynamic loading function in CLMacros (loadAndAddMachine, scheduleMachine, unscheduleMachineAtIndex, unloadMachineAtIndex). This is also advantageous when using parameterised machines that are dynamically loaded (when using call_machine_at_path for example).
-s
– turn on debugging output on suspend, resume, and restart of machines.
-S
- Loads a machine suspended. This flag effectively sets the current state of the machine to the machine's suspend state. All machines scheduled with -S are placed at the end of the schedule in the order that they are in when invoking clfsm. Note, that the entire ringlet of the suspend state executes the first time the machine runs, including the OnEntry action. This flag is necessary when using parameterised machines that are not dynamically loaded and which should exist in the schedule waiting to be called (when using call_static_machine_at for example).
-v
– Verbose: output MachineID, State, and name of machine for every state change
-d
– print additional debug information (requires -v switch to be set as well).
machines
– list of finite state machine bundles (with or without the .machine extension) to compile and run.
For complete usage information, see the man page:
man clfsmA .machine is a directory bundle containing:
States- List of state names (one per line)State_*_OnEntry.mm- Code executed when entering a stateState_*_OnExit.mm- Code executed when leaving a stateState_*_Internal.mm- Code executed while in a stateState_*_Transition_N.expr- Guard conditions for transitions*_Variables.h- State and machine variable declarations*.h/*.mm- Machine class implementationLayout.plist- Visual layout metadata (optional)
The following example shows how to run clfsm, its output, and the backtrace produced when aborting the program by pressing ^C (Control-C):
$ cd examples
$ clfsm -I.. PingPongCLFSM
Ping 62
Pong 63
Ping 63
^CCaught signal 2: aborting ...
000: 0 clfsm 0x000000010ec11b39 _ZL15print_backtracei + 73
001: 1 clfsm 0x000000010ec115f3 _ZL24backtrace_signal_handleri + 19
002: 2 libsystem_platform.dylib 0x00007fff8c949f1a _sigtramp + 26
003: 3 ??? 0x00007fff50ff9120 0x0 + 140734552314144
004: 4 libsystem_c.dylib 0x00007fff91b1eb53 abort + 129
005: 5 clfsm 0x000000010ec11a6a _ZL23aborting_signal_handleri + 794
006: 6 libsystem_platform.dylib 0x00007fff8c949f1a _sigtramp + 26
007: 7 ??? 0x00007fff50ff98a0 0x0 + 140734552316064
008: 8 libsystem_c.dylib 0x00007fff91b3fe50 usleep + 54
009: 9 clfsm 0x000000010ec06697 protected_usleep + 87
010: 10 libclfsm.dylib 0x000000010ec2a56d _ZN3FSM18CLFSMVectorFactory7executeEPFbPvPNS_18SuspensibleMachineEiES1_ + 61
011: 11 clfsm 0x000000010ec11009 main + 6249
012: 12 libdyld.dylib 0x00007fff8d2115c9 start + 1
Log file written to '/tmp/clfsm-tJD.log'
Abort
Exit 134
$
In the above backtrace, the interesting line is 008:
this shows that the scheduler was currently in the sleep state between states
(protected_usleep in trace line 009: called from the state machine vector factory's execute() function).
Important: This build requires precompiled machines.
The following example shows how to run clfsm with a precompiled machine:
$ cd examples
$ ls PingPongCLFSM.machine/Darwin-x86_64/PingPongCLFSM.so
PingPongCLFSM.machine/Darwin-x86_64/PingPongCLFSM.so
$ clfsm PingPongCLFSM
Ping 62
Pong 63
Ping 63
^CCaught signal 2: aborting ...
...
The platform-specific compiled .so file must exist before running clfsm. The platform directory is determined by uname -s and uname -m (e.g., Darwin-x86_64, Linux-arm64).
Running a single machine:
clfsm MyMachine.machineRunning multiple machines:
clfsm Machine1.machine Machine2 Machine3.machinePreloading machines for dynamic loading:
# Preload machines into memory without scheduling
clfsm -P DynamicMachine1 -P DynamicMachine2 MainMachineLoading machines in suspended state:
# Machine starts in suspend state, waiting to be called
clfsm -S ParameterisedMachine MainMachineVerbose output with debugging:
# Show state changes and debug information
clfsm -v -d MyMachine.machineCompile only (don't execute):
clfsm -c MyMachine.machineAdd include directories:
clfsm -I/path/to/headers -I/another/path MyMachine.machineAdd library search paths:
clfsm -L/path/to/libs -lmylib MyMachine.machineGenerate position-independent code:
clfsm -fPIC MyMachine.machineCombine options:
clfsm -c -fPIC -I/usr/local/include -L/usr/local/lib -lmylib MyMachine.machineTo run the unit tests:
cd build
ctest --output-on-failureTo run tests with verbose output:
ctest --output-on-failure --verboseTo run a specific test:
./libclfsm/libclfsmTests/libclfsm_testsAfter installation:
find_package(clfsm REQUIRED)
target_link_libraries(your_target PRIVATE clfsm::clfsm)This automatically adds the necessary include paths and links the library.
If not using CMake:
# Compile with headers
clang++ -std=c++11 -I/usr/local/include/gufsm your_code.cc -c
# Link with libclfsm
clang++ your_code.o -L/usr/local/lib -lclfsm -o your_programEnsure the shared library can be found at runtime:
# macOS
export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH
# Linux
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATHOr link with rpath:
clang++ -L/usr/local/lib -Wl,-rpath,/usr/local/lib -lclfsm your_code.ccThis build includes whiteboard support for inter-process communication.
#include "guwhiteboardgetter.h"
// Get a value from the whiteboard
int value = get_wb_value(kMyType);#include "guwhiteboardposter.h"
// Post a value to the whiteboard
post_wb_value(kMyType, myValue);Machines can read and write whiteboard variables for communication with other processes and FSMs.
This build includes the reflection API for runtime introspection.
#include "CLReflectAPI.h"
// Get meta machine from a CLMachine instance
refl_metaMachine meta = Create_MetaMachine();
refl_setMachine(meta, machine_instance, nullptr);
// Query machine properties
const char* machine_name = refl_getMachineName(meta);
int num_states = refl_getNumberOfStates(meta);
// Iterate over states
for (int i = 0; i < num_states; i++) {
refl_metaState state = refl_getStateAtIndex(meta, i);
const char* state_name = refl_getStateName(state);
// ... query state properties
}The reflection API enables:
- Runtime machine introspection
- Dynamic state queries
- Transition analysis
- Variable access
- Model checking and verification
The clfsm scheduler provides deterministic, low-latency execution:
- State-based execution: Each machine is in exactly one state at a time
- Transition evaluation: Guard conditions determine state changes
- Action execution: OnEntry, Internal, and OnExit actions execute at appropriate times
- Suspendable machines: Machines can suspend and resume execution
- Deterministic scheduling: Multiple machines execute in a coordinated schedule
Core FSM Classes:
FSMachine- Base finite state machine classFSMState- Individual state with entry/exit/internal actionsFSMTransition- Transition with guard conditionsFSMSuspensibleMachine- Machines that can suspend/resume
C-Like Wrapper Layer:
CLMachine,CLState,CLTransition- Simplified interfaceCLFSMVectorFactory- Factory for creating and managing machines
CLFSMWBVectorFactory- Whiteboard-enabled factory
If you see dyld: Library not loaded: @rpath/libclfsm.dylib or similar:
Solution 1: Set library path environment variable
# macOS
export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH
# Linux
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATHSolution 2: Reinstall with proper rpath
cmake .. -DCMAKE_INSTALL_RPATH=/usr/local/lib
ninja installMachine compilation failed:
Check that:
- The
.machinedirectory contains all required source files - Include paths are correct (use
-Iflag) - Required libraries are available (use
-Land-lflags) - You have write permissions to the
.machinedirectory (for creating the platform-specific output directory)
Common fixes:
# Add include paths
clfsm -I/usr/local/include/gufsm -I/path/to/headers MyMachine.machine
# Add library paths
clfsm -L/usr/local/lib -lmylib MyMachine.machineMachine not found or failed to load:
This build requires precompiled .so files. Check that:
- The platform-specific directory exists (e.g.,
MyMachine.machine/Darwin-x86_64/) - The
.sofile exists in that directory - The
.sofile was compiled for the correct platform - Required libraries are available at runtime
Common fixes:
# Check for compiled output
ls MyMachine.machine/*/MyMachine.so
# Verify platform
uname -sm
# Check library dependencies (macOS)
otool -L MyMachine.machine/Darwin-x86_64/MyMachine.so
# Check library dependencies (Linux)
ldd MyMachine.machine/Linux-x86_64/MyMachine.soBuild fails with libdispatch errors on Linux:
Most Linux distributions don't include libdispatch by default. To build without libdispatch:
cmake .. -DWITH_LIBDISPATCH=OFFTime-triggered operations not working as expected:
If timeout() functions in FSMs aren't behaving correctly:
- Check if libdispatch is available:
ls /usr/include/dispatch/(Linux) or built-in on macOS - Verify build configuration shows "Building with libdispatch support"
- For debugging, rebuild with libdispatch disabled to use fallback timing
Parallel compilation too slow or hanging:
If machine compilation is slower than expected:
# Force sequential compilation
cmake .. -DWITH_LIBDISPATCH=OFFVerify headers are installed:
ls /usr/local/include/gufsm/If missing, reinstall:
cd build
ninja installSupported platforms:
- macOS (Darwin) - x86_64, arm64
- Linux - x86_64, arm64
.
├── CMakeLists.txt # Top-level build configuration
├── project.cmake # Build options
├── README.md # This file
├── LICENSE # Licence information
├── libclfsm/ # Runtime library
│ ├── CMakeLists.txt
│ ├── project.cmake
│ ├── clfsmConfig.cmake.in
│ ├── *.cc / *.h
│ └── libclfsmTests/ # Unit tests
└── clfsm/ # Compiler and scheduler
├── CMakeLists.txt
├── project.cmake
├── clfsm.1 # Man page
└── *.cc / *.h
.
├── CMakeLists.txt # Top-level build configuration
├── project.cmake # Build options
├── README.md # This file
├── LICENSE # Licence information
├── libclfsm/ # Runtime library
│ ├── CMakeLists.txt
│ ├── project.cmake
│ ├── clfsmConfig.cmake.in
│ └── *.cc / *.h
└── clfsm/ # Compiler and scheduler
├── CMakeLists.txt
├── project.cmake
├── clfsm.1 # Man page
└── *.cc / *.h
All finite state machines currently need to be in the same directory as the current working directory clfsm is run from. A workaround is to use a symbolic link that points to the actual location of the corresponding .machine bundle directory.
The clfsm tool requires libclfsm to be compiled and installed in $PREFIX
(typically /usr/local) first.
Then, to build the clfsm tool for the host use bmake host.
To install under $PREFIX, use sudo bmake install
To build the tool for the default cross-compilation target, simply use
bmake robot.
Alternatively, to build the tool for a different target, use
bmake target TARGET=targetname.
In order to compile for ROS, you first need to
export the sources into catkin_ws for ROS using bmake catkin.
Again, make sure that you have exported the libclfsm catkin sources first!
To build the tool for ROS, make sure you have copied the
sources into catkin_ws as described above, then simply use
catkin_make.
To permanently install the tool, use catkin_make install.
See LICENSE file for licensing information.
- Man page:
man clfsm - CMake integration:
/usr/local/lib/cmake/clfsm/clfsmConfig.cmake - Header files:
/usr/local/include/gufsm/