Skip to content

Commit

Permalink
rclpy action examples (#222)
Browse files Browse the repository at this point in the history
* Copy rclpy action examples from #216

* Bump version of examples_rclpy_action to match other packages

* Consolidate composable action client example

* Add action client example that is not composable

* Wait for action server

* Restructure into separate packages for action client and action server examples

This package structure is consistent with examples for services and topics.

* Update API in action server examples

* Rename action server examples

Now the 'simplest' example is 'server.py'.

* Add action server example that is not composable

* Update setup.py

* Fix syntax

* Update action client examples to use goal handle API for the result

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Improve action client output and result handling

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Update action server examples

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Move action examples into Python packages

This avoid top-level module names from clashing when installed.

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Add action client cancel example

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Address review comments

* Update author
* Update copyright year
* Shutdown client example after result received

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* GoalResponse.ACCEPT_AND_EXECUTE -> GoalResponse.ACCEPT

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Fix client_cancel example

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Remove race from server_single_goal example

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Add action server example that defers the execution of an accepted goal

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Reduce the timer period for the client cancel example

This makes it easy to experiment with the scenario where a deferred goal is canceled prior to execution.

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Support canceling goals with non-composable action server example

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Add action server example that queues goals

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Address review

- Fix comment
- Add author tag to package.xml

Signed-off-by: Jacob Perron <jacob@openrobotics.org>
  • Loading branch information
jacobperron authored Mar 5, 2019
1 parent 2dbcf9f commit cfc9bc6
Show file tree
Hide file tree
Showing 18 changed files with 944 additions and 0 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from action_msgs.msg import GoalStatus
from example_interfaces.action import Fibonacci

import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node


class MinimalActionClient(Node):

def __init__(self):
super().__init__('minimal_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return

self.get_logger().info('Goal accepted :)')

self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)

def feedback_callback(self, feedback):
self.get_logger().info('Received feedback: {0}'.format(feedback.sequence))

def get_result_callback(self, future):
status = future.result().action_status
if status == GoalStatus.STATUS_SUCCEEDED:
self.get_logger().info('Goal succeeded! Result: {0}'.format(future.result().sequence))
else:
self.get_logger().info('Goal failed with status: {0}'.format(status))

# Shutdown after receiving a result
rclpy.shutdown()

def send_goal(self):
self.get_logger().info('Waiting for action server...')
self._action_client.wait_for_server()

goal_msg = Fibonacci.Goal()
goal_msg.order = 10

self.get_logger().info('Sending goal request...')

self._send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback)

self._send_goal_future.add_done_callback(self.goal_response_callback)


def main(args=None):
rclpy.init(args=args)

action_client = MinimalActionClient()

action_client.send_goal()

rclpy.spin(action_client)

action_client.destroy_node()


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2019 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from action_msgs.msg import GoalStatus
from example_interfaces.action import Fibonacci

import rclpy
from rclpy.action import ActionClient
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.node import Node
from rclpy.timer import WallTimer


class MinimalActionClient(Node):

def __init__(self):
super().__init__('minimal_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

def cancel_done(self, future):
cancel_response = future.result()
if len(cancel_response.goals_canceling) > 0:
self.get_logger().info('Goal successfully canceled')
else:
self.get_logger().info('Goal failed to cancel')

rclpy.shutdown()

def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return

self._goal_handle = goal_handle

self.get_logger().info('Goal accepted :)')

# Start a 2 second timer
self._timer = self.create_timer(2.0, self.timer_callback)

def feedback_callback(self, feedback):
self.get_logger().info('Received feedback: {0}'.format(feedback.sequence))

def timer_callback(self):
self.get_logger().info('Canceling goal')
# Cancel the goal
future = self._goal_handle.cancel_goal_async()
future.add_done_callback(self.cancel_done)

# Cancel the timer
self._timer.cancel()

def send_goal(self):
self.get_logger().info('Waiting for action server...')
self._action_client.wait_for_server()

goal_msg = Fibonacci.Goal()
goal_msg.order = 10

self.get_logger().info('Sending goal request...')

self._send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback)

self._send_goal_future.add_done_callback(self.goal_response_callback)


def main(args=None):
rclpy.init(args=args)

action_client = MinimalActionClient()

action_client.send_goal()

rclpy.spin(action_client)

action_client.destroy_node()


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2019 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from action_msgs.msg import GoalStatus

from example_interfaces.action import Fibonacci

import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node


def feedback_cb(logger, feedback):
logger.info('Received feedback: {0}'.format(feedback.sequence))


def main(args=None):
rclpy.init(args=args)

node = rclpy.create_node('minimal_action_client')

action_client = ActionClient(node, Fibonacci, 'fibonacci')

node.get_logger().info('Waiting for action server...')

action_client.wait_for_server()

goal_msg = Fibonacci.Goal()
goal_msg.order = 10

node.get_logger().info('Sending goal request...')

send_goal_future = action_client.send_goal_async(
goal_msg, feedback_callback=lambda feedback: feedback_cb(node.get_logger(), feedback))

rclpy.spin_until_future_complete(node, send_goal_future)

goal_handle = send_goal_future.result()

if not goal_handle.accepted:
node.get_logger().info('Goal rejected :(')
action_client.destroy()
node.destroy_node()
rclpy.shutdown()
return

node.get_logger().info('Goal accepted :)')

get_result_future = goal_handle.get_result_async()

rclpy.spin_until_future_complete(node, get_result_future)

status = get_result_future.result().action_status
if status == GoalStatus.STATUS_SUCCEEDED:
node.get_logger().info(
'Goal succeeded! Result: {0}'.format(get_result_future.result().sequence))
else:
node.get_logger().info('Goal failed with status code: {0}'.format(status))

action_client.destroy()
node.destroy_node()
rclpy.shutdown()


if __name__ == '__main__':
main()
26 changes: 26 additions & 0 deletions rclpy/actions/minimal_action_client/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
<name>examples_rclpy_minimal_action_client</name>
<version>0.6.1</version>
<description>Examples of minimal action clients using rclpy.</description>

<maintainer email="sloretz@openrobotics.org">Shane Loretz</maintainer>
<license>Apache License 2.0</license>

<author email="jacob@openrobotics.org">Jacob Perron</author>

<exec_depend>example_interfaces</exec_depend>
<exec_depend>rclpy</exec_depend>

<!-- These test dependencies are optional
Their purpose is to make sure that the code passes the linters -->
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
4 changes: 4 additions & 0 deletions rclpy/actions/minimal_action_client/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script-dir=$base/lib/examples_rclpy_minimal_action_client
[install]
install-scripts=$base/lib/examples_rclpy_minimal_action_client
37 changes: 37 additions & 0 deletions rclpy/actions/minimal_action_client/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from setuptools import setup

package_name = 'examples_rclpy_minimal_action_client'

setup(
name=package_name,
version='0.6.1',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
author='Jacob Perron',
author_email='jacob@openrobotics.org',
maintainer='Shane Loretz',
maintainer_email='sloretz@openrobotics.org',
keywords=['ROS'],
classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Topic :: Software Development',
],
description='Examples of action clients using rclpy.',
license='Apache License, Version 2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'client = ' + package_name + '.client:main',
'client_cancel = ' + package_name + '.client_cancel:main',
'client_not_composable = ' + package_name + '.client_not_composable:main',
],
},
)
Empty file.
Loading

0 comments on commit cfc9bc6

Please sign in to comment.