Client call to service from another ROS2 package hangs
I am trying to write a client for a service defined in a separate ROS2 package, but it is hanging waiting for the service to become available.
The other package is a servo package for an AWS DeepRacer car, found here. In my workspace, I git clone
this package and build it, then it works just fine. I run this package in one terminal, and then I can run another terminal and call one of the package's services directly:
ros2 service call /servo_pkg/get_led_state deepracer_interfaces_pkg/srv/GetLedCtrlSrv
This call to the service returns as expected.
So now I want to create my own package called gg_led_pkg
with a single client to call this get_led_state
service based on a timer every 5 seconds. I am using a ReentrantCallbackGroup
for the client and the timer, with a MultiThreadedExecutor
to spin the node. I set up my setup.py
and my package.xml
files, dependencies are fine, and everything builds. The client executable runs, but it hangs when trying to call the service.
What am I doing wrong? Is it not possible to call a service from a client defined in a different package but in the same workspace?
Here is the output from my client call
~/environment/deepracerws $ ros2 run ggled_pkg client
[INFO] [1655305042.750526039] [ledminimalclient_async]: Beginning demo, end with CTRL-C
[INFO] [1655305047.741162292] [ledminimalclient_async]: Client sending request
[INFO][1655305052.741094414] [ledminimalclient_async]: Client sending request
And so on and so on...
My client code:
import sys
from time import sleep
from threading import Thread
#import the two service interfaces we need to use
from deepracer_interfaces_pkg.srv import SetLedCtrlSrv, GetLedCtrlSrv
# this imports the ROS2 Client Library for Python
import rclpy
# the main object for ROS nodes
from rclpy.node import Node
# import a multi-threaded executor to be used when spinning the node.
# This allows "parallel" execution of threads
# but the python GIL is still present (so not truly parallel)
from rclpy.executors import MultiThreadedExecutor
# the re-entrant callback group allows the executor to schedule and execute the group's callbacks
# in any way it sees fit. This is not necessarily thread safe
from rclpy.callback_groups import ReentrantCallbackGroup
class LedMinimalClientAsync(Node):
def __init__(self, client_cb_group, timer_cb_group, manual_call):
# first initialize
super().__init__('led_minimal_client_async')
self.client_cb_group = client_cb_group
self.timer_cb_group = timer_cb_group
self.manual_call = manual_call
# create a CLI interface for this getter method
# we set the client to use the GetLedCtrlSrv defined in the deepracer interfaces package
# we define the name as get_led_ctrl_srv
# we define a callback group here (going to make it reentrant)
self.get_led_status = self.create_client(GetLedCtrlSrv,
'get_led_ctrl_srv',
callback_group=self.client_cb_group)
# # wait for the client to become available (this is where it is locking)
# while not self.get_led_status.wait_for_service(timeout_sec=1.0):
# self.get_logger().info('service not available, waiting again...')
# make a simple request payload, which is empty
self.req = GetLedCtrlSrv.Request()
def start(self):
'''a method to call the server request outside the __init__()'''
# if we are not making manual calls to the server
# then this is false and will use the timer to make calls every second
if not self.manual_call:
self.call_timer = self.create_timer(5,
self.timer_cb,
callback_group=self.timer_cb_group)
def call_srv(self, delay: float = 1):
'''
this method is called manually and imposes a delay with sleep.
Then is calls the private method _call_srv() that actually makes the service call
'''
sleep(delay)
self._call_srv()
def _call_srv(self):
'''
this is the method that actually makes the service call
'''
self.get_logger().info('Client sending request')
# is this a synchronous request?
self.reponse = self.get_led_status.call(self.req)
self.get_logger().info('Client received response ' + self.reponse)
# not sure if we need this one of the synchronous one
# self.future = self.get_led_status.call_async(self.req)
# self.get_logger().info('Client received response ' + self.reponse)
def timer_cb(self):
'''
if a timer is used to make the service call, and therefore not a manual call
then this is the method used. The timer will handle the wait period
'''
self._call_srv()
# def _call_srv_async(self):
# '''
# this is the ansynchronous call to the service, making a future object
# This future object can be called by an external method like so:
# if minimal_client.future.done():
# response = minimal_client.future.result()
# '''
# self.future = self.get_led_status.call_async(self.req)
def call_srv_manually(client_node):
'''
this is a way to have the client call the service manually
'''
client_node.call_srv()
client_node.get_logger().info('Test finished successfully.\n')
def run(client_cb_group, timer_cb_group, manual_call):
'''
the main function for running the various pieces of the code
'''
# initialize the ros2 client library for python
rclpy.init()
# instatiate a client node with the callback groups (default to None)
client_node = LedMinimalClientAsync(client_cb_group, timer_cb_group, manual_call)
# make a multithreaded executor to spin up parallel threads
executor = MultiThreadedExecutor()
executor.add_node(client_node)
# make a thread to call the service manually. This currently isn't used
call_thread = Thread(target=call_srv_manually, args=(client_node, ), daemon=True)
# if there is a manual call to the service, then
if manual_call:
call_thread.start()
# added this else case for moving the callback outside the __init__ definition
else:
client_node.start()
try:
print("")
client_node.get_logger().info('Beginning demo, end with CTRL-C')
executor.spin()
except KeyboardInterrupt:
client_node.get_logger().info('KeyboardInterrupt, shutting down.\n')
client_node.destroy_node()
rclpy.shutdown()
if manual_call:
call_thread.join()
def main(args=None):
# make a single instance of a re-entrant callback group,
# assign it to both the timer and the client node
# let the timer callback make the requests to the server node, instead of manual
cb_group = ReentrantCallbackGroup()
run(client_cb_group=cb_group, timer_cb_group=cb_group, manual_call=False)
if __name__ == '__main__':
main()
I am using ROS2 Foxy
on an Ubuntu 20.04 x86
machine (AWS Robomaker)
Asked by brieb on 2022-06-15 12:27:47 UTC
Comments