The Orocos Open Realtime Control Services Developer's Manual
| Documentation |
Open RObot COntrol Software
1.0.3
Copyright © 2002,2003,2004,2005,2006 Herman Bruyninckx, Peter Soetens
Orocos Version 1.0.3.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation, with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of this license can be found at http://www.fsf.org/copyleft/fdl.html.
Abstract
This document delves deeply into the Orocos ( Open RObot COntrol Software ) infrastructure. It targets developers which wish to extend or understand Orocos. It is complementary to the user's manuals.
Table of Contents
- 1. How to Read this Manual
- 2. Inside the Build System
- 3. Inside The Core Library
- 4. Inside The Task Infrastructure
- 5. Inside The Execution Engine
- 6. Inside The Program Script Parser
List of Figures
List of Tables
Table of Contents
This manual is for Software developers who wish to extend the Orocos framework.
The first Chapter details some aspects of the build system, for those who want full controll over footprint.
The second Chapter details the classes and concepts of the OS abstraction layer. It is useful to understand the Orocos threading mechanism and how Orocos can be ported to other platforms.
The third and fourth Chapters document the Core Library and Task Infrastructure and are the same as the one found in the Component Builder's manual.
The fifth Chapter shows some mechanisms on how to use CoreLib primitives behind the scenes in the Task Infrastructure.
The sixth Chapter details the workings of the Execution Engine and how it uses the various 'Processor' types to handle program execution and communication.
The last Chapter details on how to extend the Orocos Parser framework with user defined types.
The functionality available in Orocos is structured in a number of sub-projects; each of these is called a (Orocos) Package. A Package contains a coherent and related set of functionality, in the form of documentation and class libraries.
The packages directory is found under the orocos-rtt
directory. The orocos-rtt directory holds some general documentation
in the doc directory (like this manual) and scripts for setting
up a working packages directory.
Table of Contents
Abstract
This document explains how the packages of Orocos, the Open RObot COntrol Software project must be installed and configured.
| Warning | |
|---|---|
This section is not intended for classical users. Read the 'Getting Started' manual first. |
This section groups some technical installation instructions for Orocos developers or persons who wish to control tightly what is built.
When the target is selected with configure, you can run make configure_packages to pop up the GUI configtool. You need to install this program on your system. Some things to do are :
Add/Remove a Package to/from your configuration : click on . Only packages in the repository can be added or removed. This operation does not remove a package from the repository.

