Skip to content

Commit

Permalink
Refactor get topic names and types (#4)
Browse files Browse the repository at this point in the history
* ros2topic: use rclpy utility

* ros2topic: fixup

* ros2topic: support multiple types

* ros2service: initial commit

* ros2topic: support no_demangle

* fix include order

* missed a commit

* ros2service: add pep257 tests

* fix echo to support multiple types

* improve shutdown behavior of call, add loop option

* address comments
  • Loading branch information
wjwwood committed Jun 17, 2017
1 parent ceb5929 commit 19c4b53
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 14 deletions.
22 changes: 22 additions & 0 deletions ros2service/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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>ros2service</name>
<version>0.0.0</version>
<description>
The service command for ROS 2 command line tools.
</description>
<maintainer email="william@osrfoundation.org">William Woodall</maintainer>
<license>Apache License 2.0</license>

<depend>rclpy</depend>
<depend>ros2cli</depend>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
45 changes: 45 additions & 0 deletions ros2service/ros2service/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2017 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 rclpy.topic_or_service_is_hidden import topic_or_service_is_hidden
from ros2cli.node.strategy import NodeStrategy


def get_service_names_and_types(*, node, include_hidden_services=False):
service_names_and_types = node.get_service_names_and_types()
if not include_hidden_services:
service_names_and_types = [
(n, t) for (n, t) in service_names_and_types
if not topic_or_service_is_hidden(n)]
return service_names_and_types


def get_service_names(*, node, include_hidden_services=False):
service_names_and_types = get_service_names_and_types(
node=node, include_hidden_services=include_hidden_services)
return [n for (n, t) in service_names_and_types]


class ServiceNameCompleter(object):
"""Callable returning a list of service names."""

def __init__(self, *, include_hidden_services_key=None):
self.include_hidden_services_key = include_hidden_services_key

def __call__(self, prefix, parsed_args, **kwargs):
with NodeStrategy(parsed_args) as node:
return get_service_names(
node=node,
include_hidden_services=getattr(
parsed_args, self.include_hidden_services_key))
Empty file.
38 changes: 38 additions & 0 deletions ros2service/ros2service/command/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2017 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 ServiceCommand(CommandExtension):
"""Various service related sub-commands."""

def add_arguments(self, parser, cli_name):
parser.add_argument(
'--include-hidden-services', action='store_true',
help='Consider hidden services as well')

# get verb extensions and let them add their arguments
verb_extensions = get_verb_extensions('ros2service.verb')
add_subparsers(parser, cli_name, '_verb', verb_extensions)

def main(self, *, parser, args):
# the attribute should always exist
# otherwise argparse should have exited
extension = getattr(args, '_verb')

# call the verb's main method
return extension.main(args=args)
44 changes: 44 additions & 0 deletions ros2service/ros2service/verb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2017 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(object):
"""
The extension point for 'service' 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()
93 changes: 93 additions & 0 deletions ros2service/ros2service/verb/call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2016-2017 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 argparse
import importlib
import json
import time

import rclpy
from ros2service.api import ServiceNameCompleter
from ros2service.verb import VerbExtension


class CallVerb(VerbExtension):
"""Call a service."""

def add_arguments(self, parser, cli_name):
arg = parser.add_argument(
'service_name',
help="Name of the ROS service to call to (e.g. '/add_two_ints')")
arg.completer = ServiceNameCompleter(
include_hidden_services_key='include_hidden_services')
parser.add_argument(
'service_type',
help="Type of the ROS service (e.g. 'std_srvs/Empty')")
parser.add_argument(
'values', nargs='?', default='{}',
help='Values to fill the service request with in JSON format ' +
'(e.g. {"a": 1, "b": 2}), ' +
'otherwise the service request will be published with default values')

def float_or_int(input_string):
try:
value = float(input_string)
except ValueError:
raise argparse.ArgumentTypeError("'%s' is not an integer or float" % input_string)
return value

parser.add_argument(
'-r', '--repeat', metavar='N', type=float_or_int,
help='Repeat the call every N seconds')

def main(self, *, args):
return requester(args.service_type, args.service_name, args.values, args.repeat)


def requester(service_type, service_name, values, repeat):
# TODO(wjwwood) this logic should come from a rosidl related package
try:
package_name, srv_name = service_type.split('/', 2)
except ValueError:
raise RuntimeError('The passed service type is invalid')
module = importlib.import_module(package_name + '.srv')
srv_module = getattr(module, srv_name)
values_dictionary = json.loads(values)

rclpy.init()

node = rclpy.create_node('requester_%s_%s' % (package_name, srv_name))

cli = node.create_client(srv_module, service_name)

request = srv_module.Request()
for field_name, field_value in values_dictionary.items():
field_type = type(getattr(request, field_name))
setattr(request, field_name, field_type(field_value))

while True:
print('requester: making request: %r\n' % request)
last_call = time.time()
cli.call(request)
cli.wait_for_future()
if cli.response is not None:
print('response:\n%r\n' % cli.response)
if repeat is None or not rclpy.ok():
break
time_until_next_period = (last_call + repeat) - time.time()
if time_until_next_period > 0:
time.sleep(time_until_next_period)

node.destroy_node()
rclpy.try_shutdown() # avoid duplicate shutdown from shutdown within cli.wait_for_future()
46 changes: 46 additions & 0 deletions ros2service/ros2service/verb/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2017 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.node.strategy import add_arguments
from ros2cli.node.strategy import NodeStrategy
from ros2service.api import get_service_names_and_types
from ros2service.verb import VerbExtension


class ListVerb(VerbExtension):
"""Output a list of available services."""

def add_arguments(self, parser, cli_name):
add_arguments(parser)
parser.add_argument(
'-t', '--show-types', action='store_true',
help='Additionally show the service type')
parser.add_argument(
'-c', '--count-services', action='store_true',
help='Only display the number of services discovered')

def main(self, *, args):
with NodeStrategy(args) as node:
service_names_and_types = get_service_names_and_types(
node=node, include_hidden_services=args.include_hidden_services)

if args.count_services:
print(len(service_names_and_types))
elif service_names_and_types:
for (service_name, service_types) in service_names_and_types:
msg = '{service_name}'
service_types_formatted = ', '.join(service_types)
if args.show_types:
msg += ' [{service_types_formatted}]'
print(msg.format_map(locals()))
39 changes: 39 additions & 0 deletions ros2service/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from setuptools import find_packages
from setuptools import setup

setup(
name='ros2service',
version='0.0.0',
packages=find_packages(exclude=['test']),
install_requires=['ros2cli'],
author='William Woodall',
author_email='william@osrfoundation.org',
maintainer='William Woodall',
maintainer_email='william@osrfoundation.org',
url='https://github.com/ros2/ros2cli/tree/master/ros2service',
download_url='https://github.com/ros2/ros2cli/releases',
keywords=[],
classifiers=[
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
],
description='The service command for ROS 2 command line tools.',
long_description="""\
The package provides the service command for the ROS 2 command line tools.""",
license='Apache License, Version 2.0',
test_suite='test',
entry_points={
'ros2cli.command': [
'service = ros2service.command.service:ServiceCommand',
],
'ros2cli.extension_point': [
'ros2service.verb = ros2service.verb:VerbExtension',
],
'ros2service.verb': [
'call = ros2service.verb.call:CallVerb',
'list = ros2service.verb.list:ListVerb',
],
}
)
20 changes: 20 additions & 0 deletions ros2service/test/test_copyright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 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 ament_copyright.main import main


def test_copyright():
rc = main(argv=['.', 'test'])
assert rc == 0, 'Found errors'
20 changes: 20 additions & 0 deletions ros2service/test/test_flake8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 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 ament_flake8.main import main


def test_flake8():
rc = main(argv=[])
assert rc == 0, 'Found errors'
20 changes: 20 additions & 0 deletions ros2service/test/test_pep257.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 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 ament_pep257.main import main


def test_pep257():
rc = main(argv=[])
assert rc == 0, 'Found code style errors / warnings'
Loading

0 comments on commit 19c4b53

Please sign in to comment.