Rock

the Robot Construction Kit

Quickstart

The following example uses orocos.rb’s functionality to manage processes. While it works only for deployments generated using oroGen, the rest of the functionality is available for any RTT component.

This page will go through the steps needed to write a startup/monitoring script for a system based on Orocos/RTT components. It assumes that the components are generated by the oroGen component generator (and will give snippets of the associated oroGen declarations).

First of all, every orocos.rb scripts need to start with

require 'orocos'
include Orocos

Orocos.initialize

It loads the orocos.rb library and initializes the CORBA communication layer.

Starting processes and setting up modules

Now, let’s assume that the deployments you have look like this:

deployment "lowlevel" do
  task('can', "can::Task").
  task("hbridge", "hbridge::Task")
  add_default_logger
end
deployment "imu" do
  task('imu', 'imu::XsensTask')
  add_default_logger
end
deployment "gps" do
  task('gps', 'dgps::Task')
  add_default_logger
end
deployment "hokuyo" do
  task('hokuyo', 'hokuyo::LaserAcquisition')
  add_default_logger
end
deployment "state_estimator" do
  task 'stateEstimator', 'state_estimator::StateEstimator'
  add_default_logger
end

I.e., you have 5 deployments (Unix binaries), with up to three components in them (add_default_logger adds a base::Logger component in the deployment). To use these deployments, one would do:

Orocos.run 'lowlevel', 'imu', 'gps', 'hokuyo', 'state_estimator' do
  # Here goes the supervision code
end

where the names given to Orocos.run are the deployment names, i.e. the name given to the “deployment” statement.

This starts all the processes, and wait for them to be ready to be used. The advantage of this method is that, whenever you leave the do … end block (for instance because of an error in the Ruby program), all the processes will be stopped properly.

Now, let’s configure and start the imu driver. To do that, you need to get a handle on the imu::XsensTask task that is declared in the imu deployment. You achieve this by using the task name – i.e. the name given to the ‘task’ statement:

Orocos.run 'lowlevel', 'imu', 'gps', 'hokuyo', 'state_estimator' do
  imu = Orocos.name_service.get 'imu'
  pose_estimator = Orocos.name_service.get 'poseEstimator'
  # From this point on, the 'imu' variable represents the XsensTask task
  # context, and can be used to manipulate it.
end

The value returned by Orocos.name_service.get is an instance of Orocos::TaskContext

The task name is the first argument of the ‘task’ statements in the deployment definitions:

  task('imu', 'imu::XsensTask')

Now, let’s look at the definition of the XsensTask task context.

task_context 'XsensTask' do
  needs_configuration
  fd_driven

  property('port', '/std/string', "").
    doc 'the device port'

  property('max_timeouts', 'int').
    doc 'number of consecutive timeouts after which an error is raised'

  output_port('imu_readings', '/base/IMUReading').
    doc 'provides timestamped IMUReading samples containing orientation and calibrated sensor values.'
end

We can see that the drivers needs to be configured (it starts in the PreOperational state and the ‘configure’ method must be called on it). Moreover, it has a string propery called ‘port’ that allows to specify what device file can be used to communicate with the IMU. In Ruby, the properties are simply read and written with

  puts imu.port # displays the current value of the port property
  imu.port = '/dev/ttyS1' # sets a new value for the port

Then, the configuration transition is done by calling #configure. Our script now looks like:

Orocos.run 'lowlevel', 'imu', 'gps', 'hokuyo', 'state_estimator' do
  imu = Orocos.name_service.get 'imu'

  imu.port = '/dev/ttyS1'
  imu.configure
  # From this point on, the IMU is ready to be used.
end

It can then be started with TaskContext#start:

Orocos.run 'lowlevel', 'imu', 'gps', 'hokuyo', 'state_estimator' do
  imu = Orocos.name_service.get 'imu'

  imu.port = '/dev/ttyS1'
  imu.configure
  imu.start
  # The IMU is configured and runs
end

Then, the actual state of the module can be checked with {rdoc_class: TaskContext#running?}, #error? and #fatal?. It can be manipulated with #configure, #start, #stop, #reset_error, and #cleanup

Connecting modules together

In our example, the state_estimator module is a Kalman filter that processes the IMU and GPS streams. We therefore need to connect the outputs of the imu and gps tasks to the corresponding inputs of the state estimator module.

To do that, we use the Orocos::OutputPort#connect_to method:

  imu = Orocos.name_service.get 'imu'
  gps = Orocos.name_service.get 'gps'
  state_estimator = Orocos.name_service.get 'state_estimator'
  
  imu.imu_readings.connect_to state_estimator.imu_readings
  gps.position_readings.connect_to state_estimator.position_readings

Advanced topics w.r.t. data connections are covered here.

Inspecting the module output

To read data that comes out of the module, one gets a reader object. This object allows to get samples out and access them from Ruby:

imu_reader = imu.imu_readings.reader

# Display samples that get out of the IMU
# See base/base/imu_readings.h for the definition of base::IMUReading
while true
  sleep 0.1
  if sample = imu_reader.read
    orientation = sample.orientation
    puts "[#{orientation.re.to_a.join(", ")}] #{orientation.im}"
  end
end

In the above code snippet, imu.imu_readings is a Orocos::OutputPort instance and imu_reader is a Orocos::OutputReader instance.

A complete description of the manipulation of ports can be found here

We’re done !

The complete script can be found here. Note that it can safely be interrupted with Ctrl+C: we use Orocos.run, and therefore the processes will be cleanly killed on interrupt.