The Orocos Component Builder's Manual

Open RObot COntrol Software

1.4.1

Orocos Real-Time Toolkit Version 1.4.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 Front-Cover Texts, and with no Back-Cover Texts. A copy of this license can be found at http://www.fsf.org/copyleft/fdl.html.

Abstract

This document gives an introduction to building your own components for the Orocos ( Open RObot COntrol Software ) project.


Table of Contents

1. How to Read this Manual
1. Component Interfaces
2. Component Implementation
3. Orocos Real-Time Toolkit Overview
4. Orocos Real-Time Toolkit Software Structure
2. Setting up the Component Interface
1. Introduction
2. Hello World !
2.1. Setting up the TaskBrowser
2.2. Starting your First Application
2.3. Displaying a TaskContext
2.4. Listing the Interface
2.5. Calling a Method
2.6. Sending a Command
2.7. Changing Values
2.8. Emiting Events
2.9. Reading and Writing Data Ports
2.10. Advanced Component Browsing
2.11. Last Words
3. Setting Up a Basic Task
3.1. Task Application Code
3.2. Starting Task Execution
3.3. A TaskContext's Error and Active states
3.4. A TaskContext's Run-Time Errors
3.5. Error States Example
3.6. Interfacing the TaskContext
3.7. Introducing the TaskContext's Interface
3.8. The Data Flow Interface
3.9. The Method Interface
3.10. Method Argument and Return Types
3.11. The Attributes and Properties Interface
3.12. The Command Interface
3.13. The Event Interface
4. Connecting TaskContexts
4.1. Setting up the Execution Flow
4.2. Setting up the Data Flow
4.3. Disconnecting Tasks
5. Using Tasks
5.1. Task Property Configuration
5.2. Task Scripts
6. Deploying Components
6.1. Overview
6.2. Embedded TaskCore Deployment
6.3. Embedded TaskContext Deployment: C++ Interface
6.4. Full TaskContext Deployment: Dynamic Interface
6.5. Putting it together
7. Advanced Techniques
7.1. Using the TaskContext
7.2. Wrapping Methods in Functions
7.3. Waiting for Something : Synchronisation
7.4. Polymorphism : Task Interfaces
3. Orocos Scripting Reference
1. Introduction
2. General Scripting Concepts
2.1. Comments
2.2. Identifiers
2.3. Expressions
2.4. Parsing and Loading Programs
3. Orocos Program Scripts
3.1. Program Execution Semantics
3.2. Program Syntax
3.3. Starting and Stopping Programs from scripts
4. Orocos State Descriptions : The Real-Time State Machine
4.1. Introduction
4.2. StateMachine Workings
4.3. Parsing and Loading StateMachines
4.4. Defining StateMachines
4.5. Instantiating Machines: SubMachines and RootMachines
4.6. Starting and Stopping StateMachines from scripts
5. Program and State Example
4. Distributing Orocos Components with CORBA
1. Overview
1.1. Status
1.2. Requirements and Setup
1.3. Limitations
2. Code Examples
3. Usage
4. Orocos Corba Interfaces
5. Using the Naming Service
5.1. Example
5. Core Library Reference
1. Introduction
2. Periodic and non Periodic Activities
2.1. Creating a Periodic Activity
2.2. Periodic Activity Ordering
2.3. Example Periodic Activity Creation
2.4. Non Periodic Activities
2.5. Selecting the Scheduler
2.6. Custom or Slave Activities
2.7. Accessing the Threads from Activities
2.8. Simulation
3. Commands
3.1. The Command Processor
4. Events
4.1. Event Basics
4.2. setup() and the Handle object
4.3. Choosing the Asynchronous Thread
4.4. Event Overrun Policy
4.5. The Completion Processor
5. Time Measurement and Conversion
5.1. The TimeService
5.2. Usage Example
6. Attributes
7. Properties
7.1. Introduction
7.2. Grouping Properties in a PropertyBag
7.3. Marshalling and Demarshalling Properties (Serialization)
8. The NameServer
8.1. Introduction
8.2. Using the NameServer
9. Buffers and DataObjects
9.1. Buffers
9.2. DataObjects
10. Logging
6. OS Abstraction Reference
1. Introduction
1.1. Real-time OS Abstraction
2. The Operating System Interface
2.1. Basics
3. OS directory Structure
3.1. The RTAI/LXRT OS target
3.2. Porting Orocos to other Architectures / OSes
3.3. OS Header Files
4. Using Threads and Real-time Execution of Your Program
4.1. Writing the Program main()
4.2. The Orocos Thread System
4.3. Synchronisation Primitives
7. Hardware Device Interfaces
1. The Orocos Device Interface (DI)
1.1. Structure
1.2. Example
2. The Device Interface Classes
2.1. Physical IO
2.2. Logical Device Interfaces
3. Porting Device Drivers to Device Interfaces
4. Interface Name Serving

List of Figures

1.1. Orocos as Middleware
1.2. Real-Time Toolkit Layers
2.1. Components Run in Threads
2.2. Schematic Overview of a TaskContext
2.3. Schematic Overview of the Hello Component.
2.4. Schematic Overview of a TaskContext
2.5. TaskContext State Diagram
2.6. Executing a TaskContext
2.7. Extended TaskContext State Diagram
2.8. Possible Run-Time failure states.
2.9. TaskContext Interface
2.10. Component Deployment Levels
2.11. Example Component Deployment.
3.1. State Change Semantics in Reactive Mode
3.2. State Change Semantics in Automatic Mode
5.1. Execution sequence diagram
5.2. Tasks Sending Commands
5.3. Event Handling
5.4. DataObjects versus Buffers
6.1. OS Interface overview
7.1. Device Interface Overview

List of Tables

2.1. Method Return & Argument Types
3.1. array and string constructors
5.1. Thread and Activity summary
5.2. Logger Log Levels
6.1. Header Files
7.1. Physical IO Classes

List of Examples

2.1. TaskContext Data Flow Topology Example
2.2. TaskContext Peer Disconnection Example
3.1. string and array creation
3.2. StateMachine Definition Format
3.3. StateMachine Example (state.osd)
3.4. Program example (program.ops)
5.1. Example Periodic Activity Creation
5.2. Example Periodic Thread Interaction
5.3. Using Events
5.4. Event Types
5.5. Creating attributes
5.6. Using properties
5.7. Accessing a Buffer
5.8. Accessing a DataObject
5.9. Using the Logger class
6.1. Locking a Mutex
7.1. Using the name service

Chapter 1. How to Read this Manual

This manual is for Software developers who wish to write their own software components using the Orocos Real-Time Toolkit. There is also a CoreLib Reference Chapter at the end to find out the precise semantics of our communication primitives and other important classes. The Orocos hardware abstraction is included as well. The HTML version of this manual links to the API documentation of all classes.

1. Component Interfaces

The most important Chapters to get started building a component are presented first. Orocos components are implemented using the 'TaskContext' class and the following Chapter explains step by step how to define the interface of your component, such that you can interact with your component from a user interface or other component.

2. Component Implementation

For implementing algorithms within your component, various C++ function hooks are present in wich you can place custom C++ code. As your component's functionality grows, you can extend its scripting interface and call your algorithms from a script.

The Orocos Scripting Chapter details how to write programs and state machines. "Advanced Users" may benefit from this Chapter as well since the scripting language allows to 'program' components without recompiling the source.

