Name connections, not ports (aka Orocos' best kept secret)

Rationale

Problem: How to reuse a component when you need the ports to have different names?

Solution: Name the connection between ports in the deployer. This essentially allows you to rename ports. Unfortunately, this extremely useful feature is not documented anywhere (as of July, 2009). <!-- break -->

Assumptions

  • The build directory is within the source directory. Click below to read the rest of this post.== Rationale ==

Problem: How to reuse a component when you need the ports to have different names?

Solution: Name the connection between ports in the deployer. This essentially allows you to rename ports. Unfortunately, this extremely useful feature is not documented anywhere (as of July, 2009). <!-- break -->

Assumptions

  • The build directory is within the source directory. This helps with dynamic library loading.
  • Admittedly, this is contrived example but the structure is very useful and occurs more frequently than you may realise (say using N copies of a camera component, deploying components for both a left and a right robot arm within the same deployer, etc).

Files

HMI.hpp

Robot.hpp

OneAxisFilter.hpp

HMI.cpp

Robot.cpp

OneAxisFilter.cpp

Connect-1.xml

Connect-2.xml

Connect-3.xml

Buildable tarball

Example overview

This example occurs in three parts

  1. A Human-Machine-Interface (HMI) component connects to a Robot component, and provides a desired cartesian position.
  2. A one-axis filter is placed between the HMI and Robot component, to zero out one axis (say, you did not want the robot to move in one direction due to an obstacle or something similar)
  3. A second one-axis filter is placed between the first filter and the Robot. The two filters are the exact same component with the same named ports.

Components

class HMI : public RTT::TaskContext
{
protected:
    // *** OUTPUTS ***
 
    /// desired cartesian position
    RTT::WriteDataPort<KDL::Frame>            cartesianPosition_desi_port;
 
public:
    HMI(std::string name);
    virtual ~HMI();
 
protected:
    /// set the desired cartesian position to an initial value
    /// \return true
    virtual bool startHook();
};
The HMI provides one output port that specified the desired cartesian position. This is set to an initial value in startHook().

class Robot : public RTT::TaskContext
{
protected:
    // *** INPUTS ***
 
    /// desired cartesian position
    RTT::ReadDataPort<KDL::Frame>            cartesianPosition_desi_port;
 
public:
    Robot(std::string name);
    virtual ~Robot();
};
The robot accepts a desired cartesian position as input (but in this example does nothing with it).

class OneAxisFilter : public RTT::TaskContext
{
protected:
    // *** INPUTS ***
 
    /// desired cartesian position
    RTT::ReadDataPort<KDL::Frame>            inputPosition_port;
 
    // *** OUTPUTS ***
 
    /// desired cartesian position
    RTT::WriteDataPort<KDL::Frame>            outputPosition_port;
 
    // *** CONFIGURATION ***
 
    /// specify which axis to filter (should be one of "x", "y", or "z")
    RTT::Property<std::string>                axis_prop;
 
public:
    OneAxisFilter(std::string name);
    virtual ~OneAxisFilter();
 
protected:
    /// validate axis_prop value
    /// \return true if axis_prop value is valid, otherwise false
    virtual bool configureHook();
    /// filter one translational axis (as specified by axis_prop)
    virtual void updateHook();
};
The OneAxisFilter component takes an input cartesian position, zeroes out one axis (the axis of interest is specified in a property), and then outputs the filtered cartesian position.

Component implementation

The component implementations are not given in this example, as they are not the interesting part of the solution, but are available in the Files section above.

The interesting part is in the deployment files ...

Deployment

Part 1: HMI and Robot

This part simply connects the HMI and robot together (see deployment file Connect-1.xml).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "cpf.dtd">
<properties>
 
  <simple name="Import" type="string">
    <value>liborocos-rtt</value>
  </simple>
  <simple name="Import" type="string">
    <value>liborocos-kdl</value>
  </simple>
  <simple name="Import" type="string">
    <value>liborocos-kdltk</value>
  </simple>
  <simple name="Import" type="string">
    <value>libConnectionNaming</value>
  </simple>
The first section of the deployment file simply loads the Orocos libaries we use (including the KDL toolkit, so that we can inspect and modify KDL types within the deployer), and then loads our shared libary (libConnectionNaming).

  <struct name="HMI" type="HMI">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
The next section creates an HMI component, with the connection for its output port named "cartesianPosition_desi" (ie the same as the port name). The syntax for port/connection naming is:

      <simple name="portName" type="string">
        <value>connectionName</value>
      </simple>
which makes port portName part of connection connectionName.

  <struct name="Robot" type="Robot">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>HMI</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
</properties>
Lastly, the robot component is created with its input port on a connection named cartesianPosition_desi.

Now, the deployer uses connection names when connecting components between peers, not port names. So it attempts to connect a Robot.cartesianPosition_desi connection to a Vehicle. cartesianPosition_desi connection (which in this part, matches the port names).

Build the library, and then run this part with

cd /path/to/ConnectionNaming/build
deployer-macosx -s ../Connect-1.xml

Examine the HMI and Robot components, and note that each has a connected port, and the port values match.

Part 2: HMI, one filter and a robot

This part adds a filter component between the HMI and the robot (see Connect-2.xml)

As with Part 1, the first part of the file loads the appropriate libraries (left out here, as it is identical to Part 1).

  <struct name="HMI" type="HMI">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>unfiltered_cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
Again an HMI component is deployed, except this time the deployer will connect the cartesianPosition_desi port as part of a connection named unfiltered_cartesianPosition_desi.

  <struct name="Filter" type="OneAxisFilter">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>HMI</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="inputPosition" type="string">
        <value>unfiltered_cartesianPosition_desi</value></simple>
      <simple name="outputPosition" type="string">
        <value>filtered_cartesianPosition_desi</value></simple>
    </struct> 
    <simple name="PropertyFile" type="string">
      <value>../Filter1.cpf</value></simple>
  </struct>
The Filter component is deployed with its input port being part of a connection named unfiltered_cartesianPosition_desi, while its output port is part of a connection named filtered_cartesianPosition_desi. Comparison with the HMI port/connections above, and the Robot port/connections below, you can see that the Filter's input port is connected to the HMI and the output port is connected to the Robot.

  <struct name="Robot" type="Robot">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>Filter</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>filtered_cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
The robot component is the same as Part 1, except that its input port is part of a connection named filtered_cartesianPosition_desi (ie connected to the Filter).

Run this part with

cd /path/to/ConnectionNaming/build
deployer-macosx -s ../Connect-2.xml

Examine all three components, and note that all ports are connected, and in particular, that the HMI and Filter.inputPosition ports match while the Filter.outputPosition and Vehicle ports match (ie they have the 'x' axis filtered out).

Using connection naming allows us to connect ports of different names. This is particularly useful with a generic component like this filter, as in one deployment it may connect to a component with ports named cartesianPosition_desi, while in another deployment it may connect to ports named CartDesiPos, or any other names. The filter component is now decoupled from the actual port names used to deploy it.

Part 3: HMI, two filters and a robot

This part adds a second filter between the first filter and the robot.

As with Parts 1 and 2, the libraries are loaded first.

  <struct name="HMI" type="HMI">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>unfiltered_cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
There is no change in the HMI from Part 2.

  <struct name="Filter1" type="OneAxisFilter">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>HMI</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="inputPosition" type="string">
        <value>unfiltered_cartesianPosition_desi</value></simple>
      <simple name="outputPosition" type="string">
        <value>filtered_cartesianPosition_desi</value></simple>
    </struct> 
    <simple name="PropertyFile" type="string">
      <value>../Filter1.cpf</value></simple>
  </struct>
There is no change in the first filter from Part 2.

  <struct name="Filter2" type="OneAxisFilter">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>HMI</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="inputPosition" type="string">
        <value>filtered_cartesianPosition_desi</value></simple>
      <simple name="outputPosition" type="string">
        <value>double_filtered_cartesianPosition_desi</value></simple>
    </struct> 
    <simple name="PropertyFile" type="string">
      <value>../Filter2.cpf</value></simple>
  </struct>
The second filter has its input port part of a connection named filtered_cartesianPosition_desi (ie it is connected to Filter1's output port), and the second filter's output port is part of a connecton named double_filtered_cartesianPosition_desi (which as you will see, is connected to the robot's input port).

  <struct name="Robot" type="Robot">
    <struct name="Activity" type="PeriodicActivity">
      <simple name="Period" type="double"><value>0.5</value></simple>
      <simple name="Priority" type="short"><value>0</value></simple>
      <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple>
    </struct>
    <simple name="AutoConf" type="boolean"><value>1</value></simple>
    <simple name="AutoStart" type="boolean"><value>1</value></simple>
    <struct name="Peers" type="PropertyBag">
      <simple type="string"><value>Filter2</value></simple>
    </struct> 
    <struct name="Ports" type="PropertyBag">
      <simple name="cartesianPosition_desi" type="string">
        <value>double_filtered_cartesianPosition_desi</value></simple>
    </struct> 
  </struct>
The only change in the robot component, from Part 2, is to change its peer to Filter2 and to use a connection named double_filtered_cartesianPosition_desi (ie connect it to Filter2).

Run this part with

cd /path/to/ConnectionNaming/build
deployer-macosx -s ../Connect-3.xml

Examine all components, and note which ports are connected, and what their values are. Note that the vehicle has two axes knocked out (x and y).

Points to note

  1. WARNING The deployer displays port names for ports within components, while the OCL reporting component also uses port names. Only the act of connecting ports between peers when deploying a component network, makes use of the connection naming shown above.
  2. Using connection naming allows us to reuse a component without resorting to renaming its ports or modifying its code in any way. This is an example of deployment-time configuration. Note that there are certainly instances where run-time configuration of port-names may be needed (eg the component has to name its ports based on the component name itself), but in our experience, deployment-time configuration is more frequent and decouples components better.
  3. Note that as many filters as are required could be chained together in this manner, and that none of the input, output, nor filter components need know that they are connected in such a fashion. Decoupling is your friend, and allowed the Filter component writer to simply concentrate on writing a component that did one thing well: filtered a cartesian position (yes, a trivial example, but a valid point nonetheless).
  4. You may notice that the deployment files do not specify peer combinations in pairs. The peers are mentioned in one direction only. We use this to decouple (yet again) a component from knowing what peers it is connected to, where possible. For example, Filter1 in both Parts 2 and 3 does not now what component is down-stream from it. It doesn't know, nor does it care, whether it is being filtered again, connected to a robot, or whatever. Again, decoupling. This can dramatically help when deploying large systems.

To build

In a shell

cd /path/to/ConnectionNaming
mkdir build
cd build
cmake .. -DOROCOS_TARGET=macosx
make

For other operating systems substitute the appopriate value for "macosx" when setting OROCOS_TARGET (e.g. "gnulinux").

Tested in Mac OS X Leopard 10.5.7.

AttachmentSize
HMI.hpp2.16 KB
Robot.hpp1.99 KB
OneAxisFilter.hpp2.52 KB
HMI.cpp2.04 KB
Robot.cpp1.94 KB
OneAxisFilter.cpp2.92 KB
Connect-1.xml1.96 KB
Connect-2.xml2.91 KB
Connect-3.xml3.85 KB
connectionNaming.tar_.bz27.01 KB