Toolchain Tutorials

RTT 2.0 Tutorial

A walkthrough of how Orocos components can be built using the RTT. Your main companion is the Orocos TaskBrowser which allows you to interactively setup and interact with an Orocos application.

These exercises are hosted on Github .

You need to have the Component Builder's Manual (see Toolchain Reference Manuals) at hand to complete these exercises.

Connecting and Running Components

Setting up applications using the deployer application

Take also at the Toolchain Reference Manuals for in-depth explanations of the deployment XML format and the different transports (CORBA, MQueue)

Using the Logging (log4cpp) infrastructure

Links to Orocos components

Configuring and Starting Components from an Orocos Script

Purpose

To start an Orocos application without writing a single XML file. Note: this syntax is only possible from RTT 2.2.0 on. pre-2.2.0 versions only support scripts in 'program' blocks.

You'll need to have the Scripting Chapter of the Component Builder's Manual at hand for clarifications on syntax and execution semantics.

How it works

We write one or more scripts that locate the components on the filesystem, create them in the application and connect and configure them. We use the DeploymentComponent's scripting API to do all this, instead of using a DeploymentComponent XML file.

How it's done

Create a new file 'startup.ops'. 'ops' stands for Orocos Program Script. And write this code:

path("/opt/orocos/lib/orocos") // Path to where components are located   [1]
import("myproject")            // imports a specific project in the path [2]
import("ocl")                  // imports ocl from the path
require("print")               // loads the 'print' service globally.    [3]
 
loadComponent("HMI1","OCL::HMIComponent") // create a new HMI component [4]
loadComponent("Controller1","MyProjectController") // create a new controller
loadComponent("Test1","TaskContext")      // creates an empty test component

You can test this code by doing:

deployer-gnulinux -s startup.ops
OR, at the taskbrowser prompt:
deployer-gnulinux
...
Deployer [S]> help runScript 
 
 runScript( string const& File ) : bool
   Runs a script.
   File : An Orocos program script.
Deployer[S]> runScript("startup.ops")

The first line of startup.ops ([1]) extends the standard search path for components. Every component library directly in a path will be discovered using this statement, but the paths are not recursively searched. For loading components in subdirectories of a path directory, use the import statement. In our example, it will look for the myproject directory in the component paths and the ocl directory. All libraries and plugins in these directories will be loaded as well.

After importing, we can create components using loadComponent ([4]). The first argument is the name of the component instance, the second argument is the class type of the component. When these lines are executed, 3 new components have been created: HMI1, Controller1 and Test1.

Finally, the line require("print") loads the printing service globally such that your script can use the 'print.ln("text")' function. See help print in the TaskBrowser after you typed require("print").

Now extend the script to include the lines below. The create connection policy objects and connect ports between components.

// See the Doxygen API documentation of RTT for the fields of this struct:
var ConnPolicy cp_1
// set the fields of cp_1 to an application-specific value:
cp_1.type = BUFFER  // Use ''BUFFER'' or ''DATA''
cp_1.size = 10      // size of the buffer
cp_1.lock_policy = LOCKED // Use  ''LOCKED'', ''LOCK_FREE'' or ''UNSYNC''
// other fields exist too...
 
// Start connecting ports:
connect("HMI1.positions","Controller1.positions", cp_1)
cp_1 = ConnPolicy() // reset to defaults (DATA, LOCK_FREE)
connect("HMI1.commands","Controller1.commands", cp_1)
// etc...

Connecting data ports is done using ConnPolicy structs that describe the properties of the connection to be formed. You may re-use the ConnPolicy variable, or create new ones for each connection you form. The Component Builder's Manual has more details on how the ConnPolicy struct influences how connections are configured.

Finally, we configure and start our components:

if ( HMI1.configure() == false )
   print.ln("HMI1 configuration failed!")
else {
   if ( Controller1.configure() == false )
      print.ln("Controller1 configuration failed!")
   else {
      HMI1.start()
      Controller1.start()
   }
}

Advanced configuration using a State Machine

For complexer scenarios, we can put our logic in a state machine that starts/stops/configures components as we go:
StateMachine SetupShutdown {
    var bool do_cleanup = false, could_config = false;
    initial state setup {
           entry {
                // Configure components
                could_config = HMI1.configure() && Controller1.configure();
                if (could_config) {
                    HMI1.start();
                    Controller1.start();
                }
           }
           transitions { 
                if do_cleanup then select shutdown;
                if could_config == false then select failure;
           }
    }
 
    state failure {
           entry {
                print.ln("Failed to configure a component!")
           }
    }
 
    final state shutdown {
           entry {
                // Cleanup B group
                HMI1.stop() ; Controller1.stop();
                HMI1.cleanup() ; Controller1.cleanup();
           }
    }
}
RootMachine SetupShutdown deployApp;
 
deployApp.activate()
deployApp.start()

State machines are explained in detail in the Scripting Chapter of the Component Builder's Manual.

Connecting ports of components distributed with CORBA

Purpose

Connecting an output port of one component with an input port of another component, where both components are distributed using the CORBA deployer application, deployer-corba.

How it works

Connecting data flow ports of components is done by defining connections (see Naming connections ). When components are distributed using the CORBA deployment component, you need to declare a proxy component in one of the deployers and connect to a port of that proxy. You only need to setup a specific connection in one XML file, the other XML files don't need to repeat the same information.

How it's done

This is your first XML file for component A. We tell that it runs as a Server and that it registers its name in the Naming Service. (See also Using CORBA and the CORBA transport reference manual for setting up naming services)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
  <struct name="ComponentA" type="HMI">
    <simple name="Server" type="boolean"><value>1</value></simple>
    <simple name="UseNamingService" type="boolean"><value>1</value></simple>
  </struct>
