You are on page 1of 124

The Orocos Open Realtime

Control Services
Open RObot COntrol Software
0.20.1

Peter Soetens

The Orocos Open Realtime Control Services : Open RObot


COntrol Software : 0.20.1
by Peter Soetens
Copyright 2002,2003,2004,2005 Herman Bruyninckx, Peter Soetens
This document gives an introduction to the Orocos [http://www.orocos.org] ( Open RObot COntrol
Software ) project. It bundles the Installation and RealTime Control Software manuals.
Orocos Version 0.20.1.
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 FrontCover Texts, and with no Back-Cover Texts. A copy of this license can be found at http://www.fsf.org/copyleft/fdl.html.

Table of Contents
1. Orocos Overview ............................................................................................... 1
1. What is Orocos? ........................................................................................ 1
2. Target audience ......................................................................................... 1
3. Vision ..................................................................................................... 2
3.1. Software engineering requirements ..................................................... 2
4. Project Software ........................................................................................ 5
4.1. Orocos Realtime Control Software Structure ........................................ 5
4.2. Code directory structure: the Packages ................................................ 6
5. Project history ........................................................................................... 6
2. Installing Orocos ............................................................................................... 8
1. Setting up your first Orocos source tree .......................................................... 8
1.1. Introduction ................................................................................... 8
1.2. Basic Orocos Installation .................................................................. 9
1.3. Installing an Orocos Build ................................................................12
2. Configuration ...........................................................................................13
2.1. Configuring an Orocos Package ........................................................13
2.2. Orocos Build Compiler Flags ...........................................................14
2.3. Configuring Device Drivers .............................................................15
2.4. Configuring RTAI ..........................................................................15
2.5. Configuring for RTLinux .................................................................17
2.6. Upgrading Your Configuration .........................................................17
3. Getting Started with the Code ......................................................................17
3.1. A quick test ...................................................................................17
3.2. What about main() ? .......................................................................17
3.3. Header Files Overview ....................................................................18
4. Installation Technical Details ......................................................................19
4.1. Introduction to the eCos toolset .........................................................19
4.2. Installing Orocos from Subversion (similar to CVS) ..............................21
3. Operating System Abstraction .............................................................................22
1. Introduction .............................................................................................22
1.1. Realtime OS Abstraction .................................................................22
2. The Operating System Interface ...................................................................22
2.1. Basics ..........................................................................................22
3. OS Package Structure ................................................................................23
3.1. The RTAI/LXRT OS target ..............................................................23
3.2. Porting Orocos to other Architectures / OS'es ......................................23
3.3. OS Header Files .............................................................................24
4. Using Threads and Realtime Execution of Your Program .................................24
4.1. Writing the Program main() ..............................................................24
4.2. The Orocos Thread System ..............................................................24
4.3. Synchronisation Primitives ...............................................................26
4. Core Library ....................................................................................................28
1. Introduction .............................................................................................28
2. Periodic and non Periodic Tasks ..................................................................28
2.1. Periodic Tasks Run in Periodic Threads ..............................................28
2.2. Creating a Periodic Thread ...............................................................28
2.3. Creating a Periodic Task ..................................................................29
2.4. Periodic Task Ordering ....................................................................31
2.5. Example Periodic Task Creation .......................................................32
2.6. Periodic Threads Overview ..............................................................33
2.7. Non Periodic Tasks Run in Their Own Thread .....................................34
2.8. Event Driven Tasks ........................................................................35
2.9. Priority Inversions ..........................................................................35
2.10. Collecting Thread Information from Tasks ........................................37
3. Events ....................................................................................................37
3.1. Choosing the Asynchronous Thread ...................................................40
3.2. Private EventProcessors for Periodic Tasks .........................................41
iv

The Orocos Open Realtime Control Services

3.3. Event Overrun Policy ......................................................................41


3.4. The Completion Processor ...............................................................41
4. Time Measurement and Conversion .............................................................42
4.1. The TimeService ............................................................................42
4.2. Usage Example ..............................................................................42
5. Properties ................................................................................................42
5.1. Introduction ..................................................................................42
5.2. Grouping Properties in a PropertyBag ................................................43
5.3. Marshalling and Demarshalling Properties (Serialization) ......................44
6. The NameServer .......................................................................................44
6.1. Introduction ..................................................................................44
6.2. Using the NameServer ....................................................................44
7. Buffers, DataObjects and DataSources ..........................................................45
7.1. Buffers .........................................................................................45
7.2. DataObjects ..................................................................................46
7.3. DataSources ..................................................................................47
8. Logging ..................................................................................................47
9. Reporting ................................................................................................48
10. Fifos .....................................................................................................48
10.1. A warning ...................................................................................48
10.2. Using fifos ..................................................................................49
5. Hardware Device Interfaces ................................................................................50
1. The Orocos Device Interface (DI) ................................................................50
2. The Device Interface Classes ......................................................................51
2.1. Physical IO ...................................................................................51
2.2. Logical Device Interfaces ................................................................52
3. Porting Device Drivers to Device Interfaces ...................................................52
4. Interface Name Serving .............................................................................52
6. Hardware Device Drivers ...................................................................................53
1. Introduction .............................................................................................53
1.1. Requirements ................................................................................53
1.2. What's in this package .....................................................................53
2. Developers Documentation .........................................................................54
2.1. Comedi C++ .................................................................................54
2.2. Logical Device Drivers ....................................................................54
2.3. CANPie and CANOpen Device Drivers ..............................................54
7. The Task Infrastructure ......................................................................................55
1. Introduction .............................................................................................55
2. An Introductory Example to TaskContexts ....................................................56
3. Setting Up a Basic Task .............................................................................60
3.1. The Method Factory .......................................................................62
3.2. Method Argument and Return Types ..................................................64
3.3. The Attribute Repository .................................................................65
3.4. The Command Factory ....................................................................66
3.5. The DataSource Factories ................................................................68
3.6. Creating Commands Without the Factories ..........................................69
4. Connecting Tasks .....................................................................................69
5. Running Tasks .........................................................................................70
5.1. Starting Periodic Task Execution .......................................................70
5.2. Task Property Configuration .............................................................71
5.3. Task Program Scripts ......................................................................71
5.4. Adding Events ...............................................................................74
6. Applying the Task Infrastructure to Your Application ......................................75
6.1. Using the GenericTaskContext .................................................75
6.2. Wrapping Methods in Functions ........................................................75
6.3. Waiting for Something : Synchronisation ............................................75
6.4. Polymorphism : Task Interfaces ........................................................77
7. Using C++ Commands ...............................................................................80
7.1. Sending a Task a Command .............................................................80
7.2. Advanced Command Queueing .........................................................81
8. The Program Processor ......................................................................................82
1. Introduction to the Program Processor ..........................................................82
2. Executing Commands, Programs and Hierarchical StateMachines ......................82
v

The Orocos Open Realtime Control Services

2.1. The Processor ................................................................................82


2.2. Running the Processor .....................................................................82
2.3. Programs ......................................................................................83
2.4. State Machine and State ...................................................................85
3. Creating Commands ..................................................................................86
3.1. Generic Functors ............................................................................86
3.2. Creating a CommandFunctor ............................................................87
3.3. Processing a Command ...................................................................88
3.4. Common Usage Examples ...............................................................88
4. StateMachine and Program Implementation Details .........................................88
4.1. ( State ) Transitions : Condition Edge .................................................88
4.2. Statements : Command Node ............................................................89
4.3. The Command class ........................................................................89
4.4. The Condition class ........................................................................89
9. The Program Parser ...........................................................................................90
1. Introduction .............................................................................................90
2. Orocos Program Scripts .............................................................................90
2.1. General concepts ............................................................................90
2.2. Parsing and Loading Programs ..........................................................95
2.3. Program Semantics .........................................................................96
2.4. Program Syntax .............................................................................96
3. Orocos State Descriptions : The Real-Time State Machine .............................. 101
3.1. Introduction ................................................................................ 101
3.2. StateMachine Workings ................................................................. 101
3.3. Parsing and Loading StateMachines ................................................. 102
3.4. Defining StateMachines ................................................................. 102
3.5. Instantiating Machines: SubMachines and RootMachines ..................... 104
3.6. Starting and Stopping StateMachines from scripts .............................. 108
4. Program and State Example ...................................................................... 111
5. Extending the parser ................................................................................ 113
5.1. Parser Limitations ........................................................................ 113
5.2. Alleviating the Limitations ............................................................. 114

vi

List of Figures
1.1. Realtime Control Software Overview .................................................................. 5
3.1. OS Interface overview .....................................................................................22
4.1. Execution sequence diagram .............................................................................31
4.2. Event Handling ..............................................................................................37
4.3. DataObjects versus Buffers ..............................................................................45
5.1. Device Interface Overview ...............................................................................50
7.1. Tasks Run in Threads ......................................................................................55
7.2. Schematic Overview of a TaskContext ...............................................................61
7.3. Template Factories and Classes UML Diagram ....................................................64
8.1. Tasks Sending Commands ...............................................................................83
8.2. Executing Program Statements ..........................................................................84
8.3. Using a Program ............................................................................................84
8.4. Executing a StateMachine ................................................................................85
8.5. Using a StateMachine .....................................................................................85
8.6. A Generic Functor ..........................................................................................87

vii

List of Tables
2.1. Build Products Locations .................................................................................. 8
2.2. Build Products Locations .................................................................................13
2.3. Header Files ..................................................................................................18
3.1. Header Files ..................................................................................................24
4.1. Thread and Task summary ...............................................................................30
4.2. Classes Possibly Subject to Priority Inversion ......................................................36
4.3. Classes Not Subject to Priority Inversion ............................................................36
4.4. Logger Log Levels .........................................................................................47
5.1. Physical IO Classes ........................................................................................51
7.1. Method Return & Argument Types ....................................................................64

viii

List of Examples
2.1. A Makefile for an Orocos Application ................................................................18
3.1. Locking a Mutex ............................................................................................26
4.1. Example Periodic Thread Creation ....................................................................29
4.2. Example Periodic Task Creation .......................................................................32
4.3. Using Events .................................................................................................38
4.4. Event Types ..................................................................................................39
4.5. Using properties .............................................................................................42
4.6. Accessing a DataObject ...................................................................................46
4.7. Using the Logger class ....................................................................................48
5.1. Using the name service ....................................................................................52
9.1. StateMachine Definition Format ...................................................................... 102
9.2. StateMachine Example (state.osd) ................................................................... 111
9.3. Program example (program.ops) ..................................................................... 112

ix

Chapter 1. Orocos Overview


