-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add ros2action package Contains ros2cli command 'action' with verbs: list and show. The list verb lists action names for any running action servers and action clients. The show verb prints the definition for a given action type. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add 'info' verb to action command Prints a list of node names that have an action client or server for a given action name. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Use None as argument to test node Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add TODOs to move action query functions to rclpy (and rcl_action) The tool shouldn't need to know details about the implementation of actions. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add dependency to rclpy Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add 'send_goal' verb to action command Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Migrate message utility functions to rosidl_runtime_py Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Make use of rclpy functions Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Fix lint Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Fix tests Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Fix test Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add autocompletion to verbs Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Update year Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Expand and validate action name This also has the side-effect of making the forward slash optional for the action name. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Print goal ID when sendind a goal Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Cancel goal on SIGINT Wrapped send goal logic in try-finally clause. This ensures that any active goal will be canceled before the CLI command terminates and also ensure that the ROS node is shutdown. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Fix typos Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Change maintainer Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Move try-except to verb Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Catch expected exceptions only Signed-off-by: Jacob Perron <jacob@openrobotics.org>
- Loading branch information
1 parent
07640db
commit a463030
Showing
15 changed files
with
733 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?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>ros2action</name> | ||
<version>0.6.3</version> | ||
<description> | ||
The action command for ROS 2 command line tools. | ||
</description> | ||
<maintainer email="jacob@openrobotics.org">Jacob Perron</maintainer> | ||
<license>Apache License 2.0</license> | ||
|
||
<author email="jacob@openrobotics.org">Jacob Perron</author> | ||
|
||
<depend>rclpy</depend> | ||
<depend>ros2cli</depend> | ||
|
||
<exec_depend>action_msgs</exec_depend> | ||
<exec_depend>ament_index_python</exec_depend> | ||
<exec_depend>rosidl_runtime_py</exec_depend> | ||
|
||
<test_depend>ament_copyright</test_depend> | ||
<test_depend>ament_flake8</test_depend> | ||
<test_depend>ament_pep257</test_depend> | ||
<test_depend>python3-pytest</test_depend> | ||
<test_depend>test_msgs</test_depend> | ||
|
||
<export> | ||
<build_type>ament_python</build_type> | ||
</export> | ||
</package> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# 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. | ||
|
||
import os | ||
|
||
from ament_index_python import get_resource | ||
from ament_index_python import get_resources | ||
from ament_index_python import has_resource | ||
|
||
import rclpy.action | ||
from rclpy.expand_topic_name import expand_topic_name | ||
from rclpy.validate_full_topic_name import validate_full_topic_name | ||
from ros2cli.node.direct import DirectNode | ||
|
||
|
||
def _is_action_status_topic(topic_name, action_name): | ||
return action_name + '/_action/status' == topic_name | ||
|
||
|
||
def get_action_clients_and_servers(*, node, action_name): | ||
action_clients = [] | ||
action_servers = [] | ||
|
||
expanded_name = expand_topic_name(action_name, node.get_name(), node.get_namespace()) | ||
validate_full_topic_name(expanded_name) | ||
|
||
node_names_and_ns = node.get_node_names_and_namespaces() | ||
for node_name, node_ns in node_names_and_ns: | ||
# Construct fully qualified name | ||
node_fqn = '/'.join(node_ns) + node_name | ||
|
||
# Get any action clients associated with the node | ||
client_names_and_types = rclpy.action.get_action_client_names_and_types_by_node( | ||
node, | ||
node_name, | ||
node_ns, | ||
) | ||
for client_name, client_types in client_names_and_types: | ||
if client_name == expanded_name: | ||
action_clients.append((node_fqn, client_types)) | ||
|
||
# Get any action servers associated with the node | ||
server_names_and_types = rclpy.action.get_action_server_names_and_types_by_node( | ||
node, | ||
node_name, | ||
node_ns, | ||
) | ||
for server_name, server_types in server_names_and_types: | ||
if server_name == expanded_name: | ||
action_servers.append((node_fqn, server_types)) | ||
|
||
return (action_clients, action_servers) | ||
|
||
|
||
def get_action_names_and_types(*, node): | ||
return rclpy.action.get_action_names_and_types(node) | ||
|
||
|
||
def get_action_names(*, node): | ||
action_names_and_types = get_action_names_and_types(node=node) | ||
return [n for (n, t) in action_names_and_types] | ||
|
||
|
||
def get_action_types(package_name): | ||
if not has_resource('packages', package_name): | ||
raise LookupError('Unknown package name') | ||
try: | ||
content, _ = get_resource('rosidl_interfaces', package_name) | ||
except LookupError: | ||
return [] | ||
interface_names = content.splitlines() | ||
# TODO(jacobperron) this logic should come from a rosidl related package | ||
# Only return actions in action folder | ||
return list(sorted({ | ||
n[7:-7] | ||
for n in interface_names | ||
if n.startswith('action/') and n[-7:] in ('.idl', '.action')})) | ||
|
||
|
||
def get_all_action_types(): | ||
all_action_types = {} | ||
for package_name in get_resources('rosidl_interfaces'): | ||
action_types = get_action_types(package_name) | ||
if action_types: | ||
all_action_types[package_name] = action_types | ||
return all_action_types | ||
|
||
|
||
def get_action_path(package_name, action_name): | ||
action_types = get_action_types(package_name) | ||
if action_name not in action_types: | ||
raise LookupError('Unknown action type') | ||
prefix_path = has_resource('packages', package_name) | ||
# TODO(jacobperron) this logic should come from a rosidl related package | ||
return os.path.join(prefix_path, 'share', package_name, 'action', action_name + '.action') | ||
|
||
|
||
def action_name_completer(prefix, parsed_args, **kwargs): | ||
"""Callable returning a list of action names.""" | ||
with DirectNode(parsed_args) as node: | ||
return get_action_names(node=node) | ||
|
||
|
||
def action_type_completer(**kwargs): | ||
"""Callable returning a list of action types.""" | ||
action_types = [] | ||
for package_name, action_names in get_all_action_types().items(): | ||
for action_name in action_names: | ||
action_types.append( | ||
'{package_name}/{action_name}'.format_map(locals())) | ||
return action_types | ||
|
||
|
||
class ActionTypeCompleter: | ||
"""Callable returning a list of action types.""" | ||
|
||
def __init__(self, *, action_name_key=None): | ||
self.action_name_key = action_name_key | ||
|
||
def __call__(self, prefix, parsed_args, **kwargs): | ||
if self.action_name_key is None: | ||
return action_type_completer() | ||
|
||
action_name = getattr(parsed_args, self.action_name_key) | ||
with DirectNode(parsed_args) as node: | ||
names_and_types = get_action_names_and_types(node=node) | ||
for n, t in names_and_types: | ||
if n == action_name: | ||
return t | ||
return [] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# 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 ros2cli.command import add_subparsers | ||
from ros2cli.command import CommandExtension | ||
from ros2cli.verb import get_verb_extensions | ||
|
||
|
||
class ActionCommand(CommandExtension): | ||
"""Various action related sub-commands.""" | ||
|
||
def add_arguments(self, parser, cli_name): | ||
self._subparser = parser | ||
# Get verb extensions and let them add their arguments and sub-commands | ||
verb_extensions = get_verb_extensions('ros2action.verb') | ||
add_subparsers(parser, cli_name, '_verb', verb_extensions, required=False) | ||
|
||
def main(self, *, parser, args): | ||
if not hasattr(args, '_verb'): | ||
# In case no verb was passed | ||
self._subparser.print_help() | ||
return 0 | ||
|
||
extension = getattr(args, '_verb') | ||
|
||
# Call the verb's main method | ||
return extension.main(args=args) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# 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 ros2cli.plugin_system import PLUGIN_SYSTEM_VERSION | ||
from ros2cli.plugin_system import satisfies_version | ||
|
||
|
||
class VerbExtension: | ||
""" | ||
The extension point for 'action' verb extensions. | ||
The following properties must be defined: | ||
* `NAME` (will be set to the entry point name) | ||
The following methods must be defined: | ||
* `main` | ||
The following methods can be defined: | ||
* `add_arguments` | ||
""" | ||
|
||
NAME = None | ||
EXTENSION_POINT_VERSION = '0.1' | ||
|
||
def __init__(self): | ||
super(VerbExtension, self).__init__() | ||
satisfies_version(PLUGIN_SYSTEM_VERSION, '^0.1') | ||
|
||
def add_arguments(self, parser, cli_name): | ||
pass | ||
|
||
def main(self, *, args): | ||
raise NotImplementedError() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# 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. | ||
|
||
import rclpy | ||
from ros2action.api import action_name_completer | ||
from ros2action.api import get_action_clients_and_servers | ||
from ros2action.verb import VerbExtension | ||
from ros2cli.node.direct import DirectNode | ||
|
||
|
||
class InfoVerb(VerbExtension): | ||
"""Print information about an action.""" | ||
|
||
def add_arguments(self, parser, cli_name): | ||
arg = parser.add_argument( | ||
'action_name', | ||
help="Name of the ROS action to get info (e.g. '/fibonacci')") | ||
arg.completer = action_name_completer | ||
parser.add_argument( | ||
'-t', '--show-types', action='store_true', | ||
help='Additionally show the action type') | ||
parser.add_argument( | ||
'-c', '--count', action='store_true', | ||
help='Only display the number of action clients and action servers') | ||
|
||
def main(self, *, args): | ||
with DirectNode(args) as node: | ||
try: | ||
action_clients, action_servers = get_action_clients_and_servers( | ||
node=node, | ||
action_name=args.action_name, | ||
) | ||
except (ValueError, rclpy.exceptions.InvalidTopicNameException) as e: | ||
raise RuntimeError(e) | ||
|
||
print('Action: {}'.format(args.action_name)) | ||
print('Action clients: {}'.format(len(action_clients))) | ||
if not args.count: | ||
for client_name, client_types in action_clients: | ||
if args.show_types: | ||
types_formatted = ', '.join(client_types) | ||
print(' {client_name} [{types_formatted}]'.format_map(locals())) | ||
else: | ||
print(' {client_name}'.format_map(locals())) | ||
print('Action servers: {}'.format(len(action_servers))) | ||
if not args.count: | ||
for server_name, server_types in action_servers: | ||
if args.show_types: | ||
types_formatted = ', '.join(server_types) | ||
print(' {server_name} [{types_formatted}]'.format_map(locals())) | ||
else: | ||
print(' {server_name}'.format_map(locals())) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# 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 ros2action.api import get_action_names_and_types | ||
from ros2action.verb import VerbExtension | ||
from ros2cli.node.strategy import DirectNode | ||
|
||
|
||
class ListVerb(VerbExtension): | ||
"""Output a list of action names.""" | ||
|
||
def add_arguments(self, parser, cli_name): | ||
parser.add_argument( | ||
'-t', '--show-types', action='store_true', | ||
help='Additionally show the action type') | ||
parser.add_argument( | ||
'-c', '--count-actions', action='store_true', | ||
help='Only display the number of actions discovered') | ||
|
||
def main(self, *, args): | ||
with DirectNode(args) as node: | ||
action_names_and_types = get_action_names_and_types(node=node) | ||
|
||
if args.count_actions: | ||
print(len(action_names_and_types)) | ||
return | ||
|
||
for name, types in action_names_and_types: | ||
if args.show_types: | ||
types_formatted = ', '.join(types) | ||
print('{name} [{types_formatted}]'.format_map(locals())) | ||
else: | ||
print('{name}'.format_map(locals())) |
Oops, something went wrong.