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.