</properties>

Save this in component-a.xml and start it with: deployer-corba -s component-a.xml

This is your second XML file for component B. It has one port, cartesianPosition_desi. We add it to a connection, named cartesianPosition_desi_conn. Next, we declare a 'proxy' to Component A we created above, and we do the same for it's port, add it to the connection named cartesianPosition_desi_conn.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
  <struct name="ComponentB" type="Controller">
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>cartesianPosition_desi_conn</value></simple>
    </struct> 
  </struct>
 
  <!-- ComponentA is looked up using the 'CORBA' naming service -->
  <struct name="ComponentA" type="CORBA">
      <!-- We add ports of A to the connection -->
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition" type="string">
        <value>cartesianPosition_desi_conn</value></simple>
    </struct> 
  </struct>
</properties>

Save this file as component-b.xml and start it with deployer-corba -s component-b.xml

When component-b.xml is started, the port connections will be created. When ComponentA exits and re-starts, ComponentB will not notice this, and you'll need to restart the component-b xml file as well. Use a streaming based protocol (ROS, POSIX MQueue) in case you want to be more robust against such situations.

Alternative way to do the same

You can also form the connections in a third xml file, and make both components servers like this:

Starting ComponentA:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
  <struct name="ComponentA" type="HMI">
    <simple name="Server" type="boolean"><value>1</value></simple>
    <simple name="UseNamingService" type="boolean"><value>1</value></simple>
  </struct>
</properties>

Save this in component-a.xml and start it with: cdeployer -s component-a.xml

Starting ComponentB:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
  <struct name="ComponentB" type="Controller">
    <simple name="Server" type="boolean"><value>1</value></simple>
    <simple name="UseNamingService" type="boolean"><value>1</value></simple>
  </struct>
</properties>

Save this in component-b.xml and start it with: cdeployer -s component-b.xml

Creating two proxies, and the connection:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
  <!-- ComponentA is looked up using the 'CORBA' naming service -->
  <struct name="ComponentA" type="CORBA">
      <!-- We add ports of A to the connection -->
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition" type="string">
        <value>cartesianPosition_desi_conn</value></simple>
    </struct> 
  </struct>
 
  <!-- ComponentB is looked up using the 'CORBA' naming service -->
  <struct name="ComponentB" type="CORBA">
      <!-- We add ports of B to the connection -->
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>cartesianPosition_desi_conn</value></simple>
    </struct> 
  </struct>
</properties>

Save this in connect-components.xml and start it with: deployer-corba -s connect-components.xml

Further Reading

See deployer and CORBA related Toolchain Reference Manuals.

Setting up the RTT 2.4 exercises on Ubuntu

RTT Exercises Installation with ROS

These instructions are meant for the Orocos Toolchain version 2.4.0 or later.

Installation

  • Install Electric ROS using Debian packages for Ubuntu Lucid (10.04) or later. In case you don't run Ubuntu you can use the ROS install scripts. See the ros installation instructions.
    • Make sure the following debian packages are installed: ros-electric-rtt-ros-integration ros-electric-rtt-ros-comm ros-electric-rtt-geometry ros-electric-rtt-common-msgs ros-electric-pr2-controllers ros-electric-pr2-simulator ros-electric-joystick-drivers ruby
  • Create a directory in which you want to install all the workshops source (for instance training)

mkdir ~/training

  • Add this directory to your $ROS_PACKAGE_PATH

export ROS_PACKAGE_PATH=~/training:$ROS_PACKAGE_PATH

  • Get rosinstall

sudo apt-get install python-setuptools
sudo easy_install -U rosinstall

  • Get the rosinstall file . Save it as orocos_exercises.rosinstall in the training folder.
  • Run rosinstall

rosinstall ~/training orocos_exercises.rosinstall /opt/ros/electric

  • As the rosinstall tells you source the setup script

source ~/training/setup.bash

  • Install all dependencies (ignore warnings)

rosdep install youbot_common
rosdep install rFSM

  • Compile the dependencies

rosmake youbot_common rtt_dot_service rttlua_completion

Setup

  • Add the following functions in your $HOME/.bashrc file:

useOrocos(){
    source $HOME/training/setup.bash;
    source $HOME/training/setup.sh;
    source /opt/ros/electric/stacks/orocos_toolchain/env.sh;
    setLUA;
}
 
setLUA(){
    if [ "x$LUA_PATH" == "x" ]; then LUA_PATH=";;"; fi
    if [ "x$LUA_CPATH" == "x" ]; then LUA_CPATH=";;"; fi
    export LUA_PATH="$LUA_PATH;`rospack find rFSM`/?.lua"
    export LUA_PATH="$LUA_PATH;`rospack find ocl`/lua/modules/?.lua"
    export LUA_PATH="$LUA_PATH;`rospack find rttlua_completion`/?.lua"
    export LUA_CPATH="$LUA_CPATH;`rospack find rttlua_completion`/?.so"
    export PATH="$PATH:`rosstack find orocos_toolchain`/install/bin"
}
 
useOrocos

Testing

You can test your setup after all the above steps by opening a new terminal and doing:
  roscd hello-1-task-execution
  make
  rosrun ocl deployer-gnulinux -s start.ops

Using the Taskbrowser

Creating a variable

The created variables are listed as attributes to the component you created them in.
  • simple types (int, double...)

var double a
a=1.1
  • arrays, eg. of size 2

var float64[] b(2)
b[0]=4.4