When using Syskit, task contexts are directly imported from their oroGen models. Only task contexts that have been developped using oroGen (i.e. all task contexts coming from Rock) can currently be integrated in Rock. The general issue here is that, for the system management layer to work, a model of the components needs to be available and deployed instances of these components should be described as well.
This page is first going to describe the mapping from oroGen models to Syskit models. It will then go on with ways in which you can extend these models to add runtime code, custom configuration code and additional modelling (such as data services) to existing task contexts.
Modelling
The Syskit layer then generates a Ruby class that is a subclass of Syskit::TaskContext for each of the oroGen components it finds. Throught this class, one has access to all the runtime code execution features of Roby itself. See Roby’s own documentation for more information
All task contexts get the following task arguments:
- conf is the configuration that should be applied to the task
- orocos_name is the name of the deployed orocos task that is used to run this task context.
At runtime, Roby events are used to represent all state transitions except the one to and from PRE_OPERATIONAL. The RTT TaskContext state transitions are mapped to the following events:
- success: transition to STOPPED
- exception: transition to EXCEPTION
- fatal_error: transition to FATAL_ERROR
- start: transition to RUNNING
- runtime_error: transition to RUNTIME_ERROR
fatal_error and exception are both forwarded to failed. runtime_error is not by default.
Additionally, events are created for each of the custom task state, with their names converted to lower_snake_case. If substates are defined (i.e. a custom exception state defined with exception_states), the corresponding event is forwarded to the corresponding main state event.
For instance, a task defined with
name "xsens_imu"
task_context "Task" do
exception_states :IO_ERROR
end
will have an io_error event that is forwarded to the exception event.
Configurations
Task context configurations, in Syskit, are all managed through the configuration directory mechanisms. The configuration files are looked for in config/orogen/ and must follow the model_name.yml convention. For instance, configurations for xsens_imu::Task are stored in config/orogen/xsens_imu::Task.yml.
The configurations are then selected when designing subsystems by listing the configuration sections that should be applied, i.e. the “default, fast_sampling” configuration will apply the values stored in the default section and override these values by the fast_sampling section. The way this selection should be done is detailed in the subsystem design section.
Extending the Model
If a file in models/orogen/ exists, that has the same name than the oroGen project but with a .rb extension, this file is going to be loaded after the corresponding oroGen project is loaded and after the subclasses of Roby::Task have been created. This is commonly used to declare that the task context provides some service, but can also be used to extend these classes with custom events, event handlers, …
Generation template for such a file can be generated for you by Syskit. run
syskit gen orogen name_of_orogen_project
This will create models/orogen/name_of_orogen_project and the class declarations for each of the task currently existing in this project.
To extend the task’s functionality or models, “reopen” the task context class to add some information to it:
class OroGen::XsensImu::Task
# Do some declarations there
end
Note the concept of doing “class X .. end” like this to add something to an already existing class is called class reopening and is a standard feature of the Ruby programming language.
We’ll now see some use for this: how to link data services to task contexts and how to add code that is going to be executed at runtime when the task context is running.
Providing Data Services
Task contexts can provide data services. However, since they are concrete and not abstract, their list of ports is predetermined (or almost, we’ll see that later), and therefore the way the “provides” stanza works is different.
Therefore, when a task context provides a service, it is required that that the component already has all the ports listed in the service. For instance, for a task context defined in oroGen with:
name "xsens_imu"
task_context "Task" do
output_port "orientation_samples", "base/samples/RigidBodyState"
...
end
then,
OroGen::XsensImu::Task.provides Rock::Services::Orientation
will work fine. Errors are generated if no such ports exists or if multiple ports match the specification. If multiple ports match the required port type, then the system model code tries to match the port name. If an exact match is found, the ambiguity is considered to be resolved. Otherwise, one has to provide an explicit port mapping:
OroGen::XsensImu::Task.provides Rock::Services::Orientation,
"orientation_samples" => "port_name_on_the_task"
Extending with standard Roby execution features
Composition models are subclasses of Syskit::TaskContext which is itself a grandchild of Roby::Task. As such, much more can be done using the runtime code execution features of Roby.
For instance, to add a poll handler to the same task context, one would add the following code:
class OroGen::XsensImu::Task
poll do
# Do some polling
end
end
Moreover, one can add, at the Roby level, a method that is going to be called at component’s configuration time before the component’s own configureHook gets called. This feature is commonly used to add task arguments to the model, and use it to configure the task in cases where a configuration parameter depends on the general context (i.e. cannot be statically written in a configuration file). Do not forget to call super there, as the application of the task’s configuration files is done in #configure as well. For instance:
class OroGen::CorridorPlanner::Task
argument :start_point
argument :target_point
def configure
super
orocos_task.start_point = self.start_point
orocos_task.target_point = self.target_point
end
end