3. Orocos Real-Time Toolkit Overview

The Real-Time Toolkit allows setup, distribution and the building of real-time software components. It is sometimes refered to as 'middleware' because it sits between the application and the Operating System. It takes care of the real-time communication and execution of software components.

Figure 1.1.  Orocos as Middleware

Orocos as Middleware

Orocos provides a limited set of components for application development. The Orocos Component Library (OCL) is a collection of components ranging from general purpose to hardware specific components. They serve as an example, although some are using complex constructs of the Real-Time Toolkit. Two noteworthy components are the TaskBrowser and the DeploymentComponent. The former provides a console which allows you to manipulate remote or in-process components and lets you browse the component network. The latter is a component which can load other components into the same process. For beginners, the TaskBrowser is used to instruct the DeploymentComponent to load, connect and configure a given list of components, but for matured applications, the DeploymentComponent is given an XML file which describes which components to load, connect and start in the current application.

The components of the Orocos Component Library are documented seperately on the OCL webpage.

4. Orocos Real-Time Toolkit Software Structure

The Real-Time Toolkit is structured in layers on top of the Operating System and the devices (IO).

Figure 1.2.  Real-Time Toolkit Layers

Real-Time Toolkit Layers

An Orocos component is built upon the Real-Time Toolkit (RTT) library. It allows you to build components which are accessible over a network, configurable using XML files and listen to a scripting interface, which allows components to be controlled using text commands. A component which accesses IO devices can use the Orocos Device Interface as well which defines how to interact with analog and digital IO and encoders. Of course, components can make use of external, non-Orocos libraries as well.

Orocos components which only use the Real-Time Toolkit are portable over different Operating Systems (OS) and processor architectures. Orocos has an internal OS abstraction which allows the components to run on any supported architecture. When your component uses an external library, for example a camera or vision library, portability depends on these libraries.

Chapter 2. Setting up the Component Interface

Abstract

This document describes the Orocos Component Model, which allows to design Real-Time software components which transparantly communicate with each other.

1. Introduction

This manual documents how multi-threaded components can be defined in Orocos such that they form a thread-safe robotics/machine control application. Each control component is defined as a "TaskContext", which defines the environment or "context" in which an application specific task is executed. The context is described by the five Orocos primitives: Event, Property, Command, Method and Data Port. This document defines how a user can write his own task context and how it can be used in an application.

Figure 2.1.  Components Run in Threads

Components Run in Threads

Components run in (periodic) threads and can communicate transparantly. The Orocos RTT does a 'best effort' to deliver the highest performance to the highest priority threads.


A component is a basic unit of functionality which executes one or more (real-time) programs in a single thread. The program can vary from a mere C function over a real-time program script to a real-time 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). Real-time components can communicate with non real-time components (and vice verse) transparently.

[Note]Note

In this manual, the words task and component are used as equal words, meaning a software component built using the C++ TaskContext class.

The Orocos Component Model 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.

  • Interfaces for component distribution.

  • C++ class implementations for all the above.

This chapter relates to other chapters as such :

Core Library

provides the Command and Event infrastructure, activity to thread mapping, Properties and lock-free data exchange implementations.

Execution Engine

provides a real-time program execution framework. It executes the real-time programs and scripts which interact with other tasks.

Orocos Scripting

provides a real-time scripting language which is convertible to a form which can be accepted by the Execution Engine.

The Scripting chapter gives more details about script syntax for state machines and programs.

Figure 2.2.  Schematic Overview of a TaskContext

Schematic Overview of a TaskContext

The Execution Flow is formed by Programs and State Machines sending commands, events,... to Peer Tasks. The Data Flow is the propagation of data from one task to another, where one producer can have multiple consumers and vice verse.


A component's interface consists of : Attributes and Properties, Commands, Methods, Events and Data Flow ports which are all public. The class TaskContext groups all these interfaces and serves as the basic building block of applications. A component developer 'builds' these interfaces using the instructions found in this manual.

2. Hello World !

This section introduces tasks through the "hello world" application, which is included in the Orocos Component Library. It contains one TaskContext component, HelloWorld, which has one instance of each communication primitive.

Figure 2.3.  Schematic Overview of the Hello Component.

Schematic Overview of the Hello Component.

Our hello world component.


2.1. Setting up the TaskBrowser

The way we interact with TaskContexts during development of an Orocos application is through the Task Browser. 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 create a TaskBrowser and call its loop() method. When the program is started from a console, the TaskBrowser takes over user input and output. 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 or press the up arrow to scroll through previous commands.

[Note]Note

The TaskBrowser is a component of its own which is found in the Orocos Component Library (OCL).

  #include <ocl/TaskBrowser.hpp>
  #include <rtt/os/main.h>
  // ...

  using namespace Orocos;

  int ORO_main( int, char** )
  {
      // Create your tasks
      TaskContext* task = ...

      // when all is setup :
      TaskBrowser tbrowser( task );

      tbrowser.loop();
      return 0;
  }
      

2.2. Starting your First Application

Now let's start the helloworld application. If you downloaded OCL and compiled it from source, You can do this by entering the helloworld subdirectory of your OCL build directory and running ./helloworld

In case you got OCL as a binary package, enter loadComponent("Hello","orocos-helloworld") at the prompt of the deployer application for your target: ORO_LOGLEVEL=5 deployer-gnulinux for example. This command loads the Orocos-HelloWorld component library and creates a component with name "Hello" (Requires OCL 1.4.1 or later). Finally, type cd Hello to start with the exercise.

0.016 [ Info   ][main()] ./helloworld manually raises LogLevel to 'Info' (5). See also file 'orocos.log'.
0.017 [ Info   ][main()] **** Creating the 'Hello' component ****
0.018 [ Info   ][ConnectionC] Creating Asyn connection to the_event.
0.018 [ Info   ][ExecutionEngine::setActivity] Hello is periodic.
0.019 [ Info   ][main()] **** Starting the 'Hello' component ****
0.019 [ Info   ][main()] **** Using the 'Hello' component    ****
0.019 [ Info   ][main()] **** Reading a Property:            ****
0.019 [ Info   ][main()]      the_property = Hello World
0.019 [ Info   ][main()] **** Sending a Command:             ****
0.020 [ Info   ][main()]      Sending the_command : 1
0.020 [ Info   ][main()] **** Calling a Method:              ****
0.020 [ Info   ][main()]      Calling the_Method : Hello World
0.020 [ Info   ][main()] **** Emitting an Event:             ****
0.021 [ Info   ][main()] **** Starting the TaskBrowser       ****
0.021 [ Info   ][TaskBrowser] Creating a BufferConnection from the_buffer_port to the_buffer_port with size 13
0.021 [ Info   ][TaskBrowser] Connected Port the_buffer_port to peer Task Hello.
0.022 [ Info   ][Hello] Creating a DataConnection from the_data_port to the_data_port
0.022 [ Info   ][Hello] Connected Port the_data_port to peer Task TaskBrowser.
   Switched to : Hello
0.023 [ Info   ][main()] Entering Task Hello
0.023 [ Info   ][Hello] Hello Command: World
0.023 [ Info   ][Hello] Receiving Event: Hello World

  This console reader allows you to browse and manipulate TaskContexts.
  You can type in a command, event, method, expression or change variables.
  (type 'help' for instructions)
    TAB completion and HISTORY is available ('bash' like)

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