Install a new Package in the repository : click on , this allows you to select an
.epkfile from your hard disk, which will be added to your packages repository.
In between Orocos versions, configuration options may be added
or removed to reflect feature additions or removal. In that
case, your current orocos.ecc file will
not be properly parsed by the tools. To solve this, you need
to run
make upgrade_packages
such that your old configuration is imported in the new one. After an upgrade, your selected template, added packages and so on will remain as before.
| Note | |
|---|---|
You can manually save your configuration using ecosconfig export /tmp/savefile.ecc in your old build tree, followed by make new_packages and ecosconfig import /tmp/savefile.ecc in your new build tree. ( manually invoking ecosconfig requires setting ECOS_REPOSITORY ) to your packages directory). |
This section goes into more detail how package management is done behind the scenes and is optional literature for the average user.
The above make commands are optional. Take a look at the Makefile to see what they really do. A standard eCos configuration goes as follows :
Set the environment variable :
export ECOS_REPOSITORY=/path/to/orocos-1.0.3/packages/
All The ecos tools need this variable to be able to work on the packages. The make targets did this automatically, but if you want to invoke the tools yourself, you need to set it once.
Install any new .epk packages using
ecosadmin.tcl add filename.epkin the packages directory.
This only adds a package to the repository, it does not mean it is automatically compiled ! Adding a package to a configuration in the next steps will assure that it is compiled.
Go to the eCos build directory. In Orocos this is :
orocos-1.0.3/build/packagesBut technically, it can be any directory where you create an orocos.ecc configuration file.
Start a new build using the
ecosconfig new gnulinux corelib-oscommand. Where
gnulinuxorlxrtis the target name andcorelib-osis the template name. A template selects a number of packages which belong together. You are allowed to put your own template inorocos-1.0.3/packages/templates. All this information (with default options) is written in theorocos.eccfile![[Warning]](../../../../../stable/documentation/rtt/v1.0.x/doc-xml/images/icons/warning.png)
Warning This will overwrite any previous orocos.ecc configuration file in that directory !
Add all detected support packages. The make target new_packages does this by running :
for i in $(find $ECOS_REPOSITORY/support -name "*.cdl"); do \ ecosconfig add support_$(basename $i .cdl); doneThey will all be added to the orocos.ecc file. Fortunately, you can also add the support (or any other) packages in the graphical configtool menu->Build->Packages.
Select additional packages for your build with the graphical configtool or with
ecosconfig add package_nameConfigtool will give you a list of all available packages in the repository if you go to menu->Build->Packages and
ecosconfig listdoes the same. This will add the package and its options to the orocos.ecc file (which contains configuration info of each selected package). Now the packages will show up when you run configtool or run ecosconfig check.
Configure the selected packages with the graphical program
configtool orocos.eccSave and exit.
Create the makefiles using
ecosconfig treeThe build tree is setup now. You need to run this command each time you change the configuration in the above step !
Compile using
makeThe results (headers and libtarget.a) are in the install directory.
This section will do its best to help you through the Subversion ( svn ) installation process. Installing Orocos from SVN is optional and requires some familiarity with the tools we use. It is only advised for Orocos developers.
The commands below show how to obtain the orocos-trunk Package Tree from the Subversion tree (read-only)
svn co http://svn.mech.kuleuven.be/repos/orocos/orocos-trunk cd orocos-trunk
So, make sure you have the Subversion program installed, i.e., the command "svn" works on your system. The repository is web-browsable through a ViewCVS frontend .
The next step requires that you use
automake
version 1.9 and
autconf
version 2.54 or later. You can check your
versions with the --version option:
$ automake --version automake (GNU automake) 1.9.5 ... $ autoconf --version autoconf (GNU Autoconf) 2.54
| Note | |
|---|---|
You need these programs if you intend to modify the makefiles |
When you get something similar, proceed with calling the
./autogen.sh command in the base directory
, in order to initialize the autoconf
and automake files:
./autogen.sh
The configure script will detect the installed software on your system and generate the appropriate scripts.
The next steps are exactly the same as when you install the system starting from a so-called “epk” package, as explained in the next section. Of course, you don't have to apply the ecosadmin.tcl command when installing from SVN.
Abstract
This document explains the design and implementation of the Core Library of Orocos, the Open RObot COntrol Software project. The CoreLib provides infrastructural support for the functional and application components of the Orocos framework.
A Priority inversion is the term used to indicate a scheduling situation in which a high priority thread is blocked on a resource which is held by a low priority thread, while a medium priority thread is running, preventing the low priority thread to free the resource for the high priority thread.
The result is an inverted priority because a medium priority thread is running while the high priority thread should be runnen, hence, the medium priority thread has, in practice, a higher priority than the high priority thread.
There are roughly said two solution to this problem. 1. Do not block on resources from high priority threads. 2. Use priority inheritance, where a thread gets the priority of the highest priority thread being blocked on a resource it holds. Once it releases the resource, its priority goes back to normal and the high priority thread can resume.
In essence, Orocos does not know of priority inversions and does not know if the underlying Operating System properly solves this common situation. Furthermore, it can be prooven that there are situations where priority inheritance does not work. Therefore, we try to provide as much as possible lock-free implementations of inter-thread messaging. Table 3.1, “Classes Possibly Subject to Priority Inversion” lists the know uses of Orocos which might lead to priority inversion.
Table 3.1. Classes Possibly Subject to Priority Inversion
| Class/method | Rationale |
|---|---|
| DataObjectLocked |
Uses Mutex for serialising concurrent access. Alternative Lock-free implementations are possible. |
| PeriodicActivity::start(), PeriodicActivity::stop() | Uses Mutex for serialising concurrent access. Alternative implementation are incorrect, since stop() guarantees that finalize() will be called after the last step(), hence a mutex/semaphore is used such that it will block until the step() returns and then call finalize(). |
| Logger |
Uses Mutex for serialising concurrent access. No Alternative implementation is possible, Std C++ IO must be serialised. |
Table 3.2, “Classes Not Subject to Priority Inversion” shows communication infrastructure in Orocos which is especially designed to be lock-free and which is thus not subject to priority inversions. It is our aim to shrink the former table and grow the latter in Orocos' development lifetime.
Table 3.2. Classes Not Subject to Priority Inversion
| Class/method | Rationale |
|---|---|
| DataObjectLockFree |
Uses a single writer, multiple reader Lock-free implementation. A read only returns the last written value. Used by the ControlKernel application to communicate data between Components. |
| AtomicQueue |
Uses Compare And Swap (CAS) to store object pointers in an atomic queue. Used by the Processor class to queue incoming Commands. |
| BufferLockFree |
Uses a many writers, multiple readers Lock-free CAS implementation. A read returns the oldest written value in a FIFO way. |
| ListLockFree |
Uses a many writers, multiple readers Lock-free CAS implementation of a single linked list. A special member function 'apply' must be used to manipulate the objects within the list. |
| Event::emit() |
Uses the ListLockFree above for keeping track of subscribers. Concurrent invocations of emit() will lead to concurrent execution of the subscriber's callback functions. |
| Handle::connect() and Handle::disconnect() |
(Dis)Connection of an event handle is hard real-time and lock-free because of the lock-free event implementation. |
A DataSource is the fundamental data exchange entity within Orocos. It's interface is that of an 'expression': it can be evaluate()'d, its result can be get() and an AssignableDataSource can even be set(). The DataObject types are implementations of DataSources, but many more kinds exists which are used by the Scripting engine and the task infrastructure.
The DataSourceBase interface is the most basic interface for exchanging data ( value types ) between objects. They are reference counted ('smart pointers'), such that ownership across many objects must not be managed. The DataObjectInterface implements the DataSource interface, and thus all Orocos DataObject types are DataSources.
The DataObjectInterface has multiple implementations, depending on the specific data access locking needs:
DataObject. This is the most simple DataObject implementation. The
Get()andSet()methods directly map onto the contents and can always be inlined by the compiler. It offers no thread safety, but maximum efficiency for copying data.DataObjectLocked. This is a thread safe DataObject whose
Set()andGet()methods are guarded by a single mutex. The second thread accessing this object will always block, which is not always appropriate in a realtime system.DataObjectPrioritySet. This is a more complex DataObject which gives always priority to the thread calling
Set(), which will never block. The thread accessingGet()will block if theSet()thread is accessing the contents. It is mainly used for sharing data between two kernels, running at different priorities.![[Note]](../../../../../stable/documentation/rtt/v1.0.x/doc-xml/images/icons/note.png)
Note This DataObject will only work if the
Set()thread has the highest priority. When the inverse is true, data corruption will occur. It is obvious that this DataObject can only be used if both threads have static priorities (which is the case for all threads in the Orocos framework).DataObjectPriorityGet. The inverse of
DataObjectPrioritySet. The thread accessingGet()will never block.DataObjectLockFree. This DataObject implements a non blocking reader/writer buffer which always returns the last written value to the reader. If the reader is preempted with a write and a read, the last read will return a newer value, while the first read continues to read the uncorrupted old value. The depth of this buffer must be readers+3, for the algorithm to succeed in doing every write. Apart from memory consumption, it is one of the best thread-safe DataObject implementations.
Table of Contents
Abstract
This document describes the insideds of the Orocos Task Infrastructure, which allows to design Real-Time tasks which transparantly communicate with each other.
The TaskContext uses these two classes to manage properties and programs scripts.
As was seen in ???, CoreLib Properties can be added to a task's AttributeRepository. To read and write properties from or to files, you can use the PropertyLoader class. It uses the XML Component Property Format such that it is human readable.
#include <rtt/PropertyLoader.hpp> // ... TaskContext* a_task = ... PropertyLoader ploader; ploader.configure("PropertyFile.cpf", a_task ); // ... ploader.save("PropertyFile.cpf", a_task );
Where 'configure' reads the file and configures updates the task's properties and 'save' updates the given file with the properties of the task. It is allowed to share a single file with multiple tasks or update the task's properties from multiple files. Fortunately, the TaskContext has implemented this functionality also as script methods, thus you do not need to do this manually.
A function is formed by a group of commands and methods, which can be executed by a task. The scripting language defines functions as :
export function myFun( int arg1, double arg2 )
{
// Group commands and methods
var ...
do ...
}
// repeat...
where the optional export keyword makes the function
available as a task's command ( which will fail
if one of its contained commands fail ) :
do ATask.myFun( 1, 2. )
If you omit the export keyword, then the function will not become available as a command. To use such a function, you need to execute it in the Execution Engine's Program Processor ( see below ), or call it in a program, which was parsed in the same file (see ??? ).
Functions must be parsed by the Parser, before they can be executed by the Execution Engine. It executes the Function until it finishes or it goes into error. In both cases, the Function is removed from the queue and can then safely be deleted (or re-run).
| Note | |
|---|---|
The Parser and ProgramLoader are located in the Orocos Program Parser package and not in the Task Context package. |
To directly execute any number of not exported functions in a file, or add an exported function in a TaskContext's Command API, do :
#include <rtt/ProgramLoader.hpp> TaskContext* a_task = ... ProgramLoader loader; ProgramLoader::Functions funcs; funcs = loader.loadFunction( "Functions.ops", a_task );
funcs is an STL container wich contains all functions being executed.
| Warning | |
|---|---|
Using loadFunction with functions that require arguments will
execute the functions with default initialisation of the arguments.
It is easier to |
Programs are special functions in that they can be finely controlled once loaded in the ProgramProcessor. A program can be paused, it's variables inspected and reset while it is loaded in the Processor. It provides thus more funcitonality than a mere function. A program script calling the previous function would look like :
[ ... myFun() function definition ... ]
program myBar
{
var int i = 1
var double j = 2.0
do myFun(i,j)
}As with functions, any number of programs may be listed in a file.
Orocos Programs are loaded a bit different into a TaskContext :
#include <rtt/ProgramLoader.hpp> TaskContext* a_task = ... ProgramLoader parser; loader.loadProgram( "ProgramBar.ops", a_task );
Take a look the ProgramInterface class reference for more program related functions.
Hierarchical state machines are modelled in Orocos with the StateMachine class. They are like programs in that they can call a peer task's members, but the calls are grouped in a state and only executed when the state machine is in that state. This section limits to showing how an Orocos State Description (osd) script can be loaded in a Task Context.
#include <rtt/ProgramLoader.hpp> TaskContext* a_task = ... ProgramLoader loader; loader.loadStateMachine( "StateMachineBar.osd", a_task );
Again, take a look at the StateMachine class reference for more details about state context related functions.
Table of Contents
Abstract
The 'execution engine' executes Programs, State Machines, commands and events. It can execute any number of Programs and State Machines in parallel in a single thread.
Orocos is meant for building realtime systems. You will find
many useful classes in the Orocos CoreLib to build them, but
they would only act as a noninteractive whole. The Execution
package allows a user to configure a system, execute
user-defined programs on that system and interactively accept
commands. An ExecutionEngine contains a
series of Processors which loads state
machine and program definitions and executes them. Execution of
the programs and state machines will happen in real-time. In
addition, it processes incoming commands and events in
real-time as well. The execution engine thus exists of four
processors : ProgramProcessor,
StateMachineProcessor,
CommandProcessor and the
EventProcessor. The Execution Engine
needs to run in a periodic (preferably) or non periodic corelib
task in order to execute these processors.
This document bundles the execution semantics of the Execution Engine and its processors and is no mandatory literature for understanding the Orocos task execution infrastructure.
The Parser will generate Programs and StateMachines from user defined text files, which are then executed in realtime.
A TaskContext's ExecutionEngine Executes Programs, Hierarchical StateMachines and processes Commands and events.
The ExecutionEngine is the core class of this package. It executes the decision logic of the TaskContext by executing programs and state machines. Any number of these may be loaded and can be controlled separately. It allows to start, pause, step,... stop programs and state machines. No recompilation is needed in order to use new programs or state machines. Its task is to serialise (do one after the other) the execution of programs, state machines, commands and events such that they are not concurrently executing, and can thus interact thread-safely. A TaskContext is running when it's ExecutionEngine is running. The Task Context provides the interface of the control task, the Execution Engine executes the implementation.
In order to start a task context, its execution engine must be run in a periodic corelib task.
#include <rtt/TaskContext.hpp>
#include <rtt/PeriodicActivity.hpp>
RTT::TaskContext mytaskC;
RTT::PeriodicActivity ptask(5, 0.01 ); // Periodic task 100Hz.
ptask.run( mytaskC.engine() ); // run the execution engine.
ptask.start();
After it is started, it will execute all the processors in each periodic step. First the Program Processor, then the State Machine Processor, then the Command Processor and finally the Event Processor.
The FunctionGraph is a tree composed of command nodes ( See : ActionInterface class ), where each node contains one or more statements. A FunctionGraph keeps track of the start node and the node to be executed next. As such a program is executed one node at a time and the transition to the next node is done on a given boolean condition ( See : ConditionInterface class). The FunctionGraph is built by the program parser and almost never by the programmer by hand.
In order to access (start, stop, pause,...) a loaded program, use :
ProgramInterface* prog = mytaskC.engine()->programs()->getProgram("ProgName"); if (prog == 0 ) { // program not found. } int line = prog->getLineNumber(); prog->start(); // ... prog->pause(); // .. etc.
All commands are also accessible from within the interactive 'TaskBrowser'.
Programs are generated from a script. The Parser is able to convert Orocos Program Scripts to a Program which can be loaded in the Program Processor.
Loading, unloading and deleting a Program may throw
an exception. The program_load_exception
and program_unload_exception
should be try'ed and catch'ed on load and unload or deletion
of a Program in the Program Processor.
| Tip | |
|---|---|
Alternatively, you can use the ProgramLoader class as a user-friendly frontend for loading Programs and Functions in tasks. |
The StateMachine is a collection of states, linked to each other through transition definitions. It represents a state machine of the realtime system's logic and may access internal and external data for deciding on its state transitions. Every 'device' has some states or configurations in which a specific action must be taken (on entry, during or on exit) and transitions between states are defined by boundary conditions (guards) or triggered by events. Every such state is defined by the StateInterface. A state itself is defined by four programs : entry, run, handle and exit. They are called by the State Machine Processor when this state is entered, run or left. When no state transition took place, handle is called in a periodic execution step of the StateMachineProcessor. Otherwise, first the exit program of the current state is called, then the entry program and then the run program of the new state is called. The next periodic step will continue the run program, or when finished, evaluate transition conditions, and if none succeeds, handle will be called, and so on. Orocos Events may be processed during the run program. The exit program can check for interuption and clean up if necessary. After the exit program (if any), the transition program is executed (again, if any).
As transitions define when a state will be left, preconditions are used to define when a state should not be entered, and function as an extra condition which is checked before the transition is tried.
The Parser is able to convert Orocos State Descriptions to a State Machine wich can be loaded in the Processor.
In order to access (activate, start, stop, pause,...) a loaded state machine, use :
StateMachine* smach = mytaskC.engine()->states()->getStateMachine("MachineName"); if (smach == 0 ) { // state machine not found. } smach->activate(); string state = smach->getCurrentStateName(); smach->start(); // ... smach->stop(); // .. etc.
All commands are also accessible from within the interactive 'TaskBrowser'.
Loading, unloading and deleting a State Machine may throw
an exception. The program_load_exception
and program_unload_exception
should be try'ed and catch'ed on load and unload or deletion
of a State Machine in the Processor.
| Tip | |
|---|---|
Alternatively, you can use the ProgramLoader class as a user-friendly frontend for loading State Machines. |
The CommandProcessor is responsible for accepting command requests from other (realtime) tasks. It uses a non-blocking atomic queue to store incoming requests and fetch-and-execute them in its periodic step. It offers thus a thread-safe realtime means of message passing between threads, without intervention of the Operating System.
#include <rtt/TaskContext.hpp> ActionInterface* com = ... // Create a command, see the next Section. int ticket = mytaskC.engine()->commands()->process( com );
If ticket is zero, the command was
not accepted by the processor, possibly the queue was full
or it was not running. You can query if a command has been
fetched and executed by calling :
if ( mytaskC.engine()->commands()->isProcessed( ticket ) )
// done !
else
// still busy ! Figure 5.5. Tasks Sending Commands

Tasks of different threads communicate by sending commands to each other's Command Processors. When Task T1, running in Thread (a), requests that T2, running in Thread (b) processes a command, the command is stored in a command queue of that task's Command Processor. When T2 runs its Command Processor, the queue is checked and the command is executed. T1 can optionally check if the command was accepted and executed, using a Completion Condition ( see TaskContext and Program Parser manuals. )
| Note | |
|---|---|
Since Orocos Version 0.22.0, it is far easier to create commands using the command factories in the TaskContext. Please see the Task Context manual for examples. This section is still valid, but harder to apply in practice. |
Apart from the Program and State Machine Commands (which are generated by the Parsers), the user can convert C/C++ functions in commands for the Processor to execute. This is useful if a function must be called at a later moment.
Orocos uses the 'Generic Functor' paradigm to encapsulate Commands. This means that an object (the functor) is created which holds a pointer to the function to be executed. If this function needs arguments, these are also stored in the functor. The object can then be passed around until another object decides to execute the functor. Execution of a functor leads to the original function to be called, together with the arguments.
The CommandFunctor is the object used to store the function pointer in. It implements the ActionInterface such that it can be execute()'ed by the Processor :
#include <rtt/CommandFunctor.hpp> void foo(); ActionInterface* command = newCommandFunctor( &foo ); command->execute(); // calls foo() delete command;
notice that we use a factory-function newCommandFunctor
in order to avoid providing a template parameter.
It is possible to wrap more complex functions in a CommandFunctor, if the boost::bind library is used :
#include <rtt/CommandFunctor.hpp> #include <boost/bind.hpp> void foo( int x, int y ); int a1 = 1, a2 = 2; ActionInterface* command = newCommandFunctor( boost::bind( &foo, a1, a2 ) ); command->execute(); // calls foo(a1,a2) delete command;
Argument 'binding' is a very powerful feature of C++. It allows to provide the arguments of a function in advance and execute the function lateron.
It is also possible to call the memberfunction of an object. In that case, the first parameter of the function becomes the pointer to the object, followed by the arguments of the function ( which must all be variables ) :
#include <rtt/CommandFunctor.hpp>
#include <boost/bind.hpp>
class X {
public:
void foo( int x, int y );
};
X x_object;
int a1 = 1, a2 = 2;
ActionInterface* command = newCommandFunctor( boost::bind( &X::foo, x_object, a1, a2 ) );
command->execute(); // calls x_object.foo(a1,a2)
delete command;
notice that the foo function is now prefixed by the class scope 'X::'.
The CommandFunctor allows us to bind a function to a ActionInterface. Since the Program Processor can execute ActionInterface objects, it is a powerful way to delay calling of a function to a later moment.
Using the CommandFunctor from the previous section, we can pass the command to the processor :
ActionInterface* command = ... Processor* proc = ... int nr = proc->process( command );
If nr is non-zero, the command was accepted, if zero, the command fifo is full and a new process attempt must be made.
Another thread instructs the processor to execute all queued commands (and programs) synchronically calling the
proc->step();
function. The easiest way to do this is to create a Task which runs the Processor.
The CommandFunctor can be used when a separate thread of execution wants to execute a function in the Processor thread. In Orocos, this happens when an external commando must be processed by the realtime control kernel. In one thread, the CommandFunctor is created (which contains the function to be called) and is passed to the Processor of the ExecutionExtension, which is part of the control kernel. The control kernel thread executes all queued commands in the Processor after the control calculations are done. In this way, safe data access can be guaranteed.
| Note | |
|---|---|
This section gives a short description of the inside of Programs and StateMachines, and can be skipped by most users. |
The ConditionEdge defines an edge of the state diagram. It contains a condition on which a next state is entered. The ConditionInterface encapsulates that logic. Conditions can be ordered by priority, so that it is defined in which order they are checked. A multiple of conditions can lead to the same state.
The CommandNode contains a
Command and is connected by edges of
the type ConditionEdge, these edges
connect one node with another and allow the transition
if the contained condition evaluates to true. When a program
is executed, it executes the command and runs through the
list of edges of that node. When a
Condition is found valid, the next program
node to be executed is found. If no condition is
fulfilled, the same command node will be executed again.
Also a line number can be associated with each command node,
as a reference to the input file formatted by the user.
The Command is the abstraction of a
user directive that has to be executed. A Command can be
execute()'ed and reset()'ed. For each action exists one
Command, but a Command can be composed of other Commands.
The basic interface, ActionInterface, is provided by the
Orocos CoreLib.
The Condition is the abstraction of a
user expression that has to be evaluated. A Condition can be
evaluated()'ed and reset()'ed. Many primitive expressions
can be evaluated and a Condition can be composed of other
Conditions. The basic interface, ConditionInterface, is
provided by the Orocos CoreLib
Table of Contents
Abstract
This document describes the insides Orocos Parser system, in the different ways it can be used and extended.
The Orocos Parser allows users of the Orocos system to write programs and state machines controlling the system in a user-friendly realtime script language. The advantage of scripting is that it is easily extendible and does not need recompilation of the main program. It is implemented using the Boost.Spirit parser library, and should be fairly easy to work with.
This document is about extending the Orocos parser to support extra types, overload existing operators, and/or add new operators.
| Note | |
|---|---|
This Section is only relevant to persons willing to extend the internals of the Orocos Parser framework. |
For various reasons, during the development of the Orocos parser, it has proven necessary to hard-code various things, mostly relating to the defined types, and the operations supported on them. The parser supports using different types of objects than the predefined ones, but the major limitations are:
For some types like vectors, rotations and frames, special syntax was added. Currently, this is limited to the so-called constructors, that allow you to construct e.g. a vector from three doubles.
We will address the ways to address the various limitations.
This section will explain how to add a custom constructor, or a custom operator, that you will then be able to use in expressions.. The operator can take one to three arguments of any type, and can return any type..
You need to do two things in order to do this:
make the parser know about the new syntax
tell the parser what the new syntax means
You should make the parser aware of the new syntax in the file rtt/program-parser/src/ExpressionParser.cxx. There, in the ExpressionParser constructor, the syntax of an expression is defined. There, you should add the new syntax. I'm afraid I can't explain you other than either copying from an existing syntax or reading the Boost.spirit documentation. You need to couple your new syntax with a semantic action like "bind( &ExpressionParser::seen_binary, this, "%" ) for a binary action that you want to give the name "%". The name "%" is just an identifier that should be unique to your new operator, it can be any string you want.
Next, you need to define the operator in Operators.cpp, in much the same way as you should do for overloading an existing operator. However, instead of then using an existing string like "+", you should use the string you chose while defining your new syntax above.