This document explains the goals, vision, design, implementation and packages of Orocos
[http://www.orocos.org], the Open RObot COntrol Software project.

1. What is Orocos?
Orocos is the acronym of the Open Robot Control Software [http://www.orocos.org] project. The
project's aim is to develop a general-purpose, free software, and modular framework for robot control (including extensive and high-quality documentation).
A framework is a set of source code from which applications in a particular domain can be made
[Johnson97]). So, the framework is not an application in itself, but it provides the infrastructure and
the functionalities to build applications. Usually, application builders must fill in some hot spots
that are specific to their application, and hence, which cannot be provided by the framework.
Orocos works in a bottom-up fashion, so current activities are currently more focused on developing basic infrastructure than on implementing advanced control, planning or sensor processing algorithms.
Orocos is a free software project, hence its code and documentation are released under Free Software licenses.
Your feedback and suggestions are greatly appreciated. Please, use the project's mailinglist
[http://lists.mech.kuleuven.ac.be/mailman/listinfo/orocos] for this purpose.

2. Target audience
Robotics is a very broad field, and many roboticists are pursuing quite different goals, dealing with
different levels of complexity, real-time control constraints, application areas, user interaction, etc.
So, because the robotics community is not homogeneous, Orocos targets four different categories of
Users (or, in the first place, Developers):
1.

Framework Builders.
These developers do not work on any specific robotics application, but they provide the infrastructure code to support applications. This level of supporting code is most often neglected in
robot software projects, because in the (rather limited) scope of each individual project, putting
a lot of effort in a generic support platform is often considered to be overkill, or even not
taken into consideration at all. However, because of the large scope of the Orocos project, the
supporting code (the Framework) gets a lot of attention. The hope is, of course, that this
work will pay of by facilitating the developments for the other Users.

2.

Component Builders.
These developers implement basic robotics functionality, on top of the generic Framework. The
functionality is offered as a service, in the form of a (Software) Component, [Szyperski98].
Such a component is not (necessarily) a full application in itself, but the Component Builders
do their best to provide high-quality functionality, in an application-independent way. That is,
the programming interface to the robotics functionality is rich and well documented, and the
component can be used as a stand-alone part in various applications.
Components come in roughly three different types. The simplest is an object-oriented class
hierarchy, offering direct access to the objects data and functionality. The medium-level component provides monitored access to the objects' functionality, in the sense that it guarantees
that different accesses do not interfere with each other, and that the object always remains in a
consistent state. The highest-level Component has the properties as described in the CORBA
Component Model.
1

Orocos Overview

3.

Application Builders.
These developers use the Orocos' Framework and Components, and integrate them into one
particular application. That means that they add a specific, application-dependent architecture
and API on top of the functionalities offered by the Framework and the Components. An example Orocos application is the Orocos Control Kernel, which provides an application framework for (motion) control, with a set of components which implement control or planning algorithms.

4.

End Users.
These people use the products of the Application Builders to program and run their particular
tasks.

The focus in the Orocos project lies primarily with the Framework Builders and the Component
Builders. But, since all contributors are motivated by their own particular applications, the needs of
the Application Builders are also taken into account, albeit indirectly.
End Users do not directly belong to the target audience of the Orocos project, because Orocos concentrates on the common framework, independent of any application architecture. Serving the needs
of the End Users is left to (commercial and non-commercial) Application Builders.

3. Vision
The long-term goal of Orocos is to provide all necessary software infrastructure,
for all possible robots (manipulator arms, mobile robots, humanoids, rehabilitation devices, etc.) and industrial machines ( machinetools, automation systems,
etc.)
This is a very ambitious goal, and will inevitably lead to a very large code tree, various sub-projects,
and contributors that are only interested in one or two of these sub-projects. So, the danger of arriving at a chaotic and incoherent project is real. This danger will be fought by adhering to a clear
roadmap, or vision for both of the following aspects (discussed in more detail in the following
Sections):
1.

Software engineering requirements.


What quality and approach are expected from contributions to the project?

2.

Structure of the source code.


How is the project structured into sub-projects?

The project maintainers will strictly impose adherence to the project's vision and roadmap when
considering incorporation of contributions to the project. However, they always welcome a motivated
discussion
about
these
topics
on
the
project's
mailinglist.
[http://lists.mech.kuleuven.ac.be/mailman/listinfo/orocos]

3.1. Software engineering requirements


Orocos has a vision on how developers should design, implement and present new functionality, in
order to guarantee the project's coherence and scalability. These are the software engineering
guidelines that support the implementation of the project's vision:
1.

Object-oriented design.
This seems an obvious requirement for modern-day software development. However, the experience within the project has learned that designing an appropriate class hierarchy is a major
challenge, mainly because of the diversity of the field.
2

Orocos Overview

2.

Extreme decoupling and modularity.


This is the fundamental criterium to keep in mind when designing and/or refactoring the
project's class hierarchy. Object classes encapsulate data and activities, and the choice of
classes should be such that:

The implementation of one class should not rely on knowing something about the internals
of another class. Relying on such knowledge is called coupling between both classes, and
this must be avoided at all costs, because it prevents the independent evolution or reimplementation of the classes.

Every class should have an interface that fits more or less on one single page. Elaborate interfaces are a sign that the implemented functionality may better be split into multiple objects.
The strong desire to decouple software parts wherever possible is a key feature of the project; it
may seem a bit of an overkill for each individual application built with the Orocos code (and
hence a burden for the individual contributor), but for the whole project it is expected to become its major competitive advantage.
3.

Small and shallow interfaces.


It is useful to separate the interface of a certain functionality from its implementation (as a class
or as a component). The interfaces are what Application Builders and End Users need to construct their programs; the class and component implementations are contructed by the Framework and Component Builders.
Separated interfaces give more flexibility in changing implementations, and in allowing
third-party providers of components. They also allow to discuss functionality without being
biased by possible implementations problems or opportunities.
Orocos prefers interfaces that are:

Small. This improves the focus of the interface, and hence (hopefully) its quality. In this
sense, it corresponds to the minimality requirement discussed below. Another motivation is
that Orocos wants to avoid exceptions in implementations of the interfaces: when the interface is large, the chance increases that a particular developer will not be able to provide a
complete implementation. In that case, the implementation must return an exception
functionality not implemented, and this complicates the execution logic of the component
that uses the interface, and reduces the real-time performance.

Shallow. In an object-oriented paradigm, much functionality is provided by class hierarchies. Every hierarchy may seem natural in the context on one particular application, but it
will most certainly not be natural in other applications. For example, a robotics engineer
may find it normal to have a robot object at the top of a large and deep hierarchy. But that
same word robot will not be well received by machine tool builders that construct milling
machines, or laser cutters. Although most of the functionality of motion control and task
execution will be the same in both application areas. Therefore, class hierarchies should not
be deep.
In addition to the technical reasons to use small and shallow interfaces, they are a key feature
for free software projects that expect significant contributions from their community: the complexity of the whole system can only be tackled by restricting the scope of each individual
piece of code. An increased flexibility is a derived property that comes for free.
4.

Distributable.
Future machine control systems will most certainly use multiple processors, connected through
a network with sensors and actuators that most probably will have their own embedded intelligence. Hence, the robot/machine control software will have to be scalable and distributable.
Having a design that is modular and maximally decoupled fulfills already more than half of the
requirements for such distributed control systems. The other half will come from making software components with an internal design that allows their different parts to cooperate over a
network.
3

Orocos Overview

Two software aspects are important in this respect: the ubiquitous use of events for synchronization, and of a virtual (network) time (instead of the time delivered by the local processor).
Both events and virtual time are general abstractions, that are easily mapped on corresponding
primitives of the particular operating system the component is running on. For example, a hardware interrupt is also an event, and for the logic of a component it almost never matters whether the event comes from the hardware or from a local stub of remote hardware. So, make sure
that your components don't use system calls that tie them to a particular operating system or a
particular device.
Orocos provides abstraction layers for both the operating system and the interfacing hardware.
These abstractions follow the principles of minimality and decoupling. Especially for the operating system abstraction this is a big advantage, because typical (real-time) OSs tend to have
way too large and too primitive APIs, that are easily abused.
5.

Minimalistic.
Developers should only offer features that are absolutely necessary: practical experience has
learned that the availability of superfluous APIs leads to implementations with similar functionality but with different implementations. And, worse, to implementations where the programmers do not know very well what parts of the available functionality to use. Both effects
lead to sub-optimal results, and applications that are more difficult to re-use and maintain.

6.

Platform independent.
Because of its Free Software nature, Linux is the normal environment for both host development and target runtimes. This can in practice lead very quickly to an unperceived bias towards
Linux, that would compromise the portability to other operating systems. Developers must try
to use only portable language constructs.
Of course, sooner or later, the project will be confronted with the trade-off between portability
and the choice for a particular desired feature that can not be supported by all initially targeted
platforms. These trade-offs must be discussed on the mailinglist.

7.

Thorough large-scale design.


Developers must always consider that their designs will possibly be used in a very complex,
distributed and hard real-time implementation. Therefore, the design of such large-scale robotics systems must be made clear before starting the implementation of classes and components.
And the implementators must provide contributions that can safely and efficiently work in such
large-scale systems.
One particular item is real-time: if an algorithm is inherently real-time (i.e., it has a deterministic execution time), then its implementation must be implemented with constructs that are
real-time safe. For example, all variables and temporary objects must be allocated before execution of the functionality proper, and no exceptions or run-time type checking must be used.
(So, Java and C++ programmers need to be careful.)

All these requirements illustrate the project's emphasis on design: most things in robotics have
already been tried out in various ways, so the Orocos implementation should at least be as good as
the best of these tried-out predecessors. In addition, Orocos aims at a number of extra features, such
as ultimate generality. This generality is the reason why the project follows a development approach which is maybe a bit different from traditional free software projects: the trade-off during the
current development is towards having a very well thought-out and very general design, and not in
the first place towards adding as many functionality features as possible. This approach is motivated
by the observation that all existing robot control software packages are very difficult to use in applications that are different from the first application they were built for. And that sharing source or
binary code is very hard in practice, because of ill-defined interfaces, that are most often also
strongly coupled to one particular application.
The development approach of the project follows from the above-mentioned vision: the project
4

Orocos Overview

works in a bottom-up fashion, on small interfaces at a time, but while keeping the long-term goal
in mind. So, it is currently building the lowest levels of the framework, such as the hard real-time
Core, motion control functionality, primitives for communication between Orocos components,
robot kinematics, etc.

4. Project Software
The Orocos project spawned a couple of largely independent software projects. The documentation
you are reading is about the Realtime Control Software located on the Orocos.org webpage. The
other projects are :

At KTH Stockholm, several releases have been made for component-based robotic systems, and
the project has been renamed to Orca [http://orca-robotics.sourceforge.net/].

Although not a project funded partner, the FAW Ulm maintains Free CORBA communication
patterns for modular robotics : OROCOS@FAW [http://www1.faw.uni-ulm.de/orocos/].

4.1. Orocos Realtime Control Software Structure


The realtime control software is structured in layers on top of the hardware target (CPU) and the
devices (IO).

Figure 1.1. Realtime Control Software Overview

Every Orocos layer in Figure 1.1, Realtime Control Software Overview is documented seperately. All Orocos software communicates through Operating System abstractions with the underlying OS or through Device Interfaces with the IO hardware. On the left side on top of the OS Abstraction is the Core Library, which presents infrastructure for writing realtime (periodic) tasks, in5

Orocos Overview

cluding many communication primitives. On top of that is the Task Infrastructure, which allows to
define browsable task objects which are capable of receiving (remote) commands, process programs
and hierarchical state machines and share realtime data with other tasks. Your application may use
that infrastructure to define its own tasks and set up communication between them.
On the right side, lives the Device Abstraction stack. The Operating System already provides a
device driver model, Orocos device drivers map that model to machine or robot control specific
Device Interfaces. An Orocos Device combines a number of these interface into an object representing a physical entity. It may represent a complete machine, or just a mere Sensor or Axis, such that
your application can communicate with physical meaningful devices ( instead of IO ports).
Because of the Openness of Orocos, your application does not need to take-all-or-leave-all. You can
pick out the level of abstraction which suits you best for a given application.

4.2. Code directory structure: the Packages


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 and components.
The following Packages exist, or will be created in the near future: corelib, kinematics and dynamics, device interface, device drivers, control kernel, applications, estimation, execution, operating
system, robots, XY tables.
The directories of each Package contain a detailed documentation of the Package in their local /
doc directories.
The packages directory is mostly found under the orocos directory. The orocos directory holds some
general documentation in the doc directory (like this manual) and scripts for setting up a working
packages directory. This means that, unless you are very familiar with the packaging system, you
should first install the base system before trying to install a package.

5. Project history
The idea for starting a Free Software project for robot control was born in December 2000, motivated by over two decades of rather disappointing experiences and failures in trying to use commercial robot control software for advanced robotics research. The idea, together with a draft of a possible project proposal, was launched on the mailinglist of EURON [http://www.euron.org/], the
European Robotics Network. This email gave rise to a lot of responses, even though it was sent during the Christmas period. Within about two weeks, a proposal was made ready, and sent to the
European Union. Contacts with the responsible Officer made clear that the size of the project had to
be very modest, so that only three partners were selected: K.U.Leuven in Belgium, LAAS Toulouse
in France, and KTH Stockholm in Sweden. Each of these three groups would later receive only one
full man-year of support. The EU-sponsored project started in September 2001, and has a duration
for two years.
The EU sponsoring also provides some travel grants to invite non-sponsored people to the Orocos
project meetings. This, together with the classical attributes of a web page [http://www.orocos.org]
and a mailinglist [http://lists.mech.kuleuven.ac.be/mailman/listinfo/orocos], stimulated quite some
discussions and exchanges of ideas.
A first version of the hard real-time core was released in the Summer of 2002, but it was very preliminary and hard to use. In November 2002, the first version was released with which simple position and velocity control of a six degrees-of-freedom robot manipulator arm was possible.
After the EU sponsored project was finished, the project partners continued to improve on the delivered software. The realtime Orocos framework developments at the KU Leuven, knew already 6
major software ( see Downloads [download.php] ) releases after september 2003. At KTH Stockholm, several releases have been made for component-based robotic systems, and the project has
been renamed to Orca [http://orca-robotics.sourceforge.net/]. Although not a project funded partner,
the FAW Ulm maintains Free CORBA communication patterns for modular robotics : OROCOS@FAW [http://www1.faw.uni-ulm.de/orocos/].
6

Orocos Overview

Because of its applicability to industrial applications, the realtime Orocos framework has grown into
the machine control field and outgrown its robotics roots. The modularity of Orocos packages reflects this versatility. Currently, the Flanders Mechatronics Technology Centre [http://www.fmtc.be]
funds the development of the realtime Orocos framework and coordinates the integration of Orocos
in industrial machines of machinetool builders.

Chapter 2. Installing Orocos


This document explains how the packages of Orocos [http://www.orocos.org], the Open RObot
COntrol Software project must be installed and configured.

1. Setting up your first Orocos source tree


1.1. Introduction
This sections explains the supported Orocos targets and the Orocos versioning scheme.

1.1.1. Supported platforms (targets)


Orocos was designed with portability in mind. Currently, we support RTAI and (NEW)LXRT (http://www.rtai.org), GNU/Linux userspace, and very limited RTLinux (http://www.fsmlabs.com and
http://www.rtlinux-gpl.org/). So, you can first write your software as a normal program, using the
framework for testing and debugging purposes in plain userspace Linux and recompile later to another target.
RTAI with LXRT is the best supported realtime platform at this moment. RTLinux can only partially use all Orocos features, because it lacks a decent kernel space memory manager. Furthermore,
since no Orocos user is using the FSMLabs RTLinux port, it is no longer updated.

1.1.2. The versioning scheme


Orocos uses the well-known even/stable uneven/unstable version numbering scheme, just as the
Linux kernel and many other projects. A particular version is represented by three numbers separated by dots. An even middle number indicates a stable version. For example :

0.1.4 : Release 0, unstable (1), revision 4.

1.2.1 : Release 1, stable (2), revision 1.

This numbering allows to develop and release two kinds of versions, where the unstable version is
mainly for testing new features and designs and the stable version is for people wanting to run a reliable system.

1.1.3. Dependencies on other Libraries


Before you install Orocos, verify that you have the following software installed on your platform :

Table 2.1. Build Products Locations


Program / Library

Minimum Version

Description

Python

2.2

Python.org
[http://www.python.org]

TCL

8.0

tcl.tk [http://www.tcl.tk]

wxGTK

2.4.1

wxwindows.org
[http://www.wxwindows.org/]

Boost C++ Libraries

0.31.0

Boost.org
[http://www.boost.org]

GNU gcc / g++ Compilers

3.3.0

Xerces C++ Parser

2.1

Xerces
website
[http://xml.apache.org/xerces-c/
8

Installing Orocos

Program / Library

Minimum Version

Description
]

GNU Readline Library

4.0

Readline
website
[http://cnswww.cns.cwru.edu/ph
p/chet/readline/rltop.html]

CppUnit Library

1.9.6

CppUnit
website.
[http://cppunit.sourceforge.net/c
gi-bin/moin.cgi] Only needed if
you want to run the Orocos
tests.

All these packages are provided by most Linux distributions. Take also a look on the Orocos.org
download page for the latest information.

1.2. Basic Orocos Installation


Orocos has switched to a less common system for distribution and configuration. It is taken from the
eCos operating system, but, apart from the name, has nothing to do with it when used with Orocos.

1.2.1. Orocos Build and Configuration Tools


The tools you will need are ecosconfig and configtool. In Debian, you can use the official Debian
version using
apt-get install
ecosconfig
If this does not work for you, you can try the ready-to-use tools in Orocos' tools/bin subdirectory.
The build system will try to locate the tools in that directory and use them if present.
If the build tools cause problems, consult the eCos Configuration Tool Version 2
[http://www.ecoscentric.com/devzone/configtool.shtml] webpage for source and binary downloads
or consult the Orocos download page [http://www.orocos.org/download.php#ecostools].

Note
configtool is a GUI in which users can configure their packages. It uses the libwxgtk2.4 library. ecosconfig is a commandline program, used by the make system.
These tools both serve the same purpose : to control package configuration and installation. Ecosconfig is command-line based, configtool is a graphical tool. The eCos folks wrote an
(eCos-oriented) manual which explains them in great detail in the user guide
[http://sources.redhat.com/ecos/docs-latest/user-guide/ecos-user-guide.html] and how packages
work
internally
is
written
in
the
eCos
Component
Writer
Guide
[http://sources.redhat.com/ecos/docs-latest/cdl-guide/cdl-guide.html].

Note
In the eCos Operating System, a target is a hardware platform, in Orocos, a target is
RTAI, GNU/Linux, LXRT and so on, so you can skip all hardware specific sections in
the eCos manuals.

1.2.2. Quick Installation Instructions


It
is
possible
to
download
Orocos
with
the
[http://www.orocos.org/releases/orocos-install.tcl] script, this is recommended.
Save the script to your local drive and then do :
9

orocos-install.tcl

Installing Orocos

$ tclsh orocos-install.tcl
and follow the instructions. Watch for errors or warnings carefully !
After the script finishes, make sure you followed the closing instructions to source the orocosenv.sh
script :
source orocosenv.sh
mkdir orocos-0.20.1/build
cd orocos-0.20.1/build
../configure --with-<target>
make new_packages
make configure_packages
make all
Where <target> is one of listed in ../configure --help. ( currently 'gnulinux' or 'lxrt' ) make configure_packages will pop up a graphical configuration tool which allows you to add or remove packages from your configuration. Before a package can be added to a configuration, the package must
be present in the packages repository.

Note
The ../configure script must be rerun after you installed missing libraries (like boost,
xerces,...).
For convenience, Orocos can also be used as a regular configure/make system. It is thus allowed to :
mkdir orocos-0.20.1/build
cd orocos-0.20.1/build
../configure --with-<target>
make all
make check
make install
but this will use the default Orocos configuration. If you want to add/remove packages and configure options, you need to run make configure_packages and make again.

Warning
make new_packages will overwrite any existing configuration !
Each package will have been installed in a packages/package-name/version/ directory,
allowing multiple versions to be installed next to each other. The repository version is always called
'current', and considered as the most recent version. If you downloaded the epk files, you will find a
version number in place.

1.2.3. Configuration
Orocos is configured in a two-stage system. First the configure scripts detect available libraries and
take some command-line options to point it in the right direction. Also, it takes the Operating System you want to configure for as an argument ( --with-lxrt, --with-gnulinux).
The second stage is when you issue a make configure_packages, which will show a GUI where
you can further tweak build flags and package specific options. This is also the stage where you can
add/remove packages for compilation.

10

Installing Orocos

When you completed these two stages, you can now make. For more details, consult Section 2,
Configuration.

1.2.4. Building RealTime Control Services or Robot Control Software


Building all of Orocos takes a long time. We provided two build targets which allow you to specify
what to build.
make control_services
builds the Core Library, the Program Parsing and Execution Infrastructure and the OS and Device
Interfaces. These are the most important 'core' parts or Orocos, and are sufficient for the 'taskintro'
application and contains everything described in the 'RealTime Control Services' Manual.
make robot_control
builds the same packages as control_services, but adds kinematics, the control framework and the
control kernel components and contains everything described in the 'Robot Control Software' Manual.
Even then, not all packages are build. Type
make configure_packages
to add or remove specific packages to suit your needs.

1.2.5. Manual Download Instructions


If you choose to download the tools and packages manually, proceed as follows :
1.

Download orocos-0.20.1.i386linux package from the Orocos webpage

2.

Extract it using :
tar -xvzf orocos-0.20.1.i386linux.tar.bz2

11

Installing Orocos

3.

Setup the base environment :


cd orocos-0.20.1
EXPORT PATH=$PATH:$(pwd)/tools/bin
mkdir build
cd build
../configure

Now you can proceed with the Quick Installation Instructions from the previous section.

1.2.6. Building
The make all command will have made a directory packages where the building takes place. The
results of the build are in the packages/install directory. You will find the header files and a
library called libtarget.a (literally) and liborocos-<target>.a. These files allow you to
build applications with Orocos.
The make configure_packages will pop up a graphical frontend to configuration. When you save
and exit, a file packages/orocos.ecc will be generated which contains all your custom options.

Tip
Make a copy of the orocos.ecc file so that you always have a backup when it gets accidentally overwritten.

1.2.7. Using Orocos concurrently for multiple targets


When you want to build for another target, create a new build-<target> directory and simply reinvoke ../configure --with-<target> from that build directory.
If this step fails, it means that you have not everyting installed which is needed for a basic Orocos
build. Most users don't have the Boost library (libboost-dev or libboost-devel) installed.
Please install this package from the binary or source package repository of your Linux distribution,
or download and install it from the Boost project. [http://www.boost.org] As soon as the configure
step succeeds, all the rest will succeed too. Please use the mailinglist at
<orocos-dev@lists.mech.kuleuven.be> for support questions.
The make docs and make doxy-dist (both in 'build') commands build manuals and API documentation.

1.3. Installing an Orocos Build


Orocos can optionally ( but recommended ) be installed on your system with
make install
The default directory is /usr/local/orocos, but can be changed with the --with-prefix
option :
../configure --with-prefix=/opt/other/
If you choose not to install Orocos, you can find the build's result in the build/packages/install/[lib/include] directory. The ecostools allow to define a separate build directory, but this is documented in the eCos manuals. Kernel modules can be found in the install/
modules directory and might need manual installation.

12

Installing Orocos

You can find the resulting build products on the following places:

Table 2.2. Build Products Locations


What

Where
install/lib or /usr/local/orocos/lib

Orocos library. Depending on the target : liborocos-<target>.a and libtarget.a


install/include or /usr/local/orocos/include
Orocos header files. Inside the package subdirectories.
Orocos kernel modules

install/modules

2. Configuration
2.1. Configuring an Orocos Package
By default, the Orocos library will be built in packages/install and installed in /
usr/local/orocos.
You can modify the former by passing a --prefix= option to ecosconfig or change it in the GUI
and modify the latter by passing the --prefix= to configure.

Note
ecosconfig and configtool both use a different namingscheme for build directories.
configtool uses the <config_name>-build, <config_name>-mlt, <config_name>-mlt
subdirectories while ecosconfig uses the current dir to build and the 'install' directory
for installation.

Note
It is advised to only use the configtool GUI for generating the orocos.ecc file and using make for creating a build tree.

Note
If you invoke the eCos tools manually (without the make command) you need to type
the
following
command
:
export
ECOS_REPOSITORY=/path/to/orocos-trunk/packages You can then run ecosconfig help or configtool.
Just running ../configure or ../configure --with-gnulinux selects the os/gnulinux target.
When running ../configure --with-lxrt=/usr/local/realtime --with-linux=/usr/src/linux (the path is
optional) the configure script will try to find your RTAI installation in /usr/local/realtime
and select the os/lxrt target. You need to specify the path to your RTAI patched Linux Kernel in case
Orocos needs it to build properly. The default path where LXRT is being looked for is /
usr/realtime, while the default Linux path is /usr/src/linux. To use the LibC Kernel
headers in /usr/include/linux, specify --with-linux=/usr. Watch carefully the output
to find any errors.
When the target is selected with configure, you can run make configure_packages to pop up the
GUI configtool. Some things to do are :
13

Installing Orocos

Add/Remove a Package to/from your configuration : click on Build->Packages. 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 Tools->Administration, this allows you to select an .epk file from your harddisk, which will be added to your packages repository.

2.2. Orocos Build Compiler Flags


14

Installing Orocos

You must set the compiler flags in the configtool GUI, in Global Build Options

Furthermore, you MUST SET the Linux directory path in the configtool if you want to compile
Linux Kernel Modules. Otherwise the build will fail.

2.3. Configuring Device Drivers


Orocos provides device drivers for the Philips SJA1000 CAN Controller, APCI1710 Encoder card,
APCI2200 Digital IO card and Comedi Devices. These are not compiled with the default installation. In the graphical configtool you can add device drivers with the menu "Build->Packages". They
will be grouped in the device_drivers parent package.

2.3.1. Comedi and ComediLib


Orocos has a ported small part of the ComediLib interface in its C++ device interface. To use these
classes, the Orocos configure script must first detect a valid installation. This can be done by providing this option to configure :
../configure --with-comedi=/path/to/comedi
If found, the Comedi support package will be generated, and you will be able to add the Orocos
Comedi Device Driver to your configuration.
If Comedi is installed in /usr/realtime, /usr/src/comedi or in your default include path,
it will also be detected by the configure script.
Orocos can use ComediLib from the GNU/Linux target (non realtime interface), in which case you
must point configure to the 'comedilib' path and the LXRT target ( realtime interface ), in which
case you must configure to the 'comedi' path.

2.3.2. Philips SJA1000 CAN Controller


Orocos has a preliminary CAN Controller implementation using the CANPie interface. To use it,
the CANPie and CANOpen packages must be added to your configuration. The device driver works
with RTAI/LXRT.

2.4. Configuring RTAI


15

Installing Orocos

Read
first
the
'Getting
Started'
section
from
this
page
[http://people.mech.kuleuven.ac.be/~psoetens/portingtolxrt.html] if you are not familiar with RTAI
installation
RTAI 3.1 with Linux 2.6 and later versions are the most stable combination in our experience,
however you can also use RTAI 3.0 for less resource consuming applications. You can obtain it
from the RTAI home page [http://www.aero.polimi.it/projects/rtai/]. Read The README.* files in
the rtai directory for detailed instructions. First, you need to patch your Linux kernel with the
RTAI patch. A patch per kernel version can be found in the rtai-core/arch/i386/patches
directory. You should apply the hal12-X.Y.Z.patch (or later) for RTAI to a clean Linux kernel. We refer to the RTAI installation instructions for more details.
Next do make menuconfig; make dep; make;

2.4.1. RTAI settings


RTAI comes with documentation for configuration and installation. In the configuration process,
make sure that you enable the following options:

Floating Point support

Semaphores, Fifos, Bits or Events and Mailboxes

POSIX API

LXRT

C++ support

After configuring you can run 'make' and 'make install' in your RTAI directory
After installation, RTAI can be found in /usr/realtime. You'll have to specify during the Orocos configure step the alternative if you chose so.

Important
You need to set the path to your Linux source tree (on which the patch was applied ) in
the make configure_packages step.

2.4.2. Loading RTAI with LXRT


LXRT is a all-in-one scheduler that works for kernel and userspace. So if you use this, you can still
run kernel programs but have the ability to run realtime programs in userspace. Orocos provides you
the libraries to build these programs. Make sure that the following RTAI kernel modules are loaded

rtai_sem

rtai_lxrt

rtai_hal

adeos

For example, by executing as root: modprobe rtai_lxrt; modprobe rtai_sem.


For a more detailed description of what LXRT really is, you can read the LXRT HOWTO here
[http://people.mech.kuleuven.ac.be/~psoetens/lxrt/portingtolxrt.html]

2.4.3. Compiling Applications with LXRT


16

Installing Orocos

Application which use LXRT as a target need special flags when being compiled and linked. Especially :

Compiling : -I/usr/realtime/include
This is the RTAI headers installation directory. This option is not needed if you configured Orocos with the 'Agnostic headers' option.

Linking : -L/usr/realtime/lib -llxrt for dynamic (.so) linking OR add /


usr/realtime/liblxrt.a for static (.a) linking.

Important
You might also need to add /usr/realtime/lib to the /etc/ld.so.conf
file and rerun ldconfig, such that liblxrt.so can be found. This option is not needed
if you configured RTAI with LXRT-static-inlining.

2.5. Configuring for RTLinux


The latest free release of RTLinux was 3.2. RTLinux is no longer supported since no users of Orocos are using it.

2.6. Upgrading Your Configuration


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, Section 4, Installation Technical Details ).

3. Getting Started with the Code


This Section provides a short overview of how to proceed next using the Orocos Packages.

3.1. A quick test


You can issue a make check in the Orocos build dir, but this stresses your system heavily, so it
might crash not fully stable RTAI installations. make check for the gnulinux target should successfully complete.
To quickly test an Orocos application, you can download the kernel_samples tar.gz package, untar it
and type make all ( or make all-lxrt for lxrt). The kernel_samples/README file contains complete
instructions.

3.2. What about main() ?


17

Installing Orocos

The first question asked by many users is : How do I write a test program to see how it works?
Building a sample application with Orocos is quite simple, but some care must be taken in initialising the realtime environment. First of all, you need to provide a function int ORO_main(int
argc, char** argv) {...}, defined in <os/main.h> which contains your program :
#include <os/main.h>
int ORO_main(int argc, char** argv)
{
// Your code, do not use 'exit()', use 'return' to
// allow Orocos to cleanup system resources.
}
If you link with the liborocos-<target>.a library, this function will be called after the run-time environment is set up. To put in other words, the Orocos library already contains a main() function which
will call the user-defined ORO_main() function.

Example 2.1. A Makefile for an Orocos Application


You can then simply compile your program with a Makefile resembling this one :
CXXFLAGS=-O2 -I/usr/local/orocos/include -D_REENTRANT
LDFLAGS=-L/usr/local/orocos/lib -lorocos-<target> -lstdc++ lpthread -lboost_signals -lreadline -lncurses
all: myprogram.cpp
g++ myprogram.cpp ${CXXFLAGS} ${LDFLAGS} -o myprogram
As you can see, your application does not have to be inside the packages directory at all. Applications can be developped fully out of the Orocos (build) directory.

Important
The LDFLAGS option must be placed after the .cpp or .o files in the gcc command.
Furthermore, the -D_REENTRANT option is necessary if Orocos is also compiled with
this flag ( which is the default ).

Note
Make sure you have read Section 2, Configuration for your target if you application
has compilation or link errors ( for example when using LXRT ).

3.3. Header Files Overview


People new to the eCos build system might be unfamiliar with locating the headers they need. Especially if you want to develop your own package or have more control on what is happening inside
Orocos, these files will be needed.

Table 2.3. Header Files


Header

Summary

pkgconf/*.h

All these headers contain every configured item


of the configuration tool, collected per package.
If you need inside information on how an Orocos
package is configured, these files contain all the
18

Installing Orocos

Header

Summary
defines, which were used when the libtarget.a
was compiled. The macros are documented in
the .cdl files and displayed by the configuration
tool.

pkgconf/system.h

This header contains the definition of each installed package. The syntax is always of the type
OROPKG_* and quite straightforward. This file
also contains the version numbers of each installed package.

pkgconf/os.h

This file, which is created by the os package,


contains all the interfaces the current OS implements. They are defined as OROINT_OS_* to
denote the capability of the OS. They are documented in the os.cdl file. For example,
OROINT_OS_STDVECTOR denotes that the
standard C++ vector implementation is available
for the selected OS.

corelib/*.hpp

The corelib headers are documented by Doxygen


and the CoreLib manual. Most Orocos applications will use events, tasks and timing from the
CoreLib.

os/*.h, os/*.hpp

The os headers describe a limited set of OS


primitives, like locking a mutex or creating a
thread. Read the OS manual carefully before using these headers, they are mostly used internally
in the corelib's implementation.

execution/*.hpp

The program and command execution headers


enable parsing and execution of script programs
and state machines. It provides a framework for
setting up logical tasks which can communicate
in a peer-to-peer way.

control_kernel/*.hpp

The Feedback Control Kernel and Components


Headers. The Open Robot Control Software
framework, a framework for implementing configurable, interactive control loops. Uses much
of the fucntionality of the execution package.

device_drivers/*.h[pp]

Headers of any device drivers you enabled and


some common headers.

device_interface/*.hpp

Headers of the C++ device interfaces for Orocos,


such as encoders, IO, and Sensors.

geometry/*.h[pp]

The Orocos Geometry library defines Frames,


Vectors, Twists, Paths, VelocityProfiles,... for
2D and 3D path planning.

kindyn/*.hpp

An implementation of a stateful and stateless


Kinematics Component for 6DOF robots.

can/*.hpp, comedi/*.hpp

Headers for Comedi and CANOpen device communication.

4. Installation Technical Details


This section groups some technical installation instructions for Orocos developers.

4.1. Introduction to the eCos toolset


19

Installing Orocos

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

Set the environment variable :


export ECOS_REPOSITORY=/path/to/orocos-0.20.1/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.

2.

Install any new .epk packages using


ecosadmin.tcl add filename.epk
in 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.

3.

Go to the eCos build directory. In Orocos this is :


orocos-0.20.1/build/packages
But technically, it can be any directory where you create an orocos.ecc configuration file.

4.

Start a new build using the


ecosconfig new gnulinux

corelib-os

command. Where gnulinux or lxrt is the target name and corelib-os is the template
name. A template selects a number of packages which belong together. You are allowed to put
your own template in orocos-0.20.1/packages/templates. All this information
(with default options) is written in the orocos.ecc file

Warning
This will overwrite any previous orocos.ecc configuration file in that directory !
5.

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); done
They 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.

6.

Select additional packages for your build with the graphical configtool or with
ecosconfig add package_name
Configtool will give you a list of all available packages in the repository if you go to menu>Build->Packages and
ecosconfig list
does 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.

7.

Configure the selected packages with the graphical program


20

Installing Orocos

configtool orocos.ecc
Save and exit.
8.

Create the makefiles using


ecosconfig tree
The build tree is setup now. You need to run this command each time you change the configuration in the above step !

9.

Compile using
make

10. The results (headers and libtarget.a) are in the install directory.

4.2. Installing Orocos from Subversion (similar to CVS)


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.ac.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
[http://svn.mech.kuleuven.ac.be/browse/orocos].
The next step requires that you use automake [http://www.gnu.org/software/automake/] version
1.7.3 and autconf [http://www.gnu.org/software/autoconf/] version 2.54 or later. You can check
your versions with the --version option:
$ automake --version
automake (GNU automake) 1.7.3
...
$ 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.

21

Chapter
3.
Abstraction

Operating

System

This document gives a short overview of the philosophy and available classes for Operating System
( threads, mutexes, etc ) interaction within Orocos

1. Introduction
1.1. Realtime OS Abstraction
The OS package makes an abstraction of the operating system on which it runs. It provides C++ interfaces to only the minimal set of operating system primitives that it needs: mutexes, condition
variables and threads. This is in accordance with the general developers requirements of the project:
a minimalistic approach is much easier to scale, to maintain, and to port. The abstraction also allows
Orocos users to build their software on all supported systems with only a recompilation step. The
OS Abstraction layer is not directly being used by the application writer. Basic OS primitives are
leading programmers to often to pitfalls which can be avoided using well known solutions. These
solutions are implemented in the CoreLib classes and allow the programmer to think in a more natural way about the problem.
The abstractions cause (almost) no execution overhead, because the wrappers can be called inline.
See the OROBLD_OS_AGNOSTIC option in the configuration tool to control inlining.

2. The Operating System Interface


2.1. Basics
Keeping the Orocos core portable requires an extra abstraction of some operating system (OS) functionalities. For example, a thread can be created, started, paused, scheduled, etc., but each OS uses
other function calls to do this. Orocos prefers C++ interfaces, which led to the PeriodicThreadInterface interface and PeriodicThread class as the abstractions of a periodic
thread. The SingleThread is a non periodic thread which executes the functionality once each
time it is started. Each OS implements in its os/<OS-name-here> directory the specific system
calls to get the desired effect. The Mutex and MutexRecursive classes in combination with the
MutexLock and MutexTryLock classes are also commonly used abstractions in multithreaded
frameworks.
This drawing situates the Operating System abstraction with respect to device driver interfacing (DI)
and the rest of Orocos

Figure 3.1. OS Interface overview

22

Operating System Abstraction

3. OS Package Structure
The OS package has always a common directory for all common interfaces and configuration options. This directory is OS independent but may contain assembler instructions ( such as Compare
And Swap ) for specific processor architectures. However, if Orocos grows to support many different architectures, an arch directory will most likely be introduced to sort files for specific architectures. Next to the common directory is a <target> directory which contains the abstraction implementations for a specific OS. Multiple OS abstractions may be available, but only one can be configured and compiled in one configuration.

3.1. The RTAI/LXRT OS target


RTAI/LXRT is an environment that allows user programs to run with realtime determinism next to
the normal programs. The advantage is that the realtime application can use normal system libraries
for its functioning, like showing a graphical user interface.
An introduction to RTAI/LXRT can be found in the Porting to LXRT HOWTO
[http://people.mech.kuleuven.ac.be/~psoetens/lxrt/portingtolxrt.html], which is a must- read if you
don't know what LXRT is.
The common rule when using LXRT is that any userspace (GNU/Linux) library can be used and any
header included as long as their non-realtime functions are not called from within a hard realtime
thread. Specifically, this means that all the RTAI (and Orocos) OS functions, but not the native
Linux ones, may be called from within a hard realtime thread. Fortunately these system calls can be
done from a not hard realtime thread within the same program. Using the Orocos provided C++
primitives is in any case correct.

3.2. Porting Orocos to other Architectures / OS'es


The OS package is the only part of Orocos that needs to be ported to other Operating Sytems or processor architectures in case the target supports Standard C++. The os/common directory contains
code common to all OS'es and the architecture dependent headers (for example atomic counters and
compare-and-swap ). The latter must be extended to support other processor architectures. Also,
when a processor architecture is added, it must be made available in the os.cdl configuration file, allowing the user to change the target processor.
The easiest way to port Orocos to another operating sytem, is to copy the gnulinux package into a
new target and start modifying the functions to match those in your OS. This package is recommended because it uses the standard Unix/Posix functions.
23

Operating System Abstraction

3.3. OS Header Files


The following table gives a short overview of the available headers in the OS package.

Table 3.1. Header Files


Library

Which file to include

Remarks

os/fosi.h

Include this file if you want to


make system calls to the underlying operating system ( LXRT,
GNU/Linux ) .

Standard Template Library


(STL) and Standard C++ Library

<any_header>

All headers can be included, exceptions and iostreams may not


be used from a hard realtime
thread.

Streams

os/rtstreams.hpp

Defines rt_std::cout which allows realtime printing, with limited functionality

C Math Library

math.h

OS Abstraction classes

Mutex.hpp, MutexLock.hpp,
Semaphore.hpp, PeriodicThread.hpp, SingleThread.hpp,
main.h

OS functionality

Fully supported
The available C++ OS primitives. main.h is required to be included in your ORO_main()
program file.

4. Using Threads and Realtime Execution of


Your Program
4.1. Writing the Program main()
All tasks in the realtime system have to be performed by some thread. The OS abstraction expects
an int ORO_main(int argc, char** argv) function (which the user has written) and
will call that after all system initialisation has been done, meaning thread creation or setting up the
realtime primitives. Inside ORO_main() the user may expect that the system is properly set up and
can be used. The resulting libtarget.a library will contain the real main() function which will call the
ORO_main() function.

Important
Do not forget to include <os/main.h> in the main program file, or the linker will
not find the ORO_main function.

Note
Using global objects ( or static class members ) which use the OS functions before
ORO_main() is entered (because they are constructed before main() ), can come into
conflict with an uninitialised system. It is therefor advised not to use static global objects which use the OS primitives. Events in the CoreLib are an example of objects
which should not be constructed as global static. You can use dynamically created (i.e.
created with new ) global events instead.

4.2. The Orocos Thread System


24

Operating System Abstraction

4.2.1. Periodic Threads


An Orocos thread, which must execute a task periodically, is defined by the PeriodicThreadInterface. The most common operations are start(), stop() and setting the periodicity.
What is executed is defined in the RunnableInterface. It contains three methods : initialize(), step() and finalize(). You can inherit from this interface to implement your own
functionality. In initialize(), you put the code that has to be executed once when the component is
start()'ed. In step(), you put the instructions that must be executed periodically. In finalize(), you put
the instructions that must be executed right after the last step() when the component is stop()'ed.
Orocos delivers one implementation of the PeriodicThreadInterface :
PeriodicThread, which creates a thread according to the operating system you are compiling
for. However, you are encouraged NOT to use it! The CoreLib uses this class as a basis to provide a
more fundamental task-based (as opposite to thread based) execution mechanism which will insert
your periodic tasks in a periodic thread. It is provided in the corelib/task package.
Common uses are :

Running periodic control tasks.

Fetching periodic progress reports.

Running the corelib periodic tasks.

4.2.2. Non Periodic Threads


For non-periodic threads, which block or do lenghty calculations, the SingleThread class can be
used. The corelib/task package uses the SingleThread for the TaskNonPeriodic. Porting applications
to Orocos might benifit this class in a first adaptation step. It has a start() method, which will invoke
one single call to loop() ( in contrast to step() above). It can be re-started each time the loop() function returns. The initialise() and finalise() functions are called when the SingleThread is started and
after loop() returns.
The user himself is responsible for providing a mechanism to return from the loop() function. The
SingleThread expects this mechanism to be implemented in the RunnableInterface::breakLoop()
function, which must return true if the loop() function could be forced to return. Hence, SingleThread will call breakLoop() in its stop() method. The SingleThread::isRunning() function can be used to check if loop() is being executed or not.

Note
The TaskNonPeriodic in CoreLib provides a better integrated implementation for
SingleThread and should be favourably used.
Common uses are :

Listening for data on a network socket.

Reading a file or files from harddisk.

Waiting for user input.

Execute a lengthy calculation.

React to asynchronous events.

The user of this class must be aware that he must provide himself the locking primitives (like
ORO_OS::Mutex) to provide thread safety.
25

Operating System Abstraction

4.2.3. ThreadScope : Oscilloscope Monitoring of Orocos Threads


You can configure the OS package using the configtool to report thread execution as block-waves
on the parallel port or any other digital output device. Monitoring through the parallel port requires
that the Device Drivers Standard IO Ports package is installed, and for Linux based OS'es, that you
execute the Orocos program as root.
If the CoreLib Logger is active, it will log the mapping of Threads to the device's output pins to the
orocos.log file. Just before step() is entered, the pin will be set high, and when step() is left, the
pin is set low again. From within any (CoreLib) task, you may then additionally use the ThreadScope driver as such :
DigitalOutInterface* pp = DigitalOutInter#
face::nameserver.getObject("ThreadScope");
if ( pp )
pp->setBit( this->getTask()->thread()->threadNumber(), value );

which sets the corresponding bit to a boolean value. The main thread claims pin zero, the other pins
are assigned incrementally as each new Orocos thread is created.

4.3. Synchronisation Primitives


Orocos OS only provides a few synchronisation primitives, mainly for guarding critical sections.

4.3.1. Mutexes
There are two kinds of Mutexes : Mutex and MutexRecursive. To lock a mutex, it has a method lock(), to unlock, the method is unlock() and to try to lock, it is trylock(). A lock() and
trylock() on a recursive mutex from the same thread will always succeed, otherwise, it blocks.
For ease of use, there is a MutexLock which gets a Mutex as argument in the contstructor. As long
as the MutexLock object exists, the given Mutex is locked. This is called a scoped lock.

Example 3.1. Locking a Mutex


The first listing shows a complete lock over a function :
Mutex m;
void foo() {
int i;
MutexLock lock(m);
// m is locked.
// ...
} // when leaving foo(), m is unlocked.
Any scope is valid, so if the critical section is smaller than the size of the function, you can :
Mutex m;
void bar() {
int i;
// non critical section
{
MutexLock lock(m);
// m is locked.
// critical section
} // m is unlocked.
// non critical section
//...
}
26

Operating System Abstraction

4.3.2. Signals and Semaphores


Orocos provides a C++ semaphore abstraction in os/Semaphore.hpp. It is used mainly for non
periodic, blocking tasks or threads. The higher level Event implementation in CoreLib can be used
for thread safe signalling and data exchange in periodic tasks.
Semaphore sem(0); // initial value is zero.
void foo() {
// Wait on sem, decrement value (blocking ):
sem.wait()
// awake : another thread did signal().
// Signal sem, increment value (non blocking):
sem.signal();
// try wait on sem (non blocking):
bool result = sem.trywait();
if (result == false ) {
// sem.value() was zero
} else {
// sem.value() was non-zero and is now decremented.
}
}

4.3.3. Compare And Swap ( CAS )


CAS is a fundamental building block of the CoreLib package for inter-thread communication and
must be implemented for each OS target. See the Lock-Free sections of the CoreLib manual for
Orocos classes which use this primitive.

27

Chapter 4. Core Library


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.

1. Introduction
This section describes the semantics of the services available in the Orocos CoreLib Package.
The Orocos OS package allows Orocos users to build their software on all supported systems with
only a recompilation step. This library, the CoreLib, provides fully thread-safe C++ implementations for (periodic) tasks, synchronous/asynchronous Events, time measurement, simulations and
provides interfaces which are common for all realtime services. The CoreLib imposes a hard realtime architecture. The goal of this fixed architecture is to keep applications deterministic, by avoiding the classical pitfalls of letting application programmers freely choose the priorities of their
tasks, and their communication primitives. Practice has indeed showed that most programmers do
not succeed in strictly decoupling the functional and algorithmic parts of their code from the OSspecific primitives used to execute them.
Of course, the realtime performance depends not only on the underlying operating system but also
on the hardware. Hardware devices are abstracted in the Orocos Device Interface package.
The following sections will first introduce the reader to creating (periodic) Tasks in the system. Furtheron, they are extended with Events. The following sections explain usefull classes which are used
throughout the framework such as the HeartBeatGenerator, Properties, Commands, Conditions and
the object NameServer.

2. Periodic and non Periodic Tasks


Threads are the major cause of headaches in multithreaded systems programmers heads. Synchronisation, data protection, priority tweaking and resource locking must all be done very carefully and
mostly lead to suboptimal solutions. Even more, the predictability of the system highly decreases
with the introduction of more threads or interactions between threads. This section gives an introduction to defining periodic tasks which run together with other periodic tasks of the same priority
in the same periodic thread. Non periodic tasks can run in these threads or are implemented using a
non periodic thread.

2.1. Periodic Tasks Run in Periodic Threads


A basic control application needs for example, a high priority periodic thread and a low priority
periodic thread. The high priority thread is used for all periodic tasks that need to be executed atomically. No action in this thread will ever be preempted by another thread. The low priority realtime
thread is used for all periodic tasks which may be preempted but still have hard deadlines. It can always be preempted by the high priority thread. There is also a non realtime (periodic) thread which
gives no deadline guarantees. For convenience, Orocos provides implementations for these three
types of threads ( which only differ in periodicity and priority ) and allows to extend to more for
your specific needs.

2.2. Creating a Periodic Thread


Orocos provides, by default, two hard realtime periodic threads and a non realtime periodic thread.
The ZeroTimeThread has the highest priority, the ZeroLatencyThread has a lower priority
and the NonRealTimeThread has an even lower priority is not realtime at all (but still periodic).
If they are needed, Orocos will create and start/stop them automatically, thus the user does not need
to know or take care of them.

28

Core Library

Important
You must configure the priority and periodicity of these three threads in the configtool
program.
For users needing to solve multi-threaded control problems, the PriorityThread is provided with
which you can create an arbitrary number of threads with no more than one thread per priority level.
It needs a bit more setup than the standard Orocos threads, since you still have to set the priority and
start the thread. An example is given below. A PriorityThread is not automatically started like the
ZeroTimeThread, ZeroLatencyThread and NonRealTimeThread. It must be done by the user. Furthermore, The PriorityThread is the general case of the above three cases, since its priority can
match their priorities as given in the configuration tool.

Example 4.1. Example Periodic Thread Creation


This example shows how to create the PriorityThread.
#include "corelib/PriorityTask.hpp"
using namespace ORO_CoreLib;
class KineLoop : public RunnableInterface
{
bool initialize() { // ...
}
void step() {
}
void finalize() {
}
// ...
};
ORO_main( int argc, char** argv)
{
// Define your tasks (see later)
KineLoop kine_loop;
// An extra thread for low priority tasks
// 9 : The priority.
// 0.01 : The period.
// You have to manually start the thread.
PriorityThread<9>::Instance()->setPeriod( 0.01 );
PriorityThread<9>::Instance()->start();
// Optional :
PriorityThread<9>::Instance()->makeHardRealtime();
// This task is run in the extra thread above,
// kine_loop inherits from RunnableInterface :
PriorityTask<9> own_task( 0.05, &kine_loop ); // 0.05 is multiple
of 0.01
own_task->start();
// ...
own_task->stop();
return 0;
}

2.3. Creating a Periodic Task


29

Core Library

If you want to execute functionality in one of the Orocos threads, you need to create a Task of a certain type, depending on the thread type. The table below summarises which Task type there is per
thread.

Table 4.1. Thread and Task summary


Thread

Task

TimerThread

PeriodicTask

ZeroTimeThread

TaskNonPreemptible

ZeroLatencyThread

TaskPreemptible

NonRealTimeThread

TaskNonRealTime

PriorityThread< N >

PriorityTask< N >

SimulationThread

SimulationTask

There are two ways to run functionality in a periodic task. By :

Implementing the RunnableInterface in another class ( functions initialize, step and finalize ). The RunnableInterface object (i.e. run_impl) can be assigned to a task using
task.run( &run_impl )
or at construction time of a Task :
TaskNonPreemptible task( period, &run_impl );
.
#include <corelib/RunnableInterface.hpp>
#include <corelib/TaskNonPreemptible.hpp>
class MyPeriodicTask
: public ORO_CoreLib::RunnableInterface
{
public:
// ...
bool initialize() {
// your init stuff
double myperiod = this->getTask()->getPeriod();
// ...
return true; // if all went well
}
void step() {
// periodic actions
}
void finalize() {
// cleanup
}
};
// ...
MyPeriodicTask run_impl;
TaskNonPreemptible task( 0.01 ); // 100Hz
task.run( &run_impl );
task.start();
// etc...

30

Core Library

Inheriting from a Task class and overriding the initialize, step and finalize methods.
class MyOtherPeriodicTask
: public TaskNonPreemptible
{
public :
MyOtherPeriodicTask()
: TaskNonPreemptible( 0.01 ) // 100Hz
{
}
bool initialize() {
// your init stuff
double myperiod = this->getPeriod();
// ...
return true; // if all went well
}
void step() {
// periodic actions
}
void finalize() {
// cleanup
}
// ...
};
// When started, will call your step
MyOtherPeriodicTask task;
task.start();

The Task will detect if it must run an external RunnableInterface. If none was given, it will call its
own virtual methods.

2.4. Periodic Task Ordering


Periodic Tasks are executed in the order as they are started. The periodic thread responsible for the
Task will execute all tasks one after the other, respecting the periodicity of the periodic task. This
means that a Task with a lower periodicity of the thread (e.g. 10 times lower) will only be called a
fraction of the time (thus every 10th period), still respecting the ordering.

Figure 4.1. Execution sequence diagram

31

Core Library

2.5. Example Periodic Task Creation


Example 4.2. Example Periodic Task Creation
This example shows how all kinds of tasks can be created. When a task is started it will add itself to
the correct thread.
#include
#include
#include
#include

"corelib/TaskNonPreemptible.hpp"
"corelib/TaskPreemptible.hpp"
"corelib/TaskNonRealTime.hpp"
"corelib/PriorityTask.hpp"

using namespace ORO_CoreLib;


ORO_main( int argc, char** argv)
{
// Define your tasks

32

Core Library

// ...
// These tasks are run in the Orocos Thread Model
TaskNonPreemptible fast_task1(0.001, &vel_loop);
TaskNonPreemptible fast_task2(0.001, &vel_loop);
TaskPreemptible slow_task(0.01, &pos_loop);
TaskNonRealTime nrt_task( 0.1, &display_server );
// This task is run in the extra thread above
PriorityTask<9> own_task( 0.05, &kine_loop ); // 0.05 is multiple
of 0.01
// All is transparant from here on.
fast_task1->start();
fast_task2->start(); // is always run directly after fast_task1 !
slow_task->start();
own_task->start();
nrt_task->start();
// ...
fast_task1->stop();
fast_task2->stop();
slow_task->stop();
own_task->stop();
nrt_task->stop();
return 0;
}

2.6. Periodic Threads Overview


The high priority thread is the ZeroTimeThread. It will execute all TaskNonPreemptible
Tasks synchronically. You can create your own not preemptable task by inheriting from this class.
Its name is derived from the fact that some tasks need to be executed in an infinite small amount of
time to work correctly. Control loops are an example of this. To come as close as possible to this
(impossible) constraint, we make sure that the task is never preempted by another task and thus is
executed 'atomically'.
The low priority tasks are executed by the ZeroLatencyThread class. It will execute all
TaskPreemptible Tasks sequentially, when no non-preemptible tasks are executed. Every
TaskPreemptible can be preempted by a TaskNonPreemptible but not by another
TaskPreemptible. The ZeroLatencyThread has this name because the zero time constraint is
dropped, but replaced by the constraint that no latency may occur and thus, execution is still realtime. Again, to satisfy this constraint, only deterministic time operations may be done in this thread.
For not realtime executions, as there are userspace communication, memory allocations,... we use
the NonRealTimeThread. Roughly put, you can do anything in this thread, as long as it takes finite
time. This is the lowest priority thread in the system and it should never lock a resource of the realtime thread. Tasks being executed in the NonRealTimeThread are called TaskNonRealTime.
The last standard thread type Orocos provides is the SimulationThread which runs SimulationTasks. It is special in that it executes all its tasks as fast as possible ( thus without periodic
sleeps ) and adjusting the system's clock between each step(). The latter allows correct timing measurement in the tasks running in a SimulationThread. The SimulationThread runs by default notrealtime,
but
this
can
be
changed
by
calling
the
SimulationThread::Instance()->makeHardRealtime() function.
The SimulationThread is started likewise the PriorityThread above (but without the template parameter). Its priority and periodicity can be changed with the configuration tool, or before its started.
#include "corelib/SimulationTask.hpp"
33

Core Library

using namespace ORO_CoreLib;


ORO_main( int argc, char** argv)
{
// Define your tasks
// ...
// Manually start the simulation thread
// 0.001 : The (virtual) period : no task can run 'faster' than
this.
SimulationThread::Instance()->periodSet( 0.001 );
SimulationThread::Instance()->start();
// Optional, might hang your program :
SimulationThread::Instance()->makeHardRealtime();
// fast_sim_task will measure 0.001s elapses between each step(),
// slow_sim_task will measure 0.01s elapses in time between each
step()
SimulationTask fast_sim_task(0.001, &vel_loop);
SimulationTask slow_sim_task(0.01, &pos_loop);
// All is transparant from here on.
fast_sim_task->start();
slow_sim_task->start();
// ...
fast_sim_task->stop();
slow_sim_task->stop();
return 0;
}

Warning
If other threads are running in the same program executable, they will also 'notice' the
fast system time changes if the SimulationThread is started. It is thus advisable not to
mix SimulationThreads with other threads. Also, any thread with lower priority than
the SimulationThread will never run.

2.7. Non Periodic Tasks Run in Their Own Thread


Non periodic ( if blocking ) tasks can only run in non periodic threads, or ( if non blocking ) after all
periodic tasks of a certain periodic thread. The latter to avoid excessive jitter in periodic task execution.
If you want to create a task which reads file-IO, or displays information or does any other possibly
blocking operation, the TaskNonPeriodic implementation can be used. When it is
start()'ed, its loop() method will be called exactly once and then it will stop, after which it can
be started again. Analogous to periodic tasks, the user can implement initialize(), loop()
and finalize() functions in a RunnableInterface which will be used by the task for executing the user's functions. Alternatively, you can reimplement said functions in a derived class of
TaskNonPeriodic. The task's loop() will be executed in its own thread, thus creating a TaskNonPeriodic means creating a new thread :
int priority = 5;
TaskNonPeriodic task( priority, blocking_task );
task.start(); // calls blocking_task->initialize()
// now blocking_task->loop() is called in a thread with priority
34

Core Library

5.
After loop() returns, finalize() is called. TaskNonPeriodic differs from the periodic tasks in the way
it is stopped. Since the task may be blocked in the user's loop() function, the user should reimplement the RunnableInterface::breakLoop() function. This funcion must do whatever necessary to let the user's loop() function return. It must return true on success, false if it was unable to
let the loop() function return (the latter is the default implementation's return value).
There is another use for TaskNonPeriodic, it can be used to wait for Events (see Section 3, Events
). If a TaskNonPeriodic is not given a RunnableInterface, and it's methods are not overridden in a
derived class, it will call it's EventProcessor's loop() method and wait for Asynchronous Events to
process. If stop() is called in this case, the task will break from the EventProcessor's loop() and be
stopped. If you want to process Events in your own RunnableInterface implementation, you need to
manually call the step() method of the processor() :
// in your implementation :
void loop() {
while ( ... ) {
// < do non periodic stuff >
// process any pending Events :
this->getTask()->processor()->step();
}
}
// ...
If you call processor()->loop() instead, the EventProcessor will loop for events until it is
stopped, which might not be what you want. So use step() to process all pending Events.

2.8. Event Driven Tasks


An alternative way to use non periodic tasks is to use the ORO_CoreLib::TaskEventDriven,
which can be bound to an Event (see Section 3, Events ) and each time the Event is fired up, the
step() method will be invoked asynchronously in a given thread. This will happen from the moment
the task's start method is called until the stop method is called. The following example shows how
such a task can be run asynchronously.
#include <corelib/TaskEventDriven.hpp>
//...
Event<void(void)> task_event;
TaskEventDriven task( &task_event, ZeroTimeThread::Instance(),
&run_obj);
task_event.fire(); // nothing happens
task.start();
// initialize()s task
task_event.fire(); // task's step() will be executed once in
ZeroTimeThread
task.stop();

// finalize()s task

2.9. Priority Inversions


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

Core Library

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 4.2, Classes Possibly Subject to
Priority Inversion lists the know uses of Orocos which might lead to priority inversion.

Table 4.2. Classes Possibly Subject to Priority Inversion


Class/method
BufferCircular,
Locked

Rationale
BufferSimple,

DataObjectUses Mutex for serialising concurrent access. Alternative Lock-free implementations are possible.

Event::fire()
Uses Mutex for serialising concurrent access. No
Alternative implementation is possible, callback
execution must be serialised.
PeriodicTask::start(), PeriodicTask::stop()
( Applies to PriorityTask, TaskNonRealtime,
TaskPreemptible and TaskNonPreemptible).
Uses Mutex for serialising concurrent access. Alternative implementation might be possible in
certain cases, however, since stop() guarantees
that finalize() will be called after the last step(),
it will block until the step() returns and then call
finalize().
Logger
Uses Mutex for serialising concurrent access. No
Alternative implementation is possible, IO must
be serialised.
Table 4.3, 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 4.3. 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 incomming Commands.
BufferLockFree
Uses a many writers, multiple readers Lock-free
CAS implementation. A read returns the oldest
written value in a FIFO way.

36

Core Library

2.10. Collecting Thread Information from Tasks


Each Orocos Task (periodic, non periodic and event driven) type has a thread() method in its interface which gives a non-zero pointer to a ThreadInterface object which provides general
thread information such as the priority and periodicity and allows to control the realtimeness of the
thread which runs this task. A non periodic task's thread will return a period of zero.
A RunnableInterface can get the same information through the this->getTask()->thread()
method calls.

3. Events
An Event is an object to which one can connect callback functions. When the Event is raised, the
connected functions are called one after the other. An Event can carry data and deliver it to the function's arguments. Orocos allows two possibilities of calling the function : synchronous and asynchronous. The former means that when the raise method is called, all synchronous handlers are
called in the same thread. The latter means that the data is stored and the callback function is called
in another thread. The thread which will execute the deferred callback is chosen at connection time.

Figure 4.2. Event Handling

Task T1 runs in PeriodicThread (a) while Task T2 runs in PeriodicThread (b). At some time, T1 registers a synchonous and asynchronous callback function ( reaction ) with task T2's Event. When T2
fires (emits) the Event, the synchronous Event callback is executed first ( within Thread (b) ), while
the asynchronous ( deferred ) callback is queued and executed after T1 has run, in Thread (a).
The Orocos Event system has been adapted since version 0.18 to use the boost::signals
[http://www.boost.org/] library. An Orocos Event is derived from the boost::signal class. A
tutorial [http://www.boost.org/doc/html/signals.html] is located on the boost webpage.
The Orocos Event extends the boost signal with asynchronous event handling. Any kind of function
can be connected to the event as long as it has the same signature as the Event. The 'raise' or 'emit'
method of an Orocos Event is called fire().
37

Core Library

Example 4.3. Using Events


This example shows how a synchronous and asynchronous handler are connected to an Event.
#include <corelib/Event.hpp>
using boost::bind;
class SafetyStopRobot
{
public:
void handle_now() {
// Synchronous Handler code
std::cout << " Putting the robot in a safe state fast !" <<
std::endl;
}
};
Class NotifyUser
{
public:
void handle_later() {
//Asynchronous Completer code
std::cout << "The program stopped the robot
!"<<std::endl;
}
};
...
SafetyStopRobot safety;
NotifyUser
notify;
// The <..> means the callback functions must be of type "void
foo(void)"
Event<void(void)> emergencyStop;
Handle emergencyHandle;
// boost::bind is a way to connect the method of an object instance
to
// an event.
std::cout << "Register apropriate handlers to the Emergency Stop
Event\n";
emergencyHandle =
emergencyStop.connect( bind( &SafetyStopRobot::handle_now,
&safety),
bind( &NotifyUser::handle_later, &notify)
);
std::cout << "Fire the event\n";
emergencyStop.fire();
// Disconnecting the callbacks...
emergencyHandle.disconnect();
// Add only synchronous callback :
emergencyHandle =
emergencyStop.connect( bind( &SafetyStopRobot::handle_now,
&safety) );
std::cout << "Doing a quiet safety stop..."<<std::endl;
emergencyStop.fire(); // User not notified
...

Register apropriate handlers to the Emergency Stop Event


38

Core Library

Fire the event


Putting the robot in a safe state fast !
The program stopped the robot !
Doing a quiet safety stop...
Putting the robot in a safe state fast !

If you want to find out how boost::bind works, see the Boost bind manual
[http://www.boost.org/libs/bind/bind.html]. You must use bind if you want to call C++ class member functions to 'bind' the member function to an object :
ClassName object;
boost::bind( &ClassName::FunctionName, &object)
Where ClassName::FunctionName must have the same signature as the Event. When the Event is
fire( args )'d,
object->FunctionName( args )
is executed by the Event.
When you want to call free ( C ) functions, you do not need bind :
Event<void(void)> event;
void foo() { ... }
event.connect( &foo );

Whether your handle() and complete() methods contain deterministic code or not is up to
you. It depends on the choice of the Event type and in which thread it is executed. A good rule of
thumb is to make all Synchronous handling/completing deterministic time and do all the rest in the
Asynchronous part, which will be executed by the another thread.
You must choose the type of Event upon construction. This can no longer be changed once the
Event is created. The type is the same for the synchronous and asynchronous methods. If the type
changes, the fire() method must given other arguments. For example :

Example 4.4. Event Types


Event<void(void)> e_1;
e_1.fire();
Event<void(int)>
e_2.fire( 3 );

e_2;

Event<void(double,double,double)>
positionEvent.fire( x, y, z);

positionEvent;

Furthermore, you need to setup the connect call differently if the Event carries one or more arguments :
SomeClass someclass;
Event<void(int, float)> event;
// notice that for each Event argument, you need to supply _1,
_2, _3, etc...
event.connect( boost::bind( &SomeClass::foo, someclass, _1, _2 );
event.fire( 1, 2.0 );
39

Core Library

Important
The return type for synchronous and asynchronous callbacks is ignored and can not be
recovered from fire(), which always returns void.

3.1. Choosing the Asynchronous Thread


The Event implementation provides one thread for asynchronous execution. The Orocos Tasks
package provides four additional threads for executing the asynchronous callbacks.

Note
For brevity, we will not use boost::bind in the following examples and only use 'free' (
C ) functions as callbacks. Asynchronous callbacks are bound in the same way as synchronous callbacks ( Example 4.3, Using Events ).
In the example above, there was aparantly no thread choosen. The default thread which executes
asynchronous callbacks is called the Completion Processor. This is a non realtime thread, which
means that the reaction time is not bounded. If you want to execute the callback in another thread,
an additional argument can be given in the connect method :
event.connect(&syn_func, &asyn_func, Zero#
LatencyThread::Instance() );
The above lists how the ZeroLatencyThread will execute the asyn_func if event is fired(). It will do
this after it has processed all its tasks. The other Orocos threads can do this likewise :
event.connect(&syn_func, &asyn_func, ZeroTimeThread::Instance()
);
event.connect(&syn_func, &asyn_func,
meThread::Instance() );
event.connect(&syn_func, &asyn_func,
cessor::Instance() ); // Default
event.connect(&syn_func, &asyn_func,
Thread<N>::Instance() );
event.connect(&syn_func, &asyn_func,

NonRealTi#
CompletionPro#
Priority#
&nonperiodic_task );

If you would write above listings in a real program, on event.fire(), the syn_func will be
called directly five times. The asyn_func will be called in each thread once, possibly preempting itself.
It is also possible to only have the asyn_func called. In this case the synopsis is :
event.connect( &asyn_func, ZeroLatencyThread::Instance() );
to distinguish from a synchronous callback connection. In this case there is no default, so if you
wish to use the CompletionProcessor, you must specify it explicitly.
event.connect( &asyn_func, CompletionProcessor::Instance() );

For convenience, the Orocos Task threads can also be choosen in another way by specifying the
Task :
TaskNonPreemptible my_task;
event.connect(&syn_func, &asyn_func, &my_task );
or even :

40

Core Library

RunnableInterface* my_function = ... ;


// put my_function in a task;
event.connect(&syn_func, &asyn_func, my_function->getTask() );
The above says that the asyn_func function should be executed after the my_function's task execution period. This is a very powerfull way of synchronising function calls in different threads. One
should be aware that a Task is not always executed with every period of the Thread, meaning that
the asyn_func could be called before the task is run, or even multiple times in between a task run.

Note
Asynchronous event handlers can have no more than 6 arguments in the current implementation, but more can be easily added.

3.2. Private EventProcessors for Periodic Tasks


Asynchronous handlers are called in an EventProcessor. Each Orocos thread has such an EventProcessor to handle the deferred callbacks of all its tasks. A Periodic task can also have its a private
EventProcessor which handles only its deferred callbacks after each step() and only when it is started. In this way, it resembles better how a non periodic task handles its defered event callbacks. To
enable this behavior, a boolean flag must be given upon construction of a PeriodicTask or any derived task-types :
RunnableInterface* my_function = ... ;
TaskNonPreemptible mytask( 0.01, my_function, true );
able private eventproc.

// en#

event.connect(&syn_func, &asyn_func, my_function->getTask() );


event.fire(); // calls only syn_func.
mytask.start();
event.fire(); // calls syn_func and after mytask's step(),
asyn_func.
The private EventProcessor is disabled by default.

3.3. Event Overrun Policy


An Event can only be fire()'d by one thread at the same time. The synchronous handlers will always
be executed as much times as the event is fire()'d. This is not the case for asynchronous handlers. If
an Event is fire()'d multiple times before the completion thread executes, the asynchronous handler
will be called only once in the completion thread's execution step.
The question that rises is with which arguments this handler is called. The user can choose between
the first (default) and the last. The first is choosen as default because this causes the least overhead
in execution time. To choose which policy is used, an optional parameter can be given during connect :
event.connect( &asyn_func, mytask, Event::OnlyLast );
event.connect( &asyn_func, mytask, Event::OnlyFirst ); // default
event.connect( &asyn_func, mytask ); // same as previous line

3.4. The Completion Processor


The Completion Processor is implemented using the Singleton design pattern, analogous to the
periodic task threads. It is the lowest priority, not realtime thread in the Orocos framework. It will
execute all asynchronous event callbacks that have to be completed when no other work has to be
done. The only constraint it imposes is that all functions it executes must require finite time to com41

Core Library

plete (it cannot detect timeouts). You can get its thread pointer like this :
#include <corelib/CompletionProcessor.hpp>
CompletionProcessor::Instance()-> ...
The CompletionProcessor is a TaskNonPeriodic, thus not consuming time resources when no Events
need to be processed. If you need a hard realtime CompletionProcessor, use a default TaskNonPeriodic ( see Section 2.7, Non Periodic Tasks Run in Their Own Thread ) and call its makeHardRealTime() method.

4. Time Measurement and Conversion


4.1. The TimeService
The TimeService is implemented using the Singleton design pattern. You can query it for the
current (virtual) time in clock ticks or in seconds. The idea here is that it is responsible for synchronising with other (distributed) cores, for doing, for example compliant motion with two robots. This
functionality is not yet implemented though.
When the SimulationThread is used and started, it will change the TimeService's clock with each
period ( to simulate time progress). Also other threads (!) In the system will notice this change, but
time is guaranteed to increase monotonously.

4.2. Usage Example


Also take a look at the interface documentation.
#include <corelib/TimeService.hpp>
#include <corelib/Time.hpp>
TimeService::ticks timestamp = TimeSer#
vice::Instance()->getTicks();
//...
Seconds elapsed = TimeService::Instance()->secondsSince(
timestamp );

5. Properties
5.1. Introduction
Properties are well known in object oriented programming languages. They are used to store primitive data (float, strings,...) in a 'PropertyBag', which can be changed by the user and has immediate
effect on the behaviour of the program. Changing parameters of an algorithm is a good example
where properties can be used. Each parameter has a value, a name and a description. The user can
ask any PropertyBag for its contents and change the values as they see fit. Java for example presents
a Property API. The Doxygen Property API should provide enough information for succesfully using them in your Software Component.

Note
Reading and writing a properties value can be done in realtime. Every other transaction, like marshalling, demarshalling or building the property is not a realtime operation.

Example 4.5. Using properties


42

Core Library

...
// a property, respresening a double of value 1.0:
Property<double> myProp("Parameter A","A demo paramet#
er", 1.0); // not realtime !
myProp = 10.9; // realtime
double a = myProp.get(); // realtime
...

Properties are mainly used for two purposes. First, they allow an external entity to browse their contents, as they can form hierarchies using PropertyBags. Second, they can be written to screen, disk,
or any kind of stream and their contents can be restored lateron, for example after a system restart.
The next sections give a short introduction to these two usages.

5.2. Grouping Properties in a PropertyBag


First of all, a PropertyBag is not the owner of the properties it owns, it merely keeps track of them, it
defines a logical group of properties belonging together. Thus when you delete a bag, the properties
in it are not deleted, when you clone() a bag, the properties are not cloned themselves. PropertyBag
is thus a container of pointers to Property objects.
If you want to duplicate the contents of a PropertyBag or perform recursive operations on a bag, you
can use the helper functions we created and which are defined in PropertyBag.hpp (see Doxygen documentation). These operations are however, most likely not realtime.

Note
When you want to put a PropertyBag into another PropertyBag, you need to make a
Property<PropertyBag> and insert that property into the first bag.
Use add to add Properties to a bag and find or getProperty<T> to get a pointer to a PropertyBase or Property<T> :
PropertyBag bag;
Property<double> w("Weight", "in kilograms", 70.5 );
Property<int> pc("PostalCode", "", 3462 );
struct BirthDate {
BirthDate(int d, month m, int y) : day(d), month(m), year(y)
{}
int day;
enum { jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov,
dec} month;
int year;
};
Property<BirthDate> bd("BirthDate", " in 'BirthDate' format",
BirthDate(1, apr, 1977));
bag.add( &w );
bag.add( &pc );
bag.add( &bd );
PropertyBase* weight = bag.find("PostalCode");
weight != 0 )

// assert(

Property<BirthDate>* bd_bis =
bag.getProperty<BirthDate>("BirthDate"); // assert( bd_bis != 0 )

43

Core Library

5.3. Marshalling
(Serialization)

and

Demarshalling

Properties

Marshalling is converting an object from machine code to a code suitable for transportation or storage. When an object is marshalled, a copy is made so that it can be restored in its original state. Demarshalling instantiates the object again from the marshalled copy. Common formats of marshalling
are writing out properties or efficient binary memory copies. So properties are just an example of
objects that can be marshalled. We wrote however specific marshallers for properties and property
bags. These are the SimpleMarshaller, XMLMarshaller, XMLRPCMarshaller, INIMarshaller and
the CPFMarshaller. You will need the Xerces [http://xml.apache.org/xerces-c/index.html] library for
the XML related marshalling.
The (de-)marshallers know how to convert native C++ types, but if you want to store your own
classes in a Property ( like BirthDate in the example above ), then you need to provide two additional methods to the Orocos Property System : composeProperty and decomposeProperty. You'll need to consult the API documentation on how to fill in these two functions and an example.
If you are not interested in marshalling your own types, you can enable the 'Default Marshalling' option in the configuration tool, which will generate a default for these two functions for type T in any
file where you use a Property<T>.

6. The NameServer
6.1. Introduction
A key element in the Orocos framework is what we call the strong typed nameserver. It is a (string
based) nameserver which stores name, object pairs of only one type of object in the local program.
Off course, polymorphism allows us to collect many derivative types into one nameserver. A
nameserver allows late configuration of objects. All possible used objects are created first and stored
in the nameserver. Depending of the run-time users choice (from a text file, console input,...), another object is retrieved from the nameserver and used in the program.

6.2. Using the NameServer


The header is called NameServer.hpp and the API is quite straight forward. The most common
usage syntax is given below. The Doxygen documentation contains the full API.

Note
The most common use of nameserving is keeping track of pointers to objects. A
NameServer almost always takes pointers to an object as arguments and returns a
pointer when the object is looked up again.
// A NameServer collecting pointers to ClassA objects
NameServer< ClassA* > nameserver;
ClassA my_a;
nameserver.registerObject( &my_a, "ATeam" );
// ...
ClassA* an_a = nameserver.getObject( "ATeam" );
if (an_a != 0 )
cout << "ATeam was successfully stored and retrieved !" >>
endl;
A typical use of nameserving is that the nameserver is nested inside the class it is nameserving itself. For convenience, the constructor of that class is then extended to take a string as argument to
indicate the (optional) desired name of the object. Imagine that the above ClassA had such a nested
nameserver, in that case, it would be used as follows :
44

Core Library

ClassA my_a( "The ATeam" ); // give name in constructor


// ...
// notice the scope ClassA:: the nameserver is nested in :
ClassA* an_a = ClassA::nameserver.getObject( "The ATeam" );
if (an_a != 0 )
cout << "The ATeam was successfully stored and retrieved !" >>
endl;
The above technique is used in many classes inside Orocos. Events, Devices, Control Kernels and
Components, ... anything you wish to configure at runtime can be nameserved.

7. Buffers, DataObjects and DataSources


Orocos provides some basic inter-thread buffering mechanisms in the corelib/buffers package. The user should be aware of Section 2.9, Priority Inversions when using the 'locked' buffers
(which use a Mutex).
The difference between Buffers and DataObjects is that DataObjects are single writer / many readers, while buffers allow many writers and readers. Furthermore, a DataObject always returns the last
value written (and a write always succeeds), while a buffer may implement a FIFO queue to store all
written values (and thus can get full). The DataSourceBase, DataSource<T> and AssignableDataSource<T> are interface definitions to define how data of any type can be exchanged across objects or threads. DataObjects implement the DataSource interfaces.

Figure 4.3. DataObjects versus Buffers

DataObjects are most suitable for single writer, many readers, and always return the last written
value. Buffers have a fixed queue length and are used for processing all produced data by any number of producers and consumers.

7.1. Buffers
There are two kinds of buffers presently available in Orocos. The byte buffers and the typed buffers.
45

Core Library

The former write a number of bytes and might get deprecated. The latter is a templated (typed) buffer which can write any kind of data ( thus also chars ) to a threadsafe buffer.
The following buffers are available :

BufferSimple, BufferCircular : Are byte buffers, may block and are subject to priority inversions
( Section 2.9, Priority Inversions ).

BufferLockFree< T > : Is a typed buffer of type T and works as a FIFO queue for storing elements of type T. It is lock-free, non blocking and read and writes happen in bounded time. It is
not subject to Section 2.9, Priority Inversions.

7.2. DataObjects
The data inside the DataObjects can be any valid C++ type, so mostly people use classes or structs,
because these carry more semantics than just (vectors of) doubles. The default constructor of the
data is called when the DataObject is constructed. Here is an example of creating and using a
DataObject :

Example 4.6. Accessing a DataObject


#include <corelib/DataObjectInterfaces.hpp>
// A DataObject may also contain a class, instead of the simple
// double in this example
DataObjectLockFree<double> my_Do("MyData");
my_Do.Set( 3.14 );
double contents;
my_Do.Get( contents );
// contents == 3.14
contents = my_Do.Get(); // equivalent

The virtual DataObjectInterface interface provides the Get() and Set() methods that
each DataObject must have. Semantically, Set() and Get() copy all contents of the DataObject.
This interface has multiple implementations, depending on the specific data access locking needs:

DataObject. This is the most simple DataObject implementation. The Get() and Set() 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() and Get() 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 accessing Get() will block if the
Set() thread is accessing the contents. It is mainly used for sharing data between two kernels,
running at different priorities.

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 accessing


Get() will never block.
46

Core Library

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.

7.3. DataSources
The DataSource 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. This interface is used in the TaskContext Infrastructure to define how tasks can exchange data.
The DataObjectInterface implements the DataSource interface, and thus all Orocos DataObject
types are DataSources.

8. Logging
Orocos applications can have pretty complex startup and initialisation code. A logging framework
helps to track what your program is doing. Logging can only be done in the non-realtime parts of
your application, thus not in the Realtime Periodic Tasks ! Section 9, Reporting is meant for that.
There are currently 8 loglevels :

Table 4.4. Logger Log Levels


ORO_LOGLEVEL

Logger::enum

Description

-1

na

Completely disable logging

Logger::Never

Never log anything (to console)

Logger::Fatal

Only log Fatal errors. System


will abort immediately.

Logger::Critical

Only log Critical or worse errors. System may abort shortly


after.

Logger::Error

Only log Errors or worse errors.


System will come to a safe stop.

Logger::Warning

[Default] Only log Warnings or


worse errors. System will try to
resume anyway.

Logger::Info

Only log Info or worse errors.


Informative messages.

Logger::Debug

Only log Debug or worse errors.


Debug messages.

You can change the amount of log info printed on your console by setting the environment variable
ORO_LOGLEVEL to one of the above numbers :
export ORO_LOGLEVEL=5
The default is level 4, thus only warnings and errors are printed.
The minimum log level for the orocos.log file is Logger::Info. It will get more verbose if
you increase ORO_LOGLEVEL, but will not go below Info. This file is always created if the logging
47

Core Library

infrastructure is used. You can inspect this file if you want to know the most usefull information of
what is happening inside Orocos.
If you want to disable logging completely, use
export ORO_LOGLEVEL=-1
before you start your program.
For using the Logger class in your own application, consult the API documentation.

Example 4.7. Using the Logger class


#include <corelib/Logger.hpp>
Logger::log() << Logger::Error << "An error Occured : " << 333 <<
"." << Logger::endl;
Logger::log() << Logger::Debug << debugstring << data << Log#
ger::endl;
Logger::log() << " more debug info." << data << Logger::endl;
As you can see, the Logger can be used like the standard C++ input streams. You may change the
Log message's level using the Logger::... enums. The above message could result in :
0.123 [ERROR] An error Occured : 333
0.124 [Debug] <contents of debugstring and data >
0.125 [Debug] more debug info. <...data...>

9. Reporting
Having a realtime process running is one thing, knowing what its internal status is another. The reporting classes are made in such a way that existing infrastructure can be extended with a Reporting
Stub (ReportExporter ), which creates reports of the internal state of variables and waits for
client requests to update and export the data. A client can then ask each existing Stub to create and
deliver a report. A timestamp is used to tag all data. When the client has collected all reports, it may
transform it to another format, for example, in a log file or a display on screen. We call these clients
ReportWriters since they write out the gathered reports in one or another format ( this is called
marshalling ).
An example of an application which uses the CoreLib reporting infrastructure is the Orocos Control
Kernel framework.

10. Fifos
10.1. A warning
Warning
The fifos implementation is slightly outdated and unmaintained in the latest releases.
You might expect problems when trying to use them. In the past they were used to
communicate from kernel space to userspace programs, but since the Orocos Framework is now completely situated in userspace, this communication has become obsolete. To implement your own kernelspace/userspace communication, use the functions
of your OS.
For
RTAI/LXRT,
also
read
the
LXRT
[http://people.mech.kuleuven.ac.be/~psoetens/lxrt/portingtolxrt.html].
48

howto

Core Library

10.2. Using fifos


Fifos are used to send data from one address space to another. For example from realtime to userspace or vice versa. We have four kind of fifos :

FifoRTIn : Used to read data in realtime from a realtime fifo

FifoRTOut: Used to write data in realtime to a realtime fifo

FifoUSIn : Used to read data in userspace from a realtime fifo

FifoUSOut: Used to write data in userspace to a realtime fifo

Furthermore, one can still use the FifoRTIn/Out in userspace simulations. They will act as if they
get their data from real fifos. The API documentation should be clear about how to use them.
Components requireing data communition will indicate this with a WriteInterface ,ReadInterface or ObservableReadInterface in their constructors argument list. All fifos implement one of these interfaces.

Note
For examining which data would be sent through a fifo, one can always temporarily
use a WriteCout object instead of a fifo, which will print the data to the screen instead of delivering it.

49

Chapter
5.
Interfaces

Hardware

Device

This document provides a short introduction to the Orocos Hardware Device Interface definitions.
These are a collection of classes making abstraction of interacting with hardware components.

1. The Orocos Device Interface (DI)


Designing portable software which should interact with hardware is very hard. Some efforts, like
Comedi [http://http://stm.lbl.gov/comedi/] propose a generic interface to communicate with a certain
kind of hardware (mainly analog/digital IO). This allows us to change hardware and still use the
same code to communicate with it. Therefore, we aim at supporting every Comedi supported card.
We invite you to help us writing a C++ wrapper for this API and port comedilib (which adds more
functionality) to the realtime kernels.
We do not want to force people into using Comedi, and most of us have home written device
drivers. To allow total implementation independence, we are writing C++ device interfaces which
just defines which functionalities a generic device driver should implement. It is up to the developers to wrap their C device driver into a class which implements this interface. You can find an
example of this in the devices package. This package only contains the interface header files. Other
packages should always point to these interface files and never to the real drivers actually used. It is
up to the application writer to decide which driver will actually be used.
STRUCTURE The Device Interface can be structured in two major parts : physical device interfaces
and logical device interfaces. Physical device interfaces can be subdivided in four basic interfaces:
Analog Input, Analog Output, Digital Input, Digital Output. The major difference is that analog
devices are addressed with a channel as parameter and write a ranged value, while digital devices
are addressed with a bit number as parameter and a true/false value. Logical device interfaces represent the entities humans like to work with: a drive, a sensor, an encoder, etc. They put semantics
on top of the physical interfaces they use underneath. You just want to know the position of a positional encoder in radians for example. Often, the physical layer is device dependent (and thus nonportable) while the logical layer is device independent.

Figure 5.1. Device Interface Overview

50

Hardware Device Interfaces

EXAMPLE An example of the interactions between the logical and the physical layer is the logical
encoder with its physical counting card. An encoder is a physical device keeping track of the position of an axis of a robot or machine. The programmer wishes to use the encoder as a sensor and just
asks for the current position. Thus a logical encoder might choose to implement the SensorInterface which provides a read(ReadType & ) function. Upon construction of the logical
sensor, we supply the real device driver as a parameter. This device driver implements for example
AnalogInInterface which provides read(ReadType & data, unsigned int
chan) and allows to read the position of a certain encoder of that particular card. Of course, we
could also put a simulation of an encoder in place of the device driver for testing purposes.

2. The Device Interface Classes


The most common used interfaces for machine control are already implemented and tested on multiple setups. All the Device Interface classes reside in the ORO_DeviceInterface namespace.

2.1. Physical IO
There are several classes for representing different kinds of IO. Currently there are:

Table 5.1. Physical IO Classes


Interface

Description

AnalogInInterface

Reading analog input channels

AnalogOutInterface

Writing analog output channels

DigitalInInterface

Reading digital bits

DigitalOutInterface

Writing digital bits

CounterInterface

Not implemented yet

EncoderInterface

A position/turn encoder

51

Hardware Device Interfaces

2.2. Logical Device Interfaces


From a logical point of view, the generic SensorInterface<T> is an easy to use abstraction for
reading any kind of data of type T.
The Device Interface packages does however not provide implementations and thus the logical
devices are implemented in the Device Driver package and ORO_DeviceDriver namespace. Examples are Axis, DigitalInput, DigitalOutput and AnalogDrive.

3. Porting Device Drivers to Device Interfaces


The methods in each interface are well documented and porting existing drivers (which mostly have
a C API) to these should be quite straightforward. It is the intention that the developper writes a
class that inherits from one or more interfaces and implements the corresponding methods. Logical
Devices can then use these implementations to provide higher level functionalities.

4. Interface Name Serving


Name Serving is introduced in the Orocos CoreLib documentation.
The Device Interface provides name serving on interface level. This means that one can ask a certain interface by which objects it is implemented and retrieve the desired instance. No type-casting
whatsoever is needed for this operation. For now, only the physical device layer can be queried for
entities, since logical device drivers are typically instantiated where needed, given an earlier loaded
physical device driver.
Example 5.1, Using the name service shows how one could query the AnalogOutInterface.

Example 5.1. Using the name service

unsigned int pos;


new FancyCard("CardName"); // FancyCard implements AnalogOutInter#
face
AnalogOutInterface* card = AnalogOutInter#
face::nameserver.getObject("CardName");
card->read(pos, 0);
// Read some data
delete card;

52

Chapter 6. Hardware Device Drivers


This document explains what device drivers are standard available in Orocos.

1. Introduction
This section gives you the basic knowledge to start working with the Orocos Device Drivers package.

1.1. Requirements
This package has a dependency on the Orocos Core Library and Orocos-device_interface packages.
So you need to install that package first before you can proceed. Further, this package extends the
comedi [http://www.comedi.org] library and integrates it into the framework. Comedi is a realtime
device drivers package and supports a lot of hardware. You might want to install comedi and
comedilib when they support your hardware or if you want to port your home made drivers to
comedi. We refer to their website for installation instructions.
The device drivers come with kernel modules that use the (realtime) OS. RTAI/LXRT offers realtime execution in kernel and userspace and a mechanism to communicate between both (called
LXRT Extensions). If you want to convert your own device driver to RTAI/LXRT like within Orocos,
you
can
read
the
Porting
to
LXRT
HOWTO
[http://people.mech.kuleuven.ac.be/~psoetens/lxrt/portingtolxrt.html].

1.2. What's in this package

This package contains a primitive wrapper to the kernel comedi functionality and comedilib
(userspace).

A home made driver for the APCI1710, APCI2200 and APCI1032 PCI cards and a C++ classes
for reading/resetting the cards and some event handling.

An Axis implementation.

A CANOpen implementation using CANPie [http://www.microcontrol.net/CANpie/]. Currently


reading/writing Digital/Analog IO are supported and reading SSI encoders over the bus. Our
setup CAN node was a Beckhoff coupler, with these terminals installed.

Classes for reading SSI encoders and incremental encoders.

A sample implementation for wrapping the whole Comedi Library to C++

Device driver and classes for reading the JR3 6 DOF Force Sensor

The aim of this package is to show how a driver can implement one or more interfaces of the Device
Interface Package. Other packages will exclusively refer to the device interfaces, and the decision of
which driver to actually use, can be deferred until run time.
All device drivers consist of two major parts. First a C kernel module, with realtime support, to
communicate with the hardware and offer a C interface to 'the outside'. Second a C++ class using
the C interface and representing the device as a C++ object. This class uses the Orocos Core Library
for more advanced features like polling or nameserving. It also implements one of the Orocos
Device Interface, so that the hardware is properly abstracted.
Most device drivers are configurable in the configtool menus so that they match the card configuration.

53

Hardware Device Drivers

2. Developers Documentation
All the drivers code resides in the ORO_DeviceDriver namespace. An Orocos Device Driver is
a class which implements one or more interfaces of the Device Interface
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-device-interface.html].

2.1. Comedi C++


We have wrapped Comedi to C++ implementations. It provides ComediDevice, ComediSubDeviceAIn and ComediSubDeviceAOut classes which provide basic device access and
nameserving. The wrapper classes ComediSubDeviceAOut and ComediSubDeviceAIn
show how the AnalogOutInterface and the AnalogInInterface interfaces of the Device
Interface can be implemented. Likewise, the wrapper classes ComediSubDeviceDOut and
ComediSubDeviceDIn show how the DigitalOutInterface and the DigitalInInterface interfaces of the Device Interface can be implemented.

2.2. Logical Device Drivers


This contains implementation of logical devices such as DigitalInput, DigitalOutput, AnalogInput,
AnalogOutput, Axis, AnalogDrive,... they are access-wrappers around the more general IO Interfaces. They require the corresponding Device Interface to communicate with.

2.2.1. Analog and Digital IO


Helpfull utility classes AnalogInput, AnalogOutput, DigitalInput and DigitalOutput provide a 1
channel access to a device interface. They take on construction two parameters : the device interface
they should operate on and the channel of that device they represent.

2.2.2. The Axis class


The Axis is parameterised by an Encoder, a Drive and Breaks. It manages these resources in a consistent way, allowing callibration and unit ( velocity to volts ) conversions. The Axis is needed to interpret encoder 'counts' to pysical units (translation or rotation). It associates three dependent hardware parts in one object.

2.3. CANPie and CANOpen Device Drivers


Orocos has an application independent implementation of a part of the CANOpen protocol, especially for IO and reading SSI encoders. CANPie is used as the controller abstraction and a linux kernel module for the Philips SJA 1000 is provided. The device driver works in realtime with RTAI/
LXRT. Node Guarding and proper setup/cleanup are implemented using the Orocos Core Library.
Class documentation can be found on the Doxygen pages.

54

Chapter 7. The Task Infrastructure


This document describes the Orocos Task Infrastructure, which allows to design Real-Time tasks
which transparantly communicate with each other.

1. Introduction
This manual documents how multi-threaded tasks can be defined in Orocos such that they form a
thread-safe robotics/machine control application. The overall idea of the Task Infrastructure Package and the mechanisms behind it are published in a 6 page technical report
[http://www.orocos.org/pub/task-infrastructure04.pdf] which can be found on the Orocos website.

Figure 7.1. Tasks Run in Threads

Tasks may run in the same (periodic) thread, in which case they may also share the same Processor.
A task is a basic unit of functionality which executes one or more (realtime) programs in a single
thread. The program can vary from a mere C function over a realtime program script to a realtime
hierarchical state machine. The focus is completely on thread-safe time determinism. Meaning, that
the system is free of priority-inversions, and all operations are lock-free (also data sharing and other
forms of communication such as events and commands). This is done using the mechanisms of the
Orocos Real-Time CoreLib which allows each task to run in its own thread or share threads. RealTime tasks can communicate with non Real-Time tasks (and vice versa) transparantly.
The Orocos Task Infrastructure enables :

Lock free, thread-safe, inter-thread function calls.

Communication between hard Real-Time and non Real-Time threads.

Deterministic execution time during communication for the higher priority thread.

Synchronous and asynchronous communication between threads.

55

The Task Infrastructure

C++ class implementations for all the above.

This document relates to other manuals as such :


CoreLib
provides the Event infrastructure, Task-thread mapping, CommandInterface and lock-free queueing implementations.
Program Processor
provides a binary program-tree, state machines and command
execution framework. In short, it can execute realtime programs
which are composed of command objects.
Program Parser
provides a scripting language which is convertible to a binary
form which can be accepted by the Program Processor. Also
provides usefull templates to create realtime command objects
with truely minimal effort.
Control Kernel
is an application which uses all of the above and provides an
(although complex) example of how the Task Infrastructure
Package can be used in practice.
You should read the Processor and Program Parser manuals for more technical (C++) details.

2. An Introductory Example to TaskContexts


This section introduces tasks through the "taskintro" application, which can be downloaded from
Orocos.org. It contains 3 TaskContexts, each showing some features and possibilities. The 'FactoringTask' waits for a command to factor a number in its primes. When done, it fires an event with the
result. The 'ReactiveTask' is a periodic task which waits for the events to happen when it is running.
The 'PeriodicTask' can count to a certain number in each periodic step, when given the command to
do so. We show some example commands, but the application has more methods and commands
than presented here.
The way we interact with TaskContexts during development of an Orocos application is through the
TaskBrowser. The TaskBrowser is a powerful console tool which helps you to explore, execute
and debug TaskContexts in running programs. All you have to do is to instantiate a TaskBrowser
and enter its loop() method. When the program is started from a console, the TaskBrowser takes
over user input and output.

Note
The TaskBrowser is located in the Orocos Program Parser package and not in the
Task Context package.

#include <execution/TaskBrowser.hpp>
// ...
int ORO_main( int, char** )
{
// Create your tasks
TaskContext* task = ...
// when all is setup :
TaskBrowser tbrowser( task );
tbrowser.loop();
return 0;
56

The Task Infrastructure

The TaskBrowser uses the GNU readline library to easily enter commands to the tasks in your system. This means you can press TAB to complete your commands and the up arrow to scroll through
previous commands.
$ ./taskintro
0.003 [Info] Parsing file CountingSM.osd
0.069 [Info] Loading StateMachine counterMachine
0.074 [Info] ReactiveTaskContext reacting to PrimeEvent !
This console reader allows you to browse and manipulate TaskCon#
texts.
You can type in a command, datasource, method, expression or
change variables.
(type 'help' for instructions)
TAB completion and HISTORY is available ('bash' like)
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :

The first lines are printed by the Orocos ORO_CoreLib::Logger, which has been configured to display informative messages to console. You can always watch the log file 'orocos.log' in the same
directory to see all messages.
Depending on what you type, the TaskBrowser will act differently. The builtin commands cd, help,
quit and ls are seen as commands to the TaskBrowser itself, if you typed something else, it tries to
evaluate your command to an expression and will print the result to the console. If you did not type
an expression, it tries to parse it as a command to a (peer) task. If that also fails, it means you made
a typo and it prints the syntax error to console.
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :1+1
Got :1+1
= 2

To display the contents of the current task, type ls, and switch to one of the listed peers with cd,
while cd .. takes you one peer back in history :
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :ls
PeriodicTask Attributes :
(Attribute ) int Counter
(Property
) PropertyBag ItemCollection
(Property
) std::string Parameter
(Attribute ) double SpeedOfLight
(Attribute ) int Target
PeriodicTask Objects
PeriodicTask Peers

:
:

this
FactoringTask ReactiveTask states

In Task PeriodicTask. (Status of last Command : none )


(type 'ls' for context info) :cd FactoringTask
Switched to : FactoringTask
In Task FactoringTask. (Status of last Command : none )
(type 'ls' for context info) :ls
57

The Task Infrastructure

FactoringTask Attributes :
(Attribute ) int Priority
FactoringTask Objects
FactoringTask Peers

:
:

this
PeriodicTask ReactiveTask

In Task FactoringTask. (Status of last Command : none )


(type 'ls' for context info) :cd ..
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :

Note
To get a quick overview of the commands, type help.
Let's revisit the output of ls :
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :ls
PeriodicTask Attributes :
(Attribute ) int Counter
(Property
) PropertyBag ItemCollection
(Property
) std::string Parameter
(Attribute ) double SpeedOfLight
(Attribute ) int Target
PeriodicTask Objects
PeriodicTask Peers

:
:

this
FactoringTask ReactiveTask states

First you get a list of the Properties and Attributes (alphabetical) of the current TaskContext. Propeties can be written to disk, attributes are solely for script interaction. Each of them can be
changed, except task constants. The current task has only one object: 'this'. The 'this' object serves
like a public interface of the TaskContext. Objects can only contain methods, commands or datasources. Last, the peers are shown, two other tasks and 'states', which denotes that this TaskContext
has a StateMachine. Likewise, a peer task 'programs' contains all loaded programs. To get a list of
the Task's interface, you can always type an object name, for example 'this' :
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) : this
Got :this
Command
: bool countTo( int Target )
Count to a given number using a StateMachine program.
Target : Number to count to.
DataSource : bool isRunning( )
Is this GenericTaskContext started ?
Method
: bool assert( bool MustBeTrue )
Assert will get your program in the error state if the argument
is false.
MustBeTrue : The result of a boolean expression which must be
true.
Method
: bool loadProgram( const& std::string Filename )
Load an Orocos Program Script from a file.
Filename : An ops file.
Method
: bool loadStateMachine( const& std::string Filename )
Load an Orocos State Description from a file.
Filename : An osd file.
Method
: bool readProperties( const& std::string Filename )
Read Properties from a file.
58

The Task Infrastructure

Filename : A CPF formatted XML file.


Method
: bool start( )
Start this GenericTaskContext.
Method
: bool stop( )
Stop this GenericTaskContext.
Method
: bool unloadProgram( const& std::string ProgramName )
Unload an Orocos Program Script from a file.
ProgramName : The Program's name.
Method
: bool unloadStateMachine( const& std::string StateMa#
chineName )
Unload an Orocos State Description from a file.
StateMachineName : A name of a Root StateMachine instantiation.
Method
: bool writeProperties( const& std::string Filename )
Write or update Properties to a file.
Filename : A CPF formatted XML file.

The 'this' object is special in that the command or method must not be prefixed by an object name
and that it apears as if the method is called directly on the task :
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :ReactiveTask.stop()
Got :ReactiveTask.stop()
= true

'Methods' and 'DataSources' are executed directly by the TaskBrowser itself, and then it prints the
result. The return value of stop() was a boolean, which is 'true'.
When a 'Command' is entered, it is sent to the TaskContext itself, which will execute it in its own
thread. The different stages of its lifetime are displayed by the prompt. Hitting enter will refresh the
status line.
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :countTo(1000)
Got :countTo(1000)
In Task PeriodicTask. (Status of last Command : queued)
(type 'ls' for context info) :2589.528 [Info] PeriodicTaskContext
counts to 1000.
In Task PeriodicTask. (Status of last Command : busy )
(type 'ls' for context info) :
In Task PeriodicTask. (Status of last Command : done )
(type 'ls' for context info) :

A Command might be rejected (return false) by the receiving TaskContext :


In Task PeriodicTask. (Status of last Command : done )
(type 'ls' for context info) :countTo(-20)
Got :countTo(-20)
In Task PeriodicTask. (Status of last Command : queued )
(type 'ls' for context info) :
In Task PeriodicTask. (Status of last Command : fail )
(type 'ls' for context info) :

59

The Task Infrastructure

Besides giving commands to tasks, you can alter the attributes of any task, program or state machine. The TaskBrowser will confirm validity of the assignment with 'true' or 'false' :
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :ls states.counterMachine
counterMachine Attributes :
int multiplier
counterMachine Objects
counterMachine Peers

:
:

this
states

In Task PeriodicTask. (Status of last Command : none )


(type 'ls' for context info) :states.counterMachine.multiplier =
20
Got :states.counterMachine.multiplier = 20
= true
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :states.counterMachine.multiplier
Got :states.counterMachine.multiplier
= 20
In Task PeriodicTask. (Status of last Command : none )
(type 'ls' for context info) :countTo(200)
Got :countTo(200)
In Task PeriodicTask. (Status of last Command : queued )
(type 'ls' for context info) :60.272 [Info] PeriodicTaskContext
counts to 200.
In Task PeriodicTask. (Status of last Command : done )
(type 'ls' for context info) :

Last but not least, hitting TAB twice, will show you a list of possible completions, such as peers or
commands :
In Task PeriodicTask. (Status of last Command : fail )
(type 'ls' for context info) :
Counter
cd ..
readProperties
FactoringTask.
countTo
start
ItemCollection
help
states.
Parameter
isRunning
stop
ReactiveTask.
loadProgram
unloadProgram
SpeedOfLight
loadStateMachine
unloadStateMachine
Target
ls
writeProperties
assert
peers
cd
quit
(type 'ls' for context info) :

TAB completion works even across peers, such that you can type a TAB completed command to another peer than the current peer.
The TaskBrowser is application independent, so that your enduser-application might need a more
suitable interface. However, for testing and inspecting what is happening inside your realtime programs, it is a very useful tool. The next sections show how you can add properties, commands,
methods etc to a TaskContext.

3. Setting Up a Basic Task


60

The Task Infrastructure

Tasks are rather simple entities in Orocos and are implemented by the TaskContext class, which
should not be confused with the CoreLib periodic Task implementations. It is useful speaking of a
context because it defines the context in which the task operates. It defines the interface of the task,
its attributes, its peer tasks and uses a Processor to handle its programs and to accept commands
from other tasks.
A TaskContext is constructed as :

#include <execution/Processor.hpp>
#include <execution/TaskContext.hpp>
// we assume this is done in all the following code listings :
using namespace ORO_Execution;
Processor
a_processor, b_processor;
TaskContext a_task("ATask", &a_processor);
TaskContext b_task("BTask", &b_processor);
// task creates own (default) Processor :
TaskContext c_task("CTask");

The first argument is the (unique) name of the task, the second argument is its Processor, which
is optional. If none is given, the Processor will be constructed (and deleted) by the TaskContext itself.
A task's interface consists of : Commands, Methods, DataSources and Attributes, which are all public. We will refer to them as members. The TaskContext exports the members (to other tasks) by
means of object factories. Which members are exported is up to the application. Upon construction,
all factories are empty. A task factory hosts object factories, that is, a TaskContext contains objects,
which have Commands, Methods and/or DataSources, which all map to the C++ functions or variables of the task.

Figure 7.2. Schematic Overview of a TaskContext

61

The Task Infrastructure

The Execution Flow is formed by Programs and State Machines sending commands to Peer Tasks.
The Data Flow is the propagation of data from one task to another, where one producer can have
multiple consumers.
The object factories can be accessed as such :

a_task.commandFactory.getObjectFactory("objname");
a_task.methodFactory.getObjectFactory("objname");
a_task.dataFactory.getObjectFactory("objname");
a_task.attributeRepository.getAttribute<Type>("attrname");

3.1. The Method Factory


The easiest way to access a TaskContext's interface is through Methods. They resemble very much
normal C or C++ functions. They take arguments and return a value. The return value can in return
be used as an argument for other Methods or stored in a variable. For all details, we refer to the Orocos Program Parser Manual.
To add a method to the Method Factory, one can use the TemplateMethodFactory, to easily
create method factories :

#include <execution/TemplateFactories.hpp>
class ClassX
{
public:
62

The Task Infrastructure

void reset() { ... }


string getName() { ... }
double changeParameter(double f) { ... }
// ...
};
ClassX xobj;
TemplateMethodFactory<ClassX>* fact =
newMethodFactory( &xobj );
fact->add( "reset",
method( &ClassX::reset, "Reset the system." ) );
fact->add( "name",
method( &ClassX::getName,
"Read out the name of the system." ) );
fact->add( "changeP",
method( &ClassX::changeParameter,
"Change a parameter, return the old value.",
"New Value", "The new value for the paramet#
er." ) );
a_task.methodFactory.registerObject("objname", fact);
The first statement
TemplateMethodFactory<ClassX>* fact =
newMethodFactory( &xobj );
constructs a new TemplateMethodFactory, which will create methods for the object 'xobj'. A TemplateMethodFactory only provides access to one C++ object. After this factory is constructed, we
add methods to it using the add() function. The add() function requires a name for the added method, and information on the method to be associated with that name. The latter is generated using the
function method().
fact->add( "reset",
method( &ClassX::reset, "Reset the system." ) );
The method() function requires a pointer to a class function having a number of arguments, and
returning the appropriate result. The function's signature will automatically define the signature in
the exported API. After the class function pointer follows a C string containing a description of the
function, followed by a name and description for each of the arguments.
a_task.methodFactory.registerObject("objname", fact);
Finally, the factory is stored in the method interface of a_task, where it is added to the "objname"
methods.

Important
The methodFactory, and all other factories of a TaskContext take ownership of the registered or added objects. You are not allowed to delete them.
Using this mechanism, any method of any class can be added to a task's method interface. All methods are grouped in an object namespace ( here "objname" ), which can be served by different classes
( and thus factories ). To invoke this method from a script, one can then write :

63

The Task Infrastructure

do ATask.objname.changeP( 0.1 )
If the method should belong to the task's interface without an object, the "this" object name may be
given instead of "objname". Applied to the above factory, this would result in :

do ATask.changeP( 0.1 )
Any number of methods and objects can be added to a TaskContext. Also the methods of the
TaskContext itself (or its derived classes) may be added. The added methods keep their polymorphism, meaning that you may add pure virtual methods to the factory.

Figure 7.3. Template Factories and Classes UML Diagram

This diagram demonstrates how TemplateFactories can be flexibly applied to your class design.
They can represent a single object in the TaskContext interface, while it is implemented in different
classes ( here : ClassX and YourTask ). The "this" object stores the task's members.

3.2. Method Argument and Return Types


The arguments can be of any type. However, to be compatible with the Orocos Program Parser variables, it is best to follow the following guidelines :

Table 7.1. Method Return & Argument Types


C++ Type

In C++ functions passed by

Maps to Parser variable type

Primitive C types : double, int, value (no const, no reference )


bool, char

double, int, bool, char

C++
Container
types
: const &
std::string, std::vector<double>

string, array

Orocos Fixed Container types : value (no const, no reference )


ORO_CoreLib::Double6D,
ORO_Geometry::[Frame | Rotation | Twist | ... ]

double6d, frame, rotation, twist,


...

Other types than these listed in Table 7.1, Method Return & Argument Types (such as : unsigned
64

The Task Infrastructure

int, float, std::map, MyClass*, ... ) are allowed, but will be incompatible with parsed programs and
state machines, meaning, the result or arguments can not be stored in a parser variable, but can be
passed on from method to method, by using the return value of one method as argument of another
method. Also, using dynamic allocating types, will cause memory allocation allong the path, unless
proper action is taken since the parser copies all types by value.
A method can also take pointers as arguments or return pointers to objects, but the parser will
(unless properly extended) not be able to store them in a parser variable.

3.3. The Attribute Repository


A TaskContext may have any number of attributes, of any type. They can be used by programs in
the TaskContext to get (and set) configuration data. The repository allows to store any C++ value
type and also knows how to handle Property objects.

3.3.1. Adding Task Attributes


An attribute can be added in the task's AttributeRepository like this :

a_task.attributeRepository.addAttribute( "aflag", bool(false) );


a_task.attributeRepository.addAttribute( "max",
int(5) );
Property<std::string> param("Param","Param Description","Value");
a_task.attributeRepository.addProperty( &param );
// Put param also in a PropertyBag :
Property<PropertyBag> bagprop("Collection","Collection Descrip#
tion");
bagprop.get().add( &param );
a_task.attributeRepository.addProperty( &bagprop );
Which inserts an attribute of type bool and int, name 'aflag' and 'max' and initial value of false and 5
to the task's repository. Adding a Property is also straightforward. The same property is also added
in a bag which can also be added to the repository. The methods return false if an attribute with that
name already exists.
You can also add constants, which can then no longer be changed :

a_task.attributeRepository.addConstant( "pi", double(3.14) );


You should again be careful for duplicate entries.

Important
The attributeRepository, and all other factories of a TaskContext take ownership of the
registered or added objects. You are not allowed to delete them. Properties are an exception to this rule, they remain owned by you and you may only delete them after a
removeProperty().

3.3.2. Accessing Task Attributes in C++


To get a value from the repository, you can use :

TaskAttribute<bool>* attrb
= a_task.attributeRepository.getAttribute<bool>( "aflag" );
65

The Task Infrastructure

bool result = attrb->get();


assert( result == false );
TaskAttribute<std::string>* attrs
= a_task.attributeRepository.getAttribute<std::string>(
"Param" );
assert( attrs.get() == "Value" );

3.3.3. Accessing Task Attributes in Scripts


A program script can access the above attributes as in
// a program in "ATask" does :
var double pi2 = task.pi * 2.
var int
myMax = 3
set task.max = myMax
// Both assignments below change the same Property !
set task.Param = "B Value"
set task.Collection.Param = "C Value"
// an external (peer task) program does :
var double pi2 = ATask.pi * 2.
var int
myMax = 3
set ATask.max = myMax
When trying to assign a value to a constant, the script parser will throw an exception, thus before the
program is run. You must always specify the task's name (or 'task') when accessing a task's attribute,
this is different from methods and commands, which may omit the task's name if the program is running within the task.

Important
The same restrictions of Section 3.2, Method Argument and Return Types hold for
the attribute types, when you want to access them from program scripts.

3.3.4. Storing and Loading Task Properties


See Section 5.2, Task Property Configuration for storing and loading the Properties in a AttributeRepository to and from files, in order to save a TaskContext's state.

3.4. The Command Factory


The next factory we discuss is the Command Factory, which produces C++ CommandInterface
objects which encapsulate a command call to a TaskContext's interface. The philosophy behind
commands is explained in the CoreLib, which defines the CommandInterface and in the Program
Parser and Processor manuals, which provide easy to use implementations of this interface.
To add a command to the Command Factory, one can use the TemplateCommandFactory, to
easily create command factories :

#include <execution/TemplateFactories.hpp>
class ClassX
{
66

The Task Infrastructure

public:
bool startCycle() { ... }
bool cycleDone() const { ... }
bool cleanupMess(double f) { ... }
bool isMessCleaned() { ... }
// ...
};
Class_X x_obj;
TemplateCommandFactory<Class_X>* fact =
newCommandFactory( &x_obj );
fact->add( "startCycle",
command( &Class_X::startCycle,
&Class_X::cycleDone,
"Start a new cycle." ) );
fact->add( "cleanup",
command( &Class_X::cleanupMess,
&Class_X::isMessCleaned,
"Start cleanup operation.",
"factor", "A factor denoting the thorough#
ness." ) );
a_task.commandFactory.registerObject("objname", fact);
Commands differ from Methods in that they take an extra function which is called the Completion
Condition. It is a function which returns true when the command is done. The command itself also
returns a boolean which indicates if it was accepted or not. Reasons to be rejected can be faulty arguments or that the system is not ready to accept a new command.
fact->add( "startCycle",
command( &Class_X::startCycle,
&Class_X::cycleDone,
"Start a new cycle." ) );
Thus the command() function requires two member pointers instead of one. The first one is a
function that does the actual work that the command will invoke, and the second is a bool const
function having :

the same arguments as the command,

OR only the first argument of the command,

OR no arguments at all.

The second function (the completion condition) will be called to see whether the associated command is finished. The rest of the arguments to the command function remain the same. It also requires a constant C string describing the function, and two constant C strings giving a description
and name for every argument.

Important
The commandFactory, and all other factories of a TaskContext take ownership of the
registered or added objects. You are not allowed to delete them.
The above lets you write in a program script :

do objname.startCycle()
67

The Task Infrastructure

do objname.cleanupMess( 0.1 )
when the program is loaded in a_task.
Commands returning false will propagate that error to the program or function calling that command, which will cause the program to enter an error state, ie it stops its execution.

Important
The same restrictions of Section 3.2, Method Argument and Return Types hold for
the command and condition types, when you want to access them from program
scripts.

3.5. The DataSource Factories


DataSources are read-only objects which, when evaluated, return some data. The source of the data
can be anything, as long as it takes finite time to get it. DataSources can be combined to new DataSources using algorithmic expressions. The only way they differ from Methods, is that the corresponding C++ function must be const.

3.5.1. The Template DataSource Factory


To add a method to the DataSource Factory, one can use the TemplateDataSourceFactory,
to easily create DataSource factories :

#include <execution/TemplateFactories.hpp>
class ClassX
{
public:
int _number;
string getName() const { ... }
double power(double a, double b) const {}
// ...
};
ClassX xobj;
TemplateDataSourceFactory<ClassX>* fact =
newDataSourceFactory( &xobj );
fact->add( "number",
data( &ClassX::_number,
"the number." ) );
fact->add( "name",
data( &ClassX::getName,
"Read out the name." ) );
fact->add( "power",
data( &ClassX::power,
"return a to the power of b",
"a", "the base",
"b", "the exponent" ) );
a_task.dataFactory.registerObject("objname", fact);
which is dus nearly identical to using a method. Notice the usage of the _number class member. The
factory also accepts class member variables instead of const functions, but likewise, they are also
read-only.

Important
68

The Task Infrastructure

The dataFactory, and all other factories of a TaskContext take ownership of the registered or added objects. You are not allowed to delete them.

3.5.2. The Map DataSource Factory


If your application has set up DataSources itself (for example, using CoreLib DataObjects ), the
MapDataSourceFactory can be used to add them to you TaskContext's DataSource interface.
For example, continued from the program listing of the previous section :
DataObjectInterface<MyDataType>* myData
= new DataObjectLockFree<MyDataType>("MyData");
MapDataSourceFactory::Map storemap;
storemap["NewData_1"] = myData;
// store other DataSources...
// finally :
MapDataSourceFactory* mdf
= new MapDataSourceFactory( storemap, "Thread-safe DataOb#
jects");
a_task.dataFactory.registerObject("dataobjs", mdf);

Which uses the thread-safe DataObjectLockFree (which implements DataSource) to exchange data
between TaskContexts. You must use DataObjectLockFree to exchange any composite type
between threads.

3.5.3. Accessing DataSources from Scripts


In scripting, the braces are optional when no arguments must be given. An external task might read
the DataSources of "ATask" as such:

var int nb = ATask.objname.number


var double result = ATask.objname.power( 5., 6.)
task.newData = ATask.dataobjs.NewData_1
Again, the "this" object can be used if the DataSource should belong to the task's interface.

3.6. Creating Commands Without the Factories


It is possible to create a command without the Factory and DataSources. This is the prefered way if
you only intend to use C++ commands and no Orocos Program Scripts, which is possible on some
embedded systems.
The key is to use the CommandFunctor, which is explained in detail in the Program Processor
Manual [http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-processor.html].
Commands created this way can be used analogously as in the next sections.

Important
The same restrictions of Section 3.2, Method Argument and Return Types hold for
the datasource types, when you want to access them from program scripts.

4. Connecting Tasks
69

The Task Infrastructure

A Real-Time system contains multiple concurrent tasks which must communicate to each other.
TaskContext objects can be connected to each other so that they can communicate Real-Time data
or commands. We call them "Peers" as there is no fixed hierarchy. A connection from one TaskContext to its Peer can be uni- or bi-directional. In a uni-directional connection, only one peer can send
commands to the other, while in a bi-directional connection, both can send each other commands.
Peers are connected as such (hasPeer takes a string argument ):

// bi-directional :
a_task.connectPeers( &b_task );
assert( a_task.hasPeer( &b_task.getName() )
&& b_task.hasPeer( &a_task.getName() );
// uni-directional :
a_task.addPeer( &c_task );
assert( a_task.hasPeer( &c_task.getName() )
&& ! c_task.hasPeer( &a_task.getName() );
This allows to build strictly hierarchical topological networks as well as complete flat or circular
networks or any kind of mixed network.
From within a program script, peers can be accessed by merely prefixing their name to the member
you want to access. A program within "ATask" could access its peers as such :

do BTask.object.command()
var int result = CTask.method()
The peer connection graph can be traversed at arbitrary depth. Thus you can access your peer's
peers.

5. Running Tasks
When a TaskContext is running, it receives its commands from its Processor. The Processor will
check periodically for new commands in it's queue and execute programs which are running in the
task. Thus to start the task, one needs to start the Processor. As long as it is not started, it will accept
no commands and run no programs.

5.1. Starting Periodic Task Execution


To run a Processor, you need to use one of the Periodic Task classes from the CoreLib. ( See the CoreLib Manual [http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-corelib.html] for
more documentation. ) Continued from Section 3, Setting Up a Basic Task :

#include <corelib/TaskNonPreemptible.hpp>
using namespace ORO_CoreLib;
// ... start the processor of a_task :
TaskNonPreemptible periodicTask(0.001, &a_processor);
periodicTask.start();
Which will start the processor of ATask with a timer frequency of 1kHz. This is the frequency at
which state machines are evaluated, program steps taken and commands are accepted and executed.
When the processor is stopped again, all programs are stopped, state machines are brought into the
final state and no more commands are accepted.

70

The Task Infrastructure

5.2. Task Property Configuration


As was seen in Section 3.3, The Attribute Repository, 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 <execution/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. The GenericTaskContext has implemented this
functionality also as script methods.

5.3. Task Program Scripts


Commands are a Real-Time means of communication between tasks. They are asynchronous method calls from one task to another, without caring for multi-threaded locking issues. A command can
be rejected if the other task is not running, which can be detected by the sending task.
Commands can be grouped into Orocos Program Scripts (ops), which allow programs to be loaded
at runtime into a task. The program script is parsed to a command object tree, which can then be executed by the Processor of a task.

5.3.1. Functions
A function is formed by a group of commands and methods, which can be executed by a task. The
Program
Parser
Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-parser.html]
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 Processor ( see below ), or call it in a program, which
was parsed in the same file (see Section 5.3.2, Programs ).
Functions must be parsed by the Parser, before they can be executed by the Processor. The Processor executes the Function until it finishes or it goes into error. In both cases, the Function is removed from the Processor's queue and can then safely be deleted (or re-run).

Note
71

The Task Infrastructure

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 <execution/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. Use the 'low-level' Orocos API (Parser and
Processor) to initialise and execute functions with arguments. Otherwise, use programs
to call such functions.

5.3.2. Programs
Programs are special functions in that they can be finely controlled by (and are owned by) the Processor. A program can be paused, it's variables inspected and reset while it is loaded in the Processor. Also, the Processor will delete the program upon request. 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 <execution/ProgramLoader.hpp>
TaskContext* a_task = ...
ProgramLoader parser;
loader.loadProgram( "ProgramBar.ops", a_task );
When the Program is loaded in the Task Context, it can also be controlled from your scripts or
TaskBrowser. Assuming you have loaded a Program with the name 'foo', the following commands
are available :
do
do
do
do

programs.foo.start()
programs.foo.pause()
programs.foo.step()
programs.foo.stop()

72

The Task Infrastructure

While you also can inspect its status :


programs.foo.isRunning()
programs.foo.inError()
programs.foo.isPaused()

You can also inspect and change the variables of a loaded program, but as in any application, this
should only be done for debugging purposes.
set programs.foo.i = 3
var double oldj = programs.foo.j

Take
a
look
at
the
Program
Processor
Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-processor.html] and the
Processor class reference for more program related functions.

5.3.3. State Machines


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. A detailed introduction can be found in the Program
Parser
Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-parser.html]. This section
limits to showing how an Orocos State Description (osd) script can be loaded in a Task Context.

#include <execution/ProgramLoader.hpp>
TaskContext* a_task = ...
ProgramLoader loader;
loader.loadStateMachine( "StateMachineBar.osd", a_task );
When the State Machine is loaded in the Task Context, it can also be controlled from your scripts or
TaskBrowser. Assuming you have instantiated a State Machine with the name 'machine', the following commands are available :
do states.machine.activate()
do states.machine.start()
do states.machine.pause()
do states.machine.step()
do states.machine.stop()
do states.machine.deactivate()
do states.machine.reset()
do states.machine.requestMode()
do states.machine.requestState("StateName")
...

As with programs, you can inspect and change the variables of a loaded StateMachine.
set programs.machine.myParam = ...

73

The Task Infrastructure

Again,
take
a
look
at
the
Program
Processor
Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-processor.html] and the
Processor class reference for more details about state context related functions.

5.3.4. Further Reading


In addition to this text, be sure to read the Program Parser Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-program-parser.html] such that
you get a grip of the full power of Real-Time Orocos scripts.

5.4. Adding Events


Events
are
explained
in
detail
in
the
CoreLib
Manual
[http://people.mech.kuleuven.ac.be/~psoetens/orocos/doc/orocos-corelib.html]. To add Real-Time
events to your task, you can simply instantiate it with a given name :
#include <execution/TaskContext.hpp>
#include <corelib/Event.hpp>
class MyTask : public TaskContext
{
ORO_CoreLib::Event< void( int ) > myEvent;
int data;
public:
MyTask()
: TaskContext("myTask"),
myEvent("theEvent"), data(0)
{}
// ...
void foo()
{
myEvent.fire( data );
}
};
Another Task can then subscribe a handler to that Event, and should provide its Processor's task as
asynchronous completer using getTask() :
#include <execution/TaskContext.hpp>
#include <corelib/Event.hpp>
#include <boost/bind.hpp>
class MyOtherTask : public TaskContext
{
ORO_CoreLib::TaskPreemptible ptask;
int inputdata;
public:
MyOtherTask()
: TaskContext("myTask"),
ptask( 0.01, this->getProcessor() ),
inputdata(0)
{
}
void setUpEvent()
{
assert( proc->getTask() != 0 );
// connect bar with 'theEvent'
ORO_CoreLib::Event< void (int)
>::nameserver.getObject("theEvent")
->connect( boost::bind( &MyOtherTask::bar, this),
74

The Task Infrastructure

proc->getTask() );
}
// The event handler :
void bar( int in )
{
this->inputdata = in;
}
};
Anytime "theEvent" is fired, bar() will be called asynchronously in the Task's thread, guaranteeing
thread-safe data exchange, thus not requiring any mutex locks.

6. Applying the Task Infrastructure to Your


Application
In addition to the above methods of setting up tasks, this section gives some common uses for integrating your existing application framework in Orocos Tasks.

6.1. Using the GenericTaskContext


To help users in setting up quickly a TaskContext, the GenericTaskContext class has been made
available which adds some standard methods and commands to its interface which are common to
many tasks. It supports loading Programs and StateMachines, saving Properties to disk and reading
them back in and stopping and starting the Task. You can download an introduction to setting up
TaskContexts which uses this class from the Orocos.org download page.

6.2. Wrapping Methods in Functions


Methods are always executed in the thread of the caller. If a method does non-realtime operations,
like writing data to disk, it should not be called by a realtime thread. However, if the thread which
owns the method is itself not realtime, it can execute the method as a command in its own thread.
This can easily be accomplished by writing a wrapper function ( or alternatively, register the method
as a command too ).
export function domethod( int arg ) {
do mymethod( arg )
}
Load this function with the ProgramLoader in the TaskContext having 'mymethod', and hard realtime tasks can instruct it to execute that method, without jeopardizing their own realtime behaviour.

6.3. Waiting for Something : Synchronisation


When tasks need to synchronise, you have a wide range of options to use.

6.3.1. Requesting States


A State Machine can be used such that it waits for state change requests instead of discovering itself
to which state it makes a transition. This requires the State Machine to run in another mode, the requestState mode ( as opposed to the automatic mode, which is entered by start() ).
StateMachine X {
// ...
state y {
entry {
75

The Task Infrastructure

// ...
}
transitions {
// guard this transition.
if task.checkSomeCondition() then
select z
// always good to go to states :
select ok_1
select ok_2
}
}
state z {
// ...
}
state ok_1 {
// ...
}
state ok_2 {
// ...
}
}
RootMachine X x
Then, load an ops file which contains :
export function progress() {
// request to enter anther state :
do this.states.x.requestState("z")
}
export function progress_Ok() {
// this will succeed always from state 'x' :
do this.states.x.requestState("ok1")
}
This command will fail if the transition is not possible ( for example, the state machine is not in
state y, or task.checkSomeCondition() was not true ), otherwise, the state machine will make the
transition and the command succeeds and completes when the z state is fully entered (it's init program completed).
To merely request that a state is handled, one can call requestState on the current state :
export function handleState() {
// request to handle current state :
do this.states.x.requestState( this.states.x.getState() )
}
To request to go to the next possible state (or call handle if none) and then wait again for requests,
use 'step()' :
export function evaluate() {
// request go to the next state and wait :
do this.states.x.step()
}
Note that if the StateMachine happened to be paused, step() would only progress one single statement. To check if the StateMachine is waiting for requests, use the 'inRequest()' method :
export function progress() {
if ( this.states.x.inRequest() ) {
76

The Task Infrastructure

// ... it's waiting


} else {
// was not waiting, possibly running, paused or non act#
ive
}
}
All these methods can of course also be called from parent to child State Machine, or across tasks.

6.4. Polymorphism : Task Interfaces


Most projects have define their own task interfaces in C++. Assume you have a class with the following interface :
class DeviceInterface
{
public:
/**
* Set/Get a parameter. Returns false if parameter is readonly.
*/
virtual bool setParameter(int parnr, double value) = 0;
virtual double getParameter(int parnr) const = 0;
/**
* Get the newest data.
* Return false on error.
*/
virtual bool updateData() = 0;
virtual bool updated() const = 0;
/**
* Get Errors if any.
*/
virtual int getError() const = 0;
};
Now suppose you want to do make this interface available, such that program scripts of other tasks
can access this interface. Because you have many devices, you surely want all of them to be accessed transparantly from a supervising task. Luckily for you, C++ polymorphism can be transparantly adopted in Orocos TaskContexts. This is how it goes.

6.4.1. Step 1 : Export the interface


We construct a TaskContext, which exports your C++ interface to a task's interface.
#include <execution/TaskContext.hpp>
#include <execution/TemplateFactories.hpp>
#include "DeviceInterface.hpp"
class TaskDeviceInterface
: public DeviceInterface,
public TaskContext
{
public:
TaskDeviceInterface()
: TaskContext( "DeviceInterface" )
{
this->setup();
}
void setup()
77

The Task Infrastructure

{
// Add Methods :
TemplateMethodFactory<DeviceInterface>* mfact =
newMethodFactory( this );
mfact->add("setParameter",
method(&DeviceInterface::setParameter,
"Set a device parameter.",
"Parameter", "The number of the paramet#
er.",
"New Value", "The new value for the
parameter."));
this->methodFactory.registerObject("this", mfact);
// Add Data Sources :
TemplateDataSourceFactory<DeviceInterface>* dfact =
newDataSourceFactory( this );
dfact->add( "getParameter",
data(&DeviceInterface::getParameter,
"Get a device parameter.",
"Parameter", "The number of the paramet#
er."));
dfact->add("getError",
data(&DeviceInterface::getError,
"Get device error status."));
this->datasourceFactory.registerObject("this", dfact);
// Add Commands :
TemplateCommandFactory<DeviceInterface>* cfact =
newCommandFactory( this );
cfact->add( "updateData",
command( &DeviceInterface::updateData,
&DeviceInterface::updated,
"Command data acquisition." ) );
this->commandFactory.registerObject("this", cfact);
}
};
The above listing just combines all factories which were introduced in the previous sections. Also
note that the TaskContext's name is fixed to "DeviceInterface". This is not obligatory though.

6.4.2. Step 2 : Inherit from the new interface


Your DeviceInterface implementations now only need to inherit from TaskDeviceInterface
to instantiate a Device TaskContext :
#include "TaskDeviceInterface.hpp"
class MyDevice_1
: public TaskDeviceInterface
{
public:
bool setParameter(int parnr, double value) {
// ...
}
double getParameter(int parnr) const {
// ...
}
// etc.
};

6.4.3. Step 3 : Add the task to other tasks


The new TaskContext can now be added to other tasks. If needed, an alias can be given such that the
78

The Task Infrastructure

peer task knows this task under another name. This allows the user to access different incarnations
of the same interface from a task.
// now add it to the supervising task :
MyDevice_1 mydev;
supervisor.addPeer( &mydev, "device" );

From now on, the "supervisor" task will be able to access "device". If the implementation changes,
the same interface can be reused whithout changing the programs in the supervisor.
A big warning needs to be issued though : if you change a peer at runtime (after parsing programs),
you need to reload all the programs, functions, state contexts which use that peer so that they reference the new peer and its C++ implementation.

6.4.4. Step 4 : Use the task's interface


To make the example complete, here is an example script which could run in the supervisor task :
program ControlDevice
{
const int par1 = 0
const int par2 = 1
do device.setParameter(par1, supervisor.par1 )
do device.setParameter(par2, supervisor.par2 )
while ( device.getError() == 0 )
{
if ( this.updateDevice("device") == true )
do device.updateData() until {
if done || ( device.getError() != 0 ) then
continue
}
}
do this.handleError("device", device.getError() )
}
To start this program from the TaskBrowser, browse to supervisor and type the command :
device.programs.ControlDevice.start()

When the program "ControlDevice" is started, it initialises some parameters from its own attributes.
Next, the program goes into a loop and sends updateData commands to the device as long as underlying supervisor (ie "this") logic requests an update and no error is reported. This code guarantees
that no two updateData commands will intervene each other since the program waits for the commands completion or error. When the device returns an error, the supervisor can then handle the error of the device and restart the program if needed.
The advantages of this program over classical C/C++ functions are :

If any error occurs (ie a command or method returns false), the program stops and other programs or state contexts can detect this and take apropriate action.

The "device.updateData()" call waits for completion of the remote command, but can be
given other completion or error conditions to watch for.

While the program waits for updateData() to complete, it does not block other programs,
etc within the same TaskContext and thread.
79

The Task Infrastructure

There is no need for additional synchronisation primitives between the supervisor and the device
since the commands are queued and executed in the thread of the device, which leads to :

The command is executed at the priority of the device's thread, and not the supervisor's priority.

The command can never corrupt data of the device's thread, since it is serialised(executed
after) with the programs running in that thread.

7. Using C++ Commands


Besides using scripts, you can directly build C++ commands, although it requires more "red tape".

7.1. Sending a Task a Command


To request a command from one task to another task, the command must be fetched from the factories and next passed to the TaskContext which has two methods to accept commands. In a C++ program, this would be :

TaskContext* peer = this->getPeer("ATask");


// Create the command argument list :
std::vector<DataSourceBase*> args;
args.push_back( new DataSource<double>(0.1) );
// Create the command and condition :
ComCon comcon =
peer->commandFactory.getObjectFactory("objname")->
create("cleanupMess", args );
// Execute the command :
if ( peer->executeCommand( comcon.first ) ==
// Error !
}

false ) {

// wait until done :


while ( comcon.second->evaluate() == false )
sleep(1);
// command done, we can now delete it or keep it.
The factory can throw exceptions if the number of arguments, the type of arguments or the command names are unknown. It returns a ComCon struct, where the first member contains a pointer
to the CommandInterface and the second member contains a pointer to the ConditionInterface (
the Completion Condition ). executeCommand will always execute the command, no matter if
the processor is running or not. If it is not running, the command is directly executed and the return
value of the command is returned (true or false). If the processor is running, the command is queued
and you have no direct way to know what the return value is ( No panic, a solution is presented
lateron !) The alternative is queueCommand, which will only queue if the Processor is running :

// ... see previous listing


int qnb = peer->queueCommand( comcon.first );
if ( qnb == 0 ) {
// Error, not accepted !
}
while ( !peer->getProcessor()->isProcessed( qnb ) )
sleep(1); // wait until the command is processed
while ( comcon.second->evaluate() == false )
80

The Task Infrastructure

sleep(1); // wait until the command is done


// command done, we can now delete it or keep it.
Again, you only know if it got in the queue, but not if it was accepted by the task itself, meaning, if
the command returned true.

7.2. Advanced Command Queueing


The previous section showed how a command can be given to a task, but the return value of the
Command was lost. In some cases, this does not matter, but when the program logic needs to know
the result, two options are possible. One can write a program script, which check command return
values (and go into error if the command fails) or wrap the command in a TryCommand. The next
sections will discuss the first and easiest option. This sections discusses the second and thoughest
option. If you intend to use mainly scripts (or get really confused), please skip this section.
Assume you have a command,condition pair as above :

ComCon comcon = ...;


Before queueing, you can wrap both command and condition as such :

#include <execution/TryCommand.hpp>
// ...
CommandInterface* trycommand
= new TryCommand( comcon.first );
DataSource<bool>* executed
= trycommand.executed();
DataSource<bool>* accepted
= trycommand.result();
peer->queueCommand( trycommand );
while ( executed->get() == false )
sleep(1); // wait for its execution
if ( accepted->get() == false ) {
// Error, Command returned false !
}
while ( comcon.second->evaluate() == false )
sleep(1); // wait for its completion
The DataSources are used by the TryCommand to store the result into. We use the get() function to
inspect the results.

81

Chapter 8. The Program Processor


The Program Processor is the 'execution engine' of Programs and State Machines. It can execute any
number of Programs and State Machines in parallel.

1. Introduction to the Program Processor


Orocos is meant for building realtime systems. You will find many usefull 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. A StateMachine will hold the user-programmable state machine
(representing the system logic). A ProgramGraph will hold the user-defined program to be executed. A Processor will then represent an execution engine which loads the state machine and
program definitions and executes them. Loading a program is however a non-realtime operation,
since it performs dynamic memory allocations. Execution of the programs and state machines will
happen in real time. The StateMachine executes programs according to an user defined logic, so if
we write about program semantics, they hold in plain programs and state machines.
The Program Processor is not what the user will use directly. The user will mainly use the Orocos
Program Parser [orocos-program-parser.html] with the Orocos Task Infrastructure
[orocos-task-context.html]. The Parser generates ProgramGraphs and StateGraphs from user
friendly text files, which can then be executed in realtime.

2. Executing Commands, Programs and Hierarchical StateMachines


2.1. The Processor
The Processor is the core class of this package It is capable of loading and processing a state machine and loading and executing additional programs on that system. A system's configuration (or
part of) can be defined in a State Machine ( a State Machine is a real-time state machine which iterates over a collection of Orocos States ). Multiple State Machines can be loaded and started/stopped
in parallel. A Program is a sequence of commands and methods, connected to each other through
condition evaluation. Also programs can be loaded and run in parallel.

2.2. Running the Processor


The Processor is responsible for accepting command requests from other (realtime) tasks. It uses a
non-blocking atomic queue to store incomming requests and fetch-and-execute them in its periodic
step, which can be synchronised with tasks of the same priority. It offers thus a thread-safe realtime
means of message passing between threads, without intervention of the Operating System. However,
before it will accept commands, it must be attached to a periodic task and be started. It implements
the ORO_CoreLib::RunnableInterface class, which allows it to be run by periodic and non
periodic tasks :
#include <execution/Processor.hpp>
#include <corelib/TaskNonPreemptible.hpp>
ORO_Execution::Processor proc;
ORO_CoreLib::TaskNonPreemptible ptask( 0.01, proc); // see Co#
reLib doc
ptask.start();
CommandInterface* com = ... // See the last section
assert ( proc.getTask()->isRunning() );
82

The Program Processor

int ticket = proc.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 ( proc.isProcessed( ticket ) )
// done !
else
// still busy !
The commands this Processor accepts must be for tasks of the same priority, and each Processor can
only be run in one thread. Orocos provides a mechanism to transparantly serialise tasks of equal priority in the same thread, this is explained in the Orocos CoreLib documentation. Take a look at the
Task Infrastructure Manual for more elaborate examples of Command queueing.

Figure 8.1. Tasks Sending Commands

Tasks of different threads communicate by sending commands to each other's 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 Processor. When T2 runs its 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. )
In addition to command processing, the Processor provides functions to manipulate Programs and
State Machines. The available commands are presented in the figures in the next sections.

2.3. Programs
The ProgramGraph is a tree composed of command nodes ( See : CommandInterface class ),
where each node contains one or more statements. A ProgramGraph keeps track of the start node
83

The Program Processor

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 ProgramGraph has a builder interface for constructing nodes and conditions, which is used by
other packages to construct valid programs.

Figure 8.2. Executing Program Statements

Programs are generated from a script, this is explained in the Orocos Program Parser
[orocos-program-parser.html] Manual. The Parser is able to convert Orocos Program Scripts to a
Program which can be loaded in the Processor.

Figure 8.3. Using a Program

Loading,

unloading

and

deleting

Program
84

may

throw

an

exception.

The

pro-

The Program Processor

gram_load_exception and program_unload_exception should be try'ed and catch'ed


on load and unload or deletion of a Program in the Processor. Alternatively, you can use the ProgramLoader class as a user-friendly frontend for loading Programs and Functions.

2.4. State Machine and State


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). Every such state is defined by the StateInterface. A state itself
is defined by three programs : entry, handle and exit. They are called by the Processor when this
state is entered, handled or left. When no state transition took place, handle is called in a periodic
execution step of the Processor. Otherwise, first the exit program of the current state is called and
then the entry program of the new state is called. The next periodic step will evaluate transition conditions, and if none succeeds, handle will be called, and so on.
As transitions define when a state will be left, preconditions are used to define when a state should
not be entered, and an alternative must be provided (this feature is under development).

Figure 8.4. Executing a StateMachine

To see how a State Machine can be used in Orocos, we refer to the Orocos Program Parser
[orocos-program-parser.html] Manual. The Parser is able to convert Orocos State Descriptions to a
State Machine wich can be loaded in the Processor.

Figure 8.5. Using a StateMachine

85

The Program Processor

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. Alternatively, you can use the
ProgramLoader class as a user-friendly frontend for loading State Machines.

3. Creating Commands
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 usefull if a
function must be called at a later moment.

3.1. Generic Functors


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.

86

The Program Processor

Figure 8.6. A Generic Functor

3.2. Creating a CommandFunctor


The CommandFunctor is the object used to store the function pointer in. It implements the CommandInterface such that it can be execute()'ed by the Processor :
#include <corelib/CommandFunctor.hpp>
void foo();
CommandInterface* 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 <corelib/CommandFunctor.hpp>
#include <boost/bind.hpp>
void foo( int x, int y );
int a1 = 1, a2 = 2;
CommandInterface* command = newCommandFunctor( boost::bind( &foo,
a1, a2 ) );
command->execute(); // calls foo(a1,a2)
delete command;
Argument 'binding' is a very powerfull 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 <corelib/CommandFunctor.hpp>
#include <boost/bind.hpp>
class X {
87

The Program Processor

public:
void foo( int x, int y );
};
X x_object;
int a1 = 1, a2 = 2;
CommandInterface* 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 CommandInterface. Since the Program Processor can execute CommandInterface objects, it is a powerfull way to delay calling of a function to
a later moment.

3.3. Processing a Command


Using the CommandFunctor from the previous section, we can pass the command to the processor :
CommandInterface* 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.

3.4. Common Usage Examples


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.

4. StateMachine and Program Implementation


Details
This section gives a short description of the inside of Programs and StateMachines, and can be
skipped by most users.

4.1. ( State ) Transitions : Condition Edge


The ConditionEdge defines an edge of the state diagram. It contains a condition on which a next
88

The Program Processor

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.

4.2. Statements : Command Node


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.

4.3. The Command class


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, CommandInterface, is provided by the Orocos CoreLib.

4.4. The Condition class


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

89

Chapter 9. The Program Parser


This document describes the Orocos Parser system, in the different ways it can be used and extended.

1. Introduction
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.
There are three ways that people need to use the Orocos Parser framework, and the documentation is
split up accordingly. The following chapters each deal with one of these aspects, and can be read
separately. You are encouraged to skip ahead to the part that interests you.

Writing Orocos Programs and State Descriptions for Orocos Task Contexts. Introduction to syntax and terminology.

Exporting the API of an Orocos Task to the Orocos Parser. [orocos-task-context.html] This is
explained in the Task Infrastructure manual.

Extending the Orocos parser to support extra types, overload existing operators, and/or add new
operators. If you want to push the parser to your limits.

This documentation tackles only these three aspects. If you want to do more advanced things with
the Orocos Parser system, you'll have to dig into the source code. It should be fairly well documented.

2. Orocos Program Scripts


2.1. General concepts
Before starting to explain Program Syntax, it is necessary to explain some general concepts that are
used throughout the program syntax.

2.1.1. Comments
Various sorts of comments are supported in the syntax. Here is a small listing showing the various
syntaxes:
# A perl-style comment, starting at a '#', and running until
# the end of the line.
// A C++/Java style comment, starting at '//', and running
// until the end of the line.
/* A C-style comment, starting at '/*', and running until
the first closing */ /* Nesting is not allowed, that's
why I have to start a new comment here :-)
*/
Whitespace is in general ignored, except for the fact that it is used to separate tokens.

2.1.2. Identifiers
90

The Program Parser

Identifiers are names that the user can assign to variables, constants, aliases, labels. The same identifier can only be used once, except that for labels you can use an identifier that has already been used
as a variable, constant or alias. However, this is generally a bad idea, and you shouldn't do it.
Some words cannot be used as identifiers, because they are reserved by the Orocos Scripting Framework, either for current use, or for future expansions. These are called keywords. The current list of
reserved keywords is included here:
alias
catch
else
if
or
time
var
and
const
end
include
return
to
vector
array
define
false
int
rotation
true
while
break
do
for
next
set
try
wrench
bool
done
foreach
not
string
twist
char
double
frame
nothing
then
until
These, and all variations on the (upper- or lower-) case of each of the letters are reserved, and cannot
be used as identifiers.

2.1.3. Expressions
Expressions are a general concept used throughout the Parser system. Expressions represent values
that can be calculated at runtime (like a+b). They can be used as arguments to functions, conditions
and whatmore. Expressions implicitly are of a certain type, and the Parser system does strong typechecking. Expressions can be constructed in various ways, that are described below...

Task Data Sources


An Orocos TaskContext can export read-only data. This data can be of any possible type, and
the Parser system retains that type information. Getting task data is done using an expression of the
following form:
task.dataname( argument1, argument2, ..., argumentN )
where "task" is the name of the peer TaskContext whose data you want to access, dataname is
the name of the data member of the task you want to access. "argument1" through "argumentN" are
the arguments you want to pass to the data call, and can be any kind of expression. If a data call
needs no arguments, the parentheses can be left out, like this:
// equivalent to task.dataname()
task.dataname

Task Methods
Methods are analogous to the data in the previous section. But they can both be used in expressions
and executed as a command. For example, a method returning a boolean can be used in an 'if' clause
and in a 'do' statement (see below).
// executed as a method :
bool result = task.methodname( argument1, argument2, ..., argu#
mentN )
// executed as a command :
do task.methodname( argument1, argument2, ..., argumentN )
The boolean return value is optional and can be of any type (including void).

Literals
Literal values of various types are supported: string, int, double, bool. Boolean literals are either the
word "true" or the word "false". Integer literals are normal, positive or negative integers. Double lit91

The Program Parser

erals are C/C++ style double-precision floating point literals. The only difference is that in order for
the Parser to be able to see the difference with integers, we require a dot to be present. String literals
are surrounded by double quotes, and can contain all the normal C/C++ style escaped characters.
Here are some examples:
// a string with some escaped letters:
"\"OROCOS rocks, \" my mother said..."
// a normal integer
-123
// a double literal
3.14159265358979
// and another one..
1.23e10

Constants, Variables and Aliases


Constants, variables and aliases allow you to work with data in an easier way. A constant is a name
which is assigned a value at the place of its definition, and keeps that value throughout the rest of
the program. A variable is like a constant, but its value can be changed at other places in the program. An alias does not carry a value, it is defined with an expression, for which it acts as an alias or
an abbreviation during the rest of the program. All of them can always be used as expressions. Here
is some code showing how to use them.
// define a constant of type double, with name "pi",
// and value 3.14159265358979
const double pi = 3.14159265358979
// define a variable of type int, called counter,
// and give it the initial value 0.
var int counter = 0
// add 1 to the counter variable
set counter = counter + 1
// make the name "counterPlusOne" an alias for the
// expression counter + 1. After this, using
// counterPlusOne is completely equivalent to writing
// counter + 1
alias int counterPlusOne = counter + 1
// you can assign an arbitrarily complex expression
// to an alias
alias int reallycomplexalias = ( ( counter + 8 ) / 3
)*robot.position
Variables, constants and aliases are only supported for the following types: bool, int, double, string,
rotation, vector, frame, double6D.

Strings and Arrays


For convenience, two variable size types have been added to the parser : string and array. They are
special because their contents have variable size. For example a string can be empty or contain 10
characters. The same holds for an array, which contains doubles. String and array are thus container
types. They are mapped on std::string and std::vector<double>. To access them safely from a task
method or command, you need to to pass them by const reference : const std::string& s, const
std::vector<double>& v.
Container types can be used in two ways : with a predefined capacity (ie the possibility to hold N
items), or with a free capacity, where capacity is expanded as there is need for it. The former way is
necessary for realtime programs, the latter can only be used in non realtime tasks, since it may cause
a memory allocation when capacity limits are exceeded.
// A free string and free array :
// applestring is expanded to contain 6 characters (Non real#
92

The Program Parser

time!)
var string applestring = "apples"
// values is expanded to contain 15 elements (Non realtime!)
var array values
= array(15)
// A fixed string and fixed array :
var string fixstring(10) // may contain a string of maximum 10
characters
set fixstring
set fixstring
enough room.

= applestring
// ok, enough capacity
= "0123456789x" // runtime program error, not

var array fixvalues(10) // fixvalues may never contain more


than 10 elements
var array morevalues(20) // arrays are initialised with n
doubles of value 0.0
set fixvalues = morevalues
set morevalues = fixvalues
city, now contains 10 doubles

// will cause program error


// ok, morevalues has enough capa#

set fixvalues
tains 10 items.

// ok, since morevalues only con#

= morevalues

set values
= array(20)
doubles. (Non realtime!)

// expand values to contain 20

As the example above demonstrates, a fixed string or array may only be assigned from another
string or array with equal or less elements.

Important
The value given upon construction must be a legal expression at parse time and is only
evaluated once. The safest method is using a literal integer ( like in the examples ), but
if you create a Task constant or variable which holds an integer, you can also use it as
in :
var array example( 5 * task.numberOfItems )
The expression may not contain any program variables, these will all be zero upon
parse time ! The following example is illegal also :
set task.numberOfItems = 10
var array example( 5 * task.numberOfItems )
Which will not lead to '50', but to '5 times the value of task.numberOfItems when the
program is parsed'.
Another property of container types is that you can index (use []) their contents. The index may be
any expression that return an int.
// ... continued
// Set an item of a container :
for (int i=0; i < 20; set i = i+1)
set values[i] = 1.0*i
// Get an item of a container :
var double sum
for (int i=0; i < 20; set i = i+1)
set sum = sum + values[i]

93

The Program Parser

If an assignment tries to set an item out of range, the command will fail, if you try to read an item
out of range, the result will return 0.0, or for strings, the null character.

Operators
Expressions can be combined using the C-style operators that you are already familiar with if you
have ever programmed in C, C++ or Java. All operators are supported, except for the if-then-else
operator ("a?b:c"), and the precedence is the same as the one used in C, C++, Java and similar languages. In general all that you would expect, is present.

The '.' Operator


Some value types, like vector, rotation, array, string in the Parser framework are actually containing
values or useful information themselves. For accessing these values, a value type can have a 'dot'
operator which allows you to read-only contents of the container :
var string s1 = "abcdef"
// retrieve size and capacity of a string :
var int size = s1.size()
var int cap = s1.capacity()
var array a1( 10 )
var array a2(20) = a1
// retrieve size and capacity of a array :
var int size = a2.size()
// 10
var int cap = a2.capacity() // 20
Likewise, the following operators are available for the geometry types :
var frame f
var rotation
var vector
var twist
var wrench
// ...

r
v
t
w

set v = f.p
set r = f.R

// read the position


// read the rotation

set v = t.vel
set v = t.rot

// read the translational velocity


// read the rotational velocity

set v = w.force
set v = w.torque

// read the force


// read the torque

var double x = v.x

// also : v.y or v.z

var double p = r.roll

// also : r.pitch or r.yaw

You can not use the '.' operator in the reverse direction. thus the following code is invalid :
set f.p = v
set f.R = r

// Invalid !
// Invalid !

These are thus read-only accessors and can thus not be written to. To change them, you'll have to
use a constructor from the next section.

Constructors
For some special types, a special kind of operator is provided. These are called constructors. The
simplest example is the vector constructor, that looks like this:
94

The Program Parser

vector( arg1, arg2, arg3 )


where arg1, arg2 and arg3 are expressions which must be of type double. This returns an expression
of type vector, with arg1 as the x component, arg2 as the y component, and arg3 as the z component.
Other constructors currently available are:
// roll, pitch and yaw are double expressions, this
// returns a rotation, that is constructed using the
// Roll-Pitch-Yaw convention in RADIANS :
var double roll = 45.0 * (2.*3.14/360.)
// convert to ra#
dians
// ...
var rotation rot = rotation( roll, pitch, yaw )
// Vect is a vector expression, rot is a rotation
// expression. This returns a frame, constructed
// using the vector x as the origin, and rotation
// rot as the rotation..
var frame f = frame( vect, rot )
// Double6D is a commonly used type in Orocos
// and has been recently introduced in the parser
var double6d d6 = double6d(0.0)
set d6[0] = 1.0
var double d0 = d6[0]
set d6 = double6d( 1., 2., 3., 4., 5., 6. )

2.2. Parsing and Loading Programs


Before we go on describing the details of the programs syntax, we show how you can load a program in your Real-Time Task.
Assume that you have a program "progname" in a file program.ops. You've read the Orocos
Task Infrastructure manual and constructed a TaskContext which has some methods and is connected to its Peer TaskContexts. Parsing the program is then straightforward :
#include <corelib/TaskPreemptible.hpp>
#include <execution/TaskContext.hpp>
#include <execution/ProgramLoader.hpp>
using namespace ORO_CoreLib;
TaskContext
tc;
ORO_CoreLib::TaskPreemptible ptask(0.01, tc->getProcessor() );
ProgramLoader loader;
// Watch Logger output for errors :
loader.loadProgram( "program.ops", &tc );
// start the Processor :
ptask->start();
// start a program :
tc.getProcessor()->startProgram("progname");
The loader will load all programs and functions into tc. Next we start the task's processor and finally, the program "progname" is started. Programs can also be started from within other scripts.

95

The Program Parser

2.3. Program Semantics


An Orocos program is a list of statements, quite similar to a C program. Programs can call C/C++
functions and functions can be loaded into the system, such that other programs can call them.
A program's statement is located in a node. Nodes are connected to each other by arches containing
boolean conditions. A Processor which executes a program, executes a statement and then checks
each arch's conditions. If a condition evaluates to true, the program immediately executes the next
target node and so on. When no condition evaluates to true, the program 'waits', meaning that it
postpones execution to the next periodic execution step of the Processor. This happens typically
when the statement was a Task Command. Task Methods and expressions on the other hand typically do not impose a wait, and thus are executed immediately after each other.

2.4. Program Syntax


2.4.1. program
A program is formed like this:
program progname {
// an arbitrary number of statements
}
The statements are executed in order, starting at the first. One statement is executed per tick. There
are currently three kinds of statements, they are explained below. A program takes no arguments
and is executed by the Program Processor.
If any of the statements causes a run-time error, the Program Processor will put the program in the
error state and stop executing it. It is the task of other logic (like state machines, see below) to detect
such failures.

2.4.2. function
Statements can be grouped in functions. A function can only call a function which is earlier defined.
Thus recursive function calling is not allowed.
function func_name( int arg1, double arg2 ) {
// an arbitrary number of statements
}
export function func_nameN(bool arg) {
// ...
}
A function can have any number of arguments, which are passed by value, but it returns no value. A
function can be exported, in which case it becomes a public available command (see below), which
will fail if one of its commands fails. Exporting functions is a powerfull means to build reusable
code in the scripting framework, since an external task can transparantly call a function or call a
C++ command.

2.4.3. Command Call Statements


A command call statement is a statement that calls a certain command, and defines some reactions.
It looks like:
do comp.action( args ) until {
if condition then continue
if condition2 then call func()
if condition3 then return
}

96

The Program Parser

It calls the command "action", on the task "comp", with the comma-separated list of expressions
args as the arguments. The Processor executes the command once, and then checks where to go next
using the "completion clauses" in the until part. If none are true, then it waits another tick, and
checks them again..

Note
These if .. then clauses are different from the if/then/else statement later in this tekst
and purely meant to detect alternative end conditions of a command.
Sometimes, you want to continue executing the command, while checking the completion conditions, instead of only executing it the first time.. In that case, you can put the keyword "sync" before
the command call.
// continue moving to the right until you hit the wall..
do sync robot.move( robot.position + vector( 1.0, 0.0, 0.0 ) )
until { if robot.hitWall() then return }
The example above may be a bit stupid, but it only serves to show the use of the sync keyword..
A completion clause always looks like
if
//
if
//
if

condition then continue


or
condition then call func_name
or
condition then return

condition can be any kind of expression, that is of type boolean. One special condition is provided,
the keyword "done". Every command has an associated "implicit completion condition", and the
condition "done" is equivalent to that condition. You can also combine the "done" condition with
other expressions, as if it were a normal boolean expression. "continue" means to go to the next
statement, return means to end the current program or function. The "call func_name" statement
calls a function and is explained in the next section
If a completion list is left out, then an implicit one is generated. This means that the two following
statements are equivalent:
do
//
//
do

comp.action( args )
the implicitly generated completion list always looks like
"if done then continue"
comp.action( args ) until { if done then continue }

Accepting and Rejecting Commands


A command can be accepted or rejected by a task. When it returns false, this is seen as a reject and
the program goes into an error state. The user can then stop the program or try to continue the program again, which will lead to a re-issuing of the command, which may lead again to the error state.

Try ... Catch Commands


When a command is rejected ( the C++ method returns false ), the program goes into an error state
and waits for user intervention. This can be bypassed by using a try...catch statement. It tries to execute the command, and if it is rejected, the optional catch clause is executed :
// just try it :
try comp.action( args )
// When rejected, execute the catch clause :
try comp.action( args ) catch {
// statements...
}
97

The Program Parser

You may place completion conditions between try and catch :


try comp.action( args )
until {
if comp.evaluate() then continue
}
catch {
// statements...
}
// next statement
If the command was accepted, the next statement is executed.

Parallel Commands with 'and'


When it is desired to execute commands as one command ( in one time ), they can be combined with
an 'and' operator :
do comp.action1() and comp.action2() and comp.action3()

The implicit completion condition (i.e. 'done') is when all listed actions are done. This can be overridden by defining a completion condition with 'until'.
The 'and' operator can also be used with 'try'. In that case, the catch clause will be executed in case
any of the listed commands fails.

2.4.4. Method Call Statements


Methods behave like traditional functions. They take arguments and return a value immediately.
They can be used in expressions or stand alone in a do statement :
// ignore the return value :
do comp.method( args )
// this will only work if the method returns a boolean :
if ( comp.method( args ) ) {
// ...
}
// use another method in an expression :
set data = comp.getResult( args ) * 20. / comp.dataValue

A method can thus transparantly be used like a command, which implicit completion condition is always true, which means, the next statement is executed immediately after the method returns.

Warning
A method returning a boolean result, will behave like a command. This means that if it
returns false, it will be seen as a reject. If this is not wanted, use 'try' instead of 'do'. If
the method returns nothing or something else than bool, the result will be ignored in a
do statement.

2.4.5. Calling functions


A function can be called by writing :

98

The Program Parser

do foo(arg)
// see 'exported' functions
call foo(arg) // local functions
The arguments are passed by value and no return value is possible. If one of the commands of the
functions returns error, the calling program goes in error. A function may also be called in a completion clause. If the function returns, the next statement of the calling function or program is executed.

2.4.6. Variable Set Statements


A variable set statement is a statement that sets a variable to a certain value. It looks like this:
set variablename = expression
Variablename is the name of the variable you want to assign to. It should already have been defined.
Expression is an expression of the same type as the type of the variable.

2.4.7. The if then else Statement


A Program script can contain if..then..else blocks, very similar to C syntax.
if condition then statement
[ else statement ]
// or :
if condition then {
statement
// ...
} [ else {
statement
// ...
} ]
It is thus possible to group statements. Each statement can be another if clause. An else is always referring to the last if, just like in C/C++. If you like, you can also write parentheses around the condition. The else statement is optional.

2.4.8. The for Statement


The for statement is almost equal to the C language. The first statement initialises a variable or is
empty. The condition contains a boolean expression (use 'true' to simulate an empty condition). The
second statement changes a variable or is empty.
for ( statement; condition; statement )
statement
// or :
for ( statement; condition; statement ) {
statement
// ...
}

2.4.9. The while Statement


The while statement is another looping primitive in the Orocos script language. A do statement is
not ( yet ) implemented
while condition
99

The Program Parser

statement
// or :
while condition {
statement
// ...
}
As with the if statement, you can optionally put parentheses around the condition.

2.4.10. The break Statement


To break out of a while or for loop, the break statement is available. It will break out of the innermost loop, in case of nesting.
var int i = 0
while true {
set i = i + 1
if i == 50 then
break
// ...
}
It can be used likewise in a for loop.

2.4.11. Waiting : The 'nothing' Command


A special command 'nothing' is provided, and I'm sure you can already guess what it does.. It is useful to implement statements, where the completion list is really the only useful thing to do. The
nothing command's completion condition will pause execution exactly one execution step, it can
thus also be inserted between methods to force execution to pause and resume in the next execution
step.

2.4.12. Starting and Stopping Programs from scripts


Once a program is parsed and loaded into the Processor, it can be manipulated from another script.
This can be done through the programs subtask of the TaskContext in which the program was
loaded. Assume that you loaded "progname" in task "ATask", you can write
do
do
do
do

ATask.programs.progname.start()
ATask.programs.progname.pause()
ATask.programs.progname.step()
ATask.programs.progname.stop()

The first line starts a program. The second line pauses it. The next two lines executes one command
each of the program (like stepping in a debugger). The last line stops the program fully (running or
paused).
Some basic properties of the program can be inspected likewise :
var bool res = ATask.programs.progname.isRunning()
set res = ATask.programs.progname.inError()
set res = ATask.programs.progname.isPaused()
which all return a boolean indicating true or false.

3. Orocos State Descriptions : The Real-Time


100

The Program Parser

State Machine
3.1. Introduction
A StateMachine is the state machine used in the Orocos system. It contains a collection of
states, and each state defines a Program on entry of the state, when it is handled and on exit. It also
defines all transitions to a next state. A StateMachine must be loaded in a Task's Processor ( see The
Online Processor Manual [orocos-program-processor.html] ).

3.2. StateMachine Workings


A StateMachine is composed of a set of states. A running StateMachine is always in exactly one of
its states. One time per period, it checks whether it can transition from that state to another state, and
if so makes that transition. By default, only one transition can be made in one Processor execution
step.
Besides a list of the possible transitions, every state also keeps record of programs to be executed at
certain occasions. There are exactly three programs in every state: the entry program ( which will be
executed the first time that the state becomes active ), the handle program ( which will be executed
every time the state is the active state ) and the exit program ( which will be executed when the state
is left and another state is entered ).
There can be more than one StateMachine. If there are more than one, then every StateMachine
keeps working as if it was the only StateMachine available. They separately keep track of their own
current state, etc.
A StateMachine can have any number of states. It needs to have exactly one "initial state", which is
the state that will be entered when the StateMachine is first activated. There is also exactly one final
state, which is entered when the StateMachine is stopped. This means that the transition from any
state to the final state must always be meaningful.
A State Machine can run in a number of modes. The two most important ones are the automatic
mode and the request mode.

3.2.1. Automatic State Change Semantics


In order to enter automatic mode, the State Machine must be started with the start() command (see
later on).
In automatic mode, the transition table (to other states) of the current state is evaluated. If a transition succeeds, the exit program of the current state is called and then the entry program of the next
state is called. If no transition evaluated to true, the handle program of the current state is called.
This goes on until the automatic mode is left, using the pause or stop command.

3.2.2. On Request State Change Semantics


In order to enter the on request mode, the State Machine must be active, but not running (see later
on).
In request mode, nothing happens automatically. The StateMachine waits for external commands.
An external program can request a transition to a particular state, from the current state. When the
request arrives, the StateMachine checks the transition table and evaluates if this transition is allowed. If so, the exit program of the current state is called and the entry program of the requested
state is called. If a transition to the current state was requested, the handle program of the current
state is executed.
In request mode, it is also possible to request a single transition to the 'best' next state. This mechanism is similar to automatic mode, but only one transition is made ( or if none, handle is executed )
and then, the state machine waits again. The step() command triggers this behaviour.

101

The Program Parser

3.3. Parsing and Loading StateMachines


Analogous to the Program section, we first show how you can load a StateMachine in your RealTime Task.
Assume that you have a StateMachine "statename" in a file state-machine.osd. You've read
the Orocos Task Infrastructure manual and constructed a TaskContext which has some methods
and is connected to its Peer TaskContexts. Parsing the StateMachine is very analogous to parsing
Programs:
#include <corelib/TaskPreemptible.hpp>
#include <execution/TaskContext.hpp>
#include <execution/ProgramLoader.hpp>
using namespace ORO_CoreLib;
TaskContext
tc;
ORO_CoreLib::TaskPreemptible ptask(0.01, tc->getProcessor() );
ProgramLoader loader;
loader.loadStateMachine( "state-machine.osd", &tc );
// start the task's processor :
ptask->start();
// activate a state context :
tc.getProcessor()->activateStateMachine("MachineInstanceName");
// start a state context (automatic mode) :
tc.getProcessor()->startStateMachine("MachineInstanceName");
The loader loads all instantiated state machines in tc. Next we start the task's Processor. StateMachines have a more complex lifetime than Programs. They need first to be activated, upon which
they enter a fixed initial state. When they are started, they enter automatic mode and state transitions
to other states can take place. StateMachines can also be manipulated from within other scripts.

3.4. Defining StateMachines


You can think of StateMachines somewhat like C++ classes. You first need to define a type of
StateMachine, and you can then instantiate it any number of times afterwards. A StateMachine ( the
type ) can have parameters, so every instantiation can work differently based on the parameters it
got in its instantiation.
A StateMachine definition looks like this :

Example 9.1. StateMachine Definition Format


StateMachine MyStateMachineDefinition
{
initial state myInit
{
entry {
// entry program
}
handle {
// handle program
}
exit {
// exit program
}

102

The Program Parser

transitions {
// Ordered (conditional) select statements
}
}
final state myExit {
entry {
// put everything in a safe state.
}
handle {
}
exit {
}
transitions {
// ...
}
}
state Waiting {
// ...
}
// ... repeat
}
// See Section 3.5, Instantiating Machines: SubMachines and
RootMachines :
RootMachine MyStateMachineDefinition MachineInstanceName
A StateMachine definition: a StateMachine can have any number of states. It needs to have exactly
one "initial state" ( which is the state that will be entered when the StateMachine is first started ).
Within a state, any method is optional, and a state can even be defined empty.

3.4.1. The state Statement


A state context can have an unlimited number of states. A state contains optionally 4 functions :
entry, handle, exit and transitions. Any one of them is optional, and a state can even conceivably be
defined empty.

3.4.2. The entry and exit Statements


When a state is entered for the first time, the entry function is called. When it is left, the exit function is called. They both a Program to execute.

3.4.3. The handle and transitions Statement


The handle function is called only when no transition can be found to another state in the next execution step. The handle function also contains a program. To leave a state, the transitions function
defines select statements. These can be guarded by if...then clauses :
// In state XYZ :
transitions {
// conditionally select the START state
if HMI.startPushed then
select START
// Fall through state
select WAIT
}
The transitions are checked in the same order as listed. A transition is allowed to select the current
state, but the onExit and onEntry functions will not be called in that case. Even more, a transition to
the current state is always considered valid and this can not be overridden.

3.4.4. State Preconditions (Experimental)


103

The Program Parser

Often it's useful to specify some preconditions that need to hold before entering a state. Orocos
states explicitly allow for this. A state's preconditions will be checked before the state is entered.
Preconditions are specified as follows:
state X {
preconditions {
// make sure the robot is not moving axis 1 when entering
this state
if robot.movingAxis( 1 ) then
select ERROR_STATE
}
// ...
}
As you may have noticed, the syntax for preconditions is the same as for transitions. It's simply a set
of conditional select statements. The only difference is that preconditions are checked once before
entry of the state, whereas transitions are checked every time the state is handled.
The preconditions syntax may change in the future since it is still experimental. Preconditions are
only supported in automatic mode and not in request mode ( it's impossible to request a state which
has preconditions ). Future implementations may just list the preconditions which must be true,
which will be 'chained' (Logical AND) with the transitions of the current state. It is the current state
that then should decide what to do if it can not enter the target state.

3.5. Instantiating Machines: SubMachines and RootMachines


As mentionned before: you can look at a SubMachine definition as the definition of a C++ class. It
is merely the template for its instantiations, and you have to instantiate it to actually be able to do
anything with it. There is also a mechanism for passing parameter values to the StateMachines on
instantiation.
Note that you always need to write the instantiation after the definition of the StateMachine you're
instantiating.

3.5.1. Root Machines


A Root Machine is a normal instantiation of a StateMachine, one that does not depend on a parent
StateMachine ( see below ). They are defined as follows:
StateMachine SomeStateMachine
{
initial state initState
{
// ...
}
final state finalState
{
// ...
}
}
RootMachine SomeStateMachine someSMinstance
This makes an instantiation of the StateMachine type SomeStateMachine by the name of 'someSMinstance', which can then be accessed from other scripts (by that name) or via the Processor.

3.5.2. Parameters and public variables


StateMachine public variables
You can define variables at the StateMachine level. These variables are then accessible to the
104

The Program Parser

StateMachine methods (entry, handle, exit), the preconditions, the transitions and ( in the case of a
SubMachine, see below ) the parent Machine.
You can define a StateMachine public variable as follows:
StateMachine SomeStateMachine
{
// a public constant
const double pi = 3.1415926535897
var int counter = 0
initial state initState
{
handle
{
// change the value of counter...
set counter = counter + 1
}
// ...
}
final state finalState
{
entry
{
do someTask.doSomethingWithThisCounter( counter )
}
// ...
}
}
Rootmachine SomeStateMachine mymachine
This example creates some handy public variables in the StateMachine SomeStateMachine, and uses
them throughout the state context. They can also be read and modified from other tasks or programs
:
var int readcounter = 0
set readcounter = taskname.states.mymachine.counter
set taskname.states.mymachine.counter = task#
name.states.mymachine.counter * 2

StateMachine parameters
A StateMachine can have parameters that need to be set on its instantiation. Here's an example:
StateMachine AxisController
{
// a parameter specifying which axis this Controller controls
param int axisNumber
initial state init
{
entry
{
var double power = someTask.getPowerForAxis( axisNum#
ber )
// do something with it...
}
}
}
RootMachine
RootMachine
RootMachine
RootMachine
RootMachine

AxisController
AxisController
AxisController
AxisController
AxisController

axiscontroller1(
axiscontroller2(
axiscontroller3(
axiscontroller4(
axiscontroller5(
105

axisNumber
axisNumber
axisNumber
axisNumber
axisNumber

=
=
=
=
=

1
2
3
4
5

)
)
)
)
)

The Program Parser

RootMachine AxisController axiscontroller6( axisNumber = 6 )


This example creates an AxisController StateMachine with one integer parameter called axisNumber. When the StateMachine is instantiated, values for all of the parameters need to be given in the
form "oneParamName= 'some value', anotherParamName = 0, yetAnotherParamName=some_other_expression + 5". Values need to be provided for all the parameters of the
StateMachine. As you see, a StateMachine can of course be instantiated multiple times with different parameter values.

3.5.3. Building Hierarchies : SubMachines


A SubMachine is a StateMachine that is instantiated within another StateMachine ( which we'll call
the parent StateMachine ). The parent StateMachine is owner of its child, and can decide when it
needs to be started and stopped, by calling the processor's startStateMachine and stopStateMachine
methods.

Instantiating SubMachines
An instantiation of a SubMachine is written as follows:
StateMachine ChildStateMachine
{
initial state initState
{
// ...
}
final state finalState
{
// ...
}
}
StateMachine ParentStateMachine
{
SubMachine ChildStateMachine child1
SubMachine ChildStateMachine child2
initial state initState
{
entry
{
// enter initial state :
do child1.activate()
do child2.activate()
}
exit
{
// enter final state :
do child2.stop()
}
}
final state finalState
{
entry
{
// enter final state :
do child1.stop()
}
}
}
Here you see a ParentStateMachine which has two ChildStateMachines. One of them is started in
the initial state's entry method and stopped in its exit method. The other one is started in the initial
state's entry method and stopped in the final state's entry method.
106

The Program Parser

SubMachine manipulating
In addition to starting and stopping a SubMachine, a parent StateMachine can also inspect its public
variables, change its parameters, and check what state it is in...
Inspecting StateMachine public variables is simply done using the syntax "someSubMachineInstName.someValue", just as you would do if someSubMachineInstName were an Orocos task. Like
this, you can inspect all of a subcontext's public variables.
Setting a StateMachine parameter must be done at its instantiation. However, you can still change
the values of the parameters afterwards. The syntax is: "set someSubMachine.someParam = someExpression". Here's an elaborate example:
StateMachine ChildStateMachine
{
param int someValue
const double pi = 3.1415926535897
initial state initState
{
// ...
}
final state finalState
{
// ...
}
}
StateMachine ParentStateMachine
{
SubMachine ChildStateMachine child1( someValue = 0 )
SubMachine ChildStateMachine child2( someValue = 0 )
var int counter = 0
initial state initState
{
entry
{
do child1.start()
do child2.start()
// set the subcontext's parameter
set child1.someValue = 2
}
handle
{
set counter = counter + 1
// set the subcontext's parameters
set child2.someValue = counter
// use the subcontext's public variables
do someTask.doSomethingCool( child1.someValue )
}
exit
{
do child2.stop()
}
}
final state finalState
{
entry
{
do child1.stop()
}
}
}
You can also query if a child State Machine is in a certain state. The syntax looks like:

107

The Program Parser

someSubMachine.inState( "someStateName" )

3.6. Starting and Stopping StateMachines from scripts


Once a state context is parsed and loaded into the Processor, it can be manipulated from another
script. This can be done through the "states" subtask of the TaskContext in which the state context
was loaded. Assume that you loaded "machine" with subcontexts "axisx" and "axisy" in task
"ATask", you can write
do ATask.states.machine.activate()
do ATask.states.machine.axisx.activate()
// now in request mode...
do ATask.states.machine.axisx.start()
do ATask.states.machine.start()
// now in automatic mode...
do ATask.states.machine.stop()
// again in request mode, in final state
do
do
//
//

ATask.states.machine.reset()
ATask.states.machine.deactivate()
deactivated.
etc.

The first line activates a root StateMachine, thus it enters the initial state and is put in request mode ,
the next line actives its child, the next starts its child, then we start the parent, which bring both in
automatic mode. Then the parent is stopped again, reset back to its initial state and finally deactivated.
Thus both RootMachines and SubMachines can be controlled. Some basic properties of the states
can be inspected likewise :
var bool res = ATask.states.machine.isActive()
set res = ATask.states.machine.axisy.isRunning()
set res = ATask.states.machine.inRequest()
for requests ?
var string current = ATask.states.machine.getState()
rent state
set res = ATask.states.machine.inState( current )

// Active ?
// Running ?
// Waiting
// Get cur#
// inState ?

which makes it possible to monitor state machines from other scripts or an operator console.

3.6.1. On Request Mode Commands


Consider the following StateMachine :
StateMachine X {
// ...
initial state y {
entry {
// ...
}
transitions {
// guard this transition.
if task.checkSomeCondition() then
select z
if task.checkOtherCondition() then
select exit
}
}
state z {
108

The Program Parser

// ...
transitions {
// always good to go to state :
select ok_1
}
}
state ok_1 {
// ...
}
final state exit {
// ...
}
}
RootMachine X x
A program interacting with this StateMachine can look like this :
program interact {
// First activate x :
do states.x.activate()

// activate and wait.

// Request a state transition :


try states.x.requestState("z") catch {
// failed !
}
// ok we are in "z" now, try to make a valid transition :
do states.x.step()
//
do
//
do

enter pause mode :


states.x.pause()
Different ! Executes a single program statement :
states.x.step()

// unpause, by re-entering request Mode :


do states.x.requestMode()
// we are in ok_1 now, again waiting...
do states.x.stop()
// go to the final state
// we are in "exit" now
do states.reset()
// back in state "y", handle current state :
do this.states.x.requestState( this.states.x.getState() )
// etc.
}
The requestState command will fail if the transition is not possible ( for example, the state machine
is not in state y, or task.checkSomeCondition() was not true ), otherwise, the state machine will
make the transition and the command succeeds and completes when the z state is fully entered (it's
init program completed).
The next command, step(), lets the state machine decide which state to enter, and since a transition
to state "ok_1" is unconditionally, the "ok_1" state is entered. The stop() command brings the State
Machine to the final state ("exit"), while the reset command sends it to the initial state ("y"). These
transitions do not need to be specified explicitly, they are always available.
The last command, is a bit cumbersome request to execute the handle program of the current state.
At any time, the State Machine can be paused using pause(). The step() command changes to execute a single program statement or transition evaluation, instead of a full state transition.
All these methods can of course also be called from parent to child State Machine, or across tasks.
109

The Program Parser

3.6.2. Automatic Mode Commands


Consider the following StateMachine, as in the previous section :
StateMachine X {
// ...
initial state y {
entry {
// ...
}
transitions {
// guard this transition.
if task.checkSomeCondition() then
select z
if task.checkOtherCondition() then
select exit
}
}
state z {
// ...
transitions {
// always good to go to state :
select ok_1
}
}
state ok_1 {
// ...
}
final state exit {
// ...
}
}
RootMachine X x
A program interacting with this StateMachine can look like this :
program interact {
// First activate x :
do states.x.activate()

// activate and wait.

// Enter automatic mode :


do states.x.start()
// pause program execution :
do states.x.pause()
// execute a single statement :
do states.x.step()
// resume automatic mode again :
do states.x.start()
// stop, enter final state, in request mode again.
do states.x.stop()
// etc...
}
After the State Machine is activated, it is started, which lets the State Machine enter automatic
mode. If task.checkSomeCondition() evaluates to true, the State Machine will make the transition to
state "z" without user intervention, if task.checkOtherCondition() evaluates to true, the "exit" state
will be entered.
When running, the State Machine can be paused at any time using pause(), and a single program
110

The Program Parser

statement ( a single line ) or single transition evaluation can be executed with calling step(). Automatic mode can be resumed by calling start() again.
To enter the request mode when the State Machine is in automatic mode, one can call the requestMode() command, which will finish the program or transition the State Machine is making and will
complete if the State Machine is ready for requests.
All these methods can of course also be called from parent to child State Machine, or across tasks.

4. Program and State Example


This sections shows the listings of an Orocos State Description and an Orocos Program Script. They
are fictitious examples (but with valid syntax) which may differ from actual available tasks. The example tries to exploit most common functions.

Example 9.2. StateMachine Example (state.osd)


StateMachine MachineMachine
{
var bool error = false
/**
* This state is entered when the StateMachine is loaded.
* The kernel is not running yet...
*/
initial state init_state {
transitions {
select startup_state
}
}
/**
* Kernel is running, select the components.
*/
state startup_state {
entry {
do Kernel.startComponent("HWSensor")
do Kernel.startComponent("MoveToGenerator")
}
transitions {
select stop_state
}
}
/**
* This state is only reached when the StateMachine
* is stopped.
*/
final state fini_state {
entry {
do Kernel.stopComponent("HWSensor")
do Kernel.stopComponent("MoveToGenerator")
}
}
/**
* This state is the 'turn off' state of the
* machine.
*/
state stop_state {
entry {
// stop some components
do Kernel.stopComponent("HWEffector")
111

The Program Parser

do Kernel.stopComponent("PIDController")
do PIDController.reset()
transitions {
if HMI.start_pushed() && error == 0 then
select run_state
}
}
/**
* This state puts the machine under 'control'
* effectively accepting commands and driving
* the machine.
*/
state run_state {
entry {
// make sure we are not moving
do MoveToGenerator.safeStop()
// Select components controlling the machine
do Kernel.startComponent("PIDController")
do Kernel.startComponent("HWEffector")
}
transitions {
if HMI.stop_pushed() then
select stop_state
if HMI.start_program() then
select exec_state
}
}
/**
* This state starts a previously loaded
* program.
*/
state exec_state {
entry {
do programs.MyProgram.start()
}
exit {
set error = programs.MyPorgram.inError()
do programs.MyProgram.stop()
}
transitions {
if HMI.stop_program() then
select run_state
}
// Detect Program Failure :
if programs.MyProgram.inError() then
select stop_state
}
}
RootMachine MainMachine mainMachine

Example 9.3. Program example (program.ops)


/**
* This program is executed in the exec_state.
*/
/**
112

The Program Parser

* Request the HMI to load the user selected


* trajectory into the kernel.
*/
export function HMILoadTrajectory() {
// request a 'push' of the next
// trajectory :
do HMI.requestTrajectory()
// when the HMI is done :
do Generator.loadTrajectory()
}
/**
* Do a Homing (reset) of the axes.
* This could also be done using a Homing state,
* without a program.
*/
export function ResetAxes() {
do Kernel.selectComponent("HomingGenerator")
do HomingGenerator.homeAll()
}
export function ResetAxis(int nr) {
do Kernel.selectComponent("HomingGenerator")
do HomingGenerator.homeAxis( nr )
}
/**
* Request the Generator to use the current
* trajectory.
*/
function runTrajectory() {
do Generator.startTrajectory()
// this function returns when the
// trajectory is done.
}
program DemoRun {
do HMI.display("Program Started\n")
var int cycle = 0
// We actually wait here until a
// Trajectory is present in the HMI.
do nothing until {
if HMI.trajectoryPresent then continue
}
while HMI.cycle {
do HMI.display("Cycle nr: %d.\n", cycle )
do ResetAxes()
do HMIRequestTrajectory()
do runTrajectory()
do Timer.sleep( 5.0 ) // wait 5s
}
do HMI.display("Program Ended\n")
}

5. Extending the parser


5.1. Parser Limitations
For various reasons, during the development of the Orocos parser, it has proven necessary to hardcode various things, mostly relating to the defined types, and the operations supported on them. The
113

The Program Parser

parser supports using different types of objects than the predefined ones, but the major limitations
are:

It does not know of unsigned int. It can not call component methods with unsigned int arguments.

Only a hardcoded set of types can be used as variables, constants and aliases.

Operators like +, *, >= etc. are only supported for a hardcoded set of types.

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.

5.2. Alleviating the Limitations


We will address the ways to address the various limitations in the same order as they were given
above..

5.2.1. Adding value types


Adding types that can be used as variables, constants or aliases should be very trivial. It should suffise to simply add a single line to a single file.
The value types supported are kept in the TypesRepository class in orocostree/parser/src/Types.hpp. The list of value types is constructed in the TypesRepository constructor
in the Types.cpp file. It looks something like the following:
data["int"] = new TemplateTypeInfo<int>();
data["string"] = new TemplateTypeInfo<std::string>();
data["double"] = new TemplateTypeInfo<double>();
data["bool"] = new TemplateTypeInfo<bool>();
Adding your own type comes down to just adding a similar line there. There are however some restrictions on what types can be used (like having a proper, publically accessible copy constructor,
e.g. ), but if you have properly designed your class as a normal C++ value-based class, then there
should be no problem.

5.2.2. Overloading operators


Operator overloads are stored in the class OperatorRegistry in orocos-tree/parser/src/Operators.hpp.
The list of supported operators is built up in the OperatorRegistry constructor in the Operators.cpp
file. It looks something like this:
// boolean stuff:
add( newUnaryOperator( "!", std::logical_not<bool>() ) );
add( newBinaryOperator( "&&", std::logical_and<bool>() ) );
add( newBinaryOperator( "||", std::logical_or<bool>() ) );
add( newBinaryOperator( "==", std::equal_to<bool>() ) );
add( newBinaryOperator( "!=", std::not_equal_to<bool>() ) );
Adding your own should not be terribly hard. The hardest part is that as the second argument to
newUnaryOperator, newBinaryOperator or newTernaryOperator, you need to specify a STL Adaptable Functor, and even though the STL provides many predefined one's, it does not provide all possible combinations, and you might end up having to write your own.. The STL does not at all
provide any "ternary operators", so if you need one of those, you'll definitely have to write it yourself.

114

The Program Parser

Note that this section is only about adding overloads for existing operators, if you want to add new
operators, you should look at the next section.

5.2.3. Adding special syntax


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

115

You might also like