The first [ Info ] lines are printed by the Orocos Logger, which has been configured to display informative messages to console. Normally, only warnings or worse are displayed by Orocos. You can always watch the log file 'orocos.log' in the same directory to see all messages. After the [Log Level], the [Origin] of the message is printed, and finally the message itself. These messages leave a trace of what was going on in the main() function before the prompt appeared.

Depending on what you type, the TaskBrowser will act differently. The built-in 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 Hello. (Status of last Command : none )
 (type 'ls' for context info) :1+1
      Got :1+1
   = 2
      

2.3. Displaying a TaskContext

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. Since there are no peers other than the TaskBrowser itself, one can not cd anywhere in this example.

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

 Listing Hello :

 Attributes   :
 (Attribute)      string the_attribute      = Hello World
 (Attribute)      string the_constant       = Hello World
 (Property )      string the_property       = Hello World          (Hello World Description)

 Interface    :
  Methods      : the_method isRunning start stop trigger update
  Commands     : the_command
  Events       : the_event

 Objects      :
            this (  )
   the_data_port ( A Task Object. )
 the_buffer_port ( A Task Object. )

 Ports        : the_data_port the_bufferPort
 Peers        : TaskBrowser
      
[Note]Note

To get a quick overview of the commands, type help.

First you get a list of the Properties and Attributes (alphabetical) of the current component. Properties are meant for configuration and can be written to disk. Attributes are solely for run-time values. Each of them can be changed (except constants.)

Next, the interface of this component is listed: One method is present the_method, one command the_command and one event the_event. They all print a 'Hello World' string when invoked.

In the example, the current task has only three objects: this, the_data_port and the_buffer_port. The this object serves as the public interface of the Hello component. These objects contain methods, commands or events. The the_data_port and the_buffer_port objects are created to represent the data ports of the Hello component. They allow you to to send or receive data to these ports and check if they are connected.

Last, the peers are shown, that is, the components which are known, and may be used, by this component. The HelloWorld component is a stand-alone component and has only the TaskBrowser as a peer.

2.4. Listing the Interface

To get a list of the Task's interface, you can always type an object name, for example this.

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) : this
      Got :this

Printing Interface of 'Hello' :

  Command    : bool the_command( string the_arg )
   Hello Command Description
   the_arg : Use 'World' as argument to make the command succeed.
  Method     : string the_method( )
   Hello Method Description
  Event     : void the_Event( string the_data )
   Hello Event Description
   the_data : The data of this event.
      

Now we get more details about the commands, methods and events registered in the public interface. We see now that the the_command command takes one argument as a string, or that the the_method method returns a string. One can invoke each one of them.

2.5. Calling a Method

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_method()
      Got :the_method()
 = Hello World
      

Methods are called directly and the TaskBrowser prints the result. The return value of the_method() was a string, which is "Hello World". This works just like calling a 'C' function.

2.6. Sending a Command

When a command is entered, it is sent to the Hello component, which will execute it in its own threadon behalf of the sender. The different stages of its lifetime are displayed by the prompt. Hitting enter will refresh the status line:

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_command("World")
      Got :the_command("World")

 In Task Hello. (Status of last Command : queued)
1021.835 [ Info   ][Hello] Hello Command: World
 (type 'ls' for context info) :                   

1259.900 [ Info   ][main()] Checking Command: World
 In Task Hello. (Status of last Command : done )
 (type 'ls' for context info) :                   
      

A Command might be rejected (return false) in case it received invalid arguments:

 In Task Hello. (Status of last Command : done )
 (type 'ls' for context info) :the_command("Belgium")
      Got :the_command("Belgium")

 In Task Hello. (Status of last Command : queued )
 (type 'ls' for context info) :
1364.505 [ Info   ][Hello] Hello Command: Belgium


 In Task Hello. (Status of last Command : fail )
 (type 'ls' for context info) :         
      

2.7. Changing Values

Besides sending 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 Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_attribute
      Got :the_attribute
 = Hello World
 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_attribute = "Veni Vidi Vici !"
      Got :the_attribute = "Veni Vidi Vici !"
 = true

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_attribute
      Got :the_attribute
 = Veni Vidi Vici !
      

2.8. Emiting Events

Finally, let's emit an Event. The Hello World Event requires a payload. A callback handler was registered by the component, thus when we emit it, it can react to it:

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_event(the_attribute)
      Got :the_event(the_attribute)
 = true
 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :
354.592 [ Info   ][Hello] Receiving Event: Veni Vidi Vici !
      

The example above passed the the_attribute object as an argument to the event, and it was received by our task correctly. Events are related to commands, but allow broadcasting of data, while a command has a designated receiver.

2.9. Reading and Writing Data Ports

The Data Ports can be accessed through the the_data_port and the_buffer_port object interfaces.

Initially, these ports are unconnected, as the HelloWorld component did not connect its ports to another component. Unconnected ports can hardly be used. In order to test them, you can instruct the TaskBrowser to connect to them. This is done by entering the .connect command (note the '.').

Since each port has an associated object, we can inspect the interface of a port by typing its name:

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_data_port
      Got :the_data_port

Printing Interface of 'the_data_port' :

  Method     : string Get( )
   Get the current value of this Data Port
  Method     : void Set( string const& Value )
   Set the current value of this Data Port
   Value : The new value.
      

The the_data_port object has two methods: Get() and Set(). Since data ports are used for sending unbuffered data packets between components, this makes sense. One can interact with the ports as such:

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_data_port.Set("World")
      Got :the_data_port.Set("World")
 = (void)

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :the_data_port.Get()
      Got :the_data_port.Get()
 = World
      

When a value is Set(), it is sent to whatever is connected to that port, when we read the port using Get(), we see that the previously set value is present. The advantage of using ports is that they are completely thread-safe for reading and writing, without requiring user code. The Hello component also contains a the_buffer_port for buffered data transfer. You are encouraged to play with that port as well.

2.10. Advanced Component Browsing

Remember that the TaskBrowser was a component as well ? When a user enters ls, the interface of the visited component is listed. It is also possible to get an 'outside' view of the visited component, through the eyes of an external component. The leave allows a view from within the TaskBrowser itself:

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :leave
1001.607 [ Info   ][main()] Watching Task Hello

 Watching Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :ls

 Listing TaskBrowser :

 Attributes   : (none)

 Interface    :
  Methods      : isRunning start stop trigger update
  Commands     : (none)
  Events       : (none)

 Objects      :
            this (  )
        the_data_port ( A Task Object. )
      the_buffer_port ( A Task Object. )

 Ports        : the_data_port the_buffer_port
 Hello Peers : TaskBrowser
      

The following things are noteworthy: 'ls' shows now the contents of the TaskBrowser itself and no longer of the Hello Component. In this example, the TaskBrowser has the same ports as the component it visits: the_data_port and the_buffer_port. These were created when we issued the .connect previously. Otherwise, there would be no data ports.

One can return to the 'inside' view again by typing enter:

 Watching Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :enter
1322.653 [ Info   ][main()] Entering Task Hello

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

2.11. Last Words

Last but not least, hitting TAB twice, will show you a list of possible completions, such as peers or commands :

 In Task Hello. (Status of last Command : none )
 (type 'ls' for context info) :
