Skip to content

Commit

Permalink
finally finished the syskit_basics section
Browse files Browse the repository at this point in the history
  • Loading branch information
doudou committed Jun 29, 2017
1 parent 8a7322f commit fec777f
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 84 deletions.
34 changes: 18 additions & 16 deletions source/syskit_basics/composition.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ $ syskit gen cmp arm_cartesian_control_wdls

<div class="panel panel-info" markdown="1">
<div class="panel-heading" markdown="1">
#### OroGen packages in Syskit
#### OroGen packages in Syskit {#orogen}
</div>
<div class="panel-body" markdown="1">
As described in our [brief introduction](index.html), oroGen packages are
Expand Down Expand Up @@ -178,14 +178,14 @@ file as follows (`-rgazebo` is necessary because we are importing models from
<iframe width="853" height="480" src="https://www.youtube.com/embed/hTBYnlc0J3Q?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
</div>

We may now start adding connections in the composition definition. The following
code snippets will only reproduce the composition definition itself, omitting
what is around it. The `as` arguments when adding composition elements create an
accessor for the children. The children ports are then accessible with a `_port`
accessor. For instance, the `ctrl_out` port of the `CartCtrl` component is
accessed with `position2twist_child.ctrl_out_port`.
We may now start adding connections in the composition definition. The `as`
arguments when adding composition elements create an accessor for the children.
The children ports are then accessible with a `_port` accessor. For instance,
the `ctrl_out` port of the `CartCtrl` component is accessed with
`position2twist_child.ctrl_out_port`.

Let's connect that one to the `WDLSSolver` twist input, and the WDLSSolver command to the actual arm command input:
Let's connect that one to the `WDLSSolver` twist input, and the WDLSSolver
command to the actual arm command input:

~~~ruby
class ArmCartesianControlWdls < Syskit::Composition
Expand All @@ -212,13 +212,13 @@ arm_child.joints_status_port.
~~~

However, the cartesian position feedback is not directly provided by the Gazebo
model. Fortunately, the `control/orogen/robot_frames` component does the
joint-to-cartesian conversion.
model. Fortunately, the `control/orogen/robot_frames` project provides components
to do the joint-to-cartesian conversion.

Let's add it to our workspace [in the same way we added
`control/orogen/cart_ctrl_wdls`](#add_package), import it in the composition
file with `using_task_library` and finally add it to the composition. You must
restart the IDE after this.
file with `using_task_library` and reload the models in the IDE.
Then, finally add it to the composition.

~~~ruby
# This is in bundles/common_models
Expand All @@ -233,16 +233,18 @@ module SyskitBasics
add OroGen::CartCtrlWdls::WDLSSolver, as: 'twist2joint_velocity'
add OroGen::CartCtrlWdls::CartCtrl, as: 'position2twist'
add CommonModels::Devices::Gazebo::Model, as: 'arm'
add OroGen::RobotFrames::SingleChainProducer, as: 'joint2pose'
add OroGen::RobotFrames::SingleChainPublisher, as: 'joint2pose'

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
arm_child.joints_status_port.
connect_to joint2pose_child.joints_samples_port
connect_to twist2joint_velocity_child.joint_status_port
arm_child.joints_status_port.
connect_to joint2pose_child.joints_samples_port
joint2pose_child.tip_pose_port.
connect_to position2twist_child.cartesian_status_port
connect_to position2twist_child.cartesian_status_port
end
end
end
Expand All @@ -259,7 +261,7 @@ by **exporting** the command port on the composition interface. This is done
with the `export` keyword, in the composition class context:

~~~ruby
export position2twist.command_child
export position2twist_child.command_port
~~~

In the IDE, this is represented as a port on the composition, and a connection
Expand Down
29 changes: 14 additions & 15 deletions source/syskit_basics/constant_generator.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ sort_info: 30
generator. The first generator one usually creates is a constant one, creating an
interface to send a constant command from Syskit into a control network.

We will create the command generator on this page, and then [deploy and run
it](deployment.html).
We will create the command generator now, then bind the result [to the simulation](devices.html)
and finally [deploy and run it](deployment.html).

We will also learn how to write unit tests.

Expand All @@ -21,14 +21,15 @@ For the command, let's generate a single constant command. It will allow us to
move the arm tip from one point to another, expressed in the cartesian frame.

`bundles/common_models` provides `Compositions::ConstantGenerator`, a generic
component that produces a constant command of a certain type. This generator is
implementation of a component that periodically emits a value of a certain type. This generator is
a `Syskit::RubyTaskContext`, which is like an oroGen component that is part of
the Syskit process. It's useful to create very simple components without having
to get through the whole oroGen step, or stub existing components.
to get through the whole oroGen step. It is also used in tests to stub existing components.

`ConstantGenerator` is generic, so it's rarely used as-is. We will instead
create a `Compositions::ArmCartesianConstantCommandGenerator` generator specialized
for our purposes.
`ConstantGenerator` is generic, so it's not used as-is. One instead
creates a specific component for the task at hand. In our case, we'll create
the `Compositions::ArmCartesianConstantCommandGenerator` generator specialized for
our purposes.

First, generate the new model

Expand Down Expand Up @@ -71,8 +72,7 @@ page.
## Improving the arguments

The format of the 'values' is actually awkward for a command generator. Let's
provide better arguments. We need to update `#values` when position or
orientation are set.
provide a better `setpoint` argument that is transformed to set `values`.

~~~ruby
class ArmCartesianConstantCommandGenerator < ...
Expand Down Expand Up @@ -122,9 +122,8 @@ its correctness.

Each time we ran `syskit gen`, files were created both in `models/` and
`test/`. The default Syskit test framework is the spec implementation of
[minitest](https://github.com/seattlerb/minitest)

Let's now write the tests for our generator.
[minitest](https://github.com/seattlerb/minitest). Let's now adapt these
templates for our generator.

Start by deleting the boilerplate test from `test/compositions/test_arm_cartesian_constant_command_generator.rb`.

Expand Down Expand Up @@ -240,8 +239,8 @@ end

## Creating the ArmCartesianConstantControlWdls Composition

Whenever a control loop is created, it is common to also create both the corresponding
constant generator (if it does not exist) and the corresponding composition.
Now that we have a generator, let's bind it to our control loop to have
something that can move and hold our arm to a given pose.

We'll create the composition now.

Expand Down Expand Up @@ -414,7 +413,7 @@ module SyskitBasics
Types.base.commands.Joints.new(
time: Time.at(0),
names: joint_names,
values: joint_commands)]
elements joint_commands)]
end

