Skip to content

Commit

Permalink
Add actions tutorial page (#169)
Browse files Browse the repository at this point in the history
* Add actions tutorial page

Contains action server tutorial in Python and placeholders for other tutorials.

Signed-off-by: Jacob Perron <[email protected]>

* Break actions tutorials into separate pages

Signed-off-by: Jacob Perron <[email protected]>

* Fix errors and do some minor refactoring

Signed-off-by: Jacob Perron <[email protected]>

* Add Actions to tutorials page

Signed-off-by: Jacob Perron <[email protected]>

* Add doc8 exception for Actions tutorial pages

Specifically, ignore an error related to the :linenos: directive.

Signed-off-by: Jacob Perron <[email protected]>

* Add 'Writing an Action Client' Python tutorial

I've included code snippets with highlighting for clarity.

Signed-off-by: Jacob Perron <[email protected]>

* Fix typo

Signed-off-by: Jacob Perron <[email protected]>

* Refactor action server tutorial to have similar style

Signed-off-by: Jacob Perron <[email protected]>

* Minor refactor

Signed-off-by: Jacob Perron <[email protected]>

* Address review

Signed-off-by: Jacob Perron <[email protected]>
  • Loading branch information
jacobperron authored May 21, 2019
1 parent 6b3c2ff commit e06f465
Show file tree
Hide file tree
Showing 16 changed files with 750 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ install:
script:
- make html 2> stderr.log
- cat stderr.log
- doc8 --ignore D001 --ignore-path build
- doc8 --ignore D001 --ignore-path build --ignore-path source/Tutorials/Actions
# ignore D000 in action tutorials to allow for :linenos:
- doc8 --ignore D000 --ignore D001 --ignore-path build source/Tutorials/Actions
# fail the build for any stderr output
- if [ -s "stderr.log" ]; then false; fi
notifications:
Expand Down
1 change: 1 addition & 0 deletions source/Tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Basic
Tutorials/Rosidl-Tutorial.rst
Tutorials/New-features-in-ROS-2-interfaces-(msg-srv)
Tutorials/Defining-custom-interfaces-(msg-srv)
Tutorials/Actions
Tutorials/Eclipse-Oxygen-with-ROS-2-and-rviz2
Tutorials/Building-ROS-2-on-Linux-with-Eclipse-Oxygen
Tutorials/Building-Realtime-rt_preempt-kernel-for-ROS-2
Expand Down
54 changes: 54 additions & 0 deletions source/Tutorials/Actions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Actions
=======

.. contents:: Table of Contents
:depth: 2
:local:

About
-----

Actions are a form of asynchronous communication in ROS.
*Action clients* send goal requests to *action servers*.
*Action servers* send goal feedback and results to *action clients*.
For more detailed information about ROS actions, please refer to the `design article <http://design.ros2.org/articles/actions.html>`__.

This document contains a list of tutorials related to actions.
For reference, after completing all of the tutorials you should expect to have a ROS package that looks like the package `action_tutorials <https://github.com/ros2/demos/tree/master/action_tutorials>`__.

Prequisites
-----------

- `Install ROS (Dashing or later) <../Installation>`

- `Install colcon <https://colcon.readthedocs.org>`__

- Setup a workspace and create a package named ``action_tutorials``:

Linux / OSX:

.. code-block:: bash
mkdir -p action_ws/src
cd action_ws/src
ros2 pkg create action_tutorials
Windows:

.. code-block:: bash
mkdir -p action_ws\src
cd action_ws\src
ros2 pkg create action_tutorials
Tutorials
---------

.. toctree::
:maxdepth: 1

Actions/Creating-an-Action
Actions/Writing-an-Action-Server-CPP
Actions/Writing-an-Action-Client-CPP
Actions/Writing-an-Action-Server-Python
Actions/Writing-an-Action-Client-Python
94 changes: 94 additions & 0 deletions source/Tutorials/Actions/Creating-an-Action.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Creating an Action
==================

In this tutorial we look how to define an action in a ROS package.

Make sure you have satisfied all `prequisites <../Actions>`.

Defining an Action
------------------

Just like in ROS 1, actions are defined in ``.action`` files of the form:

.. code-block:: bash
# Request
---
# Result
---
# Feedback
An action definition is made up of three message definitions separated by ``---``.
An instance of an action is typically referred to as a *goal*.
A *request* message is sent from an action client to an action server initiating a new goal.
A *result* message is sent from an action server to an action client when a goal is done.
*Feedback* messages are periodically sent from an action server to an action client with updates about a goal.

Say we want to define a new action "Fibonacci" for computing the `Fibonacci sequence <https://en.wikipedia.org/wiki/Fibonacci_number>`__.

First, create a directory ``action`` in our ROS package.
With your favorite editor, add the file ``action/Fibonacci.action`` with the following content:

.. code-block:: bash
int32 order
---
int32[] sequence
---
int32[] partial_sequence
The goal request is the ``order`` of the Fibonacci sequence we want to compute, the result is the final ``sequence``, and the feedback is the ``partial_sequence`` computed so far.

Building an Action
------------------

Before we can use the new Fibonacci action type in our code, we must pass the definition to the rosidl code generation pipeline.
This is accomplished by adding the following lines to our ``CMakeLists.txt``:

.. code-block:: cmake
find_package(action_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"action/Fibonacci.action"
DEPENDENCIES action_msgs
)
We should also add the required dependencies to our ``package.xml``:

.. code-block:: xml
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
Note, we need to depend on ``action_msgs`` since action definitions include additional metadata (e.g. goal IDs).

We should now be able to build the package containing the "Fibonacci" action definition:

.. code-block:: bash
# Change to the root of the workspace (ie. action_ws)
cd ../..
# Build
colcon build
We're done!

By convention, action types will be prefixed by their package name and the word ``action``.
So when we want to refer to our new action, it will have the full name ``action_tutorials/action/Fibonacci``.

We can check that our action built successfully with the command line tool:

.. code-block:: bash
# Source our workspace
# On Windows: call install/setup.bat
. install/setup.bash
# Check that our action definition exists
ros2 action show action_tutorials/Fibonacci
You should see the Fibonacci action definition printed to the screen.
4 changes: 4 additions & 0 deletions source/Tutorials/Actions/Writing-an-Action-Client-CPP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Writing an Action Client (C++)
==============================

Coming soon.
172 changes: 172 additions & 0 deletions source/Tutorials/Actions/Writing-an-Action-Client-Python.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
Writing an Action Client (Python)
=================================

In this tutorial, we look at implementing an action client in Python.

Make sure you have satisfied all `prequisites <../Actions>`.

Sending a Goal
--------------

Let's get started!

To keep things simple, we'll scope this tutorial to a single file.
Open a new file, let's call it ``fibonacci_action_client.py``, and add the following boilerplate code:

.. literalinclude:: client_0.py
:language: python
:linenos:
:lines: 1,3,6-11,13-22

We've defined a class ``FibonacciActionClient`` that is a subclass of ``Node``.
The class is initialized by calling the ``Node`` constructor, naming our node "fibonacci_action_client":

.. literalinclude:: client_0.py
:language: python
:lines: 11

After the class defintion, we define a function ``main()`` that initializes ROS and creates an instance of our ``FibonacciActionClient`` node.
Finally, we call ``main()`` in the entry point of our Python program.

You can try running the program:

.. code-block:: bash
python3 fibonacci_action_client.py
It doesn't do anything interesting...yet.

Let's import and create an action client using the custom action definition from the previous tutorial on `Creating an Action <Creating-an-Action>`.

.. literalinclude:: client_0.py
:language: python
:linenos:
:lines: 1-12
:emphasize-lines: 2,5,12

At line 12 we create an ``ActionClient`` by passing it three arguments:

1. a ROS node to add the action client to: ``self``.
2. the type of the action: ``Fibonacci``.
3. the action name: ``'fibonacci'``.

Our action client will be able to communicate with action servers of the same action name and type.

Now let's tell the action client to send a goal.
Add a new method ``send_goal()``:

.. literalinclude:: client_1.py
:language: python
:linenos:
:lines: 8-20
:emphasize-lines: 7-13

We create a new Fibonacci goal message and assign a sequence order.
Before sending the goal message, we must wait for an action server to become available.
Otherwise, a potential action server may miss the goal we're sending.

Finally, call the ``send_goal`` method with a value:

.. literalinclude:: client_1.py
:language: python
:linenos:
:lines: 27-32
:emphasize-lines: 6

Let's test our action client by first running an action server built in the tutorial on `Writing an Action Server (Python) <Writing-an-Action-Server-Python>`:

.. code-block:: bash
ros2 run action_tutorials fibonacci_action_server.py
In another terminal, run the action client:

.. code-block:: bash
python3 fibonacci_action_client.py
Tada! You should see messages printed by the action server as it successfully executes the goal.

Getting Feedback
----------------

Our action client can send goals.
Nice!
But it would be great if we could get some feedback about the goals we send from the action server.
Easy, let's write a callback function for feedback messsages:

.. literalinclude:: client_1.py
:language: python
:linenos:
:lines: 8-24
:emphasize-lines: 15-17

In the callback we get the feedback portion of the message and print the ``partial_sequence`` field to the screen.

We need to register the callback with the action client.
This is achieved by passing the callback to the action client when we send a goal:

.. literalinclude:: client_2.py
:language: python
:linenos:
:lines: 14-21
:emphasize-lines: 7

You'll notice at this point that our action client is not printing any feedback.
This is because we're missing a call to `rclpy.spin() <http://docs.ros2.org/latest/api/rclpy/api/init_shutdown.html#rclpy.spin>`_ in order to process callbacks on our node.
Let's add it:

.. literalinclude:: client_2.py
:language: python
:linenos:
:lines: 27-34
:emphasize-lines: 8

We're all set. If we run our action client, you should see feedback being printed to the screen.

Getting a Result
----------------

So we can send a goal, but how do we know when it is completed?
We can get the result information with a couple steps.
First, we need to get a goal handle for the goal we sent.
Then, we can use the goal handle to request the result.

The `ActionClient.send_goal_async() <http://docs.ros2.org/latest/api/rclpy/api/actions.html#rclpy.action.client.ActionClient.send_goal_async>`_ method returns a future to a goal handle.
Let's register a callback for when the future is complete:

.. literalinclude:: client_3.py
:language: python
:linenos:
:lines: 8-27
:emphasize-lines: 13-20


Note, the future is completed when an action server accepts or rejects the goal request.
We can actually check to see if the goal was rejected and return early since we know there will be no result:

.. literalinclude:: client_3.py
:language: python
:linenos:
:lines: 26-33
:emphasize-lines: 4-8

Now that we've got a goal handle, we can use it to request the result with the method `get_result_async() <http://docs.ros2.org/latest/api/rclpy/api/actions.html#rclpy.action.client.ClientGoalHandle.get_result_async>`_.
Similar to sending the goal, we will get a future that will complete when the result is ready.
Let's register a callback just like we did for the goal response:

.. literalinclude:: client_3.py
:language: python
:linenos:
:lines: 26-42
:emphasize-lines: 10-17

In the callback, we log the result sequence and shutdown ROS for a clean exit.

With an action server running in a separate terminal, go ahead and try running our Fibonacci action client!

.. code-block:: bash
python3 fibonacci_action_client.py
You should see logged messages for the goal being accepted, feedback, and the final result.
4 changes: 4 additions & 0 deletions source/Tutorials/Actions/Writing-an-Action-Server-CPP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Writing an Action Server (C++)
==============================

Coming soon.
Loading

0 comments on commit e06f465

Please sign in to comment.