Rock

the Robot Construction Kit

oroGen types and Ruby scripts

When you develop oroGen components, you declare C++ types. However, since the system deployment in Rock is done in Ruby, we have to understand how the oroGen C++ types are accessed on the Ruby side.

This page will explain:

  • the mapping between C++ and Ruby in Rock
  • how to customize that mapping to get more functionality out of your Ruby scripts

General API

To create a new value, one has to first get the type definition. Type definitions available on the oroGen components are exported in the Types namespace:

time = Types::Base::Time.new

Note that this mechanism takes into account custom Ruby-to-Typelib conversions (see below). For instance, by default, /base/Time gets converted to and from Ruby Time. Therefore, the class Types::Base::Time is the same as Time.

In most cases, accessing the types directly is not necessary:

  • when modifying a complex type in a property, one should use the block form modify it and write it back
  • when writing to an input port, the InputPort#new_sample method creates a new value object suitable for this particular port

One big difference between C++ and Ruby is that, to copy a value on another, one CANNOT do

value1 = value2 # THIS DOES NOT WORK

due to how variables behave in Ruby. Copying is done with

Typelib.copy(target, source)

Mapping between C++ and Ruby

In general, when an orocos.rb method returns a value of a certain C++ type, that value will behave “as it should”, i.e.:

Structures

They are represented as structures. For instance values of the type

namespace base {
    class Time
    {
        int seconds;
        int microseconds
    };
}

can be accessed naturally with

puts time.seconds + time.microseconds
time.seconds *= 2
# and so on, and so forth

A structure can be initialized from a hash:

time_t.new(:seconds => 10, :microseconds => 20)

Arrays

They behave like Ruby arrays:

array_of_time[4].seconds = 10
array_of_time.find { |v| v.seconds > 10 }

Arrays can be initialized from Ruby arrays:

array_of_time_t.new([time1, time2])

std::vector

Values based on std::vector are mapped to an enumerable. You cannot access the elements randomly, though

vector_of_time.each do |value|
  ...
end

if you want to access the elements by their index, convert the vector to an array first

array = vector_of_time.to_a

Finally, the clear and insert methods allow you to modify the vector. Moreover, vectors can be initialized from arrays of compatible types.

Enums

They are accessed by the symbol name. The returned value is a symbol, and the enum can be assigned from an integer value, a string or a symbol. For instance

namespace base {
    namespace actuators {
        enum DRIVE_MODE {
            DM_UNKNOWN = -1,
            DM_PWM = 0,
            DM_SPEED = 1,
            DM_POSITION = 2
        }

        struct Command {
            DRIVE_MODE mode;
        };
    }
}

can be accessed with

command.mode = 'DM_PWM'
command.mode => :DM_PWM # beware, this is a Ruby symbol !!!

Opaque Types (ADVANCED SUBJECT)

The opaque types are manipulated, on the Ruby side, through their intermediate type. For instance, if a property of type base::Vector3d is created, it will be accessed as a structure of the corresponding type

Customization on the Ruby side

So, we now know how to manipulate the C++ types from within Ruby. However, the types are pretty ‘plain’. I.e., they offer no nice ways to be manipulated.

There are two ways to customize the C++ to Ruby mapping:

  • either by adding methods to the values. For instance, one could define the #+ method on ‘/base/Time’, which would add two times together
  • or by specifying convertions between some Ruby class and the oroGen-registered types. For instance, converting between /base/Time and the builting Time class in Ruby

To add methods to an oroGen-registered type, one does

Typelib.specialize '/base/Time' do
    def +(other_time)
       # add the two times together and return the result
    end
end

To allow convertion between a Ruby class and an oroGen-registered type, one does

# If we get a /base/Time, convert it to Ruby's Time class
Typelib.convert_to_ruby '/base/Time' do |value|
    Time.at(value.seconds, value.microseconds)
end
# Tell Typelib that Time instances can be converted into /base/Time values
Typelib.convert_from_ruby Time, '/base/Time' do |value, typelib_type|
    result = typelib_type.new
    result.seconds      = value.tv_sec
    result.microseconds = value.tv_usec
    result
end

See the typelib_plugin.rb file from the base/types package as an example.

You can safely add a convert_to_ruby specification late in the development process, without breaking the scripts that were accessing the structures. Indeed, the converted value is wrapped in a way that makes possible accessing the structure fields (seconds and microseconds) or accessing it as a Time object.

Note when developing with Rock, it is recommended to put the typelib C++-to-Ruby customization code in a scripts/typelib.rb in the oroGen components. This file will get installed automatically along with the oroGen code, in a place where it gets found and loaded automatically by typelib. Thus, the convertion code is made available to all Ruby scripts that use orocos.rb/orogen

Direct Access to the Type Models

The ‘Types::’ export interface provides an easy-to-use access to the types. However, it one sometimes needs access to the “raw” typelib models.

In orocos.rb, all types loaded for the benefit of accessing oroGen components are saved in the singleton Typelib::Registry instance saved in Orocos.registry. Therefore, to get the typelib model for type ‘/base/XXX’, one needs to do

Orocos.registry.get("/base/XXX")