Compositions
- Definition
- Building the Compositions' Network
- Exporting ports on Compositions
- Configuration
- Subclassing Compositions
- Common Patterns
In the system modelling, compositions are what bind components together: they define, for a specific task, behaviour or subsystem (depending from which side of robotics you come from), what components are needed and how these components should be connected together to perform the required function.
Definition
Compositions are classed, defined with
class ModelName < Syskit::Composition
...
end
Naming Convention compositions are by convention defined in the
AppName::Compositions
module, and are saved in
models/compositions/name_of_composition.rb
. For instance, a
Localization::GlobalPoseEstimator
composition in the CommonModels
bundle would be saved
in models/compositions/localization/global_pose_estimator.rb
and the full class
name would be CommonModels::Compositions::Localization::GlobalPoseEstimator
.
Generation A template for a new composition as well as its related unit tests, following Syskit's naming conventions and file system structure, can be generated with
syskit gen cmp namespace/name_of_composition
for instance
syskit gen cmp localization/global_pose_estimator
Building the Compositions' Network
The main statements in a composition are add
and connect
. From the
basics chapter:
module SyskitBasics
module Compositions
class ArmCartesianControlWdls < Syskit::Composition
add OroGen.cart_ctrl_wdls.WDLSSolver, as: 'twist2joint_velocity'
add OroGen.cart_ctrl_wdls.CartCtrl, as: 'position2twist'
add CommonModels::Devices::Gazebo::Model, as: 'arm'
end
end
end
The add
statement adds a child to the composition's network. Once defined,
children are referred to with the ${child_name}_child
syntax, as e.g.
position2twist_child
. A child's port is by calling ${port_name}_port
on the
child. Children need to be given a name (which should reflect the child's
role). Obviously, these children need to be connected together. This is done
with connect_to
:
module SyskitBasics
module Compositions
class ArmCartesianControlWdls < Syskit::Composition
add OroGen.cart_ctrl_wdls.WDLSSolver, as: 'twist2joint_velocity'
add OroGen.cart_ctrl_wdls.CartCtrl, as: 'position2twist'
add CommonModels::Devices::Gazebo::Model, as: 'arm'
position2twist_child.ctrl_out_port.
connect_to twist2joint_velocity_child.desired_twist_port
twist2joint_velocity_child.solver_output_port.
connect_to arm_child.joints_cmd_port
end
end
end
Note models used within the composition need to be loaded at the toplevel
of the composition file, before the composition definition. orogen models with
the using_task_library
statement, other models by require
'ing the file that
defines it.
Modifiers on the children have to be added to the return value of the add
statement. The
with_conf
statement
for components for instance would be added like this:
add(OroGen.cart_ctrl_wdls.WDLSSolver, as: 'twist2joint_velocity').
with_conf('default', 'high_velocity')
The result of this composition definition can be checked graphically using the syskit ide
:
syskit ide models/compositions/arm_cartesian_control_wdls.rb
Exporting ports on Compositions
Within the networks of a Syskit system, compositions can be used whenever a Rock component is. This is made possible by the ability to give ports to compositions.
However, since compositions are not "active components", but only combination
of other components, these ports are actually ports that come from the
composition's children. This is done by exporting a child's port
onto the composition interface. The following snippet creates a port named
command
onto the composition, that is in fact the position2twist
's port of
the same name:
export position2twist_child.command_port
If desired, the composition port may have a different name than the component port using the as:
argument:
export position2twist_child.command_port,
as: 'position_command`
In the IDE, an exported port is shown on the composition interface, and linked to the composition's child port that defines it.
TODO: add figure
Note since compositions can be used as composition children, a composition's exported port can be itself exported in a parent compositions, and so on and so forth.
Configuration
In the same way than compositions do not really have ports, but only "export" the ports of its children, compositions do not have configurations by themselves. Instead, configurations are defined as a list of configurations for the children.
For instance:
module SyskitBasics
module Compositions
class ArmCartesianControlWdls < Syskit::Composition
add OroGen.cart_ctrl_wdls.WDLSSolver, as: 'twist2joint_velocity'
...
conf 'high_speed',
twist2joint_velocity_child => ['default', 'high_sampling_rate']
end
end
end
Unlike what is possible with task contexts, one cannot "merge" configurations on top of each other (i.e. only one configuration can be selected at a time). Moreover, the configuration names are not verified at declaration time.
Subclassing Compositions
Compositions can subclass each other, in which case they of course inherit
their children, exported ports and connections. This is done by subclassing
the composition model class instead of Syskit::Composition
, e.g.
class Root < Syskit::Composition
end
class Submodel < Root
end
At this stage of the chapter, this would be only useful to create a "core" network that is refined in subclasses. We will see momentarily that it has also its usefulness in the reusable networks and later in building coordination code
Common Patterns
Forwarding Arguments from Composition to Children
This is the pattern we have seen and used in the
basics section. The
goal is to allow for arguments on the composition to be passed to its children.
The general mechanism is to pass from(:parent_task).name_of_argument
in place
of the actual argument. This can only be used for a whole argument. It cannot be
used as e.g. a value in a hash or array.
For instance (from the Basics chapter):
module SyskitBasics
module Compositions
class ArmCartesianConstantControlWdls < Syskit::Composition
argument :setpoint
add(ArmCartesianConstantCommandGenerator, as: 'command').
with_arguments(setpoint: from(:parent_task).setpoint)
...
end
end
end
A component's configuration, specified with the with_conf
specifier, is really only
syntactic sugar on top of the with_arguments
call. One can therefore pass configuration
from parent to child: add(...).with_arguments(conf: from(:parent_task).conf)
.
Handling Single-short Ports
Rock's defaults are tuned for components that continuously generate and process data. Some components, for instance task or motion planning components, are sometimes implemented as one short. That is, the component will rarely generate a sample.
The problem with the integration of such components is that one would need to ensure that port-to-port connections are established, and that receiving components are ready to read the data 1, before letting the source generate the data. It would be a lot of synchronization burden.
Instead, one tells the framework to remember the last written sample, and push this sample through new connections once they are established. To use this method, one must declare that the last written value on a port should be kept. Change the output port definition to read
output_port('trajectory', '/base/Trajectory')
.keep_last_written_value(true)
Then, in the composition's connect_to
statements, add init: true
. An
init: true
connection without the keep_last_written_value
will have no
effect, and will currently do so silently.
planner_child.trajectory_port
.connect_to controller_child.trajectory_port, init: true