the_attribute     the_event         cd ..         quit
the_buffer_port.  the_method        help          this.
the_command       the_property      leave
the_constant      TaskBrowser.  list
the_data_port.     cd            ls
 (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.

In order to quit the TaskBrowser, enter quit:

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

1575.720 [ Info   ][ExecutionEngine::setActivity] Hello is disconnected from its activity.
1575.741 [ Info   ][Logger] Orocos Logging Deactivated.      
      

The TaskBrowser Component is application independent, so that your end user-application might need a more suitable interface. However, for testing and inspecting what is happening inside your real-time programs, it is a very useful tool. The next sections show how you can add properties, commands, methods etc to a TaskContext.

[Note]Note

If you want a more in-depth tutorial, see the 'task-intro' example for a TaskBrowser which visits a network of three TaskContexts.

3. Setting Up a Basic Task

Tasks are implemented by the TaskContext class. It is useful speaking of a context because it defines the context in which an activity (a program) operates. It defines the interface of the task, its properties, its peer tasks and uses its ExecutionEngine to execute its programs and to process commands and events.

This section walks you through the definition of an example task in order to show you how you could build your own task.

[Important]Important

The ready-to-execute code of this section can be found in the 'simple-task' RTT example on the RTT Source code page of the Orocos.org website.

A TaskContext is constructed as :

  #include <rtt/TaskContext.hpp>

  // we assume this is done in all the following code listings :
  using namespace RTT;

  TaskContext a_task("ATask");
      

The argument is the (unique) name of the task.

A task's interface consists of : Commands, Methods, Ports, Attributes and Properties and Events, which are all public. We will refer to them as members.

Figure 2.4.  Schematic Overview of a TaskContext

Schematic Overview of a TaskContext

The Execution Flow is formed by the processing of commands, methods and events (which call in turn user functions). The Data Flow is the propagation of data from one task to another, where one producer can have multiple consumers.


When a TaskContext is running, it accepts commands or events using its Execution Engine. The Execution Engine will check periodically for new commands in it's queue and execute programs which are running in the task. When a TaskContext is started, the ExecutionEngine is running. The complete state flow of a TaskContext is shown in Figure 2.5, “ TaskContext State Diagram ”.

Figure 2.5.  TaskContext State Diagram

TaskContext State Diagram

During creation, a component is in the Init state. When constructed, it enters the PreOperational or Stopped (default) state. If it enters the PreOperational state after construction, it requires an additional configure() call before it can be start()'ed. The figure shows that for each API function, a user 'hook' is available.


The first section goes into detail on how to use these hooks.

3.1. Task Application Code

The user application code is filled in by inheriting from the TaskContext and implementing the 'Hook' functions. There are five such functions which are called when a TaskContext's state changes.

The user may insert his configuration-time setup/cleanup code in the configureHook() (read XML, print status messages etc.) and cleanupHook() (write XML, free resources etc.).

The run-time (or: real-time) application code belongs in the startHook(), updateHook() and stopHook() functions.

class MyTask 
    : public TaskContext
  {
  public:
       MyTask(std::string name) 
         : TaskContext(name)
       {
          // see later on what to put here.
       }
  
       /**
        * This function is for the configuration code.
	* Return false to abort configuration.
	*/
       bool configureHook() {
          // ...
	  return true;
       }

       /**
        * This function is for the application's start up code.
	* Return false to abort start up.
	*/
       bool startHook() {
          // ...
	  return true;
       }

       /**
        * This function is called by the Execution Engine.
	*/
       void updateHook() {
          // Your component's algorithm/code goes in here.
       }

       /**
        * This function is called when the task is stopped.
	*/
       void stopHook() {
          // Your stop code after last updateHook()
       }

       /**
        * This function is called when the task is being deconfigured.
	*/
       void cleanupHook() {
          // Your configuration cleanup code
       }
  };
[Important]Important

By default, the TaskContext enters the Stopped state (Figure 2.5, “ TaskContext State Diagram ”) when it is created, which makes configure() an optional call.

If you want to force the user to call configure() of your TaskContext, set the TaskState in your constructor as such:

class MyTask 
    : public TaskContext
  {
  public:
       MyTask(std::string name) 
         : TaskContext(name, PreOperational) // demand configure() call.
       {
          //...
       }
  };

When configure() is called, the configureHook() is executed and must return false if it failed. The TaskContext drops to the PreOperational state in that case. When configureHook() succeeds, the TaskContext enters the Stopped state and is ready to run.

A TaskContext in the Stopped state (Figure 2.5, “ TaskContext State Diagram ”) may be start()'ed upon which startHook() is called once and may abort the start up sequence by returning false. If true, it enters the Running state and updateHook() is called (a)periodically by the ExecutionEngine, see below. When the task is stop()'ed, stopHook() is called after the last updateHook() and the TaskContext enters the Stopped state again. Finally, by calling cleanup(), the cleanupHook() is called and the TaskContext enters the PreOperational state.

3.2. Starting Task Execution

The functionality of a task, i.e. its algorithm, is executed by its internal Execution Engine. To run a TaskContext, you need to use one of the ActivityInterface classes from the RTT, for example PeriodicActivity or NonPeriodicActivity. This relation is shown in Figure 2.6, “ Executing a TaskContext ”. The Activity classes will allocate a thread which executes the Execution Engine. The chosen ActivityInterface object will invoke the Execution Engine, which will in turn invoke the application's hooks above.

Figure 2.6.  Executing a TaskContext

Executing a TaskContext

You can make a TaskContext '(re-)active' by creating an Activity object which executes its Execution Engine. The Execution Engine delegates all work to specific 'Processors' and user code in updateHook().


3.2.1. Periodic Task Execution

A common task in control is executing an algorithm periodically. This is done by attaching a periodic activity to the Execution Engine.

  #include <rtt/PeriodicActivity.hpp>

  using namespace RTT;

  TaskContext* a_task = new MyTask("the_task");
  // create a periodic activity with priority=5, period=1000Hz
  PeriodicActivity act(5, 0.001, a_task->engine() );
  // ... start the execution engine of a_task :
  a_task->start(); 
  // ...
  a_task->stop();

Which will run the Execution Engine of "ATask" with a frequency of 1kHz. This is the frequency at which state machines are evaluated, program steps taken, commands and events are accepted and executed and the application code in updateHook() is run. When the periodic activity is stopped again, all programs are stopped, state machines are brought into the final state and no more commands or events are accepted.

An updateHook() function of a periodic task could look like:

  class MyTask 
    : public TaskContext
  {
  public:
       // ...
  
       /**
        * This function is periodically called.
	*/
       void updateHook() {
          // Your algorithm for periodic execution goes inhere
	  outPort.Set( inPort.Get() * 2.0 );
       }

  };

You can find more detailed information in Section 2.1, “Creating a Periodic Activity” in the CoreLib reference.

3.2.2. Non Periodic Task Execution

A TaskContext can also be run in a non periodic activity. This is useful when updateHook() must wait on network connections or does any other blocking operation.

  #include <rtt/NonPeriodicActivity.hpp>

  using namespace RTT;

  TaskContext* a_task = new MyTask("the_task");
  // create a non periodic activity with priority=5
  NonPeriodicActivity nonpAct(5, a_task->engine() );
  // ... start the execution engine of a_task :
  a_task->start();
  // ...
  a_task->stop();

In case the Execution Engine waits for new Commands or Events to come in to be executed. Each time such an event happens, the user's application code (updateHook()) is called as well.

An updateHook() function of a non periodic task could look like:

  class MyTask 
    : public TaskContext
  {
  public:
       // ...
  
       /**
        * This function is only called by the Execution Engine 
	* when 'trigger()' is called or an event or command arrives.
	*/
       void updateHook() {
            // Your blocking algorithm goes inhere
            char* data;
            double timeout = 0.02; // 20ms
            int rv = my_socket_read(data, timeout);

            if (rv == 0) {
	       // process data
               this->stateUpdate(data);
            }
	    
            // This is special for non periodic activities, it makes
            // the TaskContext call updateHook() again after
            // commands and events are processed.
            this->engine()->getActivity()->trigger(); 
       }

  };
[Warning]Warning

Non periodic activities should be used with care and with much thought in combination with scripts (see later). The ExecutionEngine will do absolutely nothing if no commands or asynchronous events or no trigger comes in. This may lead to surprising 'bugs' when program scripts or state machine scripts are executed, as they will only progress upon these events and seem to be stalled otherwise.

You can find more detailed information in Section 2.4, “Non Periodic Activities” in the CoreLib reference.

3.3. A TaskContext's Error and Active states

In addition to the PreOperational, Stopped and Running TaskContext states, you can use two additional states for more advanced component behaviour: the FatalError and the Active states, as in Figure 2.7, “ Extended TaskContext State Diagram ”

Figure 2.7.  Extended TaskContext State Diagram

Extended TaskContext State Diagram

This figure shows the complete state diagram of a TaskContext. This is Figure 2.5, “ TaskContext State Diagram ” extended with two more states: Active and FatalError.


The FatalError state is entered whenever the TaskContext's fatal() function is called, and indicates that an unrecoverable error occured, possibly in the updateHook() or in any other component function. The ExecutionEngine is immediately stopped and stopHook() is called when this state is entered.

In order to leave the FatalError state, one needs to call resetError() which calls resetHook(), the user function, in turn. When resetHook() returns true, error recovery was possible and the component becomes Stopped again. In case resetHook() returns false, the TaskContext becomes PreOperational and requires configuration.

The Active state is for processing incomming commands and events, but not yet running the updateHook() user function. It is used for components that require to accept commands before they are running. The Active state is optional and can be skipped.

The Active state is entered when activate() is called from the Stopped state. In order to check this transition the user function activateHook() is called which must return true to let the transition succeed, otherwise, the TaskContext remains Stopped. Once the TaskContext is Active, it can be start()'ed (continue to Running) or stop()'ed (go back to Stopped).

3.4. A TaskContext's Run-Time Errors

It is possible that non-fatal run-time errors occur which may require user action on one hand, but do not prevent the component from performing it's task, or allow degraded performance. Therefor, in the Running state, one can make a transition to the RunTimeWarning and RunTimeError sub-states by calling warning() and error() respectively. See Figure 2.8, “ Possible Run-Time failure states. ”

Figure 2.8.  Possible Run-Time failure states.

Possible Run-Time failure states.

This figure shows the sub-states of the Running state as a UML state chart.


When the application code calls error(), the RunTimeError state is entered and errorHook() is executed instead of updateHook(). If at some moment the component detects that it can resume normal operation, it calls the recovered() function, which leads to the Running state again and in the next iteration, updateHook() is called again. When warning() is called, the RunTimeWarning state is entered and updateHook() is still executed (there is no warningHook()). Use recovered() again to go back to the Running state.

Use getErrorCount() and getWarningCount() to read the number of times the error and warning states were entered. Using these functions, you can track if any intermittent problem has occured. The counters are reset when the cleanup() method is called in the Stopped state.

3.5. Error States Example

Here is a very simple use case, a TaskContext communicates over a socket with a remote device. Normally, we get a data packet every 10ms, but sometimes one may be missing. This is signaled as a run time warning, but we just continue. When we don't receive 5 packets in a row, we signal this as a run time error. From the moment packets come in again we go back to run time warning. Now if the data we get is corrupt, we go into fatal error mode, as we have no idea what the current state of the remote device is, and shouldn't be updating our state, as no one can rely on the correct functioning of the TaskContext.

Here's the pseudo code:

 class MyComponent : public TaskContext
 {
       int faults;
 public:
       MyComponent(const std::string &name) 
         : TaskContext(name), faults(0) 
       {}

 protected:
       // Read data from a buffer.
       // If ok, process data, otherwise, trigger
       // a runtime warning. When to many faults occur,
       // trigger a runtime error.
       void updateHook()
       {
            Data_t data;
            bool rv = mybuf.Pop( data );
            if ( rv ) {
               this->stateUpdate(data);
               faults = 0;
            } else {
               faults++;
               if (faults > 4)
                   this->error();
               else
                   this->warning();
            }

       }

       // Called instead of updateHook() when in runtime error state.
       void errorHook()
       {
            this->updateHook(); // just call updateHook anyway.
       }

       // Called by updateHook()
       void stateUpdate(Data_t data)
       {
            // Check for corrupt data
            if ( checkData(data) == -1 ) {
                 this->fatalError(); // we will enter the FatalError state.
            } else {
                 // data is ok: update internal state...
            }
       }
 };

Finally, you start this component with a NonPeriodicActivity, which allows you to wait in updateHook() for as long as you want. Commands and events are processed when you leave the function, that's why you can not use a while(1) {} loop within updateHook(), but re-trigger the activity again for a next run.

When you want to discard the 'warning' state of the component, call mycomp.recovered(). If your component went into FatalError, call mycomp.reset() and mycomp.start() again for processing updateHook() again.

3.6. Interfacing the TaskContext

During development of your TaskContext, it is handy to connect the TaskBrowser to your task such that you can interactively manipulate it and it's properties:

  #include <ocl/TaskBrowser.hpp>
  // ... see above
  TaskBrowser browser(a_task);

  // Start the interactive console:
  browser.loop();

In which you can start/stop the task and manipulate every aspect of it's interface, as was seen in the previous section.

3.7. Introducing the TaskContext's Interface

A TaskContext exists of a number of access or methods which expose a specific part of the interface. These methods are:

  a_task.ports();
  a_task.methods();
  a_task.attributes();
  a_task.properties();
  a_task.commands();
  a_task.events();
      

The meaning of these methods are explained in the following sections.

Figure 2.9.  TaskContext Interface

TaskContext Interface

The Execution Flow is formed by the processing of commands, methods and events (which call in turn user functions). The Data Flow is the propagation of data from one task to another using ports. Configuration is done using properties and attributes.


3.8. The Data Flow Interface

[Note]Purpose

The 'Data Flow' is a 'stream of data' between tasks. A classical control loop can be implemented using the Data Flow interface. The data is passed buffered or unbuffered from one task to another.

The Orocos Data Flow is implemented with the Port-Connector software pattern. Each task defines its data exchange ports and inter-task connectors transmit data from one port to another. A Port is defined by a name, unique within that task, the data type it wants to exchange and the buffered or un-buffered method of exchanging. Buffered exchange is done by "Buffer" Ports and un-buffered exchange is done by "Data" Ports.

A Data Port can offer read-only, write-only or read-write access to the unbuffered data. A Buffer Port can offer read-only, write-only and read-write access to the buffered data. The example below shows all these possibilities.

3.8.1. Setting up the Data Flow Interface

[Important]Important

The ready-to-execute code of this section can be found in the 'dataflow-task' package on the RTT Source code page of the Orocos.org website.

Any kind of data can be exchanged (also user defined types) but for readability, only the 'double' C type is used here.

  #include <rtt/Ports.hpp>
  using namespace RTT;

  class MyTask
    : public TaskContext
  {
    // Read-only data port:
    ReadDataPort<double> indatPort;
    // Write-only data port:
    WriteDataPort<double> outdatPort;
    // Read-Write data port:
    DataPort<double> rwdatPort;

    // Read-only buffer port:
    ReadBufferPort<double> inbufPort;
    // Write-only buffer port:
    WriteBufferPort<double> outbufPort;
    // Read-Write buffer port:
    BufferPort<double> rwbufPort;
  public:
    // ...
    MyTask(std::string name)
      : TaskContext(name),
        indatPort("Data_R"),
	outdatPort("Data_W", 1.0),  // note: initial value
	rwdatPort("Data_RW", 1.0),

	inbufPort("SetPoint_X"),
	outbufPort("Packet_1", 15), // note: buffer size
	rwbufPort("Packet_2", 30)
    {
       this->ports()->addPort( &indatPort, "Input Data Port" );
       this->ports()->addPort( &outdatPort, "Output Data Port" );
       this->ports()->addPort( &rwdatPort, "Read-Write Data Port" );

       this->ports()->addPort( &inbufPort, "Read-Only Buffer Port" );
       this->ports()->addPort( &outbufPort, "Write-Only Buffer Port" );
       this->ports()->addPort( &rwbufPort, "Read-Write Buffer Port" );

       // more additions to follow, see below
     }

     // ...
  };

The example starts with declaring all the ports of MyTask. A template parameter '<double>' specifies the type of data the task wants to exchange through that port. Logically, if two tasks are connected, they must agree on this type. The constructor of MyTask initialises each port with a name. This name is used to 'match' ports between connected tasks ( using 'connectPorts', see Section 4, “Connecting TaskContexts” ), but it is possible to connect Ports with different names as well.

Write and read-write Buffers take besides a name, the prefered buffer size as a parameter. In the example, these are the values 15 and 30. Before the task is connected to its peers, you can still change this value with the setBufferSize() function of the Port, for example in the configureHook() function of your TaskContext.

Finally, a 'write' port can take an initial value in the constructor as well. This value will be used when the connection between two ports is created in order to initialise the connection ( using 'connectPorts', see Section 4, “Connecting TaskContexts” ). If a 'write' port is connected to an existing connection, the initial value (and buffer size) are ignored and the settings of the existing connection are not touched. You can modify the initial value with the 'Set( value )' function in both Buffer and Data write ports.

[Note]Note

Alternatively, you can connect buffers in your main() program by writing:

  #include <rtt/ConnectionFactory.hpp>
  // ...
  int buf_size = 100;
  ConnectionFactory<double> cf;
  ConnectionInterface::shared_ptr con =
    cf.createBuffer(a_task->ports()->getPort("SetPoint_X"), 
                    b_task->ports()->getPort("SetPoint_X"), 
                    buf_size);
  if (con)
     con->connect();

before the tasks are connected. This connection will then take precedence.

3.8.2. Using the Data Flow Interface in C++

The Data Flow interface is used by your task from within the program scripts or its updateHook() method. Logically the script or method reads the inbound data, calculates something and writes the outbound data.

  #include <rtt/Ports.hpp>
  using namespace RTT;

  class MyTask
    : public TaskContext
  {
     // ...Constructor sets up Ports, see above.

     bool startHook() {
       // Check validity of (all) Ports:
       if ( !indatPort.connected() ) {
          // No connection was made !
	  return false;
       }
       if ( !outdatPort.connected() ) {
          // ...
       }
       return true;
     }

     void updateHook() {
       // Read and write the Data Flow:
       // Unbuffered:
       double val = indatPort.Get();
       // calculate...
       outdatPort.Set( val );

       // Buffered:
       if ( inbufPort.Pop( val ) ) {
          // calculate...
       } else {
          // buffer empty.
       }

       if ( outbufPort.Push( val ) ) {
          // ok.
       } else {
          // buffer full.
       }
     }
     // ...
  };

It is wise to check in the startHook() ( or earlier: in configureHook() ) function if all necessary ports are connected() ( or ready() ). At this point, the task start up can still be aborted by returning false. Otherwise, a write to a port will be discarded, while a read returns the initial value or the default value. A Pop of a disconnected port will always return false.

3.8.3. Using Data Flow in Scripts

When a Port is connected, it becomes available to the Orocos scripting system such that (part of) the calculation can happen in a script. Also, the TaskBrowser can then be used to inspect the contents of the DataFlow on-line.

A small program script could be loaded into MyTask with the following contents:

  program MyControlProgram {
    double the_K  = K        // read task property, see later.
    double setp_d

    while ( true ) {
      if ( SetPoint_X.Pop( setp_d ) ) { // read Buffer Port
        double in_d = Data_R.Get()      // read Data Port
        double out_d = (setp_d - in_d) * K  // Calculate
        do Data_W.Set( out_d )          // write Data Port
      }
      do nothing       // this is a 'wait' point.
    }
  } 

The program "MyControlProgram" starts with declaring two variables and reading the task's Property 'K'. Then it goes into an endless loop, trying to Pop a set point value from the "SetPoint_X" Buffer Port. If that succeeds (buffer not empty) the "Data_R" Data Port is read and a simple calculation is done. The result is written to the "Data_W" Data Port and can now be read by the other end. Alternatively, the result may be directly used by the Task in order to write it to a device or any non-task object. You can use methods (below) to send data from scripts back to the C++ implementation.

Remark that the program is executed within the Execution Engine. In order to avoid the endless loop, a 'wait' point must be present. The "do nothing" command inserts such a wait point and is part of the Scripting syntax. If you plan to use Scripting state machines, such a while(true) loop (and hence wait point) is not necessary. See the Scripting Manual for a full overview of the syntax.

3.9. The Method Interface

[Note]Purpose

A task's methods are intended to be called 'synchronously' by the caller, i.e. are directly executed like a function in the thread of the caller. Use it to 'calculate' a result or change a parameter.

The easiest way to access a TaskContext's interface is through Methods. They resemble very much normal C or C++ functions, but they have the advantage to be usable in scripting or can be called over a network connection. 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 Scripting Manual.

To add a TaskContext's method to the method interface, one proceeds similarly as when creating Data Ports. The data type is now replaced by a function signature, for example '

void(int, double)

' which is the signature of a function returning 'void' and having two arguments: an 'int' and a 'double'.

  #include <rtt/Method.hpp>
  using namespace RTT;

  class MyTask
    : public TaskContext
  {
    public:
    void reset() { ... }
    string getName() const { ... }
    double changeParameter(double f) { ... }
    // ...

    Method<void(void)> resetMethod;
    Method<string(void)> nameMethod;
    Method<double(double)> paramMethod;

    MyTask(std::string name)
      : TaskContext(name),
      resetMethod("reset", &MyTask::reset, this),
      nameMethod("name", &MyTask::getName, this),
      paramMethod("changeP", &MyTask::changeParameter, this)
    {
       // Add the method objects to the method interface:
       this->methods()->addMethod( &resetMethod, "Reset the system.");
       this->methods()->addMethod( &nameMethod, "Read out the name of the system.");
       this->methods()->addMethod( &paramMethod,
                                   "Change a parameter, return the old value.",
	                           "New Value", "The new value for the parameter."); 

       // more additions to follow, see below
     }
     // ...
  };

In the above example, we wish to add 3 class functions to the method interface: reset, getName and changeParameter. This can be done by constructing a Method object with the correct function signature for each such class function. Each Method object is initialised in the constructor with a name ("reset"), a pointer to the class function (&MyTask::reset) and a pointer to the class object (this). This setup allows the method objects resetMethod, nameMethod and paramMethod to be invoked just like one would call the functions directly.

After the method objects are constructed, we add methods to the method interface using the addMethod() function. The addMethod() function requires a a method object (&resetMethod), a description ("Reset the system.") and a name, description pair for each argument (such as in changeParameter).

Using this mechanism, any method of any class can be added to a task's method interface.

3.9.1. Invoking Methods in C++

In order to easily invoke a task's methods from a C++ program, only only needs a pointer to a TaskContext object, for example using the 'getPeer()' class function.

  // create a method:
  TaskContext* a_task_ptr;
  Method<void(void)> my_reset_meth 
       = a_task_ptr->methods()->getMethod<void(void)>("reset");

  // Call 'reset' of a_task:
  reset_meth();  

Methods can also be given arguments and collect return values. Both constant arguments and variable arguments are supported.

  // used to hold the return value:
  string name;
  Method<string(void)> name_meth = 
    a_task_ptr->methods()->getMethod<string(void)>("name");

  // Call 'name' of a_task:
  name = name_meth(); 

  cout << "Name was: " << name << endl; 

  // hold return value.
  double oldvalue;
  Method<double(double)> mychange_1 =
      a_task.methods()->create("changeP");

  // Call 'changeParameter' of a_task with argument '1.0'
  oldvalue = mychange_1( 1.0 );
  // oldvalue now contains previous value.

Up to 4 arguments can be given. If the signature was not correct, the method invocation will be ignored. One can check validity of a method object with the 'ready()' function:

  Method<double(double)> mychange_1 = ...;
  assert( mychange_1.ready() );

3.9.2. Invoking Methods in Scripts

To invoke methods from a script, one can then write :

  do ATask.changeP( 0.1 )
  // or :
  set result = ATask.changeP( 0.1 ) // store return value 

3.10. Method Argument and Return Types

The arguments can be of any class type and type qualifier (const, &, *,...). However, to be compatible with the Orocos Program Parser variables, it is best to follow the following guidelines :

Table 2.1. Method Return & Argument Types

C++ TypeIn C++ functions passed byMaps to Parser variable type
Primitive C types : double, int, bool, charvalue (no const, no reference )double, int, bool, char
C++ Container types : std::string, std::vector<double>const &string, array
Orocos Fixed Container types : RTT::Double6D, KDL::[Frame | Rotation | Twist | ... ]const &double6d, frame, rotation, twist, ...


Summarised, every non-class argument is best passed by value, and every class type is best passed by const reference. The parser does handle references (&) in the arguments or return type as well.

3.11. The Attributes and Properties Interface

[Note]Purpose

A task's attributes and properties are intended to configure and tune a task with certain values. Properties have the advantage of being writable to an XML format, hence can store 'persistent' state. For example, a control parameter. Attributes are lightweight values which can be read and written during run-time, for example, the current measured temperature.

A TaskContext may have any number of attributes or properties, of any type. They can be used by programs in the TaskContext to get (and set) configuration data. The task allows to store any C++ value type and also knows how to handle Property objects. Attributes are plain variables, while properties can be written to and updated from an XML file.

3.11.1. Adding Task Attributes or Properties

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

  #include <rtt/Property.hpp>
  #include <rtt/Attribute.hpp>

  class MyTask
    : public TaskContext
  {
     Attribute<bool> aflag;
     Attribute<int> max;

     Constant<double> pi;

     Property<std::string> param;
     Property<double> value;
  public:
    // ...
    MyTask(std::string name)
      : TaskContext(name),
        param("Param","Param Description","The String"),
	value("Value","Value Description", 1.23 ),
	aflag("aflag", false),
	max( "max", 5 ),
	pi( "pi", 3.14 )
    {
       // other code here...

       this->attributes()->addAttribute( &aflag );
       this->attributes()->addAttribute( &max );
      
       this->attributes()->addConstant( &pi );

       this->properties()->addProperty( &param );
       this->properties()->addProperty( &value );
     }
     // ...
  };

Which inserts an attribute of type bool and int, name 'aflag' and 'max' and initial value of false and 5 to the task's interface. A constant 'pi' is added as well. The methods return false if an attribute with that name already exists. Adding a Property is also straightforward. The property is added in a PropertyBag.

3.11.2. Accessing Task Attributes or Properties in C++

To get a value from the task, you can use the set() and get() methods :

  bool result = aflag.get();
  assert( result == false );

  param.set("New String");
  assert( param.get() == "New String" );

While another task can access it through the attributes() interface:

  Attribute<bool> the_flag = a_task->attributes()->getAttribute<bool>("aflag");
  assert( the_flag.ready() );

  bool result = the_flag.get();
  assert( result == false );

  Attribute<int> the_max = a_task->attributes()->getAttribute<int>("max");
  assert( the_max.ready() );
  the_max.set( 10 );
  assert( the_max.get() == 10 );

The attributes 'the_flag' and 'the_max' are called 'mirrors' of the original attributes of the task.

See also Section 7, “Properties” in the Orocos CoreLib reference.

3.11.3. Accessing Task Attributes in Scripts

A program script can access the above attributes as in

  // a program in "ATask" does :
  var double pi2 = pi * 2.
  var int    myMax = 3
  set max = myMax

  set Param = "B 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]Important

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

See also Section 6, “Attributes” in the Orocos CoreLib reference.

3.11.4. Storing and Loading Task Properties

See Section 5.1, “Task Property Configuration” for storing and loading the Properties to and from files, in order to store a TaskContext's state.

3.12. The Command Interface

[Note]Purpose

A task's commands are intended to be called 'asynchronously', thus sent by the caller to the receiver. Use it to 'reach a goal' in the receiver, typically this takes time to accomplish. Command functions are, in contrast with methods, executed by the receiver's thread.

The command interface is very similar to the Method interface above. The diffence with methods is two-fold:

  • The 'command function' of the TaskContext is executed in the thread of the receiving TaskContext. Is is an asynchronous function call.

  • There is a second function, the 'completion condition' which is called to check if the command's effect is done. Take a command to move to a position for example. The 'command function' programs the target position, while the 'completion condition' will check if the target position has been reached, long after the command function has been executed. Commands will often work together with the updateHook() function which further processes the command's data.

3.12.1. Adding Commands to a TaskContext

To add a command to the Command Interface, one must create Command, objects :

  #include <rtt/Command.hpp>

  class MyTask
    : public TaskContext
  {
  protected:
    /**
     * The first command starts a 'cycle'.
     */
    bool startCycle() { ... }
    /**
     * The completion condition checks if updateHook() has
     * finished processing this command.
     */
    bool cycleDone() const { ... }

    Command<bool(void)> cycleCommand;

    /**
     * This command programs the Task to go to a position.
     */
    bool gotoPosition(double p) { ... }
    /**
     * The completion condition checks if updateHook() has
     * finished processing this command.
     */
    bool positionReached(double p) const { return p == cur_pos; }

    Command<bool(double)> gotoCommand;

    /**
     * The commands 'program' the TaskContext, the
     * updateHook function, finishes off the command
     * over a period of time.
     */
    void updateHook() {
       if ( inCycleMode() ) {
         nextCycleStep();
       }
       if ( inGotoMode() ) {
         incrementPosition();
       }
    }  

  public:
    MyTask(std::string name)
      : TaskContext(name),
        cycleCommand("startCycle",
	             &MyTask::startCycle,
                     &MyTask::cycleDone, this),
        gotoCommand( "gotoPosition",
                     &MyTask::gotoPosition,
                     &MyTask::positionReached, this)
    {
      // ... other startup code here

      this->commands()->addCommand( &cycleCommand,
                                    "Start a new cycle.");
      this->commands()->addCommand( &gotoCommand,
                                    "Goto a position.",
	                            "pos", "A position endpoint.");
    }
  };

Clearly, 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's effect 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.

The Command object requires two function pointers instead of one, which must both return a 'bool'. The first one is the command function that does the actual work, and the completion condition is a function having :

  • exactly the same arguments as the command,

  • OR only the first argument of the command,

  • OR no arguments at all.

Analogous to addMethod(), addCommand adds the Command objects to the TaskContext interface and also requires a string describing the command, and two strings giving a name and description for every argument.

3.12.2. Invoking Commands in C++

Once a command is added to a TaskContext's interface, other tasks can make use of that command.

The Command class can be used to invoke commands as well. You can get such object from a task's interface:

  Command<bool(void)> mycom 
     = a_task.commands()->getCommand<bool(void)>("startCycle");
  // check if the command is ok:
  assert( mycom.ready() );

  // Send 'startCycle' to a_task (asynchronous).
  bool result = mycom();
  // next, check its status:
  bool accepted = mycom.accepted(); // accepted by execution engine?
  bool valid = mycom.valid();       // command was valid (well-formed)?
  bool done = mycom.done();         // command was done?
      

Such commands can also be given arguments. Both constant arguments and variable arguments are supported:

  // get a command:
  Command<bool(double)> myGoto_1 =
      a_task.commands()->getCommand<bool(double)>("gotoPosition");

  bool d_arg = 5.0;
  // Send 'gotoPosition' to a_task, reads contents of d_arg.
  bool result_2 = myGoto_1(d_arg);

The current implementation supports up to 4 arguments. Since the use of 'structs' is allowed, this is enough for most applications.

3.12.3. Invoking Commands from Scripts

The above lets you write in a program script :

  do startCycle()
  do gotoPosition( -3.0 )

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 script to enter an error state, i.e. it stops its execution.

[Important]Important

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

See also Section 3, “Commands” in the Orocos CoreLib reference.

3.13. The Event Interface

[Note]Purpose

A task's events are intended to be 'emitted', thus published by the task to subscribers. Use it to 'notify' interested parties of a change in the system. It allows you to have one or more functions called when an event happens.

A task may register its events in its interface in order to be used by its state machines and other tasks as well. Events are defined and explained in the Orocos CoreLib reference, Section 4, “Events”.

3.13.1. Adding Events

Events can be easily added to a task's interface, much like methods are:

  #include <rtt/Event.hpp>

  class MyTask
    : public TaskContext
  {
    // An event with a bool argument:
    Event< void(bool) > turnSwitch;
    // An event with three arguments:
    Event< bool(double, double, double) > moveAxis;
  public:
    MyTask(std::string name)
      : TaskContext(name),
        turnSwitch( "turnSwitch" ),
	moveAxis( "move" )
    {
      // ... other startup code here
  
      // add it to the task's interface:
      this->events()->addEvent( &turnSwitch, 
                                "Turn switch description", 
                                "d","Direction" ); 
      this->events()->addEvent( &moveAxis,
                                "Move the axis."
                                "x","X axis position",
				"y","Y axis position",
				"z","Z axis direction");
    }
  };

An Event object has the signature ('void(bool)') of the 'callback function' it will call when the event is 'emitted' (or 'fired'). The object is initialised with a name ("turnSwitch") and added to the interface ('addEvent').

3.13.2. Emitting Events in C++

Once events are added, they can be emitted using the Event object.

  Event< bool(double, double, double) > move_event 
    = a_task.events()->getEvent( "move" );
  assert( move_event.ready() );

  // emit the event 'move' with given args:
  move_event(1.0, 2.0, 3.0);

  // or with variable arguments:
  double a = 0.3, b = 0.2, c = 0.1;
  move_event(a, b, c); 

3.13.3. Reacting to Events in C++

Analogous to emitting an event, one can also react to an event in C++, using the Event interface. Event connections can be accessed through the Handle object. The first example shows how to setup a synchronous connection to the event of the previous examples:

   #include <boost/bind.hpp> 
  /** 
   * Example: Connect a class method to an Event.  
   */ 
  class Reactor 
  { 
  public: 
     bool react_callback(double a1, double a2, double a3) { 
        // use a1,a2, a3 
	return false; // return value is ignored.
     } 
  };

  /**
   * Example: Connect a 'C' function to an Event.
   */
  bool foo_callback( double a1, double a2, double a3 ) {
      // use a1, a2, a3
      return false; // ignored.
  }

  // Class callback:
  Reactor r;

  Handle h
    = a_task.events()->setupConnection("move")
                 .callback( &r, &Reactor::react_callback )
                 .handle();
  assert( h.ready() );
    
  h.connect(); // connect to event "move"

  move_event(1.0, 2.0, 3.0); // see previous example.

  // now Reactor::callback() was called.

  h.disconnect(); // disconnect again.

  // 'C' Function callback:
  h = a_task.events()->setupConnection("move")
                 .callback( &foo_callback )
                 .handle();

  h.connect();
  move_event(4.0, 5.0, 6.0)

  // now foo_callback is called with arguments.	
[Note]Note

Using the boost::bind function is not yet supported in this interface. You must provide the object and function separately in callback().

Analogous to the event example in the CoreLib reference (Section 4, “Events”), a class funcion is made to react to the event. A connection is setup between the "move" event and the react_callback function of "r". The connection can be controlled using the handle to connect or disconnect the reaction to events. When connect() is called, every event invocation will call react_callback() with the given arguments. Using a 'C' function works analogous as shown above.

A second example continues, but uses an asynchronous connection. First a new task (b_task) is created which will handle the event asynchronously. During setup, the EventProcessor of b_task's Execution Engine is used to process the event.

  TaskContext b_task("BTask");
  PeriodicActivity ptask_b(5, 0.1); // priority, period
  ptask_b.run( &b_task );
  ptask_b.start();

  Handle h3 
    = a_task.events()->setupConnection("move")
                            .callback(&r, &react_callback,
                                      b_task.engine()->events() ).handle();
    
  assert( h3.ready() );

  h3.connect(); // connect asynchronously to event "move"

  move_event.emit(); // see previous example.

  // wait a bit...

  // now react_callback() was called from within b_task's execution engine.
	

Note that after passing the object and function, the EventProcessor of b_task is added in the callback method, such that the callback is executed in b_task's thread.

3.13.4. Using Events from Scripts

Events are as easy to use as methods (above) from within scripts, using the keyword do:

  do ATask.move( 1.0, 2.0, 3.0 )

It is also possible to react to events from within a state machine in order to change state. We refer to the Program Parser Manual for syntax and