def values
Expand Down
88 changes: 56 additions & 32 deletions source/syskit_basics/deployment.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ All oroGen components have a default deployment scheme of one component per
process. The triggering mechanism does not have a default, but the
`port_driven` scheme (where the component is triggered whenever data is
received on its inputs) is a very common scheme. If you look into the
`cart_ctrl_wdls` package, you would see that
`cart_ctrl_wdls` package, you would see that it's what the package's components
use.

One usually starts with the defaults defined in the oroGen file. We therefore
only have left to give a name to the components Syskit is using. This is done
in the robot's config file with:
{: #requires}

~~~ruby
Robot.requires do
Expand All @@ -53,7 +55,8 @@ end
Now that we've required our toplevel profile in the robot configuration file,
we can inspect our app using the IDE by simply providing the `-rgazebo` flag.
Only new files, not yet required by the config file, must be specified on the
command line. {: .callout .callout-info}
command line.
{: .callout .callout-info}

## Component configuration {#configuration}

Expand Down Expand Up @@ -88,7 +91,7 @@ $ syskit gen orogenconf cart_ctrl_wdls::WDLSSolver
$ syskit gen orogenconf cart_ctrl_wdls::CartCtrl
exists config/orogen/
create config/orogen/cart_ctrl_wdls::CartCtrl.yml
$ syskit gen orogenconf robot_frames::SingleChainProducer
$ syskit gen orogenconf robot_frames::SingleChainPublisher
exists config/orogen/
create config/orogen/robot_frames::SingleChainPublisher.yml
~~~
Expand All @@ -98,7 +101,7 @@ Let's look at them one by one, to see what needs to actually be configured.

- `cart_ctrl_wdls::WDLSSolver`. There are robot model parameters as well as tip
and root. The former should be extracted from the SDF configuration, but the
tip and root can be set. The only algorithm parameter that does not seem to
tip and root have to be set. The only algorithm parameter that does not seem to
have a sane default is the `lambda` parameter. The documentation mentions 0.1
has a known-good parameter for some arm, let's pick that and keep in mind
that it might be wrong.
Expand All @@ -107,13 +110,18 @@ Let's look at them one by one, to see what needs to actually be configured.
represent a twist, which means that we only need to update the velocity
and angular velocity fields. Let's set `0.1` in linear and `2.deg` in angular
(the `.deg` suffix will convert a degree value in radians).
- `robot_frames::SingleChainPublisher` only has robot model and tip/root parameters.
- `robot_frames::SingleChainPublisher` only has robot model that we will
extract from the SDF model, and the tip/root parameters that have to be set

**Note** in order to ensure consistency between the tip and root parameters of
`SingleChainPublisher` and `WDLSSolver`, another way to handle this is to make
them parameters of the compositions.
{: .callout .callout-info}

The root and tip in our case are the base and hand of the robot. The link names are prefixed
with the model name (here, `ur10`) so `root` should
with the model name (here, `ur10_fixed`) so `root` should
be `ur10_fixed::ur10::base` and `tip` should be `ur10_fixed::ur10::wrist_3`. One can find this out by looking at
the SDF file - which is downloaded by Gazebo in
`$HOME/.gazebo/models/ur10/ur10.sdf`. Alternatively, the chain can be
the SDF file. Alternatively, the chain can be
inspected using the `rock-transformer` tool:

<div class="fluid-video">
Expand Down Expand Up @@ -204,9 +212,10 @@ define 'arm_cartesian_constant_control',
We now need to modify the oroGen component models to use the robot argument.
Since oroGen components are auto-generated by Syskit, there's a special
mechanism to allow modifying the generated classes after the fact. When Syskit
loads an oroGen, and after it has created the task classes, it will attempt to
loads an oroGen package, and after it has created the task classes, it will attempt to
load a file named like the project in `models/orogen/`. In the case of the
`robot_frames` project, this would be `models/orogen/robot_frames.rb`.
{: #orogen_extension_files}

In the IDE, when displaying a task model under the `OroGen` namespace, a project
that has no associated extension file in `models/orogen/` has the following message:
Expand Down Expand Up @@ -244,7 +253,7 @@ class OroGen::CartCtrlWdls::WDLSSolver
def configure
super # call super as described in the template

properties.robot_model = robot.sdf_model.make_root.to_s
properties.robot_model = robot.sdf_model.make_root.to_xml_string
properties.robot_model_format = :ROBOT_MODEL_SDF
end
end
Expand All @@ -258,7 +267,7 @@ class OroGen::RobotFrames::SingleChainPublisher
def configure
super # call super as described in the template

properties.robot_model = robot.sdf_model.make_root.to_s
properties.robot_model = robot.sdf_model.make_root.to_xml_string
properties.robot_model_format = :ROBOT_MODEL_SDF
end
end
Expand All @@ -279,19 +288,23 @@ argument :robot
# @param [String] property_name the name of the property being
# verified
# @raise [ArgumentError] if the link is not in the model
def verify_link_in_model(link_name)
if !robot.sdf_model.each_link.any? { |link| link.name == link_name }
raise ArgumentError, "link name '#{link_name}' is not a link of the robot model. Available links: #{robot.sdf_model.each_link.map(&:name).sort.join(", ")}"
def verify_link_in_model(model, link_name)
if !model.each_link.any? { |link| link.full_name == link_name }
raise ArgumentError, "link name '#{link_name}' is not a link of the robot model. Existing links: #{model.each_link.map(&:full_name).sort.join(", ")}"
end
end

def configure
super

verify_link_in_model(properties.root)
verify_link_in_model(properties.tip)
# Extract the model into its own SDF document
as_root = robot.sdf_model.make_root
# And get the new model
model = as_root.each_model.first
verify_link_in_model(model, properties.root)
verify_link_in_model(model, properties.tip)

properties.robot_model = robot.sdf_model.make_root.to_s
properties.robot_model = as_root.to_xml_string
properties.robot_model_format = :ROBOT_MODEL_SDF
end
~~~
Expand Down Expand Up @@ -344,13 +357,20 @@ describe WDLSSolver do
<link name="tip_test" />
</model>
EOSDF
@profile = flexmock(sdf_model: SDF::Element.from_xml_string(xml))
# We don't really need a full profile object, only an object
# that provides a Model object from a '#sdf_model' attribute
#
# Let's fake one using flexmock. Flexmock is loaded as part
# Of Syskit's test harness
#
# https://github.com/doudou/flexmock
@profile = flexmock(sdf_model: SDF::Model.from_xml_string(xml))
end

it "sets the robot model from its 'robot' argument" do
# Create a fake test configuration with valid root and tip
syskit_stub_conf WDLSSolver, 'default',
data: { 'root' => 'root_test', 'tip' => 'tip_test' }
data: { 'root' => 'test::root_test', 'tip' => 'test::tip_test' }
task = syskit_stub_deploy_and_configure(
WDLSSolver.with_arguments(robot: profile))
assert_equal "<sdf>#{profile.sdf_model.to_xml_string}</sdf>",
Expand All @@ -359,23 +379,23 @@ describe WDLSSolver do

it "raises if the root link does not exist" do
syskit_stub_conf WDLSSolver, 'default',
data: { 'root' => 'invalid', 'tip' => 'tip_test' }
data: { 'root' => 'invalid', 'tip' => 'test::tip_test' }
e = assert_raises(ArgumentError) do
syskit_stub_deploy_and_configure(
WDLSSolver.with_arguments(robot: profile))
end
assert_equal "link name 'invalid' is not a link of the robot model. Existing links: root_test, tip_test",
assert_equal "link name 'invalid' is not a link of the robot model. Existing links: test::root_test, test::tip_test",
e.message
end

it "raises if the tip link does not exist" do
syskit_stub_conf WDLSSolver, 'default',
data: { 'root' => 'root_test', 'tip' => 'invalid' }
data: { 'root' => 'test::root_test', 'tip' => 'invalid' }
e = assert_raises(ArgumentError) do
syskit_stub_deploy_and_configure(
WDLSSolver.with_arguments(robot: profile))
end
assert_equal "link name 'invalid' is not a link of the robot model. Existing links: root_test, tip_test",
assert_equal "link name 'invalid' is not a link of the robot model. Existing links: test::root_test, test::tip_test",
e.message
end
end
Expand Down Expand Up @@ -432,6 +452,7 @@ file. Syskit profiles are added in the action interface there, so that their
definitions are exported as an action with a `_def` suffix.

Let's modify the `actions` block in `config/robots/gazebo.rb` to have:
{: #main}

~~~ruby
Robot.actions do
Expand All @@ -440,8 +461,12 @@ Robot.actions do
end
~~~

**Note**: the profile is available in the actions block because [we've required it in the requires block](#requires)
{: .callout .callout-info}

After modifying the config file, the IDE needs to be quit and started again. We
can then look at the `Main` interface and see that it indeed has a `arm_cartesian_constant_control_def` action:
can then look at the `Main` interface and see that it indeed has `_dev` actions for
the devices in `Base`, and `_def` actions for the definitions in `ArmControl`.

![The Main interface with the arm control definition](media/main_action_with_arm_control_def.png)

Expand All @@ -456,13 +481,12 @@ rock-gazebo empty_world
~~~

Once started, the simulation can stay running regardless of whether the Syskit
app itself gets restarted. You can then start the app either on the command
line with
app itself gets restarted. You can then start the app using the IDE, and
send the command through the Syskit shell. The following video show you the process. Its goal
is to give you a feeling for Syskit's runtime workflow. Detailed explanations will come later [in the runtime part of this documentation](../syskit_runtime/index.html)

~~~
syskit run -rgazebo
~~~

Or, if you prefer, in the IDE as in the following video. Start then the action within the IDE.
<div class="fluid-video">
<iframe width="853" height="480" src="https://www.youtube.com/embed/wXYb7f_xNZo?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
</div>

TODO: video
Before we move on to the Syskit runtime aspects, [let's recap what we've just seen](recap.html){:.btn .btn-primary}
Loading

0 comments on commit fec777f

Please sign in to comment.