INTRODUCTION
Overview
Download and Install
Quick Start
Documentation
Publications

NONFRAMEWORK CODE
Driver Interfaces
Drivers
Libraries
Utilities

FRAMEWORK CODE
Interfaces
Components
Libraries
Utilities

Full Software Listings

DEVELOPER
Tutorials
Examples
Dev Guide
Dashboard

PEOPLE
Contributors
Users

SourceForge.net Logo
Project
Download
Mailing lists

 

         

Writing Your First Orca Component

Author:
Waleed Kadous

Intro

This document will step you through the process of making your first Orca component. In particular, we will build an Orca component called "drunkrobot" that simulates a robot that takes a random walk. We'll use the existing Orca component "position2dmon" to watch the robot as it moves around.

Getting started

Before reading this document, you should familiarise yourself with:

Once you've read that, things here should make a bit more sense.

Basics

The Suggestions on the Internal Component Structure guide contains the following diagram that will become the basis of our implementation.

component_design.png

Our design will be based on this sketch; beginning with the implementation of the interface, then the main loop, then finallly the component. We'll then actually get our component running, talking to the Orca services and also to other components.

Interface

Our drunken robot must implement a particular interface. Let us assume that our robot bumbles around on a flat surface. The appropriate interface in this is case orca::Position2d. The orca::Position2d data file is declared in: src/interfaces/slice/orca/position2d.ice

From that declaration, which is written in Slice, a language for describing object-oriented interfaces, we can see that our drunken robot must implement the following methods:

interface Position2d
{
    nonmutating Position2dData getData();
    nonmutating Position2dGeometry getGeometry();
};

The "nonmutating" bit means that the Ice layer can make some optimisations because getting data or geometry doesn't change our drunken robot at all.

Note also that our robot must know about the orca::Position2dData and orca::Position2dGeometry objects. These are also described in the Ice file. Orca uses BROS (Basic Robotic Standard) for representations of geometry and the like. These are also defined in the above file; for instance, orca::Position2dData definition looks like this:

class Position2dData extends OrcaObject
{
    // Robot pose in global CS according to odometry.
    Frame2d pose;
    // Translational and angular velocities in the robot CS.
    Twist2d motion;
    // Are the motors stalled
    bool stalled;
};

Setting up

For the moment, we'll be setting up in a subdirectory of the src/examples, just to make building easier. But this doesn't have to be this way. So first we go to src/examples, and make a new directory called mydrunkrobot (a copy of all of the source code for this walkthrough can be found in drunkrobot).

% mkdir mydrunkrobot

Creating the interface classes

As discussed in the design pattern, our implementation of the interface simply puts data into a buffer and our program then accesses it in its main loop. Since we're implementing a orca::Position2d object, we need to create its simple implementation. We'll put this in a class called Position2dI. The header file position2dI.h looks like this:

After we include the header files, we then declare the constructor. The first is a pointer to the 2d data, but set to use a template of a Position2dData object, and the second, since it's not going to change is the static 2d geometry.

The getData() and getGeometry() functions are also implemented. You can ignore the Ice::Current parameter or read about it in the Ice documentation. There are also members for the buffered results.

No we implement the above interface. There's nothing unexpected here; to get the latest position, we check the pointer buffer, to return the configuration, we pass the value that was initialised when the interface was created. The file position2dI.cpp looks like this:

Creating the main loop.

Now we've implemented the interface, we have to write the main loop that actually does the processing. Main loops inherit from the orcaice::Thread class and only need to implement one method: run(). When run finishes, the thread will terminate.

The other thing which is unusual about the main loop is that is also responsible for "push" data. In Orca 2, data is pushed through IceStorm, Ice's publish-subscribe system. This is done by using a proxy for a consumer of orca::Position2dData. We use the proxy (hence the Prx at the end) to represent remote IceStorm objects.

Hence the file drunkrobotmainloop.h looks like this:

The implementation in this example is trivial. The constructor sets things up. Then our main loop does the following:

  1. Make a new orca::Position2dData object.
  2. Use a library method to assign it a sane value, which includes assigning random position values.
  3. Send it to IceStorm
  4. Push it into the buffer so that the most recent data is returned when someone calls getData().
  5. Sleep for a second.

The file drunkrobotmainloop.cpp looks like this:

Putting the component together

The component glues the interface class and the main loop together. Mostly, it is boiler-plate code; code that connects one object to the other. The other important characteristic of the the component is that it inherits from the orcaice::Component class. This will allow us later to choose how we wish to run the drunkrobot: as a stand-alone process or within an application servr (called IceBox in Ice) - an environment where lots of different components can live and share resources more effectively than if they were separate processes. The header for the component is as follows:

The implementation first appears to be intimidating, but if you do find it intimidating, it might be worth reading through some examples of how Ice sets up connections in the Ice manual.

Our start() method basically does the following:

  1. Set up the geometry stuff so that it gets returned correctly
  2. Using a nice method in orcaice, sets up IceStorm output so that we can use the proxy to send out info to the proxy.
  3. Sets up our implementation of the Position2d based on naming information in the configuration file.
  4. Activates the adapter for this component so that communication can begin.
  5. Starts the main loop of our component.

Setting up the build system.

To build the software using CMake, we need to write a CMakeLists.txt file. As it turns out, we can use a generic one, for example, the one that can be found in [ORCA-SRC]/src/components/position2dmon. Make a copy of the one in position2dmon and make some minor modifications as shown:

The next thing we have to do is go one level up to src/componenents and modify the CMakeLists.txt file to add the new subdirectory.

Finally, we run cmake in the top src directory to recreate all Makefele's.

% cd [ORCA-SRC]
% cmake .
% make

We should now have a executable file called drunkrobot.

Configuration

We are almost there. Now we set up the config files for our robot.

We need to specify the name of the platform, the component name and finally that we plan to use the tcp protocol. Since we implement the orca::Position2d interface, we specify the platform and component, and then the actual interface we implement. These all go in drunkrobot.cfg:

And finally we are ready! First, however, we have to launch the support services, namely IceStorm and IcePack. In order to do this, follow the instructions in Quick-Start Guide.

Then all we have to type is:

% ./drunkrobot

And we now have a robot that is generating random positions every second or so.

Connecting to another component.

Let's now try to connect another component. Orca distribution includes position2dmon, a command line tool for displaying output from Position2d interfaces, just like the one our drunk robot provides. To get these two things to talk to one another shouldn't be hard at all, in fact all it requires is to define a new position2dmon config file.

If we go into the position2dmon directory, we can create a file called position2dmon-drunkrobot.cfg.

The contents of the file might be something like:

# component
Position2dMon.PlatformName=local
Position2dMon.ComponentName=position2dmon
Position2dMon.Endpoints=default

# requires
Position2dMon.Requires.Position2d.Proxy=position2d@local/drunkrobot

Assuming that the drunkrobot process above is still running, we can watch the position information using:

% ./position2dmon --Ice.Config=position2dmon-drunkrobot.cfg

We can hit "g" to get the data once, or hit "s" to get a constant stream of data.

Woohoo! We now have two components talking to one another.

Further things

There is still a little that you should learn about when using Orca. These include

  • Putting components into IceBoxes instead of standalone applications.
  • How to receive "pushed" messages using the orcaice::PtrNotify class.
  • Documenting your code using the Doxygen system.
  • Implementing different interfaces in a single component.
  • Implementing synchronous processing calls.

But ... that's the subject of another walkthrough.

 

Webmaster: Tobias Kaupp (tobasco at users.sourceforge.net)


Generated for Orca Robotics by  doxygen 1.4.5